From d2a1f96dbd95ce55dc95c43ad4a65b0dd3f26c58 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Sat, 6 Jul 2024 12:04:19 -0500 Subject: [PATCH 001/126] update to latest reedline commit (#13313) # Description Update to the latest reedline version. (don't ask me why libloading changed. `cargo update -p reedline` sometimes does weird things) # User-Facing Changes # Tests + Formatting # After Submitting /cc @YizhePKU (this include your reedline pwd change) --- Cargo.lock | 7 +++---- Cargo.toml | 3 +-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 72b7f72b3b..d4f759aed0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2382,9 +2382,9 @@ dependencies = [ [[package]] name = "libloading" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" +checksum = "e310b3a6b5907f99202fcdb4960ff45b93735d7c7d96b760fcff8db2dc0e103d" dependencies = [ "cfg-if", "windows-targets 0.52.5", @@ -4988,8 +4988,7 @@ dependencies = [ [[package]] name = "reedline" version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abf59e4c97b5049ba96b052cdb652368305a2eddcbce9bf1c16f9d003139eeea" +source = "git+https://github.com/nushell/reedline?branch=main#480059a3f52cf919341cda88e8c544edd846bc73" dependencies = [ "arboard", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 573ace795f..5701fe3958 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -197,7 +197,6 @@ nu-protocol = { path = "./crates/nu-protocol", version = "0.95.1" } nu-std = { path = "./crates/nu-std", version = "0.95.1" } nu-system = { path = "./crates/nu-system", version = "0.95.1" } nu-utils = { path = "./crates/nu-utils", version = "0.95.1" } - reedline = { workspace = true, features = ["bashisms", "sqlite"] } crossterm = { workspace = true } @@ -304,7 +303,7 @@ bench = false # To use a development version of a dependency please use a global override here # changing versions in each sub-crate of the workspace is tedious [patch.crates-io] -# reedline = { git = "https://github.com/nushell/reedline", branch = "main" } +reedline = { git = "https://github.com/nushell/reedline", branch = "main" } # nu-ansi-term = {git = "https://github.com/nushell/nu-ansi-term.git", branch = "main"} # Run all benchmarks with `cargo bench` From fa183b66699bbd9ed05f6b0a34a129cc981e93e0 Mon Sep 17 00:00:00 2001 From: Ian Manske Date: Sat, 6 Jul 2024 18:09:12 +0000 Subject: [PATCH 002/126] `help operators` refactor (#13307) # Description Refactors `help operators` so that its output is always up to date with the parser. # User-Facing Changes - The order of output rows for `help operators` was changed. - `not` is now listed as a boolean operator instead of a comparison operator. - Edited some of the descriptions for the operators. --- crates/nu-command/src/help/help_operators.rs | 401 +++++++------------ crates/nu-parser/src/parser.rs | 2 +- crates/nu-protocol/src/ast/expression.rs | 37 +- crates/nu-protocol/src/ast/operator.rs | 40 +- 4 files changed, 174 insertions(+), 306 deletions(-) diff --git a/crates/nu-command/src/help/help_operators.rs b/crates/nu-command/src/help/help_operators.rs index a7beee3dd5..12803fad8e 100644 --- a/crates/nu-command/src/help/help_operators.rs +++ b/crates/nu-command/src/help/help_operators.rs @@ -1,4 +1,5 @@ use nu_engine::command_prelude::*; +use nu_protocol::ast::{Assignment, Bits, Boolean, Comparison, Math, Operator}; #[derive(Clone)] pub struct HelpOperators; @@ -27,282 +28,148 @@ impl Command for HelpOperators { _input: PipelineData, ) -> Result { let head = call.head; - let op_info = generate_operator_info(); - let mut recs = vec![]; - - for op in op_info { - recs.push(Value::record( + let mut operators = [ + Operator::Assignment(Assignment::Assign), + Operator::Assignment(Assignment::PlusAssign), + Operator::Assignment(Assignment::AppendAssign), + Operator::Assignment(Assignment::MinusAssign), + Operator::Assignment(Assignment::MultiplyAssign), + Operator::Assignment(Assignment::DivideAssign), + Operator::Comparison(Comparison::Equal), + Operator::Comparison(Comparison::NotEqual), + Operator::Comparison(Comparison::LessThan), + Operator::Comparison(Comparison::GreaterThan), + Operator::Comparison(Comparison::LessThanOrEqual), + Operator::Comparison(Comparison::GreaterThanOrEqual), + Operator::Comparison(Comparison::RegexMatch), + Operator::Comparison(Comparison::NotRegexMatch), + Operator::Comparison(Comparison::In), + Operator::Comparison(Comparison::NotIn), + Operator::Comparison(Comparison::StartsWith), + Operator::Comparison(Comparison::EndsWith), + Operator::Math(Math::Plus), + Operator::Math(Math::Append), + Operator::Math(Math::Minus), + Operator::Math(Math::Multiply), + Operator::Math(Math::Divide), + Operator::Math(Math::Modulo), + Operator::Math(Math::FloorDivision), + Operator::Math(Math::Pow), + Operator::Bits(Bits::BitOr), + Operator::Bits(Bits::BitXor), + Operator::Bits(Bits::BitAnd), + Operator::Bits(Bits::ShiftLeft), + Operator::Bits(Bits::ShiftRight), + Operator::Boolean(Boolean::And), + Operator::Boolean(Boolean::Or), + Operator::Boolean(Boolean::Xor), + ] + .into_iter() + .map(|op| { + Value::record( record! { - "type" => Value::string(op.op_type, head), - "operator" => Value::string(op.operator, head), - "name" => Value::string(op.name, head), - "description" => Value::string(op.description, head), - "precedence" => Value::int(op.precedence, head), + "type" => Value::string(op_type(&op), head), + "operator" => Value::string(op.to_string(), head), + "name" => Value::string(name(&op), head), + "description" => Value::string(description(&op), head), + "precedence" => Value::int(op.precedence().into(), head), }, head, - )); - } + ) + }) + .collect::>(); - Ok(Value::list(recs, head).into_pipeline_data()) + operators.push(Value::record( + record! { + "type" => Value::string("Boolean", head), + "operator" => Value::string("not", head), + "name" => Value::string("Not", head), + "description" => Value::string("Negates a value or expression.", head), + "precedence" => Value::int(0, head), + }, + head, + )); + + Ok(Value::list(operators, head).into_pipeline_data()) } } -struct OperatorInfo { - op_type: String, - operator: String, - name: String, - description: String, - precedence: i64, +fn op_type(operator: &Operator) -> &'static str { + match operator { + Operator::Comparison(_) => "Comparison", + Operator::Math(_) => "Math", + Operator::Boolean(_) => "Boolean", + Operator::Bits(_) => "Bitwise", + Operator::Assignment(_) => "Assignment", + } } -fn generate_operator_info() -> Vec { - vec![ - OperatorInfo { - op_type: "Assignment".into(), - operator: "=".into(), - name: "Assign".into(), - description: "Assigns a value to a variable.".into(), - precedence: 10, - }, - OperatorInfo { - op_type: "Assignment".into(), - operator: "+=".into(), - name: "PlusAssign".into(), - description: "Adds a value to a variable.".into(), - precedence: 10, - }, - OperatorInfo { - op_type: "Assignment".into(), - operator: "++=".into(), - name: "AppendAssign".into(), - description: "Appends a list or a value to a variable.".into(), - precedence: 10, - }, - OperatorInfo { - op_type: "Assignment".into(), - operator: "-=".into(), - name: "MinusAssign".into(), - description: "Subtracts a value from a variable.".into(), - precedence: 10, - }, - OperatorInfo { - op_type: "Assignment".into(), - operator: "*=".into(), - name: "MultiplyAssign".into(), - description: "Multiplies a variable by a value.".into(), - precedence: 10, - }, - OperatorInfo { - op_type: "Assignment".into(), - operator: "/=".into(), - name: "DivideAssign".into(), - description: "Divides a variable by a value.".into(), - precedence: 10, - }, - OperatorInfo { - op_type: "Comparison".into(), - operator: "==".into(), - name: "Equal".into(), - description: "Checks if two values are equal.".into(), - precedence: 80, - }, - OperatorInfo { - op_type: "Comparison".into(), - operator: "!=".into(), - name: "NotEqual".into(), - description: "Checks if two values are not equal.".into(), - precedence: 80, - }, - OperatorInfo { - op_type: "Comparison".into(), - operator: "<".into(), - name: "LessThan".into(), - description: "Checks if a value is less than another.".into(), - precedence: 80, - }, - OperatorInfo { - op_type: "Comparison".into(), - operator: "<=".into(), - name: "LessThanOrEqual".into(), - description: "Checks if a value is less than or equal to another.".into(), - precedence: 80, - }, - OperatorInfo { - op_type: "Comparison".into(), - operator: ">".into(), - name: "GreaterThan".into(), - description: "Checks if a value is greater than another.".into(), - precedence: 80, - }, - OperatorInfo { - op_type: "Comparison".into(), - operator: ">=".into(), - name: "GreaterThanOrEqual".into(), - description: "Checks if a value is greater than or equal to another.".into(), - precedence: 80, - }, - OperatorInfo { - op_type: "Comparison".into(), - operator: "=~".into(), - name: "RegexMatch".into(), - description: "Checks if a value matches a regular expression.".into(), - precedence: 80, - }, - OperatorInfo { - op_type: "Comparison".into(), - operator: "!~".into(), - name: "NotRegexMatch".into(), - description: "Checks if a value does not match a regular expression.".into(), - precedence: 80, - }, - OperatorInfo { - op_type: "Comparison".into(), - operator: "in".into(), - name: "In".into(), - description: "Checks if a value is in a list or string.".into(), - precedence: 80, - }, - OperatorInfo { - op_type: "Comparison".into(), - operator: "not-in".into(), - name: "NotIn".into(), - description: "Checks if a value is not in a list or string.".into(), - precedence: 80, - }, - OperatorInfo { - op_type: "Comparison".into(), - operator: "starts-with".into(), - name: "StartsWith".into(), - description: "Checks if a string starts with another.".into(), - precedence: 80, - }, - OperatorInfo { - op_type: "Comparison".into(), - operator: "ends-with".into(), - name: "EndsWith".into(), - description: "Checks if a string ends with another.".into(), - precedence: 80, - }, - OperatorInfo { - op_type: "Comparison".into(), - operator: "not".into(), - name: "UnaryNot".into(), - description: "Negates a value or expression.".into(), - precedence: 0, - }, - OperatorInfo { - op_type: "Math".into(), - operator: "+".into(), - name: "Plus".into(), - description: "Adds two values.".into(), - precedence: 90, - }, - OperatorInfo { - op_type: "Math".into(), - operator: "++".into(), - name: "Append".into(), - description: "Appends two lists or a list and a value.".into(), - precedence: 80, - }, - OperatorInfo { - op_type: "Math".into(), - operator: "-".into(), - name: "Minus".into(), - description: "Subtracts two values.".into(), - precedence: 90, - }, - OperatorInfo { - op_type: "Math".into(), - operator: "*".into(), - name: "Multiply".into(), - description: "Multiplies two values.".into(), - precedence: 95, - }, - OperatorInfo { - op_type: "Math".into(), - operator: "/".into(), - name: "Divide".into(), - description: "Divides two values.".into(), - precedence: 95, - }, - OperatorInfo { - op_type: "Math".into(), - operator: "//".into(), - name: "FloorDivision".into(), - description: "Divides two values and floors the result.".into(), - precedence: 95, - }, - OperatorInfo { - op_type: "Math".into(), - operator: "mod".into(), - name: "Modulo".into(), - description: "Divides two values and returns the remainder.".into(), - precedence: 95, - }, - OperatorInfo { - op_type: "Math".into(), - operator: "**".into(), - name: "Pow ".into(), - description: "Raises one value to the power of another.".into(), - precedence: 100, - }, - OperatorInfo { - op_type: "Bitwise".into(), - operator: "bit-or".into(), - name: "BitOr".into(), - description: "Performs a bitwise OR on two values.".into(), - precedence: 60, - }, - OperatorInfo { - op_type: "Bitwise".into(), - operator: "bit-xor".into(), - name: "BitXor".into(), - description: "Performs a bitwise XOR on two values.".into(), - precedence: 70, - }, - OperatorInfo { - op_type: "Bitwise".into(), - operator: "bit-and".into(), - name: "BitAnd".into(), - description: "Performs a bitwise AND on two values.".into(), - precedence: 75, - }, - OperatorInfo { - op_type: "Bitwise".into(), - operator: "bit-shl".into(), - name: "ShiftLeft".into(), - description: "Shifts a value left by another.".into(), - precedence: 85, - }, - OperatorInfo { - op_type: "Bitwise".into(), - operator: "bit-shr".into(), - name: "ShiftRight".into(), - description: "Shifts a value right by another.".into(), - precedence: 85, - }, - OperatorInfo { - op_type: "Boolean".into(), - operator: "and".into(), - name: "And".into(), - description: "Checks if two values are true.".into(), - precedence: 50, - }, - OperatorInfo { - op_type: "Boolean".into(), - operator: "or".into(), - name: "Or".into(), - description: "Checks if either value is true.".into(), - precedence: 40, - }, - OperatorInfo { - op_type: "Boolean".into(), - operator: "xor".into(), - name: "Xor".into(), - description: "Checks if one value is true and the other is false.".into(), - precedence: 45, - }, - ] +fn name(operator: &Operator) -> String { + match operator { + Operator::Comparison(op) => format!("{op:?}"), + Operator::Math(op) => format!("{op:?}"), + Operator::Boolean(op) => format!("{op:?}"), + Operator::Bits(op) => format!("{op:?}"), + Operator::Assignment(op) => format!("{op:?}"), + } +} + +fn description(operator: &Operator) -> &'static str { + // NOTE: if you have to add an operator here, also add it to the operator list above. + match operator { + Operator::Comparison(Comparison::Equal) => "Checks if two values are equal.", + Operator::Comparison(Comparison::NotEqual) => "Checks if two values are not equal.", + Operator::Comparison(Comparison::LessThan) => "Checks if a value is less than another.", + Operator::Comparison(Comparison::GreaterThan) => { + "Checks if a value is greater than another." + } + Operator::Comparison(Comparison::LessThanOrEqual) => { + "Checks if a value is less than or equal to another." + } + Operator::Comparison(Comparison::GreaterThanOrEqual) => { + "Checks if a value is greater than or equal to another." + } + Operator::Comparison(Comparison::RegexMatch) => { + "Checks if a value matches a regular expression." + } + Operator::Comparison(Comparison::NotRegexMatch) => { + "Checks if a value does not match a regular expression." + } + Operator::Comparison(Comparison::In) => { + "Checks if a value is in a list, is part of a string, or is a key in a record." + } + Operator::Comparison(Comparison::NotIn) => { + "Checks if a value is not in a list, is not part of a string, or is not a key in a record." + } + 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::Minus) => "Subtracts two values.", + Operator::Math(Math::Multiply) => "Multiplies two values.", + Operator::Math(Math::Divide) => "Divides two values.", + Operator::Math(Math::Modulo) => "Divides two values and returns the remainder.", + Operator::Math(Math::FloorDivision) => "Divides two values and floors the result.", + Operator::Math(Math::Pow) => "Raises one value to the power of another.", + Operator::Boolean(Boolean::And) => "Checks if both values are true.", + Operator::Boolean(Boolean::Or) => "Checks if either value is true.", + Operator::Boolean(Boolean::Xor) => "Checks if one value is true and the other is false.", + Operator::Bits(Bits::BitOr) => "Performs a bitwise OR on two values.", + Operator::Bits(Bits::BitXor) => "Performs a bitwise XOR on two values.", + Operator::Bits(Bits::BitAnd) => "Performs a bitwise AND on two values.", + Operator::Bits(Bits::ShiftLeft) => "Bitwise shifts a value left by another.", + 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::MinusAssign) => "Subtracts a value from a variable.", + Operator::Assignment(Assignment::MultiplyAssign) => "Multiplies a variable by a value.", + Operator::Assignment(Assignment::DivideAssign) => "Divides a variable by a value.", + } } #[cfg(test)] diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index b274d77404..51935214f5 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -4950,7 +4950,7 @@ pub fn parse_math_expression( let mut expr_stack: Vec = vec![]; let mut idx = 0; - let mut last_prec = 1000000; + let mut last_prec = u8::MAX; let first_span = working_set.get_span_contents(spans[0]); diff --git a/crates/nu-protocol/src/ast/expression.rs b/crates/nu-protocol/src/ast/expression.rs index 040e140cab..906fce3fe1 100644 --- a/crates/nu-protocol/src/ast/expression.rs +++ b/crates/nu-protocol/src/ast/expression.rs @@ -27,42 +27,9 @@ impl Expression { } } - pub fn precedence(&self) -> usize { + pub fn precedence(&self) -> u8 { match &self.expr { - Expr::Operator(operator) => { - use super::operator::*; - // Higher precedence binds tighter - - match operator { - Operator::Math(Math::Pow) => 100, - Operator::Math(Math::Multiply) - | Operator::Math(Math::Divide) - | Operator::Math(Math::Modulo) - | Operator::Math(Math::FloorDivision) => 95, - Operator::Math(Math::Plus) | Operator::Math(Math::Minus) => 90, - Operator::Bits(Bits::ShiftLeft) | Operator::Bits(Bits::ShiftRight) => 85, - Operator::Comparison(Comparison::NotRegexMatch) - | Operator::Comparison(Comparison::RegexMatch) - | Operator::Comparison(Comparison::StartsWith) - | Operator::Comparison(Comparison::EndsWith) - | Operator::Comparison(Comparison::LessThan) - | Operator::Comparison(Comparison::LessThanOrEqual) - | Operator::Comparison(Comparison::GreaterThan) - | Operator::Comparison(Comparison::GreaterThanOrEqual) - | Operator::Comparison(Comparison::Equal) - | Operator::Comparison(Comparison::NotEqual) - | Operator::Comparison(Comparison::In) - | Operator::Comparison(Comparison::NotIn) - | Operator::Math(Math::Append) => 80, - Operator::Bits(Bits::BitAnd) => 75, - Operator::Bits(Bits::BitXor) => 70, - Operator::Bits(Bits::BitOr) => 60, - Operator::Boolean(Boolean::And) => 50, - Operator::Boolean(Boolean::Xor) => 45, - Operator::Boolean(Boolean::Or) => 40, - Operator::Assignment(_) => 10, - } - } + Expr::Operator(operator) => operator.precedence(), _ => 0, } } diff --git a/crates/nu-protocol/src/ast/operator.rs b/crates/nu-protocol/src/ast/operator.rs index 46484761bc..713c6c0060 100644 --- a/crates/nu-protocol/src/ast/operator.rs +++ b/crates/nu-protocol/src/ast/operator.rs @@ -68,6 +68,40 @@ pub enum Operator { Assignment(Assignment), } +impl Operator { + pub fn precedence(&self) -> u8 { + match self { + Self::Math(Math::Pow) => 100, + Self::Math(Math::Multiply) + | Self::Math(Math::Divide) + | Self::Math(Math::Modulo) + | Self::Math(Math::FloorDivision) => 95, + Self::Math(Math::Plus) | Self::Math(Math::Minus) => 90, + Self::Bits(Bits::ShiftLeft) | Self::Bits(Bits::ShiftRight) => 85, + Self::Comparison(Comparison::NotRegexMatch) + | Self::Comparison(Comparison::RegexMatch) + | Self::Comparison(Comparison::StartsWith) + | Self::Comparison(Comparison::EndsWith) + | Self::Comparison(Comparison::LessThan) + | Self::Comparison(Comparison::LessThanOrEqual) + | Self::Comparison(Comparison::GreaterThan) + | Self::Comparison(Comparison::GreaterThanOrEqual) + | Self::Comparison(Comparison::Equal) + | Self::Comparison(Comparison::NotEqual) + | Self::Comparison(Comparison::In) + | Self::Comparison(Comparison::NotIn) + | Self::Math(Math::Append) => 80, + Self::Bits(Bits::BitAnd) => 75, + Self::Bits(Bits::BitXor) => 70, + Self::Bits(Bits::BitOr) => 60, + Self::Boolean(Boolean::And) => 50, + Self::Boolean(Boolean::Xor) => 45, + Self::Boolean(Boolean::Or) => 40, + Self::Assignment(_) => 10, + } + } +} + impl Display for Operator { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { @@ -95,10 +129,10 @@ impl Display for Operator { Operator::Math(Math::Multiply) => write!(f, "*"), Operator::Math(Math::Divide) => write!(f, "/"), Operator::Math(Math::Modulo) => write!(f, "mod"), - Operator::Math(Math::FloorDivision) => write!(f, "fdiv"), + Operator::Math(Math::FloorDivision) => write!(f, "//"), Operator::Math(Math::Pow) => write!(f, "**"), - Operator::Boolean(Boolean::And) => write!(f, "&&"), - Operator::Boolean(Boolean::Or) => write!(f, "||"), + Operator::Boolean(Boolean::And) => write!(f, "and"), + Operator::Boolean(Boolean::Or) => write!(f, "or"), Operator::Boolean(Boolean::Xor) => write!(f, "xor"), Operator::Bits(Bits::BitOr) => write!(f, "bit-or"), Operator::Bits(Bits::BitXor) => write!(f, "bit-xor"), From 32db5d3aa31f7d0ac5494c78086d9ff01b3d2190 Mon Sep 17 00:00:00 2001 From: Maxim Zhiburt Date: Sat, 6 Jul 2024 22:47:39 +0300 Subject: [PATCH 003/126] Fix issue with head on separation lines (#13291) Hi there, Seems to work Though I haven't done a lot of testing. ![image](https://github.com/nushell/nushell/assets/20165848/c95aa8d4-a8d2-462c-afc9-35c48f8825f4) ![image](https://github.com/nushell/nushell/assets/20165848/1859dfe5-4a76-4776-a4e0-d3f53fc86862) ![image](https://github.com/nushell/nushell/assets/20165848/b46bb62b-a951-412d-b8fa-65cebcfbfed6) ![image](https://github.com/nushell/nushell/assets/20165848/bff0762e-42d4-41bf-b2c2-641c0436ca2e) ![image](https://github.com/nushell/nushell/assets/20165848/2c3c5664-9b90-44e4-befc-c250174cb630) close #13287 cc: @fdncred PS: Yessssss I do remember about emojie issue..... :disappointed: --- crates/nu-table/src/table.rs | 354 +++++++++++++++++++++++++++++------ 1 file changed, 300 insertions(+), 54 deletions(-) diff --git a/crates/nu-table/src/table.rs b/crates/nu-table/src/table.rs index bdaf7fb6c3..e1b1269874 100644 --- a/crates/nu-table/src/table.rs +++ b/crates/nu-table/src/table.rs @@ -12,13 +12,17 @@ use tabled::{ config::{AlignmentHorizontal, ColoredConfig, Entity, EntityMap, Position}, dimension::CompleteDimensionVecRecords, records::{ - vec_records::{CellInfo, VecRecords}, + vec_records::{Cell, CellInfo, VecRecords}, ExactRecords, PeekableRecords, Records, Resizable, }, }, settings::{ - formatting::AlignmentStrategy, object::Segment, peaker::Peaker, themes::ColumnNames, Color, - Modify, Padding, Settings, TableOption, Width, + formatting::AlignmentStrategy, + object::{Columns, Segment}, + peaker::Peaker, + themes::ColumnNames, + width::Truncate, + Color, Modify, Padding, Settings, TableOption, Width, }, Table, }; @@ -216,7 +220,7 @@ fn build_table( } let pad = indent.0 + indent.1; - let widths = maybe_truncate_columns(&mut data, &cfg.theme, termwidth, pad); + let widths = maybe_truncate_columns(&mut data, &cfg, termwidth, pad); if widths.is_empty() { return None; } @@ -269,20 +273,51 @@ fn set_indent(table: &mut Table, left: usize, right: usize) { fn set_border_head(table: &mut Table, with_footer: bool, wctrl: TableWidthCtrl) { if with_footer { let count_rows = table.count_rows(); + let last_row_index = count_rows - 1; + + // note: funnily last and row must be equal at this point but we do not rely on it just in case. + + let mut first_row = GetRow(0, Vec::new()); + let mut head_settings = GetRowSettings(0, AlignmentHorizontal::Left, None); + let mut last_row = GetRow(last_row_index, Vec::new()); + + table.with(&mut first_row); + table.with(&mut head_settings); + table.with(&mut last_row); + table.with( Settings::default() .with(wctrl) .with(StripColorFromRow(0)) .with(StripColorFromRow(count_rows - 1)) - .with(HeaderMove((0, 0), true)) - .with(HeaderMove((count_rows - 1 - 1, count_rows - 1), false)), + .with(MoveRowNext::new(0, 0)) + .with(MoveRowPrev::new(last_row_index - 1, last_row_index)) + .with(SetLineHeaders::new( + 0, + first_row.1, + head_settings.1, + head_settings.2.clone(), + )) + .with(SetLineHeaders::new( + last_row_index - 1, + last_row.1, + head_settings.1, + head_settings.2, + )), ); } else { + let mut row = GetRow(0, Vec::new()); + let mut row_opts = GetRowSettings(0, AlignmentHorizontal::Left, None); + + table.with(&mut row); + table.with(&mut row_opts); + table.with( Settings::default() .with(wctrl) .with(StripColorFromRow(0)) - .with(HeaderMove((0, 0), true)), + .with(MoveRowNext::new(0, 0)) + .with(SetLineHeaders::new(0, row.1, row_opts.1, row_opts.2)), ); } } @@ -324,11 +359,14 @@ impl TableOption, ColoredConfig> for max: self.max, strategy: self.cfg.trim, width: self.width, + do_as_head: self.cfg.header_on_border, } .change(rec, cfg, dim); } else if self.cfg.expand && self.max > total_width { Settings::new(SetDimensions(self.width), Width::increase(self.max)) .change(rec, cfg, dim) + } else { + SetDimensions(self.width).change(rec, cfg, dim); } } } @@ -337,6 +375,7 @@ struct TableTrim { width: Vec, strategy: TrimStrategy, max: usize, + do_as_head: bool, } impl TableOption, ColoredConfig> for TableTrim { @@ -346,6 +385,44 @@ impl TableOption, ColoredConfig> for cfg: &mut ColoredConfig, dims: &mut CompleteDimensionVecRecords<'_>, ) { + // we already must have been estimated that it's safe to do. + // and all dims will be suffitient + if self.do_as_head { + if recs.is_empty() { + return; + } + + let headers = recs[0].to_owned(); + for (i, head) in headers.into_iter().enumerate() { + let head_width = CellInfo::width(&head); + + match &self.strategy { + TrimStrategy::Wrap { try_to_keep_words } => { + let mut wrap = Width::wrap(head_width); + if *try_to_keep_words { + wrap = wrap.keep_words(); + } + + Modify::new(Columns::single(i)) + .with(wrap) + .change(recs, cfg, dims); + } + TrimStrategy::Truncate { suffix } => { + let mut truncate = Width::truncate(self.max); + if let Some(suffix) = suffix { + truncate = truncate.suffix(suffix).suffix_try_color(true); + } + + Modify::new(Columns::single(i)) + .with(truncate) + .change(recs, cfg, dims); + } + } + } + + return; + } + match self.strategy { TrimStrategy::Wrap { try_to_keep_words } => { let mut wrap = Width::wrap(self.max).priority::(); @@ -460,19 +537,22 @@ fn load_theme( fn maybe_truncate_columns( data: &mut NuRecords, - theme: &TableTheme, + cfg: &NuTableConfig, termwidth: usize, pad: usize, ) -> Vec { const TERMWIDTH_THRESHOLD: usize = 120; - let truncate = if termwidth > TERMWIDTH_THRESHOLD { + let preserve_content = termwidth > TERMWIDTH_THRESHOLD; + let truncate = if cfg.header_on_border { + truncate_columns_by_head + } else if preserve_content { truncate_columns_by_columns } else { truncate_columns_by_content }; - truncate(data, theme, pad, termwidth) + truncate(data, &cfg.theme, pad, termwidth) } // VERSION where we are showing AS LITTLE COLUMNS AS POSSIBLE but WITH AS MUCH CONTENT AS POSSIBLE. @@ -627,6 +707,83 @@ fn truncate_columns_by_columns( widths } +// VERSION where we are showing AS LITTLE COLUMNS AS POSSIBLE but WITH AS MUCH CONTENT AS POSSIBLE. +fn truncate_columns_by_head( + data: &mut NuRecords, + theme: &TableTheme, + pad: usize, + termwidth: usize, +) -> Vec { + const TRAILING_COLUMN_WIDTH: usize = 5; + + let config = get_config(theme, false, None); + let mut widths = build_width(&*data, pad); + let total_width = get_total_width2(&widths, &config); + if total_width <= termwidth { + return widths; + } + + if data.is_empty() { + return widths; + } + + let head = &data[0]; + + let borders = config.get_borders(); + let has_vertical = borders.has_vertical(); + + let mut width = borders.has_left() as usize + borders.has_right() as usize; + let mut truncate_pos = 0; + for (i, column_header) in head.iter().enumerate() { + let column_header_width = Cell::width(column_header); + width += column_header_width; + + if i > 0 { + width += has_vertical as usize; + } + + if width >= termwidth { + width -= column_header_width + (i > 0 && has_vertical) as usize; + break; + } + + truncate_pos += 1; + } + + // we don't need any truncation then (is it possible?) + if truncate_pos == head.len() { + return widths; + } + + if truncate_pos == 0 { + return vec![]; + } + + truncate_columns(data, truncate_pos); + widths.truncate(truncate_pos); + + // Append columns with a trailing column + + let min_width = width; + + let diff = termwidth - min_width; + let can_trailing_column_be_pushed = diff > TRAILING_COLUMN_WIDTH + has_vertical as usize; + + if !can_trailing_column_be_pushed { + if data.count_columns() == 1 { + return vec![]; + } + + truncate_columns(data, data.count_columns() - 1); + widths.pop(); + } + + push_empty_column(data); + widths.push(3 + pad); + + widths +} + /// The same as [`tabled::peaker::PriorityMax`] but prioritizes left columns first in case of equal width. #[derive(Debug, Default, Clone)] pub struct PriorityMax; @@ -714,9 +871,8 @@ impl TableOption, ColoredConfig> for SetDi } // it assumes no spans is used. +// todo: Could be replaced by Dimension impl usage fn build_width(records: &NuRecords, pad: usize) -> Vec { - use tabled::grid::records::vec_records::Cell; - let count_columns = records.count_columns(); let mut widths = vec![0; count_columns]; for columns in records.iter_rows() { @@ -729,50 +885,156 @@ fn build_width(records: &NuRecords, pad: usize) -> Vec { widths } -struct HeaderMove((usize, usize), bool); +struct GetRow(usize, Vec); -impl TableOption, ColoredConfig> for HeaderMove { +impl TableOption, ColoredConfig> for &mut GetRow { + fn change( + self, + recs: &mut NuRecords, + _: &mut ColoredConfig, + _: &mut CompleteDimensionVecRecords<'_>, + ) { + let row = self.0; + self.1 = recs[row].iter().map(|c| c.as_ref().to_owned()).collect(); + } +} + +struct GetRowSettings(usize, AlignmentHorizontal, Option); + +impl TableOption, ColoredConfig> + for &mut GetRowSettings +{ + fn change( + self, + _: &mut NuRecords, + cfg: &mut ColoredConfig, + _: &mut CompleteDimensionVecRecords<'_>, + ) { + let row = self.0; + self.1 = *cfg.get_alignment_horizontal(Entity::Row(row)); + self.2 = cfg + .get_colors() + .get_color((row, 0)) + .cloned() + .map(Color::from); + } +} + +struct SetLineHeaders { + line: usize, + columns: Vec, + alignment: AlignmentHorizontal, + color: Option, +} + +impl SetLineHeaders { + fn new( + line: usize, + columns: Vec, + alignment: AlignmentHorizontal, + color: Option, + ) -> Self { + Self { + line, + columns, + alignment, + color, + } + } +} + +impl TableOption, ColoredConfig> for SetLineHeaders { fn change( self, recs: &mut NuRecords, cfg: &mut ColoredConfig, dims: &mut CompleteDimensionVecRecords<'_>, ) { - let (row, line) = self.0; - if self.1 { - move_header_on_next(recs, cfg, dims, row, line); - } else { - move_header_on_prev(recs, cfg, dims, row, line); - } + let mut columns = self.columns; + match dims.get_widths() { + Some(widths) => { + columns = columns + .into_iter() + .zip(widths.iter().map(|w| w.checked_sub(2).unwrap_or(*w))) // exclude padding; which is generally 2 + .map(|(s, width)| Truncate::truncate_text(&s, width).into_owned()) + .collect(); + } + None => { + // we don't have widths cached; which means that NO widtds adjustmens were done + // which means we are OK to leave columns as they are. + // + // but we are actually always got to have widths at this point + } + }; + + set_column_names( + recs, + cfg, + dims, + columns, + self.line, + self.alignment, + self.color, + ) } } -fn move_header_on_next( - recs: &mut NuRecords, - cfg: &mut ColoredConfig, - dims: &mut CompleteDimensionVecRecords<'_>, +struct MoveRowNext { row: usize, line: usize, -) { +} + +impl MoveRowNext { + fn new(row: usize, line: usize) -> Self { + Self { row, line } + } +} + +struct MoveRowPrev { + row: usize, + line: usize, +} + +impl MoveRowPrev { + fn new(row: usize, line: usize) -> Self { + Self { row, line } + } +} + +impl TableOption, ColoredConfig> for MoveRowNext { + fn change( + self, + recs: &mut NuRecords, + cfg: &mut ColoredConfig, + _: &mut CompleteDimensionVecRecords<'_>, + ) { + row_shift_next(recs, cfg, self.row, self.line); + } +} + +impl TableOption, ColoredConfig> for MoveRowPrev { + fn change( + self, + recs: &mut NuRecords, + cfg: &mut ColoredConfig, + _: &mut CompleteDimensionVecRecords<'_>, + ) { + row_shift_prev(recs, cfg, self.row, self.line); + } +} + +fn row_shift_next(recs: &mut NuRecords, cfg: &mut ColoredConfig, row: usize, line: usize) { let count_rows = recs.count_rows(); let count_columns = recs.count_columns(); let has_line = cfg.has_horizontal(line, count_rows); let has_next_line = cfg.has_horizontal(line + 1, count_rows); - let align = *cfg.get_alignment_horizontal(Entity::Row(row)); - let color = cfg - .get_colors() - .get_color((row, 0)) - .cloned() - .map(Color::from); - if !has_line && !has_next_line { return; } if !has_line { - let head = remove_row(recs, row); + let _ = remove_row(recs, row); let count_rows = recs.count_rows(); - set_column_names(recs, cfg, dims, head, line, align, color); shift_alignments_down(cfg, row, count_rows, count_columns); shift_colors_down(cfg, row, count_rows, count_columns); shift_lines_up(cfg, count_rows, &[line + 1]); @@ -780,47 +1042,31 @@ fn move_header_on_next( return; } - let head = remove_row(recs, row); + let _ = remove_row(recs, row); let count_rows = recs.count_rows(); - set_column_names(recs, cfg, dims, head, line, align, color); shift_alignments_down(cfg, row, count_rows, count_columns); shift_colors_down(cfg, row, count_rows, count_columns); remove_lines(cfg, count_rows, &[line + 1]); shift_lines_up(cfg, count_rows, &[count_rows]); } -fn move_header_on_prev( - recs: &mut NuRecords, - cfg: &mut ColoredConfig, - dims: &mut CompleteDimensionVecRecords<'_>, - row: usize, - line: usize, -) { +fn row_shift_prev(recs: &mut NuRecords, cfg: &mut ColoredConfig, row: usize, line: usize) { let count_rows = recs.count_rows(); let count_columns = recs.count_columns(); let has_line = cfg.has_horizontal(line, count_rows); let has_prev_line = cfg.has_horizontal(line - 1, count_rows); - let align = *cfg.get_alignment_horizontal(Entity::Row(row)); - let color = cfg - .get_colors() - .get_color((row, 0)) - .cloned() - .map(Color::from); - if !has_line && !has_prev_line { return; } if !has_line { - let head = remove_row(recs, row); + let _ = remove_row(recs, row); // shift_lines_down(table, &[line - 1]); - set_column_names(recs, cfg, dims, head, line - 1, align, color); return; } - let head = remove_row(recs, row); + let _ = remove_row(recs, row); let count_rows = count_rows - 1; - set_column_names(recs, cfg, dims, head, line - 1, align, color); shift_alignments_down(cfg, row, count_rows, count_columns); shift_colors_down(cfg, row, count_rows, count_columns); remove_lines(cfg, count_rows, &[line - 1]); From 5af8d6266600ceec4305121afe652a3da2d36f31 Mon Sep 17 00:00:00 2001 From: goldfish <37319612+ito-hiroki@users.noreply.github.com> Date: Sun, 7 Jul 2024 21:55:06 +0900 Subject: [PATCH 004/126] Fix `from toml` to handle toml datetime correctly (#13315) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description fixed #12699 When bare dates or naive times are specified in toml files, `from toml` returns invalid dates or times. This PR fixes the problem to correctly handle toml datetime. The current version command returns the default datetime (`chrono::DateTime::default()`) if the datetime parse fails. However, I felt that this behavior was a bit unfriendly, so I changed it to return `Value::string`. # User-Facing Changes The command returns a date with default time and timezone if a bare date is specified. ``` ~/Development/nushell> "dob = 2023-05-27" | from toml ╭─────┬────────────╮ │ dob │ a year ago │ ╰─────┴────────────╯ ~/Development/nushell> "dob = 2023-05-27" | from toml | Sat, 27 May 2023 00:00:00 +0000 (a year ago) ~/Development/nushell> ``` If a bare time is given, a time string is returned. ``` ~/Development/nushell> "tm = 11:00:00" | from toml ╭────┬──────────╮ │ tm │ 11:00:00 │ ╰────┴──────────╯ ~/Development/nushell> "tm = 11:00:00" | from toml | get tm 11:00:00 ~/Development/nushell> ``` # Tests + Formatting When I ran tests, `commands::touch::change_file_mtime_to_reference` failed with the following error. The error also occurs in the master branch, so it's probably unrelated to these changes. (maybe a problem with my dev environment) ``` $ ~/Development/nushell> toolkit check pr ~~~~~~~~ test usage_start_uppercase ... ok test format_conversions::yaml::convert_dict_to_yaml_with_integer_floats_key ... ok test format_conversions::yaml::convert_dict_to_yaml_with_boolean_key ... ok test format_conversions::yaml::table_to_yaml_text_and_from_yaml_text_back_into_table ... ok test quickcheck_parse ... ok test format_conversions::yaml::convert_dict_to_yaml_with_integer_key ... ok failures: ---- commands::touch::change_file_mtime_to_reference stdout ---- === stderr thread 'commands::touch::change_file_mtime_to_reference' panicked at crates/nu-command/tests/commands/touch.rs:298:9: assertion `left == right` failed left: SystemTime { tv_sec: 1720344745, tv_nsec: 862392750 } right: SystemTime { tv_sec: 1720344745, tv_nsec: 887670417 } failures: commands::touch::change_file_mtime_to_reference test result: FAILED. 1542 passed; 1 failed; 32 ignored; 0 measured; 0 filtered out; finished in 12.04s error: test failed, to rerun pass `-p nu-command --test main` - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :red_circle: `toolkit test` - :black_circle: `toolkit test stdlib` ~/Development/nushell> toolkit test stdlib Compiling nu v0.95.1 (/Users/hiroki/Development/nushell) Compiling nu-cmd-lang v0.95.1 (/Users/hiroki/Development/nushell/crates/nu-cmd-lang) Finished dev [unoptimized + debuginfo] target(s) in 6.64s Running `target/debug/nu --no-config-file -c ' use crates/nu-std/testing.nu testing run-tests --path crates/nu-std '` 2024-07-07T19:00:20.423|INF|Running from_jsonl_invalid_object in module test_formats 2024-07-07T19:00:20.436|INF|Running env_log-prefix in module test_logger_env ~~~~~~~~~~~ 2024-07-07T19:00:22.196|INF|Running debug_short in module test_basic_commands ~/Development/nushell> ``` # After Submitting nothing --- crates/nu-command/src/formats/from/toml.rs | 193 +++++++++++++++++---- 1 file changed, 159 insertions(+), 34 deletions(-) diff --git a/crates/nu-command/src/formats/from/toml.rs b/crates/nu-command/src/formats/from/toml.rs index e1ddca3164..266911f50a 100644 --- a/crates/nu-command/src/formats/from/toml.rs +++ b/crates/nu-command/src/formats/from/toml.rs @@ -1,5 +1,5 @@ use nu_engine::command_prelude::*; -use std::str::FromStr; +use toml::value::{Datetime, Offset}; #[derive(Clone)] pub struct FromToml; @@ -56,6 +56,54 @@ b = [1, 2]' | from toml", } } +fn convert_toml_datetime_to_value(dt: &Datetime, span: Span) -> Value { + match &dt.clone() { + toml::value::Datetime { + date: Some(_), + time: _, + offset: _, + } => (), + _ => return Value::string(dt.to_string(), span), + } + + let date = match dt.date { + Some(date) => { + chrono::NaiveDate::from_ymd_opt(date.year.into(), date.month.into(), date.day.into()) + } + None => Some(chrono::NaiveDate::default()), + }; + + let time = match dt.time { + Some(time) => chrono::NaiveTime::from_hms_nano_opt( + time.hour.into(), + time.minute.into(), + time.second.into(), + time.nanosecond, + ), + None => Some(chrono::NaiveTime::default()), + }; + + let tz = match dt.offset { + Some(offset) => match offset { + Offset::Z => chrono::FixedOffset::east_opt(0), + Offset::Custom { minutes: min } => chrono::FixedOffset::east_opt(min as i32 * 60), + }, + None => chrono::FixedOffset::east_opt(0), + }; + + let datetime = match (date, time, tz) { + (Some(date), Some(time), Some(tz)) => chrono::NaiveDateTime::new(date, time) + .and_local_timezone(tz) + .earliest(), + _ => None, + }; + + match datetime { + Some(datetime) => Value::date(datetime, span), + None => Value::string(dt.to_string(), span), + } +} + fn convert_toml_to_value(value: &toml::Value, span: Span) -> Value { match value { toml::Value::Array(array) => { @@ -76,13 +124,7 @@ fn convert_toml_to_value(value: &toml::Value, span: Span) -> Value { span, ), toml::Value::String(s) => Value::string(s.clone(), span), - toml::Value::Datetime(d) => match chrono::DateTime::from_str(&d.to_string()) { - Ok(nushell_date) => Value::date(nushell_date, span), - // in the unlikely event that parsing goes wrong, this function still returns a valid - // nushell date (however the default one). This decision was made to make the output of - // this function uniform amongst all eventualities - Err(_) => Value::date(chrono::DateTime::default(), span), - }, + toml::Value::Datetime(dt) => convert_toml_datetime_to_value(dt, span), } } @@ -113,32 +155,6 @@ mod tests { test_examples(FromToml {}) } - #[test] - fn from_toml_creates_nushell_date() { - let toml_date = toml::Value::Datetime(Datetime { - date: Option::from(toml::value::Date { - year: 1980, - month: 10, - day: 12, - }), - time: Option::from(toml::value::Time { - hour: 10, - minute: 12, - second: 44, - nanosecond: 0, - }), - offset: Option::from(toml::value::Offset::Custom { minutes: 120 }), - }); - - let span = Span::test_data(); - let reference_date = Value::date(Default::default(), Span::test_data()); - - let result = convert_toml_to_value(&toml_date, span); - - //positive test (from toml returns a nushell date) - assert_eq!(result.get_type(), reference_date.get_type()); - } - #[test] fn from_toml_creates_correct_date() { let toml_date = toml::Value::Datetime(Datetime { @@ -206,4 +222,113 @@ mod tests { assert!(result.is_err()); } + + #[test] + fn convert_toml_datetime_to_value_date_time_offset() { + let toml_date = Datetime { + date: Option::from(toml::value::Date { + year: 2000, + month: 1, + day: 1, + }), + time: Option::from(toml::value::Time { + hour: 12, + minute: 12, + second: 12, + nanosecond: 0, + }), + offset: Option::from(toml::value::Offset::Custom { minutes: 120 }), + }; + + let span = Span::test_data(); + let reference_date = Value::date( + chrono::FixedOffset::east_opt(60 * 120) + .unwrap() + .with_ymd_and_hms(2000, 1, 1, 12, 12, 12) + .unwrap(), + span, + ); + + let result = convert_toml_datetime_to_value(&toml_date, span); + + assert_eq!(result, reference_date); + } + + #[test] + fn convert_toml_datetime_to_value_date_time() { + let toml_date = Datetime { + date: Option::from(toml::value::Date { + year: 2000, + month: 1, + day: 1, + }), + time: Option::from(toml::value::Time { + hour: 12, + minute: 12, + second: 12, + nanosecond: 0, + }), + offset: None, + }; + + let span = Span::test_data(); + let reference_date = Value::date( + chrono::FixedOffset::east_opt(0) + .unwrap() + .with_ymd_and_hms(2000, 1, 1, 12, 12, 12) + .unwrap(), + span, + ); + + let result = convert_toml_datetime_to_value(&toml_date, span); + + assert_eq!(result, reference_date); + } + + #[test] + fn convert_toml_datetime_to_value_date() { + let toml_date = Datetime { + date: Option::from(toml::value::Date { + year: 2000, + month: 1, + day: 1, + }), + time: None, + offset: None, + }; + + let span = Span::test_data(); + let reference_date = Value::date( + chrono::FixedOffset::east_opt(0) + .unwrap() + .with_ymd_and_hms(2000, 1, 1, 0, 0, 0) + .unwrap(), + span, + ); + + let result = convert_toml_datetime_to_value(&toml_date, span); + + assert_eq!(result, reference_date); + } + + #[test] + fn convert_toml_datetime_to_value_only_time() { + let toml_date = Datetime { + date: None, + time: Option::from(toml::value::Time { + hour: 12, + minute: 12, + second: 12, + nanosecond: 0, + }), + offset: None, + }; + + let span = Span::test_data(); + let reference_date = Value::string(toml_date.to_string(), span); + + let result = convert_toml_datetime_to_value(&toml_date, span); + + assert_eq!(result, reference_date); + } } From 6ce5530fc2d6fc746bb32a3e20e1ab5d1b4c0c1b Mon Sep 17 00:00:00 2001 From: Devyn Cairns Date: Sun, 7 Jul 2024 06:00:57 -0700 Subject: [PATCH 005/126] Make `into bits` produce bitstring stream (#13310) # Description Fix `into bits` to have consistent behavior when passed a byte stream. # User-Facing Changes Previously, it was returning a binary on stream, even though its input/output types don't describe this possibility. We don't need this since we have `into binary` anyway. # Tests + Formatting Tests added --- crates/nu-cmd-extra/src/extra/bits/into.rs | 34 +++++++++++++++++-- .../nu-cmd-extra/tests/commands/bits/into.rs | 13 +++++++ .../nu-cmd-extra/tests/commands/bits/mod.rs | 1 + crates/nu-cmd-extra/tests/commands/mod.rs | 1 + 4 files changed, 46 insertions(+), 3 deletions(-) create mode 100644 crates/nu-cmd-extra/tests/commands/bits/into.rs create mode 100644 crates/nu-cmd-extra/tests/commands/bits/mod.rs diff --git a/crates/nu-cmd-extra/src/extra/bits/into.rs b/crates/nu-cmd-extra/src/extra/bits/into.rs index cf85f92ac5..9891a971f6 100644 --- a/crates/nu-cmd-extra/src/extra/bits/into.rs +++ b/crates/nu-cmd-extra/src/extra/bits/into.rs @@ -1,3 +1,5 @@ +use std::io::{self, Read, Write}; + use nu_cmd_base::input_handler::{operate, CmdArgument}; use nu_engine::command_prelude::*; @@ -118,15 +120,41 @@ fn into_bits( let cell_paths = call.rest(engine_state, stack, 0)?; let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths); - if let PipelineData::ByteStream(stream, ..) = input { - // TODO: in the future, we may want this to stream out, converting each to bytes - Ok(Value::binary(stream.into_bytes()?, head).into_pipeline_data()) + if let PipelineData::ByteStream(stream, metadata) = input { + Ok(PipelineData::ByteStream( + byte_stream_to_bits(stream, head), + metadata, + )) } else { let args = Arguments { cell_paths }; operate(action, args, input, call.head, engine_state.ctrlc.clone()) } } +fn byte_stream_to_bits(stream: ByteStream, head: Span) -> ByteStream { + if let Some(mut reader) = stream.reader() { + let mut is_first = true; + ByteStream::from_fn(head, None, ByteStreamType::String, move |buffer| { + let mut byte = [0]; + if reader.read(&mut byte[..]).err_span(head)? > 0 { + // Format the byte as bits + if is_first { + is_first = false; + } else { + buffer.push(b' '); + } + write!(buffer, "{:08b}", byte[0]).expect("format failed"); + Ok(true) + } else { + // EOF + Ok(false) + } + }) + } else { + ByteStream::read(io::empty(), head, None, ByteStreamType::String) + } +} + fn convert_to_smallest_number_type(num: i64, span: Span) -> Value { if let Some(v) = num.to_i8() { let bytes = v.to_ne_bytes(); diff --git a/crates/nu-cmd-extra/tests/commands/bits/into.rs b/crates/nu-cmd-extra/tests/commands/bits/into.rs new file mode 100644 index 0000000000..b7e7700583 --- /dev/null +++ b/crates/nu-cmd-extra/tests/commands/bits/into.rs @@ -0,0 +1,13 @@ +use nu_test_support::nu; + +#[test] +fn byte_stream_into_bits() { + let result = nu!("[0x[01] 0x[02 03]] | bytes collect | into bits"); + assert_eq!("00000001 00000010 00000011", result.out); +} + +#[test] +fn byte_stream_into_bits_is_stream() { + let result = nu!("[0x[01] 0x[02 03]] | bytes collect | into bits | describe"); + assert_eq!("string (stream)", result.out); +} diff --git a/crates/nu-cmd-extra/tests/commands/bits/mod.rs b/crates/nu-cmd-extra/tests/commands/bits/mod.rs new file mode 100644 index 0000000000..0d4ee04b0d --- /dev/null +++ b/crates/nu-cmd-extra/tests/commands/bits/mod.rs @@ -0,0 +1 @@ +mod into; diff --git a/crates/nu-cmd-extra/tests/commands/mod.rs b/crates/nu-cmd-extra/tests/commands/mod.rs index 2354122e35..fd216cecb6 100644 --- a/crates/nu-cmd-extra/tests/commands/mod.rs +++ b/crates/nu-cmd-extra/tests/commands/mod.rs @@ -1 +1,2 @@ +mod bits; mod bytes; From 83081f9852dfc6f743fb569c084690d325526502 Mon Sep 17 00:00:00 2001 From: Reilly Wood <26268125+rgwood@users.noreply.github.com> Date: Sun, 7 Jul 2024 06:09:59 -0700 Subject: [PATCH 006/126] `explore`: pass config to views at creation time (#13312) cc: @zhiburt This is an internal refactoring for `explore`. Previously, views inside `explore` were created with default/incorrect configuration and then the correct configuration was passed to them using a function called `setup()`. I believe this was because configuration was dynamic and could change while `explore` was running. After https://github.com/nushell/nushell/pull/10259, configuration can no longer be changed on the fly. So we can clean this up by removing `setup()` and passing configuration to views when they are created. --- crates/nu-explore/src/commands/expand.rs | 3 +- crates/nu-explore/src/commands/help.rs | 10 ++- crates/nu-explore/src/commands/mod.rs | 3 + crates/nu-explore/src/commands/nu.rs | 10 +-- crates/nu-explore/src/commands/table.rs | 5 +- crates/nu-explore/src/commands/try.rs | 5 +- crates/nu-explore/src/lib.rs | 9 +- crates/nu-explore/src/pager/mod.rs | 105 +++++++++------------- crates/nu-explore/src/registry/command.rs | 5 +- crates/nu-explore/src/views/binary/mod.rs | 19 ++-- crates/nu-explore/src/views/mod.rs | 6 -- crates/nu-explore/src/views/record/mod.rs | 28 +----- crates/nu-explore/src/views/try.rs | 35 ++++---- 13 files changed, 93 insertions(+), 150 deletions(-) diff --git a/crates/nu-explore/src/commands/expand.rs b/crates/nu-explore/src/commands/expand.rs index d7d75af6c5..cbb86336af 100644 --- a/crates/nu-explore/src/commands/expand.rs +++ b/crates/nu-explore/src/commands/expand.rs @@ -1,7 +1,7 @@ use super::ViewCommand; use crate::{ nu_common::{self, collect_input}, - views::Preview, + views::{Preview, ViewConfig}, }; use anyhow::Result; use nu_color_config::StyleComputer; @@ -43,6 +43,7 @@ impl ViewCommand for ExpandCmd { engine_state: &EngineState, stack: &mut Stack, value: Option, + _: &ViewConfig, ) -> Result { if let Some(value) = value { let value_as_string = convert_value_to_string(value, engine_state, stack)?; diff --git a/crates/nu-explore/src/commands/help.rs b/crates/nu-explore/src/commands/help.rs index 8be468383e..684e713939 100644 --- a/crates/nu-explore/src/commands/help.rs +++ b/crates/nu-explore/src/commands/help.rs @@ -1,5 +1,5 @@ use super::ViewCommand; -use crate::views::Preview; +use crate::views::{Preview, ViewConfig}; use anyhow::Result; use nu_ansi_term::Color; use nu_protocol::{ @@ -99,7 +99,13 @@ impl ViewCommand for HelpCmd { Ok(()) } - fn spawn(&mut self, _: &EngineState, _: &mut Stack, _: Option) -> Result { + fn spawn( + &mut self, + _: &EngineState, + _: &mut Stack, + _: Option, + _: &ViewConfig, + ) -> Result { Ok(HelpCmd::view()) } } diff --git a/crates/nu-explore/src/commands/mod.rs b/crates/nu-explore/src/commands/mod.rs index 141dc25f56..a748ddaaa3 100644 --- a/crates/nu-explore/src/commands/mod.rs +++ b/crates/nu-explore/src/commands/mod.rs @@ -1,3 +1,5 @@ +use crate::views::ViewConfig; + use super::pager::{Pager, Transition}; use anyhow::Result; use nu_protocol::{ @@ -49,5 +51,6 @@ pub trait ViewCommand { engine_state: &EngineState, stack: &mut Stack, value: Option, + config: &ViewConfig, ) -> Result; } diff --git a/crates/nu-explore/src/commands/nu.rs b/crates/nu-explore/src/commands/nu.rs index a0d479fd92..84e1fb9706 100644 --- a/crates/nu-explore/src/commands/nu.rs +++ b/crates/nu-explore/src/commands/nu.rs @@ -48,6 +48,7 @@ impl ViewCommand for NuCmd { engine_state: &EngineState, stack: &mut Stack, value: Option, + config: &ViewConfig, ) -> Result { let value = value.unwrap_or_default(); @@ -62,7 +63,7 @@ impl ViewCommand for NuCmd { return Ok(NuView::Preview(Preview::new(&text))); } - let mut view = RecordView::new(columns, values); + let mut view = RecordView::new(columns, values, config.explore_config.clone()); if is_record { view.set_top_layer_orientation(Orientation::Left); @@ -119,11 +120,4 @@ impl View for NuView { NuView::Preview(v) => v.exit(), } } - - fn setup(&mut self, config: ViewConfig<'_>) { - match self { - NuView::Records(v) => v.setup(config), - NuView::Preview(v) => v.setup(config), - } - } } diff --git a/crates/nu-explore/src/commands/table.rs b/crates/nu-explore/src/commands/table.rs index 1079cf6cea..9ab1e39b98 100644 --- a/crates/nu-explore/src/commands/table.rs +++ b/crates/nu-explore/src/commands/table.rs @@ -1,7 +1,7 @@ use super::ViewCommand; use crate::{ nu_common::collect_input, - views::{Orientation, RecordView}, + views::{Orientation, RecordView, ViewConfig}, }; use anyhow::Result; use nu_protocol::{ @@ -49,13 +49,14 @@ impl ViewCommand for TableCmd { _: &EngineState, _: &mut Stack, value: Option, + config: &ViewConfig, ) -> Result { let value = value.unwrap_or_default(); let is_record = matches!(value, Value::Record { .. }); let (columns, data) = collect_input(value)?; - let mut view = RecordView::new(columns, data); + let mut view = RecordView::new(columns, data, config.explore_config.clone()); if is_record { view.set_top_layer_orientation(Orientation::Left); diff --git a/crates/nu-explore/src/commands/try.rs b/crates/nu-explore/src/commands/try.rs index e70b4237d9..e1290651c8 100644 --- a/crates/nu-explore/src/commands/try.rs +++ b/crates/nu-explore/src/commands/try.rs @@ -1,5 +1,5 @@ use super::ViewCommand; -use crate::views::TryView; +use crate::views::{TryView, ViewConfig}; use anyhow::Result; use nu_protocol::{ engine::{EngineState, Stack}, @@ -43,9 +43,10 @@ impl ViewCommand for TryCmd { engine_state: &EngineState, stack: &mut Stack, value: Option, + config: &ViewConfig, ) -> Result { let value = value.unwrap_or_default(); - let mut view = TryView::new(value); + let mut view = TryView::new(value, config.explore_config.clone()); view.init(self.command.clone()); view.try_run(engine_state, stack)?; diff --git a/crates/nu-explore/src/lib.rs b/crates/nu-explore/src/lib.rs index d347321354..30b3e5cf87 100644 --- a/crates/nu-explore/src/lib.rs +++ b/crates/nu-explore/src/lib.rs @@ -10,6 +10,7 @@ use anyhow::Result; use commands::{ExpandCmd, HelpCmd, NuCmd, QuitCmd, TableCmd, TryCmd}; pub use default_context::add_explore_context; pub use explore::Explore; +use explore::ExploreConfig; use nu_common::{collect_pipeline, has_simple_value, CtrlC}; use nu_protocol::{ engine::{EngineState, Stack}, @@ -43,7 +44,7 @@ fn run_pager( if is_binary { p.show_message("For help type :help"); - let view = binary_view(input)?; + let view = binary_view(input, config.explore_config)?; return p.run(engine_state, stack, ctrlc, Some(view), commands); } @@ -73,7 +74,7 @@ fn create_record_view( is_record: bool, config: PagerConfig, ) -> Option { - let mut view = RecordView::new(columns, data); + let mut view = RecordView::new(columns, data, config.explore_config.clone()); if is_record { view.set_top_layer_orientation(Orientation::Left); } @@ -91,14 +92,14 @@ fn help_view() -> Option { Some(Page::new(HelpCmd::view(), false)) } -fn binary_view(input: PipelineData) -> Result { +fn binary_view(input: PipelineData, config: &ExploreConfig) -> Result { let data = match input { PipelineData::Value(Value::Binary { val, .. }, _) => val, PipelineData::ByteStream(bs, _) => bs.into_bytes()?, _ => unreachable!("checked beforehand"), }; - let view = BinaryView::new(data); + let view = BinaryView::new(data, config); Ok(Page::new(view, true)) } diff --git a/crates/nu-explore/src/pager/mod.rs b/crates/nu-explore/src/pager/mod.rs index a56df95bc7..3114543f7c 100644 --- a/crates/nu-explore/src/pager/mod.rs +++ b/crates/nu-explore/src/pager/mod.rs @@ -90,19 +90,42 @@ impl<'a> Pager<'a> { engine_state: &EngineState, stack: &mut Stack, ctrlc: CtrlC, - mut view: Option, + view: Option, commands: CommandRegistry, ) -> Result> { - if let Some(page) = &mut view { - page.view.setup(ViewConfig::new( - self.config.nu_config, - self.config.explore_config, - self.config.style_computer, - self.config.lscolors, - )) + // setup terminal + enable_raw_mode()?; + let mut stdout = io::stdout(); + execute!(stdout, EnterAlternateScreen, Clear(ClearType::All))?; + + let backend = CrosstermBackend::new(stdout); + let mut terminal = Terminal::new(backend)?; + + let mut info = ViewInfo { + status: Some(Report::default()), + ..Default::default() + }; + + if let Some(text) = self.message.take() { + info.status = Some(Report::message(text, Severity::Info)); } - run_pager(engine_state, stack, ctrlc, self, view, commands) + let result = render_ui( + &mut terminal, + engine_state, + stack, + ctrlc, + self, + &mut info, + view, + commands, + )?; + + // restore terminal + disable_raw_mode()?; + execute!(io::stdout(), LeaveAlternateScreen)?; + + Ok(result) } } @@ -145,49 +168,6 @@ impl<'a> PagerConfig<'a> { } } -fn run_pager( - engine_state: &EngineState, - stack: &mut Stack, - ctrlc: CtrlC, - pager: &mut Pager, - view: Option, - commands: CommandRegistry, -) -> Result> { - // setup terminal - enable_raw_mode()?; - let mut stdout = io::stdout(); - execute!(stdout, EnterAlternateScreen, Clear(ClearType::All))?; - - let backend = CrosstermBackend::new(stdout); - let mut terminal = Terminal::new(backend)?; - - let mut info = ViewInfo { - status: Some(Report::default()), - ..Default::default() - }; - - if let Some(text) = pager.message.take() { - info.status = Some(Report::message(text, Severity::Info)); - } - - let result = render_ui( - &mut terminal, - engine_state, - stack, - ctrlc, - pager, - &mut info, - view, - commands, - )?; - - // restore terminal - disable_raw_mode()?; - execute!(io::stdout(), LeaveAlternateScreen)?; - - Ok(result) -} - #[allow(clippy::too_many_arguments)] fn render_ui( term: &mut Terminal, @@ -440,15 +420,20 @@ fn run_command( Command::View { mut cmd, stackable } => { // what we do we just replace the view. let value = view_stack.curr_view.as_mut().and_then(|p| p.view.exit()); - let mut new_view = cmd.spawn(engine_state, stack, value)?; + let view_cfg = ViewConfig::new( + pager.config.nu_config, + pager.config.explore_config, + pager.config.style_computer, + pager.config.lscolors, + ); + + let new_view = cmd.spawn(engine_state, stack, value, &view_cfg)?; if let Some(view) = view_stack.curr_view.take() { if !view.stackable { view_stack.stack.push(view); } } - setup_view(&mut new_view, &pager.config); - view_stack.curr_view = Some(Page::raw(new_view, stackable)); Ok(CmdResult::new(false, true, cmd.name().to_owned())) @@ -456,16 +441,6 @@ fn run_command( } } -fn setup_view(view: &mut Box, cfg: &PagerConfig<'_>) { - let cfg = ViewConfig::new( - cfg.nu_config, - cfg.explore_config, - cfg.style_computer, - cfg.lscolors, - ); - view.setup(cfg); -} - fn set_cursor_cmd_bar(f: &mut Frame, area: Rect, pager: &Pager) { if pager.cmd_buf.is_cmd_input { // todo: deal with a situation where we exceed the bar width diff --git a/crates/nu-explore/src/registry/command.rs b/crates/nu-explore/src/registry/command.rs index 7e0bc131b5..6a3e9a128f 100644 --- a/crates/nu-explore/src/registry/command.rs +++ b/crates/nu-explore/src/registry/command.rs @@ -1,6 +1,6 @@ use crate::{ commands::{SimpleCommand, ViewCommand}, - views::View, + views::{View, ViewConfig}, }; use anyhow::Result; @@ -78,8 +78,9 @@ where engine_state: &nu_protocol::engine::EngineState, stack: &mut nu_protocol::engine::Stack, value: Option, + cfg: &ViewConfig, ) -> Result { - let view = self.0.spawn(engine_state, stack, value)?; + let view = self.0.spawn(engine_state, stack, value, cfg)?; Ok(Box::new(view) as Box) } } diff --git a/crates/nu-explore/src/views/binary/mod.rs b/crates/nu-explore/src/views/binary/mod.rs index f288861be8..2a35b7c180 100644 --- a/crates/nu-explore/src/views/binary/mod.rs +++ b/crates/nu-explore/src/views/binary/mod.rs @@ -40,11 +40,15 @@ struct Settings { } impl BinaryView { - pub fn new(data: Vec) -> Self { + pub fn new(data: Vec, cfg: &ExploreConfig) -> Self { + let settings = settings_from_config(cfg); + // There's gotta be a nicer way of doing this than creating a widget just to count lines + let count_rows = BinaryWidget::new(&data, settings.opts, Default::default()).count_lines(); + Self { data, - cursor: WindowCursor2D::default(), - settings: Settings::default(), + cursor: WindowCursor2D::new(count_rows, 1).expect("Failed to create XYCursor"), + settings, } } } @@ -87,15 +91,6 @@ impl View for BinaryView { // todo: impl Cursor + peek of a value None } - - fn setup(&mut self, cfg: ViewConfig<'_>) { - self.settings = settings_from_config(cfg.explore_config); - - let count_rows = - BinaryWidget::new(&self.data, self.settings.opts, Default::default()).count_lines(); - // TODO: refactor View so setup() is fallible and we don't have to panic here - self.cursor = WindowCursor2D::new(count_rows, 1).expect("Failed to create XYCursor"); - } } fn create_binary_widget(v: &BinaryView) -> BinaryWidget<'_> { diff --git a/crates/nu-explore/src/views/mod.rs b/crates/nu-explore/src/views/mod.rs index 7aab197eb5..bdbf3129db 100644 --- a/crates/nu-explore/src/views/mod.rs +++ b/crates/nu-explore/src/views/mod.rs @@ -99,8 +99,6 @@ pub trait View { fn exit(&mut self) -> Option { None } - - fn setup(&mut self, _: ViewConfig<'_>) {} } impl View for Box { @@ -131,8 +129,4 @@ impl View for Box { fn show_data(&mut self, i: usize) -> bool { self.as_mut().show_data(i) } - - fn setup(&mut self, cfg: ViewConfig<'_>) { - self.as_mut().setup(cfg) - } } diff --git a/crates/nu-explore/src/views/record/mod.rs b/crates/nu-explore/src/views/record/mod.rs index bd3c7e8e0a..4f617686f1 100644 --- a/crates/nu-explore/src/views/record/mod.rs +++ b/crates/nu-explore/src/views/record/mod.rs @@ -36,14 +36,12 @@ pub struct RecordView { } impl RecordView { - pub fn new(columns: Vec, records: Vec>) -> Self { + pub fn new(columns: Vec, records: Vec>, cfg: ExploreConfig) -> Self { Self { layer_stack: vec![RecordLayer::new(columns, records)], mode: UIMode::View, orientation: Orientation::Top, - // TODO: It's kind of gross how this temporarily has an incorrect/default config. - // See if we can pass correct config in through the constructor - cfg: ExploreConfig::default(), + cfg, } } @@ -72,23 +70,6 @@ impl RecordView { .expect("we guarantee that 1 entry is always in a list") } - pub fn get_top_layer_orientation(&mut self) -> Orientation { - self.get_top_layer().orientation - } - - pub fn set_orientation(&mut self, orientation: Orientation) { - self.orientation = orientation; - - // we need to reset all indexes as we can't no more use them. - self.reset_cursors(); - } - - fn reset_cursors(&mut self) { - for layer in &mut self.layer_stack { - layer.reset_cursor(); - } - } - pub fn set_top_layer_orientation(&mut self, orientation: Orientation) { let layer = self.get_top_layer_mut(); layer.orientation = orientation; @@ -299,11 +280,6 @@ impl View for RecordView { fn exit(&mut self) -> Option { Some(build_last_value(self)) } - - // todo: move the method to Command? - fn setup(&mut self, cfg: ViewConfig<'_>) { - self.cfg = cfg.explore_config.clone(); - } } fn get_element_info( diff --git a/crates/nu-explore/src/views/try.rs b/crates/nu-explore/src/views/try.rs index b23ca3e9f4..099e792e38 100644 --- a/crates/nu-explore/src/views/try.rs +++ b/crates/nu-explore/src/views/try.rs @@ -1,5 +1,6 @@ use super::{record::RecordView, util::nu_style_to_tui, Layout, Orientation, View, ViewConfig}; use crate::{ + explore::ExploreConfig, nu_common::{collect_pipeline, run_command_with_value}, pager::{report::Report, Frame, Transition, ViewInfo}, }; @@ -23,17 +24,19 @@ pub struct TryView { table: Option, view_mode: bool, border_color: Style, + config: ExploreConfig, } impl TryView { - pub fn new(input: Value) -> Self { + pub fn new(input: Value, config: ExploreConfig) -> Self { Self { input, table: None, - immediate: false, - border_color: Style::default(), + immediate: config.try_reactive, + border_color: nu_style_to_tui(config.table.separator_style), view_mode: false, command: String::new(), + config, } } @@ -42,7 +45,13 @@ impl TryView { } pub fn try_run(&mut self, engine_state: &EngineState, stack: &mut Stack) -> Result<()> { - let view = run_command(&self.command, &self.input, engine_state, stack)?; + let view = run_command( + &self.command, + &self.input, + engine_state, + stack, + &self.config, + )?; self.table = Some(view); Ok(()) } @@ -122,7 +131,6 @@ impl View for TryView { f.render_widget(table_block, table_area); if let Some(table) = &mut self.table { - table.setup(cfg); let area = Rect::new( area.x + 2, area.y + 4, @@ -232,20 +240,6 @@ impl View for TryView { fn show_data(&mut self, i: usize) -> bool { self.table.as_mut().map_or(false, |v| v.show_data(i)) } - - fn setup(&mut self, config: ViewConfig<'_>) { - self.border_color = nu_style_to_tui(config.explore_config.table.separator_style); - self.immediate = config.explore_config.try_reactive; - - let mut r = RecordView::new(vec![], vec![]); - r.setup(config); - - if let Some(view) = &mut self.table { - view.setup(config); - view.set_orientation(r.get_top_layer_orientation()); - view.set_top_layer_orientation(r.get_top_layer_orientation()); - } - } } fn run_command( @@ -253,6 +247,7 @@ fn run_command( input: &Value, engine_state: &EngineState, stack: &mut Stack, + config: &ExploreConfig, ) -> Result { let pipeline = run_command_with_value(command, input, engine_state, stack)?; @@ -260,7 +255,7 @@ fn run_command( let (columns, values) = collect_pipeline(pipeline)?; - let mut view = RecordView::new(columns, values); + let mut view = RecordView::new(columns, values, config.clone()); if is_record { view.set_top_layer_orientation(Orientation::Left); } From 152fb5be39dc9e37f3dd5ede2bc7eba11cc6d810 Mon Sep 17 00:00:00 2001 From: YizhePKU Date: Mon, 8 Jul 2024 00:43:22 +0800 Subject: [PATCH 007/126] Fix PWD-aware command hints (#13024) This PR fixes PWD-aware command hints by sending PWD to the Reedline state in every REPL loop. This PR should be merged along with https://github.com/nushell/reedline/pull/796. Fixes https://github.com/nushell/nushell/issues/12951. --- crates/nu-cli/src/repl.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/crates/nu-cli/src/repl.rs b/crates/nu-cli/src/repl.rs index 251876022a..d2de8f4bea 100644 --- a/crates/nu-cli/src/repl.rs +++ b/crates/nu-cli/src/repl.rs @@ -336,6 +336,13 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) { .with_quick_completions(config.quick_completions) .with_partial_completions(config.partial_completions) .with_ansi_colors(config.use_ansi_coloring) + .with_cwd(Some( + engine_state + .cwd(None) + .unwrap_or_default() + .to_string_lossy() + .to_string(), + )) .with_cursor_config(cursor_config); perf!("reedline builder", start_time, use_color); @@ -666,13 +673,14 @@ fn prepare_history_metadata( line_editor: &mut Reedline, ) { if !s.is_empty() && line_editor.has_last_command_context() { - #[allow(deprecated)] let result = line_editor .update_last_command_context(&|mut c| { c.start_timestamp = Some(chrono::Utc::now()); c.hostname = hostname.map(str::to_string); - - c.cwd = Some(StateWorkingSet::new(engine_state).get_cwd()); + c.cwd = engine_state + .cwd(None) + .ok() + .map(|path| path.to_string_lossy().to_string()); c }) .into_diagnostic(); From c6b6b1b7a87481305d502d1932a19322bfaaaf0a Mon Sep 17 00:00:00 2001 From: Justin Ma Date: Mon, 8 Jul 2024 06:15:03 +0800 Subject: [PATCH 008/126] Upgrade Ubuntu runners to 22.04 to fix nightly build errors, fix #13255 (#13273) # Description Upgrade Ubuntu runners to 22.04 to fix nightly build errors related to `GLIBC` versions like: https://github.com/nushell/nushell/actions/runs/9727979044/job/26848189346 Should close #13255 BTW: The [workflow of `nushell/nightly`](https://github.com/nushell/nightly/blob/6faf3c3aed728b641701add604cf58f28a87e67f/.github/workflows/nightly-build.yml#L108) has been upgraded a long time ago # User-Facing Changes Older linux system users can download `*-x86_64-unknown-linux-musl.tar.gz` binaries if any `GLIBC` error found while starting `nu` binary --- .github/workflows/nightly-build.yml | 8 ++++---- .github/workflows/release.yml | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/nightly-build.yml b/.github/workflows/nightly-build.yml index 8097b02fe6..9b7f44feba 100644 --- a/.github/workflows/nightly-build.yml +++ b/.github/workflows/nightly-build.yml @@ -99,13 +99,13 @@ jobs: extra: msi os: windows-latest - target: x86_64-unknown-linux-gnu - os: ubuntu-20.04 + os: ubuntu-22.04 - target: x86_64-unknown-linux-musl - os: ubuntu-20.04 + os: ubuntu-22.04 - target: aarch64-unknown-linux-gnu - os: ubuntu-20.04 + os: ubuntu-22.04 - target: armv7-unknown-linux-gnueabihf - os: ubuntu-20.04 + os: ubuntu-22.04 - target: riscv64gc-unknown-linux-gnu os: ubuntu-latest diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index dd3371e0d3..34cad24fbf 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -49,13 +49,13 @@ jobs: extra: msi os: windows-latest - target: x86_64-unknown-linux-gnu - os: ubuntu-20.04 + os: ubuntu-22.04 - target: x86_64-unknown-linux-musl - os: ubuntu-20.04 + os: ubuntu-22.04 - target: aarch64-unknown-linux-gnu - os: ubuntu-20.04 + os: ubuntu-22.04 - target: armv7-unknown-linux-gnueabihf - os: ubuntu-20.04 + os: ubuntu-22.04 - target: riscv64gc-unknown-linux-gnu os: ubuntu-latest From 399a7c8836488db9fd2601404eb154fddf55e48a Mon Sep 17 00:00:00 2001 From: Ian Manske Date: Sun, 7 Jul 2024 22:29:01 +0000 Subject: [PATCH 009/126] Add and use new `Signals` struct (#13314) # Description This PR introduces a new `Signals` struct to replace our adhoc passing around of `ctrlc: Option>`. Doing so has a few benefits: - We can better enforce when/where resetting or triggering an interrupt is allowed. - Consolidates `nu_utils::ctrl_c::was_pressed` and other ad-hoc re-implementations into a single place: `Signals::check`. - This allows us to add other types of signals later if we want. E.g., exiting or suspension. - Similarly, we can more easily change the underlying implementation if we need to in the future. - Places that used to have a `ctrlc` of `None` now use `Signals::empty()`, so we can double check these usages for correctness in the future. --- benches/benchmarks.rs | 21 ++- .../nu-cli/src/commands/history/history_.rs | 6 +- crates/nu-cli/src/nu_highlight.rs | 4 +- crates/nu-cli/src/repl.rs | 9 +- crates/nu-cmd-base/src/input_handler.rs | 10 +- crates/nu-cmd-extra/src/extra/bits/and.rs | 2 +- crates/nu-cmd-extra/src/extra/bits/into.rs | 38 ++-- crates/nu-cmd-extra/src/extra/bits/not.rs | 2 +- crates/nu-cmd-extra/src/extra/bits/or.rs | 2 +- .../src/extra/bits/rotate_left.rs | 2 +- .../src/extra/bits/rotate_right.rs | 2 +- .../nu-cmd-extra/src/extra/bits/shift_left.rs | 2 +- .../src/extra/bits/shift_right.rs | 2 +- crates/nu-cmd-extra/src/extra/bits/xor.rs | 2 +- .../nu-cmd-extra/src/extra/conversions/fmt.rs | 2 +- .../src/extra/filters/each_while.rs | 4 +- .../src/extra/filters/update_cells.rs | 2 +- crates/nu-cmd-extra/src/extra/math/arccos.rs | 2 +- crates/nu-cmd-extra/src/extra/math/arccosh.rs | 5 +- crates/nu-cmd-extra/src/extra/math/arcsin.rs | 2 +- crates/nu-cmd-extra/src/extra/math/arcsinh.rs | 5 +- crates/nu-cmd-extra/src/extra/math/arctan.rs | 2 +- crates/nu-cmd-extra/src/extra/math/arctanh.rs | 5 +- crates/nu-cmd-extra/src/extra/math/cos.rs | 2 +- crates/nu-cmd-extra/src/extra/math/cosh.rs | 5 +- crates/nu-cmd-extra/src/extra/math/exp.rs | 5 +- crates/nu-cmd-extra/src/extra/math/ln.rs | 5 +- crates/nu-cmd-extra/src/extra/math/sin.rs | 2 +- crates/nu-cmd-extra/src/extra/math/sinh.rs | 5 +- crates/nu-cmd-extra/src/extra/math/tan.rs | 2 +- crates/nu-cmd-extra/src/extra/math/tanh.rs | 5 +- .../src/extra/platform/ansi/gradient.rs | 2 +- .../src/extra/strings/encode_decode/hex.rs | 2 +- .../src/extra/strings/format/command.rs | 2 +- .../src/extra/strings/str_/case/mod.rs | 2 +- crates/nu-cmd-lang/src/core_commands/for_.rs | 10 +- crates/nu-cmd-lang/src/core_commands/loop_.rs | 5 +- .../nu-cmd-lang/src/core_commands/while_.rs | 5 +- crates/nu-command/src/bytes/add.rs | 2 +- crates/nu-command/src/bytes/at.rs | 2 +- crates/nu-command/src/bytes/collect.rs | 7 +- crates/nu-command/src/bytes/ends_with.rs | 2 +- crates/nu-command/src/bytes/index_of.rs | 2 +- crates/nu-command/src/bytes/length.rs | 2 +- crates/nu-command/src/bytes/remove.rs | 2 +- crates/nu-command/src/bytes/replace.rs | 2 +- crates/nu-command/src/bytes/reverse.rs | 2 +- crates/nu-command/src/bytes/starts_with.rs | 2 +- crates/nu-command/src/conversions/fill.rs | 2 +- .../nu-command/src/conversions/into/binary.rs | 2 +- .../nu-command/src/conversions/into/bool.rs | 2 +- .../src/conversions/into/datetime.rs | 2 +- .../src/conversions/into/duration.rs | 2 +- .../src/conversions/into/filesize.rs | 2 +- .../nu-command/src/conversions/into/float.rs | 2 +- .../nu-command/src/conversions/into/glob.rs | 2 +- crates/nu-command/src/conversions/into/int.rs | 2 +- .../nu-command/src/conversions/into/record.rs | 2 +- .../nu-command/src/conversions/into/string.rs | 2 +- .../nu-command/src/conversions/into/value.rs | 8 +- .../src/database/commands/into_sqlite.rs | 46 ++--- .../nu-command/src/database/values/sqlite.rs | 149 +++++++-------- crates/nu-command/src/date/humanize.rs | 2 +- crates/nu-command/src/date/list_timezone.rs | 2 +- crates/nu-command/src/date/to_record.rs | 2 +- crates/nu-command/src/date/to_table.rs | 2 +- crates/nu-command/src/date/to_timezone.rs | 2 +- crates/nu-command/src/debug/debug_.rs | 2 +- crates/nu-command/src/debug/metadata_set.rs | 12 +- crates/nu-command/src/filesystem/du.rs | 27 ++- crates/nu-command/src/filesystem/glob.rs | 30 ++- crates/nu-command/src/filesystem/ls.rs | 23 +-- crates/nu-command/src/filesystem/open.rs | 13 +- crates/nu-command/src/filesystem/rm.rs | 7 +- crates/nu-command/src/filesystem/save.rs | 35 ++-- crates/nu-command/src/filesystem/watch.rs | 3 +- crates/nu-command/src/filters/append.rs | 2 +- crates/nu-command/src/filters/compact.rs | 2 +- crates/nu-command/src/filters/default.rs | 6 +- crates/nu-command/src/filters/drop/column.rs | 6 +- crates/nu-command/src/filters/drop/nth.rs | 4 +- crates/nu-command/src/filters/each.rs | 6 +- crates/nu-command/src/filters/enumerate.rs | 3 +- crates/nu-command/src/filters/every.rs | 2 +- crates/nu-command/src/filters/filter.rs | 6 +- crates/nu-command/src/filters/find.rs | 17 +- crates/nu-command/src/filters/first.rs | 14 +- crates/nu-command/src/filters/flatten.rs | 2 +- crates/nu-command/src/filters/get.rs | 5 +- crates/nu-command/src/filters/group.rs | 9 +- crates/nu-command/src/filters/insert.rs | 6 +- crates/nu-command/src/filters/interleave.rs | 2 +- crates/nu-command/src/filters/items.rs | 2 +- crates/nu-command/src/filters/last.rs | 6 +- crates/nu-command/src/filters/lines.rs | 3 +- crates/nu-command/src/filters/merge.rs | 7 +- crates/nu-command/src/filters/move_.rs | 7 +- crates/nu-command/src/filters/par_each.rs | 15 +- crates/nu-command/src/filters/prepend.rs | 2 +- crates/nu-command/src/filters/range.rs | 4 +- crates/nu-command/src/filters/reduce.rs | 5 +- crates/nu-command/src/filters/rename.rs | 2 +- crates/nu-command/src/filters/reverse.rs | 2 +- crates/nu-command/src/filters/select.rs | 10 +- crates/nu-command/src/filters/shuffle.rs | 6 +- crates/nu-command/src/filters/skip/skip_.rs | 16 +- .../nu-command/src/filters/skip/skip_until.rs | 2 +- .../nu-command/src/filters/skip/skip_while.rs | 2 +- crates/nu-command/src/filters/sort.rs | 2 +- crates/nu-command/src/filters/sort_by.rs | 2 +- crates/nu-command/src/filters/take/take_.rs | 18 +- .../nu-command/src/filters/take/take_until.rs | 2 +- .../nu-command/src/filters/take/take_while.rs | 2 +- crates/nu-command/src/filters/tee.rs | 40 ++-- crates/nu-command/src/filters/transpose.rs | 7 +- crates/nu-command/src/filters/uniq.rs | 4 +- crates/nu-command/src/filters/update.rs | 6 +- crates/nu-command/src/filters/upsert.rs | 6 +- crates/nu-command/src/filters/utils.rs | 5 +- crates/nu-command/src/filters/values.rs | 10 +- crates/nu-command/src/filters/where_.rs | 2 +- crates/nu-command/src/filters/window.rs | 7 +- crates/nu-command/src/filters/wrap.rs | 2 +- crates/nu-command/src/filters/zip.rs | 2 +- .../nu-command/src/formats/from/delimited.rs | 8 +- crates/nu-command/src/formats/from/json.rs | 20 +- crates/nu-command/src/formats/from/msgpack.rs | 10 +- .../nu-command/src/formats/from/msgpackz.rs | 2 +- crates/nu-command/src/formats/to/delimited.rs | 63 ++++--- crates/nu-command/src/formats/to/msgpack.rs | 4 +- crates/nu-command/src/formats/to/text.rs | 2 +- crates/nu-command/src/generators/generate.rs | 2 +- crates/nu-command/src/generators/seq.rs | 4 +- crates/nu-command/src/hash/generic_digest.rs | 2 +- crates/nu-command/src/math/abs.rs | 5 +- crates/nu-command/src/math/ceil.rs | 5 +- crates/nu-command/src/math/floor.rs | 5 +- crates/nu-command/src/math/log.rs | 2 +- crates/nu-command/src/math/round.rs | 2 +- crates/nu-command/src/math/sqrt.rs | 5 +- crates/nu-command/src/math/utils.rs | 4 +- crates/nu-command/src/network/http/client.rs | 68 ++++--- crates/nu-command/src/network/http/delete.rs | 9 +- crates/nu-command/src/network/http/get.rs | 9 +- crates/nu-command/src/network/http/head.rs | 13 +- crates/nu-command/src/network/http/options.rs | 9 +- crates/nu-command/src/network/http/patch.rs | 9 +- crates/nu-command/src/network/http/post.rs | 9 +- crates/nu-command/src/network/http/put.rs | 9 +- crates/nu-command/src/network/url/decode.rs | 2 +- crates/nu-command/src/network/url/encode.rs | 10 +- crates/nu-command/src/path/basename.rs | 4 +- crates/nu-command/src/path/dirname.rs | 4 +- crates/nu-command/src/path/exists.rs | 4 +- crates/nu-command/src/path/expand.rs | 4 +- crates/nu-command/src/path/parse.rs | 4 +- crates/nu-command/src/path/relative_to.rs | 4 +- crates/nu-command/src/path/split.rs | 4 +- crates/nu-command/src/path/type.rs | 4 +- crates/nu-command/src/platform/ansi/ansi_.rs | 25 +-- crates/nu-command/src/platform/ansi/link.rs | 4 +- crates/nu-command/src/platform/ansi/strip.rs | 2 +- crates/nu-command/src/platform/dir_info.rs | 31 ++- crates/nu-command/src/platform/sleep.rs | 7 +- crates/nu-command/src/random/dice.rs | 2 +- crates/nu-command/src/stor/create.rs | 27 ++- crates/nu-command/src/stor/delete.rs | 6 +- crates/nu-command/src/stor/export.rs | 6 +- crates/nu-command/src/stor/import.rs | 6 +- crates/nu-command/src/stor/insert.rs | 36 +++- crates/nu-command/src/stor/open.rs | 6 +- crates/nu-command/src/stor/reset.rs | 6 +- crates/nu-command/src/stor/update.rs | 6 +- crates/nu-command/src/strings/char_.rs | 18 +- .../nu-command/src/strings/detect_columns.rs | 7 +- .../src/strings/encode_decode/base64.rs | 2 +- crates/nu-command/src/strings/format/date.rs | 2 +- .../nu-command/src/strings/format/duration.rs | 4 +- .../nu-command/src/strings/format/filesize.rs | 4 +- crates/nu-command/src/strings/parse.rs | 23 +-- crates/nu-command/src/strings/split/chars.rs | 2 +- crates/nu-command/src/strings/split/column.rs | 2 +- crates/nu-command/src/strings/split/list.rs | 4 +- crates/nu-command/src/strings/split/row.rs | 2 +- crates/nu-command/src/strings/split/words.rs | 2 +- .../src/strings/str_/case/capitalize.rs | 2 +- .../src/strings/str_/case/downcase.rs | 2 +- .../nu-command/src/strings/str_/case/mod.rs | 2 +- .../src/strings/str_/case/upcase.rs | 2 +- .../nu-command/src/strings/str_/contains.rs | 4 +- .../nu-command/src/strings/str_/deunicode.rs | 4 +- .../nu-command/src/strings/str_/distance.rs | 4 +- .../nu-command/src/strings/str_/ends_with.rs | 4 +- crates/nu-command/src/strings/str_/expand.rs | 2 +- .../nu-command/src/strings/str_/index_of.rs | 4 +- crates/nu-command/src/strings/str_/join.rs | 50 ++--- crates/nu-command/src/strings/str_/length.rs | 2 +- crates/nu-command/src/strings/str_/replace.rs | 4 +- crates/nu-command/src/strings/str_/reverse.rs | 4 +- .../src/strings/str_/starts_with.rs | 4 +- crates/nu-command/src/strings/str_/stats.rs | 2 +- .../nu-command/src/strings/str_/substring.rs | 4 +- .../nu-command/src/strings/str_/trim/trim_.rs | 2 +- crates/nu-command/src/system/ps.rs | 2 +- .../nu-command/src/system/registry_query.rs | 2 +- crates/nu-command/src/system/run_external.rs | 29 ++- crates/nu-command/src/system/which_.rs | 5 +- crates/nu-command/src/viewers/table.rs | 145 +++++++------- crates/nu-engine/src/eval.rs | 4 +- crates/nu-explore/src/commands/expand.rs | 3 +- crates/nu-explore/src/explore.rs | 3 +- crates/nu-explore/src/lib.rs | 11 +- crates/nu-explore/src/nu_common/mod.rs | 2 - crates/nu-explore/src/nu_common/table.rs | 17 +- crates/nu-explore/src/pager/mod.rs | 13 +- crates/nu-lsp/src/lib.rs | 19 +- crates/nu-plugin-core/src/interface/mod.rs | 10 +- crates/nu-plugin-core/src/interface/tests.rs | 20 +- crates/nu-plugin-engine/src/context.rs | 20 +- crates/nu-plugin-engine/src/interface/mod.rs | 28 +-- .../nu-plugin-engine/src/interface/tests.rs | 30 +-- crates/nu-plugin-test-support/src/lib.rs | 8 +- .../nu-plugin-test-support/src/plugin_test.rs | 10 +- .../tests/lowercase/mod.rs | 9 +- crates/nu-plugin/src/plugin/command.rs | 4 +- crates/nu-plugin/src/plugin/interface/mod.rs | 8 +- .../nu-plugin/src/plugin/interface/tests.rs | 18 +- crates/nu-protocol/src/engine/engine_state.rs | 18 +- crates/nu-protocol/src/errors/shell_error.rs | 7 + .../nu-protocol/src/pipeline/byte_stream.rs | 178 ++++++++---------- .../nu-protocol/src/pipeline/list_stream.rs | 29 ++- crates/nu-protocol/src/pipeline/mod.rs | 2 + .../nu-protocol/src/pipeline/pipeline_data.rs | 112 +++++------ crates/nu-protocol/src/pipeline/signals.rs | 76 ++++++++ crates/nu-protocol/src/value/mod.rs | 7 +- crates/nu-protocol/src/value/range.rs | 53 ++---- crates/nu-table/src/types/expanded.rs | 16 +- crates/nu-table/src/types/general.rs | 12 +- crates/nu-table/src/types/mod.rs | 9 +- crates/nu-utils/src/ctrl_c.rs | 13 -- crates/nu-utils/src/lib.rs | 1 - .../src/commands/collect_bytes.rs | 6 +- .../src/commands/generate.rs | 6 +- crates/nu_plugin_example/src/commands/seq.rs | 5 +- src/main.rs | 11 +- src/signals.rs | 13 +- 246 files changed, 1332 insertions(+), 1234 deletions(-) create mode 100644 crates/nu-protocol/src/pipeline/signals.rs delete mode 100644 crates/nu-utils/src/ctrl_c.rs diff --git a/benches/benchmarks.rs b/benches/benchmarks.rs index 7a23715c8d..ea296f0d06 100644 --- a/benches/benchmarks.rs +++ b/benches/benchmarks.rs @@ -4,11 +4,14 @@ use nu_plugin_protocol::{PluginCallResponse, PluginOutput}; use nu_protocol::{ engine::{EngineState, Stack}, - PipelineData, Span, Spanned, Value, + PipelineData, Signals, Span, Spanned, Value, }; use nu_std::load_standard_library; use nu_utils::{get_default_config, get_default_env}; -use std::rc::Rc; +use std::{ + rc::Rc, + sync::{atomic::AtomicBool, Arc}, +}; use std::hint::black_box; @@ -248,14 +251,12 @@ fn bench_eval_interleave(n: i32) -> impl IntoBenchmarks { ) } -fn bench_eval_interleave_with_ctrlc(n: i32) -> impl IntoBenchmarks { +fn bench_eval_interleave_with_interrupt(n: i32) -> impl IntoBenchmarks { let mut engine = setup_engine(); - engine.ctrlc = Some(std::sync::Arc::new(std::sync::atomic::AtomicBool::new( - false, - ))); + engine.set_signals(Signals::new(Arc::new(AtomicBool::new(false)))); let stack = Stack::new(); bench_command( - &format!("eval_interleave_with_ctrlc_{n}"), + &format!("eval_interleave_with_interrupt_{n}"), &format!("seq 1 {n} | wrap a | interleave {{ seq 1 {n} | wrap b }} | ignore"), stack, engine, @@ -443,9 +444,9 @@ tango_benchmarks!( bench_eval_interleave(100), bench_eval_interleave(1_000), bench_eval_interleave(10_000), - bench_eval_interleave_with_ctrlc(100), - bench_eval_interleave_with_ctrlc(1_000), - bench_eval_interleave_with_ctrlc(10_000), + bench_eval_interleave_with_interrupt(100), + bench_eval_interleave_with_interrupt(1_000), + bench_eval_interleave_with_interrupt(10_000), // For bench_eval_for(1), bench_eval_for(10), diff --git a/crates/nu-cli/src/commands/history/history_.rs b/crates/nu-cli/src/commands/history/history_.rs index 8b0714216e..cdf85eea72 100644 --- a/crates/nu-cli/src/commands/history/history_.rs +++ b/crates/nu-cli/src/commands/history/history_.rs @@ -47,7 +47,7 @@ impl Command for History { if let Some(config_path) = nu_path::config_dir() { let clear = call.has_flag(engine_state, stack, "clear")?; let long = call.has_flag(engine_state, stack, "long")?; - let ctrlc = engine_state.ctrlc.clone(); + let signals = engine_state.signals().clone(); let mut history_path = config_path; history_path.push("nushell"); @@ -107,7 +107,7 @@ impl Command for History { file: history_path.display().to_string(), span: head, })? - .into_pipeline_data(head, ctrlc)), + .into_pipeline_data(head, signals)), HistoryFileFormat::Sqlite => Ok(history_reader .and_then(|h| { h.search(SearchQuery::everything(SearchDirection::Forward, None)) @@ -122,7 +122,7 @@ impl Command for History { file: history_path.display().to_string(), span: head, })? - .into_pipeline_data(head, ctrlc)), + .into_pipeline_data(head, signals)), } } } else { diff --git a/crates/nu-cli/src/nu_highlight.rs b/crates/nu-cli/src/nu_highlight.rs index 07084c6258..f4f38296de 100644 --- a/crates/nu-cli/src/nu_highlight.rs +++ b/crates/nu-cli/src/nu_highlight.rs @@ -32,7 +32,7 @@ impl Command for NuHighlight { ) -> Result { let head = call.head; - let ctrlc = engine_state.ctrlc.clone(); + let signals = engine_state.signals(); let engine_state = std::sync::Arc::new(engine_state.clone()); let config = engine_state.get_config().clone(); @@ -50,7 +50,7 @@ impl Command for NuHighlight { } Err(err) => Value::error(err, head), }, - ctrlc, + signals, ) } diff --git a/crates/nu-cli/src/repl.rs b/crates/nu-cli/src/repl.rs index d2de8f4bea..5b0db96741 100644 --- a/crates/nu-cli/src/repl.rs +++ b/crates/nu-cli/src/repl.rs @@ -43,7 +43,7 @@ use std::{ io::{self, IsTerminal, Write}, panic::{catch_unwind, AssertUnwindSafe}, path::{Path, PathBuf}, - sync::{atomic::Ordering, Arc}, + sync::Arc, time::{Duration, Instant}, }; use sysinfo::System; @@ -271,11 +271,8 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) { perf!("merge env", start_time, use_color); start_time = std::time::Instant::now(); - // Reset the ctrl-c handler - if let Some(ctrlc) = &mut engine_state.ctrlc { - ctrlc.store(false, Ordering::SeqCst); - } - perf!("reset ctrlc", start_time, use_color); + engine_state.reset_signals(); + perf!("reset signals", start_time, use_color); start_time = std::time::Instant::now(); // Right before we start our prompt and take input from the user, diff --git a/crates/nu-cmd-base/src/input_handler.rs b/crates/nu-cmd-base/src/input_handler.rs index d81193e190..7d61f90cb0 100644 --- a/crates/nu-cmd-base/src/input_handler.rs +++ b/crates/nu-cmd-base/src/input_handler.rs @@ -1,5 +1,5 @@ -use nu_protocol::{ast::CellPath, PipelineData, ShellError, Span, Value}; -use std::sync::{atomic::AtomicBool, Arc}; +use nu_protocol::{ast::CellPath, PipelineData, ShellError, Signals, Span, Value}; +use std::sync::Arc; pub trait CmdArgument { fn take_cell_paths(&mut self) -> Option>; @@ -40,7 +40,7 @@ pub fn operate( mut arg: A, input: PipelineData, span: Span, - ctrlc: Option>, + signals: &Signals, ) -> Result where A: CmdArgument + Send + Sync + 'static, @@ -55,7 +55,7 @@ where _ => cmd(&v, &arg, span), } }, - ctrlc, + signals, ), Some(column_paths) => { let arg = Arc::new(arg); @@ -79,7 +79,7 @@ where } v }, - ctrlc, + signals, ) } } diff --git a/crates/nu-cmd-extra/src/extra/bits/and.rs b/crates/nu-cmd-extra/src/extra/bits/and.rs index 538cf6e60f..234b4e5cc1 100644 --- a/crates/nu-cmd-extra/src/extra/bits/and.rs +++ b/crates/nu-cmd-extra/src/extra/bits/and.rs @@ -79,7 +79,7 @@ impl Command for BitsAnd { input.map( move |value| binary_op(&value, &target, little_endian, |(l, r)| l & r, head), - engine_state.ctrlc.clone(), + engine_state.signals(), ) } diff --git a/crates/nu-cmd-extra/src/extra/bits/into.rs b/crates/nu-cmd-extra/src/extra/bits/into.rs index 9891a971f6..9ff8bd0a05 100644 --- a/crates/nu-cmd-extra/src/extra/bits/into.rs +++ b/crates/nu-cmd-extra/src/extra/bits/into.rs @@ -3,6 +3,7 @@ use std::io::{self, Read, Write}; use nu_cmd_base::input_handler::{operate, CmdArgument}; use nu_engine::command_prelude::*; +use nu_protocol::Signals; use num_traits::ToPrimitive; pub struct Arguments { @@ -127,31 +128,36 @@ fn into_bits( )) } else { let args = Arguments { cell_paths }; - operate(action, args, input, call.head, engine_state.ctrlc.clone()) + operate(action, args, input, call.head, engine_state.signals()) } } fn byte_stream_to_bits(stream: ByteStream, head: Span) -> ByteStream { if let Some(mut reader) = stream.reader() { let mut is_first = true; - ByteStream::from_fn(head, None, ByteStreamType::String, move |buffer| { - let mut byte = [0]; - if reader.read(&mut byte[..]).err_span(head)? > 0 { - // Format the byte as bits - if is_first { - is_first = false; + ByteStream::from_fn( + head, + Signals::empty(), + ByteStreamType::String, + move |buffer| { + let mut byte = [0]; + if reader.read(&mut byte[..]).err_span(head)? > 0 { + // Format the byte as bits + if is_first { + is_first = false; + } else { + buffer.push(b' '); + } + write!(buffer, "{:08b}", byte[0]).expect("format failed"); + Ok(true) } else { - buffer.push(b' '); + // EOF + Ok(false) } - write!(buffer, "{:08b}", byte[0]).expect("format failed"); - Ok(true) - } else { - // EOF - Ok(false) - } - }) + }, + ) } else { - ByteStream::read(io::empty(), head, None, ByteStreamType::String) + ByteStream::read(io::empty(), head, Signals::empty(), ByteStreamType::String) } } diff --git a/crates/nu-cmd-extra/src/extra/bits/not.rs b/crates/nu-cmd-extra/src/extra/bits/not.rs index e4c344f137..405cc79d7e 100644 --- a/crates/nu-cmd-extra/src/extra/bits/not.rs +++ b/crates/nu-cmd-extra/src/extra/bits/not.rs @@ -82,7 +82,7 @@ impl Command for BitsNot { number_size, }; - operate(action, args, input, head, engine_state.ctrlc.clone()) + operate(action, args, input, head, engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-cmd-extra/src/extra/bits/or.rs b/crates/nu-cmd-extra/src/extra/bits/or.rs index 2352d65c23..a0af2dc8d0 100644 --- a/crates/nu-cmd-extra/src/extra/bits/or.rs +++ b/crates/nu-cmd-extra/src/extra/bits/or.rs @@ -80,7 +80,7 @@ impl Command for BitsOr { input.map( move |value| binary_op(&value, &target, little_endian, |(l, r)| l | r, head), - engine_state.ctrlc.clone(), + engine_state.signals(), ) } diff --git a/crates/nu-cmd-extra/src/extra/bits/rotate_left.rs b/crates/nu-cmd-extra/src/extra/bits/rotate_left.rs index cbd9d17eb5..5bb9e42f6b 100644 --- a/crates/nu-cmd-extra/src/extra/bits/rotate_left.rs +++ b/crates/nu-cmd-extra/src/extra/bits/rotate_left.rs @@ -86,7 +86,7 @@ impl Command for BitsRol { bits, }; - operate(action, args, input, head, engine_state.ctrlc.clone()) + operate(action, args, input, head, engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-cmd-extra/src/extra/bits/rotate_right.rs b/crates/nu-cmd-extra/src/extra/bits/rotate_right.rs index 0aea603ce1..31e17891a3 100644 --- a/crates/nu-cmd-extra/src/extra/bits/rotate_right.rs +++ b/crates/nu-cmd-extra/src/extra/bits/rotate_right.rs @@ -86,7 +86,7 @@ impl Command for BitsRor { bits, }; - operate(action, args, input, head, engine_state.ctrlc.clone()) + operate(action, args, input, head, engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-cmd-extra/src/extra/bits/shift_left.rs b/crates/nu-cmd-extra/src/extra/bits/shift_left.rs index 049408c24a..6a67a45e0e 100644 --- a/crates/nu-cmd-extra/src/extra/bits/shift_left.rs +++ b/crates/nu-cmd-extra/src/extra/bits/shift_left.rs @@ -88,7 +88,7 @@ impl Command for BitsShl { bits, }; - operate(action, args, input, head, engine_state.ctrlc.clone()) + operate(action, args, input, head, engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-cmd-extra/src/extra/bits/shift_right.rs b/crates/nu-cmd-extra/src/extra/bits/shift_right.rs index d66db68ee5..e45e10ac94 100644 --- a/crates/nu-cmd-extra/src/extra/bits/shift_right.rs +++ b/crates/nu-cmd-extra/src/extra/bits/shift_right.rs @@ -88,7 +88,7 @@ impl Command for BitsShr { bits, }; - operate(action, args, input, head, engine_state.ctrlc.clone()) + operate(action, args, input, head, engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-cmd-extra/src/extra/bits/xor.rs b/crates/nu-cmd-extra/src/extra/bits/xor.rs index 65c3be4e1a..4a71487137 100644 --- a/crates/nu-cmd-extra/src/extra/bits/xor.rs +++ b/crates/nu-cmd-extra/src/extra/bits/xor.rs @@ -80,7 +80,7 @@ impl Command for BitsXor { input.map( move |value| binary_op(&value, &target, little_endian, |(l, r)| l ^ r, head), - engine_state.ctrlc.clone(), + engine_state.signals(), ) } diff --git a/crates/nu-cmd-extra/src/extra/conversions/fmt.rs b/crates/nu-cmd-extra/src/extra/conversions/fmt.rs index fec0745dac..3b682291e7 100644 --- a/crates/nu-cmd-extra/src/extra/conversions/fmt.rs +++ b/crates/nu-cmd-extra/src/extra/conversions/fmt.rs @@ -59,7 +59,7 @@ fn fmt( ) -> Result { let cell_paths: Vec = call.rest(engine_state, stack, 0)?; let args = CellPathOnlyArgs::from(cell_paths); - operate(action, args, input, call.head, engine_state.ctrlc.clone()) + operate(action, args, input, call.head, engine_state.signals()) } fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value { diff --git a/crates/nu-cmd-extra/src/extra/filters/each_while.rs b/crates/nu-cmd-extra/src/extra/filters/each_while.rs index 58679c8eea..1f30e5b9b9 100644 --- a/crates/nu-cmd-extra/src/extra/filters/each_while.rs +++ b/crates/nu-cmd-extra/src/extra/filters/each_while.rs @@ -89,7 +89,7 @@ impl Command for EachWhile { } }) .fuse() - .into_pipeline_data(head, engine_state.ctrlc.clone())) + .into_pipeline_data(head, engine_state.signals().clone())) } PipelineData::ByteStream(stream, ..) => { let span = stream.span(); @@ -107,7 +107,7 @@ impl Command for EachWhile { } }) .fuse() - .into_pipeline_data(head, engine_state.ctrlc.clone())) + .into_pipeline_data(head, engine_state.signals().clone())) } else { Ok(PipelineData::Empty) } diff --git a/crates/nu-cmd-extra/src/extra/filters/update_cells.rs b/crates/nu-cmd-extra/src/extra/filters/update_cells.rs index c90e933410..b102c13c0d 100644 --- a/crates/nu-cmd-extra/src/extra/filters/update_cells.rs +++ b/crates/nu-cmd-extra/src/extra/filters/update_cells.rs @@ -108,7 +108,7 @@ impl Command for UpdateCells { columns, span: head, } - .into_pipeline_data(head, engine_state.ctrlc.clone()) + .into_pipeline_data(head, engine_state.signals().clone()) .set_metadata(metadata)) } } diff --git a/crates/nu-cmd-extra/src/extra/math/arccos.rs b/crates/nu-cmd-extra/src/extra/math/arccos.rs index 120fc4df98..5d0a659380 100644 --- a/crates/nu-cmd-extra/src/extra/math/arccos.rs +++ b/crates/nu-cmd-extra/src/extra/math/arccos.rs @@ -45,7 +45,7 @@ impl Command for SubCommand { } input.map( move |value| operate(value, head, use_degrees), - engine_state.ctrlc.clone(), + engine_state.signals(), ) } diff --git a/crates/nu-cmd-extra/src/extra/math/arccosh.rs b/crates/nu-cmd-extra/src/extra/math/arccosh.rs index 30e0d2cfb6..532a388b3f 100644 --- a/crates/nu-cmd-extra/src/extra/math/arccosh.rs +++ b/crates/nu-cmd-extra/src/extra/math/arccosh.rs @@ -41,10 +41,7 @@ impl Command for SubCommand { if matches!(input, PipelineData::Empty) { return Err(ShellError::PipelineEmpty { dst_span: head }); } - input.map( - move |value| operate(value, head), - engine_state.ctrlc.clone(), - ) + input.map(move |value| operate(value, head), engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-cmd-extra/src/extra/math/arcsin.rs b/crates/nu-cmd-extra/src/extra/math/arcsin.rs index a68e0648ef..a85c438367 100644 --- a/crates/nu-cmd-extra/src/extra/math/arcsin.rs +++ b/crates/nu-cmd-extra/src/extra/math/arcsin.rs @@ -45,7 +45,7 @@ impl Command for SubCommand { } input.map( move |value| operate(value, head, use_degrees), - engine_state.ctrlc.clone(), + engine_state.signals(), ) } diff --git a/crates/nu-cmd-extra/src/extra/math/arcsinh.rs b/crates/nu-cmd-extra/src/extra/math/arcsinh.rs index 67addfdba2..91cb814a32 100644 --- a/crates/nu-cmd-extra/src/extra/math/arcsinh.rs +++ b/crates/nu-cmd-extra/src/extra/math/arcsinh.rs @@ -41,10 +41,7 @@ impl Command for SubCommand { if matches!(input, PipelineData::Empty) { return Err(ShellError::PipelineEmpty { dst_span: head }); } - input.map( - move |value| operate(value, head), - engine_state.ctrlc.clone(), - ) + input.map(move |value| operate(value, head), engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-cmd-extra/src/extra/math/arctan.rs b/crates/nu-cmd-extra/src/extra/math/arctan.rs index 9c14203312..f52c8bd40c 100644 --- a/crates/nu-cmd-extra/src/extra/math/arctan.rs +++ b/crates/nu-cmd-extra/src/extra/math/arctan.rs @@ -45,7 +45,7 @@ impl Command for SubCommand { } input.map( move |value| operate(value, head, use_degrees), - engine_state.ctrlc.clone(), + engine_state.signals(), ) } diff --git a/crates/nu-cmd-extra/src/extra/math/arctanh.rs b/crates/nu-cmd-extra/src/extra/math/arctanh.rs index 920e56eeb6..7791b56948 100644 --- a/crates/nu-cmd-extra/src/extra/math/arctanh.rs +++ b/crates/nu-cmd-extra/src/extra/math/arctanh.rs @@ -41,10 +41,7 @@ impl Command for SubCommand { if matches!(input, PipelineData::Empty) { return Err(ShellError::PipelineEmpty { dst_span: head }); } - input.map( - move |value| operate(value, head), - engine_state.ctrlc.clone(), - ) + input.map(move |value| operate(value, head), engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-cmd-extra/src/extra/math/cos.rs b/crates/nu-cmd-extra/src/extra/math/cos.rs index 633c131b8b..252c7dbbd6 100644 --- a/crates/nu-cmd-extra/src/extra/math/cos.rs +++ b/crates/nu-cmd-extra/src/extra/math/cos.rs @@ -44,7 +44,7 @@ impl Command for SubCommand { } input.map( move |value| operate(value, head, use_degrees), - engine_state.ctrlc.clone(), + engine_state.signals(), ) } diff --git a/crates/nu-cmd-extra/src/extra/math/cosh.rs b/crates/nu-cmd-extra/src/extra/math/cosh.rs index a772540b5c..e46d3c4df8 100644 --- a/crates/nu-cmd-extra/src/extra/math/cosh.rs +++ b/crates/nu-cmd-extra/src/extra/math/cosh.rs @@ -41,10 +41,7 @@ impl Command for SubCommand { if matches!(input, PipelineData::Empty) { return Err(ShellError::PipelineEmpty { dst_span: head }); } - input.map( - move |value| operate(value, head), - engine_state.ctrlc.clone(), - ) + input.map(move |value| operate(value, head), engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-cmd-extra/src/extra/math/exp.rs b/crates/nu-cmd-extra/src/extra/math/exp.rs index b89d6f553f..d8f6a52899 100644 --- a/crates/nu-cmd-extra/src/extra/math/exp.rs +++ b/crates/nu-cmd-extra/src/extra/math/exp.rs @@ -41,10 +41,7 @@ impl Command for SubCommand { if matches!(input, PipelineData::Empty) { return Err(ShellError::PipelineEmpty { dst_span: head }); } - input.map( - move |value| operate(value, head), - engine_state.ctrlc.clone(), - ) + input.map(move |value| operate(value, head), engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-cmd-extra/src/extra/math/ln.rs b/crates/nu-cmd-extra/src/extra/math/ln.rs index dd9782b467..694192bc8e 100644 --- a/crates/nu-cmd-extra/src/extra/math/ln.rs +++ b/crates/nu-cmd-extra/src/extra/math/ln.rs @@ -41,10 +41,7 @@ impl Command for SubCommand { if matches!(input, PipelineData::Empty) { return Err(ShellError::PipelineEmpty { dst_span: head }); } - input.map( - move |value| operate(value, head), - engine_state.ctrlc.clone(), - ) + input.map(move |value| operate(value, head), engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-cmd-extra/src/extra/math/sin.rs b/crates/nu-cmd-extra/src/extra/math/sin.rs index 883007d1ed..0caedbabe7 100644 --- a/crates/nu-cmd-extra/src/extra/math/sin.rs +++ b/crates/nu-cmd-extra/src/extra/math/sin.rs @@ -44,7 +44,7 @@ impl Command for SubCommand { } input.map( move |value| operate(value, head, use_degrees), - engine_state.ctrlc.clone(), + engine_state.signals(), ) } diff --git a/crates/nu-cmd-extra/src/extra/math/sinh.rs b/crates/nu-cmd-extra/src/extra/math/sinh.rs index c768dba739..d40db3bcb5 100644 --- a/crates/nu-cmd-extra/src/extra/math/sinh.rs +++ b/crates/nu-cmd-extra/src/extra/math/sinh.rs @@ -41,10 +41,7 @@ impl Command for SubCommand { if matches!(input, PipelineData::Empty) { return Err(ShellError::PipelineEmpty { dst_span: head }); } - input.map( - move |value| operate(value, head), - engine_state.ctrlc.clone(), - ) + input.map(move |value| operate(value, head), engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-cmd-extra/src/extra/math/tan.rs b/crates/nu-cmd-extra/src/extra/math/tan.rs index e10807279d..97c5d2ff93 100644 --- a/crates/nu-cmd-extra/src/extra/math/tan.rs +++ b/crates/nu-cmd-extra/src/extra/math/tan.rs @@ -44,7 +44,7 @@ impl Command for SubCommand { } input.map( move |value| operate(value, head, use_degrees), - engine_state.ctrlc.clone(), + engine_state.signals(), ) } diff --git a/crates/nu-cmd-extra/src/extra/math/tanh.rs b/crates/nu-cmd-extra/src/extra/math/tanh.rs index 4d09f93cf4..6679d04fe5 100644 --- a/crates/nu-cmd-extra/src/extra/math/tanh.rs +++ b/crates/nu-cmd-extra/src/extra/math/tanh.rs @@ -41,10 +41,7 @@ impl Command for SubCommand { if matches!(input, PipelineData::Empty) { return Err(ShellError::PipelineEmpty { dst_span: head }); } - input.map( - move |value| operate(value, head), - engine_state.ctrlc.clone(), - ) + input.map(move |value| operate(value, head), engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-cmd-extra/src/extra/platform/ansi/gradient.rs b/crates/nu-cmd-extra/src/extra/platform/ansi/gradient.rs index 21c7e42a61..5934b57a5d 100644 --- a/crates/nu-cmd-extra/src/extra/platform/ansi/gradient.rs +++ b/crates/nu-cmd-extra/src/extra/platform/ansi/gradient.rs @@ -140,7 +140,7 @@ fn operate( ret } }, - engine_state.ctrlc.clone(), + engine_state.signals(), ) } diff --git a/crates/nu-cmd-extra/src/extra/strings/encode_decode/hex.rs b/crates/nu-cmd-extra/src/extra/strings/encode_decode/hex.rs index 7628d6e240..be681b382a 100644 --- a/crates/nu-cmd-extra/src/extra/strings/encode_decode/hex.rs +++ b/crates/nu-cmd-extra/src/extra/strings/encode_decode/hex.rs @@ -88,7 +88,7 @@ pub fn operate( cell_paths, }; - general_operate(action, args, input, call.head, engine_state.ctrlc.clone()) + general_operate(action, args, input, call.head, engine_state.signals()) } fn action( diff --git a/crates/nu-cmd-extra/src/extra/strings/format/command.rs b/crates/nu-cmd-extra/src/extra/strings/format/command.rs index 50e89e61e3..08df92ef40 100644 --- a/crates/nu-cmd-extra/src/extra/strings/format/command.rs +++ b/crates/nu-cmd-extra/src/extra/strings/format/command.rs @@ -220,7 +220,7 @@ fn format( } } - Ok(ListStream::new(list.into_iter(), head_span, engine_state.ctrlc.clone()).into()) + Ok(ListStream::new(list.into_iter(), head_span, engine_state.signals().clone()).into()) } // Unwrapping this ShellError is a bit unfortunate. // Ideally, its Span would be preserved. diff --git a/crates/nu-cmd-extra/src/extra/strings/str_/case/mod.rs b/crates/nu-cmd-extra/src/extra/strings/str_/case/mod.rs index 980a1b83fc..bfa54921e2 100644 --- a/crates/nu-cmd-extra/src/extra/strings/str_/case/mod.rs +++ b/crates/nu-cmd-extra/src/extra/strings/str_/case/mod.rs @@ -44,7 +44,7 @@ where case_operation, cell_paths, }; - general_operate(action, args, input, call.head, engine_state.ctrlc.clone()) + general_operate(action, args, input, call.head, engine_state.signals()) } fn action(input: &Value, args: &Arguments, head: Span) -> Value diff --git a/crates/nu-cmd-lang/src/core_commands/for_.rs b/crates/nu-cmd-lang/src/core_commands/for_.rs index 9410be74c7..1e90e5f06d 100644 --- a/crates/nu-cmd-lang/src/core_commands/for_.rs +++ b/crates/nu-cmd-lang/src/core_commands/for_.rs @@ -1,5 +1,5 @@ use nu_engine::{command_prelude::*, get_eval_block, get_eval_expression}; -use nu_protocol::engine::CommandType; +use nu_protocol::{engine::CommandType, Signals}; #[derive(Clone)] pub struct For; @@ -72,7 +72,6 @@ impl Command for For { let value = eval_expression(engine_state, stack, keyword_expr)?; - let ctrlc = engine_state.ctrlc.clone(); let engine_state = engine_state.clone(); let block = engine_state.get_block(block_id); @@ -82,9 +81,7 @@ impl Command for For { match value { Value::List { vals, .. } => { for x in vals.into_iter() { - if nu_utils::ctrl_c::was_pressed(&ctrlc) { - break; - } + engine_state.signals().check(head)?; // with_env() is used here to ensure that each iteration uses // a different set of environment variables. @@ -116,7 +113,8 @@ impl Command for For { } } Value::Range { val, .. } => { - for x in val.into_range_iter(span, ctrlc) { + for x in val.into_range_iter(span, Signals::empty()) { + engine_state.signals().check(head)?; stack.add_var(var_id, x); match eval_block(&engine_state, stack, block, PipelineData::empty()) { diff --git a/crates/nu-cmd-lang/src/core_commands/loop_.rs b/crates/nu-cmd-lang/src/core_commands/loop_.rs index a9c642ca3c..86e18389de 100644 --- a/crates/nu-cmd-lang/src/core_commands/loop_.rs +++ b/crates/nu-cmd-lang/src/core_commands/loop_.rs @@ -37,6 +37,7 @@ impl Command for Loop { call: &Call, _input: PipelineData, ) -> Result { + let head = call.head; let block_id = call .positional_nth(0) .expect("checked through parser") @@ -49,9 +50,7 @@ impl Command for Loop { let stack = &mut stack.push_redirection(None, None); loop { - if nu_utils::ctrl_c::was_pressed(&engine_state.ctrlc) { - break; - } + engine_state.signals().check(head)?; match eval_block(engine_state, stack, block, PipelineData::empty()) { Err(ShellError::Break { .. }) => { diff --git a/crates/nu-cmd-lang/src/core_commands/while_.rs b/crates/nu-cmd-lang/src/core_commands/while_.rs index 646b95c82e..22bb4c5dbd 100644 --- a/crates/nu-cmd-lang/src/core_commands/while_.rs +++ b/crates/nu-cmd-lang/src/core_commands/while_.rs @@ -46,6 +46,7 @@ impl Command for While { call: &Call, _input: PipelineData, ) -> Result { + let head = call.head; let cond = call.positional_nth(0).expect("checked through parser"); let block_id = call .positional_nth(1) @@ -59,9 +60,7 @@ impl Command for While { let stack = &mut stack.push_redirection(None, None); loop { - if nu_utils::ctrl_c::was_pressed(&engine_state.ctrlc) { - break; - } + engine_state.signals().check(head)?; let result = eval_expression(engine_state, stack, cond)?; diff --git a/crates/nu-command/src/bytes/add.rs b/crates/nu-command/src/bytes/add.rs index 8514718cfd..ab31f74b12 100644 --- a/crates/nu-command/src/bytes/add.rs +++ b/crates/nu-command/src/bytes/add.rs @@ -78,7 +78,7 @@ impl Command for BytesAdd { end, cell_paths, }; - operate(add, arg, input, call.head, engine_state.ctrlc.clone()) + operate(add, arg, input, call.head, engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-command/src/bytes/at.rs b/crates/nu-command/src/bytes/at.rs index c5164c3550..5e95f4fd62 100644 --- a/crates/nu-command/src/bytes/at.rs +++ b/crates/nu-command/src/bytes/at.rs @@ -83,7 +83,7 @@ impl Command for BytesAt { cell_paths, }; - operate(action, args, input, call.head, engine_state.ctrlc.clone()) + operate(action, args, input, call.head, engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-command/src/bytes/collect.rs b/crates/nu-command/src/bytes/collect.rs index 74ea3e5d14..afb70bfb36 100644 --- a/crates/nu-command/src/bytes/collect.rs +++ b/crates/nu-command/src/bytes/collect.rs @@ -60,7 +60,12 @@ impl Command for BytesCollect { ) .flatten(); - let output = ByteStream::from_result_iter(iter, span, None, ByteStreamType::Binary); + let output = ByteStream::from_result_iter( + iter, + span, + engine_state.signals().clone(), + ByteStreamType::Binary, + ); Ok(PipelineData::ByteStream(output, metadata)) } diff --git a/crates/nu-command/src/bytes/ends_with.rs b/crates/nu-command/src/bytes/ends_with.rs index d6174a189c..8e3966716c 100644 --- a/crates/nu-command/src/bytes/ends_with.rs +++ b/crates/nu-command/src/bytes/ends_with.rs @@ -102,7 +102,7 @@ impl Command for BytesEndsWith { pattern, cell_paths, }; - operate(ends_with, arg, input, head, engine_state.ctrlc.clone()) + operate(ends_with, arg, input, head, engine_state.signals()) } } diff --git a/crates/nu-command/src/bytes/index_of.rs b/crates/nu-command/src/bytes/index_of.rs index bdf51b24d9..e10bd6c200 100644 --- a/crates/nu-command/src/bytes/index_of.rs +++ b/crates/nu-command/src/bytes/index_of.rs @@ -71,7 +71,7 @@ impl Command for BytesIndexOf { all: call.has_flag(engine_state, stack, "all")?, cell_paths, }; - operate(index_of, arg, input, call.head, engine_state.ctrlc.clone()) + operate(index_of, arg, input, call.head, engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-command/src/bytes/length.rs b/crates/nu-command/src/bytes/length.rs index aaaf23e0a5..78b3d31eac 100644 --- a/crates/nu-command/src/bytes/length.rs +++ b/crates/nu-command/src/bytes/length.rs @@ -46,7 +46,7 @@ impl Command for BytesLen { ) -> Result { let cell_paths: Vec = call.rest(engine_state, stack, 1)?; let arg = CellPathOnlyArgs::from(cell_paths); - operate(length, arg, input, call.head, engine_state.ctrlc.clone()) + operate(length, arg, input, call.head, engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-command/src/bytes/remove.rs b/crates/nu-command/src/bytes/remove.rs index 9afef07e8b..34d3c427f4 100644 --- a/crates/nu-command/src/bytes/remove.rs +++ b/crates/nu-command/src/bytes/remove.rs @@ -73,7 +73,7 @@ impl Command for BytesRemove { all: call.has_flag(engine_state, stack, "all")?, }; - operate(remove, arg, input, call.head, engine_state.ctrlc.clone()) + operate(remove, arg, input, call.head, engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-command/src/bytes/replace.rs b/crates/nu-command/src/bytes/replace.rs index ab7ede7588..db2b6fb790 100644 --- a/crates/nu-command/src/bytes/replace.rs +++ b/crates/nu-command/src/bytes/replace.rs @@ -73,7 +73,7 @@ impl Command for BytesReplace { all: call.has_flag(engine_state, stack, "all")?, }; - operate(replace, arg, input, call.head, engine_state.ctrlc.clone()) + operate(replace, arg, input, call.head, engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-command/src/bytes/reverse.rs b/crates/nu-command/src/bytes/reverse.rs index 171add213d..fd769eaa40 100644 --- a/crates/nu-command/src/bytes/reverse.rs +++ b/crates/nu-command/src/bytes/reverse.rs @@ -42,7 +42,7 @@ impl Command for BytesReverse { ) -> Result { let cell_paths: Vec = call.rest(engine_state, stack, 0)?; let arg = CellPathOnlyArgs::from(cell_paths); - operate(reverse, arg, input, call.head, engine_state.ctrlc.clone()) + operate(reverse, arg, input, call.head, engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-command/src/bytes/starts_with.rs b/crates/nu-command/src/bytes/starts_with.rs index 92cc16f02c..89cc4f7afc 100644 --- a/crates/nu-command/src/bytes/starts_with.rs +++ b/crates/nu-command/src/bytes/starts_with.rs @@ -79,7 +79,7 @@ impl Command for BytesStartsWith { pattern, cell_paths, }; - operate(starts_with, arg, input, head, engine_state.ctrlc.clone()) + operate(starts_with, arg, input, head, engine_state.signals()) } } diff --git a/crates/nu-command/src/conversions/fill.rs b/crates/nu-command/src/conversions/fill.rs index 6507e4a368..eaf8c05da1 100644 --- a/crates/nu-command/src/conversions/fill.rs +++ b/crates/nu-command/src/conversions/fill.rs @@ -165,7 +165,7 @@ fn fill( cell_paths, }; - operate(action, arg, input, call.head, engine_state.ctrlc.clone()) + operate(action, arg, input, call.head, engine_state.signals()) } fn action(input: &Value, args: &Arguments, span: Span) -> Value { diff --git a/crates/nu-command/src/conversions/into/binary.rs b/crates/nu-command/src/conversions/into/binary.rs index 8eb7715754..e5c34ae6bc 100644 --- a/crates/nu-command/src/conversions/into/binary.rs +++ b/crates/nu-command/src/conversions/into/binary.rs @@ -138,7 +138,7 @@ fn into_binary( cell_paths, compact: call.has_flag(engine_state, stack, "compact")?, }; - operate(action, args, input, head, engine_state.ctrlc.clone()) + operate(action, args, input, head, engine_state.signals()) } } diff --git a/crates/nu-command/src/conversions/into/bool.rs b/crates/nu-command/src/conversions/into/bool.rs index b1d433cb93..0fcd33b4a3 100644 --- a/crates/nu-command/src/conversions/into/bool.rs +++ b/crates/nu-command/src/conversions/into/bool.rs @@ -107,7 +107,7 @@ fn into_bool( ) -> Result { let cell_paths: Vec = call.rest(engine_state, stack, 0)?; let args = CellPathOnlyArgs::from(cell_paths); - operate(action, args, input, call.head, engine_state.ctrlc.clone()) + operate(action, args, input, call.head, engine_state.signals()) } fn string_to_boolean(s: &str, span: Span) -> Result { diff --git a/crates/nu-command/src/conversions/into/datetime.rs b/crates/nu-command/src/conversions/into/datetime.rs index b928bebe23..8dc0340ba1 100644 --- a/crates/nu-command/src/conversions/into/datetime.rs +++ b/crates/nu-command/src/conversions/into/datetime.rs @@ -141,7 +141,7 @@ impl Command for SubCommand { zone_options, cell_paths, }; - operate(action, args, input, call.head, engine_state.ctrlc.clone()) + operate(action, args, input, call.head, engine_state.signals()) } } diff --git a/crates/nu-command/src/conversions/into/duration.rs b/crates/nu-command/src/conversions/into/duration.rs index 21494f3bcc..b459dd04b1 100644 --- a/crates/nu-command/src/conversions/into/duration.rs +++ b/crates/nu-command/src/conversions/into/duration.rs @@ -166,7 +166,7 @@ fn into_duration( ret } }, - engine_state.ctrlc.clone(), + engine_state.signals(), ) } diff --git a/crates/nu-command/src/conversions/into/filesize.rs b/crates/nu-command/src/conversions/into/filesize.rs index 010c031b2a..5be167e30c 100644 --- a/crates/nu-command/src/conversions/into/filesize.rs +++ b/crates/nu-command/src/conversions/into/filesize.rs @@ -68,7 +68,7 @@ impl Command for SubCommand { ) -> Result { let cell_paths: Vec = call.rest(engine_state, stack, 0)?; let args = CellPathOnlyArgs::from(cell_paths); - operate(action, args, input, call.head, engine_state.ctrlc.clone()) + operate(action, args, input, call.head, engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-command/src/conversions/into/float.rs b/crates/nu-command/src/conversions/into/float.rs index 9ccd7ea03f..43556bb5ef 100644 --- a/crates/nu-command/src/conversions/into/float.rs +++ b/crates/nu-command/src/conversions/into/float.rs @@ -49,7 +49,7 @@ impl Command for SubCommand { ) -> Result { let cell_paths: Vec = call.rest(engine_state, stack, 0)?; let args = CellPathOnlyArgs::from(cell_paths); - operate(action, args, input, call.head, engine_state.ctrlc.clone()) + operate(action, args, input, call.head, engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-command/src/conversions/into/glob.rs b/crates/nu-command/src/conversions/into/glob.rs index e5d03093f4..ffc3655330 100644 --- a/crates/nu-command/src/conversions/into/glob.rs +++ b/crates/nu-command/src/conversions/into/glob.rs @@ -87,7 +87,7 @@ fn glob_helper( Ok(Value::glob(stream.into_string()?, false, head).into_pipeline_data()) } else { let args = Arguments { cell_paths }; - operate(action, args, input, head, engine_state.ctrlc.clone()) + operate(action, args, input, head, engine_state.signals()) } } diff --git a/crates/nu-command/src/conversions/into/int.rs b/crates/nu-command/src/conversions/into/int.rs index a3f1c92a4f..d4bfa61639 100644 --- a/crates/nu-command/src/conversions/into/int.rs +++ b/crates/nu-command/src/conversions/into/int.rs @@ -158,7 +158,7 @@ impl Command for SubCommand { signed, cell_paths, }; - operate(action, args, input, call.head, engine_state.ctrlc.clone()) + operate(action, args, input, call.head, engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-command/src/conversions/into/record.rs b/crates/nu-command/src/conversions/into/record.rs index e867f06e15..1f332d9c85 100644 --- a/crates/nu-command/src/conversions/into/record.rs +++ b/crates/nu-command/src/conversions/into/record.rs @@ -125,7 +125,7 @@ fn into_record( ), }, Value::Range { val, .. } => Value::record( - val.into_range_iter(span, engine_state.ctrlc.clone()) + val.into_range_iter(span, engine_state.signals().clone()) .enumerate() .map(|(idx, val)| (format!("{idx}"), val)) .collect(), diff --git a/crates/nu-command/src/conversions/into/string.rs b/crates/nu-command/src/conversions/into/string.rs index 7c7d69cf4e..c394f9fd21 100644 --- a/crates/nu-command/src/conversions/into/string.rs +++ b/crates/nu-command/src/conversions/into/string.rs @@ -180,7 +180,7 @@ fn string_helper( cell_paths, config, }; - operate(action, args, input, head, engine_state.ctrlc.clone()) + operate(action, args, input, head, engine_state.signals()) } } diff --git a/crates/nu-command/src/conversions/into/value.rs b/crates/nu-command/src/conversions/into/value.rs index 4bf7e68f53..e8787e75bb 100644 --- a/crates/nu-command/src/conversions/into/value.rs +++ b/crates/nu-command/src/conversions/into/value.rs @@ -57,14 +57,12 @@ impl Command for IntoValue { call: &Call, input: PipelineData, ) -> Result { - let engine_state = engine_state.clone(); let metadata = input.metadata(); - let ctrlc = engine_state.ctrlc.clone(); let span = call.head; - let display_as_filesizes = call.has_flag(&engine_state, stack, "prefer-filesizes")?; + let display_as_filesizes = call.has_flag(engine_state, stack, "prefer-filesizes")?; // the columns to update - let columns: Option = call.get_flag(&engine_state, stack, "columns")?; + let columns: Option = call.get_flag(engine_state, stack, "columns")?; let columns: Option> = match columns { Some(val) => Some( val.into_list()? @@ -81,7 +79,7 @@ impl Command for IntoValue { display_as_filesizes, span, } - .into_pipeline_data(span, ctrlc) + .into_pipeline_data(span, engine_state.signals().clone()) .set_metadata(metadata)) } } diff --git a/crates/nu-command/src/database/commands/into_sqlite.rs b/crates/nu-command/src/database/commands/into_sqlite.rs index f3a2e3622a..88a6114ab3 100644 --- a/crates/nu-command/src/database/commands/into_sqlite.rs +++ b/crates/nu-command/src/database/commands/into_sqlite.rs @@ -2,13 +2,8 @@ use crate::database::values::sqlite::{open_sqlite_db, values_to_sql}; use nu_engine::command_prelude::*; use itertools::Itertools; -use std::{ - path::Path, - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, - }, -}; +use nu_protocol::Signals; +use std::path::Path; pub const DEFAULT_TABLE_NAME: &str = "main"; @@ -188,23 +183,18 @@ fn operate( let file_name: Spanned = call.req(engine_state, stack, 0)?; let table_name: Option> = call.get_flag(engine_state, stack, "table-name")?; let table = Table::new(&file_name, table_name)?; - let ctrl_c = engine_state.ctrlc.clone(); - - match action(input, table, span, ctrl_c) { - Ok(val) => Ok(val.into_pipeline_data()), - Err(e) => Err(e), - } + Ok(action(input, table, span, engine_state.signals())?.into_pipeline_data()) } fn action( input: PipelineData, table: Table, span: Span, - ctrl_c: Option>, + signals: &Signals, ) -> Result { match input { PipelineData::ListStream(stream, _) => { - insert_in_transaction(stream.into_iter(), span, table, ctrl_c) + insert_in_transaction(stream.into_iter(), span, table, signals) } PipelineData::Value( Value::List { @@ -212,9 +202,9 @@ fn action( internal_span, }, _, - ) => insert_in_transaction(vals.into_iter(), internal_span, table, ctrl_c), + ) => insert_in_transaction(vals.into_iter(), internal_span, table, signals), PipelineData::Value(val, _) => { - insert_in_transaction(std::iter::once(val), span, table, ctrl_c) + insert_in_transaction(std::iter::once(val), span, table, signals) } _ => Err(ShellError::OnlySupportsThisInputType { exp_input_type: "list".into(), @@ -229,7 +219,7 @@ fn insert_in_transaction( stream: impl Iterator, span: Span, mut table: Table, - ctrl_c: Option>, + signals: &Signals, ) -> Result { let mut stream = stream.peekable(); let first_val = match stream.peek() { @@ -251,17 +241,15 @@ fn insert_in_transaction( let tx = table.try_init(&first_val)?; for stream_value in stream { - if let Some(ref ctrlc) = ctrl_c { - if ctrlc.load(Ordering::Relaxed) { - tx.rollback().map_err(|e| ShellError::GenericError { - error: "Failed to rollback SQLite transaction".into(), - msg: e.to_string(), - span: None, - help: None, - inner: Vec::new(), - })?; - return Err(ShellError::InterruptedByUser { span: None }); - } + if let Err(err) = signals.check(span) { + tx.rollback().map_err(|e| ShellError::GenericError { + error: "Failed to rollback SQLite transaction".into(), + msg: e.to_string(), + span: None, + help: None, + inner: Vec::new(), + })?; + return Err(err); } let val = stream_value.as_record()?; diff --git a/crates/nu-command/src/database/values/sqlite.rs b/crates/nu-command/src/database/values/sqlite.rs index 483da7672e..f253ba1cbd 100644 --- a/crates/nu-command/src/database/values/sqlite.rs +++ b/crates/nu-command/src/database/values/sqlite.rs @@ -2,7 +2,7 @@ use super::definitions::{ db_column::DbColumn, db_constraint::DbConstraint, db_foreignkey::DbForeignKey, db_index::DbIndex, db_table::DbTable, }; -use nu_protocol::{CustomValue, PipelineData, Record, ShellError, Span, Spanned, Value}; +use nu_protocol::{CustomValue, PipelineData, Record, ShellError, Signals, Span, Spanned, Value}; use rusqlite::{ types::ValueRef, Connection, DatabaseName, Error as SqliteError, OpenFlags, Row, Statement, ToSql, @@ -12,7 +12,6 @@ use std::{ fs::File, io::Read, path::{Path, PathBuf}, - sync::{atomic::AtomicBool, Arc}, }; const SQLITE_MAGIC_BYTES: &[u8] = "SQLite format 3\0".as_bytes(); @@ -24,25 +23,21 @@ pub struct SQLiteDatabase { // 1) YAGNI, 2) it's not obvious how cloning a connection could work, 3) state // management gets tricky quick. Revisit this approach if we find a compelling use case. pub path: PathBuf, - #[serde(skip)] + #[serde(skip, default = "Signals::empty")] // this understandably can't be serialized. think that's OK, I'm not aware of a // reason why a CustomValue would be serialized outside of a plugin - ctrlc: Option>, + signals: Signals, } impl SQLiteDatabase { - pub fn new(path: &Path, ctrlc: Option>) -> Self { + pub fn new(path: &Path, signals: Signals) -> Self { Self { path: PathBuf::from(path), - ctrlc, + signals, } } - pub fn try_from_path( - path: &Path, - span: Span, - ctrlc: Option>, - ) -> Result { + pub fn try_from_path(path: &Path, span: Span, signals: Signals) -> Result { let mut file = File::open(path).map_err(|e| ShellError::ReadingFile { msg: e.to_string(), span, @@ -56,7 +51,7 @@ impl SQLiteDatabase { }) .and_then(|_| { if buf == SQLITE_MAGIC_BYTES { - Ok(SQLiteDatabase::new(path, ctrlc)) + Ok(SQLiteDatabase::new(path, signals)) } else { Err(ShellError::ReadingFile { msg: "Not a SQLite file".into(), @@ -72,7 +67,7 @@ impl SQLiteDatabase { Value::Custom { val, .. } => match val.as_any().downcast_ref::() { Some(db) => Ok(Self { path: db.path.clone(), - ctrlc: db.ctrlc.clone(), + signals: db.signals.clone(), }), None => Err(ShellError::CantConvert { to_type: "database".into(), @@ -107,16 +102,8 @@ impl SQLiteDatabase { call_span: Span, ) -> Result { let conn = open_sqlite_db(&self.path, call_span)?; - - let stream = run_sql_query(conn, sql, params, self.ctrlc.clone()).map_err(|e| { - ShellError::GenericError { - error: "Failed to query SQLite database".into(), - msg: e.to_string(), - span: Some(sql.span), - help: None, - inner: vec![], - } - })?; + let stream = run_sql_query(conn, sql, params, &self.signals) + .map_err(|e| e.into_shell_error(sql.span, "Failed to query SQLite database"))?; Ok(stream) } @@ -352,12 +339,7 @@ impl SQLiteDatabase { impl CustomValue for SQLiteDatabase { fn clone_value(&self, span: Span) -> Value { - let cloned = SQLiteDatabase { - path: self.path.clone(), - ctrlc: self.ctrlc.clone(), - }; - - Value::custom(Box::new(cloned), span) + Value::custom(Box::new(self.clone()), span) } fn type_name(&self) -> String { @@ -366,13 +348,8 @@ impl CustomValue for SQLiteDatabase { fn to_base_value(&self, span: Span) -> Result { let db = open_sqlite_db(&self.path, span)?; - read_entire_sqlite_db(db, span, self.ctrlc.clone()).map_err(|e| ShellError::GenericError { - error: "Failed to read from SQLite database".into(), - msg: e.to_string(), - span: Some(span), - help: None, - inner: vec![], - }) + read_entire_sqlite_db(db, span, &self.signals) + .map_err(|e| e.into_shell_error(span, "Failed to read from SQLite database")) } fn as_any(&self) -> &dyn std::any::Any { @@ -396,20 +373,12 @@ impl CustomValue for SQLiteDatabase { fn follow_path_string( &self, _self_span: Span, - _column_name: String, + column_name: String, path_span: Span, ) -> Result { let db = open_sqlite_db(&self.path, path_span)?; - - read_single_table(db, _column_name, path_span, self.ctrlc.clone()).map_err(|e| { - ShellError::GenericError { - error: "Failed to read from SQLite database".into(), - msg: e.to_string(), - span: Some(path_span), - help: None, - inner: vec![], - } - }) + read_single_table(db, column_name, path_span, &self.signals) + .map_err(|e| e.into_shell_error(path_span, "Failed to read from SQLite database")) } fn typetag_name(&self) -> &'static str { @@ -426,12 +395,12 @@ pub fn open_sqlite_db(path: &Path, call_span: Span) -> Result, params: NuSqlParams, - ctrlc: Option>, -) -> Result { + signals: &Signals, +) -> Result { let stmt = conn.prepare(&sql.item)?; - - prepared_statement_to_nu_list(stmt, params, sql.span, ctrlc) + prepared_statement_to_nu_list(stmt, params, sql.span, signals) } // This is taken from to text local_into_string but tweaks it a bit so that certain formatting does not happen @@ -534,23 +502,56 @@ pub fn nu_value_to_params(value: Value) -> Result { } } +#[derive(Debug)] +enum SqliteOrShellError { + SqliteError(SqliteError), + ShellError(ShellError), +} + +impl From for SqliteOrShellError { + fn from(error: SqliteError) -> Self { + Self::SqliteError(error) + } +} + +impl From for SqliteOrShellError { + fn from(error: ShellError) -> Self { + Self::ShellError(error) + } +} + +impl SqliteOrShellError { + fn into_shell_error(self, span: Span, msg: &str) -> ShellError { + match self { + Self::SqliteError(err) => ShellError::GenericError { + error: msg.into(), + msg: err.to_string(), + span: Some(span), + help: None, + inner: Vec::new(), + }, + Self::ShellError(err) => err, + } + } +} + fn read_single_table( conn: Connection, table_name: String, call_span: Span, - ctrlc: Option>, -) -> Result { + signals: &Signals, +) -> Result { // TODO: Should use params here? let stmt = conn.prepare(&format!("SELECT * FROM [{table_name}]"))?; - prepared_statement_to_nu_list(stmt, NuSqlParams::default(), call_span, ctrlc) + prepared_statement_to_nu_list(stmt, NuSqlParams::default(), call_span, signals) } fn prepared_statement_to_nu_list( mut stmt: Statement, params: NuSqlParams, call_span: Span, - ctrlc: Option>, -) -> Result { + signals: &Signals, +) -> Result { let column_names = stmt .column_names() .into_iter() @@ -576,11 +577,7 @@ fn prepared_statement_to_nu_list( let mut row_values = vec![]; for row_result in row_results { - if nu_utils::ctrl_c::was_pressed(&ctrlc) { - // return whatever we have so far, let the caller decide whether to use it - return Ok(Value::list(row_values, call_span)); - } - + signals.check(call_span)?; if let Ok(row_value) = row_result { row_values.push(row_value); } @@ -606,11 +603,7 @@ fn prepared_statement_to_nu_list( let mut row_values = vec![]; for row_result in row_results { - if nu_utils::ctrl_c::was_pressed(&ctrlc) { - // return whatever we have so far, let the caller decide whether to use it - return Ok(Value::list(row_values, call_span)); - } - + signals.check(call_span)?; if let Ok(row_value) = row_result { row_values.push(row_value); } @@ -626,8 +619,8 @@ fn prepared_statement_to_nu_list( fn read_entire_sqlite_db( conn: Connection, call_span: Span, - ctrlc: Option>, -) -> Result { + signals: &Signals, +) -> Result { let mut tables = Record::new(); let mut get_table_names = @@ -638,12 +631,8 @@ fn read_entire_sqlite_db( let table_name: String = row?; // TODO: Should use params here? let table_stmt = conn.prepare(&format!("select * from [{table_name}]"))?; - let rows = prepared_statement_to_nu_list( - table_stmt, - NuSqlParams::default(), - call_span, - ctrlc.clone(), - )?; + let rows = + prepared_statement_to_nu_list(table_stmt, NuSqlParams::default(), call_span, signals)?; tables.push(table_name, rows); } @@ -710,7 +699,7 @@ mod test { #[test] fn can_read_empty_db() { let db = open_connection_in_memory().unwrap(); - let converted_db = read_entire_sqlite_db(db, Span::test_data(), None).unwrap(); + let converted_db = read_entire_sqlite_db(db, Span::test_data(), &Signals::empty()).unwrap(); let expected = Value::test_record(Record::new()); @@ -730,7 +719,7 @@ mod test { [], ) .unwrap(); - let converted_db = read_entire_sqlite_db(db, Span::test_data(), None).unwrap(); + let converted_db = read_entire_sqlite_db(db, Span::test_data(), &Signals::empty()).unwrap(); let expected = Value::test_record(record! { "person" => Value::test_list(vec![]), @@ -759,7 +748,7 @@ mod test { db.execute("INSERT INTO item (id, name) VALUES (456, 'foo bar')", []) .unwrap(); - let converted_db = read_entire_sqlite_db(db, span, None).unwrap(); + let converted_db = read_entire_sqlite_db(db, span, &Signals::empty()).unwrap(); let expected = Value::test_record(record! { "item" => Value::test_list( diff --git a/crates/nu-command/src/date/humanize.rs b/crates/nu-command/src/date/humanize.rs index 2815571520..d8542a540c 100644 --- a/crates/nu-command/src/date/humanize.rs +++ b/crates/nu-command/src/date/humanize.rs @@ -50,7 +50,7 @@ impl Command for SubCommand { if matches!(input, PipelineData::Empty) { return Err(ShellError::PipelineEmpty { dst_span: head }); } - input.map(move |value| helper(value, head), engine_state.ctrlc.clone()) + input.map(move |value| helper(value, head), engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-command/src/date/list_timezone.rs b/crates/nu-command/src/date/list_timezone.rs index 6f9267947d..56f7fe5376 100644 --- a/crates/nu-command/src/date/list_timezone.rs +++ b/crates/nu-command/src/date/list_timezone.rs @@ -40,7 +40,7 @@ impl Command for SubCommand { head, ) }) - .into_pipeline_data(head, engine_state.ctrlc.clone())) + .into_pipeline_data(head, engine_state.signals().clone())) } fn examples(&self) -> Vec { diff --git a/crates/nu-command/src/date/to_record.rs b/crates/nu-command/src/date/to_record.rs index f9c0ceff1b..c0b09c040b 100644 --- a/crates/nu-command/src/date/to_record.rs +++ b/crates/nu-command/src/date/to_record.rs @@ -40,7 +40,7 @@ impl Command for SubCommand { if matches!(input, PipelineData::Empty) { return Err(ShellError::PipelineEmpty { dst_span: head }); } - input.map(move |value| helper(value, head), engine_state.ctrlc.clone()) + input.map(move |value| helper(value, head), engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-command/src/date/to_table.rs b/crates/nu-command/src/date/to_table.rs index 36c3f4a94a..7ce8bc171b 100644 --- a/crates/nu-command/src/date/to_table.rs +++ b/crates/nu-command/src/date/to_table.rs @@ -40,7 +40,7 @@ impl Command for SubCommand { if matches!(input, PipelineData::Empty) { return Err(ShellError::PipelineEmpty { dst_span: head }); } - input.map(move |value| helper(value, head), engine_state.ctrlc.clone()) + input.map(move |value| helper(value, head), engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-command/src/date/to_timezone.rs b/crates/nu-command/src/date/to_timezone.rs index 5f41d287ae..3d08d7271b 100644 --- a/crates/nu-command/src/date/to_timezone.rs +++ b/crates/nu-command/src/date/to_timezone.rs @@ -55,7 +55,7 @@ impl Command for SubCommand { } input.map( move |value| helper(value, head, &timezone), - engine_state.ctrlc.clone(), + engine_state.signals(), ) } diff --git a/crates/nu-command/src/debug/debug_.rs b/crates/nu-command/src/debug/debug_.rs index c766081410..f4e5707491 100644 --- a/crates/nu-command/src/debug/debug_.rs +++ b/crates/nu-command/src/debug/debug_.rs @@ -46,7 +46,7 @@ impl Command for Debug { Value::string(x.to_expanded_string(", ", &config), head) } }, - engine_state.ctrlc.clone(), + engine_state.signals(), ) } diff --git a/crates/nu-command/src/debug/metadata_set.rs b/crates/nu-command/src/debug/metadata_set.rs index e4ee97a76b..96f50cfdba 100644 --- a/crates/nu-command/src/debug/metadata_set.rs +++ b/crates/nu-command/src/debug/metadata_set.rs @@ -48,7 +48,7 @@ impl Command for MetadataSet { let ds_fp: Option = call.get_flag(engine_state, stack, "datasource-filepath")?; let ds_ls = call.has_flag(engine_state, stack, "datasource-ls")?; let content_type: Option = call.get_flag(engine_state, stack, "content-type")?; - + let signals = engine_state.signals().clone(); let metadata = input .metadata() .clone() @@ -58,19 +58,15 @@ impl Command for MetadataSet { match (ds_fp, ds_ls) { (Some(path), false) => Ok(input.into_pipeline_data_with_metadata( head, - engine_state.ctrlc.clone(), + signals, metadata.with_data_source(DataSource::FilePath(path.into())), )), (None, true) => Ok(input.into_pipeline_data_with_metadata( head, - engine_state.ctrlc.clone(), + signals, metadata.with_data_source(DataSource::Ls), )), - _ => Ok(input.into_pipeline_data_with_metadata( - head, - engine_state.ctrlc.clone(), - metadata, - )), + _ => Ok(input.into_pipeline_data_with_metadata(head, signals, metadata)), } } diff --git a/crates/nu-command/src/filesystem/du.rs b/crates/nu-command/src/filesystem/du.rs index c48a1f7ac1..93f08f7785 100644 --- a/crates/nu-command/src/filesystem/du.rs +++ b/crates/nu-command/src/filesystem/du.rs @@ -3,10 +3,9 @@ use crate::{DirBuilder, DirInfo, FileInfo}; #[allow(deprecated)] use nu_engine::{command_prelude::*, current_dir}; use nu_glob::Pattern; -use nu_protocol::NuGlob; +use nu_protocol::{NuGlob, Signals}; use serde::Deserialize; use std::path::Path; -use std::sync::{atomic::AtomicBool, Arc}; #[derive(Clone)] pub struct Du; @@ -120,8 +119,8 @@ impl Command for Du { min_size, }; Ok( - du_for_one_pattern(args, ¤t_dir, tag, engine_state.ctrlc.clone())? - .into_pipeline_data(tag, engine_state.ctrlc.clone()), + du_for_one_pattern(args, ¤t_dir, tag, engine_state.signals())? + .into_pipeline_data(tag, engine_state.signals().clone()), ) } Some(paths) => { @@ -139,7 +138,7 @@ impl Command for Du { args, ¤t_dir, tag, - engine_state.ctrlc.clone(), + engine_state.signals(), )?) } @@ -147,7 +146,7 @@ impl Command for Du { Ok(result_iters .into_iter() .flatten() - .into_pipeline_data(tag, engine_state.ctrlc.clone())) + .into_pipeline_data(tag, engine_state.signals().clone())) } } } @@ -164,8 +163,8 @@ impl Command for Du { fn du_for_one_pattern( args: DuArgs, current_dir: &Path, - call_span: Span, - ctrl_c: Option>, + span: Span, + signals: &Signals, ) -> Result + Send, ShellError> { let exclude = args.exclude.map_or(Ok(None), move |x| { Pattern::new(x.item.as_ref()) @@ -178,7 +177,7 @@ fn du_for_one_pattern( let include_files = args.all; let mut paths = match args.path { - Some(p) => nu_engine::glob_from(&p, current_dir, call_span, None), + Some(p) => nu_engine::glob_from(&p, current_dir, span, None), // The * pattern should never fail. None => nu_engine::glob_from( &Spanned { @@ -186,7 +185,7 @@ fn du_for_one_pattern( span: Span::unknown(), }, current_dir, - call_span, + span, None, ), } @@ -205,7 +204,7 @@ fn du_for_one_pattern( let min_size = args.min_size.map(|f| f.item as u64); let params = DirBuilder { - tag: call_span, + tag: span, min: min_size, deref, exclude, @@ -217,13 +216,13 @@ fn du_for_one_pattern( match p { Ok(a) => { if a.is_dir() { - output.push(DirInfo::new(a, ¶ms, max_depth, ctrl_c.clone()).into()); - } else if let Ok(v) = FileInfo::new(a, deref, call_span) { + output.push(DirInfo::new(a, ¶ms, max_depth, span, signals)?.into()); + } else if let Ok(v) = FileInfo::new(a, deref, span) { output.push(v.into()); } } Err(e) => { - output.push(Value::error(e, call_span)); + output.push(Value::error(e, span)); } } } diff --git a/crates/nu-command/src/filesystem/glob.rs b/crates/nu-command/src/filesystem/glob.rs index b10e8893a0..212c6ceb92 100644 --- a/crates/nu-command/src/filesystem/glob.rs +++ b/crates/nu-command/src/filesystem/glob.rs @@ -1,5 +1,5 @@ use nu_engine::command_prelude::*; -use std::sync::{atomic::AtomicBool, Arc}; +use nu_protocol::Signals; use wax::{Glob as WaxGlob, WalkBehavior, WalkEntry}; #[derive(Clone)] @@ -125,7 +125,6 @@ impl Command for Glob { call: &Call, _input: PipelineData, ) -> Result { - let ctrlc = engine_state.ctrlc.clone(); let span = call.head; let glob_pattern: Spanned = call.req(engine_state, stack, 0)?; let depth = call.get_flag(engine_state, stack, "depth")?; @@ -216,7 +215,14 @@ impl Command for Glob { inner: vec![], })? .flatten(); - glob_to_value(ctrlc, glob_results, no_dirs, no_files, no_symlinks, span) + glob_to_value( + engine_state.signals(), + glob_results, + no_dirs, + no_files, + no_symlinks, + span, + ) } else { let glob_results = glob .walk_with_behavior( @@ -227,12 +233,19 @@ impl Command for Glob { }, ) .flatten(); - glob_to_value(ctrlc, glob_results, no_dirs, no_files, no_symlinks, span) + glob_to_value( + engine_state.signals(), + glob_results, + no_dirs, + no_files, + no_symlinks, + span, + ) }?; Ok(result .into_iter() - .into_pipeline_data(span, engine_state.ctrlc.clone())) + .into_pipeline_data(span, engine_state.signals().clone())) } } @@ -252,7 +265,7 @@ fn convert_patterns(columns: &[Value]) -> Result, ShellError> { } fn glob_to_value<'a>( - ctrlc: Option>, + signals: &Signals, glob_results: impl Iterator>, no_dirs: bool, no_files: bool, @@ -261,10 +274,7 @@ fn glob_to_value<'a>( ) -> Result, ShellError> { let mut result: Vec = Vec::new(); for entry in glob_results { - if nu_utils::ctrl_c::was_pressed(&ctrlc) { - result.clear(); - return Err(ShellError::InterruptedByUser { span: None }); - } + signals.check(span)?; let file_type = entry.file_type(); if !(no_dirs && file_type.is_dir() diff --git a/crates/nu-command/src/filesystem/ls.rs b/crates/nu-command/src/filesystem/ls.rs index a3ab1eca54..f465a93dc1 100644 --- a/crates/nu-command/src/filesystem/ls.rs +++ b/crates/nu-command/src/filesystem/ls.rs @@ -6,14 +6,13 @@ use nu_engine::glob_from; use nu_engine::{command_prelude::*, env::current_dir}; use nu_glob::MatchOptions; use nu_path::expand_to_real_path; -use nu_protocol::{DataSource, NuGlob, PipelineMetadata}; +use nu_protocol::{DataSource, NuGlob, PipelineMetadata, Signals}; use pathdiff::diff_paths; #[cfg(unix)] use std::os::unix::fs::PermissionsExt; use std::{ path::PathBuf, - sync::Arc, time::{SystemTime, UNIX_EPOCH}, }; @@ -93,7 +92,6 @@ impl Command for Ls { let du = call.has_flag(engine_state, stack, "du")?; let directory = call.has_flag(engine_state, stack, "directory")?; let use_mime_type = call.has_flag(engine_state, stack, "mime-type")?; - let ctrl_c = engine_state.ctrlc.clone(); let call_span = call.head; #[allow(deprecated)] let cwd = current_dir(engine_state, stack)?; @@ -116,10 +114,10 @@ impl Command for Ls { Some(pattern_arg) }; match input_pattern_arg { - None => Ok(ls_for_one_pattern(None, args, ctrl_c.clone(), cwd)? + None => Ok(ls_for_one_pattern(None, args, engine_state.signals(), cwd)? .into_pipeline_data_with_metadata( call_span, - ctrl_c, + engine_state.signals().clone(), PipelineMetadata { data_source: DataSource::Ls, content_type: None, @@ -131,7 +129,7 @@ impl Command for Ls { result_iters.push(ls_for_one_pattern( Some(pat), args, - ctrl_c.clone(), + engine_state.signals(), cwd.clone(), )?) } @@ -143,7 +141,7 @@ impl Command for Ls { .flatten() .into_pipeline_data_with_metadata( call_span, - ctrl_c, + engine_state.signals().clone(), PipelineMetadata { data_source: DataSource::Ls, content_type: None, @@ -215,7 +213,7 @@ impl Command for Ls { fn ls_for_one_pattern( pattern_arg: Option>, args: Args, - ctrl_c: Option>, + signals: &Signals, cwd: PathBuf, ) -> Result + Send>, ShellError> { let Args { @@ -342,7 +340,7 @@ fn ls_for_one_pattern( let mut hidden_dirs = vec![]; - let one_ctrl_c = ctrl_c.clone(); + let signals = signals.clone(); Ok(Box::new(paths_peek.filter_map(move |x| match x { Ok(path) => { let metadata = match std::fs::symlink_metadata(&path) { @@ -412,7 +410,7 @@ fn ls_for_one_pattern( call_span, long, du, - one_ctrl_c.clone(), + &signals, use_mime_type, ); match entry { @@ -474,7 +472,6 @@ fn path_contains_hidden_folder(path: &Path, folders: &[PathBuf]) -> bool { #[cfg(unix)] use std::os::unix::fs::FileTypeExt; use std::path::Path; -use std::sync::atomic::AtomicBool; pub fn get_file_type(md: &std::fs::Metadata, display_name: &str, use_mime_type: bool) -> String { let ft = md.file_type(); @@ -523,7 +520,7 @@ pub(crate) fn dir_entry_dict( span: Span, long: bool, du: bool, - ctrl_c: Option>, + signals: &Signals, use_mime_type: bool, ) -> Result { #[cfg(windows)] @@ -618,7 +615,7 @@ pub(crate) fn dir_entry_dict( if md.is_dir() { if du { let params = DirBuilder::new(Span::new(0, 2), None, false, None, false); - let dir_size = DirInfo::new(filename, ¶ms, None, ctrl_c).get_size(); + let dir_size = DirInfo::new(filename, ¶ms, None, span, signals)?.get_size(); Value::filesize(dir_size as i64, span) } else { diff --git a/crates/nu-command/src/filesystem/open.rs b/crates/nu-command/src/filesystem/open.rs index 9000359450..e654b27f05 100644 --- a/crates/nu-command/src/filesystem/open.rs +++ b/crates/nu-command/src/filesystem/open.rs @@ -51,7 +51,6 @@ impl Command for Open { ) -> Result { let raw = call.has_flag(engine_state, stack, "raw")?; let call_span = call.head; - let ctrlc = engine_state.ctrlc.clone(); #[allow(deprecated)] let cwd = current_dir(engine_state, stack)?; let mut paths = get_rest_for_glob_pattern(engine_state, stack, call, 0)?; @@ -122,8 +121,12 @@ impl Command for Open { } else { #[cfg(feature = "sqlite")] if !raw { - let res = SQLiteDatabase::try_from_path(path, arg_span, ctrlc.clone()) - .map(|db| db.into_value(call.head).into_pipeline_data()); + let res = SQLiteDatabase::try_from_path( + path, + arg_span, + engine_state.signals().clone(), + ) + .map(|db| db.into_value(call.head).into_pipeline_data()); if res.is_ok() { return res; @@ -144,7 +147,7 @@ impl Command for Open { }; let stream = PipelineData::ByteStream( - ByteStream::file(file, call_span, ctrlc.clone()), + ByteStream::file(file, call_span, engine_state.signals().clone()), Some(PipelineMetadata { data_source: DataSource::FilePath(path.to_path_buf()), content_type: None, @@ -203,7 +206,7 @@ impl Command for Open { Ok(output .into_iter() .flatten() - .into_pipeline_data(call_span, ctrlc)) + .into_pipeline_data(call_span, engine_state.signals().clone())) } } diff --git a/crates/nu-command/src/filesystem/rm.rs b/crates/nu-command/src/filesystem/rm.rs index 9696ae0c2f..d67046ffe1 100644 --- a/crates/nu-command/src/filesystem/rm.rs +++ b/crates/nu-command/src/filesystem/rm.rs @@ -451,12 +451,7 @@ fn rm( }); for result in iter { - if nu_utils::ctrl_c::was_pressed(&engine_state.ctrlc) { - return Err(ShellError::InterruptedByUser { - span: Some(call.head), - }); - } - + engine_state.signals().check(call.head)?; match result { Ok(None) => {} Ok(Some(msg)) => eprintln!("{msg}"), diff --git a/crates/nu-command/src/filesystem/save.rs b/crates/nu-command/src/filesystem/save.rs index 1cfbbc67b4..6ca5c09559 100644 --- a/crates/nu-command/src/filesystem/save.rs +++ b/crates/nu-command/src/filesystem/save.rs @@ -5,15 +5,14 @@ use nu_engine::{command_prelude::*, current_dir}; use nu_path::expand_path_with; use nu_protocol::{ ast::{Expr, Expression}, - byte_stream::copy_with_interrupt, + byte_stream::copy_with_signals, process::ChildPipe, - ByteStreamSource, DataSource, OutDest, PipelineMetadata, + ByteStreamSource, DataSource, OutDest, PipelineMetadata, Signals, }; use std::{ fs::File, io::{self, BufRead, BufReader, Read, Write}, path::{Path, PathBuf}, - sync::{atomic::AtomicBool, Arc}, thread, }; @@ -120,30 +119,30 @@ impl Command for Save { )?; let size = stream.known_size(); - let ctrlc = engine_state.ctrlc.clone(); + let signals = engine_state.signals(); match stream.into_source() { ByteStreamSource::Read(read) => { - stream_to_file(read, size, ctrlc, file, span, progress)?; + stream_to_file(read, size, signals, file, span, progress)?; } ByteStreamSource::File(source) => { - stream_to_file(source, size, ctrlc, file, span, progress)?; + stream_to_file(source, size, signals, file, span, progress)?; } ByteStreamSource::Child(mut child) => { fn write_or_consume_stderr( stderr: ChildPipe, file: Option, span: Span, - ctrlc: Option>, + signals: &Signals, progress: bool, ) -> Result<(), ShellError> { if let Some(file) = file { match stderr { ChildPipe::Pipe(pipe) => { - stream_to_file(pipe, None, ctrlc, file, span, progress) + stream_to_file(pipe, None, signals, file, span, progress) } ChildPipe::Tee(tee) => { - stream_to_file(tee, None, ctrlc, file, span, progress) + stream_to_file(tee, None, signals, file, span, progress) } }? } else { @@ -163,14 +162,14 @@ impl Command for Save { // delegate a thread to redirect stderr to result. let handler = stderr .map(|stderr| { - let ctrlc = ctrlc.clone(); + let signals = signals.clone(); thread::Builder::new().name("stderr saver".into()).spawn( move || { write_or_consume_stderr( stderr, stderr_file, span, - ctrlc, + &signals, progress, ) }, @@ -181,10 +180,10 @@ impl Command for Save { let res = match stdout { ChildPipe::Pipe(pipe) => { - stream_to_file(pipe, None, ctrlc, file, span, progress) + stream_to_file(pipe, None, signals, file, span, progress) } ChildPipe::Tee(tee) => { - stream_to_file(tee, None, ctrlc, file, span, progress) + stream_to_file(tee, None, signals, file, span, progress) } }; if let Some(h) = handler { @@ -202,7 +201,7 @@ impl Command for Save { stderr, stderr_file, span, - ctrlc, + signals, progress, )?; } @@ -510,7 +509,7 @@ fn get_files( fn stream_to_file( source: impl Read, known_size: Option, - ctrlc: Option>, + signals: &Signals, mut file: File, span: Span, progress: bool, @@ -526,9 +525,9 @@ fn stream_to_file( let mut reader = BufReader::new(source); let res = loop { - if nu_utils::ctrl_c::was_pressed(&ctrlc) { + if let Err(err) = signals.check(span) { bar.abandoned_msg("# Cancelled #".to_owned()); - return Ok(()); + return Err(err); } match reader.fill_buf() { @@ -555,7 +554,7 @@ fn stream_to_file( Ok(()) } } else { - copy_with_interrupt(source, file, span, ctrlc.as_deref())?; + copy_with_signals(source, file, span, signals)?; Ok(()) } } diff --git a/crates/nu-command/src/filesystem/watch.rs b/crates/nu-command/src/filesystem/watch.rs index fda542c8a8..c9817de250 100644 --- a/crates/nu-command/src/filesystem/watch.rs +++ b/crates/nu-command/src/filesystem/watch.rs @@ -143,7 +143,6 @@ impl Command for Watch { None => RecursiveMode::Recursive, }; - let ctrlc_ref = &engine_state.ctrlc.clone(); let (tx, rx) = channel(); let mut debouncer = match new_debouncer(debounce_duration, None, tx) { @@ -256,7 +255,7 @@ impl Command for Watch { } Err(RecvTimeoutError::Timeout) => {} } - if nu_utils::ctrl_c::was_pressed(ctrlc_ref) { + if engine_state.signals().interrupted() { break; } } diff --git a/crates/nu-command/src/filters/append.rs b/crates/nu-command/src/filters/append.rs index af5bd49283..2064bdf1e8 100644 --- a/crates/nu-command/src/filters/append.rs +++ b/crates/nu-command/src/filters/append.rs @@ -116,7 +116,7 @@ only unwrap the outer list, and leave the variable's contents untouched."# Ok(input .into_iter() .chain(other.into_pipeline_data()) - .into_pipeline_data_with_metadata(call.head, engine_state.ctrlc.clone(), metadata)) + .into_pipeline_data_with_metadata(call.head, engine_state.signals().clone(), metadata)) } } diff --git a/crates/nu-command/src/filters/compact.rs b/crates/nu-command/src/filters/compact.rs index 9a4e968e9b..f43d738ed1 100644 --- a/crates/nu-command/src/filters/compact.rs +++ b/crates/nu-command/src/filters/compact.rs @@ -140,7 +140,7 @@ pub fn compact( _ => true, } }, - engine_state.ctrlc.clone(), + engine_state.signals(), ) .map(|m| m.set_metadata(metadata)) } diff --git a/crates/nu-command/src/filters/default.rs b/crates/nu-command/src/filters/default.rs index 3eaa8d342e..a12d78f0b7 100644 --- a/crates/nu-command/src/filters/default.rs +++ b/crates/nu-command/src/filters/default.rs @@ -80,8 +80,6 @@ fn default( let value: Value = call.req(engine_state, stack, 0)?; let column: Option> = call.opt(engine_state, stack, 1)?; - let ctrlc = engine_state.ctrlc.clone(); - if let Some(column) = column { input .map( @@ -109,7 +107,7 @@ fn default( } _ => item, }, - ctrlc, + engine_state.signals(), ) .map(|x| x.set_metadata(metadata)) } else if input.is_nothing() { @@ -121,7 +119,7 @@ fn default( Value::Nothing { .. } => value.clone(), x => x, }, - ctrlc, + engine_state.signals(), ) .map(|x| x.set_metadata(metadata)) } diff --git a/crates/nu-command/src/filters/drop/column.rs b/crates/nu-command/src/filters/drop/column.rs index 94c0308ea8..f168e6c2ca 100644 --- a/crates/nu-command/src/filters/drop/column.rs +++ b/crates/nu-command/src/filters/drop/column.rs @@ -102,7 +102,11 @@ fn drop_cols( Err(e) => Value::error(e, head), } })) - .into_pipeline_data_with_metadata(head, engine_state.ctrlc.clone(), metadata)) + .into_pipeline_data_with_metadata( + head, + engine_state.signals().clone(), + metadata, + )) } else { Ok(PipelineData::Empty) } diff --git a/crates/nu-command/src/filters/drop/nth.rs b/crates/nu-command/src/filters/drop/nth.rs index a530de5aa1..a76c4f0d92 100644 --- a/crates/nu-command/src/filters/drop/nth.rs +++ b/crates/nu-command/src/filters/drop/nth.rs @@ -156,7 +156,7 @@ impl Command for DropNth { .take(start) .into_pipeline_data_with_metadata( head, - engine_state.ctrlc.clone(), + engine_state.signals().clone(), metadata, )) } @@ -177,7 +177,7 @@ impl Command for DropNth { rows, current: 0, } - .into_pipeline_data_with_metadata(head, engine_state.ctrlc.clone(), metadata)) + .into_pipeline_data_with_metadata(head, engine_state.signals().clone(), metadata)) } } diff --git a/crates/nu-command/src/filters/each.rs b/crates/nu-command/src/filters/each.rs index a074f63abb..fa1bf390b8 100644 --- a/crates/nu-command/src/filters/each.rs +++ b/crates/nu-command/src/filters/each.rs @@ -140,7 +140,7 @@ with 'transpose' first."# } } }) - .into_pipeline_data(head, engine_state.ctrlc.clone())) + .into_pipeline_data(head, engine_state.signals().clone())) } PipelineData::ByteStream(stream, ..) => { if let Some(chunks) = stream.chunks() { @@ -171,7 +171,7 @@ with 'transpose' first."# } } }) - .into_pipeline_data(head, engine_state.ctrlc.clone())) + .into_pipeline_data(head, engine_state.signals().clone())) } else { Ok(PipelineData::Empty) } @@ -185,7 +185,7 @@ with 'transpose' first."# .and_then(|x| { x.filter( move |x| if !keep_empty { !x.is_nothing() } else { true }, - engine_state.ctrlc.clone(), + engine_state.signals(), ) }) .map(|data| data.set_metadata(metadata)) diff --git a/crates/nu-command/src/filters/enumerate.rs b/crates/nu-command/src/filters/enumerate.rs index 1034780657..df0f4e2fca 100644 --- a/crates/nu-command/src/filters/enumerate.rs +++ b/crates/nu-command/src/filters/enumerate.rs @@ -52,7 +52,6 @@ impl Command for Enumerate { ) -> Result { let head = call.head; let metadata = input.metadata(); - let ctrlc = engine_state.ctrlc.clone(); Ok(input .into_iter() @@ -66,7 +65,7 @@ impl Command for Enumerate { head, ) }) - .into_pipeline_data_with_metadata(head, ctrlc, metadata)) + .into_pipeline_data_with_metadata(head, engine_state.signals().clone(), metadata)) } } diff --git a/crates/nu-command/src/filters/every.rs b/crates/nu-command/src/filters/every.rs index 1202b4f7c9..664be33bed 100644 --- a/crates/nu-command/src/filters/every.rs +++ b/crates/nu-command/src/filters/every.rs @@ -78,7 +78,7 @@ impl Command for Every { None } }) - .into_pipeline_data_with_metadata(call.head, engine_state.ctrlc.clone(), metadata)) + .into_pipeline_data_with_metadata(call.head, engine_state.signals().clone(), metadata)) } } diff --git a/crates/nu-command/src/filters/filter.rs b/crates/nu-command/src/filters/filter.rs index 1ba1508839..f2efaa3af3 100644 --- a/crates/nu-command/src/filters/filter.rs +++ b/crates/nu-command/src/filters/filter.rs @@ -72,7 +72,7 @@ a variable. On the other hand, the "row condition" syntax is not supported."# } } }) - .into_pipeline_data(head, engine_state.ctrlc.clone())) + .into_pipeline_data(head, engine_state.signals().clone())) } PipelineData::ByteStream(stream, ..) => { if let Some(chunks) = stream.chunks() { @@ -97,7 +97,7 @@ a variable. On the other hand, the "row condition" syntax is not supported."# } } }) - .into_pipeline_data(head, engine_state.ctrlc.clone())) + .into_pipeline_data(head, engine_state.signals().clone())) } else { Ok(PipelineData::Empty) } @@ -117,7 +117,7 @@ a variable. On the other hand, the "row condition" syntax is not supported."# Some(Value::error(err, span)) } } - .into_pipeline_data(head, engine_state.ctrlc.clone())) + .into_pipeline_data(head, engine_state.signals().clone())) } } .map(|data| data.set_metadata(metadata)) diff --git a/crates/nu-command/src/filters/find.rs b/crates/nu-command/src/filters/find.rs index af626c1e75..7669190f7e 100644 --- a/crates/nu-command/src/filters/find.rs +++ b/crates/nu-command/src/filters/find.rs @@ -213,7 +213,6 @@ fn find_with_regex( input: PipelineData, ) -> Result { let span = call.head; - let ctrlc = engine_state.ctrlc.clone(); let config = engine_state.get_config().clone(); let insensitive = call.has_flag(engine_state, stack, "ignore-case")?; @@ -246,7 +245,7 @@ fn find_with_regex( Value::List { vals, .. } => values_match_find(vals, &re, &config, invert), _ => false, }, - ctrlc, + engine_state.signals(), ) } @@ -349,18 +348,16 @@ fn find_with_rest_and_highlight( input: PipelineData, ) -> Result { let span = call.head; - let ctrlc = engine_state.ctrlc.clone(); - let engine_state = engine_state.clone(); let config = engine_state.get_config().clone(); let filter_config = engine_state.get_config().clone(); - let invert = call.has_flag(&engine_state, stack, "invert")?; - let terms = call.rest::(&engine_state, stack, 0)?; + let invert = call.has_flag(engine_state, stack, "invert")?; + let terms = call.rest::(engine_state, stack, 0)?; let lower_terms = terms .iter() .map(|v| Value::string(v.to_expanded_string("", &config).to_lowercase(), span)) .collect::>(); - let style_computer = StyleComputer::from_config(&engine_state, stack); + let style_computer = StyleComputer::from_config(engine_state, stack); // Currently, search results all use the same style. // Also note that this sample string is passed into user-written code (the closure that may or may not be // defined for "string"). @@ -369,7 +366,7 @@ fn find_with_rest_and_highlight( style_computer.compute("search_result", &Value::string("search result", span)); let cols_to_search_in_map: Vec<_> = call - .get_flag(&engine_state, stack, "columns")? + .get_flag(engine_state, stack, "columns")? .unwrap_or_default(); let cols_to_search_in_filter = cols_to_search_in_map.clone(); @@ -401,7 +398,7 @@ fn find_with_rest_and_highlight( _ => x, } }, - ctrlc.clone(), + engine_state.signals(), )? .filter( move |value| { @@ -414,7 +411,7 @@ fn find_with_rest_and_highlight( invert, ) }, - ctrlc, + engine_state.signals(), ), PipelineData::ListStream(stream, metadata) => { let stream = stream.modify(|iter| { diff --git a/crates/nu-command/src/filters/first.rs b/crates/nu-command/src/filters/first.rs index 97c5159b97..8f2e8db1b9 100644 --- a/crates/nu-command/src/filters/first.rs +++ b/crates/nu-command/src/filters/first.rs @@ -1,4 +1,5 @@ use nu_engine::command_prelude::*; +use nu_protocol::Signals; use std::io::Read; #[derive(Clone)] @@ -133,8 +134,7 @@ fn first_helper( } } Value::Range { val, .. } => { - let ctrlc = engine_state.ctrlc.clone(); - let mut iter = val.into_range_iter(span, ctrlc.clone()); + let mut iter = val.into_range_iter(span, Signals::empty()); if return_single_element { if let Some(v) = iter.next() { Ok(v.into_pipeline_data()) @@ -142,9 +142,11 @@ fn first_helper( Err(ShellError::AccessEmptyContent { span: head }) } } else { - Ok(iter - .take(rows) - .into_pipeline_data_with_metadata(span, ctrlc, metadata)) + Ok(iter.take(rows).into_pipeline_data_with_metadata( + span, + engine_state.signals().clone(), + metadata, + )) } } // Propagate errors by explicitly matching them before the final case. @@ -189,7 +191,7 @@ fn first_helper( ByteStream::read( reader.take(rows as u64), head, - None, + Signals::empty(), ByteStreamType::Binary, ), metadata, diff --git a/crates/nu-command/src/filters/flatten.rs b/crates/nu-command/src/filters/flatten.rs index ec86677af4..b4faec9589 100644 --- a/crates/nu-command/src/filters/flatten.rs +++ b/crates/nu-command/src/filters/flatten.rs @@ -127,7 +127,7 @@ fn flatten( input .flat_map( move |item| flat_value(&columns, item, flatten_all), - engine_state.ctrlc.clone(), + engine_state.signals(), ) .map(|x| x.set_metadata(metadata)) } diff --git a/crates/nu-command/src/filters/get.rs b/crates/nu-command/src/filters/get.rs index 07f0ea9440..9f7d76277d 100644 --- a/crates/nu-command/src/filters/get.rs +++ b/crates/nu-command/src/filters/get.rs @@ -62,7 +62,6 @@ If multiple cell paths are given, this will produce a list of values."# let mut rest: Vec = call.rest(engine_state, stack, 1)?; let ignore_errors = call.has_flag(engine_state, stack, "ignore-errors")?; let sensitive = call.has_flag(engine_state, stack, "sensitive")?; - let ctrlc = engine_state.ctrlc.clone(); let metadata = input.metadata(); if ignore_errors { @@ -89,7 +88,9 @@ If multiple cell paths are given, this will produce a list of values."# output.push(val?); } - Ok(output.into_iter().into_pipeline_data(span, ctrlc)) + Ok(output + .into_iter() + .into_pipeline_data(span, engine_state.signals().clone())) } .map(|x| x.set_metadata(metadata)) } diff --git a/crates/nu-command/src/filters/group.rs b/crates/nu-command/src/filters/group.rs index 821f35f34e..13b53850d2 100644 --- a/crates/nu-command/src/filters/group.rs +++ b/crates/nu-command/src/filters/group.rs @@ -55,18 +55,19 @@ impl Command for Group { ) -> Result { let head = call.head; let group_size: Spanned = call.req(engine_state, stack, 0)?; - let ctrlc = engine_state.ctrlc.clone(); let metadata = input.metadata(); - //FIXME: add in support for external redirection when engine-q supports it generally - let each_group_iterator = EachGroupIterator { group_size: group_size.item, input: Box::new(input.into_iter()), span: head, }; - Ok(each_group_iterator.into_pipeline_data_with_metadata(head, ctrlc, metadata)) + Ok(each_group_iterator.into_pipeline_data_with_metadata( + head, + engine_state.signals().clone(), + metadata, + )) } } diff --git a/crates/nu-command/src/filters/insert.rs b/crates/nu-command/src/filters/insert.rs index 5f1380b2ac..3b47c4e6a9 100644 --- a/crates/nu-command/src/filters/insert.rs +++ b/crates/nu-command/src/filters/insert.rs @@ -222,7 +222,11 @@ fn insert( Ok(pre_elems .into_iter() .chain(stream) - .into_pipeline_data_with_metadata(head, engine_state.ctrlc.clone(), metadata)) + .into_pipeline_data_with_metadata( + head, + engine_state.signals().clone(), + metadata, + )) } else if let Value::Closure { val, .. } = replacement { let mut closure = ClosureEval::new(engine_state, stack, *val); let stream = stream.map(move |mut value| { diff --git a/crates/nu-command/src/filters/interleave.rs b/crates/nu-command/src/filters/interleave.rs index 85a92741a1..9890fede1e 100644 --- a/crates/nu-command/src/filters/interleave.rs +++ b/crates/nu-command/src/filters/interleave.rs @@ -147,7 +147,7 @@ interleave // Now that threads are writing to the channel, we just return it as a stream Ok(rx .into_iter() - .into_pipeline_data(head, engine_state.ctrlc.clone())) + .into_pipeline_data(head, engine_state.signals().clone())) } } diff --git a/crates/nu-command/src/filters/items.rs b/crates/nu-command/src/filters/items.rs index ed30486bee..04a4c6d672 100644 --- a/crates/nu-command/src/filters/items.rs +++ b/crates/nu-command/src/filters/items.rs @@ -67,7 +67,7 @@ impl Command for Items { } } }) - .into_pipeline_data(head, engine_state.ctrlc.clone())) + .into_pipeline_data(head, engine_state.signals().clone())) } Value::Error { error, .. } => Err(*error), other => Err(ShellError::OnlySupportsThisInputType { diff --git a/crates/nu-command/src/filters/last.rs b/crates/nu-command/src/filters/last.rs index efb300db01..bb5a75fecd 100644 --- a/crates/nu-command/src/filters/last.rs +++ b/crates/nu-command/src/filters/last.rs @@ -99,14 +99,10 @@ impl Command for Last { let mut buf = VecDeque::new(); for row in iterator { - if nu_utils::ctrl_c::was_pressed(&engine_state.ctrlc) { - return Err(ShellError::InterruptedByUser { span: Some(head) }); - } - + engine_state.signals().check(head)?; if buf.len() == rows { buf.pop_front(); } - buf.push_back(row); } diff --git a/crates/nu-command/src/filters/lines.rs b/crates/nu-command/src/filters/lines.rs index 0b037dcaac..fc957e1c0e 100644 --- a/crates/nu-command/src/filters/lines.rs +++ b/crates/nu-command/src/filters/lines.rs @@ -26,7 +26,6 @@ impl Command for Lines { input: PipelineData, ) -> Result { let head = call.head; - let ctrlc = engine_state.ctrlc.clone(); let skip_empty = call.has_flag(engine_state, stack, "skip-empty")?; let span = input.span().unwrap_or(call.head); @@ -91,7 +90,7 @@ impl Command for Lines { Ok(line) => Value::string(line, head), Err(err) => Value::error(err, head), }) - .into_pipeline_data(head, ctrlc)) + .into_pipeline_data(head, engine_state.signals().clone())) } else { Ok(PipelineData::empty()) } diff --git a/crates/nu-command/src/filters/merge.rs b/crates/nu-command/src/filters/merge.rs index 8a3e613c56..191ed1706c 100644 --- a/crates/nu-command/src/filters/merge.rs +++ b/crates/nu-command/src/filters/merge.rs @@ -88,7 +88,6 @@ repeating this process with row 1, and so on."# let head = call.head; let merge_value: Value = call.req(engine_state, stack, 0)?; let metadata = input.metadata(); - let ctrlc = engine_state.ctrlc.clone(); match (&input, merge_value) { // table (list of records) @@ -110,7 +109,11 @@ repeating this process with row 1, and so on."# (Err(error), _) => Value::error(error, head), }); - Ok(res.into_pipeline_data_with_metadata(head, ctrlc, metadata)) + Ok(res.into_pipeline_data_with_metadata( + head, + engine_state.signals().clone(), + metadata, + )) } // record ( diff --git a/crates/nu-command/src/filters/move_.rs b/crates/nu-command/src/filters/move_.rs index d93368f291..6e826af050 100644 --- a/crates/nu-command/src/filters/move_.rs +++ b/crates/nu-command/src/filters/move_.rs @@ -144,7 +144,6 @@ impl Command for Move { }; let metadata = input.metadata(); - let ctrlc = engine_state.ctrlc.clone(); match input { PipelineData::Value(Value::List { .. }, ..) | PipelineData::ListStream { .. } => { @@ -158,7 +157,11 @@ impl Command for Move { Err(error) => Value::error(error, head), }); - Ok(res.into_pipeline_data_with_metadata(head, ctrlc, metadata)) + Ok(res.into_pipeline_data_with_metadata( + head, + engine_state.signals().clone(), + metadata, + )) } PipelineData::Value(Value::Record { val, .. }, ..) => { Ok(move_record_columns(&val, &columns, &before_or_after, head)? diff --git a/crates/nu-command/src/filters/par_each.rs b/crates/nu-command/src/filters/par_each.rs index af72895df6..958a7fbc76 100644 --- a/crates/nu-command/src/filters/par_each.rs +++ b/crates/nu-command/src/filters/par_each.rs @@ -1,6 +1,6 @@ use super::utils::chain_error_with_input; use nu_engine::{command_prelude::*, ClosureEvalOnce}; -use nu_protocol::engine::Closure; +use nu_protocol::{engine::Closure, Signals}; use rayon::prelude::*; #[derive(Clone)] @@ -158,12 +158,11 @@ impl Command for ParEach { }) .collect::>(); - apply_order(vec).into_pipeline_data(span, engine_state.ctrlc.clone()) + apply_order(vec).into_pipeline_data(span, engine_state.signals().clone()) })), Value::Range { val, .. } => Ok(create_pool(max_threads)?.install(|| { - let ctrlc = engine_state.ctrlc.clone(); let vec = val - .into_range_iter(span, ctrlc.clone()) + .into_range_iter(span, Signals::empty()) .enumerate() .par_bridge() .map(move |(index, value)| { @@ -184,7 +183,7 @@ impl Command for ParEach { }) .collect::>(); - apply_order(vec).into_pipeline_data(span, ctrlc) + apply_order(vec).into_pipeline_data(span, engine_state.signals().clone()) })), // This match allows non-iterables to be accepted, // which is currently considered undesirable (Nov 2022). @@ -212,7 +211,7 @@ impl Command for ParEach { }) .collect::>(); - apply_order(vec).into_pipeline_data(head, engine_state.ctrlc.clone()) + apply_order(vec).into_pipeline_data(head, engine_state.signals().clone()) })), PipelineData::ByteStream(stream, ..) => { if let Some(chunks) = stream.chunks() { @@ -236,14 +235,14 @@ impl Command for ParEach { }) .collect::>(); - apply_order(vec).into_pipeline_data(head, engine_state.ctrlc.clone()) + apply_order(vec).into_pipeline_data(head, engine_state.signals().clone()) })) } else { Ok(PipelineData::empty()) } } } - .and_then(|x| x.filter(|v| !v.is_nothing(), engine_state.ctrlc.clone())) + .and_then(|x| x.filter(|v| !v.is_nothing(), engine_state.signals())) .map(|data| data.set_metadata(metadata)) } } diff --git a/crates/nu-command/src/filters/prepend.rs b/crates/nu-command/src/filters/prepend.rs index f017420595..eeae0ef656 100644 --- a/crates/nu-command/src/filters/prepend.rs +++ b/crates/nu-command/src/filters/prepend.rs @@ -117,7 +117,7 @@ only unwrap the outer list, and leave the variable's contents untouched."# .into_pipeline_data() .into_iter() .chain(input) - .into_pipeline_data_with_metadata(call.head, engine_state.ctrlc.clone(), metadata)) + .into_pipeline_data_with_metadata(call.head, engine_state.signals().clone(), metadata)) } } diff --git a/crates/nu-command/src/filters/range.rs b/crates/nu-command/src/filters/range.rs index c89a0b11f2..0d4e703bb1 100644 --- a/crates/nu-command/src/filters/range.rs +++ b/crates/nu-command/src/filters/range.rs @@ -106,7 +106,7 @@ impl Command for Range { Ok(PipelineData::Value(Value::nothing(head), None)) } else { let iter = v.into_iter().skip(from).take(to - from + 1); - Ok(iter.into_pipeline_data(head, engine_state.ctrlc.clone())) + Ok(iter.into_pipeline_data(head, engine_state.signals().clone())) } } else { let from = start as usize; @@ -116,7 +116,7 @@ impl Command for Range { Ok(PipelineData::Value(Value::nothing(head), None)) } else { let iter = input.into_iter().skip(from).take(to - from + 1); - Ok(iter.into_pipeline_data(head, engine_state.ctrlc.clone())) + Ok(iter.into_pipeline_data(head, engine_state.signals().clone())) } } .map(|x| x.set_metadata(metadata)) diff --git a/crates/nu-command/src/filters/reduce.rs b/crates/nu-command/src/filters/reduce.rs index fc808ca9af..87fbe3b23e 100644 --- a/crates/nu-command/src/filters/reduce.rs +++ b/crates/nu-command/src/filters/reduce.rs @@ -107,10 +107,7 @@ impl Command for Reduce { let mut closure = ClosureEval::new(engine_state, stack, closure); for value in iter { - if nu_utils::ctrl_c::was_pressed(&engine_state.ctrlc) { - break; - } - + engine_state.signals().check(head)?; acc = closure .add_arg(value) .add_arg(acc) diff --git a/crates/nu-command/src/filters/rename.rs b/crates/nu-command/src/filters/rename.rs index b803bd8567..1e7ce34028 100644 --- a/crates/nu-command/src/filters/rename.rs +++ b/crates/nu-command/src/filters/rename.rs @@ -221,7 +221,7 @@ fn rename( ), } }, - engine_state.ctrlc.clone(), + engine_state.signals(), ) .map(|data| data.set_metadata(metadata)) } diff --git a/crates/nu-command/src/filters/reverse.rs b/crates/nu-command/src/filters/reverse.rs index f40e5f3be4..18891637ab 100644 --- a/crates/nu-command/src/filters/reverse.rs +++ b/crates/nu-command/src/filters/reverse.rs @@ -63,7 +63,7 @@ impl Command for Reverse { let metadata = input.metadata(); let values = input.into_iter_strict(head)?.collect::>(); let iter = values.into_iter().rev(); - Ok(iter.into_pipeline_data_with_metadata(head, engine_state.ctrlc.clone(), metadata)) + Ok(iter.into_pipeline_data_with_metadata(head, engine_state.signals().clone(), metadata)) } } diff --git a/crates/nu-command/src/filters/select.rs b/crates/nu-command/src/filters/select.rs index 3514ac6be7..2ca04b3999 100644 --- a/crates/nu-command/src/filters/select.rs +++ b/crates/nu-command/src/filters/select.rs @@ -215,7 +215,11 @@ fn select( rows: unique_rows.into_iter().peekable(), current: 0, } - .into_pipeline_data_with_metadata(call_span, engine_state.ctrlc.clone(), metadata) + .into_pipeline_data_with_metadata( + call_span, + engine_state.signals().clone(), + metadata, + ) } else { input }; @@ -255,7 +259,7 @@ fn select( Ok(output.into_iter().into_pipeline_data_with_metadata( call_span, - engine_state.ctrlc.clone(), + engine_state.signals().clone(), metadata, )) } @@ -304,7 +308,7 @@ fn select( Ok(values.into_pipeline_data_with_metadata( call_span, - engine_state.ctrlc.clone(), + engine_state.signals().clone(), metadata, )) } diff --git a/crates/nu-command/src/filters/shuffle.rs b/crates/nu-command/src/filters/shuffle.rs index 9e023b86c6..ec4bf8c454 100644 --- a/crates/nu-command/src/filters/shuffle.rs +++ b/crates/nu-command/src/filters/shuffle.rs @@ -33,7 +33,11 @@ impl Command for Shuffle { let mut values = input.into_iter_strict(call.head)?.collect::>(); values.shuffle(&mut thread_rng()); let iter = values.into_iter(); - Ok(iter.into_pipeline_data_with_metadata(call.head, engine_state.ctrlc.clone(), metadata)) + Ok(iter.into_pipeline_data_with_metadata( + call.head, + engine_state.signals().clone(), + metadata, + )) } fn examples(&self) -> Vec { diff --git a/crates/nu-command/src/filters/skip/skip_.rs b/crates/nu-command/src/filters/skip/skip_.rs index d293a743da..b64f438858 100644 --- a/crates/nu-command/src/filters/skip/skip_.rs +++ b/crates/nu-command/src/filters/skip/skip_.rs @@ -1,4 +1,5 @@ use nu_engine::command_prelude::*; +use nu_protocol::Signals; use std::io::{self, Read}; #[derive(Clone)] @@ -90,8 +91,6 @@ impl Command for Skip { } None => 1, }; - - let ctrlc = engine_state.ctrlc.clone(); let input_span = input.span().unwrap_or(call.head); match input { PipelineData::ByteStream(stream, metadata) => { @@ -102,7 +101,12 @@ impl Command for Skip { io::copy(&mut (&mut reader).take(n as u64), &mut io::sink()) .err_span(span)?; Ok(PipelineData::ByteStream( - ByteStream::read(reader, call.head, None, ByteStreamType::Binary), + ByteStream::read( + reader, + call.head, + Signals::empty(), + ByteStreamType::Binary, + ), metadata, )) } else { @@ -124,7 +128,11 @@ impl Command for Skip { _ => Ok(input .into_iter_strict(call.head)? .skip(n) - .into_pipeline_data_with_metadata(input_span, ctrlc, metadata)), + .into_pipeline_data_with_metadata( + input_span, + engine_state.signals().clone(), + metadata, + )), } } } diff --git a/crates/nu-command/src/filters/skip/skip_until.rs b/crates/nu-command/src/filters/skip/skip_until.rs index bb36785e00..72cae739af 100644 --- a/crates/nu-command/src/filters/skip/skip_until.rs +++ b/crates/nu-command/src/filters/skip/skip_until.rs @@ -89,7 +89,7 @@ impl Command for SkipUntil { .map(|cond| cond.is_false()) .unwrap_or(false) }) - .into_pipeline_data_with_metadata(head, engine_state.ctrlc.clone(), metadata)) + .into_pipeline_data_with_metadata(head, engine_state.signals().clone(), metadata)) } } diff --git a/crates/nu-command/src/filters/skip/skip_while.rs b/crates/nu-command/src/filters/skip/skip_while.rs index 2747ea6f97..ea9c12bf6a 100644 --- a/crates/nu-command/src/filters/skip/skip_while.rs +++ b/crates/nu-command/src/filters/skip/skip_while.rs @@ -94,7 +94,7 @@ impl Command for SkipWhile { .map(|cond| cond.is_true()) .unwrap_or(false) }) - .into_pipeline_data_with_metadata(head, engine_state.ctrlc.clone(), metadata)) + .into_pipeline_data_with_metadata(head, engine_state.signals().clone(), metadata)) } } diff --git a/crates/nu-command/src/filters/sort.rs b/crates/nu-command/src/filters/sort.rs index 965b997355..179235302d 100644 --- a/crates/nu-command/src/filters/sort.rs +++ b/crates/nu-command/src/filters/sort.rs @@ -173,7 +173,7 @@ impl Command for Sort { let iter = vec.into_iter(); Ok(iter.into_pipeline_data_with_metadata( head, - engine_state.ctrlc.clone(), + engine_state.signals().clone(), metadata, )) } diff --git a/crates/nu-command/src/filters/sort_by.rs b/crates/nu-command/src/filters/sort_by.rs index f3f715bc9d..e832255a26 100644 --- a/crates/nu-command/src/filters/sort_by.rs +++ b/crates/nu-command/src/filters/sort_by.rs @@ -100,7 +100,7 @@ impl Command for SortBy { } let iter = vec.into_iter(); - Ok(iter.into_pipeline_data_with_metadata(head, engine_state.ctrlc.clone(), metadata)) + Ok(iter.into_pipeline_data_with_metadata(head, engine_state.signals().clone(), metadata)) } } diff --git a/crates/nu-command/src/filters/take/take_.rs b/crates/nu-command/src/filters/take/take_.rs index 7ebe22f914..1876244bdd 100644 --- a/crates/nu-command/src/filters/take/take_.rs +++ b/crates/nu-command/src/filters/take/take_.rs @@ -1,4 +1,5 @@ use nu_engine::command_prelude::*; +use nu_protocol::Signals; use std::io::Read; #[derive(Clone)] @@ -46,7 +47,6 @@ impl Command for Take { let head = call.head; let rows_desired: usize = call.req(engine_state, stack, 0)?; - let ctrlc = engine_state.ctrlc.clone(); let metadata = input.metadata(); match input { @@ -56,15 +56,23 @@ impl Command for Take { Value::List { vals, .. } => Ok(vals .into_iter() .take(rows_desired) - .into_pipeline_data_with_metadata(head, ctrlc, metadata)), + .into_pipeline_data_with_metadata( + head, + engine_state.signals().clone(), + metadata, + )), Value::Binary { val, .. } => { let slice: Vec = val.into_iter().take(rows_desired).collect(); Ok(PipelineData::Value(Value::binary(slice, span), metadata)) } Value::Range { val, .. } => Ok(val - .into_range_iter(span, ctrlc.clone()) + .into_range_iter(span, Signals::empty()) .take(rows_desired) - .into_pipeline_data_with_metadata(head, ctrlc, metadata)), + .into_pipeline_data_with_metadata( + head, + engine_state.signals().clone(), + metadata, + )), // Propagate errors by explicitly matching them before the final case. Value::Error { error, .. } => Err(*error), other => Err(ShellError::OnlySupportsThisInputType { @@ -87,7 +95,7 @@ impl Command for Take { ByteStream::read( reader.take(rows_desired as u64), head, - None, + Signals::empty(), ByteStreamType::Binary, ), metadata, diff --git a/crates/nu-command/src/filters/take/take_until.rs b/crates/nu-command/src/filters/take/take_until.rs index 0df2407cb1..c7debf5dee 100644 --- a/crates/nu-command/src/filters/take/take_until.rs +++ b/crates/nu-command/src/filters/take/take_until.rs @@ -85,7 +85,7 @@ impl Command for TakeUntil { .map(|cond| cond.is_false()) .unwrap_or(false) }) - .into_pipeline_data_with_metadata(head, engine_state.ctrlc.clone(), metadata)) + .into_pipeline_data_with_metadata(head, engine_state.signals().clone(), metadata)) } } diff --git a/crates/nu-command/src/filters/take/take_while.rs b/crates/nu-command/src/filters/take/take_while.rs index 7c282ac38a..b8045080ea 100644 --- a/crates/nu-command/src/filters/take/take_while.rs +++ b/crates/nu-command/src/filters/take/take_while.rs @@ -85,7 +85,7 @@ impl Command for TakeWhile { .map(|cond| cond.is_true()) .unwrap_or(false) }) - .into_pipeline_data_with_metadata(head, engine_state.ctrlc.clone(), metadata)) + .into_pipeline_data_with_metadata(head, engine_state.signals().clone(), metadata)) } } diff --git a/crates/nu-command/src/filters/tee.rs b/crates/nu-command/src/filters/tee.rs index 2251f6646a..663b2f19af 100644 --- a/crates/nu-command/src/filters/tee.rs +++ b/crates/nu-command/src/filters/tee.rs @@ -1,15 +1,11 @@ use nu_engine::{command_prelude::*, get_eval_block_with_early_return}; use nu_protocol::{ - byte_stream::copy_with_interrupt, engine::Closure, process::ChildPipe, ByteStream, - ByteStreamSource, OutDest, PipelineMetadata, + byte_stream::copy_with_signals, engine::Closure, process::ChildPipe, ByteStream, + ByteStreamSource, OutDest, PipelineMetadata, Signals, }; use std::{ io::{self, Read, Write}, - sync::{ - atomic::AtomicBool, - mpsc::{self, Sender}, - Arc, - }, + sync::mpsc::{self, Sender}, thread::{self, JoinHandle}, }; @@ -103,12 +99,11 @@ use it in your pipeline."# if let PipelineData::ByteStream(stream, metadata) = input { let span = stream.span(); - let ctrlc = engine_state.ctrlc.clone(); let type_ = stream.type_(); let info = StreamInfo { span, - ctrlc: ctrlc.clone(), + signals: engine_state.signals().clone(), type_, metadata: metadata.clone(), }; @@ -123,7 +118,7 @@ use it in your pipeline."# let tee = IoTee::new(read, tee_thread); Ok(PipelineData::ByteStream( - ByteStream::read(tee, span, ctrlc, type_), + ByteStream::read(tee, span, engine_state.signals().clone(), type_), metadata, )) } @@ -136,7 +131,7 @@ use it in your pipeline."# let tee = IoTee::new(file, tee_thread); Ok(PipelineData::ByteStream( - ByteStream::read(tee, span, ctrlc, type_), + ByteStream::read(tee, span, engine_state.signals().clone(), type_), metadata, )) } @@ -234,19 +229,19 @@ use it in your pipeline."# } let span = input.span().unwrap_or(head); - let ctrlc = engine_state.ctrlc.clone(); let metadata = input.metadata(); let metadata_clone = metadata.clone(); + let signals = engine_state.signals().clone(); Ok(tee(input.into_iter(), move |rx| { - let input = rx.into_pipeline_data_with_metadata(span, ctrlc, metadata_clone); + let input = rx.into_pipeline_data_with_metadata(span, signals, metadata_clone); eval_block(input) }) .err_span(call.head)? .map(move |result| result.unwrap_or_else(|err| Value::error(err, closure_span))) .into_pipeline_data_with_metadata( span, - engine_state.ctrlc.clone(), + engine_state.signals().clone(), metadata, )) } @@ -386,8 +381,13 @@ fn spawn_tee( let thread = thread::Builder::new() .name("tee".into()) .spawn(move || { - // We don't use ctrlc here because we assume it already has it on the other side - let stream = ByteStream::from_iter(receiver.into_iter(), info.span, None, info.type_); + // We use Signals::empty() here because we assume there already is a Signals on the other side + let stream = ByteStream::from_iter( + receiver.into_iter(), + info.span, + Signals::empty(), + info.type_, + ); eval_block(PipelineData::ByteStream(stream, info.metadata)) }) .err_span(info.span)?; @@ -398,13 +398,13 @@ fn spawn_tee( #[derive(Clone)] struct StreamInfo { span: Span, - ctrlc: Option>, + signals: Signals, type_: ByteStreamType, metadata: Option, } fn copy(src: impl Read, dest: impl Write, info: &StreamInfo) -> Result<(), ShellError> { - copy_with_interrupt(src, dest, info.span, info.ctrlc.as_deref())?; + copy_with_signals(src, dest, info.span, &info.signals)?; Ok(()) } @@ -421,11 +421,11 @@ fn copy_on_thread( info: &StreamInfo, ) -> Result>, ShellError> { let span = info.span; - let ctrlc = info.ctrlc.clone(); + let signals = info.signals.clone(); thread::Builder::new() .name("stderr copier".into()) .spawn(move || { - copy_with_interrupt(src, dest, span, ctrlc.as_deref())?; + copy_with_signals(src, dest, span, &signals)?; Ok(()) }) .map_err(|e| e.into_spanned(span).into()) diff --git a/crates/nu-command/src/filters/transpose.rs b/crates/nu-command/src/filters/transpose.rs index 49caa56d18..95aa382e4f 100644 --- a/crates/nu-command/src/filters/transpose.rs +++ b/crates/nu-command/src/filters/transpose.rs @@ -173,7 +173,6 @@ pub fn transpose( }); } - let ctrlc = engine_state.ctrlc.clone(); let metadata = input.metadata(); let input: Vec<_> = input.into_iter().collect(); @@ -284,7 +283,11 @@ pub fn transpose( metadata, )) } else { - Ok(result_data.into_pipeline_data_with_metadata(name, ctrlc, metadata)) + Ok(result_data.into_pipeline_data_with_metadata( + name, + engine_state.signals().clone(), + metadata, + )) } } diff --git a/crates/nu-command/src/filters/uniq.rs b/crates/nu-command/src/filters/uniq.rs index 7d71d868f0..99abb4e152 100644 --- a/crates/nu-command/src/filters/uniq.rs +++ b/crates/nu-command/src/filters/uniq.rs @@ -241,18 +241,18 @@ pub fn uniq( item_mapper: Box ValueCounter>, metadata: Option, ) -> Result { - let ctrlc = engine_state.ctrlc.clone(); let head = call.head; let flag_show_count = call.has_flag(engine_state, stack, "count")?; let flag_show_repeated = call.has_flag(engine_state, stack, "repeated")?; let flag_ignore_case = call.has_flag(engine_state, stack, "ignore-case")?; let flag_only_uniques = call.has_flag(engine_state, stack, "unique")?; + let signals = engine_state.signals().clone(); let uniq_values = input .into_iter() .enumerate() .map_while(|(index, item)| { - if nu_utils::ctrl_c::was_pressed(&ctrlc) { + if signals.interrupted() { return None; } Some(item_mapper(ItemMapperState { diff --git a/crates/nu-command/src/filters/update.rs b/crates/nu-command/src/filters/update.rs index e724ae77ad..d5ef0825ec 100644 --- a/crates/nu-command/src/filters/update.rs +++ b/crates/nu-command/src/filters/update.rs @@ -187,7 +187,11 @@ fn update( Ok(pre_elems .into_iter() .chain(stream) - .into_pipeline_data_with_metadata(head, engine_state.ctrlc.clone(), metadata)) + .into_pipeline_data_with_metadata( + head, + engine_state.signals().clone(), + metadata, + )) } else if let Value::Closure { val, .. } = replacement { let mut closure = ClosureEval::new(engine_state, stack, *val); let stream = stream.map(move |mut value| { diff --git a/crates/nu-command/src/filters/upsert.rs b/crates/nu-command/src/filters/upsert.rs index e3678972fb..af2e6c74b1 100644 --- a/crates/nu-command/src/filters/upsert.rs +++ b/crates/nu-command/src/filters/upsert.rs @@ -247,7 +247,11 @@ fn upsert( Ok(pre_elems .into_iter() .chain(stream) - .into_pipeline_data_with_metadata(head, engine_state.ctrlc.clone(), metadata)) + .into_pipeline_data_with_metadata( + head, + engine_state.signals().clone(), + metadata, + )) } else if let Value::Closure { val, .. } = replacement { let mut closure = ClosureEval::new(engine_state, stack, *val); let stream = stream.map(move |mut value| { diff --git a/crates/nu-command/src/filters/utils.rs b/crates/nu-command/src/filters/utils.rs index 8d9b1300f6..3ebd4bafbd 100644 --- a/crates/nu-command/src/filters/utils.rs +++ b/crates/nu-command/src/filters/utils.rs @@ -32,10 +32,7 @@ pub fn boolean_fold( let mut closure = ClosureEval::new(engine_state, stack, closure); for value in input { - if nu_utils::ctrl_c::was_pressed(&engine_state.ctrlc) { - break; - } - + engine_state.signals().check(head)?; let pred = closure.run_with_value(value)?.into_value(head)?.is_true(); if pred == accumulator { diff --git a/crates/nu-command/src/filters/values.rs b/crates/nu-command/src/filters/values.rs index f6ff8cda2e..9a08f1168c 100644 --- a/crates/nu-command/src/filters/values.rs +++ b/crates/nu-command/src/filters/values.rs @@ -134,7 +134,7 @@ fn values( head: Span, input: PipelineData, ) -> Result { - let ctrlc = engine_state.ctrlc.clone(); + let signals = engine_state.signals().clone(); let metadata = input.metadata(); match input { PipelineData::Empty => Ok(PipelineData::Empty), @@ -144,7 +144,7 @@ fn values( Value::List { vals, .. } => match get_values(&vals, head, span) { Ok(cols) => Ok(cols .into_iter() - .into_pipeline_data_with_metadata(head, ctrlc, metadata)), + .into_pipeline_data_with_metadata(head, signals, metadata)), Err(err) => Err(err), }, Value::Custom { val, .. } => { @@ -152,7 +152,7 @@ fn values( match get_values(&[input_as_base_value], head, span) { Ok(cols) => Ok(cols .into_iter() - .into_pipeline_data_with_metadata(head, ctrlc, metadata)), + .into_pipeline_data_with_metadata(head, signals, metadata)), Err(err) => Err(err), } } @@ -160,7 +160,7 @@ fn values( .values() .cloned() .collect::>() - .into_pipeline_data_with_metadata(head, ctrlc, metadata)), + .into_pipeline_data_with_metadata(head, signals, metadata)), // Propagate errors Value::Error { error, .. } => Err(*error), other => Err(ShellError::OnlySupportsThisInputType { @@ -176,7 +176,7 @@ fn values( match get_values(&vals, head, head) { Ok(cols) => Ok(cols .into_iter() - .into_pipeline_data_with_metadata(head, ctrlc, metadata)), + .into_pipeline_data_with_metadata(head, signals, metadata)), Err(err) => Err(err), } } diff --git a/crates/nu-command/src/filters/where_.rs b/crates/nu-command/src/filters/where_.rs index 922879d09b..0a8c8c9ef3 100644 --- a/crates/nu-command/src/filters/where_.rs +++ b/crates/nu-command/src/filters/where_.rs @@ -70,7 +70,7 @@ not supported."# Err(err) => Some(Value::error(err, head)), } }) - .into_pipeline_data_with_metadata(head, engine_state.ctrlc.clone(), metadata)) + .into_pipeline_data_with_metadata(head, engine_state.signals().clone(), metadata)) } fn examples(&self) -> Vec { diff --git a/crates/nu-command/src/filters/window.rs b/crates/nu-command/src/filters/window.rs index 5b386b9f84..3c470f478d 100644 --- a/crates/nu-command/src/filters/window.rs +++ b/crates/nu-command/src/filters/window.rs @@ -113,7 +113,6 @@ impl Command for Window { ) -> Result { let head = call.head; let group_size: Spanned = call.req(engine_state, stack, 0)?; - let ctrlc = engine_state.ctrlc.clone(); let metadata = input.metadata(); let stride: Option = call.get_flag(engine_state, stack, "stride")?; let remainder = call.has_flag(engine_state, stack, "remainder")?; @@ -131,7 +130,11 @@ impl Command for Window { remainder, }; - Ok(each_group_iterator.into_pipeline_data_with_metadata(head, ctrlc, metadata)) + Ok(each_group_iterator.into_pipeline_data_with_metadata( + head, + engine_state.signals().clone(), + metadata, + )) } } diff --git a/crates/nu-command/src/filters/wrap.rs b/crates/nu-command/src/filters/wrap.rs index 52a0fb22c3..dfe1c11d03 100644 --- a/crates/nu-command/src/filters/wrap.rs +++ b/crates/nu-command/src/filters/wrap.rs @@ -42,7 +42,7 @@ impl Command for Wrap { | PipelineData::ListStream { .. } => Ok(input .into_iter() .map(move |x| Value::record(record! { name.clone() => x }, span)) - .into_pipeline_data_with_metadata(span, engine_state.ctrlc.clone(), metadata)), + .into_pipeline_data_with_metadata(span, engine_state.signals().clone(), metadata)), PipelineData::ByteStream(stream, ..) => Ok(Value::record( record! { name => stream.into_value()? }, span, diff --git a/crates/nu-command/src/filters/zip.rs b/crates/nu-command/src/filters/zip.rs index 9d81451ed4..59e2b4ac98 100644 --- a/crates/nu-command/src/filters/zip.rs +++ b/crates/nu-command/src/filters/zip.rs @@ -112,7 +112,7 @@ impl Command for Zip { .into_iter() .zip(other) .map(move |(x, y)| Value::list(vec![x, y], head)) - .into_pipeline_data_with_metadata(head, engine_state.ctrlc.clone(), metadata)) + .into_pipeline_data_with_metadata(head, engine_state.signals().clone(), metadata)) } } diff --git a/crates/nu-command/src/formats/from/delimited.rs b/crates/nu-command/src/formats/from/delimited.rs index 8f84890645..0fea7e082b 100644 --- a/crates/nu-command/src/formats/from/delimited.rs +++ b/crates/nu-command/src/formats/from/delimited.rs @@ -1,5 +1,5 @@ use csv::{ReaderBuilder, Trim}; -use nu_protocol::{ByteStream, ListStream, PipelineData, ShellError, Span, Value}; +use nu_protocol::{ByteStream, ListStream, PipelineData, ShellError, Signals, Span, Value}; fn from_csv_error(err: csv::Error, span: Span) -> ShellError { ShellError::DelimiterError { @@ -25,7 +25,7 @@ fn from_delimited_stream( let input_reader = if let Some(stream) = input.reader() { stream } else { - return Ok(ListStream::new(std::iter::empty(), span, None)); + return Ok(ListStream::new(std::iter::empty(), span, Signals::empty())); }; let mut reader = ReaderBuilder::new() @@ -83,7 +83,7 @@ fn from_delimited_stream( Value::record(columns.zip(values).collect(), span) }); - Ok(ListStream::new(iter, span, None)) + Ok(ListStream::new(iter, span, Signals::empty())) } pub(super) struct DelimitedReaderConfig { @@ -106,7 +106,7 @@ pub(super) fn from_delimited_data( PipelineData::Empty => Ok(PipelineData::Empty), PipelineData::Value(value, metadata) => { let string = value.into_string()?; - let byte_stream = ByteStream::read_string(string, name, None); + let byte_stream = ByteStream::read_string(string, name, Signals::empty()); Ok(PipelineData::ListStream( from_delimited_stream(config, byte_stream, name)?, metadata, diff --git a/crates/nu-command/src/formats/from/json.rs b/crates/nu-command/src/formats/from/json.rs index a1b43abb0a..6252afff4c 100644 --- a/crates/nu-command/src/formats/from/json.rs +++ b/crates/nu-command/src/formats/from/json.rs @@ -1,10 +1,7 @@ -use std::{ - io::{BufRead, Cursor}, - sync::{atomic::AtomicBool, Arc}, -}; +use std::io::{BufRead, Cursor}; use nu_engine::command_prelude::*; -use nu_protocol::{ListStream, PipelineMetadata}; +use nu_protocol::{ListStream, PipelineMetadata, Signals}; #[derive(Clone)] pub struct FromJson; @@ -80,7 +77,12 @@ impl Command for FromJson { match input { PipelineData::Value(Value::String { val, .. }, metadata) => { Ok(PipelineData::ListStream( - read_json_lines(Cursor::new(val), span, strict, engine_state.ctrlc.clone()), + read_json_lines( + Cursor::new(val), + span, + strict, + engine_state.signals().clone(), + ), update_metadata(metadata), )) } @@ -89,7 +91,7 @@ impl Command for FromJson { { if let Some(reader) = stream.reader() { Ok(PipelineData::ListStream( - read_json_lines(reader, span, strict, None), + read_json_lines(reader, span, strict, Signals::empty()), update_metadata(metadata), )) } else { @@ -127,7 +129,7 @@ fn read_json_lines( input: impl BufRead + Send + 'static, span: Span, strict: bool, - interrupt: Option>, + signals: Signals, ) -> ListStream { let iter = input .lines() @@ -142,7 +144,7 @@ fn read_json_lines( }) .map(move |result| result.unwrap_or_else(|err| Value::error(err, span))); - ListStream::new(iter, span, interrupt) + ListStream::new(iter, span, signals) } fn convert_nujson_to_value(value: nu_json::Value, span: Span) -> Value { diff --git a/crates/nu-command/src/formats/from/msgpack.rs b/crates/nu-command/src/formats/from/msgpack.rs index 4d8ea5e320..d3d0d710f1 100644 --- a/crates/nu-command/src/formats/from/msgpack.rs +++ b/crates/nu-command/src/formats/from/msgpack.rs @@ -5,12 +5,12 @@ use std::{ error::Error, io::{self, Cursor, ErrorKind}, string::FromUtf8Error, - sync::{atomic::AtomicBool, Arc}, }; use byteorder::{BigEndian, ReadBytesExt}; use chrono::{TimeZone, Utc}; use nu_engine::command_prelude::*; +use nu_protocol::Signals; use rmp::decode::{self as mp, ValueReadError}; /// Max recursion depth @@ -111,7 +111,7 @@ MessagePack: https://msgpack.org/ let opts = Opts { span: call.head, objects, - ctrlc: engine_state.ctrlc.clone(), + signals: engine_state.signals().clone(), }; match input { // Deserialize from a byte buffer @@ -227,7 +227,7 @@ impl From for ShellError { pub(crate) struct Opts { pub span: Span, pub objects: bool, - pub ctrlc: Option>, + pub signals: Signals, } /// Read single or multiple values into PipelineData @@ -238,7 +238,7 @@ pub(crate) fn read_msgpack( let Opts { span, objects, - ctrlc, + signals, } = opts; if objects { // Make an iterator that reads multiple values from the reader @@ -262,7 +262,7 @@ pub(crate) fn read_msgpack( None } }) - .into_pipeline_data(span, ctrlc)) + .into_pipeline_data(span, signals)) } else { // Read a single value and then make sure it's EOF let result = read_value(&mut input, span, 0)?; diff --git a/crates/nu-command/src/formats/from/msgpackz.rs b/crates/nu-command/src/formats/from/msgpackz.rs index 7960f3f97a..c98b72cdd6 100644 --- a/crates/nu-command/src/formats/from/msgpackz.rs +++ b/crates/nu-command/src/formats/from/msgpackz.rs @@ -41,7 +41,7 @@ impl Command for FromMsgpackz { let opts = Opts { span, objects, - ctrlc: engine_state.ctrlc.clone(), + signals: engine_state.signals().clone(), }; match input { // Deserialize from a byte buffer diff --git a/crates/nu-command/src/formats/to/delimited.rs b/crates/nu-command/src/formats/to/delimited.rs index a7a2480a34..34bb06b8a2 100644 --- a/crates/nu-command/src/formats/to/delimited.rs +++ b/crates/nu-command/src/formats/to/delimited.rs @@ -1,7 +1,7 @@ use csv::WriterBuilder; use nu_cmd_base::formats::to::delimited::merge_descriptors; use nu_protocol::{ - ByteStream, ByteStreamType, Config, PipelineData, ShellError, Span, Spanned, Value, + ByteStream, ByteStreamType, Config, PipelineData, ShellError, Signals, Span, Spanned, Value, }; use std::{iter, sync::Arc}; @@ -128,37 +128,42 @@ pub fn to_delimited_data( // If we're configured to generate a header, we generate it first, then set this false let mut is_header = !noheaders; - let stream = ByteStream::from_fn(head, None, ByteStreamType::String, move |buffer| { - let mut wtr = WriterBuilder::new() - .delimiter(separator) - .from_writer(buffer); + let stream = ByteStream::from_fn( + head, + Signals::empty(), + ByteStreamType::String, + move |buffer| { + let mut wtr = WriterBuilder::new() + .delimiter(separator) + .from_writer(buffer); - if is_header { - // Unless we are configured not to write a header, we write the header row now, once, - // before everything else. - wtr.write_record(&columns) - .map_err(|err| make_csv_error(err, format_name, head))?; - is_header = false; - Ok(true) - } else if let Some(row) = iter.next() { - // Write each column of a normal row, in order - let record = row.into_record()?; - for column in &columns { - let field = record - .get(column) - .map(|v| to_string_tagged_value(v, &config, format_name)) - .unwrap_or(Ok(String::new()))?; - wtr.write_field(field) + if is_header { + // Unless we are configured not to write a header, we write the header row now, once, + // before everything else. + wtr.write_record(&columns) .map_err(|err| make_csv_error(err, format_name, head))?; + is_header = false; + Ok(true) + } else if let Some(row) = iter.next() { + // Write each column of a normal row, in order + let record = row.into_record()?; + for column in &columns { + let field = record + .get(column) + .map(|v| to_string_tagged_value(v, &config, format_name)) + .unwrap_or(Ok(String::new()))?; + wtr.write_field(field) + .map_err(|err| make_csv_error(err, format_name, head))?; + } + // End the row + wtr.write_record(iter::empty::()) + .map_err(|err| make_csv_error(err, format_name, head))?; + Ok(true) + } else { + Ok(false) } - // End the row - wtr.write_record(iter::empty::()) - .map_err(|err| make_csv_error(err, format_name, head))?; - Ok(true) - } else { - Ok(false) - } - }); + }, + ); Ok(PipelineData::ByteStream(stream, metadata)) } diff --git a/crates/nu-command/src/formats/to/msgpack.rs b/crates/nu-command/src/formats/to/msgpack.rs index bfeb428e3e..02515e26fe 100644 --- a/crates/nu-command/src/formats/to/msgpack.rs +++ b/crates/nu-command/src/formats/to/msgpack.rs @@ -5,7 +5,7 @@ use std::io; use byteorder::{BigEndian, WriteBytesExt}; use nu_engine::command_prelude::*; -use nu_protocol::{ast::PathMember, Spanned}; +use nu_protocol::{ast::PathMember, Signals, Spanned}; use rmp::encode as mp; /// Max recursion depth @@ -189,7 +189,7 @@ pub(crate) fn write_value( // Convert range to list write_value( out, - &Value::list(val.into_range_iter(span, None).collect(), span), + &Value::list(val.into_range_iter(span, Signals::empty()).collect(), span), depth, )?; } diff --git a/crates/nu-command/src/formats/to/text.rs b/crates/nu-command/src/formats/to/text.rs index 1aa1114bce..82b853248c 100644 --- a/crates/nu-command/src/formats/to/text.rs +++ b/crates/nu-command/src/formats/to/text.rs @@ -60,7 +60,7 @@ impl Command for ToText { ByteStream::from_iter( iter, span, - engine_state.ctrlc.clone(), + engine_state.signals().clone(), ByteStreamType::String, ), update_metadata(meta), diff --git a/crates/nu-command/src/generators/generate.rs b/crates/nu-command/src/generators/generate.rs index 44bca993d0..8d4f48d3fb 100644 --- a/crates/nu-command/src/generators/generate.rs +++ b/crates/nu-command/src/generators/generate.rs @@ -166,7 +166,7 @@ used as the next argument to the closure, otherwise generation stops. Ok(iter .flatten() - .into_pipeline_data(call.head, engine_state.ctrlc.clone())) + .into_pipeline_data(call.head, engine_state.signals().clone())) } } diff --git a/crates/nu-command/src/generators/seq.rs b/crates/nu-command/src/generators/seq.rs index 359d479ca0..3636d89d4c 100644 --- a/crates/nu-command/src/generators/seq.rs +++ b/crates/nu-command/src/generators/seq.rs @@ -129,7 +129,7 @@ pub fn run_seq( span, }, span, - engine_state.ctrlc.clone(), + engine_state.signals().clone(), ) } else { ListStream::new( @@ -141,7 +141,7 @@ pub fn run_seq( span, }, span, - engine_state.ctrlc.clone(), + engine_state.signals().clone(), ) }; diff --git a/crates/nu-command/src/hash/generic_digest.rs b/crates/nu-command/src/hash/generic_digest.rs index ab15ccae7a..f098f8a0cb 100644 --- a/crates/nu-command/src/hash/generic_digest.rs +++ b/crates/nu-command/src/hash/generic_digest.rs @@ -96,7 +96,7 @@ where } } else { let args = Arguments { binary, cell_paths }; - operate(action::, args, input, head, engine_state.ctrlc.clone()) + operate(action::, args, input, head, engine_state.signals()) } } } diff --git a/crates/nu-command/src/math/abs.rs b/crates/nu-command/src/math/abs.rs index 8f653142fa..b16097ba8d 100644 --- a/crates/nu-command/src/math/abs.rs +++ b/crates/nu-command/src/math/abs.rs @@ -42,10 +42,7 @@ impl Command for SubCommand { input: PipelineData, ) -> Result { let head = call.head; - input.map( - move |value| abs_helper(value, head), - engine_state.ctrlc.clone(), - ) + input.map(move |value| abs_helper(value, head), engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-command/src/math/ceil.rs b/crates/nu-command/src/math/ceil.rs index 0b0f6d1696..0886ff0bc5 100644 --- a/crates/nu-command/src/math/ceil.rs +++ b/crates/nu-command/src/math/ceil.rs @@ -41,10 +41,7 @@ impl Command for SubCommand { if matches!(input, PipelineData::Empty) { return Err(ShellError::PipelineEmpty { dst_span: head }); } - input.map( - move |value| operate(value, head), - engine_state.ctrlc.clone(), - ) + input.map(move |value| operate(value, head), engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-command/src/math/floor.rs b/crates/nu-command/src/math/floor.rs index e85e3ca674..c2b34c2cf9 100644 --- a/crates/nu-command/src/math/floor.rs +++ b/crates/nu-command/src/math/floor.rs @@ -41,10 +41,7 @@ impl Command for SubCommand { if matches!(input, PipelineData::Empty) { return Err(ShellError::PipelineEmpty { dst_span: head }); } - input.map( - move |value| operate(value, head), - engine_state.ctrlc.clone(), - ) + input.map(move |value| operate(value, head), engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-command/src/math/log.rs b/crates/nu-command/src/math/log.rs index 90fad17daf..7a7e4bd27b 100644 --- a/crates/nu-command/src/math/log.rs +++ b/crates/nu-command/src/math/log.rs @@ -59,7 +59,7 @@ impl Command for SubCommand { let base = base.item; input.map( move |value| operate(value, head, base), - engine_state.ctrlc.clone(), + engine_state.signals(), ) } diff --git a/crates/nu-command/src/math/round.rs b/crates/nu-command/src/math/round.rs index 7693c9a316..eafe669cf1 100644 --- a/crates/nu-command/src/math/round.rs +++ b/crates/nu-command/src/math/round.rs @@ -50,7 +50,7 @@ impl Command for SubCommand { } input.map( move |value| operate(value, head, precision_param), - engine_state.ctrlc.clone(), + engine_state.signals(), ) } diff --git a/crates/nu-command/src/math/sqrt.rs b/crates/nu-command/src/math/sqrt.rs index c9c9765912..a3ab5fbbd8 100644 --- a/crates/nu-command/src/math/sqrt.rs +++ b/crates/nu-command/src/math/sqrt.rs @@ -41,10 +41,7 @@ impl Command for SubCommand { if matches!(input, PipelineData::Empty) { return Err(ShellError::PipelineEmpty { dst_span: head }); } - input.map( - move |value| operate(value, head), - engine_state.ctrlc.clone(), - ) + input.map(move |value| operate(value, head), engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-command/src/math/utils.rs b/crates/nu-command/src/math/utils.rs index 9d2c15e15f..765c1f42fb 100644 --- a/crates/nu-command/src/math/utils.rs +++ b/crates/nu-command/src/math/utils.rs @@ -1,6 +1,6 @@ use core::slice; use indexmap::IndexMap; -use nu_protocol::{ast::Call, IntoPipelineData, PipelineData, ShellError, Span, Value}; +use nu_protocol::{ast::Call, IntoPipelineData, PipelineData, ShellError, Signals, Span, Value}; pub fn run_with_function( call: &Call, @@ -92,7 +92,7 @@ pub fn calculate( } PipelineData::Value(Value::Range { val, .. }, ..) => { let new_vals: Result, ShellError> = val - .into_range_iter(span, None) + .into_range_iter(span, Signals::empty()) .map(|val| mf(&[val], span, name)) .collect(); diff --git a/crates/nu-command/src/network/http/client.rs b/crates/nu-command/src/network/http/client.rs index 2c02d7be33..deeb768eea 100644 --- a/crates/nu-command/src/network/http/client.rs +++ b/crates/nu-command/src/network/http/client.rs @@ -5,16 +5,12 @@ use base64::{ Engine, }; use nu_engine::command_prelude::*; -use nu_protocol::ByteStream; +use nu_protocol::{ByteStream, Signals}; use std::{ collections::HashMap, path::PathBuf, str::FromStr, - sync::{ - atomic::AtomicBool, - mpsc::{self, RecvTimeoutError}, - Arc, - }, + sync::mpsc::{self, RecvTimeoutError}, time::Duration, }; use ureq::{Error, ErrorKind, Request, Response}; @@ -129,7 +125,7 @@ pub fn response_to_buffer( let reader = response.into_reader(); PipelineData::ByteStream( - ByteStream::read(reader, span, engine_state.ctrlc.clone(), response_type) + ByteStream::read(reader, span, engine_state.signals().clone(), response_type) .with_known_size(buffer_size), None, ) @@ -192,13 +188,14 @@ pub fn send_request( request: Request, http_body: HttpBody, content_type: Option, - ctrl_c: Option>, + span: Span, + signals: &Signals, ) -> Result { let request_url = request.url().to_string(); match http_body { HttpBody::None => { - send_cancellable_request(&request_url, Box::new(|| request.call()), ctrl_c) + send_cancellable_request(&request_url, Box::new(|| request.call()), span, signals) } HttpBody::ByteStream(byte_stream) => { let req = if let Some(content_type) = content_type { @@ -207,7 +204,7 @@ pub fn send_request( request }; - send_cancellable_request_bytes(&request_url, req, byte_stream, ctrl_c) + send_cancellable_request_bytes(&request_url, req, byte_stream, span, signals) } HttpBody::Value(body) => { let (body_type, req) = match content_type { @@ -224,20 +221,32 @@ pub fn send_request( Value::Binary { val, .. } => send_cancellable_request( &request_url, Box::new(move || req.send_bytes(&val)), - ctrl_c, + span, + signals, ), Value::String { .. } if body_type == BodyType::Json => { let data = value_to_json_value(&body)?; - send_cancellable_request(&request_url, Box::new(|| req.send_json(data)), ctrl_c) + send_cancellable_request( + &request_url, + Box::new(|| req.send_json(data)), + span, + signals, + ) } Value::String { val, .. } => send_cancellable_request( &request_url, Box::new(move || req.send_string(&val)), - ctrl_c, + span, + signals, ), Value::Record { .. } if body_type == BodyType::Json => { let data = value_to_json_value(&body)?; - send_cancellable_request(&request_url, Box::new(|| req.send_json(data)), ctrl_c) + send_cancellable_request( + &request_url, + Box::new(|| req.send_json(data)), + span, + signals, + ) } Value::Record { val, .. } if body_type == BodyType::Form => { let mut data: Vec<(String, String)> = Vec::with_capacity(val.len()); @@ -254,7 +263,7 @@ pub fn send_request( .collect::>(); req.send_form(&data) }; - send_cancellable_request(&request_url, Box::new(request_fn), ctrl_c) + send_cancellable_request(&request_url, Box::new(request_fn), span, signals) } Value::List { vals, .. } if body_type == BodyType::Form => { if vals.len() % 2 != 0 { @@ -276,11 +285,16 @@ pub fn send_request( .collect::>(); req.send_form(&data) }; - send_cancellable_request(&request_url, Box::new(request_fn), ctrl_c) + send_cancellable_request(&request_url, Box::new(request_fn), span, signals) } Value::List { .. } if body_type == BodyType::Json => { let data = value_to_json_value(&body)?; - send_cancellable_request(&request_url, Box::new(|| req.send_json(data)), ctrl_c) + send_cancellable_request( + &request_url, + Box::new(|| req.send_json(data)), + span, + signals, + ) } _ => Err(ShellErrorOrRequestError::ShellError(ShellError::IOError { msg: "unsupported body input".into(), @@ -295,7 +309,8 @@ pub fn send_request( fn send_cancellable_request( request_url: &str, request_fn: Box Result + Sync + Send>, - ctrl_c: Option>, + span: Span, + signals: &Signals, ) -> Result { let (tx, rx) = mpsc::channel::>(); @@ -310,12 +325,7 @@ fn send_cancellable_request( // ...and poll the channel for responses loop { - if nu_utils::ctrl_c::was_pressed(&ctrl_c) { - // Return early and give up on the background thread. The connection will either time out or be disconnected - return Err(ShellErrorOrRequestError::ShellError( - ShellError::InterruptedByUser { span: None }, - )); - } + signals.check(span)?; // 100ms wait time chosen arbitrarily match rx.recv_timeout(Duration::from_millis(100)) { @@ -336,7 +346,8 @@ fn send_cancellable_request_bytes( request_url: &str, request: Request, byte_stream: ByteStream, - ctrl_c: Option>, + span: Span, + signals: &Signals, ) -> Result { let (tx, rx) = mpsc::channel::>(); let request_url_string = request_url.to_string(); @@ -369,12 +380,7 @@ fn send_cancellable_request_bytes( // ...and poll the channel for responses loop { - if nu_utils::ctrl_c::was_pressed(&ctrl_c) { - // Return early and give up on the background thread. The connection will either time out or be disconnected - return Err(ShellErrorOrRequestError::ShellError( - ShellError::InterruptedByUser { span: None }, - )); - } + signals.check(span)?; // 100ms wait time chosen arbitrarily match rx.recv_timeout(Duration::from_millis(100)) { diff --git a/crates/nu-command/src/network/http/delete.rs b/crates/nu-command/src/network/http/delete.rs index c2ef774a01..f32c9d15bb 100644 --- a/crates/nu-command/src/network/http/delete.rs +++ b/crates/nu-command/src/network/http/delete.rs @@ -203,7 +203,6 @@ fn helper( args: Arguments, ) -> Result { let span = args.url.span(); - let ctrl_c = engine_state.ctrlc.clone(); let (requested_url, _) = http_parse_url(call, span, args.url)?; let redirect_mode = http_parse_redirect_mode(args.redirect)?; @@ -214,7 +213,13 @@ fn helper( request = request_add_authorization_header(args.user, args.password, request); request = request_add_custom_headers(args.headers, request)?; - let response = send_request(request.clone(), args.data, args.content_type, ctrl_c); + let response = send_request( + request.clone(), + args.data, + args.content_type, + call.head, + engine_state.signals(), + ); let request_flags = RequestFlags { raw: args.raw, diff --git a/crates/nu-command/src/network/http/get.rs b/crates/nu-command/src/network/http/get.rs index 04582d5f00..3b702404f4 100644 --- a/crates/nu-command/src/network/http/get.rs +++ b/crates/nu-command/src/network/http/get.rs @@ -171,7 +171,6 @@ fn helper( args: Arguments, ) -> Result { let span = args.url.span(); - let ctrl_c = engine_state.ctrlc.clone(); let (requested_url, _) = http_parse_url(call, span, args.url)?; let redirect_mode = http_parse_redirect_mode(args.redirect)?; @@ -182,7 +181,13 @@ fn helper( request = request_add_authorization_header(args.user, args.password, request); request = request_add_custom_headers(args.headers, request)?; - let response = send_request(request.clone(), HttpBody::None, None, ctrl_c); + let response = send_request( + request.clone(), + HttpBody::None, + None, + call.head, + engine_state.signals(), + ); let request_flags = RequestFlags { raw: args.raw, diff --git a/crates/nu-command/src/network/http/head.rs b/crates/nu-command/src/network/http/head.rs index 57dfafcba2..797fa138e3 100644 --- a/crates/nu-command/src/network/http/head.rs +++ b/crates/nu-command/src/network/http/head.rs @@ -1,13 +1,11 @@ +use super::client::HttpBody; use crate::network::http::client::{ check_response_redirection, http_client, http_parse_redirect_mode, http_parse_url, request_add_authorization_header, request_add_custom_headers, request_handle_response_headers, request_set_timeout, send_request, }; use nu_engine::command_prelude::*; - -use std::sync::{atomic::AtomicBool, Arc}; - -use super::client::HttpBody; +use nu_protocol::Signals; #[derive(Clone)] pub struct SubCommand; @@ -133,9 +131,8 @@ fn run_head( timeout: call.get_flag(engine_state, stack, "max-time")?, redirect: call.get_flag(engine_state, stack, "redirect-mode")?, }; - let ctrl_c = engine_state.ctrlc.clone(); - helper(engine_state, stack, call, args, ctrl_c) + helper(engine_state, stack, call, args, engine_state.signals()) } // Helper function that actually goes to retrieve the resource from the url given @@ -145,7 +142,7 @@ fn helper( stack: &mut Stack, call: &Call, args: Arguments, - ctrlc: Option>, + signals: &Signals, ) -> Result { let span = args.url.span(); let (requested_url, _) = http_parse_url(call, span, args.url)?; @@ -158,7 +155,7 @@ fn helper( request = request_add_authorization_header(args.user, args.password, request); request = request_add_custom_headers(args.headers, request)?; - let response = send_request(request, HttpBody::None, None, ctrlc); + let response = send_request(request, HttpBody::None, None, call.head, signals); check_response_redirection(redirect_mode, span, &response)?; request_handle_response_headers(span, response) } diff --git a/crates/nu-command/src/network/http/options.rs b/crates/nu-command/src/network/http/options.rs index 91ecac02d5..cd9a1f79c1 100644 --- a/crates/nu-command/src/network/http/options.rs +++ b/crates/nu-command/src/network/http/options.rs @@ -151,7 +151,6 @@ fn helper( args: Arguments, ) -> Result { let span = args.url.span(); - let ctrl_c = engine_state.ctrlc.clone(); let (requested_url, _) = http_parse_url(call, span, args.url)?; let client = http_client(args.insecure, RedirectMode::Follow, engine_state, stack)?; @@ -161,7 +160,13 @@ fn helper( request = request_add_authorization_header(args.user, args.password, request); request = request_add_custom_headers(args.headers, request)?; - let response = send_request(request.clone(), HttpBody::None, None, ctrl_c); + let response = send_request( + request.clone(), + HttpBody::None, + None, + call.head, + engine_state.signals(), + ); // http options' response always showed in header, so we set full to true. // And `raw` is useless too because options method doesn't return body, here we set to true diff --git a/crates/nu-command/src/network/http/patch.rs b/crates/nu-command/src/network/http/patch.rs index 2cf66a2a82..7f49781284 100644 --- a/crates/nu-command/src/network/http/patch.rs +++ b/crates/nu-command/src/network/http/patch.rs @@ -205,7 +205,6 @@ fn helper( args: Arguments, ) -> Result { let span = args.url.span(); - let ctrl_c = engine_state.ctrlc.clone(); let (requested_url, _) = http_parse_url(call, span, args.url)?; let redirect_mode = http_parse_redirect_mode(args.redirect)?; @@ -216,7 +215,13 @@ fn helper( request = request_add_authorization_header(args.user, args.password, request); request = request_add_custom_headers(args.headers, request)?; - let response = send_request(request.clone(), args.data, args.content_type, ctrl_c); + let response = send_request( + request.clone(), + args.data, + args.content_type, + call.head, + engine_state.signals(), + ); let request_flags = RequestFlags { raw: args.raw, diff --git a/crates/nu-command/src/network/http/post.rs b/crates/nu-command/src/network/http/post.rs index d48bbcc9fe..ea4ec093f3 100644 --- a/crates/nu-command/src/network/http/post.rs +++ b/crates/nu-command/src/network/http/post.rs @@ -203,7 +203,6 @@ fn helper( args: Arguments, ) -> Result { let span = args.url.span(); - let ctrl_c = engine_state.ctrlc.clone(); let (requested_url, _) = http_parse_url(call, span, args.url)?; let redirect_mode = http_parse_redirect_mode(args.redirect)?; @@ -214,7 +213,13 @@ fn helper( request = request_add_authorization_header(args.user, args.password, request); request = request_add_custom_headers(args.headers, request)?; - let response = send_request(request.clone(), args.data, args.content_type, ctrl_c); + let response = send_request( + request.clone(), + args.data, + args.content_type, + call.head, + engine_state.signals(), + ); let request_flags = RequestFlags { raw: args.raw, diff --git a/crates/nu-command/src/network/http/put.rs b/crates/nu-command/src/network/http/put.rs index dbd3b245cb..e2118ea359 100644 --- a/crates/nu-command/src/network/http/put.rs +++ b/crates/nu-command/src/network/http/put.rs @@ -204,7 +204,6 @@ fn helper( args: Arguments, ) -> Result { let span = args.url.span(); - let ctrl_c = engine_state.ctrlc.clone(); let (requested_url, _) = http_parse_url(call, span, args.url)?; let redirect_mode = http_parse_redirect_mode(args.redirect)?; @@ -215,7 +214,13 @@ fn helper( request = request_add_authorization_header(args.user, args.password, request); request = request_add_custom_headers(args.headers, request)?; - let response = send_request(request.clone(), args.data, args.content_type, ctrl_c); + let response = send_request( + request.clone(), + args.data, + args.content_type, + call.head, + engine_state.signals(), + ); let request_flags = RequestFlags { raw: args.raw, diff --git a/crates/nu-command/src/network/url/decode.rs b/crates/nu-command/src/network/url/decode.rs index 8789eb13ca..b98e50a56e 100644 --- a/crates/nu-command/src/network/url/decode.rs +++ b/crates/nu-command/src/network/url/decode.rs @@ -48,7 +48,7 @@ impl Command for SubCommand { ) -> Result { let cell_paths: Vec = call.rest(engine_state, stack, 0)?; let args = CellPathOnlyArgs::from(cell_paths); - operate(action, args, input, call.head, engine_state.ctrlc.clone()) + operate(action, args, input, call.head, engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-command/src/network/url/encode.rs b/crates/nu-command/src/network/url/encode.rs index 845487963b..96e0289903 100644 --- a/crates/nu-command/src/network/url/encode.rs +++ b/crates/nu-command/src/network/url/encode.rs @@ -50,15 +50,9 @@ impl Command for SubCommand { let cell_paths: Vec = call.rest(engine_state, stack, 0)?; let args = CellPathOnlyArgs::from(cell_paths); if call.has_flag(engine_state, stack, "all")? { - operate( - action_all, - args, - input, - call.head, - engine_state.ctrlc.clone(), - ) + operate(action_all, args, input, call.head, engine_state.signals()) } else { - operate(action, args, input, call.head, engine_state.ctrlc.clone()) + operate(action, args, input, call.head, engine_state.signals()) } } diff --git a/crates/nu-command/src/path/basename.rs b/crates/nu-command/src/path/basename.rs index 04f7cf380e..8b7082048e 100644 --- a/crates/nu-command/src/path/basename.rs +++ b/crates/nu-command/src/path/basename.rs @@ -61,7 +61,7 @@ impl Command for SubCommand { } input.map( move |value| super::operate(&get_basename, &args, value, head), - engine_state.ctrlc.clone(), + engine_state.signals(), ) } @@ -82,7 +82,7 @@ impl Command for SubCommand { } input.map( move |value| super::operate(&get_basename, &args, value, head), - working_set.permanent().ctrlc.clone(), + working_set.permanent().signals(), ) } diff --git a/crates/nu-command/src/path/dirname.rs b/crates/nu-command/src/path/dirname.rs index 2eb864215e..218091ee34 100644 --- a/crates/nu-command/src/path/dirname.rs +++ b/crates/nu-command/src/path/dirname.rs @@ -69,7 +69,7 @@ impl Command for SubCommand { } input.map( move |value| super::operate(&get_dirname, &args, value, head), - engine_state.ctrlc.clone(), + engine_state.signals(), ) } @@ -91,7 +91,7 @@ impl Command for SubCommand { } input.map( move |value| super::operate(&get_dirname, &args, value, head), - working_set.permanent().ctrlc.clone(), + working_set.permanent().signals(), ) } diff --git a/crates/nu-command/src/path/exists.rs b/crates/nu-command/src/path/exists.rs index a99f04113e..86b00c6024 100644 --- a/crates/nu-command/src/path/exists.rs +++ b/crates/nu-command/src/path/exists.rs @@ -65,7 +65,7 @@ If you need to distinguish dirs and files, please use `path type`."# } input.map( move |value| super::operate(&exists, &args, value, head), - engine_state.ctrlc.clone(), + engine_state.signals(), ) } @@ -87,7 +87,7 @@ If you need to distinguish dirs and files, please use `path type`."# } input.map( move |value| super::operate(&exists, &args, value, head), - working_set.permanent().ctrlc.clone(), + working_set.permanent().signals(), ) } diff --git a/crates/nu-command/src/path/expand.rs b/crates/nu-command/src/path/expand.rs index 18497c6426..ac51978810 100644 --- a/crates/nu-command/src/path/expand.rs +++ b/crates/nu-command/src/path/expand.rs @@ -70,7 +70,7 @@ impl Command for SubCommand { } input.map( move |value| super::operate(&expand, &args, value, head), - engine_state.ctrlc.clone(), + engine_state.signals(), ) } @@ -93,7 +93,7 @@ impl Command for SubCommand { } input.map( move |value| super::operate(&expand, &args, value, head), - working_set.permanent().ctrlc.clone(), + working_set.permanent().signals(), ) } diff --git a/crates/nu-command/src/path/parse.rs b/crates/nu-command/src/path/parse.rs index 039f1012ed..cec2f5c6ac 100644 --- a/crates/nu-command/src/path/parse.rs +++ b/crates/nu-command/src/path/parse.rs @@ -63,7 +63,7 @@ On Windows, an extra 'prefix' column is added."# } input.map( move |value| super::operate(&parse, &args, value, head), - engine_state.ctrlc.clone(), + engine_state.signals(), ) } @@ -84,7 +84,7 @@ On Windows, an extra 'prefix' column is added."# } input.map( move |value| super::operate(&parse, &args, value, head), - working_set.permanent().ctrlc.clone(), + working_set.permanent().signals(), ) } diff --git a/crates/nu-command/src/path/relative_to.rs b/crates/nu-command/src/path/relative_to.rs index 6533f6fa79..35df385d74 100644 --- a/crates/nu-command/src/path/relative_to.rs +++ b/crates/nu-command/src/path/relative_to.rs @@ -67,7 +67,7 @@ path."# } input.map( move |value| super::operate(&relative_to, &args, value, head), - engine_state.ctrlc.clone(), + engine_state.signals(), ) } @@ -88,7 +88,7 @@ path."# } input.map( move |value| super::operate(&relative_to, &args, value, head), - working_set.permanent().ctrlc.clone(), + working_set.permanent().signals(), ) } diff --git a/crates/nu-command/src/path/split.rs b/crates/nu-command/src/path/split.rs index ea8e7e1b1f..80d86fb998 100644 --- a/crates/nu-command/src/path/split.rs +++ b/crates/nu-command/src/path/split.rs @@ -51,7 +51,7 @@ impl Command for SubCommand { } input.map( move |value| super::operate(&split, &args, value, head), - engine_state.ctrlc.clone(), + engine_state.signals(), ) } @@ -70,7 +70,7 @@ impl Command for SubCommand { } input.map( move |value| super::operate(&split, &args, value, head), - working_set.permanent().ctrlc.clone(), + working_set.permanent().signals(), ) } diff --git a/crates/nu-command/src/path/type.rs b/crates/nu-command/src/path/type.rs index e58e697604..bf66c8cb3b 100644 --- a/crates/nu-command/src/path/type.rs +++ b/crates/nu-command/src/path/type.rs @@ -64,7 +64,7 @@ If the path does not exist, null will be returned."# } input.map( move |value| super::operate(&path_type, &args, value, head), - engine_state.ctrlc.clone(), + engine_state.signals(), ) } @@ -85,7 +85,7 @@ If the path does not exist, null will be returned."# } input.map( move |value| super::operate(&path_type, &args, value, head), - working_set.permanent().ctrlc.clone(), + working_set.permanent().signals(), ) } diff --git a/crates/nu-command/src/platform/ansi/ansi_.rs b/crates/nu-command/src/platform/ansi/ansi_.rs index 9e0b0bba66..29603be9e7 100644 --- a/crates/nu-command/src/platform/ansi/ansi_.rs +++ b/crates/nu-command/src/platform/ansi/ansi_.rs @@ -1,11 +1,8 @@ use nu_ansi_term::*; use nu_engine::command_prelude::*; -use nu_protocol::engine::StateWorkingSet; +use nu_protocol::{engine::StateWorkingSet, Signals}; use once_cell::sync::Lazy; -use std::{ - collections::HashMap, - sync::{atomic::AtomicBool, Arc}, -}; +use std::collections::HashMap; #[derive(Clone)] pub struct AnsiCommand; @@ -657,10 +654,13 @@ Operating system commands: let escape: bool = call.has_flag(engine_state, stack, "escape")?; let osc: bool = call.has_flag(engine_state, stack, "osc")?; let use_ansi_coloring = engine_state.get_config().use_ansi_coloring; - let ctrlc = engine_state.ctrlc.clone(); if list { - return Ok(generate_ansi_code_list(ctrlc, call.head, use_ansi_coloring)); + return Ok(generate_ansi_code_list( + engine_state.signals().clone(), + call.head, + use_ansi_coloring, + )); } // The code can now be one of the ansi abbreviations like green_bold @@ -691,10 +691,13 @@ Operating system commands: let escape: bool = call.has_flag_const(working_set, "escape")?; let osc: bool = call.has_flag_const(working_set, "osc")?; let use_ansi_coloring = working_set.get_config().use_ansi_coloring; - let ctrlc = working_set.permanent().ctrlc.clone(); if list { - return Ok(generate_ansi_code_list(ctrlc, call.head, use_ansi_coloring)); + return Ok(generate_ansi_code_list( + working_set.permanent().signals().clone(), + call.head, + use_ansi_coloring, + )); } // The code can now be one of the ansi abbreviations like green_bold @@ -827,7 +830,7 @@ pub fn str_to_ansi(s: &str) -> Option { } fn generate_ansi_code_list( - ctrlc: Option>, + signals: Signals, call_span: Span, use_ansi_coloring: bool, ) -> PipelineData { @@ -862,7 +865,7 @@ fn generate_ansi_code_list( Value::record(record, call_span) }) - .into_pipeline_data(call_span, ctrlc) + .into_pipeline_data(call_span, signals.clone()) } fn build_ansi_hashmap(v: &[AnsiCode]) -> HashMap<&str, &str> { diff --git a/crates/nu-command/src/platform/ansi/link.rs b/crates/nu-command/src/platform/ansi/link.rs index 68fc17977b..b45b365d3f 100644 --- a/crates/nu-command/src/platform/ansi/link.rs +++ b/crates/nu-command/src/platform/ansi/link.rs @@ -91,12 +91,12 @@ fn operate( if column_paths.is_empty() { input.map( move |v| process_value(&v, text.as_deref()), - engine_state.ctrlc.clone(), + engine_state.signals(), ) } else { input.map( move |v| process_each_path(v, &column_paths, text.as_deref(), command_span), - engine_state.ctrlc.clone(), + engine_state.signals(), ) } } diff --git a/crates/nu-command/src/platform/ansi/strip.rs b/crates/nu-command/src/platform/ansi/strip.rs index 35d410161c..3d59da6a10 100644 --- a/crates/nu-command/src/platform/ansi/strip.rs +++ b/crates/nu-command/src/platform/ansi/strip.rs @@ -56,7 +56,7 @@ impl Command for SubCommand { cell_paths, config: config.clone(), }; - operate(action, args, input, call.head, engine_state.ctrlc.clone()) + operate(action, args, input, call.head, engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-command/src/platform/dir_info.rs b/crates/nu-command/src/platform/dir_info.rs index e4a3039672..10ce4f5420 100644 --- a/crates/nu-command/src/platform/dir_info.rs +++ b/crates/nu-command/src/platform/dir_info.rs @@ -1,10 +1,7 @@ use filesize::file_real_size_fast; use nu_glob::Pattern; -use nu_protocol::{record, ShellError, Span, Value}; -use std::{ - path::PathBuf, - sync::{atomic::AtomicBool, Arc}, -}; +use nu_protocol::{record, ShellError, Signals, Span, Value}; +use std::path::PathBuf; #[derive(Debug, Clone)] pub struct DirBuilder { @@ -82,8 +79,9 @@ impl DirInfo { path: impl Into, params: &DirBuilder, depth: Option, - ctrl_c: Option>, - ) -> Self { + span: Span, + signals: &Signals, + ) -> Result { let path = path.into(); let mut s = Self { @@ -107,14 +105,12 @@ impl DirInfo { match std::fs::read_dir(&s.path) { Ok(d) => { for f in d { - if nu_utils::ctrl_c::was_pressed(&ctrl_c) { - break; - } + signals.check(span)?; match f { Ok(i) => match i.file_type() { Ok(t) if t.is_dir() => { - s = s.add_dir(i.path(), depth, params, ctrl_c.clone()) + s = s.add_dir(i.path(), depth, params, span, signals)? } Ok(_t) => s = s.add_file(i.path(), params), Err(e) => s = s.add_error(e.into()), @@ -125,7 +121,7 @@ impl DirInfo { } Err(e) => s = s.add_error(e.into()), } - s + Ok(s) } fn add_dir( @@ -133,21 +129,22 @@ impl DirInfo { path: impl Into, mut depth: Option, params: &DirBuilder, - ctrl_c: Option>, - ) -> Self { + span: Span, + signals: &Signals, + ) -> Result { if let Some(current) = depth { if let Some(new) = current.checked_sub(1) { depth = Some(new); } else { - return self; + return Ok(self); } } - let d = DirInfo::new(path, params, depth, ctrl_c); + let d = DirInfo::new(path, params, depth, span, signals)?; self.size += d.size; self.blocks += d.blocks; self.dirs.push(d); - self + Ok(self) } fn add_file(mut self, f: impl Into, params: &DirBuilder) -> Self { diff --git a/crates/nu-command/src/platform/sleep.rs b/crates/nu-command/src/platform/sleep.rs index ddf429509c..4d5f6ec827 100644 --- a/crates/nu-command/src/platform/sleep.rs +++ b/crates/nu-command/src/platform/sleep.rs @@ -56,12 +56,7 @@ impl Command for Sleep { break; } thread::sleep(CTRL_C_CHECK_INTERVAL.min(time_until_deadline)); - // exit early if Ctrl+C was pressed - if nu_utils::ctrl_c::was_pressed(&engine_state.ctrlc) { - return Err(ShellError::InterruptedByUser { - span: Some(call.head), - }); - } + engine_state.signals().check(call.head)?; } Ok(Value::nothing(call.head).into_pipeline_data()) diff --git a/crates/nu-command/src/random/dice.rs b/crates/nu-command/src/random/dice.rs index 2fb659ad1e..5e3a1b98b6 100644 --- a/crates/nu-command/src/random/dice.rs +++ b/crates/nu-command/src/random/dice.rs @@ -78,7 +78,7 @@ fn dice( Value::int(thread_rng.gen_range(1..sides + 1) as i64, span) }); - Ok(ListStream::new(iter, span, engine_state.ctrlc.clone()).into()) + Ok(ListStream::new(iter, span, engine_state.signals().clone()).into()) } #[cfg(test)] diff --git a/crates/nu-command/src/stor/create.rs b/crates/nu-command/src/stor/create.rs index 630718489b..1e1219889d 100644 --- a/crates/nu-command/src/stor/create.rs +++ b/crates/nu-command/src/stor/create.rs @@ -54,7 +54,10 @@ impl Command for StorCreate { let span = call.head; let table_name: Option = call.get_flag(engine_state, stack, "table-name")?; let columns: Option = call.get_flag(engine_state, stack, "columns")?; - let db = Box::new(SQLiteDatabase::new(std::path::Path::new(MEMORY_DB), None)); + let db = Box::new(SQLiteDatabase::new( + std::path::Path::new(MEMORY_DB), + engine_state.signals().clone(), + )); process(table_name, span, &db, columns)?; // dbg!(db.clone()); @@ -141,6 +144,8 @@ fn process( #[cfg(test)] mod test { + use nu_protocol::Signals; + use super::*; #[test] @@ -154,7 +159,10 @@ mod test { fn test_process_with_valid_parameters() { let table_name = Some("test_table".to_string()); let span = Span::unknown(); - let db = Box::new(SQLiteDatabase::new(std::path::Path::new(MEMORY_DB), None)); + let db = Box::new(SQLiteDatabase::new( + std::path::Path::new(MEMORY_DB), + Signals::empty(), + )); let mut columns = Record::new(); columns.insert( "int_column".to_string(), @@ -170,7 +178,10 @@ mod test { fn test_process_with_missing_table_name() { let table_name = None; let span = Span::unknown(); - let db = Box::new(SQLiteDatabase::new(std::path::Path::new(MEMORY_DB), None)); + let db = Box::new(SQLiteDatabase::new( + std::path::Path::new(MEMORY_DB), + Signals::empty(), + )); let mut columns = Record::new(); columns.insert( "int_column".to_string(), @@ -190,7 +201,10 @@ mod test { fn test_process_with_missing_columns() { let table_name = Some("test_table".to_string()); let span = Span::unknown(); - let db = Box::new(SQLiteDatabase::new(std::path::Path::new(MEMORY_DB), None)); + let db = Box::new(SQLiteDatabase::new( + std::path::Path::new(MEMORY_DB), + Signals::empty(), + )); let result = process(table_name, span, &db, None); @@ -205,7 +219,10 @@ mod test { fn test_process_with_unsupported_column_data_type() { let table_name = Some("test_table".to_string()); let span = Span::unknown(); - let db = Box::new(SQLiteDatabase::new(std::path::Path::new(MEMORY_DB), None)); + let db = Box::new(SQLiteDatabase::new( + std::path::Path::new(MEMORY_DB), + Signals::empty(), + )); let mut columns = Record::new(); let column_datatype = "bogus_data_type".to_string(); columns.insert( diff --git a/crates/nu-command/src/stor/delete.rs b/crates/nu-command/src/stor/delete.rs index 4de4874140..676e0490b0 100644 --- a/crates/nu-command/src/stor/delete.rs +++ b/crates/nu-command/src/stor/delete.rs @@ -1,5 +1,6 @@ use crate::database::{SQLiteDatabase, MEMORY_DB}; use nu_engine::command_prelude::*; +use nu_protocol::Signals; #[derive(Clone)] pub struct StorDelete; @@ -82,7 +83,10 @@ impl Command for StorDelete { } // Open the in-mem database - let db = Box::new(SQLiteDatabase::new(std::path::Path::new(MEMORY_DB), None)); + let db = Box::new(SQLiteDatabase::new( + std::path::Path::new(MEMORY_DB), + Signals::empty(), + )); if let Some(new_table_name) = table_name_opt { if let Ok(conn) = db.open_connection() { diff --git a/crates/nu-command/src/stor/export.rs b/crates/nu-command/src/stor/export.rs index 95c5ee9f35..f8255eec49 100644 --- a/crates/nu-command/src/stor/export.rs +++ b/crates/nu-command/src/stor/export.rs @@ -1,5 +1,6 @@ use crate::database::{SQLiteDatabase, MEMORY_DB}; use nu_engine::command_prelude::*; +use nu_protocol::Signals; #[derive(Clone)] pub struct StorExport; @@ -58,7 +59,10 @@ impl Command for StorExport { }; // Open the in-mem database - let db = Box::new(SQLiteDatabase::new(std::path::Path::new(MEMORY_DB), None)); + let db = Box::new(SQLiteDatabase::new( + std::path::Path::new(MEMORY_DB), + Signals::empty(), + )); if let Ok(conn) = db.open_connection() { // This uses vacuum. I'm not really sure if this is the best way to do this. diff --git a/crates/nu-command/src/stor/import.rs b/crates/nu-command/src/stor/import.rs index 682694e8bb..20b5b64f2d 100644 --- a/crates/nu-command/src/stor/import.rs +++ b/crates/nu-command/src/stor/import.rs @@ -1,5 +1,6 @@ use crate::database::{SQLiteDatabase, MEMORY_DB}; use nu_engine::command_prelude::*; +use nu_protocol::Signals; #[derive(Clone)] pub struct StorImport; @@ -58,7 +59,10 @@ impl Command for StorImport { }; // Open the in-mem database - let db = Box::new(SQLiteDatabase::new(std::path::Path::new(MEMORY_DB), None)); + let db = Box::new(SQLiteDatabase::new( + std::path::Path::new(MEMORY_DB), + Signals::empty(), + )); if let Ok(mut conn) = db.open_connection() { db.restore_database_from_file(&mut conn, file_name) diff --git a/crates/nu-command/src/stor/insert.rs b/crates/nu-command/src/stor/insert.rs index 4b9677941b..b6b8d50906 100644 --- a/crates/nu-command/src/stor/insert.rs +++ b/crates/nu-command/src/stor/insert.rs @@ -1,5 +1,6 @@ use crate::database::{values_to_sql, SQLiteDatabase, MEMORY_DB}; use nu_engine::command_prelude::*; +use nu_protocol::Signals; use rusqlite::params_from_iter; #[derive(Clone)] @@ -65,7 +66,10 @@ impl Command for StorInsert { let table_name: Option = call.get_flag(engine_state, stack, "table-name")?; let data_record: Option = call.get_flag(engine_state, stack, "data-record")?; // let config = engine_state.get_config(); - let db = Box::new(SQLiteDatabase::new(std::path::Path::new(MEMORY_DB), None)); + let db = Box::new(SQLiteDatabase::new( + std::path::Path::new(MEMORY_DB), + Signals::empty(), + )); // Check if the record is being passed as input or using the data record parameter let columns = handle(span, data_record, input)?; @@ -198,7 +202,10 @@ mod test { #[test] fn test_process_with_simple_parameters() { - let db = Box::new(SQLiteDatabase::new(std::path::Path::new(MEMORY_DB), None)); + let db = Box::new(SQLiteDatabase::new( + std::path::Path::new(MEMORY_DB), + Signals::empty(), + )); let create_stmt = "CREATE TABLE test_process_with_simple_parameters ( int_column INTEGER, real_column REAL, @@ -237,7 +244,10 @@ mod test { #[test] fn test_process_string_with_space() { - let db = Box::new(SQLiteDatabase::new(std::path::Path::new(MEMORY_DB), None)); + let db = Box::new(SQLiteDatabase::new( + std::path::Path::new(MEMORY_DB), + Signals::empty(), + )); let create_stmt = "CREATE TABLE test_process_string_with_space ( str_column VARCHAR(255) )"; @@ -262,7 +272,10 @@ mod test { #[test] fn test_no_errors_when_string_too_long() { - let db = Box::new(SQLiteDatabase::new(std::path::Path::new(MEMORY_DB), None)); + let db = Box::new(SQLiteDatabase::new( + std::path::Path::new(MEMORY_DB), + Signals::empty(), + )); let create_stmt = "CREATE TABLE test_errors_when_string_too_long ( str_column VARCHAR(8) )"; @@ -287,7 +300,10 @@ mod test { #[test] fn test_no_errors_when_param_is_wrong_type() { - let db = Box::new(SQLiteDatabase::new(std::path::Path::new(MEMORY_DB), None)); + let db = Box::new(SQLiteDatabase::new( + std::path::Path::new(MEMORY_DB), + Signals::empty(), + )); let create_stmt = "CREATE TABLE test_errors_when_param_is_wrong_type ( int_column INT )"; @@ -312,7 +328,10 @@ mod test { #[test] fn test_errors_when_column_doesnt_exist() { - let db = Box::new(SQLiteDatabase::new(std::path::Path::new(MEMORY_DB), None)); + let db = Box::new(SQLiteDatabase::new( + std::path::Path::new(MEMORY_DB), + Signals::empty(), + )); let create_stmt = "CREATE TABLE test_errors_when_column_doesnt_exist ( int_column INT )"; @@ -337,7 +356,10 @@ mod test { #[test] fn test_errors_when_table_doesnt_exist() { - let db = Box::new(SQLiteDatabase::new(std::path::Path::new(MEMORY_DB), None)); + let db = Box::new(SQLiteDatabase::new( + std::path::Path::new(MEMORY_DB), + Signals::empty(), + )); let table_name = Some("test_errors_when_table_doesnt_exist".to_string()); let span = Span::unknown(); diff --git a/crates/nu-command/src/stor/open.rs b/crates/nu-command/src/stor/open.rs index c7f6f9f746..ba0f17c2af 100644 --- a/crates/nu-command/src/stor/open.rs +++ b/crates/nu-command/src/stor/open.rs @@ -1,5 +1,6 @@ use crate::database::{SQLiteDatabase, MEMORY_DB}; use nu_engine::command_prelude::*; +use nu_protocol::Signals; #[derive(Clone)] pub struct StorOpen; @@ -54,7 +55,10 @@ impl Command for StorOpen { // It returns the output of `select * from my_table_name` // Just create an empty database with MEMORY_DB and nothing else - let db = Box::new(SQLiteDatabase::new(std::path::Path::new(MEMORY_DB), None)); + let db = Box::new(SQLiteDatabase::new( + std::path::Path::new(MEMORY_DB), + Signals::empty(), + )); // dbg!(db.clone()); Ok(db.into_value(call.head).into_pipeline_data()) diff --git a/crates/nu-command/src/stor/reset.rs b/crates/nu-command/src/stor/reset.rs index d4489fb702..ba9e2a9681 100644 --- a/crates/nu-command/src/stor/reset.rs +++ b/crates/nu-command/src/stor/reset.rs @@ -1,5 +1,6 @@ use crate::database::{SQLiteDatabase, MEMORY_DB}; use nu_engine::command_prelude::*; +use nu_protocol::Signals; #[derive(Clone)] pub struct StorReset; @@ -42,7 +43,10 @@ impl Command for StorReset { let span = call.head; // Open the in-mem database - let db = Box::new(SQLiteDatabase::new(std::path::Path::new(MEMORY_DB), None)); + let db = Box::new(SQLiteDatabase::new( + std::path::Path::new(MEMORY_DB), + Signals::empty(), + )); if let Ok(conn) = db.open_connection() { db.drop_all_tables(&conn) diff --git a/crates/nu-command/src/stor/update.rs b/crates/nu-command/src/stor/update.rs index d731207a3f..18cf6f9f0f 100644 --- a/crates/nu-command/src/stor/update.rs +++ b/crates/nu-command/src/stor/update.rs @@ -1,5 +1,6 @@ use crate::database::{SQLiteDatabase, MEMORY_DB}; use nu_engine::command_prelude::*; +use nu_protocol::Signals; #[derive(Clone)] pub struct StorUpdate; @@ -79,7 +80,10 @@ impl Command for StorUpdate { call.get_flag(engine_state, stack, "where-clause")?; // Open the in-mem database - let db = Box::new(SQLiteDatabase::new(std::path::Path::new(MEMORY_DB), None)); + let db = Box::new(SQLiteDatabase::new( + std::path::Path::new(MEMORY_DB), + Signals::empty(), + )); // Check if the record is being passed as input or using the update record parameter let columns = handle(span, update_record, input)?; diff --git a/crates/nu-command/src/strings/char_.rs b/crates/nu-command/src/strings/char_.rs index 6834134b26..7737210254 100644 --- a/crates/nu-command/src/strings/char_.rs +++ b/crates/nu-command/src/strings/char_.rs @@ -1,8 +1,8 @@ use indexmap::{indexmap, IndexMap}; use nu_engine::command_prelude::*; +use nu_protocol::Signals; use once_cell::sync::Lazy; -use std::sync::{atomic::AtomicBool, Arc}; // Character used to separate directories in a Path Environment variable on windows is ";" #[cfg(target_family = "windows")] @@ -229,11 +229,13 @@ impl Command for Char { let list = call.has_flag_const(working_set, "list")?; let integer = call.has_flag_const(working_set, "integer")?; let unicode = call.has_flag_const(working_set, "unicode")?; - let ctrlc = working_set.permanent().ctrlc.clone(); // handle -l flag if list { - return Ok(generate_character_list(ctrlc, call.head)); + return Ok(generate_character_list( + working_set.permanent().signals().clone(), + call.head, + )); } // handle -i flag @@ -264,11 +266,13 @@ impl Command for Char { let list = call.has_flag(engine_state, stack, "list")?; let integer = call.has_flag(engine_state, stack, "integer")?; let unicode = call.has_flag(engine_state, stack, "unicode")?; - let ctrlc = engine_state.ctrlc.clone(); // handle -l flag if list { - return Ok(generate_character_list(ctrlc, call_span)); + return Ok(generate_character_list( + engine_state.signals().clone(), + call_span, + )); } // handle -i flag @@ -289,7 +293,7 @@ impl Command for Char { } } -fn generate_character_list(ctrlc: Option>, call_span: Span) -> PipelineData { +fn generate_character_list(signals: Signals, call_span: Span) -> PipelineData { CHAR_MAP .iter() .map(move |(name, s)| { @@ -308,7 +312,7 @@ fn generate_character_list(ctrlc: Option>, call_span: Span) -> P Value::record(record, call_span) }) - .into_pipeline_data(call_span, ctrlc) + .into_pipeline_data(call_span, signals) } fn handle_integer_flag( diff --git a/crates/nu-command/src/strings/detect_columns.rs b/crates/nu-command/src/strings/detect_columns.rs index 62a2d8bbcc..2f1f713b33 100644 --- a/crates/nu-command/src/strings/detect_columns.rs +++ b/crates/nu-command/src/strings/detect_columns.rs @@ -199,7 +199,7 @@ fn guess_width( Err(e) => Value::error(e, input_span), } }) - .into_pipeline_data(input_span, engine_state.ctrlc.clone())) + .into_pipeline_data(input_span, engine_state.signals().clone())) } else { let length = result[0].len(); let columns: Vec = (0..length).map(|n| format!("column{n}")).collect(); @@ -224,7 +224,7 @@ fn guess_width( Err(e) => Value::error(e, input_span), } }) - .into_pipeline_data(input_span, engine_state.ctrlc.clone())) + .into_pipeline_data(input_span, engine_state.signals().clone())) } } @@ -235,7 +235,6 @@ fn detect_columns( args: Arguments, ) -> Result { let name_span = call.head; - let ctrlc = engine_state.ctrlc.clone(); let config = engine_state.get_config(); let input = input.collect_string("", config)?; @@ -316,7 +315,7 @@ fn detect_columns( None => Value::record(record, name_span), } }) - .into_pipeline_data(call.head, ctrlc)) + .into_pipeline_data(call.head, engine_state.signals().clone())) } else { Ok(PipelineData::empty()) } diff --git a/crates/nu-command/src/strings/encode_decode/base64.rs b/crates/nu-command/src/strings/encode_decode/base64.rs index 8050193212..afc143983e 100644 --- a/crates/nu-command/src/strings/encode_decode/base64.rs +++ b/crates/nu-command/src/strings/encode_decode/base64.rs @@ -75,7 +75,7 @@ pub fn operate( cell_paths, }; - general_operate(action, args, input, call.head, engine_state.ctrlc.clone()) + general_operate(action, args, input, call.head, engine_state.signals()) } fn action( diff --git a/crates/nu-command/src/strings/format/date.rs b/crates/nu-command/src/strings/format/date.rs index 1aaf1fb851..dd005b9216 100644 --- a/crates/nu-command/src/strings/format/date.rs +++ b/crates/nu-command/src/strings/format/date.rs @@ -127,7 +127,7 @@ fn run( Some(format) => format_helper(value, format.item.as_str(), format.span, head), None => format_helper_rfc2822(value, head), }, - engine_state.ctrlc.clone(), + engine_state.signals(), ) } diff --git a/crates/nu-command/src/strings/format/duration.rs b/crates/nu-command/src/strings/format/duration.rs index 281542f49a..fbbe192048 100644 --- a/crates/nu-command/src/strings/format/duration.rs +++ b/crates/nu-command/src/strings/format/duration.rs @@ -81,7 +81,7 @@ impl Command for FormatDuration { arg, input, call.head, - engine_state.ctrlc.clone(), + engine_state.signals(), ) } @@ -108,7 +108,7 @@ impl Command for FormatDuration { arg, input, call.head, - working_set.permanent().ctrlc.clone(), + working_set.permanent().signals(), ) } diff --git a/crates/nu-command/src/strings/format/filesize.rs b/crates/nu-command/src/strings/format/filesize.rs index ebd43d90b1..63865ac2ac 100644 --- a/crates/nu-command/src/strings/format/filesize.rs +++ b/crates/nu-command/src/strings/format/filesize.rs @@ -76,7 +76,7 @@ impl Command for FormatFilesize { arg, input, call.head, - engine_state.ctrlc.clone(), + engine_state.signals(), ) } @@ -101,7 +101,7 @@ impl Command for FormatFilesize { arg, input, call.head, - working_set.permanent().ctrlc.clone(), + working_set.permanent().signals(), ) } diff --git a/crates/nu-command/src/strings/parse.rs b/crates/nu-command/src/strings/parse.rs index 4318b3da8b..9bf31de73c 100644 --- a/crates/nu-command/src/strings/parse.rs +++ b/crates/nu-command/src/strings/parse.rs @@ -1,10 +1,7 @@ use fancy_regex::{Captures, Regex}; use nu_engine::command_prelude::*; -use nu_protocol::{engine::StateWorkingSet, ListStream}; -use std::{ - collections::VecDeque, - sync::{atomic::AtomicBool, Arc}, -}; +use nu_protocol::{engine::StateWorkingSet, ListStream, Signals}; +use std::collections::VecDeque; #[derive(Clone)] pub struct Parse; @@ -163,8 +160,6 @@ fn operate( }) .collect::>(); - let ctrlc = engine_state.ctrlc.clone(); - match input { PipelineData::Empty => Ok(PipelineData::Empty), PipelineData::Value(value, ..) => match value { @@ -192,10 +187,10 @@ fn operate( columns, iter, span: head, - ctrlc, + signals: engine_state.signals().clone(), }; - Ok(ListStream::new(iter, head, None).into()) + Ok(ListStream::new(iter, head, Signals::empty()).into()) } value => Err(ShellError::PipelineMismatch { exp_input_type: "string".into(), @@ -220,7 +215,7 @@ fn operate( columns, iter, span: head, - ctrlc, + signals: engine_state.signals().clone(), } }) .into()), @@ -232,10 +227,10 @@ fn operate( columns, iter: lines, span: head, - ctrlc, + signals: engine_state.signals().clone(), }; - Ok(ListStream::new(iter, head, None).into()) + Ok(ListStream::new(iter, head, Signals::empty()).into()) } else { Ok(PipelineData::Empty) } @@ -302,7 +297,7 @@ struct ParseIter>> { columns: Vec, iter: I, span: Span, - ctrlc: Option>, + signals: Signals, } impl>> ParseIter { @@ -320,7 +315,7 @@ impl>> Iterator for ParseIter { fn next(&mut self) -> Option { loop { - if nu_utils::ctrl_c::was_pressed(&self.ctrlc) { + if self.signals.interrupted() { return None; } diff --git a/crates/nu-command/src/strings/split/chars.rs b/crates/nu-command/src/strings/split/chars.rs index 08e73b9830..370df262ea 100644 --- a/crates/nu-command/src/strings/split/chars.rs +++ b/crates/nu-command/src/strings/split/chars.rs @@ -124,7 +124,7 @@ fn split_chars( let span = call.head; input.map( move |x| split_chars_helper(&x, span, graphemes), - engine_state.ctrlc.clone(), + engine_state.signals(), ) } diff --git a/crates/nu-command/src/strings/split/column.rs b/crates/nu-command/src/strings/split/column.rs index 02eff7845f..540cfabe54 100644 --- a/crates/nu-command/src/strings/split/column.rs +++ b/crates/nu-command/src/strings/split/column.rs @@ -170,7 +170,7 @@ fn split_column( input.flat_map( move |x| split_column_helper(&x, ®ex, &args.rest, args.collapse_empty, name_span), - engine_state.ctrlc.clone(), + engine_state.signals(), ) } diff --git a/crates/nu-command/src/strings/split/list.rs b/crates/nu-command/src/strings/split/list.rs index d52bb5401a..eb874841a2 100644 --- a/crates/nu-command/src/strings/split/list.rs +++ b/crates/nu-command/src/strings/split/list.rs @@ -215,9 +215,7 @@ fn split_list( let matcher = Matcher::new(has_regex, separator)?; for val in input { - if nu_utils::ctrl_c::was_pressed(&engine_state.ctrlc) { - break; - } + engine_state.signals().check(call.head)?; if matcher.compare(&val)? { if !temp_list.is_empty() { diff --git a/crates/nu-command/src/strings/split/row.rs b/crates/nu-command/src/strings/split/row.rs index 8bc0003cb6..1f427a06e0 100644 --- a/crates/nu-command/src/strings/split/row.rs +++ b/crates/nu-command/src/strings/split/row.rs @@ -170,7 +170,7 @@ fn split_row( })?; input.flat_map( move |x| split_row_helper(&x, ®ex, args.max_split, name_span), - engine_state.ctrlc.clone(), + engine_state.signals(), ) } diff --git a/crates/nu-command/src/strings/split/words.rs b/crates/nu-command/src/strings/split/words.rs index 0dd5dc9383..6cb5562a70 100644 --- a/crates/nu-command/src/strings/split/words.rs +++ b/crates/nu-command/src/strings/split/words.rs @@ -177,7 +177,7 @@ fn split_words( input.map( move |x| split_words_helper(&x, args.word_length, span, args.graphemes), - engine_state.ctrlc.clone(), + engine_state.signals(), ) } diff --git a/crates/nu-command/src/strings/str_/case/capitalize.rs b/crates/nu-command/src/strings/str_/case/capitalize.rs index 20f334976c..862ca127c2 100644 --- a/crates/nu-command/src/strings/str_/case/capitalize.rs +++ b/crates/nu-command/src/strings/str_/case/capitalize.rs @@ -108,7 +108,7 @@ fn operate( ret } }, - engine_state.ctrlc.clone(), + engine_state.signals(), ) } diff --git a/crates/nu-command/src/strings/str_/case/downcase.rs b/crates/nu-command/src/strings/str_/case/downcase.rs index 316050d501..0493a663aa 100644 --- a/crates/nu-command/src/strings/str_/case/downcase.rs +++ b/crates/nu-command/src/strings/str_/case/downcase.rs @@ -116,7 +116,7 @@ fn operate( ret } }, - engine_state.ctrlc.clone(), + engine_state.signals(), ) } diff --git a/crates/nu-command/src/strings/str_/case/mod.rs b/crates/nu-command/src/strings/str_/case/mod.rs index 32b50c4bc6..3390ff097e 100644 --- a/crates/nu-command/src/strings/str_/case/mod.rs +++ b/crates/nu-command/src/strings/str_/case/mod.rs @@ -38,7 +38,7 @@ where case_operation, cell_paths, }; - general_operate(action, args, input, call.head, engine_state.ctrlc.clone()) + general_operate(action, args, input, call.head, engine_state.signals()) } fn action(input: &Value, args: &Arguments, head: Span) -> Value diff --git a/crates/nu-command/src/strings/str_/case/upcase.rs b/crates/nu-command/src/strings/str_/case/upcase.rs index 9e55f25f64..557239ec91 100644 --- a/crates/nu-command/src/strings/str_/case/upcase.rs +++ b/crates/nu-command/src/strings/str_/case/upcase.rs @@ -93,7 +93,7 @@ fn operate( ret } }, - engine_state.ctrlc.clone(), + engine_state.signals(), ) } diff --git a/crates/nu-command/src/strings/str_/contains.rs b/crates/nu-command/src/strings/str_/contains.rs index ff9cb85fcb..27d8a8e313 100644 --- a/crates/nu-command/src/strings/str_/contains.rs +++ b/crates/nu-command/src/strings/str_/contains.rs @@ -68,7 +68,7 @@ impl Command for SubCommand { cell_paths, case_insensitive: call.has_flag(engine_state, stack, "ignore-case")?, }; - operate(action, args, input, call.head, engine_state.ctrlc.clone()) + operate(action, args, input, call.head, engine_state.signals()) } fn run_const( @@ -103,7 +103,7 @@ impl Command for SubCommand { args, input, call.head, - working_set.permanent().ctrlc.clone(), + working_set.permanent().signals(), ) } diff --git a/crates/nu-command/src/strings/str_/deunicode.rs b/crates/nu-command/src/strings/str_/deunicode.rs index 0b70cf2003..ef732571f9 100644 --- a/crates/nu-command/src/strings/str_/deunicode.rs +++ b/crates/nu-command/src/strings/str_/deunicode.rs @@ -39,7 +39,7 @@ impl Command for SubCommand { let cell_paths: Vec = call.rest(engine_state, stack, 0)?; let args = CellPathOnlyArgs::from(cell_paths); - operate(action, args, input, call.head, engine_state.ctrlc.clone()) + operate(action, args, input, call.head, engine_state.signals()) } fn run_const( @@ -56,7 +56,7 @@ impl Command for SubCommand { args, input, call.head, - working_set.permanent().ctrlc.clone(), + working_set.permanent().signals(), ) } diff --git a/crates/nu-command/src/strings/str_/distance.rs b/crates/nu-command/src/strings/str_/distance.rs index bd666e53f5..9b88f8e3e0 100644 --- a/crates/nu-command/src/strings/str_/distance.rs +++ b/crates/nu-command/src/strings/str_/distance.rs @@ -67,7 +67,7 @@ impl Command for SubCommand { compare_string, cell_paths, }; - operate(action, args, input, call.head, engine_state.ctrlc.clone()) + operate(action, args, input, call.head, engine_state.signals()) } fn run_const( @@ -88,7 +88,7 @@ impl Command for SubCommand { args, input, call.head, - working_set.permanent().ctrlc.clone(), + working_set.permanent().signals(), ) } diff --git a/crates/nu-command/src/strings/str_/ends_with.rs b/crates/nu-command/src/strings/str_/ends_with.rs index 40f643ea8e..d4841b74e0 100644 --- a/crates/nu-command/src/strings/str_/ends_with.rs +++ b/crates/nu-command/src/strings/str_/ends_with.rs @@ -68,7 +68,7 @@ impl Command for SubCommand { cell_paths, case_insensitive: call.has_flag(engine_state, stack, "ignore-case")?, }; - operate(action, args, input, call.head, engine_state.ctrlc.clone()) + operate(action, args, input, call.head, engine_state.signals()) } fn run_const( @@ -89,7 +89,7 @@ impl Command for SubCommand { args, input, call.head, - working_set.permanent().ctrlc.clone(), + working_set.permanent().signals(), ) } diff --git a/crates/nu-command/src/strings/str_/expand.rs b/crates/nu-command/src/strings/str_/expand.rs index 70fb51ec4c..b9759ef6a1 100644 --- a/crates/nu-command/src/strings/str_/expand.rs +++ b/crates/nu-command/src/strings/str_/expand.rs @@ -233,7 +233,7 @@ fn run( ), } }, - engine_state.ctrlc.clone(), + engine_state.signals(), ) } diff --git a/crates/nu-command/src/strings/str_/index_of.rs b/crates/nu-command/src/strings/str_/index_of.rs index 5b40f80d3d..a6b06ef9b7 100644 --- a/crates/nu-command/src/strings/str_/index_of.rs +++ b/crates/nu-command/src/strings/str_/index_of.rs @@ -93,7 +93,7 @@ impl Command for SubCommand { cell_paths, graphemes: grapheme_flags(engine_state, stack, call)?, }; - operate(action, args, input, call.head, engine_state.ctrlc.clone()) + operate(action, args, input, call.head, engine_state.signals()) } fn run_const( @@ -117,7 +117,7 @@ impl Command for SubCommand { args, input, call.head, - working_set.permanent().ctrlc.clone(), + working_set.permanent().signals(), ) } diff --git a/crates/nu-command/src/strings/str_/join.rs b/crates/nu-command/src/strings/str_/join.rs index 1d36f216d0..d297ea4cbc 100644 --- a/crates/nu-command/src/strings/str_/join.rs +++ b/crates/nu-command/src/strings/str_/join.rs @@ -1,4 +1,5 @@ use nu_engine::command_prelude::*; +use nu_protocol::Signals; use std::io::Write; @@ -88,30 +89,35 @@ fn run( let mut iter = input.into_iter(); let mut first = true; - let output = ByteStream::from_fn(span, None, ByteStreamType::String, move |buffer| { - // Write each input to the buffer - if let Some(value) = iter.next() { - // Write the separator if this is not the first - if first { - first = false; - } else if let Some(separator) = &separator { - write!(buffer, "{}", separator)?; - } - - match value { - Value::Error { error, .. } => { - return Err(*error); + let output = ByteStream::from_fn( + span, + Signals::empty(), + ByteStreamType::String, + move |buffer| { + // Write each input to the buffer + if let Some(value) = iter.next() { + // Write the separator if this is not the first + if first { + first = false; + } else if let Some(separator) = &separator { + write!(buffer, "{}", separator)?; } - // Hmm, not sure what we actually want. - // `to_expanded_string` formats dates as human readable which feels funny. - Value::Date { val, .. } => write!(buffer, "{val:?}")?, - value => write!(buffer, "{}", value.to_expanded_string("\n", &config))?, + + match value { + Value::Error { error, .. } => { + return Err(*error); + } + // Hmm, not sure what we actually want. + // `to_expanded_string` formats dates as human readable which feels funny. + Value::Date { val, .. } => write!(buffer, "{val:?}")?, + value => write!(buffer, "{}", value.to_expanded_string("\n", &config))?, + } + Ok(true) + } else { + Ok(false) } - Ok(true) - } else { - Ok(false) - } - }); + }, + ); Ok(PipelineData::ByteStream(output, metadata)) } diff --git a/crates/nu-command/src/strings/str_/length.rs b/crates/nu-command/src/strings/str_/length.rs index f456fe9467..ab0ba8db49 100644 --- a/crates/nu-command/src/strings/str_/length.rs +++ b/crates/nu-command/src/strings/str_/length.rs @@ -130,7 +130,7 @@ fn run( cell_paths: (!cell_paths.is_empty()).then_some(cell_paths), graphemes, }; - operate(action, args, input, call.head, engine_state.ctrlc.clone()) + operate(action, args, input, call.head, engine_state.signals()) } fn action(input: &Value, arg: &Arguments, head: Span) -> Value { diff --git a/crates/nu-command/src/strings/str_/replace.rs b/crates/nu-command/src/strings/str_/replace.rs index 58e2574681..342dd0693e 100644 --- a/crates/nu-command/src/strings/str_/replace.rs +++ b/crates/nu-command/src/strings/str_/replace.rs @@ -102,7 +102,7 @@ impl Command for SubCommand { no_regex, multiline, }; - operate(action, args, input, call.head, engine_state.ctrlc.clone()) + operate(action, args, input, call.head, engine_state.signals()) } fn run_const( @@ -134,7 +134,7 @@ impl Command for SubCommand { args, input, call.head, - working_set.permanent().ctrlc.clone(), + working_set.permanent().signals(), ) } diff --git a/crates/nu-command/src/strings/str_/reverse.rs b/crates/nu-command/src/strings/str_/reverse.rs index cc3772db7c..9a339f7bcc 100644 --- a/crates/nu-command/src/strings/str_/reverse.rs +++ b/crates/nu-command/src/strings/str_/reverse.rs @@ -50,7 +50,7 @@ impl Command for SubCommand { ) -> Result { let cell_paths: Vec = call.rest(engine_state, stack, 0)?; let args = CellPathOnlyArgs::from(cell_paths); - operate(action, args, input, call.head, engine_state.ctrlc.clone()) + operate(action, args, input, call.head, engine_state.signals()) } fn run_const( @@ -66,7 +66,7 @@ impl Command for SubCommand { args, input, call.head, - working_set.permanent().ctrlc.clone(), + working_set.permanent().signals(), ) } diff --git a/crates/nu-command/src/strings/str_/starts_with.rs b/crates/nu-command/src/strings/str_/starts_with.rs index aec12f6f77..bac451466c 100644 --- a/crates/nu-command/src/strings/str_/starts_with.rs +++ b/crates/nu-command/src/strings/str_/starts_with.rs @@ -70,7 +70,7 @@ impl Command for SubCommand { cell_paths, case_insensitive: call.has_flag(engine_state, stack, "ignore-case")?, }; - operate(action, args, input, call.head, engine_state.ctrlc.clone()) + operate(action, args, input, call.head, engine_state.signals()) } fn run_const( @@ -92,7 +92,7 @@ impl Command for SubCommand { args, input, call.head, - working_set.permanent().ctrlc.clone(), + working_set.permanent().signals(), ) } diff --git a/crates/nu-command/src/strings/str_/stats.rs b/crates/nu-command/src/strings/str_/stats.rs index 28c65afe2b..eb091ae4f6 100644 --- a/crates/nu-command/src/strings/str_/stats.rs +++ b/crates/nu-command/src/strings/str_/stats.rs @@ -122,7 +122,7 @@ fn stats( ), } }, - engine_state.ctrlc.clone(), + engine_state.signals(), ) } diff --git a/crates/nu-command/src/strings/str_/substring.rs b/crates/nu-command/src/strings/str_/substring.rs index 5ad7a967da..10464580c7 100644 --- a/crates/nu-command/src/strings/str_/substring.rs +++ b/crates/nu-command/src/strings/str_/substring.rs @@ -103,7 +103,7 @@ impl Command for SubCommand { cell_paths, graphemes: grapheme_flags(engine_state, stack, call)?, }; - operate(action, args, input, call.head, engine_state.ctrlc.clone()) + operate(action, args, input, call.head, engine_state.signals()) } fn run_const( @@ -133,7 +133,7 @@ impl Command for SubCommand { args, input, call.head, - working_set.permanent().ctrlc.clone(), + working_set.permanent().signals(), ) } diff --git a/crates/nu-command/src/strings/str_/trim/trim_.rs b/crates/nu-command/src/strings/str_/trim/trim_.rs index 52c4017cda..76d886a010 100644 --- a/crates/nu-command/src/strings/str_/trim/trim_.rs +++ b/crates/nu-command/src/strings/str_/trim/trim_.rs @@ -190,7 +190,7 @@ fn run( cell_paths, mode, }; - operate(action, args, input, call.head, engine_state.ctrlc.clone()) + operate(action, args, input, call.head, engine_state.signals()) } #[derive(Debug, Copy, Clone)] diff --git a/crates/nu-command/src/system/ps.rs b/crates/nu-command/src/system/ps.rs index c64549a44d..859575abbd 100644 --- a/crates/nu-command/src/system/ps.rs +++ b/crates/nu-command/src/system/ps.rs @@ -182,5 +182,5 @@ fn run_ps( Ok(output .into_iter() - .into_pipeline_data(span, engine_state.ctrlc.clone())) + .into_pipeline_data(span, engine_state.signals().clone())) } diff --git a/crates/nu-command/src/system/registry_query.rs b/crates/nu-command/src/system/registry_query.rs index 1e2a328356..9b68059ffa 100644 --- a/crates/nu-command/src/system/registry_query.rs +++ b/crates/nu-command/src/system/registry_query.rs @@ -106,7 +106,7 @@ fn registry_query( *registry_key_span, )) } - Ok(reg_values.into_pipeline_data(call_span, engine_state.ctrlc.clone())) + Ok(reg_values.into_pipeline_data(call_span, engine_state.signals().clone())) } else { match registry_value { Some(value) => { diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs index f82c23a0e0..e475833747 100644 --- a/crates/nu-command/src/system/run_external.rs +++ b/crates/nu-command/src/system/run_external.rs @@ -2,7 +2,7 @@ use nu_cmd_base::hook::eval_hook; use nu_engine::{command_prelude::*, env_to_strings, get_eval_expression}; use nu_path::{dots::expand_ndots, expand_tilde}; use nu_protocol::{ - ast::Expression, did_you_mean, process::ChildProcess, ByteStream, NuGlob, OutDest, + ast::Expression, did_you_mean, process::ChildProcess, ByteStream, NuGlob, OutDest, Signals, }; use nu_system::ForegroundChild; use nu_utils::IgnoreCaseExt; @@ -13,7 +13,7 @@ use std::{ io::Write, path::{Path, PathBuf}, process::Stdio, - sync::{atomic::AtomicBool, Arc}, + sync::Arc, thread, }; @@ -221,7 +221,6 @@ pub fn eval_arguments_from_call( stack: &mut Stack, call: &Call, ) -> Result>, ShellError> { - let ctrlc = &engine_state.ctrlc; let cwd = engine_state.cwd(Some(stack))?; let mut args: Vec> = vec![]; for (expr, spread) in call.rest_iter(1) { @@ -229,7 +228,7 @@ pub fn eval_arguments_from_call( match arg { // Expand globs passed to run-external Value::Glob { val, no_expand, .. } if !no_expand => args.extend( - expand_glob(&val, &cwd, expr.span, ctrlc)? + expand_glob(&val, &cwd, expr.span, engine_state.signals())? .into_iter() .map(|s| s.into_spanned(expr.span)), ), @@ -289,7 +288,7 @@ fn expand_glob( arg: &str, cwd: &Path, span: Span, - interrupt: &Option>, + signals: &Signals, ) -> Result, ShellError> { const GLOB_CHARS: &[char] = &['*', '?', '[']; @@ -307,9 +306,7 @@ fn expand_glob( let mut result: Vec = vec![]; for m in matches { - if nu_utils::ctrl_c::was_pressed(interrupt) { - return Err(ShellError::InterruptedByUser { span: Some(span) }); - } + signals.check(span)?; if let Ok(arg) = m { let arg = resolve_globbed_path_to_cwd_relative(arg, prefix.as_ref(), cwd); result.push(arg.into()); @@ -611,30 +608,30 @@ mod test { let cwd = dirs.test(); - let actual = expand_glob("*.txt", cwd, Span::unknown(), &None).unwrap(); + let actual = expand_glob("*.txt", cwd, Span::unknown(), &Signals::empty()).unwrap(); let expected = &["a.txt", "b.txt"]; assert_eq!(actual, expected); - let actual = expand_glob("./*.txt", cwd, Span::unknown(), &None).unwrap(); + let actual = expand_glob("./*.txt", cwd, Span::unknown(), &Signals::empty()).unwrap(); assert_eq!(actual, expected); - let actual = expand_glob("'*.txt'", cwd, Span::unknown(), &None).unwrap(); + let actual = expand_glob("'*.txt'", cwd, Span::unknown(), &Signals::empty()).unwrap(); let expected = &["'*.txt'"]; assert_eq!(actual, expected); - let actual = expand_glob(".", cwd, Span::unknown(), &None).unwrap(); + let actual = expand_glob(".", cwd, Span::unknown(), &Signals::empty()).unwrap(); let expected = &["."]; assert_eq!(actual, expected); - let actual = expand_glob("./a.txt", cwd, Span::unknown(), &None).unwrap(); + let actual = expand_glob("./a.txt", cwd, Span::unknown(), &Signals::empty()).unwrap(); let expected = &["./a.txt"]; assert_eq!(actual, expected); - let actual = expand_glob("[*.txt", cwd, Span::unknown(), &None).unwrap(); + let actual = expand_glob("[*.txt", cwd, Span::unknown(), &Signals::empty()).unwrap(); let expected = &["[*.txt"]; assert_eq!(actual, expected); - let actual = expand_glob("~/foo.txt", cwd, Span::unknown(), &None).unwrap(); + let actual = expand_glob("~/foo.txt", cwd, Span::unknown(), &Signals::empty()).unwrap(); let home = dirs_next::home_dir().expect("failed to get home dir"); let expected: Vec = vec![home.join("foo.txt").into()]; assert_eq!(actual, expected); @@ -666,7 +663,7 @@ mod test { ByteStream::read( b"foo".as_slice(), Span::unknown(), - None, + Signals::empty(), ByteStreamType::Unknown, ), None, diff --git a/crates/nu-command/src/system/which_.rs b/crates/nu-command/src/system/which_.rs index f0cf4ece39..fd5b0beb18 100644 --- a/crates/nu-command/src/system/which_.rs +++ b/crates/nu-command/src/system/which_.rs @@ -188,7 +188,6 @@ fn which( applications: call.rest(engine_state, stack, 0)?, all: call.has_flag(engine_state, stack, "all")?, }; - let ctrlc = engine_state.ctrlc.clone(); if which_args.applications.is_empty() { return Err(ShellError::MissingParameter { @@ -214,7 +213,9 @@ fn which( output.extend(values); } - Ok(output.into_iter().into_pipeline_data(head, ctrlc)) + Ok(output + .into_iter() + .into_pipeline_data(head, engine_state.signals().clone())) } #[cfg(test)] diff --git a/crates/nu-command/src/viewers/table.rs b/crates/nu-command/src/viewers/table.rs index f0cc90fa9f..e3738a3952 100644 --- a/crates/nu-command/src/viewers/table.rs +++ b/crates/nu-command/src/viewers/table.rs @@ -7,7 +7,7 @@ use nu_color_config::{color_from_hex, StyleComputer, TextStyle}; use nu_engine::{command_prelude::*, env::get_config, env_to_string}; use nu_pretty_hex::HexConfig; use nu_protocol::{ - ByteStream, Config, DataSource, ListStream, PipelineMetadata, TableMode, ValueIterator, + ByteStream, Config, DataSource, ListStream, PipelineMetadata, Signals, TableMode, ValueIterator, }; use nu_table::{ common::create_nu_table_config, CollapsedTable, ExpandedTable, JustTable, NuTable, NuTableCell, @@ -19,7 +19,6 @@ use std::{ io::{IsTerminal, Read}, path::PathBuf, str::FromStr, - sync::{atomic::AtomicBool, Arc}, time::Instant, }; use terminal_size::{Height, Width}; @@ -377,8 +376,8 @@ fn handle_table_command( ), PipelineData::ByteStream(..) => Ok(input.data), PipelineData::Value(Value::Binary { val, .. }, ..) => { - let ctrlc = input.engine_state.ctrlc.clone(); - let stream = ByteStream::read_binary(val, input.call.head, ctrlc); + let signals = input.engine_state.signals().clone(); + let stream = ByteStream::read_binary(val, input.call.head, signals); Ok(PipelineData::ByteStream( pretty_hex_stream(stream, input.call.head), None, @@ -386,8 +385,8 @@ fn handle_table_command( } // None of these two receive a StyleComputer because handle_row_stream() can produce it by itself using engine_state and stack. PipelineData::Value(Value::List { vals, .. }, metadata) => { - let ctrlc = input.engine_state.ctrlc.clone(); - let stream = ListStream::new(vals.into_iter(), span, ctrlc); + let signals = input.engine_state.signals().clone(); + let stream = ListStream::new(vals.into_iter(), span, signals); input.data = PipelineData::Empty; handle_row_stream(input, cfg, stream, metadata) @@ -410,8 +409,9 @@ fn handle_table_command( Table.run(input.engine_state, input.stack, input.call, base_pipeline) } PipelineData::Value(Value::Range { val, .. }, metadata) => { - let ctrlc = input.engine_state.ctrlc.clone(); - let stream = ListStream::new(val.into_range_iter(span, ctrlc), span, None); + let signals = input.engine_state.signals().clone(); + let stream = + ListStream::new(val.into_range_iter(span, Signals::empty()), span, signals); input.data = PipelineData::Empty; handle_row_stream(input, cfg, stream, metadata) } @@ -437,50 +437,55 @@ fn pretty_hex_stream(stream: ByteStream, span: Span) -> ByteStream { reader } else { // No stream to read from - return ByteStream::read_string("".into(), span, None); + return ByteStream::read_string("".into(), span, Signals::empty()); }; - ByteStream::from_fn(span, None, ByteStreamType::String, move |buffer| { - // Turn the buffer into a String we can write to - let mut write_buf = std::mem::take(buffer); - write_buf.clear(); - // SAFETY: we just truncated it empty - let mut write_buf = unsafe { String::from_utf8_unchecked(write_buf) }; + ByteStream::from_fn( + span, + Signals::empty(), + ByteStreamType::String, + move |buffer| { + // Turn the buffer into a String we can write to + let mut write_buf = std::mem::take(buffer); + write_buf.clear(); + // SAFETY: we just truncated it empty + let mut write_buf = unsafe { String::from_utf8_unchecked(write_buf) }; - // Write the title at the beginning - if cfg.title { - nu_pretty_hex::write_title(&mut write_buf, cfg, true).expect("format error"); - cfg.title = false; - - // Put the write_buf back into buffer - *buffer = write_buf.into_bytes(); - - Ok(true) - } else { - // Read up to `cfg.width` bytes - read_buf.clear(); - (&mut reader) - .take(cfg.width as u64) - .read_to_end(&mut read_buf) - .err_span(span)?; - - if !read_buf.is_empty() { - nu_pretty_hex::hex_write(&mut write_buf, &read_buf, cfg, Some(true)) - .expect("format error"); - write_buf.push('\n'); - - // Advance the address offset for next time - cfg.address_offset += read_buf.len(); + // Write the title at the beginning + if cfg.title { + nu_pretty_hex::write_title(&mut write_buf, cfg, true).expect("format error"); + cfg.title = false; // Put the write_buf back into buffer *buffer = write_buf.into_bytes(); Ok(true) } else { - Ok(false) + // Read up to `cfg.width` bytes + read_buf.clear(); + (&mut reader) + .take(cfg.width as u64) + .read_to_end(&mut read_buf) + .err_span(span)?; + + if !read_buf.is_empty() { + nu_pretty_hex::hex_write(&mut write_buf, &read_buf, cfg, Some(true)) + .expect("format error"); + write_buf.push('\n'); + + // Advance the address offset for next time + cfg.address_offset += read_buf.len(); + + // Put the write_buf back into buffer + *buffer = write_buf.into_bytes(); + + Ok(true) + } else { + Ok(false) + } } - } - }) + }, + ) } fn handle_record( @@ -491,8 +496,6 @@ fn handle_record( let config = get_config(input.engine_state, input.stack); let span = input.data.span().unwrap_or(input.call.head); let styles = &StyleComputer::from_config(input.engine_state, input.stack); - let ctrlc = input.engine_state.ctrlc.clone(); - let ctrlc1 = ctrlc.clone(); if record.is_empty() { let value = @@ -517,7 +520,7 @@ fn handle_record( let opts = TableOpts::new( &config, styles, - ctrlc, + input.engine_state.signals(), span, cfg.term_width, indent, @@ -529,7 +532,7 @@ fn handle_record( let result = match result { Some(output) => maybe_strip_color(output, &config), - None => report_unsuccessful_output(ctrlc1, cfg.term_width), + None => report_unsuccessful_output(input.engine_state.signals(), cfg.term_width), }; let val = Value::string(result, span); @@ -537,8 +540,8 @@ fn handle_record( Ok(val.into_pipeline_data()) } -fn report_unsuccessful_output(ctrlc1: Option>, term_width: usize) -> String { - if nu_utils::ctrl_c::was_pressed(&ctrlc1) { +fn report_unsuccessful_output(signals: &Signals, term_width: usize) -> String { + if signals.interrupted() { "".into() } else { // assume this failed because the table was too wide @@ -599,8 +602,6 @@ fn handle_row_stream( stream: ListStream, metadata: Option, ) -> Result { - let ctrlc = input.engine_state.ctrlc.clone(); - let stream = match metadata.as_ref() { // First, `ls` sources: Some(PipelineMetadata { @@ -680,11 +681,14 @@ fn handle_row_stream( // for the values it outputs. Because engine_state is passed in, config doesn't need to. input.engine_state.clone(), input.stack.clone(), - ctrlc.clone(), cfg, ); - let stream = - ByteStream::from_result_iter(paginator, input.call.head, None, ByteStreamType::String); + let stream = ByteStream::from_result_iter( + paginator, + input.call.head, + Signals::empty(), + ByteStreamType::String, + ); Ok(PipelineData::ByteStream(stream, None)) } @@ -717,7 +721,6 @@ struct PagingTableCreator { stream: ValueIterator, engine_state: EngineState, stack: Stack, - ctrlc: Option>, elements_displayed: usize, reached_end: bool, cfg: TableConfig, @@ -730,7 +733,6 @@ impl PagingTableCreator { stream: ListStream, engine_state: EngineState, stack: Stack, - ctrlc: Option>, cfg: TableConfig, ) -> Self { PagingTableCreator { @@ -738,7 +740,6 @@ impl PagingTableCreator { stream: stream.into_inner(), engine_state, stack, - ctrlc, cfg, elements_displayed: 0, reached_end: false, @@ -790,14 +791,14 @@ impl PagingTableCreator { } fn create_table_opts<'a>( - &self, + &'a self, cfg: &'a Config, style_comp: &'a StyleComputer<'a>, ) -> TableOpts<'a> { TableOpts::new( cfg, style_comp, - self.ctrlc.clone(), + self.engine_state.signals(), self.head, self.cfg.term_width, (cfg.table_indent.left, cfg.table_indent.right), @@ -830,12 +831,15 @@ impl Iterator for PagingTableCreator { match self.cfg.abbreviation { Some(abbr) => { (batch, _, end) = - stream_collect_abbriviated(&mut self.stream, abbr, self.ctrlc.clone()); + stream_collect_abbriviated(&mut self.stream, abbr, self.engine_state.signals()); } None => { // Pull from stream until time runs out or we have enough items - (batch, end) = - stream_collect(&mut self.stream, STREAM_PAGE_SIZE, self.ctrlc.clone()); + (batch, end) = stream_collect( + &mut self.stream, + STREAM_PAGE_SIZE, + self.engine_state.signals(), + ); } } @@ -869,14 +873,19 @@ impl Iterator for PagingTableCreator { self.row_offset += batch_size; let config = get_config(&self.engine_state, &self.stack); - convert_table_to_output(table, &config, &self.ctrlc, self.cfg.term_width) + convert_table_to_output( + table, + &config, + self.engine_state.signals(), + self.cfg.term_width, + ) } } fn stream_collect( stream: impl Iterator, size: usize, - ctrlc: Option>, + signals: &Signals, ) -> (Vec, bool) { let start_time = Instant::now(); let mut end = true; @@ -896,7 +905,7 @@ fn stream_collect( break; } - if nu_utils::ctrl_c::was_pressed(&ctrlc) { + if signals.interrupted() { break; } } @@ -907,7 +916,7 @@ fn stream_collect( fn stream_collect_abbriviated( stream: impl Iterator, size: usize, - ctrlc: Option>, + signals: &Signals, ) -> (Vec, usize, bool) { let mut end = true; let mut read = 0; @@ -930,7 +939,7 @@ fn stream_collect_abbriviated( tail.push_back(item); } - if nu_utils::ctrl_c::was_pressed(&ctrlc) { + if signals.interrupted() { end = false; break; } @@ -1062,7 +1071,7 @@ fn create_empty_placeholder( fn convert_table_to_output( table: Result, ShellError>, config: &Config, - ctrlc: &Option>, + signals: &Signals, term_width: usize, ) -> Option, ShellError>> { match table { @@ -1075,7 +1084,7 @@ fn convert_table_to_output( Some(Ok(bytes)) } Ok(None) => { - let msg = if nu_utils::ctrl_c::was_pressed(ctrlc) { + let msg = if signals.interrupted() { String::from("") } else { // assume this failed because the table was too wide diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index 044bf86980..1460dec464 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -21,9 +21,7 @@ pub fn eval_call( call: &Call, input: PipelineData, ) -> Result { - if nu_utils::ctrl_c::was_pressed(&engine_state.ctrlc) { - return Ok(Value::nothing(call.head).into_pipeline_data()); - } + engine_state.signals().check(call.head)?; let decl = engine_state.get_decl(call.decl_id); if !decl.is_known_external() && call.named_iter().any(|(flag, _, _)| flag.item == "help") { diff --git a/crates/nu-explore/src/commands/expand.rs b/crates/nu-explore/src/commands/expand.rs index cbb86336af..b748ce2b89 100644 --- a/crates/nu-explore/src/commands/expand.rs +++ b/crates/nu-explore/src/commands/expand.rs @@ -67,12 +67,11 @@ fn convert_value_to_string( let config = engine_state.get_config(); Ok(vals[0][0].to_abbreviated_string(config)) } else { - let ctrlc = engine_state.ctrlc.clone(); let config = engine_state.get_config(); let style_computer = StyleComputer::from_config(engine_state, stack); Ok(nu_common::try_build_table( - ctrlc, + engine_state.signals(), config, &style_computer, value, diff --git a/crates/nu-explore/src/explore.rs b/crates/nu-explore/src/explore.rs index e5498ce5b3..8d59591beb 100644 --- a/crates/nu-explore/src/explore.rs +++ b/crates/nu-explore/src/explore.rs @@ -63,7 +63,6 @@ impl Command for Explore { let tail: bool = call.has_flag(engine_state, stack, "tail")?; let peek_value: bool = call.has_flag(engine_state, stack, "peek")?; - let ctrlc = engine_state.ctrlc.clone(); let nu_config = engine_state.get_config(); let style_computer = StyleComputer::from_config(engine_state, stack); @@ -83,7 +82,7 @@ impl Command for Explore { tail, ); - let result = run_pager(engine_state, &mut stack.clone(), ctrlc, input, config); + let result = run_pager(engine_state, &mut stack.clone(), input, config); match result { Ok(Some(value)) => Ok(PipelineData::Value(value, None)), diff --git a/crates/nu-explore/src/lib.rs b/crates/nu-explore/src/lib.rs index 30b3e5cf87..043c85eb02 100644 --- a/crates/nu-explore/src/lib.rs +++ b/crates/nu-explore/src/lib.rs @@ -11,7 +11,7 @@ use commands::{ExpandCmd, HelpCmd, NuCmd, QuitCmd, TableCmd, TryCmd}; pub use default_context::add_explore_context; pub use explore::Explore; use explore::ExploreConfig; -use nu_common::{collect_pipeline, has_simple_value, CtrlC}; +use nu_common::{collect_pipeline, has_simple_value}; use nu_protocol::{ engine::{EngineState, Stack}, PipelineData, Value, @@ -28,7 +28,6 @@ mod util { fn run_pager( engine_state: &EngineState, stack: &mut Stack, - ctrlc: CtrlC, input: PipelineData, config: PagerConfig, ) -> Result> { @@ -45,14 +44,14 @@ fn run_pager( p.show_message("For help type :help"); let view = binary_view(input, config.explore_config)?; - return p.run(engine_state, stack, ctrlc, Some(view), commands); + return p.run(engine_state, stack, Some(view), commands); } let (columns, data) = collect_pipeline(input)?; let has_no_input = columns.is_empty() && data.is_empty(); if has_no_input { - return p.run(engine_state, stack, ctrlc, help_view(), commands); + return p.run(engine_state, stack, help_view(), commands); } p.show_message("For help type :help"); @@ -60,11 +59,11 @@ fn run_pager( if let Some(value) = has_simple_value(&data) { let text = value.to_abbreviated_string(config.nu_config); let view = Some(Page::new(Preview::new(&text), false)); - return p.run(engine_state, stack, ctrlc, view, commands); + return p.run(engine_state, stack, view, commands); } let view = create_record_view(columns, data, is_record, config); - p.run(engine_state, stack, ctrlc, view, commands) + p.run(engine_state, stack, view, commands) } fn create_record_view( diff --git a/crates/nu-explore/src/nu_common/mod.rs b/crates/nu-explore/src/nu_common/mod.rs index 091a7d6f36..42ba2f0896 100644 --- a/crates/nu-explore/src/nu_common/mod.rs +++ b/crates/nu-explore/src/nu_common/mod.rs @@ -6,13 +6,11 @@ mod value; use nu_color_config::TextStyle; use nu_protocol::Value; -use std::sync::{atomic::AtomicBool, Arc}; pub use nu_ansi_term::{Color as NuColor, Style as NuStyle}; pub use nu_protocol::{Config as NuConfig, Span as NuSpan}; pub type NuText = (String, TextStyle); -pub type CtrlC = Option>; pub use command::run_command_with_value; pub use lscolor::{create_lscolors, lscolorize}; diff --git a/crates/nu-explore/src/nu_common/table.rs b/crates/nu-explore/src/nu_common/table.rs index cb580e404b..164c837bf9 100644 --- a/crates/nu-explore/src/nu_common/table.rs +++ b/crates/nu-explore/src/nu_common/table.rs @@ -1,22 +1,21 @@ use crate::nu_common::NuConfig; use nu_color_config::StyleComputer; -use nu_protocol::{Record, Span, Value}; +use nu_protocol::{Record, Signals, Span, Value}; use nu_table::{ common::{nu_value_to_string, nu_value_to_string_clean}, ExpandedTable, TableOpts, }; -use std::sync::{atomic::AtomicBool, Arc}; pub fn try_build_table( - ctrlc: Option>, + signals: &Signals, config: &NuConfig, style_computer: &StyleComputer, value: Value, ) -> String { let span = value.span(); match value { - Value::List { vals, .. } => try_build_list(vals, ctrlc, config, span, style_computer), - Value::Record { val, .. } => try_build_map(&val, span, style_computer, ctrlc, config), + Value::List { vals, .. } => try_build_list(vals, signals, config, span, style_computer), + Value::Record { val, .. } => try_build_map(&val, span, style_computer, signals, config), val if matches!(val, Value::String { .. }) => { nu_value_to_string_clean(&val, config, style_computer).0 } @@ -28,13 +27,13 @@ fn try_build_map( record: &Record, span: Span, style_computer: &StyleComputer, - ctrlc: Option>, + signals: &Signals, config: &NuConfig, ) -> String { let opts = TableOpts::new( config, style_computer, - ctrlc, + signals, Span::unknown(), usize::MAX, (config.table_indent.left, config.table_indent.right), @@ -53,7 +52,7 @@ fn try_build_map( fn try_build_list( vals: Vec, - ctrlc: Option>, + signals: &Signals, config: &NuConfig, span: Span, style_computer: &StyleComputer, @@ -61,7 +60,7 @@ fn try_build_list( let opts = TableOpts::new( config, style_computer, - ctrlc, + signals, Span::unknown(), usize::MAX, (config.table_indent.left, config.table_indent.right), diff --git a/crates/nu-explore/src/pager/mod.rs b/crates/nu-explore/src/pager/mod.rs index 3114543f7c..4a043476bb 100644 --- a/crates/nu-explore/src/pager/mod.rs +++ b/crates/nu-explore/src/pager/mod.rs @@ -11,7 +11,7 @@ use self::{ use super::views::{Layout, View}; use crate::{ explore::ExploreConfig, - nu_common::{CtrlC, NuColor, NuConfig, NuStyle}, + nu_common::{NuColor, NuConfig, NuStyle}, registry::{Command, CommandRegistry}, views::{util::nu_style_to_tui, ViewConfig}, }; @@ -36,7 +36,6 @@ use std::{ cmp::min, io::{self, Stdout}, result, - sync::atomic::Ordering, }; pub type Frame<'a> = ratatui::Frame<'a>; @@ -89,7 +88,6 @@ impl<'a> Pager<'a> { &mut self, engine_state: &EngineState, stack: &mut Stack, - ctrlc: CtrlC, view: Option, commands: CommandRegistry, ) -> Result> { @@ -114,7 +112,6 @@ impl<'a> Pager<'a> { &mut terminal, engine_state, stack, - ctrlc, self, &mut info, view, @@ -173,7 +170,6 @@ fn render_ui( term: &mut Terminal, engine_state: &EngineState, stack: &mut Stack, - ctrlc: CtrlC, pager: &mut Pager<'_>, info: &mut ViewInfo, view: Option, @@ -183,11 +179,8 @@ fn render_ui( let mut view_stack = ViewStack::new(view, Vec::new()); loop { - // handle CTRLC event - if let Some(ctrlc) = ctrlc.clone() { - if ctrlc.load(Ordering::SeqCst) { - break Ok(None); - } + if engine_state.signals().interrupted() { + break Ok(None); } let mut layout = Layout::default(); diff --git a/crates/nu-lsp/src/lib.rs b/crates/nu-lsp/src/lib.rs index 44eeeb5756..fdeededac5 100644 --- a/crates/nu-lsp/src/lib.rs +++ b/crates/nu-lsp/src/lib.rs @@ -17,10 +17,7 @@ use ropey::Rope; use std::{ collections::BTreeMap, path::{Path, PathBuf}, - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, - }, + sync::Arc, time::Duration, }; @@ -57,11 +54,7 @@ impl LanguageServer { }) } - pub fn serve_requests( - mut self, - engine_state: EngineState, - ctrlc: Arc, - ) -> Result<()> { + pub fn serve_requests(mut self, engine_state: EngineState) -> Result<()> { let server_capabilities = serde_json::to_value(ServerCapabilities { text_document_sync: Some(lsp_types::TextDocumentSyncCapability::Kind( TextDocumentSyncKind::INCREMENTAL, @@ -75,10 +68,12 @@ impl LanguageServer { let _initialization_params = self .connection - .initialize_while(server_capabilities, || !ctrlc.load(Ordering::SeqCst)) + .initialize_while(server_capabilities, || { + !engine_state.signals().interrupted() + }) .into_diagnostic()?; - while !ctrlc.load(Ordering::SeqCst) { + while !engine_state.signals().interrupted() { let msg = match self .connection .receiver @@ -631,7 +626,7 @@ mod tests { std::thread::spawn(move || { let engine_state = nu_cmd_lang::create_default_context(); let engine_state = nu_command::add_shell_command_context(engine_state); - send.send(lsp_server.serve_requests(engine_state, Arc::new(AtomicBool::new(false)))) + send.send(lsp_server.serve_requests(engine_state)) }); client_connection diff --git a/crates/nu-plugin-core/src/interface/mod.rs b/crates/nu-plugin-core/src/interface/mod.rs index 4f287f39c0..89aa2b15e5 100644 --- a/crates/nu-plugin-core/src/interface/mod.rs +++ b/crates/nu-plugin-core/src/interface/mod.rs @@ -1,10 +1,10 @@ //! Implements the stream multiplexing interface for both the plugin side and the engine side. use nu_plugin_protocol::{ByteStreamInfo, ListStreamInfo, PipelineDataHeader, StreamMessage}; -use nu_protocol::{ByteStream, IntoSpanned, ListStream, PipelineData, Reader, ShellError}; +use nu_protocol::{ByteStream, IntoSpanned, ListStream, PipelineData, Reader, ShellError, Signals}; use std::{ io::{Read, Write}, - sync::{atomic::AtomicBool, Arc, Mutex}, + sync::Mutex, thread, }; @@ -170,7 +170,7 @@ pub trait InterfaceManager { fn read_pipeline_data( &self, header: PipelineDataHeader, - ctrlc: Option<&Arc>, + signals: &Signals, ) -> Result { self.prepare_pipeline_data(match header { PipelineDataHeader::Empty => PipelineData::Empty, @@ -178,12 +178,12 @@ pub trait InterfaceManager { PipelineDataHeader::ListStream(info) => { let handle = self.stream_manager().get_handle(); let reader = handle.read_stream(info.id, self.get_interface())?; - ListStream::new(reader, info.span, ctrlc.cloned()).into() + ListStream::new(reader, info.span, signals.clone()).into() } PipelineDataHeader::ByteStream(info) => { let handle = self.stream_manager().get_handle(); let reader = handle.read_stream(info.id, self.get_interface())?; - ByteStream::from_result_iter(reader, info.span, ctrlc.cloned(), info.type_).into() + ByteStream::from_result_iter(reader, info.span, signals.clone(), info.type_).into() } }) } diff --git a/crates/nu-plugin-core/src/interface/tests.rs b/crates/nu-plugin-core/src/interface/tests.rs index 6b86aba97d..456eb547b5 100644 --- a/crates/nu-plugin-core/src/interface/tests.rs +++ b/crates/nu-plugin-core/src/interface/tests.rs @@ -11,7 +11,7 @@ use nu_plugin_protocol::{ }; use nu_protocol::{ ByteStream, ByteStreamSource, ByteStreamType, DataSource, ListStream, PipelineData, - PipelineMetadata, ShellError, Span, Value, + PipelineMetadata, ShellError, Signals, Span, Value, }; use std::{path::Path, sync::Arc}; @@ -129,7 +129,7 @@ fn read_pipeline_data_empty() -> Result<(), ShellError> { let header = PipelineDataHeader::Empty; assert!(matches!( - manager.read_pipeline_data(header, None)?, + manager.read_pipeline_data(header, &Signals::empty())?, PipelineData::Empty )); Ok(()) @@ -141,7 +141,7 @@ fn read_pipeline_data_value() -> Result<(), ShellError> { let value = Value::test_int(4); let header = PipelineDataHeader::Value(value.clone()); - match manager.read_pipeline_data(header, None)? { + match manager.read_pipeline_data(header, &Signals::empty())? { PipelineData::Value(read_value, ..) => assert_eq!(value, read_value), PipelineData::ListStream(..) => panic!("unexpected ListStream"), PipelineData::ByteStream(..) => panic!("unexpected ByteStream"), @@ -168,7 +168,7 @@ fn read_pipeline_data_list_stream() -> Result<(), ShellError> { span: Span::test_data(), }); - let pipe = manager.read_pipeline_data(header, None)?; + let pipe = manager.read_pipeline_data(header, &Signals::empty())?; assert!( matches!(pipe, PipelineData::ListStream(..)), "unexpected PipelineData: {pipe:?}" @@ -212,7 +212,7 @@ fn read_pipeline_data_byte_stream() -> Result<(), ShellError> { type_: ByteStreamType::Unknown, }); - let pipe = manager.read_pipeline_data(header, None)?; + let pipe = manager.read_pipeline_data(header, &Signals::empty())?; // need to consume input manager.consume_all()?; @@ -257,7 +257,7 @@ fn read_pipeline_data_prepared_properly() -> Result<(), ShellError> { id: 0, span: Span::test_data(), }); - match manager.read_pipeline_data(header, None)? { + match manager.read_pipeline_data(header, &Signals::empty())? { PipelineData::ListStream(_, meta) => match meta { Some(PipelineMetadata { data_source, .. }) => match data_source { DataSource::FilePath(path) => { @@ -353,7 +353,11 @@ fn write_pipeline_data_list_stream() -> Result<(), ShellError> { // Set up pipeline data for a list stream let pipe = PipelineData::ListStream( - ListStream::new(values.clone().into_iter(), Span::test_data(), None), + ListStream::new( + values.clone().into_iter(), + Span::test_data(), + Signals::empty(), + ), None, ); @@ -406,7 +410,7 @@ fn write_pipeline_data_byte_stream() -> Result<(), ShellError> { ByteStream::read( std::io::Cursor::new(expected), span, - None, + Signals::empty(), ByteStreamType::Unknown, ), None, diff --git a/crates/nu-plugin-engine/src/context.rs b/crates/nu-plugin-engine/src/context.rs index c87e64aa48..b026d21b23 100644 --- a/crates/nu-plugin-engine/src/context.rs +++ b/crates/nu-plugin-engine/src/context.rs @@ -3,23 +3,21 @@ use nu_engine::{get_eval_block_with_early_return, get_full_help, ClosureEvalOnce use nu_protocol::{ ast::Call, engine::{Closure, EngineState, Redirection, Stack}, - Config, IntoSpanned, OutDest, PipelineData, PluginIdentity, ShellError, Span, Spanned, Value, + Config, IntoSpanned, OutDest, PipelineData, PluginIdentity, ShellError, Signals, Span, Spanned, + Value, }; use std::{ borrow::Cow, collections::HashMap, - sync::{ - atomic::{AtomicBool, AtomicU32}, - Arc, - }, + sync::{atomic::AtomicU32, Arc}, }; /// Object safe trait for abstracting operations required of the plugin context. pub trait PluginExecutionContext: Send + Sync { /// A span pointing to the command being executed fn span(&self) -> Span; - /// The interrupt signal, if present - fn ctrlc(&self) -> Option<&Arc>; + /// The [`Signals`] struct, if present + fn signals(&self) -> &Signals; /// The pipeline externals state, for tracking the foreground process group, if present fn pipeline_externals_state(&self) -> Option<&Arc<(AtomicU32, AtomicU32)>>; /// Get engine configuration @@ -80,8 +78,8 @@ impl<'a> PluginExecutionContext for PluginExecutionCommandContext<'a> { self.call.head } - fn ctrlc(&self) -> Option<&Arc> { - self.engine_state.ctrlc.as_ref() + fn signals(&self) -> &Signals { + self.engine_state.signals() } fn pipeline_externals_state(&self) -> Option<&Arc<(AtomicU32, AtomicU32)>> { @@ -234,8 +232,8 @@ impl PluginExecutionContext for PluginExecutionBogusContext { Span::test_data() } - fn ctrlc(&self) -> Option<&Arc> { - None + fn signals(&self) -> &Signals { + &Signals::EMPTY } fn pipeline_externals_state(&self) -> Option<&Arc<(AtomicU32, AtomicU32)>> { diff --git a/crates/nu-plugin-engine/src/interface/mod.rs b/crates/nu-plugin-engine/src/interface/mod.rs index 9a4c72d2c1..79ab7f7720 100644 --- a/crates/nu-plugin-engine/src/interface/mod.rs +++ b/crates/nu-plugin-engine/src/interface/mod.rs @@ -12,11 +12,11 @@ use nu_plugin_protocol::{ }; use nu_protocol::{ ast::Operator, CustomValue, IntoSpanned, PipelineData, PluginMetadata, PluginSignature, - ShellError, Span, Spanned, Value, + ShellError, Signals, Span, Spanned, Value, }; use std::{ collections::{btree_map, BTreeMap}, - sync::{atomic::AtomicBool, mpsc, Arc, OnceLock}, + sync::{mpsc, Arc, OnceLock}, }; use crate::{ @@ -103,8 +103,8 @@ struct PluginCallState { /// Don't try to send the plugin call response. This is only used for `Dropped` to avoid an /// error dont_send_response: bool, - /// Interrupt signal to be used for stream iterators - ctrlc: Option>, + /// Signals to be used for stream iterators + signals: Signals, /// Channel to receive context on to be used if needed context_rx: Option>, /// Span associated with the call, if any @@ -231,14 +231,14 @@ impl PluginInterfaceManager { } } - /// Find the ctrlc signal corresponding to the given plugin call id - fn get_ctrlc(&mut self, id: PluginCallId) -> Result>, ShellError> { + /// Find the [`Signals`] struct corresponding to the given plugin call id + fn get_signals(&mut self, id: PluginCallId) -> Result { // Make sure we're up to date self.receive_plugin_call_subscriptions(); // Find the subscription and return the context self.plugin_call_states .get(&id) - .map(|state| state.ctrlc.clone()) + .map(|state| state.signals.clone()) .ok_or_else(|| ShellError::PluginFailedToDecode { msg: format!("Unknown plugin call ID: {id}"), }) @@ -517,14 +517,14 @@ impl InterfaceManager for PluginInterfaceManager { // Handle reading the pipeline data, if any let response = response .map_data(|data| { - let ctrlc = self.get_ctrlc(id)?; + let signals = self.get_signals(id)?; // Register the stream in the response if let Some(stream_id) = data.stream_id() { self.recv_stream_started(id, stream_id); } - self.read_pipeline_data(data, ctrlc.as_ref()) + self.read_pipeline_data(data, &signals) }) .unwrap_or_else(|err| { // If there's an error with initializing this stream, change it to a plugin @@ -544,8 +544,8 @@ impl InterfaceManager for PluginInterfaceManager { let call = call // Handle reading the pipeline data, if any .map_data(|input| { - let ctrlc = self.get_ctrlc(context)?; - self.read_pipeline_data(input, ctrlc.as_ref()) + let signals = self.get_signals(context)?; + self.read_pipeline_data(input, &signals) }) // Do anything extra needed for each engine call setup .and_then(|mut engine_call| { @@ -698,7 +698,9 @@ impl PluginInterface { context: Option<&dyn PluginExecutionContext>, ) -> Result { let id = self.state.plugin_call_id_sequence.next()?; - let ctrlc = context.and_then(|c| c.ctrlc().cloned()); + let signals = context + .map(|c| c.signals().clone()) + .unwrap_or_else(Signals::empty); let (tx, rx) = mpsc::channel(); let (context_tx, context_rx) = mpsc::channel(); let keep_plugin_custom_values = mpsc::channel(); @@ -746,7 +748,7 @@ impl PluginInterface { PluginCallState { sender: Some(tx).filter(|_| !dont_send_response), dont_send_response, - ctrlc, + signals, context_rx: Some(context_rx), span: call.span(), keep_plugin_custom_values, diff --git a/crates/nu-plugin-engine/src/interface/tests.rs b/crates/nu-plugin-engine/src/interface/tests.rs index 5665beb92b..2bab67f25f 100644 --- a/crates/nu-plugin-engine/src/interface/tests.rs +++ b/crates/nu-plugin-engine/src/interface/tests.rs @@ -18,7 +18,7 @@ use nu_protocol::{ ast::{Math, Operator}, engine::Closure, ByteStreamType, CustomValue, IntoInterruptiblePipelineData, IntoSpanned, PipelineData, - PluginMetadata, PluginSignature, ShellError, Span, Spanned, Value, + PluginMetadata, PluginSignature, ShellError, Signals, Span, Spanned, Value, }; use serde::{Deserialize, Serialize}; use std::{ @@ -56,7 +56,7 @@ fn manager_consume_all_exits_after_streams_and_interfaces_are_dropped() -> Resul id: 0, span: Span::test_data(), }), - None, + &Signals::empty(), )?; // and an interface... @@ -112,7 +112,7 @@ fn manager_consume_all_propagates_io_error_to_readers() -> Result<(), ShellError id: 0, span: Span::test_data(), }), - None, + &Signals::empty(), )?; manager @@ -159,7 +159,7 @@ fn manager_consume_all_propagates_message_error_to_readers() -> Result<(), Shell span: Span::test_data(), type_: ByteStreamType::Unknown, }), - None, + &Signals::empty(), )?; manager @@ -190,7 +190,7 @@ fn fake_plugin_call( PluginCallState { sender: Some(tx), dont_send_response: false, - ctrlc: None, + signals: Signals::empty(), context_rx: None, span: None, keep_plugin_custom_values: mpsc::channel(), @@ -493,7 +493,7 @@ fn manager_handle_engine_call_after_response_received() -> Result<(), ShellError PluginCallState { sender: None, dont_send_response: false, - ctrlc: None, + signals: Signals::empty(), context_rx: Some(context_rx), span: None, keep_plugin_custom_values: mpsc::channel(), @@ -559,7 +559,7 @@ fn manager_send_plugin_call_response_removes_context_only_if_no_streams_to_read( PluginCallState { sender: None, dont_send_response: false, - ctrlc: None, + signals: Signals::empty(), context_rx: None, span: None, keep_plugin_custom_values: mpsc::channel(), @@ -595,7 +595,7 @@ fn manager_consume_stream_end_removes_context_only_if_last_stream() -> Result<() PluginCallState { sender: None, dont_send_response: false, - ctrlc: None, + signals: Signals::empty(), context_rx: None, span: None, keep_plugin_custom_values: mpsc::channel(), @@ -678,7 +678,7 @@ fn manager_prepare_pipeline_data_adds_source_to_list_streams() -> Result<(), She [Value::test_custom_value(Box::new( test_plugin_custom_value(), ))] - .into_pipeline_data(Span::test_data(), None), + .into_pipeline_data(Span::test_data(), Signals::empty()), )?; let value = data @@ -852,7 +852,9 @@ fn interface_write_plugin_call_writes_run_with_stream_input() -> Result<(), Shel positional: vec![], named: vec![], }, - input: values.clone().into_pipeline_data(Span::test_data(), None), + input: values + .clone() + .into_pipeline_data(Span::test_data(), Signals::empty()), }), None, )?; @@ -1148,7 +1150,9 @@ fn interface_prepare_pipeline_data_accepts_normal_streams() -> Result<(), ShellE let values = normal_values(&interface); let state = CurrentCallState::default(); let data = interface.prepare_pipeline_data( - values.clone().into_pipeline_data(Span::test_data(), None), + values + .clone() + .into_pipeline_data(Span::test_data(), Signals::empty()), &state, )?; @@ -1211,7 +1215,9 @@ fn interface_prepare_pipeline_data_rejects_bad_custom_value_in_a_stream() -> Res let values = bad_custom_values(); let state = CurrentCallState::default(); let data = interface.prepare_pipeline_data( - values.clone().into_pipeline_data(Span::test_data(), None), + values + .clone() + .into_pipeline_data(Span::test_data(), Signals::empty()), &state, )?; diff --git a/crates/nu-plugin-test-support/src/lib.rs b/crates/nu-plugin-test-support/src/lib.rs index a53b353981..998c31ac0d 100644 --- a/crates/nu-plugin-test-support/src/lib.rs +++ b/crates/nu-plugin-test-support/src/lib.rs @@ -8,8 +8,8 @@ //! use nu_plugin::*; //! use nu_plugin_test_support::PluginTest; //! use nu_protocol::{ -//! Example, IntoInterruptiblePipelineData, LabeledError, PipelineData, ShellError, Signature, -//! Span, Type, Value, +//! Example, IntoInterruptiblePipelineData, LabeledError, PipelineData, ShellError, Signals, +//! Signature, Span, Type, Value, //! }; //! //! struct LowercasePlugin; @@ -60,7 +60,7 @@ //! // Errors in a stream should be returned as values. //! .unwrap_or_else(|err| Value::error(err, span)) //! }, -//! None, +//! &Signals::empty(), //! )?) //! } //! } @@ -83,7 +83,7 @@ //! //! // #[test] //! fn test_lowercase() -> Result<(), ShellError> { -//! let input = vec![Value::test_string("FooBar")].into_pipeline_data(Span::test_data(), None); +//! let input = vec![Value::test_string("FooBar")].into_pipeline_data(Span::test_data(), Signals::empty()); //! let output = PluginTest::new("lowercase", LowercasePlugin.into())? //! .eval_with("lowercase", input)? //! .into_value(Span::test_data())?; diff --git a/crates/nu-plugin-test-support/src/plugin_test.rs b/crates/nu-plugin-test-support/src/plugin_test.rs index 3d6b3eec23..5e3334c78b 100644 --- a/crates/nu-plugin-test-support/src/plugin_test.rs +++ b/crates/nu-plugin-test-support/src/plugin_test.rs @@ -11,7 +11,7 @@ use nu_protocol::{ debugger::WithoutDebug, engine::{EngineState, Stack, StateWorkingSet}, report_error_new, CustomValue, Example, IntoSpanned as _, LabeledError, PipelineData, - ShellError, Span, Value, + ShellError, Signals, Span, Value, }; use crate::{diff::diff_by_line, fake_register::fake_register}; @@ -85,13 +85,13 @@ impl PluginTest { /// /// ```rust,no_run /// # use nu_plugin_test_support::PluginTest; - /// # use nu_protocol::{ShellError, Span, Value, IntoInterruptiblePipelineData}; + /// # use nu_protocol::{IntoInterruptiblePipelineData, ShellError, Signals, Span, Value}; /// # use nu_plugin::*; /// # fn test(MyPlugin: impl Plugin + Send + 'static) -> Result<(), ShellError> { /// let result = PluginTest::new("my_plugin", MyPlugin.into())? /// .eval_with( /// "my-command", - /// vec![Value::test_int(42)].into_pipeline_data(Span::test_data(), None) + /// vec![Value::test_int(42)].into_pipeline_data(Span::test_data(), Signals::empty()) /// )? /// .into_value(Span::test_data())?; /// assert_eq!(Value::test_string("42"), result); @@ -151,7 +151,7 @@ impl PluginTest { Err(err) => Value::error(err, value.span()), } }, - None, + &Signals::empty(), )? }; @@ -171,7 +171,7 @@ impl PluginTest { Err(err) => Value::error(err, value.span()), } }, - None, + &Signals::empty(), ) } } diff --git a/crates/nu-plugin-test-support/tests/lowercase/mod.rs b/crates/nu-plugin-test-support/tests/lowercase/mod.rs index 50271a8cc2..5be127e7f5 100644 --- a/crates/nu-plugin-test-support/tests/lowercase/mod.rs +++ b/crates/nu-plugin-test-support/tests/lowercase/mod.rs @@ -1,8 +1,8 @@ use nu_plugin::*; use nu_plugin_test_support::PluginTest; use nu_protocol::{ - Example, IntoInterruptiblePipelineData, LabeledError, PipelineData, ShellError, Signature, - Span, Type, Value, + Example, IntoInterruptiblePipelineData, LabeledError, PipelineData, ShellError, Signals, + Signature, Span, Type, Value, }; struct LowercasePlugin; @@ -53,7 +53,7 @@ impl PluginCommand for Lowercase { // Errors in a stream should be returned as values. .unwrap_or_else(|err| Value::error(err, span)) }, - None, + &Signals::empty(), )?) } } @@ -72,7 +72,8 @@ impl Plugin for LowercasePlugin { fn test_lowercase_using_eval_with() -> Result<(), ShellError> { let result = PluginTest::new("lowercase", LowercasePlugin.into())?.eval_with( "lowercase", - vec![Value::test_string("HeLlO wOrLd")].into_pipeline_data(Span::test_data(), None), + vec![Value::test_string("HeLlO wOrLd")] + .into_pipeline_data(Span::test_data(), Signals::empty()), )?; assert_eq!( diff --git a/crates/nu-plugin/src/plugin/command.rs b/crates/nu-plugin/src/plugin/command.rs index d33fc445e2..a90555ac40 100644 --- a/crates/nu-plugin/src/plugin/command.rs +++ b/crates/nu-plugin/src/plugin/command.rs @@ -22,7 +22,7 @@ use crate::{EngineInterface, EvaluatedCall, Plugin}; /// Basic usage: /// ``` /// # use nu_plugin::*; -/// # use nu_protocol::{Signature, PipelineData, Type, Value, LabeledError}; +/// # use nu_protocol::{LabeledError, PipelineData, Signals, Signature, Type, Value}; /// struct LowercasePlugin; /// struct Lowercase; /// @@ -55,7 +55,7 @@ use crate::{EngineInterface, EvaluatedCall, Plugin}; /// .map(|string| Value::string(string.to_lowercase(), span)) /// // Errors in a stream should be returned as values. /// .unwrap_or_else(|err| Value::error(err, span)) -/// }, None)?) +/// }, &Signals::empty())?) /// } /// } /// diff --git a/crates/nu-plugin/src/plugin/interface/mod.rs b/crates/nu-plugin/src/plugin/interface/mod.rs index fc9c9e85b3..dcb4119dba 100644 --- a/crates/nu-plugin/src/plugin/interface/mod.rs +++ b/crates/nu-plugin/src/plugin/interface/mod.rs @@ -12,7 +12,7 @@ use nu_plugin_protocol::{ }; use nu_protocol::{ engine::Closure, Config, LabeledError, PipelineData, PluginMetadata, PluginSignature, - ShellError, Span, Spanned, Value, + ShellError, Signals, Span, Spanned, Value, }; use std::{ collections::{btree_map, BTreeMap, HashMap}, @@ -274,7 +274,9 @@ impl InterfaceManager for EngineInterfaceManager { PluginInput::Call(id, call) => { let interface = self.interface_for_context(id); // Read streams in the input - let call = match call.map_data(|input| self.read_pipeline_data(input, None)) { + let call = match call + .map_data(|input| self.read_pipeline_data(input, &Signals::empty())) + { Ok(call) => call, Err(err) => { // If there's an error with initialization of the input stream, just send @@ -320,7 +322,7 @@ impl InterfaceManager for EngineInterfaceManager { } PluginInput::EngineCallResponse(id, response) => { let response = response - .map_data(|header| self.read_pipeline_data(header, None)) + .map_data(|header| self.read_pipeline_data(header, &Signals::empty())) .unwrap_or_else(|err| { // If there's an error with initializing this stream, change it to an engine // call error response, but send it anyway diff --git a/crates/nu-plugin/src/plugin/interface/tests.rs b/crates/nu-plugin/src/plugin/interface/tests.rs index b195b43197..7065c6f64d 100644 --- a/crates/nu-plugin/src/plugin/interface/tests.rs +++ b/crates/nu-plugin/src/plugin/interface/tests.rs @@ -10,7 +10,7 @@ use nu_plugin_protocol::{ }; use nu_protocol::{ engine::Closure, ByteStreamType, Config, CustomValue, IntoInterruptiblePipelineData, - LabeledError, PipelineData, PluginSignature, ShellError, Span, Spanned, Value, + LabeledError, PipelineData, PluginSignature, ShellError, Signals, Span, Spanned, Value, }; use std::{ collections::HashMap, @@ -59,7 +59,7 @@ fn manager_consume_all_exits_after_streams_and_interfaces_are_dropped() -> Resul id: 0, span: Span::test_data(), }), - None, + &Signals::empty(), )?; // and an interface... @@ -115,7 +115,7 @@ fn manager_consume_all_propagates_io_error_to_readers() -> Result<(), ShellError id: 0, span: Span::test_data(), }), - None, + &Signals::empty(), )?; manager @@ -162,7 +162,7 @@ fn manager_consume_all_propagates_message_error_to_readers() -> Result<(), Shell span: Span::test_data(), type_: ByteStreamType::Unknown, }), - None, + &Signals::empty(), )?; manager @@ -615,7 +615,7 @@ fn manager_prepare_pipeline_data_deserializes_custom_values_in_streams() -> Resu [Value::test_custom_value(Box::new( test_plugin_custom_value(), ))] - .into_pipeline_data(Span::test_data(), None), + .into_pipeline_data(Span::test_data(), Signals::empty()), )?; let value = data @@ -647,7 +647,7 @@ fn manager_prepare_pipeline_data_embeds_deserialization_errors_in_streams() -> R let span = Span::new(20, 30); let data = manager.prepare_pipeline_data( [Value::custom(Box::new(invalid_custom_value), span)] - .into_pipeline_data(Span::test_data(), None), + .into_pipeline_data(Span::test_data(), Signals::empty()), )?; let value = data @@ -730,7 +730,7 @@ fn interface_write_response_with_stream() -> Result<(), ShellError> { interface .write_response(Ok::<_, ShellError>( [Value::test_int(3), Value::test_int(4), Value::test_int(5)] - .into_pipeline_data(Span::test_data(), None), + .into_pipeline_data(Span::test_data(), Signals::empty()), ))? .write()?; @@ -1132,7 +1132,7 @@ fn interface_prepare_pipeline_data_serializes_custom_values_in_streams() -> Resu [Value::test_custom_value(Box::new( expected_test_custom_value(), ))] - .into_pipeline_data(Span::test_data(), None), + .into_pipeline_data(Span::test_data(), Signals::empty()), &(), )?; @@ -1191,7 +1191,7 @@ fn interface_prepare_pipeline_data_embeds_serialization_errors_in_streams() -> R let span = Span::new(40, 60); let data = interface.prepare_pipeline_data( [Value::custom(Box::new(CantSerialize::BadVariant), span)] - .into_pipeline_data(Span::test_data(), None), + .into_pipeline_data(Span::test_data(), Signals::empty()), &(), )?; diff --git a/crates/nu-protocol/src/engine/engine_state.rs b/crates/nu-protocol/src/engine/engine_state.rs index c436621627..e393d78d41 100644 --- a/crates/nu-protocol/src/engine/engine_state.rs +++ b/crates/nu-protocol/src/engine/engine_state.rs @@ -8,7 +8,7 @@ use crate::{ }, eval_const::create_nu_constant, BlockId, Category, Config, DeclId, FileId, GetSpan, HistoryConfig, Module, ModuleId, OverlayId, - ShellError, Signature, Span, SpanId, Type, Value, VarId, VirtualPathId, + ShellError, Signals, Signature, Span, SpanId, Type, Value, VarId, VirtualPathId, }; use fancy_regex::Regex; use lru::LruCache; @@ -84,7 +84,7 @@ pub struct EngineState { pub spans: Vec, usage: Usage, pub scope: ScopeFrame, - pub ctrlc: Option>, + signals: Signals, pub env_vars: Arc, pub previous_env_vars: Arc>, pub config: Arc, @@ -144,7 +144,7 @@ impl EngineState { 0, false, ), - ctrlc: None, + signals: Signals::empty(), env_vars: Arc::new( [(DEFAULT_OVERLAY_NAME.to_string(), HashMap::new())] .into_iter() @@ -177,6 +177,18 @@ impl EngineState { } } + pub fn signals(&self) -> &Signals { + &self.signals + } + + pub fn reset_signals(&mut self) { + self.signals.reset() + } + + pub fn set_signals(&mut self, signals: Signals) { + self.signals = signals; + } + /// Merges a `StateDelta` onto the current state. These deltas come from a system, like the parser, that /// creates a new set of definitions and visible symbols in the current scope. We make this transactional /// as there are times when we want to run the parser and immediately throw away the results (namely: diff --git a/crates/nu-protocol/src/errors/shell_error.rs b/crates/nu-protocol/src/errors/shell_error.rs index 30752d5c9e..be24fd093e 100644 --- a/crates/nu-protocol/src/errors/shell_error.rs +++ b/crates/nu-protocol/src/errors/shell_error.rs @@ -1187,6 +1187,13 @@ pub enum ShellError { span: Option, }, + /// Operation interrupted + #[error("Operation interrupted")] + Interrupted { + #[label("This operation was interrupted")] + span: Span, + }, + /// Operation interrupted by user #[error("Operation interrupted by user")] InterruptedByUser { diff --git a/crates/nu-protocol/src/pipeline/byte_stream.rs b/crates/nu-protocol/src/pipeline/byte_stream.rs index babd195a9e..6226f1d8db 100644 --- a/crates/nu-protocol/src/pipeline/byte_stream.rs +++ b/crates/nu-protocol/src/pipeline/byte_stream.rs @@ -1,9 +1,8 @@ -use serde::{Deserialize, Serialize}; - use crate::{ process::{ChildPipe, ChildProcess, ExitStatus}, - ErrSpan, IntoSpanned, OutDest, PipelineData, ShellError, Span, Type, Value, + ErrSpan, IntoSpanned, OutDest, PipelineData, ShellError, Signals, Span, Type, Value, }; +use serde::{Deserialize, Serialize}; #[cfg(unix)] use std::os::fd::OwnedFd; #[cfg(windows)] @@ -13,10 +12,6 @@ use std::{ fs::File, io::{self, BufRead, BufReader, Cursor, ErrorKind, Read, Write}, process::Stdio, - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, - }, thread, }; @@ -182,7 +177,7 @@ impl From for Type { pub struct ByteStream { stream: ByteStreamSource, span: Span, - ctrlc: Option>, + signals: Signals, type_: ByteStreamType, known_size: Option, } @@ -192,13 +187,13 @@ impl ByteStream { pub fn new( stream: ByteStreamSource, span: Span, - interrupt: Option>, + signals: Signals, type_: ByteStreamType, ) -> Self { Self { stream, span, - ctrlc: interrupt, + signals, type_, known_size: None, } @@ -208,33 +203,33 @@ impl ByteStream { pub fn read( reader: impl Read + Send + 'static, span: Span, - interrupt: Option>, + signals: Signals, type_: ByteStreamType, ) -> Self { Self::new( ByteStreamSource::Read(Box::new(reader)), span, - interrupt, + signals, type_, ) } /// Create a [`ByteStream`] from a string. The type of the stream is always `String`. - pub fn read_string(string: String, span: Span, interrupt: Option>) -> Self { + pub fn read_string(string: String, span: Span, signals: Signals) -> Self { let len = string.len(); ByteStream::read( Cursor::new(string.into_bytes()), span, - interrupt, + signals, ByteStreamType::String, ) .with_known_size(Some(len as u64)) } /// Create a [`ByteStream`] from a byte vector. The type of the stream is always `Binary`. - pub fn read_binary(bytes: Vec, span: Span, interrupt: Option>) -> Self { + pub fn read_binary(bytes: Vec, span: Span, signals: Signals) -> Self { let len = bytes.len(); - ByteStream::read(Cursor::new(bytes), span, interrupt, ByteStreamType::Binary) + ByteStream::read(Cursor::new(bytes), span, signals, ByteStreamType::Binary) .with_known_size(Some(len as u64)) } @@ -242,11 +237,11 @@ impl ByteStream { /// /// The type is implicitly `Unknown`, as it's not typically known whether files will /// return text or binary. - pub fn file(file: File, span: Span, interrupt: Option>) -> Self { + pub fn file(file: File, span: Span, signals: Signals) -> Self { Self::new( ByteStreamSource::File(file), span, - interrupt, + signals, ByteStreamType::Unknown, ) } @@ -259,7 +254,7 @@ impl ByteStream { Self::new( ByteStreamSource::Child(Box::new(child)), span, - None, + Signals::empty(), ByteStreamType::Unknown, ) } @@ -271,14 +266,19 @@ impl ByteStream { pub fn stdin(span: Span) -> Result { let stdin = os_pipe::dup_stdin().err_span(span)?; let source = ByteStreamSource::File(convert_file(stdin)); - Ok(Self::new(source, span, None, ByteStreamType::Unknown)) + Ok(Self::new( + source, + span, + Signals::empty(), + ByteStreamType::Unknown, + )) } /// Create a [`ByteStream`] from a generator function that writes data to the given buffer /// when called, and returns `Ok(false)` on end of stream. pub fn from_fn( span: Span, - interrupt: Option>, + signals: Signals, type_: ByteStreamType, generator: impl FnMut(&mut Vec) -> Result + Send + 'static, ) -> Self { @@ -288,7 +288,7 @@ impl ByteStream { generator, }, span, - interrupt, + signals, type_, ) } @@ -301,12 +301,7 @@ impl ByteStream { /// Create a new [`ByteStream`] from an [`Iterator`] of bytes slices. /// /// The returned [`ByteStream`] will have a [`ByteStreamSource`] of `Read`. - pub fn from_iter( - iter: I, - span: Span, - interrupt: Option>, - type_: ByteStreamType, - ) -> Self + pub fn from_iter(iter: I, span: Span, signals: Signals, type_: ByteStreamType) -> Self where I: IntoIterator, I::IntoIter: Send + 'static, @@ -314,7 +309,7 @@ impl ByteStream { { let iter = iter.into_iter(); let cursor = Some(Cursor::new(I::Item::default())); - Self::read(ReadIterator { iter, cursor }, span, interrupt, type_) + Self::read(ReadIterator { iter, cursor }, span, signals, type_) } /// Create a new [`ByteStream`] from an [`Iterator`] of [`Result`] bytes slices. @@ -323,7 +318,7 @@ impl ByteStream { pub fn from_result_iter( iter: I, span: Span, - interrupt: Option>, + signals: Signals, type_: ByteStreamType, ) -> Self where @@ -333,7 +328,7 @@ impl ByteStream { { let iter = iter.into_iter(); let cursor = Some(Cursor::new(T::default())); - Self::read(ReadResultIterator { iter, cursor }, span, interrupt, type_) + Self::read(ReadResultIterator { iter, cursor }, span, signals, type_) } /// Set the known size, in number of bytes, of the [`ByteStream`]. @@ -378,7 +373,7 @@ impl ByteStream { Some(Reader { reader: BufReader::new(reader), span: self.span, - ctrlc: self.ctrlc, + signals: self.signals, }) } @@ -394,7 +389,7 @@ impl ByteStream { Some(Lines { reader: BufReader::new(reader), span: self.span, - ctrlc: self.ctrlc, + signals: self.signals, }) } @@ -415,7 +410,7 @@ impl ByteStream { /// then the stream is considered empty and `None` will be returned. pub fn chunks(self) -> Option { let reader = self.stream.reader()?; - Some(Chunks::new(reader, self.span, self.ctrlc, self.type_)) + Some(Chunks::new(reader, self.span, self.signals, self.type_)) } /// Convert the [`ByteStream`] into its inner [`ByteStreamSource`]. @@ -552,7 +547,7 @@ impl ByteStream { pub fn drain(self) -> Result, ShellError> { match self.stream { ByteStreamSource::Read(read) => { - copy_with_interrupt(read, io::sink(), self.span, self.ctrlc.as_deref())?; + copy_with_signals(read, io::sink(), self.span, &self.signals)?; Ok(None) } ByteStreamSource::File(_) => Ok(None), @@ -578,14 +573,14 @@ impl ByteStream { /// then the [`ExitStatus`] of the [`ChildProcess`] is returned. pub fn write_to(self, dest: impl Write) -> Result, ShellError> { let span = self.span; - let ctrlc = self.ctrlc.as_deref(); + let signals = &self.signals; match self.stream { ByteStreamSource::Read(read) => { - copy_with_interrupt(read, dest, span, ctrlc)?; + copy_with_signals(read, dest, span, signals)?; Ok(None) } ByteStreamSource::File(file) => { - copy_with_interrupt(file, dest, span, ctrlc)?; + copy_with_signals(file, dest, span, signals)?; Ok(None) } ByteStreamSource::Child(mut child) => { @@ -597,10 +592,10 @@ impl ByteStream { if let Some(stdout) = child.stdout.take() { match stdout { ChildPipe::Pipe(pipe) => { - copy_with_interrupt(pipe, dest, span, ctrlc)?; + copy_with_signals(pipe, dest, span, signals)?; } ChildPipe::Tee(tee) => { - copy_with_interrupt(tee, dest, span, ctrlc)?; + copy_with_signals(tee, dest, span, signals)?; } } } @@ -615,21 +610,21 @@ impl ByteStream { stderr: &OutDest, ) -> Result, ShellError> { let span = self.span; - let ctrlc = self.ctrlc.as_deref(); + let signals = &self.signals; match self.stream { ByteStreamSource::Read(read) => { - write_to_out_dest(read, stdout, true, span, ctrlc)?; + write_to_out_dest(read, stdout, true, span, signals)?; Ok(None) } ByteStreamSource::File(file) => { match stdout { OutDest::Pipe | OutDest::Capture | OutDest::Null => {} OutDest::Inherit => { - copy_with_interrupt(file, io::stdout(), span, ctrlc)?; + copy_with_signals(file, io::stdout(), span, signals)?; } OutDest::File(f) => { - copy_with_interrupt(file, f.as_ref(), span, ctrlc)?; + copy_with_signals(file, f.as_ref(), span, signals)?; } } Ok(None) @@ -643,20 +638,20 @@ impl ByteStream { .name("stderr writer".into()) .spawn_scoped(s, || match err { ChildPipe::Pipe(pipe) => { - write_to_out_dest(pipe, stderr, false, span, ctrlc) + write_to_out_dest(pipe, stderr, false, span, signals) } ChildPipe::Tee(tee) => { - write_to_out_dest(tee, stderr, false, span, ctrlc) + write_to_out_dest(tee, stderr, false, span, signals) } }) .err_span(span); match out { ChildPipe::Pipe(pipe) => { - write_to_out_dest(pipe, stdout, true, span, ctrlc) + write_to_out_dest(pipe, stdout, true, span, signals) } ChildPipe::Tee(tee) => { - write_to_out_dest(tee, stdout, true, span, ctrlc) + write_to_out_dest(tee, stdout, true, span, signals) } }?; @@ -672,11 +667,11 @@ impl ByteStream { } (Some(out), None) => { // single output stream, we can consume directly - write_to_out_dest(out, stdout, true, span, ctrlc)?; + write_to_out_dest(out, stdout, true, span, signals)?; } (None, Some(err)) => { // single output stream, we can consume directly - write_to_out_dest(err, stderr, false, span, ctrlc)?; + write_to_out_dest(err, stderr, false, span, signals)?; } (None, None) => {} } @@ -749,7 +744,7 @@ where pub struct Reader { reader: BufReader, span: Span, - ctrlc: Option>, + signals: Signals, } impl Reader { @@ -760,14 +755,8 @@ impl Reader { impl Read for Reader { fn read(&mut self, buf: &mut [u8]) -> io::Result { - if nu_utils::ctrl_c::was_pressed(&self.ctrlc) { - Err(ShellError::InterruptedByUser { - span: Some(self.span), - } - .into()) - } else { - self.reader.read(buf) - } + self.signals.check(self.span)?; + self.reader.read(buf) } } @@ -784,7 +773,7 @@ impl BufRead for Reader { pub struct Lines { reader: BufReader, span: Span, - ctrlc: Option>, + signals: Signals, } impl Lines { @@ -797,7 +786,7 @@ impl Iterator for Lines { type Item = Result; fn next(&mut self) -> Option { - if nu_utils::ctrl_c::was_pressed(&self.ctrlc) { + if self.signals.interrupted() { None } else { let mut buf = Vec::new(); @@ -826,23 +815,18 @@ pub struct Chunks { pos: u64, error: bool, span: Span, - ctrlc: Option>, + signals: Signals, type_: ByteStreamType, } impl Chunks { - fn new( - reader: SourceReader, - span: Span, - ctrlc: Option>, - type_: ByteStreamType, - ) -> Self { + fn new(reader: SourceReader, span: Span, signals: Signals, type_: ByteStreamType) -> Self { Self { reader: BufReader::new(reader), pos: 0, error: false, span, - ctrlc, + signals, type_, } } @@ -922,7 +906,7 @@ impl Iterator for Chunks { type Item = Result; fn next(&mut self) -> Option { - if self.error || nu_utils::ctrl_c::was_pressed(&self.ctrlc) { + if self.error || self.signals.interrupted() { None } else { match self.type_ { @@ -988,14 +972,14 @@ fn write_to_out_dest( stream: &OutDest, stdout: bool, span: Span, - ctrlc: Option<&AtomicBool>, + signals: &Signals, ) -> Result<(), ShellError> { match stream { OutDest::Pipe | OutDest::Capture => return Ok(()), - OutDest::Null => copy_with_interrupt(read, io::sink(), span, ctrlc), - OutDest::Inherit if stdout => copy_with_interrupt(read, io::stdout(), span, ctrlc), - OutDest::Inherit => copy_with_interrupt(read, io::stderr(), span, ctrlc), - OutDest::File(file) => copy_with_interrupt(read, file.as_ref(), span, ctrlc), + OutDest::Null => copy_with_signals(read, io::sink(), span, signals), + OutDest::Inherit if stdout => copy_with_signals(read, io::stdout(), span, signals), + OutDest::Inherit => copy_with_signals(read, io::stderr(), span, signals), + OutDest::File(file) => copy_with_signals(read, file.as_ref(), span, signals), }?; Ok(()) } @@ -1012,28 +996,13 @@ pub(crate) fn convert_file>(file: impl Into) - const DEFAULT_BUF_SIZE: usize = 8192; -pub fn copy_with_interrupt( +pub fn copy_with_signals( mut reader: impl Read, mut writer: impl Write, span: Span, - interrupt: Option<&AtomicBool>, + signals: &Signals, ) -> Result { - if let Some(interrupt) = interrupt { - // #[cfg(any(target_os = "linux", target_os = "android"))] - // { - // return crate::sys::kernel_copy::copy_spec(reader, writer); - // } - match generic_copy(&mut reader, &mut writer, span, interrupt) { - Ok(len) => { - writer.flush().err_span(span)?; - Ok(len) - } - Err(err) => { - let _ = writer.flush(); - Err(err) - } - } - } else { + if signals.is_empty() { match io::copy(&mut reader, &mut writer) { Ok(n) => { writer.flush().err_span(span)?; @@ -1044,6 +1013,21 @@ pub fn copy_with_interrupt( Err(err.into_spanned(span).into()) } } + } else { + // #[cfg(any(target_os = "linux", target_os = "android"))] + // { + // return crate::sys::kernel_copy::copy_spec(reader, writer); + // } + match generic_copy(&mut reader, &mut writer, span, signals) { + Ok(len) => { + writer.flush().err_span(span)?; + Ok(len) + } + Err(err) => { + let _ = writer.flush(); + Err(err) + } + } } } @@ -1052,14 +1036,12 @@ fn generic_copy( mut reader: impl Read, mut writer: impl Write, span: Span, - interrupt: &AtomicBool, + signals: &Signals, ) -> Result { let buf = &mut [0; DEFAULT_BUF_SIZE]; let mut len = 0; loop { - if interrupt.load(Ordering::Relaxed) { - return Err(ShellError::InterruptedByUser { span: Some(span) }); - } + signals.check(span)?; let n = match reader.read(buf) { Ok(0) => break, Ok(n) => n, @@ -1134,7 +1116,7 @@ mod tests { Chunks::new( SourceReader::Read(Box::new(reader)), Span::test_data(), - None, + Signals::empty(), type_, ) } diff --git a/crates/nu-protocol/src/pipeline/list_stream.rs b/crates/nu-protocol/src/pipeline/list_stream.rs index 117c264219..997cc3f77b 100644 --- a/crates/nu-protocol/src/pipeline/list_stream.rs +++ b/crates/nu-protocol/src/pipeline/list_stream.rs @@ -1,8 +1,5 @@ -use crate::{Config, PipelineData, ShellError, Span, Value}; -use std::{ - fmt::Debug, - sync::{atomic::AtomicBool, Arc}, -}; +use crate::{Config, PipelineData, ShellError, Signals, Span, Value}; +use std::fmt::Debug; pub type ValueIterator = Box + Send + 'static>; @@ -21,10 +18,10 @@ impl ListStream { pub fn new( iter: impl Iterator + Send + 'static, span: Span, - interrupt: Option>, + signals: Signals, ) -> Self { Self { - stream: Box::new(Interrupt::new(iter, interrupt)), + stream: Box::new(InterruptIter::new(iter, signals)), span, } } @@ -69,10 +66,10 @@ impl ListStream { /// E.g., `take`, `filter`, `step_by`, and more. /// /// ``` - /// use nu_protocol::{ListStream, Span, Value}; + /// use nu_protocol::{ListStream, Signals, Span, Value}; /// /// let span = Span::unknown(); - /// let stream = ListStream::new(std::iter::repeat(Value::int(0, span)), span, None); + /// let stream = ListStream::new(std::iter::repeat(Value::int(0, span)), span, Signals::empty()); /// let new_stream = stream.modify(|iter| iter.take(100)); /// ``` pub fn modify(self, f: impl FnOnce(ValueIterator) -> I) -> Self @@ -128,22 +125,22 @@ impl Iterator for IntoIter { } } -struct Interrupt { +struct InterruptIter { iter: I, - interrupt: Option>, + signals: Signals, } -impl Interrupt { - fn new(iter: I, interrupt: Option>) -> Self { - Self { iter, interrupt } +impl InterruptIter { + fn new(iter: I, signals: Signals) -> Self { + Self { iter, signals } } } -impl Iterator for Interrupt { +impl Iterator for InterruptIter { type Item = ::Item; fn next(&mut self) -> Option { - if nu_utils::ctrl_c::was_pressed(&self.interrupt) { + if self.signals.interrupted() { None } else { self.iter.next() diff --git a/crates/nu-protocol/src/pipeline/mod.rs b/crates/nu-protocol/src/pipeline/mod.rs index a018a084ed..525806425d 100644 --- a/crates/nu-protocol/src/pipeline/mod.rs +++ b/crates/nu-protocol/src/pipeline/mod.rs @@ -3,9 +3,11 @@ pub mod list_stream; mod metadata; mod out_dest; mod pipeline_data; +mod signals; pub use byte_stream::*; pub use list_stream::*; pub use metadata::*; pub use out_dest::*; pub use pipeline_data::*; +pub use signals::*; diff --git a/crates/nu-protocol/src/pipeline/pipeline_data.rs b/crates/nu-protocol/src/pipeline/pipeline_data.rs index c0c0bd1c33..a546e90191 100644 --- a/crates/nu-protocol/src/pipeline/pipeline_data.rs +++ b/crates/nu-protocol/src/pipeline/pipeline_data.rs @@ -3,13 +3,10 @@ use crate::{ engine::{EngineState, Stack}, process::{ChildPipe, ChildProcess, ExitStatus}, ByteStream, ByteStreamType, Config, ErrSpan, ListStream, OutDest, PipelineMetadata, Range, - ShellError, Span, Type, Value, + ShellError, Signals, Span, Type, Value, }; use nu_utils::{stderr_write_all_and_flush, stdout_write_all_and_flush}; -use std::{ - io::{Cursor, Read, Write}, - sync::{atomic::AtomicBool, Arc}, -}; +use std::io::{Cursor, Read, Write}; const LINE_ENDING_PATTERN: &[char] = &['\r', '\n']; @@ -196,19 +193,23 @@ impl PipelineData { let val_span = value.span(); match value { Value::List { vals, .. } => PipelineIteratorInner::ListStream( - ListStream::new(vals.into_iter(), val_span, None).into_iter(), + ListStream::new(vals.into_iter(), val_span, Signals::empty()).into_iter(), ), Value::Binary { val, .. } => PipelineIteratorInner::ListStream( ListStream::new( val.into_iter().map(move |x| Value::int(x as i64, val_span)), val_span, - None, + Signals::empty(), ) .into_iter(), ), Value::Range { val, .. } => PipelineIteratorInner::ListStream( - ListStream::new(val.into_range_iter(val_span, None), val_span, None) - .into_iter(), + ListStream::new( + val.into_range_iter(val_span, Signals::empty()), + val_span, + Signals::empty(), + ) + .into_iter(), ), // Propagate errors by explicitly matching them before the final case. Value::Error { error, .. } => return Err(*error), @@ -301,11 +302,7 @@ impl PipelineData { } /// Simplified mapper to help with simple values also. For full iterator support use `.into_iter()` instead - pub fn map( - self, - mut f: F, - ctrlc: Option>, - ) -> Result + pub fn map(self, mut f: F, signals: &Signals) -> Result where Self: Sized, F: FnMut(Value) -> Value + 'static + Send, @@ -314,13 +311,14 @@ impl PipelineData { PipelineData::Value(value, metadata) => { let span = value.span(); let pipeline = match value { - Value::List { vals, .. } => { - vals.into_iter().map(f).into_pipeline_data(span, ctrlc) - } - Value::Range { val, .. } => val - .into_range_iter(span, ctrlc.clone()) + Value::List { vals, .. } => vals + .into_iter() .map(f) - .into_pipeline_data(span, ctrlc), + .into_pipeline_data(span, signals.clone()), + Value::Range { val, .. } => val + .into_range_iter(span, Signals::empty()) + .map(f) + .into_pipeline_data(span, signals.clone()), value => match f(value) { Value::Error { error, .. } => return Err(*error), v => v.into_pipeline_data(), @@ -339,11 +337,7 @@ impl PipelineData { } /// Simplified flatmapper. For full iterator support use `.into_iter()` instead - pub fn flat_map( - self, - mut f: F, - ctrlc: Option>, - ) -> Result + pub fn flat_map(self, mut f: F, signals: &Signals) -> Result where Self: Sized, U: IntoIterator + 'static, @@ -355,14 +349,17 @@ impl PipelineData { PipelineData::Value(value, metadata) => { let span = value.span(); let pipeline = match value { - Value::List { vals, .. } => { - vals.into_iter().flat_map(f).into_pipeline_data(span, ctrlc) - } - Value::Range { val, .. } => val - .into_range_iter(span, ctrlc.clone()) + Value::List { vals, .. } => vals + .into_iter() .flat_map(f) - .into_pipeline_data(span, ctrlc), - value => f(value).into_iter().into_pipeline_data(span, ctrlc), + .into_pipeline_data(span, signals.clone()), + Value::Range { val, .. } => val + .into_range_iter(span, Signals::empty()) + .flat_map(f) + .into_pipeline_data(span, signals.clone()), + value => f(value) + .into_iter() + .into_pipeline_data(span, signals.clone()), }; Ok(pipeline.set_metadata(metadata)) } @@ -380,18 +377,16 @@ impl PipelineData { } Err(err) => f(Value::binary(err.into_bytes(), span)), }; - Ok(iter - .into_iter() - .into_pipeline_data_with_metadata(span, ctrlc, metadata)) + Ok(iter.into_iter().into_pipeline_data_with_metadata( + span, + signals.clone(), + metadata, + )) } } } - pub fn filter( - self, - mut f: F, - ctrlc: Option>, - ) -> Result + pub fn filter(self, mut f: F, signals: &Signals) -> Result where Self: Sized, F: FnMut(&Value) -> bool + 'static + Send, @@ -401,13 +396,14 @@ impl PipelineData { PipelineData::Value(value, metadata) => { let span = value.span(); let pipeline = match value { - Value::List { vals, .. } => { - vals.into_iter().filter(f).into_pipeline_data(span, ctrlc) - } - Value::Range { val, .. } => val - .into_range_iter(span, ctrlc.clone()) + Value::List { vals, .. } => vals + .into_iter() .filter(f) - .into_pipeline_data(span, ctrlc), + .into_pipeline_data(span, signals.clone()), + Value::Range { val, .. } => val + .into_range_iter(span, Signals::empty()) + .filter(f) + .into_pipeline_data(span, signals.clone()), value => { if f(&value) { value.into_pipeline_data() @@ -538,7 +534,8 @@ impl PipelineData { } } } - let range_values: Vec = val.into_range_iter(span, None).collect(); + let range_values: Vec = + val.into_range_iter(span, Signals::empty()).collect(); Ok(PipelineData::Value(Value::list(range_values, span), None)) } x => Ok(PipelineData::Value(x, metadata)), @@ -638,10 +635,15 @@ impl IntoIterator for PipelineData { let span = value.span(); match value { Value::List { vals, .. } => PipelineIteratorInner::ListStream( - ListStream::new(vals.into_iter(), span, None).into_iter(), + ListStream::new(vals.into_iter(), span, Signals::empty()).into_iter(), ), Value::Range { val, .. } => PipelineIteratorInner::ListStream( - ListStream::new(val.into_range_iter(span, None), span, None).into_iter(), + ListStream::new( + val.into_range_iter(span, Signals::empty()), + span, + Signals::empty(), + ) + .into_iter(), ), x => PipelineIteratorInner::Value(x), } @@ -703,11 +705,11 @@ where } pub trait IntoInterruptiblePipelineData { - fn into_pipeline_data(self, span: Span, ctrlc: Option>) -> PipelineData; + fn into_pipeline_data(self, span: Span, signals: Signals) -> PipelineData; fn into_pipeline_data_with_metadata( self, span: Span, - ctrlc: Option>, + signals: Signals, metadata: impl Into>, ) -> PipelineData; } @@ -718,18 +720,18 @@ where I::IntoIter: Send + 'static, ::Item: Into, { - fn into_pipeline_data(self, span: Span, ctrlc: Option>) -> PipelineData { - ListStream::new(self.into_iter().map(Into::into), span, ctrlc).into() + fn into_pipeline_data(self, span: Span, signals: Signals) -> PipelineData { + ListStream::new(self.into_iter().map(Into::into), span, signals).into() } fn into_pipeline_data_with_metadata( self, span: Span, - ctrlc: Option>, + signals: Signals, metadata: impl Into>, ) -> PipelineData { PipelineData::ListStream( - ListStream::new(self.into_iter().map(Into::into), span, ctrlc), + ListStream::new(self.into_iter().map(Into::into), span, signals), metadata.into(), ) } diff --git a/crates/nu-protocol/src/pipeline/signals.rs b/crates/nu-protocol/src/pipeline/signals.rs new file mode 100644 index 0000000000..06ce583c82 --- /dev/null +++ b/crates/nu-protocol/src/pipeline/signals.rs @@ -0,0 +1,76 @@ +use crate::{ShellError, Span}; +use std::sync::{ + atomic::{AtomicBool, Ordering}, + Arc, +}; + +/// Used to check for signals to suspend or terminate the execution of Nushell code. +/// +/// For now, this struct only supports interruption (ctrl+c or SIGINT). +#[derive(Debug, Clone)] +pub struct Signals { + signals: Option>, +} + +impl Signals { + /// A [`Signals`] that is not hooked up to any event/signals source. + /// + /// So, this [`Signals`] will never be interrupted. + pub const EMPTY: Self = Signals { signals: None }; + + /// Create a new [`Signals`] with `ctrlc` as the interrupt source. + /// + /// Once `ctrlc` is set to `true`, [`check`](Self::check) will error + /// and [`interrupted`](Self::interrupted) will return `true`. + pub fn new(ctrlc: Arc) -> Self { + Self { + signals: Some(ctrlc), + } + } + + /// Create a [`Signals`] that is not hooked up to any event/signals source. + /// + /// So, the returned [`Signals`] will never be interrupted. + /// + /// This should only be used in test code, or if the stream/iterator being created + /// already has an underlying [`Signals`]. + pub const fn empty() -> Self { + Self::EMPTY + } + + /// Returns an `Err` if an interrupt has been triggered. + /// + /// Otherwise, returns `Ok`. + #[inline] + pub fn check(&self, span: Span) -> Result<(), ShellError> { + #[inline] + #[cold] + fn interrupt_error(span: Span) -> Result<(), ShellError> { + Err(ShellError::Interrupted { span }) + } + + if self.interrupted() { + interrupt_error(span) + } else { + Ok(()) + } + } + + /// Returns whether an interrupt has been triggered. + #[inline] + pub fn interrupted(&self) -> bool { + self.signals + .as_deref() + .is_some_and(|b| b.load(Ordering::Relaxed)) + } + + pub(crate) fn is_empty(&self) -> bool { + self.signals.is_none() + } + + pub(crate) fn reset(&self) { + if let Some(signals) = &self.signals { + signals.store(false, Ordering::Relaxed); + } + } +} diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index df030ad3e9..fc618d6341 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -23,7 +23,7 @@ use crate::{ ast::{Bits, Boolean, CellPath, Comparison, Math, Operator, PathMember}, did_you_mean, engine::{Closure, EngineState}, - Config, ShellError, Span, Type, + Config, ShellError, Signals, Span, Type, }; use chrono::{DateTime, Datelike, FixedOffset, Locale, TimeZone}; use chrono_humanize::HumanTime; @@ -1017,8 +1017,9 @@ impl Value { } } Value::Range { ref val, .. } => { - if let Some(item) = - val.into_range_iter(current.span(), None).nth(*count) + if let Some(item) = val + .into_range_iter(current.span(), Signals::empty()) + .nth(*count) { current = item; } else if *optional { diff --git a/crates/nu-protocol/src/value/range.rs b/crates/nu-protocol/src/value/range.rs index 5b1fa32ec7..6ec7ce16ad 100644 --- a/crates/nu-protocol/src/value/range.rs +++ b/crates/nu-protocol/src/value/range.rs @@ -1,25 +1,13 @@ //! A Range is an iterator over integers or floats. +use crate::{ast::RangeInclusion, ShellError, Signals, Span, Value}; use serde::{Deserialize, Serialize}; -use std::{ - cmp::Ordering, - fmt::Display, - sync::{atomic::AtomicBool, Arc}, -}; - -use crate::{ast::RangeInclusion, ShellError, Span, Value}; +use std::{cmp::Ordering, fmt::Display}; mod int_range { - use std::{ - cmp::Ordering, - fmt::Display, - ops::Bound, - sync::{atomic::AtomicBool, Arc}, - }; - + use crate::{ast::RangeInclusion, ShellError, Signals, Span, Value}; use serde::{Deserialize, Serialize}; - - use crate::{ast::RangeInclusion, ShellError, Span, Value}; + use std::{cmp::Ordering, fmt::Display, ops::Bound}; #[derive(Debug, Clone, Copy, Serialize, Deserialize)] pub struct IntRange { @@ -123,12 +111,12 @@ mod int_range { } } - pub fn into_range_iter(self, ctrlc: Option>) -> Iter { + pub fn into_range_iter(self, signals: Signals) -> Iter { Iter { current: Some(self.start), step: self.step, end: self.end, - ctrlc, + signals, } } } @@ -202,7 +190,7 @@ mod int_range { current: Option, step: i64, end: Bound, - ctrlc: Option>, + signals: Signals, } impl Iterator for Iter { @@ -218,7 +206,7 @@ mod int_range { (_, Bound::Unbounded) => true, // will stop once integer overflows }; - if not_end && !nu_utils::ctrl_c::was_pressed(&self.ctrlc) { + if not_end && !self.signals.interrupted() { self.current = current.checked_add(self.step); Some(current) } else { @@ -233,16 +221,9 @@ mod int_range { } mod float_range { - use std::{ - cmp::Ordering, - fmt::Display, - ops::Bound, - sync::{atomic::AtomicBool, Arc}, - }; - + use crate::{ast::RangeInclusion, IntRange, Range, ShellError, Signals, Span, Value}; use serde::{Deserialize, Serialize}; - - use crate::{ast::RangeInclusion, IntRange, Range, ShellError, Span, Value}; + use std::{cmp::Ordering, fmt::Display, ops::Bound}; #[derive(Debug, Clone, Copy, Serialize, Deserialize)] pub struct FloatRange { @@ -365,13 +346,13 @@ mod float_range { } } - pub fn into_range_iter(self, ctrlc: Option>) -> Iter { + pub fn into_range_iter(self, signals: Signals) -> Iter { Iter { start: self.start, step: self.step, end: self.end, iter: Some(0), - ctrlc, + signals, } } } @@ -477,7 +458,7 @@ mod float_range { step: f64, end: Bound, iter: Option, - ctrlc: Option>, + signals: Signals, } impl Iterator for Iter { @@ -495,7 +476,7 @@ mod float_range { (_, Bound::Unbounded) => current.is_finite(), }; - if not_end && !nu_utils::ctrl_c::was_pressed(&self.ctrlc) { + if not_end && !self.signals.interrupted() { self.iter = iter.checked_add(1); Some(current) } else { @@ -549,10 +530,10 @@ impl Range { } } - pub fn into_range_iter(self, span: Span, ctrlc: Option>) -> Iter { + pub fn into_range_iter(self, span: Span, signals: Signals) -> Iter { match self { - Range::IntRange(range) => Iter::IntIter(range.into_range_iter(ctrlc), span), - Range::FloatRange(range) => Iter::FloatIter(range.into_range_iter(ctrlc), span), + Range::IntRange(range) => Iter::IntIter(range.into_range_iter(signals), span), + Range::FloatRange(range) => Iter::FloatIter(range.into_range_iter(signals), span), } } } diff --git a/crates/nu-table/src/types/expanded.rs b/crates/nu-table/src/types/expanded.rs index 9472cc2846..668e5ea25c 100644 --- a/crates/nu-table/src/types/expanded.rs +++ b/crates/nu-table/src/types/expanded.rs @@ -102,9 +102,7 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult { } for (row, item) in input.iter().enumerate() { - if nu_utils::ctrl_c::was_pressed(&cfg.opts.ctrlc) { - return Ok(None); - } + cfg.opts.signals.check(cfg.opts.span)?; if let Value::Error { error, .. } = item { return Err(*error.clone()); @@ -143,9 +141,7 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult { } for (row, item) in input.iter().enumerate() { - if nu_utils::ctrl_c::was_pressed(&cfg.opts.ctrlc) { - return Ok(None); - } + cfg.opts.signals.check(cfg.opts.span)?; if let Value::Error { error, .. } = item { return Err(*error.clone()); @@ -230,9 +226,7 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult { } for (row, item) in input.iter().enumerate() { - if nu_utils::ctrl_c::was_pressed(&cfg.opts.ctrlc) { - return Ok(None); - } + cfg.opts.signals.check(cfg.opts.span)?; if let Value::Error { error, .. } = item { return Err(*error.clone()); @@ -353,9 +347,7 @@ fn expanded_table_kv(record: &Record, cfg: Cfg<'_>) -> StringResult { let mut data = Vec::with_capacity(record.len()); for (key, value) in record { - if nu_utils::ctrl_c::was_pressed(&cfg.opts.ctrlc) { - return Ok(None); - } + cfg.opts.signals.check(cfg.opts.span)?; let (value, is_expanded) = match expand_table_value(value, value_width, &cfg)? { Some(val) => val, diff --git a/crates/nu-table/src/types/general.rs b/crates/nu-table/src/types/general.rs index 4a6cd302e8..66048267d7 100644 --- a/crates/nu-table/src/types/general.rs +++ b/crates/nu-table/src/types/general.rs @@ -43,9 +43,7 @@ fn create_table(input: &[Value], opts: TableOpts<'_>) -> Result, fn kv_table(record: &Record, opts: TableOpts<'_>) -> StringResult { let mut data = vec![Vec::with_capacity(2); record.len()]; for ((column, value), row) in record.iter().zip(data.iter_mut()) { - if nu_utils::ctrl_c::was_pressed(&opts.ctrlc) { - return Ok(None); - } + opts.signals.check(opts.span)?; let value = nu_value_to_string_colored(value, opts.config, opts.style_computer); @@ -123,9 +121,7 @@ fn to_table_with_header( } for (row, item) in input.iter().enumerate() { - if nu_utils::ctrl_c::was_pressed(&opts.ctrlc) { - return Ok(None); - } + opts.signals.check(opts.span)?; if let Value::Error { error, .. } = item { return Err(*error.clone()); @@ -158,9 +154,7 @@ fn to_table_with_no_header( table.set_index_style(get_index_style(opts.style_computer)); for (row, item) in input.iter().enumerate() { - if nu_utils::ctrl_c::was_pressed(&opts.ctrlc) { - return Ok(None); - } + opts.signals.check(opts.span)?; if let Value::Error { error, .. } = item { return Err(*error.clone()); diff --git a/crates/nu-table/src/types/mod.rs b/crates/nu-table/src/types/mod.rs index 10289c3b81..163e4b0c81 100644 --- a/crates/nu-table/src/types/mod.rs +++ b/crates/nu-table/src/types/mod.rs @@ -8,8 +8,7 @@ pub use general::JustTable; use crate::{common::INDEX_COLUMN_NAME, NuTable}; use nu_color_config::StyleComputer; -use nu_protocol::{Config, Span, TableIndexMode, TableMode}; -use std::sync::{atomic::AtomicBool, Arc}; +use nu_protocol::{Config, Signals, Span, TableIndexMode, TableMode}; pub struct TableOutput { pub table: NuTable, @@ -29,7 +28,7 @@ impl TableOutput { #[derive(Debug, Clone)] pub struct TableOpts<'a> { - ctrlc: Option>, + signals: &'a Signals, config: &'a Config, style_computer: &'a StyleComputer<'a>, span: Span, @@ -45,7 +44,7 @@ impl<'a> TableOpts<'a> { pub fn new( config: &'a Config, style_computer: &'a StyleComputer<'a>, - ctrlc: Option>, + signals: &'a Signals, span: Span, width: usize, indent: (usize, usize), @@ -54,7 +53,7 @@ impl<'a> TableOpts<'a> { index_remove: bool, ) -> Self { Self { - ctrlc, + signals, config, style_computer, span, diff --git a/crates/nu-utils/src/ctrl_c.rs b/crates/nu-utils/src/ctrl_c.rs deleted file mode 100644 index 72719139ad..0000000000 --- a/crates/nu-utils/src/ctrl_c.rs +++ /dev/null @@ -1,13 +0,0 @@ -use std::sync::{ - atomic::{AtomicBool, Ordering}, - Arc, -}; - -/// Returns true if Nu has received a SIGINT signal / ctrl+c event -pub fn was_pressed(ctrlc: &Option>) -> bool { - if let Some(ctrlc) = ctrlc { - ctrlc.load(Ordering::SeqCst) - } else { - false - } -} diff --git a/crates/nu-utils/src/lib.rs b/crates/nu-utils/src/lib.rs index f1a915290b..cb834e5e0f 100644 --- a/crates/nu-utils/src/lib.rs +++ b/crates/nu-utils/src/lib.rs @@ -1,5 +1,4 @@ mod casing; -pub mod ctrl_c; mod deansi; pub mod emoji; pub mod filesystem; diff --git a/crates/nu_plugin_example/src/commands/collect_bytes.rs b/crates/nu_plugin_example/src/commands/collect_bytes.rs index 398a1de4b1..e716493785 100644 --- a/crates/nu_plugin_example/src/commands/collect_bytes.rs +++ b/crates/nu_plugin_example/src/commands/collect_bytes.rs @@ -1,7 +1,7 @@ use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand}; use nu_protocol::{ - ByteStream, ByteStreamType, Category, Example, LabeledError, PipelineData, Signature, Type, - Value, + ByteStream, ByteStreamType, Category, Example, LabeledError, PipelineData, Signals, Signature, + Type, Value, }; use crate::ExamplePlugin; @@ -52,7 +52,7 @@ impl PluginCommand for CollectBytes { ByteStream::from_result_iter( input.into_iter().map(Value::coerce_into_binary), call.head, - None, + Signals::empty(), ByteStreamType::Unknown, ), None, diff --git a/crates/nu_plugin_example/src/commands/generate.rs b/crates/nu_plugin_example/src/commands/generate.rs index 9938518f4c..c039cc23d5 100644 --- a/crates/nu_plugin_example/src/commands/generate.rs +++ b/crates/nu_plugin_example/src/commands/generate.rs @@ -1,7 +1,7 @@ use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand}; use nu_protocol::{ - Category, Example, IntoInterruptiblePipelineData, LabeledError, PipelineData, Signature, - SyntaxShape, Type, Value, + Category, Example, IntoInterruptiblePipelineData, LabeledError, PipelineData, Signals, + Signature, SyntaxShape, Type, Value, }; use crate::ExamplePlugin; @@ -85,7 +85,7 @@ impl PluginCommand for Generate { }) .map(|result| result.unwrap_or_else(|err| Value::error(err, head))) }) - .into_pipeline_data(head, None)) + .into_pipeline_data(head, Signals::empty())) } } diff --git a/crates/nu_plugin_example/src/commands/seq.rs b/crates/nu_plugin_example/src/commands/seq.rs index 5652b3ef9a..88254394c1 100644 --- a/crates/nu_plugin_example/src/commands/seq.rs +++ b/crates/nu_plugin_example/src/commands/seq.rs @@ -1,6 +1,7 @@ use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand}; use nu_protocol::{ - Category, Example, LabeledError, ListStream, PipelineData, Signature, SyntaxShape, Type, Value, + Category, Example, LabeledError, ListStream, PipelineData, Signals, Signature, SyntaxShape, + Type, Value, }; use crate::ExamplePlugin; @@ -54,7 +55,7 @@ impl PluginCommand for Seq { let first: i64 = call.req(0)?; let last: i64 = call.req(1)?; let iter = (first..=last).map(move |number| Value::int(number, head)); - Ok(ListStream::new(iter, head, None).into()) + Ok(ListStream::new(iter, head, Signals::empty()).into()) } } diff --git a/src/main.rs b/src/main.rs index 6eb65276b9..2c0c4d5e4b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -32,11 +32,7 @@ use nu_std::load_standard_library; use nu_utils::perf; use run::{run_commands, run_file, run_repl}; use signals::ctrlc_protection; -use std::{ - path::PathBuf, - str::FromStr, - sync::{atomic::AtomicBool, Arc}, -}; +use std::{path::PathBuf, str::FromStr, sync::Arc}; fn get_engine_state() -> EngineState { let engine_state = nu_cmd_lang::create_default_context(); @@ -74,9 +70,8 @@ fn main() -> Result<()> { report_error_new(&engine_state, &err); } - let ctrlc = Arc::new(AtomicBool::new(false)); // TODO: make this conditional in the future - ctrlc_protection(&mut engine_state, &ctrlc); + ctrlc_protection(&mut engine_state); // Begin: Default NU_LIB_DIRS, NU_PLUGIN_DIRS // Set default NU_LIB_DIRS and NU_PLUGIN_DIRS here before the env.nu is processed. If @@ -397,7 +392,7 @@ fn main() -> Result<()> { ); } - LanguageServer::initialize_stdio_connection()?.serve_requests(engine_state, ctrlc)? + LanguageServer::initialize_stdio_connection()?.serve_requests(engine_state)? } else if let Some(commands) = parsed_nu_cli_args.commands.clone() { run_commands( &mut engine_state, diff --git a/src/signals.rs b/src/signals.rs index 9247ccb095..bb3539b95e 100644 --- a/src/signals.rs +++ b/src/signals.rs @@ -1,17 +1,14 @@ -use nu_protocol::engine::EngineState; +use nu_protocol::{engine::EngineState, Signals}; use std::sync::{ atomic::{AtomicBool, Ordering}, Arc, }; -pub(crate) fn ctrlc_protection(engine_state: &mut EngineState, ctrlc: &Arc) { - let handler_ctrlc = ctrlc.clone(); - let engine_state_ctrlc = ctrlc.clone(); - +pub(crate) fn ctrlc_protection(engine_state: &mut EngineState) { + let interrupt = Arc::new(AtomicBool::new(false)); + engine_state.set_signals(Signals::new(interrupt.clone())); ctrlc::set_handler(move || { - handler_ctrlc.store(true, Ordering::SeqCst); + interrupt.store(true, Ordering::Relaxed); }) .expect("Error setting Ctrl-C handler"); - - engine_state.ctrlc = Some(engine_state_ctrlc); } From e98b2ceb8c668fed6073373a445456a29115a206 Mon Sep 17 00:00:00 2001 From: Ian Manske Date: Tue, 9 Jul 2024 09:25:23 +0000 Subject: [PATCH 010/126] Path migration 1 (#13309) # Description Part 1 of replacing `std::path` types with `nu_path` types added in #13115. --- crates/nu-cli/src/repl.rs | 1 + crates/nu-cmd-base/src/util.rs | 2 + .../nu-command/src/env/config/config_env.rs | 7 ++- crates/nu-command/src/env/config/config_nu.rs | 7 ++- crates/nu-command/src/filesystem/cd.rs | 6 +- crates/nu-command/src/path/type.rs | 8 +-- crates/nu-command/src/system/exec.rs | 2 +- crates/nu-command/src/system/run_external.rs | 4 +- crates/nu-command/src/viewers/griddle.rs | 6 +- crates/nu-engine/src/eval.rs | 7 ++- crates/nu-protocol/src/engine/engine_state.rs | 55 +++++++------------ 11 files changed, 51 insertions(+), 54 deletions(-) diff --git a/crates/nu-cli/src/repl.rs b/crates/nu-cli/src/repl.rs index 5b0db96741..7099b70ba8 100644 --- a/crates/nu-cli/src/repl.rs +++ b/crates/nu-cli/src/repl.rs @@ -336,6 +336,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) { .with_cwd(Some( engine_state .cwd(None) + .map(|cwd| cwd.into_std_path_buf()) .unwrap_or_default() .to_string_lossy() .to_string(), diff --git a/crates/nu-cmd-base/src/util.rs b/crates/nu-cmd-base/src/util.rs index 905c990a9e..e485243877 100644 --- a/crates/nu-cmd-base/src/util.rs +++ b/crates/nu-cmd-base/src/util.rs @@ -1,3 +1,4 @@ +use nu_path::AbsolutePathBuf; use nu_protocol::{ engine::{EngineState, Stack}, Range, ShellError, Span, Value, @@ -15,6 +16,7 @@ pub fn get_init_cwd() -> PathBuf { pub fn get_guaranteed_cwd(engine_state: &EngineState, stack: &Stack) -> PathBuf { engine_state .cwd(Some(stack)) + .map(AbsolutePathBuf::into_std_path_buf) .unwrap_or(crate::util::get_init_cwd()) } diff --git a/crates/nu-command/src/env/config/config_env.rs b/crates/nu-command/src/env/config/config_env.rs index 32bd7bba90..777b6f3aac 100644 --- a/crates/nu-command/src/env/config/config_env.rs +++ b/crates/nu-command/src/env/config/config_env.rs @@ -60,12 +60,13 @@ impl Command for ConfigEnv { let (editor_name, editor_args) = get_editor(engine_state, stack, call.head)?; let paths = nu_engine::env::path_str(engine_state, stack, call.head)?; let cwd = engine_state.cwd(Some(stack))?; - let editor_executable = - crate::which(&editor_name, &paths, &cwd).ok_or(ShellError::ExternalCommand { + let editor_executable = crate::which(&editor_name, &paths, cwd.as_ref()).ok_or( + ShellError::ExternalCommand { label: format!("`{editor_name}` not found"), help: "Failed to find the editor executable".into(), span: call.head, - })?; + }, + )?; let Some(env_path) = engine_state.get_config_path("env-path") else { return Err(ShellError::GenericError { diff --git a/crates/nu-command/src/env/config/config_nu.rs b/crates/nu-command/src/env/config/config_nu.rs index 08695ca5bb..80f0bbc012 100644 --- a/crates/nu-command/src/env/config/config_nu.rs +++ b/crates/nu-command/src/env/config/config_nu.rs @@ -64,12 +64,13 @@ impl Command for ConfigNu { let (editor_name, editor_args) = get_editor(engine_state, stack, call.head)?; let paths = nu_engine::env::path_str(engine_state, stack, call.head)?; let cwd = engine_state.cwd(Some(stack))?; - let editor_executable = - crate::which(&editor_name, &paths, &cwd).ok_or(ShellError::ExternalCommand { + let editor_executable = crate::which(&editor_name, &paths, cwd.as_ref()).ok_or( + ShellError::ExternalCommand { label: format!("`{editor_name}` not found"), help: "Failed to find the editor executable".into(), span: call.head, - })?; + }, + )?; let Some(config_path) = engine_state.get_config_path("config-path") else { return Err(ShellError::GenericError { diff --git a/crates/nu-command/src/filesystem/cd.rs b/crates/nu-command/src/filesystem/cd.rs index 4e09d900d2..23faf6f67b 100644 --- a/crates/nu-command/src/filesystem/cd.rs +++ b/crates/nu-command/src/filesystem/cd.rs @@ -1,5 +1,6 @@ use nu_cmd_base::util::get_init_cwd; use nu_engine::command_prelude::*; +use nu_path::AbsolutePathBuf; use nu_utils::filesystem::{have_permission, PermissionResult}; #[derive(Clone)] @@ -43,7 +44,10 @@ impl Command for Cd { // If getting PWD failed, default to the initial directory. This way, the // user can use `cd` to recover PWD to a good state. - let cwd = engine_state.cwd(Some(stack)).unwrap_or(get_init_cwd()); + let cwd = engine_state + .cwd(Some(stack)) + .map(AbsolutePathBuf::into_std_path_buf) + .unwrap_or(get_init_cwd()); let path_val = { if let Some(path) = path_val { diff --git a/crates/nu-command/src/path/type.rs b/crates/nu-command/src/path/type.rs index bf66c8cb3b..010fd295ad 100644 --- a/crates/nu-command/src/path/type.rs +++ b/crates/nu-command/src/path/type.rs @@ -1,13 +1,11 @@ use super::PathSubcommandArguments; use nu_engine::command_prelude::*; +use nu_path::AbsolutePathBuf; use nu_protocol::engine::StateWorkingSet; -use std::{ - io, - path::{Path, PathBuf}, -}; +use std::{io, path::Path}; struct Arguments { - pwd: PathBuf, + pwd: AbsolutePathBuf, } impl PathSubcommandArguments for Arguments {} diff --git a/crates/nu-command/src/system/exec.rs b/crates/nu-command/src/system/exec.rs index 35ab9428be..018017cfb2 100644 --- a/crates/nu-command/src/system/exec.rs +++ b/crates/nu-command/src/system/exec.rs @@ -39,7 +39,7 @@ On Windows based systems, Nushell will wait for the command to finish and then e let name: Spanned = call.req(engine_state, stack, 0)?; let executable = { let paths = nu_engine::env::path_str(engine_state, stack, call.head)?; - let Some(executable) = crate::which(&name.item, &paths, &cwd) else { + let Some(executable) = crate::which(&name.item, &paths, cwd.as_ref()) else { return Err(crate::command_not_found( &name.item, call.head, diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs index e475833747..06bc5a69ca 100644 --- a/crates/nu-command/src/system/run_external.rs +++ b/crates/nu-command/src/system/run_external.rs @@ -79,7 +79,7 @@ impl Command for External { // Determine the PATH to be used and then use `which` to find it - though this has no // effect if it's an absolute path already let paths = nu_engine::env::path_str(engine_state, stack, call.head)?; - let Some(executable) = which(expanded_name, &paths, &cwd) else { + let Some(executable) = which(expanded_name, &paths, cwd.as_ref()) else { return Err(command_not_found(&name_str, call.head, engine_state, stack)); }; executable @@ -228,7 +228,7 @@ pub fn eval_arguments_from_call( match arg { // Expand globs passed to run-external Value::Glob { val, no_expand, .. } if !no_expand => args.extend( - expand_glob(&val, &cwd, expr.span, engine_state.signals())? + expand_glob(&val, cwd.as_ref(), expr.span, engine_state.signals())? .into_iter() .map(|s| s.into_spanned(expr.span)), ), diff --git a/crates/nu-command/src/viewers/griddle.rs b/crates/nu-command/src/viewers/griddle.rs index 8b39bf5649..cd73c1ca71 100644 --- a/crates/nu-command/src/viewers/griddle.rs +++ b/crates/nu-command/src/viewers/griddle.rs @@ -83,7 +83,7 @@ prints out the list properly."# separator_param, env_str, use_grid_icons, - &cwd, + cwd.as_ref(), )?) } else { Ok(PipelineData::empty()) @@ -101,7 +101,7 @@ prints out the list properly."# separator_param, env_str, use_grid_icons, - &cwd, + cwd.as_ref(), )?) } else { // dbg!(data); @@ -124,7 +124,7 @@ prints out the list properly."# separator_param, env_str, use_grid_icons, - &cwd, + cwd.as_ref(), )?) } x => { diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index 1460dec464..b495589011 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -1,6 +1,6 @@ #[allow(deprecated)] use crate::{current_dir, get_config, get_full_help}; -use nu_path::expand_path_with; +use nu_path::{expand_path_with, AbsolutePathBuf}; use nu_protocol::{ ast::{ Assignment, Block, Call, Expr, Expression, ExternalArgument, PathMember, PipelineElement, @@ -679,7 +679,10 @@ impl Eval for EvalRuntime { } else if quoted { Ok(Value::string(path, span)) } else { - let cwd = engine_state.cwd(Some(stack)).unwrap_or_default(); + let cwd = engine_state + .cwd(Some(stack)) + .map(AbsolutePathBuf::into_std_path_buf) + .unwrap_or_default(); let path = expand_path_with(path, cwd, true); Ok(Value::string(path.to_string_lossy(), span)) diff --git a/crates/nu-protocol/src/engine/engine_state.rs b/crates/nu-protocol/src/engine/engine_state.rs index e393d78d41..d45fbafb88 100644 --- a/crates/nu-protocol/src/engine/engine_state.rs +++ b/crates/nu-protocol/src/engine/engine_state.rs @@ -12,6 +12,7 @@ use crate::{ }; use fancy_regex::Regex; use lru::LruCache; +use nu_path::AbsolutePathBuf; use std::{ collections::HashMap, num::NonZeroUsize, @@ -922,58 +923,44 @@ impl EngineState { /// /// If `stack` is supplied, also considers modifications to the working /// directory on the stack that have yet to be merged into the engine state. - pub fn cwd(&self, stack: Option<&Stack>) -> Result { + pub fn cwd(&self, stack: Option<&Stack>) -> Result { // Helper function to create a simple generic error. - fn error(msg: &str, cwd: impl AsRef) -> Result { - Err(ShellError::GenericError { + fn error(msg: &str, cwd: impl AsRef) -> ShellError { + ShellError::GenericError { error: msg.into(), msg: format!("$env.PWD = {}", cwd.as_ref().display()), span: None, help: Some("Use `cd` to reset $env.PWD into a good state".into()), inner: vec![], - }) - } - - // Helper function to check if a path is a root path. - fn is_root(path: &Path) -> bool { - path.parent().is_none() - } - - // Helper function to check if a path has trailing slashes. - fn has_trailing_slash(path: &Path) -> bool { - nu_path::components(path).last() - == Some(std::path::Component::Normal(std::ffi::OsStr::new(""))) + } } // Retrieve $env.PWD from the stack or the engine state. let pwd = if let Some(stack) = stack { stack.get_env_var(self, "PWD") } else { - self.get_env_var("PWD").map(ToOwned::to_owned) + self.get_env_var("PWD").cloned() }; - if let Some(pwd) = pwd { - if let Value::String { val, .. } = pwd { - let path = PathBuf::from(val); + let pwd = pwd.ok_or_else(|| error("$env.PWD not found", ""))?; - // Technically, a root path counts as "having trailing slashes", but - // for the purpose of PWD, a root path is acceptable. - if !is_root(&path) && has_trailing_slash(&path) { - error("$env.PWD contains trailing slashes", path) - } else if !path.is_absolute() { - error("$env.PWD is not an absolute path", path) - } else if !path.exists() { - error("$env.PWD points to a non-existent directory", path) - } else if !path.is_dir() { - error("$env.PWD points to a non-directory", path) - } else { - Ok(path) - } + if let Value::String { val, .. } = pwd { + let path = AbsolutePathBuf::try_from(val) + .map_err(|path| error("$env.PWD is not an absolute path", path))?; + + // Technically, a root path counts as "having trailing slashes", but + // for the purpose of PWD, a root path is acceptable. + if path.parent().is_some() && nu_path::has_trailing_slash(path.as_ref()) { + Err(error("$env.PWD contains trailing slashes", &path)) + } else if !path.exists() { + Err(error("$env.PWD points to a non-existent directory", &path)) + } else if !path.is_dir() { + Err(error("$env.PWD points to a non-directory", &path)) } else { - error("$env.PWD is not a string", format!("{pwd:?}")) + Ok(path) } } else { - error("$env.PWD not found", "") + Err(error("$env.PWD is not a string", format!("{pwd:?}"))) } } From 1964dacaefefdf99e6c2ea8e5f907e625f872727 Mon Sep 17 00:00:00 2001 From: Wind Date: Tue, 9 Jul 2024 20:11:25 +0800 Subject: [PATCH 011/126] Raise error when using `o>|` pipe (#13323) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description From the feedbacks from @amtoine , it's good to make nushell shows error for `o>|` syntax. # User-Facing Changes ## Before ```nushell 'foo' o>| print 07/09/2024 06:44:23 AM Error: nu::parser::parse_mismatch × Parse mismatch during operation. ╭─[entry #6:1:9] 1 │ 'foo' o>| print · ┬ · ╰── expected redirection target ``` ## After ```nushell 'foo' o>| print 07/09/2024 06:47:26 AM Error: nu::parser::parse_mismatch × Parse mismatch during operation. ╭─[entry #1:1:7] 1 │ 'foo' o>| print · ─┬─ · ╰── expected `|`. Redirection stdout to pipe is the same as piping directly. ╰──── ``` # Tests + Formatting Added one test --------- Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com> --- crates/nu-parser/src/lex.rs | 53 ++++++++++++++++------- tests/shell/pipeline/commands/internal.rs | 8 ++++ 2 files changed, 46 insertions(+), 15 deletions(-) diff --git a/crates/nu-parser/src/lex.rs b/crates/nu-parser/src/lex.rs index 4639cae25d..9b52467ef4 100644 --- a/crates/nu-parser/src/lex.rs +++ b/crates/nu-parser/src/lex.rs @@ -580,7 +580,10 @@ fn lex_internal( // If the next character is non-newline whitespace, skip it. curr_offset += 1; } else { - let token = try_lex_special_piped_item(input, &mut curr_offset, span_offset); + let (token, err) = try_lex_special_piped_item(input, &mut curr_offset, span_offset); + if error.is_none() { + error = err; + } if let Some(token) = token { output.push(token); is_complete = false; @@ -614,40 +617,60 @@ fn try_lex_special_piped_item( input: &[u8], curr_offset: &mut usize, span_offset: usize, -) -> Option { +) -> (Option, Option) { let c = input[*curr_offset]; let e_pipe_len = 3; let eo_pipe_len = 5; + let o_pipe_len = 3; let offset = *curr_offset; if c == b'e' { // expect `e>|` if (offset + e_pipe_len <= input.len()) && (&input[offset..offset + e_pipe_len] == b"e>|") { *curr_offset += e_pipe_len; - return Some(Token::new( - TokenContents::ErrGreaterPipe, - Span::new(span_offset + offset, span_offset + offset + e_pipe_len), - )); + return ( + Some(Token::new( + TokenContents::ErrGreaterPipe, + Span::new(span_offset + offset, span_offset + offset + e_pipe_len), + )), + None, + ); } if (offset + eo_pipe_len <= input.len()) && (&input[offset..offset + eo_pipe_len] == b"e+o>|") { *curr_offset += eo_pipe_len; - return Some(Token::new( - TokenContents::OutErrGreaterPipe, - Span::new(span_offset + offset, span_offset + offset + eo_pipe_len), - )); + return ( + Some(Token::new( + TokenContents::OutErrGreaterPipe, + Span::new(span_offset + offset, span_offset + offset + eo_pipe_len), + )), + None, + ); } } else if c == b'o' { + // indicates an error if user happened to type `o>|` + if offset + o_pipe_len <= input.len() && (&input[offset..offset + o_pipe_len] == b"o>|") { + return ( + None, + Some(ParseError::Expected( + "`|`. Redirecting stdout to a pipe is the same as normal piping.", + Span::new(span_offset + offset, span_offset + offset + o_pipe_len), + )), + ); + } // it can be the following case: `o+e>|` if (offset + eo_pipe_len <= input.len()) && (&input[offset..offset + eo_pipe_len] == b"o+e>|") { *curr_offset += eo_pipe_len; - return Some(Token::new( - TokenContents::OutErrGreaterPipe, - Span::new(span_offset + offset, span_offset + offset + eo_pipe_len), - )); + return ( + Some(Token::new( + TokenContents::OutErrGreaterPipe, + Span::new(span_offset + offset, span_offset + offset + eo_pipe_len), + )), + None, + ); } } - None + (None, None) } diff --git a/tests/shell/pipeline/commands/internal.rs b/tests/shell/pipeline/commands/internal.rs index 84f0bf146f..59c9f31fce 100644 --- a/tests/shell/pipeline/commands/internal.rs +++ b/tests/shell/pipeline/commands/internal.rs @@ -1161,3 +1161,11 @@ fn command_not_found_error_shows_not_found_2() { && actual.err.contains("Did you mean `for`?") ); } + +#[test] +fn error_on_out_greater_pipe() { + let actual = nu!(r#""foo" o>| print"#); + assert!(actual + .err + .contains("Redirecting stdout to a pipe is the same as normal piping")) +} From 4cdceca1f716d4c7d8a815365f6052ecbe3f7d19 Mon Sep 17 00:00:00 2001 From: Maxim Zhiburt Date: Tue, 9 Jul 2024 17:49:04 +0300 Subject: [PATCH 012/126] Fix kv table width issue with header_on_border configuration (#13325) GOOD CATCH............................................................. SORRY I've added a test to catch regression just in case. close #13319 cc: @fdncred --- crates/nu-command/tests/commands/table.rs | 6 ++++++ crates/nu-table/src/table.rs | 14 ++++++++++---- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/crates/nu-command/tests/commands/table.rs b/crates/nu-command/tests/commands/table.rs index 9c4f34ebfd..16d5eb54ab 100644 --- a/crates/nu-command/tests/commands/table.rs +++ b/crates/nu-command/tests/commands/table.rs @@ -2889,3 +2889,9 @@ fn table_list() { let actual = nu!("table --list --theme basic"); assert_eq!(actual.out, "╭────┬────────────────╮│ 0 │ basic ││ 1 │ compact ││ 2 │ compact_double ││ 3 │ default ││ 4 │ heavy ││ 5 │ light ││ 6 │ none ││ 7 │ reinforced ││ 8 │ rounded ││ 9 │ thin ││ 10 │ with_love ││ 11 │ psql ││ 12 │ markdown ││ 13 │ dots ││ 14 │ restructured ││ 15 │ ascii_rounded ││ 16 │ basic_compact │╰────┴────────────────╯"); } + +#[test] +fn table_kv_header_on_separator_trim_algorithm() { + let actual = nu!("$env.config.table.header_on_separator = true; {key1: '111111111111111111111111111111111111111111111111111111111111'} | table --width=60 --theme basic"); + assert_eq!(actual.out, "+------+---------------------------------------------------+| key1 | 1111111111111111111111111111111111111111111111111 || | 11111111111 |+------+---------------------------------------------------+"); +} diff --git a/crates/nu-table/src/table.rs b/crates/nu-table/src/table.rs index e1b1269874..e39284c2ea 100644 --- a/crates/nu-table/src/table.rs +++ b/crates/nu-table/src/table.rs @@ -355,11 +355,14 @@ impl TableOption, ColoredConfig> for let total_width = get_total_width2(&self.width, cfg); if total_width > self.max { + let has_header = self.cfg.with_header && rec.count_rows() > 1; + let trim_as_head = has_header && self.cfg.header_on_border; + TableTrim { max: self.max, strategy: self.cfg.trim, width: self.width, - do_as_head: self.cfg.header_on_border, + trim_as_head, } .change(rec, cfg, dim); } else if self.cfg.expand && self.max > total_width { @@ -375,7 +378,7 @@ struct TableTrim { width: Vec, strategy: TrimStrategy, max: usize, - do_as_head: bool, + trim_as_head: bool, } impl TableOption, ColoredConfig> for TableTrim { @@ -387,7 +390,7 @@ impl TableOption, ColoredConfig> for ) { // we already must have been estimated that it's safe to do. // and all dims will be suffitient - if self.do_as_head { + if self.trim_as_head { if recs.is_empty() { return; } @@ -544,7 +547,10 @@ fn maybe_truncate_columns( const TERMWIDTH_THRESHOLD: usize = 120; let preserve_content = termwidth > TERMWIDTH_THRESHOLD; - let truncate = if cfg.header_on_border { + let has_header = cfg.with_header && data.count_rows() > 1; + let is_header_on_border = has_header && cfg.header_on_border; + + let truncate = if is_header_on_border { truncate_columns_by_head } else if preserve_content { truncate_columns_by_columns From ff27d6a18eaa0d98db792abe048b1c5052e6c6dd Mon Sep 17 00:00:00 2001 From: Jack Wright <56345+ayax79@users.noreply.github.com> Date: Tue, 9 Jul 2024 10:17:20 -0700 Subject: [PATCH 013/126] Implemented a command to expose polar's pivot functionality (#13282) # Description Implementing pivot support The example below is a port of the [python API example](https://docs.pola.rs/api/python/stable/reference/dataframe/api/polars.DataFrame.pivot.html) Screenshot 2024-07-01 at 14 29 27 # User-Facing Changes * Introduction of the `polars pivot` command --- crates/nu_plugin_polars/Cargo.toml | 2 +- .../src/dataframe/eager/mod.rs | 2 + .../src/dataframe/eager/pivot.rs | 265 ++++++++++++++++++ 3 files changed, 268 insertions(+), 1 deletion(-) create mode 100644 crates/nu_plugin_polars/src/dataframe/eager/pivot.rs diff --git a/crates/nu_plugin_polars/Cargo.toml b/crates/nu_plugin_polars/Cargo.toml index 9589c89525..f9b4295cb9 100644 --- a/crates/nu_plugin_polars/Cargo.toml +++ b/crates/nu_plugin_polars/Cargo.toml @@ -33,7 +33,7 @@ serde = { version = "1.0", features = ["derive"] } sqlparser = { version = "0.47"} polars-io = { version = "0.41", features = ["avro"]} polars-arrow = { version = "0.41"} -polars-ops = { version = "0.41"} +polars-ops = { version = "0.41", features = ["pivot"]} polars-plan = { version = "0.41", features = ["regex"]} polars-utils = { version = "0.41"} typetag = "0.2" diff --git a/crates/nu_plugin_polars/src/dataframe/eager/mod.rs b/crates/nu_plugin_polars/src/dataframe/eager/mod.rs index 509c2b6dc4..6aa37d1ed1 100644 --- a/crates/nu_plugin_polars/src/dataframe/eager/mod.rs +++ b/crates/nu_plugin_polars/src/dataframe/eager/mod.rs @@ -10,6 +10,7 @@ mod first; mod get; mod last; mod open; +mod pivot; mod query_df; mod rename; mod sample; @@ -76,6 +77,7 @@ pub(crate) fn eager_commands() -> Vec &str { + "polars pivot" + } + + fn usage(&self) -> &str { + "Pivot a DataFrame from wide to long format." + } + + fn signature(&self) -> Signature { + Signature::build(self.name()) + .required_named( + "on", + SyntaxShape::List(Box::new(SyntaxShape::String)), + "column names for pivoting", + Some('o'), + ) + .required_named( + "index", + SyntaxShape::List(Box::new(SyntaxShape::String)), + "column names for indexes", + Some('i'), + ) + .required_named( + "values", + SyntaxShape::List(Box::new(SyntaxShape::String)), + "column names used as value columns", + Some('v'), + ) + .named( + "aggregate", + SyntaxShape::String, + "Aggregation to apply when pivoting. The following are supported: first, sum, min, max, mean, median, count, last", + Some('a'), + ) + .switch( + "sort", + "Sort columns", + Some('s'), + ) + .switch( + "streamable", + "Whether or not to use the polars streaming engine. Only valid for lazy dataframes", + Some('t'), + ) + .input_output_type( + Type::Custom("dataframe".into()), + Type::Custom("dataframe".into()), + ) + .category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![ + Example { + example: "[[name subject test_1 test_2]; [Cady maths 98 100] [Cady physics 99 100] [Karen maths 61 60] [Karen physics 58 60]] | polars into-df | polars pivot --on [subject] --index [name] --values [test_1]", + description: "Perform a pivot in order to show individuals test score by subject", + result: Some( + NuDataFrame::try_from_columns( + vec![ + Column::new( + "name".to_string(), + vec![Value::string("Cady", Span::test_data()), Value::string("Karen", Span::test_data())], + ), + Column::new( + "maths".to_string(), + vec![Value::int(98, Span::test_data()), Value::int(61, Span::test_data())], + ), + Column::new( + "physics".to_string(), + vec![Value::int(99, Span::test_data()), Value::int(58, Span::test_data())], + ), + ], + None, + ) + .expect("simple df for test should not fail") + .into_value(Span::unknown()) + ) + } + ] + } + + fn run( + &self, + plugin: &Self::Plugin, + engine: &EngineInterface, + call: &EvaluatedCall, + input: PipelineData, + ) -> Result { + match PolarsPluginObject::try_from_pipeline(plugin, input, call.head)? { + PolarsPluginObject::NuDataFrame(df) => command_eager(plugin, engine, call, df), + PolarsPluginObject::NuLazyFrame(lazy) => { + command_eager(plugin, engine, call, lazy.collect(call.head)?) + } + _ => Err(ShellError::GenericError { + error: "Must be a dataframe or lazy dataframe".into(), + msg: "".into(), + span: Some(call.head), + help: None, + inner: vec![], + }), + } + .map_err(LabeledError::from) + } +} + +fn command_eager( + plugin: &PolarsPlugin, + engine: &EngineInterface, + call: &EvaluatedCall, + df: NuDataFrame, +) -> Result { + let on_col: Vec = call.get_flag("on")?.expect("required value"); + let index_col: Vec = call.get_flag("index")?.expect("required value"); + let val_col: Vec = call.get_flag("values")?.expect("required value"); + + let (on_col_string, id_col_span) = convert_columns_string(on_col, call.head)?; + let (index_col_string, index_col_span) = convert_columns_string(index_col, call.head)?; + let (val_col_string, val_col_span) = convert_columns_string(val_col, call.head)?; + + check_column_datatypes(df.as_ref(), &on_col_string, id_col_span)?; + check_column_datatypes(df.as_ref(), &index_col_string, index_col_span)?; + check_column_datatypes(df.as_ref(), &val_col_string, val_col_span)?; + + let aggregate: Option = call + .get_flag::("aggregate")? + .map(pivot_agg_for_str) + .transpose()?; + + let sort = call.has_flag("sort")?; + + let polars_df = df.to_polars(); + // todo add other args + let pivoted = pivot( + &polars_df, + &on_col_string, + Some(&index_col_string), + Some(&val_col_string), + sort, + aggregate, + None, + ) + .map_err(|e| ShellError::GenericError { + error: format!("Pivot error: {e}"), + msg: "".into(), + span: Some(call.head), + help: None, + inner: vec![], + })?; + + let res = NuDataFrame::new(false, pivoted); + res.to_pipeline_data(plugin, engine, call.head) +} + +fn check_column_datatypes>( + df: &polars::prelude::DataFrame, + cols: &[T], + col_span: Span, +) -> Result<(), ShellError> { + if cols.is_empty() { + return Err(ShellError::GenericError { + error: "Merge error".into(), + msg: "empty column list".into(), + span: Some(col_span), + help: None, + inner: vec![], + }); + } + + // Checking if they are same type + if cols.len() > 1 { + for w in cols.windows(2) { + let l_series = df + .column(w[0].as_ref()) + .map_err(|e| ShellError::GenericError { + error: "Error selecting columns".into(), + msg: e.to_string(), + span: Some(col_span), + help: None, + inner: vec![], + })?; + + let r_series = df + .column(w[1].as_ref()) + .map_err(|e| ShellError::GenericError { + error: "Error selecting columns".into(), + msg: e.to_string(), + span: Some(col_span), + help: None, + inner: vec![], + })?; + + if l_series.dtype() != r_series.dtype() { + return Err(ShellError::GenericError { + error: "Merge error".into(), + msg: "found different column types in list".into(), + span: Some(col_span), + help: Some(format!( + "datatypes {} and {} are incompatible", + l_series.dtype(), + r_series.dtype() + )), + inner: vec![], + }); + } + } + } + + Ok(()) +} + +fn pivot_agg_for_str(agg: impl AsRef) -> Result { + match agg.as_ref() { + "first" => Ok(PivotAgg::First), + "sum" => Ok(PivotAgg::Sum), + "min" => Ok(PivotAgg::Min), + "max" => Ok(PivotAgg::Max), + "mean" => Ok(PivotAgg::Mean), + "median" => Ok(PivotAgg::Median), + "count" => Ok(PivotAgg::Count), + "last" => Ok(PivotAgg::Last), + s => Err(ShellError::GenericError { + error: format!("{s} is not a valid aggregation"), + msg: "".into(), + span: None, + help: Some( + "Use one of the following: first, sum, min, max, mean, median, count, last".into(), + ), + inner: vec![], + }), + } +} + +#[cfg(test)] +mod test { + use crate::test::test_polars_plugin_command; + + use super::*; + + #[test] + fn test_examples() -> Result<(), ShellError> { + test_polars_plugin_command(&PivotDF) + } +} From ad8054ebed7db1bbfbfcf15a85b00f96fc0f8325 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Tue, 9 Jul 2024 19:52:57 -0500 Subject: [PATCH 014/126] update table comments --- crates/nu-table/src/table.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/nu-table/src/table.rs b/crates/nu-table/src/table.rs index e39284c2ea..d2cf8f4d63 100644 --- a/crates/nu-table/src/table.rs +++ b/crates/nu-table/src/table.rs @@ -966,10 +966,10 @@ impl TableOption, ColoredConfig> for .collect(); } None => { - // we don't have widths cached; which means that NO widtds adjustmens were done + // we don't have widths cached; which means that NO width adjustments were done // which means we are OK to leave columns as they are. // - // but we are actually always got to have widths at this point + // but we actually always have to have widths at this point } }; From 0178295363fb00a455a9ad403c2aca20093e4ee5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Jul 2024 19:53:46 -0500 Subject: [PATCH 015/126] Bump crate-ci/typos from 1.22.9 to 1.23.1 (#13328) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.22.9 to 1.23.1.
Release notes

Sourced from crate-ci/typos's releases.

v1.23.1

[1.23.1] - 2024-07-05

Fixes

v1.23.0

[1.23.0] - 2024-07-05

Fixes

  • Updated the dictionary with the June 2024 changes
Changelog

Sourced from crate-ci/typos's changelog.

[1.23.1] - 2024-07-05

Fixes

[1.23.0] - 2024-07-05

Fixes

  • Updated the dictionary with the June 2024 changes
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=crate-ci/typos&package-manager=github_actions&previous-version=1.22.9&new-version=1.23.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 will merge this PR once CI passes on it, as requested by @fdncred. [//]: # (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 95fc51b970..0f1e4cb5d9 100644 --- a/.github/workflows/typos.yml +++ b/.github/workflows/typos.yml @@ -10,4 +10,4 @@ jobs: uses: actions/checkout@v4.1.7 - name: Check spelling - uses: crate-ci/typos@v1.22.9 + uses: crate-ci/typos@v1.23.1 From b68c7cf3fac9b5510c600e08ff70b6af0ec6adb3 Mon Sep 17 00:00:00 2001 From: Jack Wright <56345+ayax79@users.noreply.github.com> Date: Wed, 10 Jul 2024 14:36:38 -0700 Subject: [PATCH 016/126] Make `polars unpivot` consistent with `polars pivot` (#13335) # Description Makes `polars unpivot` use the same arguments as `polars pivot` and makes it consistent with the polars' rust api. Additionally, support for the polar's streaming engine has been exposed on eager dataframes. Previously, it would only work with lazy dataframes. # User-Facing Changes * `polars unpivot` argument `--columns`|`-c` has been renamed to `--index`|`-i` * `polars unpivot` argument `--values`|`-v` has been renamed to `--on`|`-o` * `polars unpivot` short argument for `--streamable` is now `-t` to make it consistent with `polars pivot`. It was made `-t` for `polars pivot` because `-s` is short for `--short` --- .../src/dataframe/eager/unpivot.rs | 74 ++++++++----------- 1 file changed, 31 insertions(+), 43 deletions(-) diff --git a/crates/nu_plugin_polars/src/dataframe/eager/unpivot.rs b/crates/nu_plugin_polars/src/dataframe/eager/unpivot.rs index c535b54c0e..dafdc65ab4 100644 --- a/crates/nu_plugin_polars/src/dataframe/eager/unpivot.rs +++ b/crates/nu_plugin_polars/src/dataframe/eager/unpivot.rs @@ -30,16 +30,16 @@ impl PluginCommand for UnpivotDF { fn signature(&self) -> Signature { Signature::build(self.name()) .required_named( - "columns", + "index", SyntaxShape::Table(vec![]), "column names for unpivoting", - Some('c'), + Some('i'), ) .required_named( - "values", + "on", SyntaxShape::Table(vec![]), "column names used as value columns", - Some('v'), + Some('o'), ) .named( "variable-name", @@ -60,7 +60,7 @@ impl PluginCommand for UnpivotDF { .switch( "streamable", "Whether or not to use the polars streaming engine. Only valid for lazy dataframes", - Some('s'), + Some('t'), ) .category(Category::Custom("dataframe".into())) } @@ -70,7 +70,7 @@ impl PluginCommand for UnpivotDF { Example { description: "unpivot on an eager dataframe", example: - "[[a b c d]; [x 1 4 a] [y 2 5 b] [z 3 6 c]] | polars into-df | polars unpivot -c [b c] -v [a d]", + "[[a b c d]; [x 1 4 a] [y 2 5 b] [z 3 6 c]] | polars into-df | polars unpivot -i [b c] -o [a d]", result: Some( NuDataFrame::try_from_columns(vec![ Column::new( @@ -125,7 +125,7 @@ impl PluginCommand for UnpivotDF { Example { description: "unpivot on a lazy dataframe", example: - "[[a b c d]; [x 1 4 a] [y 2 5 b] [z 3 6 c]] | polars into-lazy | polars unpivot -c [b c] -v [a d] | polars collect", + "[[a b c d]; [x 1 4 a] [y 2 5 b] [z 3 6 c]] | polars into-lazy | polars unpivot -i [b c] -o [a d] | polars collect", result: Some( NuDataFrame::try_from_columns(vec![ Column::new( @@ -208,21 +208,31 @@ fn command_eager( call: &EvaluatedCall, df: NuDataFrame, ) -> Result { - let id_col: Vec = call.get_flag("columns")?.expect("required value"); - let val_col: Vec = call.get_flag("values")?.expect("required value"); + let index_col: Vec = call.get_flag("index")?.expect("required value"); + let on_col: Vec = call.get_flag("on")?.expect("required value"); let value_name: Option> = call.get_flag("value-name")?; let variable_name: Option> = call.get_flag("variable-name")?; - let (id_col_string, id_col_span) = convert_columns_string(id_col, call.head)?; - let (val_col_string, val_col_span) = convert_columns_string(val_col, call.head)?; + let (index_col_string, index_col_span) = convert_columns_string(index_col, call.head)?; + let (on_col_string, on_col_span) = convert_columns_string(on_col, call.head)?; - check_column_datatypes(df.as_ref(), &id_col_string, id_col_span)?; - check_column_datatypes(df.as_ref(), &val_col_string, val_col_span)?; + check_column_datatypes(df.as_ref(), &index_col_string, index_col_span)?; + check_column_datatypes(df.as_ref(), &on_col_string, on_col_span)?; - let mut res = df + let streamable = call.has_flag("streamable")?; + + let args = UnpivotArgs { + on: on_col_string.iter().map(Into::into).collect(), + index: index_col_string.iter().map(Into::into).collect(), + variable_name: variable_name.map(|s| s.item.into()), + value_name: value_name.map(|s| s.item.into()), + streamable, + }; + + let res = df .as_ref() - .unpivot(&val_col_string, &id_col_string) + .unpivot2(args) .map_err(|e| ShellError::GenericError { error: "Error calculating unpivot".into(), msg: e.to_string(), @@ -231,28 +241,6 @@ fn command_eager( inner: vec![], })?; - if let Some(name) = &variable_name { - res.rename("variable", &name.item) - .map_err(|e| ShellError::GenericError { - error: "Error renaming column".into(), - msg: e.to_string(), - span: Some(name.span), - help: None, - inner: vec![], - })?; - } - - if let Some(name) = &value_name { - res.rename("value", &name.item) - .map_err(|e| ShellError::GenericError { - error: "Error renaming column".into(), - msg: e.to_string(), - span: Some(name.span), - help: None, - inner: vec![], - })?; - } - let res = NuDataFrame::new(false, res); res.to_pipeline_data(plugin, engine, call.head) } @@ -263,11 +251,11 @@ fn command_lazy( call: &EvaluatedCall, df: NuLazyFrame, ) -> Result { - let id_col: Vec = call.get_flag("columns")?.expect("required value"); - let val_col: Vec = call.get_flag("values")?.expect("required value"); + let index_col: Vec = call.get_flag("index")?.expect("required value"); + let on_col: Vec = call.get_flag("on")?.expect("required value"); - let (id_col_string, _id_col_span) = convert_columns_string(id_col, call.head)?; - let (val_col_string, _val_col_span) = convert_columns_string(val_col, call.head)?; + let (index_col_string, _index_col_span) = convert_columns_string(index_col, call.head)?; + let (on_col_string, _on_col_span) = convert_columns_string(on_col, call.head)?; let value_name: Option = call.get_flag("value-name")?; let variable_name: Option = call.get_flag("variable-name")?; @@ -275,8 +263,8 @@ fn command_lazy( let streamable = call.has_flag("streamable")?; let unpivot_args = UnpivotArgs { - on: val_col_string.iter().map(Into::into).collect(), - index: id_col_string.iter().map(Into::into).collect(), + on: on_col_string.iter().map(Into::into).collect(), + index: index_col_string.iter().map(Into::into).collect(), value_name: value_name.map(Into::into), variable_name: variable_name.map(Into::into), streamable, From 616e9faaf13ccd90fb898008b5921c235893357e Mon Sep 17 00:00:00 2001 From: 132ikl <132@ikl.sh> Date: Wed, 10 Jul 2024 19:05:24 -0400 Subject: [PATCH 017/126] Fix main binary being rebuilt when nothing has changed (#13337) # Description The build script is currently re-run on each `cargo build` even when it has not changed. The `rerun-if-changed` line points to `/build.rs`, but `build.rs` is actually located at `/scripts/build.rs`. This updates that path. # User-Facing Changes N/A # Tests + Formatting N/A --- scripts/build.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/build.rs b/scripts/build.rs index beabba80dc..e366b4998c 100644 --- a/scripts/build.rs +++ b/scripts/build.rs @@ -14,5 +14,5 @@ fn main() { // Tango uses dynamic linking, to allow us to dynamically change between two bench suit at runtime. // This is currently not supported on non nightly rust, on windows. println!("cargo:rustc-link-arg-benches=-rdynamic"); - println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-changed=scripts/build.rs"); } From ea8c4e3af28d778d98fd570800c74a3e19cddf29 Mon Sep 17 00:00:00 2001 From: Devyn Cairns Date: Wed, 10 Jul 2024 16:16:22 -0700 Subject: [PATCH 018/126] Make pipe redirections consistent, add `err>|` etc. forms (#13334) # Description Fixes the lexer to recognize `out>|`, `err>|`, `out+err>|`, etc. Previously only the short-style forms were recognized, which was inconsistent with normal file redirections. I also integrated it all more into the normal lex path by checking `|` in a special way, which should be more performant and consistent, and cleans up the code a bunch. Closes #13331. # User-Facing Changes - Adds `out>|` (error), `err>|`, `out+err>|`, `err+out>|` as recognized forms of the pipe redirection. # Tests + Formatting All passing. Added tests for the new forms. # After Submitting - [ ] release notes --- crates/nu-parser/src/lex.rs | 103 ++++++---------------- tests/shell/pipeline/commands/external.rs | 23 +++-- 2 files changed, 44 insertions(+), 82 deletions(-) diff --git a/crates/nu-parser/src/lex.rs b/crates/nu-parser/src/lex.rs index 9b52467ef4..3290a774f4 100644 --- a/crates/nu-parser/src/lex.rs +++ b/crates/nu-parser/src/lex.rs @@ -238,6 +238,10 @@ pub fn lex_item( Some(e), ); } + } else if c == b'|' && is_redirection(&input[token_start..*curr_offset]) { + // matches err>| etc. + *curr_offset += 1; + break; } else if is_item_terminator(&block_level, c, additional_whitespace, special_tokens) { break; } @@ -301,6 +305,16 @@ pub fn lex_item( contents: TokenContents::OutGreaterGreaterThan, span, }, + b"out>|" | b"o>|" => { + err = Some(ParseError::Expected( + "`|`. Redirecting stdout to a pipe is the same as normal piping.", + span, + )); + Token { + contents: TokenContents::Item, + span, + } + } b"err>" | b"e>" => Token { contents: TokenContents::ErrGreaterThan, span, @@ -309,6 +323,10 @@ pub fn lex_item( contents: TokenContents::ErrGreaterGreaterThan, span, }, + b"err>|" | b"e>|" => Token { + contents: TokenContents::ErrGreaterPipe, + span, + }, b"out+err>" | b"err+out>" | b"o+e>" | b"e+o>" => Token { contents: TokenContents::OutErrGreaterThan, span, @@ -317,6 +335,10 @@ pub fn lex_item( contents: TokenContents::OutErrGreaterGreaterThan, span, }, + b"out+err>|" | b"err+out>|" | b"o+e>|" | b"e+o>|" => Token { + contents: TokenContents::OutErrGreaterPipe, + span, + }, b"&&" => { err = Some(ParseError::ShellAndAnd(span)); Token { @@ -580,17 +602,6 @@ fn lex_internal( // If the next character is non-newline whitespace, skip it. curr_offset += 1; } else { - let (token, err) = try_lex_special_piped_item(input, &mut curr_offset, span_offset); - if error.is_none() { - error = err; - } - if let Some(token) = token { - output.push(token); - is_complete = false; - continue; - } - - // Otherwise, try to consume an unclassified token. let (token, err) = lex_item( input, &mut curr_offset, @@ -609,68 +620,10 @@ fn lex_internal( (output, error) } -/// trying to lex for the following item: -/// e>|, e+o>|, o+e>| -/// -/// It returns Some(token) if we find the item, or else return None. -fn try_lex_special_piped_item( - input: &[u8], - curr_offset: &mut usize, - span_offset: usize, -) -> (Option, Option) { - let c = input[*curr_offset]; - let e_pipe_len = 3; - let eo_pipe_len = 5; - let o_pipe_len = 3; - let offset = *curr_offset; - if c == b'e' { - // expect `e>|` - if (offset + e_pipe_len <= input.len()) && (&input[offset..offset + e_pipe_len] == b"e>|") { - *curr_offset += e_pipe_len; - return ( - Some(Token::new( - TokenContents::ErrGreaterPipe, - Span::new(span_offset + offset, span_offset + offset + e_pipe_len), - )), - None, - ); - } - if (offset + eo_pipe_len <= input.len()) - && (&input[offset..offset + eo_pipe_len] == b"e+o>|") - { - *curr_offset += eo_pipe_len; - return ( - Some(Token::new( - TokenContents::OutErrGreaterPipe, - Span::new(span_offset + offset, span_offset + offset + eo_pipe_len), - )), - None, - ); - } - } else if c == b'o' { - // indicates an error if user happened to type `o>|` - if offset + o_pipe_len <= input.len() && (&input[offset..offset + o_pipe_len] == b"o>|") { - return ( - None, - Some(ParseError::Expected( - "`|`. Redirecting stdout to a pipe is the same as normal piping.", - Span::new(span_offset + offset, span_offset + offset + o_pipe_len), - )), - ); - } - // it can be the following case: `o+e>|` - if (offset + eo_pipe_len <= input.len()) - && (&input[offset..offset + eo_pipe_len] == b"o+e>|") - { - *curr_offset += eo_pipe_len; - return ( - Some(Token::new( - TokenContents::OutErrGreaterPipe, - Span::new(span_offset + offset, span_offset + offset + eo_pipe_len), - )), - None, - ); - } - } - (None, None) +/// True if this the start of a redirection. Does not match `>>` or `>|` forms. +fn is_redirection(token: &[u8]) -> bool { + matches!( + token, + b"o>" | b"out>" | b"e>" | b"err>" | b"o+e>" | b"e+o>" | b"out+err>" | b"err+out>" + ) } diff --git a/tests/shell/pipeline/commands/external.rs b/tests/shell/pipeline/commands/external.rs index d138b65766..a6efe2b6c9 100644 --- a/tests/shell/pipeline/commands/external.rs +++ b/tests/shell/pipeline/commands/external.rs @@ -149,17 +149,26 @@ fn command_substitution_wont_output_extra_newline() { assert_eq!(actual.out, "bar"); } -#[test] -fn basic_err_pipe_works() { - let actual = - nu!(r#"with-env { FOO: "bar" } { nu --testbin echo_env_stderr FOO e>| str length }"#); +#[rstest::rstest] +#[case("err>|")] +#[case("e>|")] +fn basic_err_pipe_works(#[case] redirection: &str) { + let actual = nu!( + r#"with-env { FOO: "bar" } { nu --testbin echo_env_stderr FOO {redirection} str length }"# + .replace("{redirection}", redirection) + ); assert_eq!(actual.out, "3"); } -#[test] -fn basic_outerr_pipe_works() { +#[rstest::rstest] +#[case("out+err>|")] +#[case("err+out>|")] +#[case("o+e>|")] +#[case("e+o>|")] +fn basic_outerr_pipe_works(#[case] redirection: &str) { let actual = nu!( - r#"with-env { FOO: "bar" } { nu --testbin echo_env_mixed out-err FOO FOO o+e>| str length }"# + r#"with-env { FOO: "bar" } { nu --testbin echo_env_mixed out-err FOO FOO {redirection} str length }"# + .replace("{redirection}", redirection) ); assert_eq!(actual.out, "7"); } From d7392f1f3b9b8ffb58bc5f6f00d4de02b149a23d Mon Sep 17 00:00:00 2001 From: Devyn Cairns Date: Wed, 10 Jul 2024 17:33:59 -0700 Subject: [PATCH 019/126] Internal representation (IR) compiler and evaluator (#13330) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description This PR adds an internal representation language to Nushell, offering an alternative evaluator based on simple instructions, stream-containing registers, and indexed control flow. The number of registers required is determined statically at compile-time, and the fixed size required is allocated upon entering the block. Each instruction is associated with a span, which makes going backwards from IR instructions to source code very easy. Motivations for IR: 1. **Performance.** By simplifying the evaluation path and making it more cache-friendly and branch predictor-friendly, code that does a lot of computation in Nushell itself can be sped up a decent bit. Because the IR is fairly easy to reason about, we can also implement optimization passes in the future to eliminate and simplify code. 2. **Correctness.** The instructions mostly have very simple and easily-specified behavior, so hopefully engine changes are a little bit easier to reason about, and they can be specified in a more formal way at some point. I have made an effort to document each of the instructions in the docs for the enum itself in a reasonably specific way. Some of the errors that would have happened during evaluation before are now moved to the compilation step instead, because they don't make sense to check during evaluation. 3. **As an intermediate target.** This is a good step for us to bring the [`new-nu-parser`](https://github.com/nushell/new-nu-parser) in at some point, as code generated from new AST can be directly compared to code generated from old AST. If the IR code is functionally equivalent, it will behave the exact same way. 4. **Debugging.** With a little bit more work, we can probably give control over advancing the virtual machine that `IrBlock`s run on to some sort of external driver, making things like breakpoints and single stepping possible. Tools like `view ir` and [`explore ir`](https://github.com/devyn/nu_plugin_explore_ir) make it easier than before to see what exactly is going on with your Nushell code. The goal is to eventually replace the AST evaluator entirely, once we're sure it's working just as well. You can help dogfood this by running Nushell with `$env.NU_USE_IR` set to some value. The environment variable is checked when Nushell starts, so config runs with IR, or it can also be set on a line at the REPL to change it dynamically. It is also checked when running `do` in case within a script you want to just run a specific piece of code with or without IR. # Example ```nushell view ir { |data| mut sum = 0 for n in $data { $sum += $n } $sum } ``` ```gas # 3 registers, 19 instructions, 0 bytes of data 0: load-literal %0, int(0) 1: store-variable var 904, %0 # let 2: drain %0 3: drop %0 4: load-variable %1, var 903 5: iterate %0, %1, end 15 # for, label(1), from(14:) 6: store-variable var 905, %0 7: load-variable %0, var 904 8: load-variable %2, var 905 9: binary-op %0, Math(Plus), %2 10: span %0 11: store-variable var 904, %0 12: load-literal %0, nothing 13: drain %0 14: jump 5 15: drop %0 # label(0), from(5:) 16: drain %0 17: load-variable %0, var 904 18: return %0 ``` # Benchmarks All benchmarks run on a base model Mac Mini M1. ## Iterative Fibonacci sequence This is about as best case as possible, making use of the much faster control flow. Most code will not experience a speed improvement nearly this large. ```nushell def fib [n: int] { mut a = 0 mut b = 1 for _ in 2..=$n { let c = $a + $b $a = $b $b = $c } $b } use std bench bench { 0..50 | each { |n| fib $n } } ``` IR disabled: ``` ╭───────┬─────────────────╮ │ mean │ 1ms 924µs 665ns │ │ min │ 1ms 700µs 83ns │ │ max │ 3ms 450µs 125ns │ │ std │ 395µs 759ns │ │ times │ [list 50 items] │ ╰───────┴─────────────────╯ ``` IR enabled: ``` ╭───────┬─────────────────╮ │ mean │ 452µs 820ns │ │ min │ 427µs 417ns │ │ max │ 540µs 167ns │ │ std │ 17µs 158ns │ │ times │ [list 50 items] │ ╰───────┴─────────────────╯ ``` ![explore ir view](https://github.com/nushell/nushell/assets/10729/d7bccc03-5222-461c-9200-0dce71b83b83) ## [gradient_benchmark_no_check.nu](https://github.com/nushell/nu_scripts/blob/main/benchmarks/gradient_benchmark_no_check.nu) IR disabled: ``` ╭───┬──────────────────╮ │ 0 │ 27ms 929µs 958ns │ │ 1 │ 21ms 153µs 459ns │ │ 2 │ 18ms 639µs 666ns │ │ 3 │ 19ms 554µs 583ns │ │ 4 │ 13ms 383µs 375ns │ │ 5 │ 11ms 328µs 208ns │ │ 6 │ 5ms 659µs 542ns │ ╰───┴──────────────────╯ ``` IR enabled: ``` ╭───┬──────────────────╮ │ 0 │ 22ms 662µs │ │ 1 │ 17ms 221µs 792ns │ │ 2 │ 14ms 786µs 708ns │ │ 3 │ 13ms 876µs 834ns │ │ 4 │ 13ms 52µs 875ns │ │ 5 │ 11ms 269µs 666ns │ │ 6 │ 6ms 942µs 500ns │ ╰───┴──────────────────╯ ``` ## [random-bytes.nu](https://github.com/nushell/nu_scripts/blob/main/benchmarks/random-bytes.nu) I got pretty random results out of this benchmark so I decided not to include it. Not clear why. # User-Facing Changes - IR compilation errors may appear even if the user isn't evaluating with IR. - IR evaluation can be enabled by setting the `NU_USE_IR` environment variable to any value. - New command `view ir` pretty-prints the IR for a block, and `view ir --json` can be piped into an external tool like [`explore ir`](https://github.com/devyn/nu_plugin_explore_ir). # Tests + Formatting All tests are passing with `NU_USE_IR=1`, and I've added some more eval tests to compare the results for some very core operations. I will probably want to add some more so we don't have to always check `NU_USE_IR=1 toolkit test --workspace` on a regular basis. # After Submitting - [ ] release notes - [ ] further documentation of instructions? - [ ] post-release: publish `nu_plugin_explore_ir` --- Cargo.lock | 3 + Cargo.toml | 3 +- benches/benchmarks.rs | 4 + .../nu-cli/src/commands/keybindings_list.rs | 28 +- crates/nu-cli/src/eval_cmds.rs | 5 + crates/nu-cli/src/repl.rs | 3 + crates/nu-cli/src/util.rs | 5 + .../nu-cmd-lang/src/core_commands/const_.rs | 3 + crates/nu-cmd-lang/src/core_commands/do_.rs | 4 + crates/nu-cmd-lang/src/core_commands/for_.rs | 3 + crates/nu-cmd-lang/src/core_commands/if_.rs | 6 + crates/nu-cmd-lang/src/core_commands/let_.rs | 3 + crates/nu-cmd-lang/src/core_commands/loop_.rs | 3 + .../nu-cmd-lang/src/core_commands/match_.rs | 3 + crates/nu-cmd-lang/src/core_commands/mut_.rs | 3 + .../src/core_commands/overlay/use_.rs | 8 +- crates/nu-cmd-lang/src/core_commands/try_.rs | 3 + crates/nu-cmd-lang/src/core_commands/use_.rs | 7 +- .../nu-cmd-lang/src/core_commands/while_.rs | 3 + crates/nu-command/src/bytes/build_.rs | 6 +- crates/nu-command/src/debug/explain.rs | 4 +- crates/nu-command/src/debug/metadata.rs | 6 +- crates/nu-command/src/debug/mod.rs | 2 + crates/nu-command/src/debug/timeit.rs | 14 +- crates/nu-command/src/debug/view_ir.rs | 83 + crates/nu-command/src/default_context.rs | 1 + crates/nu-command/src/env/export_env.rs | 6 +- crates/nu-command/src/env/source_env.rs | 2 +- crates/nu-command/src/filesystem/du.rs | 2 +- crates/nu-command/src/filesystem/ls.rs | 2 +- crates/nu-command/src/filesystem/open.rs | 7 +- crates/nu-command/src/filesystem/save.rs | 64 +- crates/nu-command/src/filesystem/util.rs | 39 +- crates/nu-command/src/filters/transpose.rs | 10 +- crates/nu-command/src/filters/utils.rs | 3 +- crates/nu-command/src/generators/cal.rs | 11 +- crates/nu-command/src/math/utils.rs | 2 +- crates/nu-command/src/platform/ansi/ansi_.rs | 27 +- crates/nu-command/src/platform/is_terminal.rs | 2 +- crates/nu-command/src/platform/kill.rs | 17 +- .../src/strings/encode_decode/base64.rs | 4 +- crates/nu-command/src/strings/mod.rs | 3 +- crates/nu-command/src/system/nu_check.rs | 2 +- crates/nu-command/src/system/run_external.rs | 68 +- crates/nu-command/src/system/uname.rs | 9 +- crates/nu-command/src/viewers/table.rs | 4 +- crates/nu-engine/Cargo.toml | 3 +- crates/nu-engine/src/call_ext.rs | 219 ++- crates/nu-engine/src/command_prelude.rs | 4 +- crates/nu-engine/src/compile/builder.rs | 575 +++++++ crates/nu-engine/src/compile/call.rs | 270 +++ crates/nu-engine/src/compile/expression.rs | 535 ++++++ crates/nu-engine/src/compile/keyword.rs | 902 ++++++++++ crates/nu-engine/src/compile/mod.rs | 204 +++ crates/nu-engine/src/compile/operator.rs | 378 +++++ crates/nu-engine/src/compile/redirect.rs | 157 ++ crates/nu-engine/src/documentation.rs | 9 +- crates/nu-engine/src/env.rs | 21 +- crates/nu-engine/src/eval.rs | 16 +- crates/nu-engine/src/eval_helpers.rs | 16 +- crates/nu-engine/src/eval_ir.rs | 1462 +++++++++++++++++ crates/nu-engine/src/lib.rs | 4 + crates/nu-parser/src/known_external.rs | 204 ++- crates/nu-parser/src/parse_patterns.rs | 2 +- crates/nu-parser/src/parser.rs | 26 +- crates/nu-parser/tests/test_parser.rs | 9 +- crates/nu-plugin-engine/src/context.rs | 11 +- .../nu-plugin-protocol/src/evaluated_call.rs | 40 +- crates/nu-protocol/Cargo.toml | 3 +- crates/nu-protocol/src/alias.rs | 4 +- crates/nu-protocol/src/ast/block.rs | 11 +- crates/nu-protocol/src/ast/expr.rs | 14 +- crates/nu-protocol/src/ast/match_pattern.rs | 6 +- crates/nu-protocol/src/ast/pipeline.rs | 14 +- crates/nu-protocol/src/engine/argument.rs | 124 ++ crates/nu-protocol/src/engine/call.rs | 223 +++ crates/nu-protocol/src/engine/command.rs | 8 +- .../nu-protocol/src/engine/error_handler.rs | 55 + crates/nu-protocol/src/engine/mod.rs | 6 + crates/nu-protocol/src/engine/stack.rs | 22 +- .../src/engine/state_working_set.rs | 12 +- crates/nu-protocol/src/errors/cli_error.rs | 4 + .../nu-protocol/src/errors/compile_error.rs | 238 +++ crates/nu-protocol/src/errors/mod.rs | 2 + crates/nu-protocol/src/errors/shell_error.rs | 17 + crates/nu-protocol/src/eval_const.rs | 2 +- crates/nu-protocol/src/id.rs | 14 + crates/nu-protocol/src/ir/call.rs | 351 ++++ crates/nu-protocol/src/ir/display.rs | 452 +++++ crates/nu-protocol/src/ir/mod.rs | 419 +++++ crates/nu-protocol/src/lib.rs | 1 + .../nu-protocol/src/pipeline/byte_stream.rs | 6 + .../nu-protocol/src/pipeline/list_stream.rs | 11 + .../nu-protocol/src/pipeline/pipeline_data.rs | 29 +- crates/nu-protocol/src/signature.rs | 3 +- crates/nu-protocol/src/span.rs | 16 + crates/nu-test-support/src/macros.rs | 11 + src/run.rs | 12 + tests/eval/mod.rs | 457 +++++- 99 files changed, 7768 insertions(+), 346 deletions(-) create mode 100644 crates/nu-command/src/debug/view_ir.rs create mode 100644 crates/nu-engine/src/compile/builder.rs create mode 100644 crates/nu-engine/src/compile/call.rs create mode 100644 crates/nu-engine/src/compile/expression.rs create mode 100644 crates/nu-engine/src/compile/keyword.rs create mode 100644 crates/nu-engine/src/compile/mod.rs create mode 100644 crates/nu-engine/src/compile/operator.rs create mode 100644 crates/nu-engine/src/compile/redirect.rs create mode 100644 crates/nu-engine/src/eval_ir.rs create mode 100644 crates/nu-protocol/src/engine/argument.rs create mode 100644 crates/nu-protocol/src/engine/call.rs create mode 100644 crates/nu-protocol/src/engine/error_handler.rs create mode 100644 crates/nu-protocol/src/errors/compile_error.rs create mode 100644 crates/nu-protocol/src/ir/call.rs create mode 100644 crates/nu-protocol/src/ir/display.rs create mode 100644 crates/nu-protocol/src/ir/mod.rs diff --git a/Cargo.lock b/Cargo.lock index d4f759aed0..7243a801fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2900,6 +2900,7 @@ dependencies = [ "openssl", "pretty_assertions", "reedline", + "regex", "rstest", "serde_json", "serial_test", @@ -3151,6 +3152,7 @@ dependencies = [ name = "nu-engine" version = "0.95.1" dependencies = [ + "log", "nu-glob", "nu-path", "nu-protocol", @@ -3339,6 +3341,7 @@ dependencies = [ "convert_case", "fancy-regex", "indexmap", + "log", "lru", "miette", "nix", diff --git a/Cargo.toml b/Cargo.toml index 5701fe3958..eb8a92c630 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -232,6 +232,7 @@ assert_cmd = "2.0" dirs-next = { workspace = true } tango-bench = "0.5" pretty_assertions = { workspace = true } +regex = { workspace = true } rstest = { workspace = true, default-features = false } serial_test = "3.1" tempfile = { workspace = true } @@ -310,4 +311,4 @@ reedline = { git = "https://github.com/nushell/reedline", branch = "main" } # Run individual benchmarks like `cargo bench -- ` e.g. `cargo bench -- parse` [[bench]] name = "benchmarks" -harness = false \ No newline at end of file +harness = false diff --git a/benches/benchmarks.rs b/benches/benchmarks.rs index ea296f0d06..4efb666507 100644 --- a/benches/benchmarks.rs +++ b/benches/benchmarks.rs @@ -45,6 +45,10 @@ fn setup_stack_and_engine_from_command(command: &str) -> (Stack, EngineState) { }; let mut stack = Stack::new(); + + // Support running benchmarks with IR mode + stack.use_ir = std::env::var_os("NU_USE_IR").is_some(); + evaluate_commands( &commands, &mut engine, diff --git a/crates/nu-cli/src/commands/keybindings_list.rs b/crates/nu-cli/src/commands/keybindings_list.rs index f4450c0c23..350df7b820 100644 --- a/crates/nu-cli/src/commands/keybindings_list.rs +++ b/crates/nu-cli/src/commands/keybindings_list.rs @@ -49,22 +49,24 @@ impl Command for KeybindingsList { fn run( &self, - _engine_state: &EngineState, - _stack: &mut Stack, + engine_state: &EngineState, + stack: &mut Stack, call: &Call, _input: PipelineData, ) -> Result { - let records = if call.named_len() == 0 { - let all_options = ["modifiers", "keycodes", "edits", "modes", "events"]; - all_options - .iter() - .flat_map(|argument| get_records(argument, call.head)) - .collect() - } else { - call.named_iter() - .flat_map(|(argument, _, _)| get_records(argument.item.as_str(), call.head)) - .collect() - }; + let all_options = ["modifiers", "keycodes", "edits", "modes", "events"]; + + let presence = all_options + .iter() + .map(|option| call.has_flag(engine_state, stack, option)) + .collect::, ShellError>>()?; + + let records = all_options + .iter() + .zip(presence) + .filter(|(_, present)| *present) + .flat_map(|(option, _)| get_records(option, call.head)) + .collect(); Ok(Value::list(records, call.head).into_pipeline_data()) } diff --git a/crates/nu-cli/src/eval_cmds.rs b/crates/nu-cli/src/eval_cmds.rs index 13141f6174..ad3a15304d 100644 --- a/crates/nu-cli/src/eval_cmds.rs +++ b/crates/nu-cli/src/eval_cmds.rs @@ -70,6 +70,11 @@ pub fn evaluate_commands( std::process::exit(1); } + if let Some(err) = working_set.compile_errors.first() { + report_error(&working_set, err); + // Not a fatal error, for now + } + (output, working_set.render()) }; diff --git a/crates/nu-cli/src/repl.rs b/crates/nu-cli/src/repl.rs index 7099b70ba8..07272701f3 100644 --- a/crates/nu-cli/src/repl.rs +++ b/crates/nu-cli/src/repl.rs @@ -268,6 +268,9 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) { if let Err(err) = engine_state.merge_env(&mut stack, cwd) { report_error_new(engine_state, &err); } + // Check whether $env.NU_USE_IR is set, so that the user can change it in the REPL + // Temporary while IR eval is optional + stack.use_ir = stack.has_env_var(engine_state, "NU_USE_IR"); perf!("merge env", start_time, use_color); start_time = std::time::Instant::now(); diff --git a/crates/nu-cli/src/util.rs b/crates/nu-cli/src/util.rs index d3cf73056f..bcee53c9b0 100644 --- a/crates/nu-cli/src/util.rs +++ b/crates/nu-cli/src/util.rs @@ -262,6 +262,11 @@ fn evaluate_source( return Ok(Some(1)); } + if let Some(err) = working_set.compile_errors.first() { + report_error(&working_set, err); + // Not a fatal error, for now + } + (output, working_set.render()) }; diff --git a/crates/nu-cmd-lang/src/core_commands/const_.rs b/crates/nu-cmd-lang/src/core_commands/const_.rs index f780c5ada9..5b3d03443a 100644 --- a/crates/nu-cmd-lang/src/core_commands/const_.rs +++ b/crates/nu-cmd-lang/src/core_commands/const_.rs @@ -46,6 +46,9 @@ impl Command for Const { call: &Call, _input: PipelineData, ) -> Result { + // This is compiled specially by the IR compiler. The code here is never used when + // running in IR mode. + let call = call.assert_ast_call()?; let var_id = if let Some(id) = call.positional_nth(0).and_then(|pos| pos.as_var()) { id } else { diff --git a/crates/nu-cmd-lang/src/core_commands/do_.rs b/crates/nu-cmd-lang/src/core_commands/do_.rs index adf13cc0bb..bf29a2159a 100644 --- a/crates/nu-cmd-lang/src/core_commands/do_.rs +++ b/crates/nu-cmd-lang/src/core_commands/do_.rs @@ -81,6 +81,10 @@ impl Command for Do { bind_args_to(&mut callee_stack, &block.signature, rest, head)?; let eval_block_with_early_return = get_eval_block_with_early_return(engine_state); + + // Applies to all block evaluation once set true + callee_stack.use_ir = caller_stack.has_env_var(engine_state, "NU_USE_IR"); + let result = eval_block_with_early_return(engine_state, &mut callee_stack, block, input); if has_env { diff --git a/crates/nu-cmd-lang/src/core_commands/for_.rs b/crates/nu-cmd-lang/src/core_commands/for_.rs index 1e90e5f06d..36df743e5f 100644 --- a/crates/nu-cmd-lang/src/core_commands/for_.rs +++ b/crates/nu-cmd-lang/src/core_commands/for_.rs @@ -48,6 +48,9 @@ impl Command for For { call: &Call, _input: PipelineData, ) -> Result { + // This is compiled specially by the IR compiler. The code here is never used when + // running in IR mode. + let call = call.assert_ast_call()?; let head = call.head; let var_id = call .positional_nth(0) diff --git a/crates/nu-cmd-lang/src/core_commands/if_.rs b/crates/nu-cmd-lang/src/core_commands/if_.rs index 738d901759..8667843770 100644 --- a/crates/nu-cmd-lang/src/core_commands/if_.rs +++ b/crates/nu-cmd-lang/src/core_commands/if_.rs @@ -60,6 +60,9 @@ impl Command for If { call: &Call, input: PipelineData, ) -> Result { + // This is compiled specially by the IR compiler. The code here is never used when + // running in IR mode. + let call = call.assert_ast_call()?; let cond = call.positional_nth(0).expect("checked through parser"); let then_block = call .positional_nth(1) @@ -99,6 +102,9 @@ impl Command for If { call: &Call, input: PipelineData, ) -> Result { + // This is compiled specially by the IR compiler. The code here is never used when + // running in IR mode. + let call = call.assert_ast_call()?; let cond = call.positional_nth(0).expect("checked through parser"); let then_block = call .positional_nth(1) diff --git a/crates/nu-cmd-lang/src/core_commands/let_.rs b/crates/nu-cmd-lang/src/core_commands/let_.rs index f2da628c31..46324ef39e 100644 --- a/crates/nu-cmd-lang/src/core_commands/let_.rs +++ b/crates/nu-cmd-lang/src/core_commands/let_.rs @@ -46,6 +46,9 @@ impl Command for Let { call: &Call, input: PipelineData, ) -> Result { + // This is compiled specially by the IR compiler. The code here is never used when + // running in IR mode. + let call = call.assert_ast_call()?; let var_id = call .positional_nth(0) .expect("checked through parser") diff --git a/crates/nu-cmd-lang/src/core_commands/loop_.rs b/crates/nu-cmd-lang/src/core_commands/loop_.rs index 86e18389de..f495c8d3ae 100644 --- a/crates/nu-cmd-lang/src/core_commands/loop_.rs +++ b/crates/nu-cmd-lang/src/core_commands/loop_.rs @@ -37,6 +37,9 @@ impl Command for Loop { call: &Call, _input: PipelineData, ) -> Result { + // This is compiled specially by the IR compiler. The code here is never used when + // running in IR mode. + let call = call.assert_ast_call()?; let head = call.head; let block_id = call .positional_nth(0) diff --git a/crates/nu-cmd-lang/src/core_commands/match_.rs b/crates/nu-cmd-lang/src/core_commands/match_.rs index d28a59cbad..c3a3d61216 100644 --- a/crates/nu-cmd-lang/src/core_commands/match_.rs +++ b/crates/nu-cmd-lang/src/core_commands/match_.rs @@ -43,6 +43,9 @@ impl Command for Match { call: &Call, input: PipelineData, ) -> Result { + // This is compiled specially by the IR compiler. The code here is never used when + // running in IR mode. + let call = call.assert_ast_call()?; let value: Value = call.req(engine_state, stack, 0)?; let matches = call .positional_nth(1) diff --git a/crates/nu-cmd-lang/src/core_commands/mut_.rs b/crates/nu-cmd-lang/src/core_commands/mut_.rs index 5db3c929af..b729590027 100644 --- a/crates/nu-cmd-lang/src/core_commands/mut_.rs +++ b/crates/nu-cmd-lang/src/core_commands/mut_.rs @@ -46,6 +46,9 @@ impl Command for Mut { call: &Call, input: PipelineData, ) -> Result { + // This is compiled specially by the IR compiler. The code here is never used when + // running in IR mode. + let call = call.assert_ast_call()?; let var_id = call .positional_nth(0) .expect("checked through parser") diff --git a/crates/nu-cmd-lang/src/core_commands/overlay/use_.rs b/crates/nu-cmd-lang/src/core_commands/overlay/use_.rs index e8b51fb59b..d6d3ae745a 100644 --- a/crates/nu-cmd-lang/src/core_commands/overlay/use_.rs +++ b/crates/nu-cmd-lang/src/core_commands/overlay/use_.rs @@ -65,9 +65,9 @@ impl Command for OverlayUse { name_arg.item = trim_quotes_str(&name_arg.item).to_string(); let maybe_origin_module_id = - if let Some(overlay_expr) = call.get_parser_info("overlay_expr") { + if let Some(overlay_expr) = call.get_parser_info(caller_stack, "overlay_expr") { if let Expr::Overlay(module_id) = &overlay_expr.expr { - module_id + *module_id } else { return Err(ShellError::NushellFailedSpanned { msg: "Not an overlay".to_string(), @@ -110,7 +110,7 @@ impl Command for OverlayUse { // a) adding a new overlay // b) refreshing an active overlay (the origin module changed) - let module = engine_state.get_module(*module_id); + let module = engine_state.get_module(module_id); // Evaluate the export-env block (if any) and keep its environment if let Some(block_id) = module.env_block { @@ -118,7 +118,7 @@ impl Command for OverlayUse { &name_arg.item, engine_state, caller_stack, - get_dirs_var_from_call(call), + get_dirs_var_from_call(caller_stack, call), )?; let block = engine_state.get_block(block_id); diff --git a/crates/nu-cmd-lang/src/core_commands/try_.rs b/crates/nu-cmd-lang/src/core_commands/try_.rs index f99825b88d..2309897a1b 100644 --- a/crates/nu-cmd-lang/src/core_commands/try_.rs +++ b/crates/nu-cmd-lang/src/core_commands/try_.rs @@ -47,6 +47,9 @@ impl Command for Try { call: &Call, input: PipelineData, ) -> Result { + // This is compiled specially by the IR compiler. The code here is never used when + // running in IR mode. + let call = call.assert_ast_call()?; let try_block = call .positional_nth(0) .expect("checked through parser") diff --git a/crates/nu-cmd-lang/src/core_commands/use_.rs b/crates/nu-cmd-lang/src/core_commands/use_.rs index b0f3648304..7f544fa5d4 100644 --- a/crates/nu-cmd-lang/src/core_commands/use_.rs +++ b/crates/nu-cmd-lang/src/core_commands/use_.rs @@ -57,7 +57,7 @@ This command is a parser keyword. For details, check: let Some(Expression { expr: Expr::ImportPattern(import_pattern), .. - }) = call.get_parser_info("import_pattern") + }) = call.get_parser_info(caller_stack, "import_pattern") else { return Err(ShellError::GenericError { error: "Unexpected import".into(), @@ -68,6 +68,9 @@ This command is a parser keyword. For details, check: }); }; + // Necessary so that we can modify the stack. + let import_pattern = import_pattern.clone(); + if let Some(module_id) = import_pattern.head.id { // Add constants for var_id in &import_pattern.constants { @@ -99,7 +102,7 @@ This command is a parser keyword. For details, check: &module_arg_str, engine_state, caller_stack, - get_dirs_var_from_call(call), + get_dirs_var_from_call(caller_stack, call), )?; let maybe_parent = maybe_file_path .as_ref() diff --git a/crates/nu-cmd-lang/src/core_commands/while_.rs b/crates/nu-cmd-lang/src/core_commands/while_.rs index 22bb4c5dbd..a67c47fcab 100644 --- a/crates/nu-cmd-lang/src/core_commands/while_.rs +++ b/crates/nu-cmd-lang/src/core_commands/while_.rs @@ -46,6 +46,9 @@ impl Command for While { call: &Call, _input: PipelineData, ) -> Result { + // This is compiled specially by the IR compiler. The code here is never used when + // running in IR mode. + let call = call.assert_ast_call()?; let head = call.head; let cond = call.positional_nth(0).expect("checked through parser"); let block_id = call diff --git a/crates/nu-command/src/bytes/build_.rs b/crates/nu-command/src/bytes/build_.rs index f6b1327621..9a3599a071 100644 --- a/crates/nu-command/src/bytes/build_.rs +++ b/crates/nu-command/src/bytes/build_.rs @@ -49,10 +49,8 @@ impl Command for BytesBuild { _input: PipelineData, ) -> Result { let mut output = vec![]; - for val in call.rest_iter_flattened(0, |expr| { - let eval_expression = get_eval_expression(engine_state); - eval_expression(engine_state, stack, expr) - })? { + let eval_expression = get_eval_expression(engine_state); + for val in call.rest_iter_flattened(engine_state, stack, eval_expression, 0)? { let val_span = val.span(); match val { Value::Binary { mut val, .. } => output.append(&mut val), diff --git a/crates/nu-command/src/debug/explain.rs b/crates/nu-command/src/debug/explain.rs index b451d6916a..710e37935a 100644 --- a/crates/nu-command/src/debug/explain.rs +++ b/crates/nu-command/src/debug/explain.rs @@ -1,6 +1,6 @@ use nu_engine::{command_prelude::*, get_eval_expression}; use nu_protocol::{ - ast::{Argument, Block, Expr, Expression}, + ast::{self, Argument, Block, Expr, Expression}, engine::Closure, }; @@ -106,7 +106,7 @@ pub fn get_pipeline_elements( fn get_arguments( engine_state: &EngineState, stack: &mut Stack, - call: &Call, + call: &ast::Call, eval_expression_fn: fn(&EngineState, &mut Stack, &Expression) -> Result, ) -> Vec { let mut arg_value = vec![]; diff --git a/crates/nu-command/src/debug/metadata.rs b/crates/nu-command/src/debug/metadata.rs index 543e598e28..245c150cea 100644 --- a/crates/nu-command/src/debug/metadata.rs +++ b/crates/nu-command/src/debug/metadata.rs @@ -28,6 +28,10 @@ impl Command for Metadata { .category(Category::Debug) } + fn requires_ast_for_arguments(&self) -> bool { + true + } + fn run( &self, engine_state: &EngineState, @@ -35,7 +39,7 @@ impl Command for Metadata { call: &Call, input: PipelineData, ) -> Result { - let arg = call.positional_nth(0); + let arg = call.positional_nth(stack, 0); let head = call.head; match arg { diff --git a/crates/nu-command/src/debug/mod.rs b/crates/nu-command/src/debug/mod.rs index f19ddab916..ec18c2be87 100644 --- a/crates/nu-command/src/debug/mod.rs +++ b/crates/nu-command/src/debug/mod.rs @@ -10,6 +10,7 @@ mod profile; mod timeit; mod view; mod view_files; +mod view_ir; mod view_source; mod view_span; @@ -25,5 +26,6 @@ pub use profile::DebugProfile; pub use timeit::TimeIt; pub use view::View; pub use view_files::ViewFiles; +pub use view_ir::ViewIr; pub use view_source::ViewSource; pub use view_span::ViewSpan; diff --git a/crates/nu-command/src/debug/timeit.rs b/crates/nu-command/src/debug/timeit.rs index a445679b81..7a48644a6d 100644 --- a/crates/nu-command/src/debug/timeit.rs +++ b/crates/nu-command/src/debug/timeit.rs @@ -32,6 +32,10 @@ impl Command for TimeIt { vec!["timing", "timer", "benchmark", "measure"] } + fn requires_ast_for_arguments(&self) -> bool { + true + } + fn run( &self, engine_state: &EngineState, @@ -39,13 +43,14 @@ impl Command for TimeIt { call: &Call, input: PipelineData, ) -> Result { - let command_to_run = call.positional_nth(0); + // reset outdest, so the command can write to stdout and stderr. + let stack = &mut stack.push_redirection(None, None); + + let command_to_run = call.positional_nth(stack, 0); // Get the start time after all other computation has been done. let start_time = Instant::now(); - // reset outdest, so the command can write to stdout and stderr. - let stack = &mut stack.push_redirection(None, None); if let Some(command_to_run) = command_to_run { if let Some(block_id) = command_to_run.as_block() { let eval_block = get_eval_block(engine_state); @@ -53,7 +58,8 @@ impl Command for TimeIt { eval_block(engine_state, stack, block, input)? } else { let eval_expression_with_input = get_eval_expression_with_input(engine_state); - eval_expression_with_input(engine_state, stack, command_to_run, input)?.0 + let expression = &command_to_run.clone(); + eval_expression_with_input(engine_state, stack, expression, input)?.0 } } else { PipelineData::empty() diff --git a/crates/nu-command/src/debug/view_ir.rs b/crates/nu-command/src/debug/view_ir.rs new file mode 100644 index 0000000000..df4f6cad6b --- /dev/null +++ b/crates/nu-command/src/debug/view_ir.rs @@ -0,0 +1,83 @@ +use nu_engine::command_prelude::*; +use nu_protocol::engine::Closure; + +#[derive(Clone)] +pub struct ViewIr; + +impl Command for ViewIr { + fn name(&self) -> &str { + "view ir" + } + + fn signature(&self) -> Signature { + Signature::new(self.name()) + .required( + "closure", + SyntaxShape::Closure(None), + "The closure to see compiled code for.", + ) + .switch( + "json", + "Dump the raw block data as JSON (unstable).", + Some('j'), + ) + .input_output_type(Type::Nothing, Type::String) + } + + fn usage(&self) -> &str { + "View the compiled IR code for a block of code." + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + let closure: Closure = call.req(engine_state, stack, 0)?; + let json = call.has_flag(engine_state, stack, "json")?; + + let block = engine_state.get_block(closure.block_id); + let ir_block = block + .ir_block + .as_ref() + .ok_or_else(|| ShellError::GenericError { + error: "Can't view IR for this block".into(), + msg: "block is missing compiled representation".into(), + span: block.span, + help: Some("the IrBlock is probably missing due to a compilation error".into()), + inner: vec![], + })?; + + let formatted = if json { + let formatted_instructions = ir_block + .instructions + .iter() + .map(|instruction| { + instruction + .display(engine_state, &ir_block.data) + .to_string() + }) + .collect::>(); + + serde_json::to_string_pretty(&serde_json::json!({ + "block_id": closure.block_id, + "span": block.span, + "ir_block": ir_block, + "formatted_instructions": formatted_instructions, + })) + .map_err(|err| ShellError::GenericError { + error: "JSON serialization failed".into(), + msg: err.to_string(), + span: Some(call.head), + help: None, + inner: vec![], + })? + } else { + format!("{}", ir_block.display(engine_state)) + }; + + Ok(Value::string(formatted, call.head).into_pipeline_data()) + } +} diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 847d2349ed..b270a78dce 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -154,6 +154,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState { TimeIt, View, ViewFiles, + ViewIr, ViewSource, ViewSpan, }; diff --git a/crates/nu-command/src/env/export_env.rs b/crates/nu-command/src/env/export_env.rs index 00f2c73ef4..7b583c9959 100644 --- a/crates/nu-command/src/env/export_env.rs +++ b/crates/nu-command/src/env/export_env.rs @@ -33,6 +33,10 @@ impl Command for ExportEnv { CommandType::Keyword } + fn requires_ast_for_arguments(&self) -> bool { + true + } + fn run( &self, engine_state: &EngineState, @@ -41,7 +45,7 @@ impl Command for ExportEnv { input: PipelineData, ) -> Result { let block_id = call - .positional_nth(0) + .positional_nth(caller_stack, 0) .expect("checked through parser") .as_block() .expect("internal error: missing block"); diff --git a/crates/nu-command/src/env/source_env.rs b/crates/nu-command/src/env/source_env.rs index 0d8b118e8d..1813a92f1f 100644 --- a/crates/nu-command/src/env/source_env.rs +++ b/crates/nu-command/src/env/source_env.rs @@ -56,7 +56,7 @@ impl Command for SourceEnv { &source_filename.item, engine_state, caller_stack, - get_dirs_var_from_call(call), + get_dirs_var_from_call(caller_stack, call), )? { PathBuf::from(&path) } else { diff --git a/crates/nu-command/src/filesystem/du.rs b/crates/nu-command/src/filesystem/du.rs index 93f08f7785..d34892ace9 100644 --- a/crates/nu-command/src/filesystem/du.rs +++ b/crates/nu-command/src/filesystem/du.rs @@ -102,7 +102,7 @@ impl Command for Du { let current_dir = current_dir(engine_state, stack)?; let paths = get_rest_for_glob_pattern(engine_state, stack, call, 0)?; - let paths = if call.rest_iter(0).count() == 0 { + let paths = if !call.has_positional_args(stack, 0) { None } else { Some(paths) diff --git a/crates/nu-command/src/filesystem/ls.rs b/crates/nu-command/src/filesystem/ls.rs index f465a93dc1..807e4f3409 100644 --- a/crates/nu-command/src/filesystem/ls.rs +++ b/crates/nu-command/src/filesystem/ls.rs @@ -108,7 +108,7 @@ impl Command for Ls { }; let pattern_arg = get_rest_for_glob_pattern(engine_state, stack, call, 0)?; - let input_pattern_arg = if call.rest_iter(0).count() == 0 { + let input_pattern_arg = if !call.has_positional_args(stack, 0) { None } else { Some(pattern_arg) diff --git a/crates/nu-command/src/filesystem/open.rs b/crates/nu-command/src/filesystem/open.rs index e654b27f05..0351d1d9b2 100644 --- a/crates/nu-command/src/filesystem/open.rs +++ b/crates/nu-command/src/filesystem/open.rs @@ -1,7 +1,7 @@ use super::util::get_rest_for_glob_pattern; #[allow(deprecated)] use nu_engine::{command_prelude::*, current_dir, get_eval_block}; -use nu_protocol::{ByteStream, DataSource, NuGlob, PipelineMetadata}; +use nu_protocol::{ast, ByteStream, DataSource, NuGlob, PipelineMetadata}; use std::path::Path; #[cfg(feature = "sqlite")] @@ -56,7 +56,7 @@ impl Command for Open { let mut paths = get_rest_for_glob_pattern(engine_state, stack, call, 0)?; let eval_block = get_eval_block(engine_state); - if paths.is_empty() && call.rest_iter(0).next().is_none() { + if paths.is_empty() && !call.has_positional_args(stack, 0) { // try to use path from pipeline input if there were no positional or spread args let (filename, span) = match input { PipelineData::Value(val, ..) => { @@ -180,7 +180,8 @@ impl Command for Open { let block = engine_state.get_block(block_id); eval_block(engine_state, stack, block, stream) } else { - decl.run(engine_state, stack, &Call::new(call_span), stream) + let call = ast::Call::new(call_span); + decl.run(engine_state, stack, &(&call).into(), stream) }; output.push(command_output.map_err(|inner| { ShellError::GenericError{ diff --git a/crates/nu-command/src/filesystem/save.rs b/crates/nu-command/src/filesystem/save.rs index 6ca5c09559..be5073ef20 100644 --- a/crates/nu-command/src/filesystem/save.rs +++ b/crates/nu-command/src/filesystem/save.rs @@ -4,10 +4,8 @@ use nu_engine::get_eval_block; use nu_engine::{command_prelude::*, current_dir}; use nu_path::expand_path_with; use nu_protocol::{ - ast::{Expr, Expression}, - byte_stream::copy_with_signals, - process::ChildPipe, - ByteStreamSource, DataSource, OutDest, PipelineMetadata, Signals, + ast, byte_stream::copy_with_signals, process::ChildPipe, ByteStreamSource, DataSource, OutDest, + PipelineMetadata, Signals, }; use std::{ fs::File, @@ -69,24 +67,6 @@ impl Command for Save { let append = call.has_flag(engine_state, stack, "append")?; let force = call.has_flag(engine_state, stack, "force")?; let progress = call.has_flag(engine_state, stack, "progress")?; - let out_append = if let Some(Expression { - expr: Expr::Bool(out_append), - .. - }) = call.get_parser_info("out-append") - { - *out_append - } else { - false - }; - let err_append = if let Some(Expression { - expr: Expr::Bool(err_append), - .. - }) = call.get_parser_info("err-append") - { - *err_append - } else { - false - }; let span = call.head; #[allow(deprecated)] @@ -109,14 +89,7 @@ impl Command for Save { PipelineData::ByteStream(stream, metadata) => { check_saving_to_source_file(metadata.as_ref(), &path, stderr_path.as_ref())?; - let (file, stderr_file) = get_files( - &path, - stderr_path.as_ref(), - append, - out_append, - err_append, - force, - )?; + let (file, stderr_file) = get_files(&path, stderr_path.as_ref(), append, force)?; let size = stream.known_size(); let signals = engine_state.signals(); @@ -221,14 +194,7 @@ impl Command for Save { stderr_path.as_ref(), )?; - let (mut file, _) = get_files( - &path, - stderr_path.as_ref(), - append, - out_append, - err_append, - force, - )?; + let (mut file, _) = get_files(&path, stderr_path.as_ref(), append, force)?; for val in ls { file.write_all(&value_to_bytes(val)?) .map_err(|err| ShellError::IOError { @@ -258,14 +224,7 @@ impl Command for Save { input_to_bytes(input, Path::new(&path.item), raw, engine_state, stack, span)?; // Only open file after successful conversion - let (mut file, _) = get_files( - &path, - stderr_path.as_ref(), - append, - out_append, - err_append, - force, - )?; + let (mut file, _) = get_files(&path, stderr_path.as_ref(), append, force)?; file.write_all(&bytes).map_err(|err| ShellError::IOError { msg: err.to_string(), @@ -397,7 +356,8 @@ fn convert_to_extension( let eval_block = get_eval_block(engine_state); eval_block(engine_state, stack, block, input) } else { - decl.run(engine_state, stack, &Call::new(span), input) + let call = ast::Call::new(span); + decl.run(engine_state, stack, &(&call).into(), input) } } else { Ok(input) @@ -473,19 +433,17 @@ fn get_files( path: &Spanned, stderr_path: Option<&Spanned>, append: bool, - out_append: bool, - err_append: bool, force: bool, ) -> Result<(File, Option), ShellError> { // First check both paths - let (path, path_span) = prepare_path(path, append || out_append, force)?; + let (path, path_span) = prepare_path(path, append, force)?; let stderr_path_and_span = stderr_path .as_ref() - .map(|stderr_path| prepare_path(stderr_path, append || err_append, force)) + .map(|stderr_path| prepare_path(stderr_path, append, force)) .transpose()?; // Only if both files can be used open and possibly truncate them - let file = open_file(path, path_span, append || out_append)?; + let file = open_file(path, path_span, append)?; let stderr_file = stderr_path_and_span .map(|(stderr_path, stderr_path_span)| { @@ -498,7 +456,7 @@ fn get_files( inner: vec![], }) } else { - open_file(stderr_path, stderr_path_span, append || err_append) + open_file(stderr_path, stderr_path_span, append) } }) .transpose()?; diff --git a/crates/nu-command/src/filesystem/util.rs b/crates/nu-command/src/filesystem/util.rs index 1b755875bd..de32d204a0 100644 --- a/crates/nu-command/src/filesystem/util.rs +++ b/crates/nu-command/src/filesystem/util.rs @@ -1,6 +1,6 @@ use dialoguer::Input; use nu_engine::{command_prelude::*, get_eval_expression}; -use nu_protocol::{ast::Expr, FromValue, NuGlob}; +use nu_protocol::{FromValue, NuGlob}; use std::{ error::Error, path::{Path, PathBuf}, @@ -92,42 +92,19 @@ pub fn is_older(src: &Path, dst: &Path) -> Option { /// Get rest arguments from given `call`, starts with `starting_pos`. /// -/// It's similar to `call.rest`, except that it always returns NuGlob. And if input argument has -/// Type::Glob, the NuGlob is unquoted, which means it's required to expand. +/// It's similar to `call.rest`, except that it always returns NuGlob. pub fn get_rest_for_glob_pattern( engine_state: &EngineState, stack: &mut Stack, call: &Call, starting_pos: usize, ) -> Result>, ShellError> { - let mut output = vec![]; let eval_expression = get_eval_expression(engine_state); - for result in call.rest_iter_flattened(starting_pos, |expr| { - let result = eval_expression(engine_state, stack, expr); - match result { - Err(e) => Err(e), - Ok(result) => { - let span = result.span(); - // convert from string to quoted string if expr is a variable - // or string interpolation - match result { - Value::String { val, .. } - if matches!( - &expr.expr, - Expr::FullCellPath(_) | Expr::StringInterpolation(_) - ) => - { - // should not expand if given input type is not glob. - Ok(Value::glob(val, expr.ty != Type::Glob, span)) - } - other => Ok(other), - } - } - } - })? { - output.push(FromValue::from_value(result)?); - } - - Ok(output) + call.rest_iter_flattened(engine_state, stack, eval_expression, starting_pos)? + .into_iter() + // This used to be much more complex, but I think `FromValue` should be able to handle the + // nuance here. + .map(FromValue::from_value) + .collect() } diff --git a/crates/nu-command/src/filters/transpose.rs b/crates/nu-command/src/filters/transpose.rs index 95aa382e4f..4a14c1aea2 100644 --- a/crates/nu-command/src/filters/transpose.rs +++ b/crates/nu-command/src/filters/transpose.rs @@ -149,27 +149,27 @@ pub fn transpose( if !args.rest.is_empty() && args.header_row { return Err(ShellError::IncompatibleParametersSingle { msg: "Can not provide header names and use `--header-row`".into(), - span: call.get_named_arg("header-row").expect("has flag").span, + span: call.get_flag_span(stack, "header-row").expect("has flag"), }); } if !args.header_row && args.keep_all { return Err(ShellError::IncompatibleParametersSingle { msg: "Can only be used with `--header-row`(`-r`)".into(), - span: call.get_named_arg("keep-all").expect("has flag").span, + span: call.get_flag_span(stack, "keep-all").expect("has flag"), }); } if !args.header_row && args.keep_last { return Err(ShellError::IncompatibleParametersSingle { msg: "Can only be used with `--header-row`(`-r`)".into(), - span: call.get_named_arg("keep-last").expect("has flag").span, + span: call.get_flag_span(stack, "keep-last").expect("has flag"), }); } if args.keep_all && args.keep_last { return Err(ShellError::IncompatibleParameters { left_message: "can't use `--keep-last` at the same time".into(), - left_span: call.get_named_arg("keep-last").expect("has flag").span, + left_span: call.get_flag_span(stack, "keep-last").expect("has flag"), right_message: "because of `--keep-all`".into(), - right_span: call.get_named_arg("keep-all").expect("has flag").span, + right_span: call.get_flag_span(stack, "keep-all").expect("has flag"), }); } diff --git a/crates/nu-command/src/filters/utils.rs b/crates/nu-command/src/filters/utils.rs index 3ebd4bafbd..4c67667e8e 100644 --- a/crates/nu-command/src/filters/utils.rs +++ b/crates/nu-command/src/filters/utils.rs @@ -1,7 +1,6 @@ use nu_engine::{CallExt, ClosureEval}; use nu_protocol::{ - ast::Call, - engine::{Closure, EngineState, Stack}, + engine::{Call, Closure, EngineState, Stack}, IntoPipelineData, PipelineData, ShellError, Span, Value, }; diff --git a/crates/nu-command/src/generators/cal.rs b/crates/nu-command/src/generators/cal.rs index a257f3ab7c..018d9370dd 100644 --- a/crates/nu-command/src/generators/cal.rs +++ b/crates/nu-command/src/generators/cal.rs @@ -1,7 +1,7 @@ use chrono::{Datelike, Local, NaiveDate}; use nu_color_config::StyleComputer; use nu_engine::command_prelude::*; -use nu_protocol::ast::{Expr, Expression}; +use nu_protocol::ast::{self, Expr, Expression}; use std::collections::VecDeque; @@ -143,7 +143,7 @@ pub fn cal( style_computer, )?; - let mut table_no_index = Call::new(Span::unknown()); + let mut table_no_index = ast::Call::new(Span::unknown()); table_no_index.add_named(( Spanned { item: "index".to_string(), @@ -160,7 +160,12 @@ pub fn cal( let cal_table_output = Value::list(calendar_vec_deque.into_iter().collect(), tag).into_pipeline_data(); if !arguments.as_table { - crate::Table.run(engine_state, stack, &table_no_index, cal_table_output) + crate::Table.run( + engine_state, + stack, + &(&table_no_index).into(), + cal_table_output, + ) } else { Ok(cal_table_output) } diff --git a/crates/nu-command/src/math/utils.rs b/crates/nu-command/src/math/utils.rs index 765c1f42fb..62f96ea073 100644 --- a/crates/nu-command/src/math/utils.rs +++ b/crates/nu-command/src/math/utils.rs @@ -1,6 +1,6 @@ use core::slice; use indexmap::IndexMap; -use nu_protocol::{ast::Call, IntoPipelineData, PipelineData, ShellError, Signals, Span, Value}; +use nu_protocol::{engine::Call, IntoPipelineData, PipelineData, ShellError, Signals, Span, Value}; pub fn run_with_function( call: &Call, diff --git a/crates/nu-command/src/platform/ansi/ansi_.rs b/crates/nu-command/src/platform/ansi/ansi_.rs index 29603be9e7..23161eb7bf 100644 --- a/crates/nu-command/src/platform/ansi/ansi_.rs +++ b/crates/nu-command/src/platform/ansi/ansi_.rs @@ -676,7 +676,7 @@ Operating system commands: } }; - let output = heavy_lifting(code, escape, osc, call)?; + let output = heavy_lifting(code, escape, osc, stack, call)?; Ok(Value::string(output, call.head).into_pipeline_data()) } @@ -713,26 +713,30 @@ Operating system commands: } }; - let output = heavy_lifting(code, escape, osc, call)?; + let output = heavy_lifting(code, escape, osc, &Stack::new(), call)?; Ok(Value::string(output, call.head).into_pipeline_data()) } } -fn heavy_lifting(code: Value, escape: bool, osc: bool, call: &Call) -> Result { +fn heavy_lifting( + code: Value, + escape: bool, + osc: bool, + stack: &Stack, + call: &Call, +) -> Result { let param_is_string = matches!(code, Value::String { .. }); if escape && osc { return Err(ShellError::IncompatibleParameters { left_message: "escape".into(), left_span: call - .get_named_arg("escape") - .expect("Unexpected missing argument") - .span, + .get_flag_span(stack, "escape") + .expect("Unexpected missing argument"), right_message: "osc".into(), right_span: call - .get_named_arg("osc") - .expect("Unexpected missing argument") - .span, + .get_flag_span(stack, "osc") + .expect("Unexpected missing argument"), }); } let code_string = if param_is_string { @@ -744,10 +748,7 @@ fn heavy_lifting(code: Value, escape: bool, osc: bool, call: &Call) -> Result = code_string.chars().collect(); if code_vec[0] == '\\' { - let span = match call.get_flag_expr("escape") { - Some(expr) => expr.span, - None => call.head, - }; + let span = call.get_flag_span(stack, "escape").unwrap_or(call.head); return Err(ShellError::TypeMismatch { err_message: "no need for escape characters".into(), diff --git a/crates/nu-command/src/platform/is_terminal.rs b/crates/nu-command/src/platform/is_terminal.rs index c67329e839..2195f3ff8a 100644 --- a/crates/nu-command/src/platform/is_terminal.rs +++ b/crates/nu-command/src/platform/is_terminal.rs @@ -58,7 +58,7 @@ impl Command for IsTerminal { _ => { return Err(ShellError::IncompatibleParametersSingle { msg: "Only one stream may be checked".into(), - span: Span::merge_many(call.arguments.iter().map(|arg| arg.span())), + span: call.arguments_span(), }); } }; diff --git a/crates/nu-command/src/platform/kill.rs b/crates/nu-command/src/platform/kill.rs index 2e47ee8c78..1cf6f15f01 100644 --- a/crates/nu-command/src/platform/kill.rs +++ b/crates/nu-command/src/platform/kill.rs @@ -84,27 +84,26 @@ impl Command for Kill { { return Err(ShellError::IncompatibleParameters { left_message: "force".to_string(), - left_span: call - .get_named_arg("force") - .ok_or_else(|| ShellError::GenericError { + left_span: call.get_flag_span(stack, "force").ok_or_else(|| { + ShellError::GenericError { error: "Flag error".into(), msg: "flag force not found".into(), span: Some(call.head), help: None, inner: vec![], - })? - .span, + } + })?, right_message: "signal".to_string(), right_span: Span::merge( - call.get_named_arg("signal") - .ok_or_else(|| ShellError::GenericError { + call.get_flag_span(stack, "signal").ok_or_else(|| { + ShellError::GenericError { error: "Flag error".into(), msg: "flag signal not found".into(), span: Some(call.head), help: None, inner: vec![], - })? - .span, + } + })?, signal_span, ), }); diff --git a/crates/nu-command/src/strings/encode_decode/base64.rs b/crates/nu-command/src/strings/encode_decode/base64.rs index afc143983e..dd9289a141 100644 --- a/crates/nu-command/src/strings/encode_decode/base64.rs +++ b/crates/nu-command/src/strings/encode_decode/base64.rs @@ -8,8 +8,8 @@ use base64::{ }; use nu_cmd_base::input_handler::{operate as general_operate, CmdArgument}; use nu_protocol::{ - ast::{Call, CellPath}, - engine::EngineState, + ast::CellPath, + engine::{Call, EngineState}, PipelineData, ShellError, Span, Spanned, Value, }; diff --git a/crates/nu-command/src/strings/mod.rs b/crates/nu-command/src/strings/mod.rs index d1ebf540e5..8b5af2dec4 100644 --- a/crates/nu-command/src/strings/mod.rs +++ b/crates/nu-command/src/strings/mod.rs @@ -17,8 +17,7 @@ pub use str_::*; use nu_engine::CallExt; use nu_protocol::{ - ast::Call, - engine::{EngineState, Stack, StateWorkingSet}, + engine::{Call, EngineState, Stack, StateWorkingSet}, ShellError, }; diff --git a/crates/nu-command/src/system/nu_check.rs b/crates/nu-command/src/system/nu_check.rs index f9e0879c00..334569c79e 100644 --- a/crates/nu-command/src/system/nu_check.rs +++ b/crates/nu-command/src/system/nu_check.rs @@ -87,7 +87,7 @@ impl Command for NuCheck { &path_str.item, engine_state, stack, - get_dirs_var_from_call(call), + get_dirs_var_from_call(stack, call), ) { Ok(path) => { if let Some(path) = path { diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs index 06bc5a69ca..a5cf343970 100644 --- a/crates/nu-command/src/system/run_external.rs +++ b/crates/nu-command/src/system/run_external.rs @@ -1,9 +1,7 @@ use nu_cmd_base::hook::eval_hook; use nu_engine::{command_prelude::*, env_to_strings, get_eval_expression}; use nu_path::{dots::expand_ndots, expand_tilde}; -use nu_protocol::{ - ast::Expression, did_you_mean, process::ChildProcess, ByteStream, NuGlob, OutDest, Signals, -}; +use nu_protocol::{did_you_mean, process::ChildProcess, ByteStream, NuGlob, OutDest, Signals}; use nu_system::ForegroundChild; use nu_utils::IgnoreCaseExt; use pathdiff::diff_paths; @@ -222,20 +220,21 @@ pub fn eval_arguments_from_call( call: &Call, ) -> Result>, ShellError> { let cwd = engine_state.cwd(Some(stack))?; - let mut args: Vec> = vec![]; - for (expr, spread) in call.rest_iter(1) { - for arg in eval_argument(engine_state, stack, expr, spread)? { - match arg { - // Expand globs passed to run-external - Value::Glob { val, no_expand, .. } if !no_expand => args.extend( - expand_glob(&val, cwd.as_ref(), expr.span, engine_state.signals())? - .into_iter() - .map(|s| s.into_spanned(expr.span)), - ), - other => { - args.push(OsString::from(coerce_into_string(other)?).into_spanned(expr.span)) - } - } + let eval_expression = get_eval_expression(engine_state); + let call_args = call.rest_iter_flattened(engine_state, stack, eval_expression, 1)?; + let mut args: Vec> = Vec::with_capacity(call_args.len()); + + for arg in call_args { + let span = arg.span(); + match arg { + // Expand globs passed to run-external + Value::Glob { val, no_expand, .. } if !no_expand => args.extend( + expand_glob(&val, cwd.as_std_path(), span, engine_state.signals())? + .into_iter() + .map(|s| s.into_spanned(span)), + ), + other => args + .push(OsString::from(coerce_into_string(engine_state, other)?).into_spanned(span)), } } Ok(args) @@ -243,42 +242,17 @@ pub fn eval_arguments_from_call( /// Custom `coerce_into_string()`, including globs, since those are often args to `run-external` /// as well -fn coerce_into_string(val: Value) -> Result { +fn coerce_into_string(engine_state: &EngineState, val: Value) -> Result { match val { + Value::List { .. } => Err(ShellError::CannotPassListToExternal { + arg: String::from_utf8_lossy(engine_state.get_span_contents(val.span())).into_owned(), + span: val.span(), + }), Value::Glob { val, .. } => Ok(val), _ => val.coerce_into_string(), } } -/// Evaluate an argument, returning more than one value if it was a list to be spread. -fn eval_argument( - engine_state: &EngineState, - stack: &mut Stack, - expr: &Expression, - spread: bool, -) -> Result, ShellError> { - let eval = get_eval_expression(engine_state); - match eval(engine_state, stack, expr)? { - Value::List { vals, .. } => { - if spread { - Ok(vals) - } else { - Err(ShellError::CannotPassListToExternal { - arg: String::from_utf8_lossy(engine_state.get_span_contents(expr.span)).into(), - span: expr.span, - }) - } - } - value => { - if spread { - Err(ShellError::CannotSpreadAsList { span: expr.span }) - } else { - Ok(vec![value]) - } - } - } -} - /// Performs glob expansion on `arg`. If the expansion found no matches or the pattern /// is not a valid glob, then this returns the original string as the expansion result. /// diff --git a/crates/nu-command/src/system/uname.rs b/crates/nu-command/src/system/uname.rs index e267fcaeb2..0bcb749f02 100644 --- a/crates/nu-command/src/system/uname.rs +++ b/crates/nu-command/src/system/uname.rs @@ -1,10 +1,5 @@ -use nu_protocol::record; -use nu_protocol::Value; -use nu_protocol::{ - ast::Call, - engine::{Command, EngineState, Stack}, - Category, Example, PipelineData, ShellError, Signature, Type, -}; +use nu_engine::command_prelude::*; +use nu_protocol::{record, Value}; #[derive(Clone)] pub struct UName; diff --git a/crates/nu-command/src/viewers/table.rs b/crates/nu-command/src/viewers/table.rs index e3738a3952..190c3659af 100644 --- a/crates/nu-command/src/viewers/table.rs +++ b/crates/nu-command/src/viewers/table.rs @@ -344,7 +344,7 @@ fn get_theme_flag( struct CmdInput<'a> { engine_state: &'a EngineState, stack: &'a mut Stack, - call: &'a Call, + call: &'a Call<'a>, data: PipelineData, } @@ -352,7 +352,7 @@ impl<'a> CmdInput<'a> { fn new( engine_state: &'a EngineState, stack: &'a mut Stack, - call: &'a Call, + call: &'a Call<'a>, data: PipelineData, ) -> Self { Self { diff --git a/crates/nu-engine/Cargo.toml b/crates/nu-engine/Cargo.toml index 3e6c3f787b..c416a2f590 100644 --- a/crates/nu-engine/Cargo.toml +++ b/crates/nu-engine/Cargo.toml @@ -15,6 +15,7 @@ nu-protocol = { path = "../nu-protocol", features = ["plugin"], version = "0.95. nu-path = { path = "../nu-path", version = "0.95.1" } nu-glob = { path = "../nu-glob", version = "0.95.1" } nu-utils = { path = "../nu-utils", version = "0.95.1" } +log = { workspace = true } [features] -plugin = [] \ No newline at end of file +plugin = [] diff --git a/crates/nu-engine/src/call_ext.rs b/crates/nu-engine/src/call_ext.rs index daedb24a1f..d3f36215e6 100644 --- a/crates/nu-engine/src/call_ext.rs +++ b/crates/nu-engine/src/call_ext.rs @@ -1,10 +1,10 @@ use crate::eval_expression; use nu_protocol::{ - ast::Call, + ast, debugger::WithoutDebug, - engine::{EngineState, Stack, StateWorkingSet}, + engine::{self, EngineState, Stack, StateWorkingSet}, eval_const::eval_constant, - FromValue, ShellError, Value, + ir, FromValue, ShellError, Span, Value, }; pub trait CallExt { @@ -23,6 +23,9 @@ pub trait CallExt { name: &str, ) -> Result, ShellError>; + /// Efficiently get the span of a flag argument + fn get_flag_span(&self, stack: &Stack, name: &str) -> Option; + fn rest( &self, engine_state: &EngineState, @@ -56,9 +59,12 @@ pub trait CallExt { stack: &mut Stack, name: &str, ) -> Result; + + /// True if the command has any positional or rest arguments, excluding before the given index. + fn has_positional_args(&self, stack: &Stack, starting_pos: usize) -> bool; } -impl CallExt for Call { +impl CallExt for ast::Call { fn has_flag( &self, engine_state: &EngineState, @@ -104,6 +110,10 @@ impl CallExt for Call { } } + fn get_flag_span(&self, _stack: &Stack, name: &str) -> Option { + self.get_named_arg(name).map(|arg| arg.span) + } + fn rest( &self, engine_state: &EngineState, @@ -189,4 +199,205 @@ impl CallExt for Call { }) } } + + fn has_positional_args(&self, _stack: &Stack, starting_pos: usize) -> bool { + self.rest_iter(starting_pos).next().is_some() + } +} + +impl CallExt for ir::Call { + fn has_flag( + &self, + _engine_state: &EngineState, + stack: &mut Stack, + flag_name: &str, + ) -> Result { + Ok(self + .named_iter(stack) + .find(|(name, _)| name.item == flag_name) + .is_some_and(|(_, value)| { + // Handle --flag=false + !matches!(value, Some(Value::Bool { val: false, .. })) + })) + } + + fn get_flag( + &self, + _engine_state: &EngineState, + stack: &mut Stack, + name: &str, + ) -> Result, ShellError> { + if let Some(val) = self.get_named_arg(stack, name) { + T::from_value(val.clone()).map(Some) + } else { + Ok(None) + } + } + + fn get_flag_span(&self, stack: &Stack, name: &str) -> Option { + self.named_iter(stack) + .find_map(|(i_name, _)| (i_name.item == name).then_some(i_name.span)) + } + + fn rest( + &self, + _engine_state: &EngineState, + stack: &mut Stack, + starting_pos: usize, + ) -> Result, ShellError> { + self.rest_iter_flattened(stack, starting_pos)? + .into_iter() + .map(T::from_value) + .collect() + } + + fn opt( + &self, + _engine_state: &EngineState, + stack: &mut Stack, + pos: usize, + ) -> Result, ShellError> { + self.positional_iter(stack) + .nth(pos) + .cloned() + .map(T::from_value) + .transpose() + } + + fn opt_const( + &self, + _working_set: &StateWorkingSet, + _pos: usize, + ) -> Result, ShellError> { + Err(ShellError::IrEvalError { + msg: "const evaluation is not yet implemented on ir::Call".into(), + span: Some(self.head), + }) + } + + fn req( + &self, + engine_state: &EngineState, + stack: &mut Stack, + pos: usize, + ) -> Result { + if let Some(val) = self.opt(engine_state, stack, pos)? { + Ok(val) + } else if self.positional_len(stack) == 0 { + Err(ShellError::AccessEmptyContent { span: self.head }) + } else { + Err(ShellError::AccessBeyondEnd { + max_idx: self.positional_len(stack) - 1, + span: self.head, + }) + } + } + + fn req_parser_info( + &self, + engine_state: &EngineState, + stack: &mut Stack, + name: &str, + ) -> Result { + // FIXME: this depends on the AST evaluator. We can fix this by making the parser info an + // enum rather than using expressions. It's not clear that evaluation of this is ever really + // needed. + if let Some(expr) = self.get_parser_info(stack, name) { + let expr = expr.clone(); + let stack = &mut stack.use_call_arg_out_dest(); + let result = eval_expression::(engine_state, stack, &expr)?; + FromValue::from_value(result) + } else { + Err(ShellError::CantFindColumn { + col_name: name.into(), + span: None, + src_span: self.head, + }) + } + } + + fn has_positional_args(&self, stack: &Stack, starting_pos: usize) -> bool { + self.rest_iter(stack, starting_pos).next().is_some() + } +} + +macro_rules! proxy { + ($self:ident . $method:ident ($($param:expr),*)) => (match &$self.inner { + engine::CallImpl::AstRef(call) => call.$method($($param),*), + engine::CallImpl::AstBox(call) => call.$method($($param),*), + engine::CallImpl::IrRef(call) => call.$method($($param),*), + engine::CallImpl::IrBox(call) => call.$method($($param),*), + }) +} + +impl CallExt for engine::Call<'_> { + fn has_flag( + &self, + engine_state: &EngineState, + stack: &mut Stack, + flag_name: &str, + ) -> Result { + proxy!(self.has_flag(engine_state, stack, flag_name)) + } + + fn get_flag( + &self, + engine_state: &EngineState, + stack: &mut Stack, + name: &str, + ) -> Result, ShellError> { + proxy!(self.get_flag(engine_state, stack, name)) + } + + fn get_flag_span(&self, stack: &Stack, name: &str) -> Option { + proxy!(self.get_flag_span(stack, name)) + } + + fn rest( + &self, + engine_state: &EngineState, + stack: &mut Stack, + starting_pos: usize, + ) -> Result, ShellError> { + proxy!(self.rest(engine_state, stack, starting_pos)) + } + + fn opt( + &self, + engine_state: &EngineState, + stack: &mut Stack, + pos: usize, + ) -> Result, ShellError> { + proxy!(self.opt(engine_state, stack, pos)) + } + + fn opt_const( + &self, + working_set: &StateWorkingSet, + pos: usize, + ) -> Result, ShellError> { + proxy!(self.opt_const(working_set, pos)) + } + + fn req( + &self, + engine_state: &EngineState, + stack: &mut Stack, + pos: usize, + ) -> Result { + proxy!(self.req(engine_state, stack, pos)) + } + + fn req_parser_info( + &self, + engine_state: &EngineState, + stack: &mut Stack, + name: &str, + ) -> Result { + proxy!(self.req_parser_info(engine_state, stack, name)) + } + + fn has_positional_args(&self, stack: &Stack, starting_pos: usize) -> bool { + proxy!(self.has_positional_args(stack, starting_pos)) + } } diff --git a/crates/nu-engine/src/command_prelude.rs b/crates/nu-engine/src/command_prelude.rs index 5c21af27e0..e6ddb5fb91 100644 --- a/crates/nu-engine/src/command_prelude.rs +++ b/crates/nu-engine/src/command_prelude.rs @@ -1,7 +1,7 @@ pub use crate::CallExt; pub use nu_protocol::{ - ast::{Call, CellPath}, - engine::{Command, EngineState, Stack, StateWorkingSet}, + ast::CellPath, + engine::{Call, Command, EngineState, Stack, StateWorkingSet}, record, ByteStream, ByteStreamType, Category, ErrSpan, Example, IntoInterruptiblePipelineData, IntoPipelineData, IntoSpanned, PipelineData, Record, ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value, diff --git a/crates/nu-engine/src/compile/builder.rs b/crates/nu-engine/src/compile/builder.rs new file mode 100644 index 0000000000..d77075cc3f --- /dev/null +++ b/crates/nu-engine/src/compile/builder.rs @@ -0,0 +1,575 @@ +use nu_protocol::{ + ir::{DataSlice, Instruction, IrAstRef, IrBlock, Literal}, + CompileError, IntoSpanned, RegId, Span, Spanned, +}; + +/// A label identifier. Only exists while building code. Replaced with the actual target. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) struct LabelId(pub usize); + +/// Builds [`IrBlock`]s progressively by consuming instructions and handles register allocation. +#[derive(Debug)] +pub(crate) struct BlockBuilder { + pub(crate) block_span: Option, + pub(crate) instructions: Vec, + pub(crate) spans: Vec, + /// The actual instruction index that a label refers to. While building IR, branch targets are + /// specified as indices into this array rather than the true instruction index. This makes it + /// easier to make modifications to code, as just this array needs to be changed, and it's also + /// less error prone as during `finish()` we check to make sure all of the used labels have had + /// an index actually set. + pub(crate) labels: Vec>, + pub(crate) data: Vec, + pub(crate) ast: Vec>, + pub(crate) comments: Vec, + pub(crate) register_allocation_state: Vec, + pub(crate) file_count: u32, + pub(crate) loop_stack: Vec, +} + +impl BlockBuilder { + /// Starts a new block, with the first register (`%0`) allocated as input. + pub(crate) fn new(block_span: Option) -> Self { + BlockBuilder { + block_span, + instructions: vec![], + spans: vec![], + labels: vec![], + data: vec![], + ast: vec![], + comments: vec![], + register_allocation_state: vec![true], + file_count: 0, + loop_stack: vec![], + } + } + + /// Get the next unused register for code generation. + pub(crate) fn next_register(&mut self) -> Result { + if let Some(index) = self + .register_allocation_state + .iter_mut() + .position(|is_allocated| { + if !*is_allocated { + *is_allocated = true; + true + } else { + false + } + }) + { + Ok(RegId(index as u32)) + } else if self.register_allocation_state.len() < (u32::MAX as usize - 2) { + let reg_id = RegId(self.register_allocation_state.len() as u32); + self.register_allocation_state.push(true); + Ok(reg_id) + } else { + Err(CompileError::RegisterOverflow { + block_span: self.block_span, + }) + } + } + + /// Check if a register is initialized with a value. + pub(crate) fn is_allocated(&self, reg_id: RegId) -> bool { + self.register_allocation_state + .get(reg_id.0 as usize) + .is_some_and(|state| *state) + } + + /// Mark a register as initialized. + pub(crate) fn mark_register(&mut self, reg_id: RegId) -> Result<(), CompileError> { + if let Some(is_allocated) = self.register_allocation_state.get_mut(reg_id.0 as usize) { + *is_allocated = true; + Ok(()) + } else { + Err(CompileError::RegisterOverflow { + block_span: self.block_span, + }) + } + } + + /// Mark a register as empty, so that it can be used again by something else. + #[track_caller] + pub(crate) fn free_register(&mut self, reg_id: RegId) -> Result<(), CompileError> { + let index = reg_id.0 as usize; + + if self + .register_allocation_state + .get(index) + .is_some_and(|is_allocated| *is_allocated) + { + self.register_allocation_state[index] = false; + Ok(()) + } else { + log::warn!("register {reg_id} uninitialized, builder = {self:#?}"); + Err(CompileError::RegisterUninitialized { + reg_id, + caller: std::panic::Location::caller().to_string(), + }) + } + } + + /// Define a label, which can be used by branch instructions. The target can optionally be + /// specified now. + pub(crate) fn label(&mut self, target_index: Option) -> LabelId { + let label_id = self.labels.len(); + self.labels.push(target_index); + LabelId(label_id) + } + + /// Change the target of a label. + pub(crate) fn set_label( + &mut self, + label_id: LabelId, + target_index: usize, + ) -> Result<(), CompileError> { + *self + .labels + .get_mut(label_id.0) + .ok_or(CompileError::UndefinedLabel { + label_id: label_id.0, + span: None, + })? = Some(target_index); + Ok(()) + } + + /// Insert an instruction into the block, automatically marking any registers populated by + /// the instruction, and freeing any registers consumed by the instruction. + #[track_caller] + pub(crate) fn push(&mut self, instruction: Spanned) -> Result<(), CompileError> { + // Free read registers, and mark write registers. + // + // If a register is both read and written, it should be on both sides, so that we can verify + // that the register was in the right state beforehand. + let mut allocate = |read: &[RegId], write: &[RegId]| -> Result<(), CompileError> { + for reg in read { + self.free_register(*reg)?; + } + for reg in write { + self.mark_register(*reg)?; + } + Ok(()) + }; + + let allocate_result = match &instruction.item { + Instruction::Unreachable => Ok(()), + Instruction::LoadLiteral { dst, lit } => { + allocate(&[], &[*dst]).and( + // Free any registers on the literal + match lit { + Literal::Range { + start, + step, + end, + inclusion: _, + } => allocate(&[*start, *step, *end], &[]), + Literal::Bool(_) + | Literal::Int(_) + | Literal::Float(_) + | Literal::Filesize(_) + | Literal::Duration(_) + | Literal::Binary(_) + | Literal::Block(_) + | Literal::Closure(_) + | Literal::RowCondition(_) + | Literal::List { capacity: _ } + | Literal::Record { capacity: _ } + | Literal::Filepath { + val: _, + no_expand: _, + } + | Literal::Directory { + val: _, + no_expand: _, + } + | Literal::GlobPattern { + val: _, + no_expand: _, + } + | Literal::String(_) + | Literal::RawString(_) + | Literal::CellPath(_) + | Literal::Date(_) + | Literal::Nothing => Ok(()), + }, + ) + } + Instruction::LoadValue { dst, val: _ } => allocate(&[], &[*dst]), + Instruction::Move { dst, src } => allocate(&[*src], &[*dst]), + Instruction::Clone { dst, src } => allocate(&[*src], &[*dst, *src]), + Instruction::Collect { src_dst } => allocate(&[*src_dst], &[*src_dst]), + Instruction::Span { src_dst } => allocate(&[*src_dst], &[*src_dst]), + Instruction::Drop { src } => allocate(&[*src], &[]), + Instruction::Drain { src } => allocate(&[*src], &[]), + Instruction::LoadVariable { dst, var_id: _ } => allocate(&[], &[*dst]), + Instruction::StoreVariable { var_id: _, src } => allocate(&[*src], &[]), + Instruction::LoadEnv { dst, key: _ } => allocate(&[], &[*dst]), + Instruction::LoadEnvOpt { dst, key: _ } => allocate(&[], &[*dst]), + Instruction::StoreEnv { key: _, src } => allocate(&[*src], &[]), + Instruction::PushPositional { src } => allocate(&[*src], &[]), + Instruction::AppendRest { src } => allocate(&[*src], &[]), + Instruction::PushFlag { name: _ } => Ok(()), + Instruction::PushShortFlag { short: _ } => Ok(()), + Instruction::PushNamed { name: _, src } => allocate(&[*src], &[]), + Instruction::PushShortNamed { short: _, src } => allocate(&[*src], &[]), + Instruction::PushParserInfo { name: _, info: _ } => Ok(()), + Instruction::RedirectOut { mode: _ } => Ok(()), + Instruction::RedirectErr { mode: _ } => Ok(()), + Instruction::CheckErrRedirected { src } => allocate(&[*src], &[*src]), + Instruction::OpenFile { + file_num: _, + path, + append: _, + } => allocate(&[*path], &[]), + Instruction::WriteFile { file_num: _, src } => allocate(&[*src], &[]), + Instruction::CloseFile { file_num: _ } => Ok(()), + Instruction::Call { + decl_id: _, + src_dst, + } => allocate(&[*src_dst], &[*src_dst]), + Instruction::StringAppend { src_dst, val } => allocate(&[*src_dst, *val], &[*src_dst]), + Instruction::GlobFrom { + src_dst, + no_expand: _, + } => allocate(&[*src_dst], &[*src_dst]), + Instruction::ListPush { src_dst, item } => allocate(&[*src_dst, *item], &[*src_dst]), + Instruction::ListSpread { src_dst, items } => { + allocate(&[*src_dst, *items], &[*src_dst]) + } + Instruction::RecordInsert { src_dst, key, val } => { + allocate(&[*src_dst, *key, *val], &[*src_dst]) + } + Instruction::RecordSpread { src_dst, items } => { + allocate(&[*src_dst, *items], &[*src_dst]) + } + Instruction::Not { src_dst } => allocate(&[*src_dst], &[*src_dst]), + Instruction::BinaryOp { + lhs_dst, + op: _, + rhs, + } => allocate(&[*lhs_dst, *rhs], &[*lhs_dst]), + Instruction::FollowCellPath { src_dst, path } => { + allocate(&[*src_dst, *path], &[*src_dst]) + } + Instruction::CloneCellPath { dst, src, path } => { + allocate(&[*src, *path], &[*src, *dst]) + } + Instruction::UpsertCellPath { + src_dst, + path, + new_value, + } => allocate(&[*src_dst, *path, *new_value], &[*src_dst]), + Instruction::Jump { index: _ } => Ok(()), + Instruction::BranchIf { cond, index: _ } => allocate(&[*cond], &[]), + Instruction::BranchIfEmpty { src, index: _ } => allocate(&[*src], &[*src]), + Instruction::Match { + pattern: _, + src, + index: _, + } => allocate(&[*src], &[*src]), + Instruction::CheckMatchGuard { src } => allocate(&[*src], &[*src]), + Instruction::Iterate { + dst, + stream, + end_index: _, + } => allocate(&[*stream], &[*dst, *stream]), + Instruction::OnError { index: _ } => Ok(()), + Instruction::OnErrorInto { index: _, dst } => allocate(&[], &[*dst]), + Instruction::PopErrorHandler => Ok(()), + Instruction::CheckExternalFailed { dst, src } => allocate(&[*src], &[*dst, *src]), + Instruction::ReturnEarly { src } => allocate(&[*src], &[]), + Instruction::Return { src } => allocate(&[*src], &[]), + }; + + // Add more context to the error + match allocate_result { + Ok(()) => (), + Err(CompileError::RegisterUninitialized { reg_id, caller }) => { + return Err(CompileError::RegisterUninitializedWhilePushingInstruction { + reg_id, + caller, + instruction: format!("{:?}", instruction.item), + span: instruction.span, + }); + } + Err(err) => return Err(err), + } + + self.instructions.push(instruction.item); + self.spans.push(instruction.span); + self.ast.push(None); + self.comments.push(String::new()); + Ok(()) + } + + /// Set the AST of the last instruction. Separate method because it's rarely used. + pub(crate) fn set_last_ast(&mut self, ast_ref: Option) { + *self.ast.last_mut().expect("no last instruction") = ast_ref; + } + + /// Add a comment to the last instruction. + pub(crate) fn add_comment(&mut self, comment: impl std::fmt::Display) { + add_comment( + self.comments.last_mut().expect("no last instruction"), + comment, + ) + } + + /// Load a register with a literal. + pub(crate) fn load_literal( + &mut self, + reg_id: RegId, + literal: Spanned, + ) -> Result<(), CompileError> { + self.push( + Instruction::LoadLiteral { + dst: reg_id, + lit: literal.item, + } + .into_spanned(literal.span), + )?; + Ok(()) + } + + /// Allocate a new register and load a literal into it. + pub(crate) fn literal(&mut self, literal: Spanned) -> Result { + let reg_id = self.next_register()?; + self.load_literal(reg_id, literal)?; + Ok(reg_id) + } + + /// Deallocate a register and set it to `Empty`, if it is allocated + pub(crate) fn drop_reg(&mut self, reg_id: RegId) -> Result<(), CompileError> { + if self.is_allocated(reg_id) { + self.push(Instruction::Drop { src: reg_id }.into_spanned(Span::unknown()))?; + } + Ok(()) + } + + /// Set a register to `Empty`, but mark it as in-use, e.g. for input + pub(crate) fn load_empty(&mut self, reg_id: RegId) -> Result<(), CompileError> { + self.drop_reg(reg_id)?; + self.mark_register(reg_id) + } + + /// Drain the stream in a register (fully consuming it) + pub(crate) fn drain(&mut self, src: RegId, span: Span) -> Result<(), CompileError> { + self.push(Instruction::Drain { src }.into_spanned(span)) + } + + /// Add data to the `data` array and return a [`DataSlice`] referencing it. + pub(crate) fn data(&mut self, data: impl AsRef<[u8]>) -> Result { + let data = data.as_ref(); + let start = self.data.len(); + if data.is_empty() { + Ok(DataSlice::empty()) + } else if start + data.len() < u32::MAX as usize { + let slice = DataSlice { + start: start as u32, + len: data.len() as u32, + }; + self.data.extend_from_slice(data); + Ok(slice) + } else { + Err(CompileError::DataOverflow { + block_span: self.block_span, + }) + } + } + + /// Clone a register with a `clone` instruction. + pub(crate) fn clone_reg(&mut self, src: RegId, span: Span) -> Result { + let dst = self.next_register()?; + self.push(Instruction::Clone { dst, src }.into_spanned(span))?; + Ok(dst) + } + + /// Add a `branch-if` instruction + pub(crate) fn branch_if( + &mut self, + cond: RegId, + label_id: LabelId, + span: Span, + ) -> Result<(), CompileError> { + self.push( + Instruction::BranchIf { + cond, + index: label_id.0, + } + .into_spanned(span), + ) + } + + /// Add a `branch-if-empty` instruction + pub(crate) fn branch_if_empty( + &mut self, + src: RegId, + label_id: LabelId, + span: Span, + ) -> Result<(), CompileError> { + self.push( + Instruction::BranchIfEmpty { + src, + index: label_id.0, + } + .into_spanned(span), + ) + } + + /// Add a `jump` instruction + pub(crate) fn jump(&mut self, label_id: LabelId, span: Span) -> Result<(), CompileError> { + self.push(Instruction::Jump { index: label_id.0 }.into_spanned(span)) + } + + /// The index that the next instruction [`.push()`]ed will have. + pub(crate) fn here(&self) -> usize { + self.instructions.len() + } + + /// Allocate a new file number, for redirection. + pub(crate) fn next_file_num(&mut self) -> Result { + let next = self.file_count; + self.file_count = self + .file_count + .checked_add(1) + .ok_or(CompileError::FileOverflow { + block_span: self.block_span, + })?; + Ok(next) + } + + /// Push a new loop state onto the builder. Creates new labels that must be set. + pub(crate) fn begin_loop(&mut self) -> Loop { + let loop_ = Loop { + break_label: self.label(None), + continue_label: self.label(None), + }; + self.loop_stack.push(loop_); + loop_ + } + + /// True if we are currently in a loop. + pub(crate) fn is_in_loop(&self) -> bool { + !self.loop_stack.is_empty() + } + + /// Add a loop breaking jump instruction. + pub(crate) fn push_break(&mut self, span: Span) -> Result<(), CompileError> { + let loop_ = self + .loop_stack + .last() + .ok_or_else(|| CompileError::NotInALoop { + msg: "`break` called from outside of a loop".into(), + span: Some(span), + })?; + self.jump(loop_.break_label, span) + } + + /// Add a loop continuing jump instruction. + pub(crate) fn push_continue(&mut self, span: Span) -> Result<(), CompileError> { + let loop_ = self + .loop_stack + .last() + .ok_or_else(|| CompileError::NotInALoop { + msg: "`continue` called from outside of a loop".into(), + span: Some(span), + })?; + self.jump(loop_.continue_label, span) + } + + /// Pop the loop state. Checks that the loop being ended is the same one that was expected. + pub(crate) fn end_loop(&mut self, loop_: Loop) -> Result<(), CompileError> { + let ended_loop = self + .loop_stack + .pop() + .ok_or_else(|| CompileError::NotInALoop { + msg: "end_loop() called outside of a loop".into(), + span: None, + })?; + + if ended_loop == loop_ { + Ok(()) + } else { + Err(CompileError::IncoherentLoopState { + block_span: self.block_span, + }) + } + } + + /// Mark an unreachable code path. Produces an error at runtime if executed. + pub(crate) fn unreachable(&mut self, span: Span) -> Result<(), CompileError> { + self.push(Instruction::Unreachable.into_spanned(span)) + } + + /// Consume the builder and produce the final [`IrBlock`]. + pub(crate) fn finish(mut self) -> Result { + // Add comments to label targets + for (index, label_target) in self.labels.iter().enumerate() { + if let Some(label_target) = label_target { + add_comment( + &mut self.comments[*label_target], + format_args!("label({index})"), + ); + } + } + + // Populate the actual target indices of labels into the instructions + for ((index, instruction), span) in + self.instructions.iter_mut().enumerate().zip(&self.spans) + { + if let Some(label_id) = instruction.branch_target() { + let target_index = self.labels.get(label_id).cloned().flatten().ok_or( + CompileError::UndefinedLabel { + label_id, + span: Some(*span), + }, + )?; + // Add a comment to the target index that we come from here + add_comment( + &mut self.comments[target_index], + format_args!("from({index}:)"), + ); + instruction.set_branch_target(target_index).map_err(|_| { + CompileError::SetBranchTargetOfNonBranchInstruction { + instruction: format!("{:?}", instruction), + span: *span, + } + })?; + } + } + + Ok(IrBlock { + instructions: self.instructions, + spans: self.spans, + data: self.data.into(), + ast: self.ast, + comments: self.comments.into_iter().map(|s| s.into()).collect(), + register_count: self + .register_allocation_state + .len() + .try_into() + .expect("register count overflowed in finish() despite previous checks"), + file_count: self.file_count, + }) + } +} + +/// Keeps track of the `break` and `continue` target labels for a loop. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) struct Loop { + pub(crate) break_label: LabelId, + pub(crate) continue_label: LabelId, +} + +/// Add a new comment to an existing one +fn add_comment(comment: &mut String, new_comment: impl std::fmt::Display) { + use std::fmt::Write; + write!( + comment, + "{}{}", + if comment.is_empty() { "" } else { ", " }, + new_comment + ) + .expect("formatting failed"); +} diff --git a/crates/nu-engine/src/compile/call.rs b/crates/nu-engine/src/compile/call.rs new file mode 100644 index 0000000000..d9f1b8e581 --- /dev/null +++ b/crates/nu-engine/src/compile/call.rs @@ -0,0 +1,270 @@ +use std::sync::Arc; + +use nu_protocol::{ + ast::{Argument, Call, Expression, ExternalArgument}, + engine::StateWorkingSet, + ir::{Instruction, IrAstRef, Literal}, + IntoSpanned, RegId, Span, Spanned, +}; + +use super::{compile_expression, keyword::*, BlockBuilder, CompileError, RedirectModes}; + +pub(crate) fn compile_call( + working_set: &StateWorkingSet, + builder: &mut BlockBuilder, + call: &Call, + redirect_modes: RedirectModes, + io_reg: RegId, +) -> Result<(), CompileError> { + let decl = working_set.get_decl(call.decl_id); + + // Check if this call has --help - if so, just redirect to `help` + if call.named_iter().any(|(name, _, _)| name.item == "help") { + return compile_help( + working_set, + builder, + decl.name().into_spanned(call.head), + io_reg, + ); + } + + // Try to figure out if this is a keyword call like `if`, and handle those specially + if decl.is_keyword() { + match decl.name() { + "if" => { + return compile_if(working_set, builder, call, redirect_modes, io_reg); + } + "match" => { + return compile_match(working_set, builder, call, redirect_modes, io_reg); + } + "const" => { + // This differs from the behavior of the const command, which adds the const value + // to the stack. Since `load-variable` also checks `engine_state` for the variable + // and will get a const value though, is it really necessary to do that? + return builder.load_empty(io_reg); + } + "alias" => { + // Alias does nothing + return builder.load_empty(io_reg); + } + "let" | "mut" => { + return compile_let(working_set, builder, call, redirect_modes, io_reg); + } + "try" => { + return compile_try(working_set, builder, call, redirect_modes, io_reg); + } + "loop" => { + return compile_loop(working_set, builder, call, redirect_modes, io_reg); + } + "while" => { + return compile_while(working_set, builder, call, redirect_modes, io_reg); + } + "for" => { + return compile_for(working_set, builder, call, redirect_modes, io_reg); + } + "break" => { + return compile_break(working_set, builder, call, redirect_modes, io_reg); + } + "continue" => { + return compile_continue(working_set, builder, call, redirect_modes, io_reg); + } + "return" => { + return compile_return(working_set, builder, call, redirect_modes, io_reg); + } + _ => (), + } + } + + // Keep AST if the decl needs it. + let requires_ast = decl.requires_ast_for_arguments(); + + // It's important that we evaluate the args first before trying to set up the argument + // state for the call. + // + // We could technically compile anything that isn't another call safely without worrying about + // the argument state, but we'd have to check all of that first and it just isn't really worth + // it. + enum CompiledArg<'a> { + Positional(RegId, Span, Option), + Named( + &'a str, + Option<&'a str>, + Option, + Span, + Option, + ), + Spread(RegId, Span, Option), + } + + let mut compiled_args = vec![]; + + for arg in &call.arguments { + let arg_reg = arg + .expr() + .map(|expr| { + let arg_reg = builder.next_register()?; + + compile_expression( + working_set, + builder, + expr, + RedirectModes::capture_out(arg.span()), + None, + arg_reg, + )?; + + Ok(arg_reg) + }) + .transpose()?; + + let ast_ref = arg + .expr() + .filter(|_| requires_ast) + .map(|expr| IrAstRef(Arc::new(expr.clone()))); + + match arg { + Argument::Positional(_) | Argument::Unknown(_) => { + compiled_args.push(CompiledArg::Positional( + arg_reg.expect("expr() None in non-Named"), + arg.span(), + ast_ref, + )) + } + Argument::Named((name, short, _)) => compiled_args.push(CompiledArg::Named( + &name.item, + short.as_ref().map(|spanned| spanned.item.as_str()), + arg_reg, + arg.span(), + ast_ref, + )), + Argument::Spread(_) => compiled_args.push(CompiledArg::Spread( + arg_reg.expect("expr() None in non-Named"), + arg.span(), + ast_ref, + )), + } + } + + // Now that the args are all compiled, set up the call state (argument stack and redirections) + for arg in compiled_args { + match arg { + CompiledArg::Positional(reg, span, ast_ref) => { + builder.push(Instruction::PushPositional { src: reg }.into_spanned(span))?; + builder.set_last_ast(ast_ref); + } + CompiledArg::Named(name, short, Some(reg), span, ast_ref) => { + if !name.is_empty() { + let name = builder.data(name)?; + builder.push(Instruction::PushNamed { name, src: reg }.into_spanned(span))?; + } else { + let short = builder.data(short.unwrap_or(""))?; + builder + .push(Instruction::PushShortNamed { short, src: reg }.into_spanned(span))?; + } + builder.set_last_ast(ast_ref); + } + CompiledArg::Named(name, short, None, span, ast_ref) => { + if !name.is_empty() { + let name = builder.data(name)?; + builder.push(Instruction::PushFlag { name }.into_spanned(span))?; + } else { + let short = builder.data(short.unwrap_or(""))?; + builder.push(Instruction::PushShortFlag { short }.into_spanned(span))?; + } + builder.set_last_ast(ast_ref); + } + CompiledArg::Spread(reg, span, ast_ref) => { + builder.push(Instruction::AppendRest { src: reg }.into_spanned(span))?; + builder.set_last_ast(ast_ref); + } + } + } + + // Add any parser info from the call + for (name, info) in &call.parser_info { + let name = builder.data(name)?; + let info = Box::new(info.clone()); + builder.push(Instruction::PushParserInfo { name, info }.into_spanned(call.head))?; + } + + if let Some(mode) = redirect_modes.out { + builder.push(mode.map(|mode| Instruction::RedirectOut { mode }))?; + } + + if let Some(mode) = redirect_modes.err { + builder.push(mode.map(|mode| Instruction::RedirectErr { mode }))?; + } + + // The state is set up, so we can do the call into io_reg + builder.push( + Instruction::Call { + decl_id: call.decl_id, + src_dst: io_reg, + } + .into_spanned(call.head), + )?; + + Ok(()) +} + +pub(crate) fn compile_help( + working_set: &StateWorkingSet<'_>, + builder: &mut BlockBuilder, + decl_name: Spanned<&str>, + io_reg: RegId, +) -> Result<(), CompileError> { + let help_command_id = + working_set + .find_decl(b"help") + .ok_or_else(|| CompileError::MissingRequiredDeclaration { + decl_name: "help".into(), + span: decl_name.span, + })?; + + let name_data = builder.data(decl_name.item)?; + let name_literal = builder.literal(decl_name.map(|_| Literal::String(name_data)))?; + + builder.push(Instruction::PushPositional { src: name_literal }.into_spanned(decl_name.span))?; + + builder.push( + Instruction::Call { + decl_id: help_command_id, + src_dst: io_reg, + } + .into_spanned(decl_name.span), + )?; + + Ok(()) +} + +pub(crate) fn compile_external_call( + working_set: &StateWorkingSet, + builder: &mut BlockBuilder, + head: &Expression, + args: &[ExternalArgument], + redirect_modes: RedirectModes, + io_reg: RegId, +) -> Result<(), CompileError> { + // Pass everything to run-external + let run_external_id = working_set + .find_decl(b"run-external") + .ok_or(CompileError::RunExternalNotFound { span: head.span })?; + + let mut call = Call::new(head.span); + call.decl_id = run_external_id; + + call.arguments.push(Argument::Positional(head.clone())); + + for arg in args { + match arg { + ExternalArgument::Regular(expr) => { + call.arguments.push(Argument::Positional(expr.clone())); + } + ExternalArgument::Spread(expr) => { + call.arguments.push(Argument::Spread(expr.clone())); + } + } + } + + compile_call(working_set, builder, &call, redirect_modes, io_reg) +} diff --git a/crates/nu-engine/src/compile/expression.rs b/crates/nu-engine/src/compile/expression.rs new file mode 100644 index 0000000000..38ee58ea26 --- /dev/null +++ b/crates/nu-engine/src/compile/expression.rs @@ -0,0 +1,535 @@ +use super::{ + compile_binary_op, compile_block, compile_call, compile_external_call, compile_load_env, + BlockBuilder, CompileError, RedirectModes, +}; + +use nu_protocol::{ + ast::{CellPath, Expr, Expression, ListItem, RecordItem, ValueWithUnit}, + engine::StateWorkingSet, + ir::{DataSlice, Instruction, Literal}, + IntoSpanned, RegId, Span, Value, ENV_VARIABLE_ID, +}; + +pub(crate) fn compile_expression( + working_set: &StateWorkingSet, + builder: &mut BlockBuilder, + expr: &Expression, + redirect_modes: RedirectModes, + in_reg: Option, + out_reg: RegId, +) -> Result<(), CompileError> { + let drop_input = |builder: &mut BlockBuilder| { + if let Some(in_reg) = in_reg { + if in_reg != out_reg { + builder.drop_reg(in_reg)?; + } + } + Ok(()) + }; + + let lit = |builder: &mut BlockBuilder, literal: Literal| { + drop_input(builder)?; + + builder + .push( + Instruction::LoadLiteral { + dst: out_reg, + lit: literal, + } + .into_spanned(expr.span), + ) + .map(|_| ()) + }; + + let ignore = |builder: &mut BlockBuilder| { + drop_input(builder)?; + builder.load_empty(out_reg) + }; + + let unexpected = |expr_name: &str| CompileError::UnexpectedExpression { + expr_name: expr_name.into(), + span: expr.span, + }; + + let move_in_reg_to_out_reg = |builder: &mut BlockBuilder| { + // Ensure that out_reg contains the input value, because a call only uses one register + if let Some(in_reg) = in_reg { + if in_reg != out_reg { + // Have to move in_reg to out_reg so it can be used + builder.push( + Instruction::Move { + dst: out_reg, + src: in_reg, + } + .into_spanned(expr.span), + )?; + } + } else { + // Will have to initialize out_reg with Empty first + builder.load_empty(out_reg)?; + } + Ok(()) + }; + + match &expr.expr { + Expr::Bool(b) => lit(builder, Literal::Bool(*b)), + Expr::Int(i) => lit(builder, Literal::Int(*i)), + Expr::Float(f) => lit(builder, Literal::Float(*f)), + Expr::Binary(bin) => { + let data_slice = builder.data(bin)?; + lit(builder, Literal::Binary(data_slice)) + } + Expr::Range(range) => { + // Compile the subexpressions of the range + let compile_part = |builder: &mut BlockBuilder, + part_expr: Option<&Expression>| + -> Result { + let reg = builder.next_register()?; + if let Some(part_expr) = part_expr { + compile_expression( + working_set, + builder, + part_expr, + RedirectModes::capture_out(part_expr.span), + None, + reg, + )?; + } else { + builder.load_literal(reg, Literal::Nothing.into_spanned(expr.span))?; + } + Ok(reg) + }; + + drop_input(builder)?; + + let start = compile_part(builder, range.from.as_ref())?; + let step = compile_part(builder, range.next.as_ref())?; + let end = compile_part(builder, range.to.as_ref())?; + + // Assemble the range + builder.load_literal( + out_reg, + Literal::Range { + start, + step, + end, + inclusion: range.operator.inclusion, + } + .into_spanned(expr.span), + ) + } + Expr::Var(var_id) => { + drop_input(builder)?; + builder.push( + Instruction::LoadVariable { + dst: out_reg, + var_id: *var_id, + } + .into_spanned(expr.span), + )?; + Ok(()) + } + Expr::VarDecl(_) => Err(unexpected("VarDecl")), + Expr::Call(call) => { + move_in_reg_to_out_reg(builder)?; + + compile_call(working_set, builder, call, redirect_modes, out_reg) + } + Expr::ExternalCall(head, args) => { + move_in_reg_to_out_reg(builder)?; + + compile_external_call(working_set, builder, head, args, redirect_modes, out_reg) + } + Expr::Operator(_) => Err(unexpected("Operator")), + Expr::RowCondition(block_id) => lit(builder, Literal::RowCondition(*block_id)), + Expr::UnaryNot(subexpr) => { + drop_input(builder)?; + compile_expression( + working_set, + builder, + subexpr, + RedirectModes::capture_out(subexpr.span), + None, + out_reg, + )?; + builder.push(Instruction::Not { src_dst: out_reg }.into_spanned(expr.span))?; + Ok(()) + } + Expr::BinaryOp(lhs, op, rhs) => { + if let Expr::Operator(ref operator) = op.expr { + drop_input(builder)?; + compile_binary_op( + working_set, + builder, + lhs, + operator.clone().into_spanned(op.span), + rhs, + expr.span, + out_reg, + ) + } else { + Err(CompileError::UnsupportedOperatorExpression { span: op.span }) + } + } + Expr::Subexpression(block_id) => { + let block = working_set.get_block(*block_id); + compile_block(working_set, builder, block, redirect_modes, in_reg, out_reg) + } + Expr::Block(block_id) => lit(builder, Literal::Block(*block_id)), + Expr::Closure(block_id) => lit(builder, Literal::Closure(*block_id)), + Expr::MatchBlock(_) => Err(unexpected("MatchBlock")), // only for `match` keyword + Expr::List(items) => { + // Guess capacity based on items (does not consider spread as more than 1) + lit( + builder, + Literal::List { + capacity: items.len(), + }, + )?; + for item in items { + // Compile the expression of the item / spread + let reg = builder.next_register()?; + let expr = match item { + ListItem::Item(expr) | ListItem::Spread(_, expr) => expr, + }; + compile_expression( + working_set, + builder, + expr, + RedirectModes::capture_out(expr.span), + None, + reg, + )?; + + match item { + ListItem::Item(_) => { + // Add each item using list-push + builder.push( + Instruction::ListPush { + src_dst: out_reg, + item: reg, + } + .into_spanned(expr.span), + )?; + } + ListItem::Spread(spread_span, _) => { + // Spread the list using list-spread + builder.push( + Instruction::ListSpread { + src_dst: out_reg, + items: reg, + } + .into_spanned(*spread_span), + )?; + } + } + } + Ok(()) + } + Expr::Table(table) => { + lit( + builder, + Literal::List { + capacity: table.rows.len(), + }, + )?; + + // Evaluate the columns + let column_registers = table + .columns + .iter() + .map(|column| { + let reg = builder.next_register()?; + compile_expression( + working_set, + builder, + column, + RedirectModes::capture_out(column.span), + None, + reg, + )?; + Ok(reg) + }) + .collect::, CompileError>>()?; + + // Build records for each row + for row in table.rows.iter() { + let row_reg = builder.next_register()?; + builder.load_literal( + row_reg, + Literal::Record { + capacity: table.columns.len(), + } + .into_spanned(expr.span), + )?; + for (column_reg, item) in column_registers.iter().zip(row.iter()) { + let column_reg = builder.clone_reg(*column_reg, item.span)?; + let item_reg = builder.next_register()?; + compile_expression( + working_set, + builder, + item, + RedirectModes::capture_out(item.span), + None, + item_reg, + )?; + builder.push( + Instruction::RecordInsert { + src_dst: row_reg, + key: column_reg, + val: item_reg, + } + .into_spanned(item.span), + )?; + } + builder.push( + Instruction::ListPush { + src_dst: out_reg, + item: row_reg, + } + .into_spanned(expr.span), + )?; + } + + // Free the column registers, since they aren't needed anymore + for reg in column_registers { + builder.drop_reg(reg)?; + } + + Ok(()) + } + Expr::Record(items) => { + lit( + builder, + Literal::Record { + capacity: items.len(), + }, + )?; + + for item in items { + match item { + RecordItem::Pair(key, val) => { + // Add each item using record-insert + let key_reg = builder.next_register()?; + let val_reg = builder.next_register()?; + compile_expression( + working_set, + builder, + key, + RedirectModes::capture_out(key.span), + None, + key_reg, + )?; + compile_expression( + working_set, + builder, + val, + RedirectModes::capture_out(val.span), + None, + val_reg, + )?; + builder.push( + Instruction::RecordInsert { + src_dst: out_reg, + key: key_reg, + val: val_reg, + } + .into_spanned(expr.span), + )?; + } + RecordItem::Spread(spread_span, expr) => { + // Spread the expression using record-spread + let reg = builder.next_register()?; + compile_expression( + working_set, + builder, + expr, + RedirectModes::capture_out(expr.span), + None, + reg, + )?; + builder.push( + Instruction::RecordSpread { + src_dst: out_reg, + items: reg, + } + .into_spanned(*spread_span), + )?; + } + } + } + Ok(()) + } + Expr::Keyword(kw) => { + // keyword: just pass through expr, since commands that use it and are not being + // specially handled already are often just positional anyway + compile_expression( + working_set, + builder, + &kw.expr, + redirect_modes, + in_reg, + out_reg, + ) + } + Expr::ValueWithUnit(value_with_unit) => { + lit(builder, literal_from_value_with_unit(value_with_unit)?) + } + Expr::DateTime(dt) => lit(builder, Literal::Date(Box::new(*dt))), + Expr::Filepath(path, no_expand) => { + let val = builder.data(path)?; + lit( + builder, + Literal::Filepath { + val, + no_expand: *no_expand, + }, + ) + } + Expr::Directory(path, no_expand) => { + let val = builder.data(path)?; + lit( + builder, + Literal::Directory { + val, + no_expand: *no_expand, + }, + ) + } + Expr::GlobPattern(path, no_expand) => { + let val = builder.data(path)?; + lit( + builder, + Literal::GlobPattern { + val, + no_expand: *no_expand, + }, + ) + } + Expr::String(s) => { + let data_slice = builder.data(s)?; + lit(builder, Literal::String(data_slice)) + } + Expr::RawString(rs) => { + let data_slice = builder.data(rs)?; + lit(builder, Literal::RawString(data_slice)) + } + Expr::CellPath(path) => lit(builder, Literal::CellPath(Box::new(path.clone()))), + Expr::FullCellPath(full_cell_path) => { + if matches!(full_cell_path.head.expr, Expr::Var(ENV_VARIABLE_ID)) { + compile_load_env(builder, expr.span, &full_cell_path.tail, out_reg) + } else { + compile_expression( + working_set, + builder, + &full_cell_path.head, + RedirectModes::capture_out(expr.span), + in_reg, + out_reg, + )?; + // Only do the follow if this is actually needed + if !full_cell_path.tail.is_empty() { + let cell_path_reg = builder.literal( + Literal::CellPath(Box::new(CellPath { + members: full_cell_path.tail.clone(), + })) + .into_spanned(expr.span), + )?; + builder.push( + Instruction::FollowCellPath { + src_dst: out_reg, + path: cell_path_reg, + } + .into_spanned(expr.span), + )?; + } + Ok(()) + } + } + Expr::ImportPattern(_) => Err(unexpected("ImportPattern")), + Expr::Overlay(_) => Err(unexpected("Overlay")), + Expr::Signature(_) => ignore(builder), // no effect + Expr::StringInterpolation(exprs) | Expr::GlobInterpolation(exprs, _) => { + let mut exprs_iter = exprs.iter().peekable(); + + if exprs_iter + .peek() + .is_some_and(|e| matches!(e.expr, Expr::String(..) | Expr::RawString(..))) + { + // If the first expression is a string or raw string literal, just take it and build + // from that + compile_expression( + working_set, + builder, + exprs_iter.next().expect("peek() was Some"), + RedirectModes::capture_out(expr.span), + None, + out_reg, + )?; + } else { + // Start with an empty string + lit(builder, Literal::String(DataSlice::empty()))?; + } + + // Compile each expression and append to out_reg + for expr in exprs_iter { + let scratch_reg = builder.next_register()?; + compile_expression( + working_set, + builder, + expr, + RedirectModes::capture_out(expr.span), + None, + scratch_reg, + )?; + builder.push( + Instruction::StringAppend { + src_dst: out_reg, + val: scratch_reg, + } + .into_spanned(expr.span), + )?; + } + + // If it's a glob interpolation, change it to a glob + if let Expr::GlobInterpolation(_, no_expand) = expr.expr { + builder.push( + Instruction::GlobFrom { + src_dst: out_reg, + no_expand, + } + .into_spanned(expr.span), + )?; + } + + Ok(()) + } + Expr::Nothing => lit(builder, Literal::Nothing), + Expr::Garbage => Err(CompileError::Garbage { span: expr.span }), + } +} + +fn literal_from_value_with_unit(value_with_unit: &ValueWithUnit) -> Result { + let Expr::Int(int_value) = value_with_unit.expr.expr else { + return Err(CompileError::UnexpectedExpression { + expr_name: format!("{:?}", value_with_unit.expr), + span: value_with_unit.expr.span, + }); + }; + + match value_with_unit + .unit + .item + .build_value(int_value, Span::unknown()) + .map_err(|err| CompileError::InvalidLiteral { + msg: err.to_string(), + span: value_with_unit.expr.span, + })? { + Value::Filesize { val, .. } => Ok(Literal::Filesize(val)), + Value::Duration { val, .. } => Ok(Literal::Duration(val)), + other => Err(CompileError::InvalidLiteral { + msg: format!("bad value returned by Unit::build_value(): {other:?}"), + span: value_with_unit.unit.span, + }), + } +} diff --git a/crates/nu-engine/src/compile/keyword.rs b/crates/nu-engine/src/compile/keyword.rs new file mode 100644 index 0000000000..12f4a54c10 --- /dev/null +++ b/crates/nu-engine/src/compile/keyword.rs @@ -0,0 +1,902 @@ +use nu_protocol::{ + ast::{Block, Call, Expr, Expression}, + engine::StateWorkingSet, + ir::Instruction, + IntoSpanned, RegId, Type, VarId, +}; + +use super::{compile_block, compile_expression, BlockBuilder, CompileError, RedirectModes}; + +/// Compile a call to `if` as a branch-if +pub(crate) fn compile_if( + working_set: &StateWorkingSet, + builder: &mut BlockBuilder, + call: &Call, + redirect_modes: RedirectModes, + io_reg: RegId, +) -> Result<(), CompileError> { + // Pseudocode: + // + // %io_reg <- + // not %io_reg + // branch-if %io_reg, FALSE + // TRUE: ...... + // jump END + // FALSE: ...... OR drop %io_reg + // END: + let invalid = || CompileError::InvalidKeywordCall { + keyword: "if".into(), + span: call.head, + }; + + let condition = call.positional_nth(0).ok_or_else(invalid)?; + let true_block_arg = call.positional_nth(1).ok_or_else(invalid)?; + let else_arg = call.positional_nth(2); + + let true_block_id = true_block_arg.as_block().ok_or_else(invalid)?; + let true_block = working_set.get_block(true_block_id); + + let true_label = builder.label(None); + let false_label = builder.label(None); + let end_label = builder.label(None); + + let not_condition_reg = { + // Compile the condition first + let condition_reg = builder.next_register()?; + compile_expression( + working_set, + builder, + condition, + RedirectModes::capture_out(condition.span), + None, + condition_reg, + )?; + + // Negate the condition - we basically only want to jump if the condition is false + builder.push( + Instruction::Not { + src_dst: condition_reg, + } + .into_spanned(call.head), + )?; + + condition_reg + }; + + // Set up a branch if the condition is false. + builder.branch_if(not_condition_reg, false_label, call.head)?; + builder.add_comment("if false"); + + // Compile the true case + builder.set_label(true_label, builder.here())?; + compile_block( + working_set, + builder, + true_block, + redirect_modes.clone(), + Some(io_reg), + io_reg, + )?; + + // Add a jump over the false case + builder.jump(end_label, else_arg.map(|e| e.span).unwrap_or(call.head))?; + builder.add_comment("end if"); + + // On the else side now, assert that io_reg is still valid + builder.set_label(false_label, builder.here())?; + builder.mark_register(io_reg)?; + + if let Some(else_arg) = else_arg { + let Expression { + expr: Expr::Keyword(else_keyword), + .. + } = else_arg + else { + return Err(invalid()); + }; + + if else_keyword.keyword.as_ref() != b"else" { + return Err(invalid()); + } + + let else_expr = &else_keyword.expr; + + match &else_expr.expr { + Expr::Block(block_id) => { + let false_block = working_set.get_block(*block_id); + compile_block( + working_set, + builder, + false_block, + redirect_modes, + Some(io_reg), + io_reg, + )?; + } + _ => { + // The else case supports bare expressions too, not only blocks + compile_expression( + working_set, + builder, + else_expr, + redirect_modes, + Some(io_reg), + io_reg, + )?; + } + } + } else { + // We don't have an else expression/block, so just set io_reg = Empty + builder.load_empty(io_reg)?; + } + + // Set the end label + builder.set_label(end_label, builder.here())?; + + Ok(()) +} + +/// Compile a call to `match` +pub(crate) fn compile_match( + working_set: &StateWorkingSet, + builder: &mut BlockBuilder, + call: &Call, + redirect_modes: RedirectModes, + io_reg: RegId, +) -> Result<(), CompileError> { + // Pseudocode: + // + // %match_reg <- + // collect %match_reg + // match (pat1), %match_reg, PAT1 + // MATCH2: match (pat2), %match_reg, PAT2 + // FAIL: drop %io_reg + // drop %match_reg + // jump END + // PAT1: %guard_reg <- + // check-match-guard %guard_reg + // not %guard_reg + // branch-if %guard_reg, MATCH2 + // drop %match_reg + // <...expr...> + // jump END + // PAT2: drop %match_reg + // <...expr...> + // jump END + // END: + let invalid = || CompileError::InvalidKeywordCall { + keyword: "match".into(), + span: call.head, + }; + + let match_expr = call.positional_nth(0).ok_or_else(invalid)?; + + let match_block_arg = call.positional_nth(1).ok_or_else(invalid)?; + let match_block = match_block_arg.as_match_block().ok_or_else(invalid)?; + + let match_reg = builder.next_register()?; + + // Evaluate the match expression (patterns will be checked against this). + compile_expression( + working_set, + builder, + match_expr, + RedirectModes::capture_out(match_expr.span), + None, + match_reg, + )?; + + // Important to collect it first + builder.push(Instruction::Collect { src_dst: match_reg }.into_spanned(match_expr.span))?; + + // Generate the `match` instructions. Guards are not used at this stage. + let mut match_labels = Vec::with_capacity(match_block.len()); + let mut next_labels = Vec::with_capacity(match_block.len()); + let end_label = builder.label(None); + + for (pattern, _) in match_block { + let match_label = builder.label(None); + match_labels.push(match_label); + builder.push( + Instruction::Match { + pattern: Box::new(pattern.pattern.clone()), + src: match_reg, + index: match_label.0, + } + .into_spanned(pattern.span), + )?; + // Also add a label for the next match instruction or failure case + next_labels.push(builder.label(Some(builder.here()))); + } + + // Match fall-through to jump to the end, if no match + builder.load_empty(io_reg)?; + builder.drop_reg(match_reg)?; + builder.jump(end_label, call.head)?; + + // Generate each of the match expressions. Handle guards here, if present. + for (index, (pattern, expr)) in match_block.iter().enumerate() { + let match_label = match_labels[index]; + let next_label = next_labels[index]; + + // `io_reg` and `match_reg` are still valid at each of these branch targets + builder.mark_register(io_reg)?; + builder.mark_register(match_reg)?; + + // Set the original match instruction target here + builder.set_label(match_label, builder.here())?; + + // Handle guard, if present + if let Some(guard) = &pattern.guard { + let guard_reg = builder.next_register()?; + compile_expression( + working_set, + builder, + guard, + RedirectModes::capture_out(guard.span), + None, + guard_reg, + )?; + builder + .push(Instruction::CheckMatchGuard { src: guard_reg }.into_spanned(guard.span))?; + builder.push(Instruction::Not { src_dst: guard_reg }.into_spanned(guard.span))?; + // Branch to the next match instruction if the branch fails to match + builder.branch_if( + guard_reg, + next_label, + // Span the branch with the next pattern, or the head if this is the end + match_block + .get(index + 1) + .map(|b| b.0.span) + .unwrap_or(call.head), + )?; + builder.add_comment("if match guard false"); + } + + // match_reg no longer needed, successful match + builder.drop_reg(match_reg)?; + + // Execute match right hand side expression + if let Some(block_id) = expr.as_block() { + let block = working_set.get_block(block_id); + compile_block( + working_set, + builder, + block, + redirect_modes.clone(), + Some(io_reg), + io_reg, + )?; + } else { + compile_expression( + working_set, + builder, + expr, + redirect_modes.clone(), + Some(io_reg), + io_reg, + )?; + } + + // Jump to the end after the match logic is done + builder.jump(end_label, call.head)?; + builder.add_comment("end match"); + } + + // Set the end destination + builder.set_label(end_label, builder.here())?; + + Ok(()) +} + +/// Compile a call to `let` or `mut` (just do store-variable) +pub(crate) fn compile_let( + working_set: &StateWorkingSet, + builder: &mut BlockBuilder, + call: &Call, + _redirect_modes: RedirectModes, + io_reg: RegId, +) -> Result<(), CompileError> { + // Pseudocode: + // + // %io_reg <- ...... <- %io_reg + // store-variable $var, %io_reg + let invalid = || CompileError::InvalidKeywordCall { + keyword: "let".into(), + span: call.head, + }; + + let var_decl_arg = call.positional_nth(0).ok_or_else(invalid)?; + let block_arg = call.positional_nth(1).ok_or_else(invalid)?; + + let var_id = var_decl_arg.as_var().ok_or_else(invalid)?; + let block_id = block_arg.as_block().ok_or_else(invalid)?; + let block = working_set.get_block(block_id); + + let variable = working_set.get_variable(var_id); + + compile_block( + working_set, + builder, + block, + RedirectModes::capture_out(call.head), + Some(io_reg), + io_reg, + )?; + + // If the variable is a glob type variable, we should cast it with GlobFrom + if variable.ty == Type::Glob { + builder.push( + Instruction::GlobFrom { + src_dst: io_reg, + no_expand: true, + } + .into_spanned(call.head), + )?; + } + + builder.push( + Instruction::StoreVariable { + var_id, + src: io_reg, + } + .into_spanned(call.head), + )?; + builder.add_comment("let"); + + // Don't forget to set io_reg to Empty afterward, as that's the result of an assignment + builder.load_empty(io_reg)?; + + Ok(()) +} + +/// Compile a call to `try`, setting an error handler over the evaluated block +pub(crate) fn compile_try( + working_set: &StateWorkingSet, + builder: &mut BlockBuilder, + call: &Call, + redirect_modes: RedirectModes, + io_reg: RegId, +) -> Result<(), CompileError> { + // Pseudocode (literal block): + // + // on-error-into ERR, %io_reg // or without + // %io_reg <- <...block...> <- %io_reg + // check-external-failed %failed_reg, %io_reg + // branch-if %failed_reg, FAIL + // pop-error-handler + // jump END + // FAIL: drain %io_reg + // unreachable + // ERR: clone %err_reg, %io_reg + // store-variable $err_var, %err_reg // or without + // %io_reg <- <...catch block...> <- %io_reg // set to empty if no catch block + // END: + // + // with expression that can't be inlined: + // + // %closure_reg <- + // on-error-into ERR, %io_reg + // %io_reg <- <...block...> <- %io_reg + // check-external-failed %failed_reg, %io_reg + // branch-if %failed_reg, FAIL + // pop-error-handler + // jump END + // FAIL: drain %io_reg + // unreachable + // ERR: clone %err_reg, %io_reg + // push-positional %closure_reg + // push-positional %err_reg + // call "do", %io_reg + // END: + let invalid = || CompileError::InvalidKeywordCall { + keyword: "try".into(), + span: call.head, + }; + + let block_arg = call.positional_nth(0).ok_or_else(invalid)?; + let block_id = block_arg.as_block().ok_or_else(invalid)?; + let block = working_set.get_block(block_id); + + let catch_expr = match call.positional_nth(1) { + Some(kw_expr) => Some(kw_expr.as_keyword().ok_or_else(invalid)?), + None => None, + }; + let catch_span = catch_expr.map(|e| e.span).unwrap_or(call.head); + + let err_label = builder.label(None); + let failed_label = builder.label(None); + let end_label = builder.label(None); + + // We have two ways of executing `catch`: if it was provided as a literal, we can inline it. + // Otherwise, we have to evaluate the expression and keep it as a register, and then call `do`. + enum CatchType<'a> { + Block { + block: &'a Block, + var_id: Option, + }, + Closure { + closure_reg: RegId, + }, + } + + let catch_type = catch_expr + .map(|catch_expr| match catch_expr.as_block() { + Some(block_id) => { + let block = working_set.get_block(block_id); + let var_id = block.signature.get_positional(0).and_then(|v| v.var_id); + Ok(CatchType::Block { block, var_id }) + } + None => { + // We have to compile the catch_expr and use it as a closure + let closure_reg = builder.next_register()?; + compile_expression( + working_set, + builder, + catch_expr, + RedirectModes::capture_out(catch_expr.span), + None, + closure_reg, + )?; + Ok(CatchType::Closure { closure_reg }) + } + }) + .transpose()?; + + // Put the error handler instruction. If we have a catch expression then we should capture the + // error. + if catch_type.is_some() { + builder.push( + Instruction::OnErrorInto { + index: err_label.0, + dst: io_reg, + } + .into_spanned(call.head), + )? + } else { + // Otherwise, we don't need the error value. + builder.push(Instruction::OnError { index: err_label.0 }.into_spanned(call.head))? + }; + + builder.add_comment("try"); + + // Compile the block + compile_block( + working_set, + builder, + block, + redirect_modes.clone(), + Some(io_reg), + io_reg, + )?; + + // Check for external command exit code failure, and also redirect that to the catch handler + let failed_reg = builder.next_register()?; + builder.push( + Instruction::CheckExternalFailed { + dst: failed_reg, + src: io_reg, + } + .into_spanned(catch_span), + )?; + builder.branch_if(failed_reg, failed_label, catch_span)?; + + // Successful case: pop the error handler + builder.push(Instruction::PopErrorHandler.into_spanned(call.head))?; + + // Jump over the failure case + builder.jump(end_label, catch_span)?; + + // Set up an error handler preamble for failed external. + // Draining the %io_reg results in the error handler being called with Empty, and sets + // $env.LAST_EXIT_CODE + builder.set_label(failed_label, builder.here())?; + builder.drain(io_reg, catch_span)?; + builder.add_comment("branches to err"); + builder.unreachable(catch_span)?; + + // This is the real error handler + builder.set_label(err_label, builder.here())?; + + // Mark out register as likely not clean - state in error handler is not well defined + builder.mark_register(io_reg)?; + + // Now compile whatever is necessary for the error handler + match catch_type { + Some(CatchType::Block { block, var_id }) => { + // Error will be in io_reg + builder.mark_register(io_reg)?; + if let Some(var_id) = var_id { + // Take a copy of the error as $err, since it will also be input + let err_reg = builder.next_register()?; + builder.push( + Instruction::Clone { + dst: err_reg, + src: io_reg, + } + .into_spanned(catch_span), + )?; + builder.push( + Instruction::StoreVariable { + var_id, + src: err_reg, + } + .into_spanned(catch_span), + )?; + } + // Compile the block, now that the variable is set + compile_block( + working_set, + builder, + block, + redirect_modes, + Some(io_reg), + io_reg, + )?; + } + Some(CatchType::Closure { closure_reg }) => { + // We should call `do`. Error will be in io_reg + let do_decl_id = working_set.find_decl(b"do").ok_or_else(|| { + CompileError::MissingRequiredDeclaration { + decl_name: "do".into(), + span: call.head, + } + })?; + + // Take a copy of io_reg, because we pass it both as an argument and input + builder.mark_register(io_reg)?; + let err_reg = builder.next_register()?; + builder.push( + Instruction::Clone { + dst: err_reg, + src: io_reg, + } + .into_spanned(catch_span), + )?; + + // Push the closure and the error + builder + .push(Instruction::PushPositional { src: closure_reg }.into_spanned(catch_span))?; + builder.push(Instruction::PushPositional { src: err_reg }.into_spanned(catch_span))?; + + // Call `$err | do $closure $err` + builder.push( + Instruction::Call { + decl_id: do_decl_id, + src_dst: io_reg, + } + .into_spanned(catch_span), + )?; + } + None => { + // Just set out to empty. + builder.load_empty(io_reg)?; + } + } + + // This is the end - if we succeeded, should jump here + builder.set_label(end_label, builder.here())?; + + Ok(()) +} + +/// Compile a call to `loop` (via `jump`) +pub(crate) fn compile_loop( + working_set: &StateWorkingSet, + builder: &mut BlockBuilder, + call: &Call, + _redirect_modes: RedirectModes, + io_reg: RegId, +) -> Result<(), CompileError> { + // Pseudocode: + // + // drop %io_reg + // LOOP: %io_reg <- ...... + // drain %io_reg + // jump %LOOP + // END: drop %io_reg + let invalid = || CompileError::InvalidKeywordCall { + keyword: "loop".into(), + span: call.head, + }; + + let block_arg = call.positional_nth(0).ok_or_else(invalid)?; + let block_id = block_arg.as_block().ok_or_else(invalid)?; + let block = working_set.get_block(block_id); + + let loop_ = builder.begin_loop(); + builder.load_empty(io_reg)?; + + builder.set_label(loop_.continue_label, builder.here())?; + + compile_block( + working_set, + builder, + block, + RedirectModes::default(), + None, + io_reg, + )?; + + // Drain the output, just like for a semicolon + builder.drain(io_reg, call.head)?; + + builder.jump(loop_.continue_label, call.head)?; + builder.add_comment("loop"); + + builder.set_label(loop_.break_label, builder.here())?; + builder.end_loop(loop_)?; + + // State of %io_reg is not necessarily well defined here due to control flow, so make sure it's + // empty. + builder.mark_register(io_reg)?; + builder.load_empty(io_reg)?; + + Ok(()) +} + +/// Compile a call to `while`, via branch instructions +pub(crate) fn compile_while( + working_set: &StateWorkingSet, + builder: &mut BlockBuilder, + call: &Call, + _redirect_modes: RedirectModes, + io_reg: RegId, +) -> Result<(), CompileError> { + // Pseudocode: + // + // LOOP: %io_reg <- + // branch-if %io_reg, TRUE + // jump FALSE + // TRUE: %io_reg <- ...... + // drain %io_reg + // jump LOOP + // FALSE: drop %io_reg + let invalid = || CompileError::InvalidKeywordCall { + keyword: "while".into(), + span: call.head, + }; + + let cond_arg = call.positional_nth(0).ok_or_else(invalid)?; + let block_arg = call.positional_nth(1).ok_or_else(invalid)?; + let block_id = block_arg.as_block().ok_or_else(invalid)?; + let block = working_set.get_block(block_id); + + let loop_ = builder.begin_loop(); + builder.set_label(loop_.continue_label, builder.here())?; + + let true_label = builder.label(None); + + compile_expression( + working_set, + builder, + cond_arg, + RedirectModes::capture_out(call.head), + None, + io_reg, + )?; + + builder.branch_if(io_reg, true_label, call.head)?; + builder.add_comment("while"); + builder.jump(loop_.break_label, call.head)?; + builder.add_comment("end while"); + + builder.set_label(true_label, builder.here())?; + + compile_block( + working_set, + builder, + block, + RedirectModes::default(), + None, + io_reg, + )?; + + // Drain the result, just like for a semicolon + builder.drain(io_reg, call.head)?; + + builder.jump(loop_.continue_label, call.head)?; + builder.add_comment("while"); + + builder.set_label(loop_.break_label, builder.here())?; + builder.end_loop(loop_)?; + + // State of %io_reg is not necessarily well defined here due to control flow, so make sure it's + // empty. + builder.mark_register(io_reg)?; + builder.load_empty(io_reg)?; + + Ok(()) +} + +/// Compile a call to `for` (via `iterate`) +pub(crate) fn compile_for( + working_set: &StateWorkingSet, + builder: &mut BlockBuilder, + call: &Call, + _redirect_modes: RedirectModes, + io_reg: RegId, +) -> Result<(), CompileError> { + // Pseudocode: + // + // %stream_reg <- + // LOOP: iterate %io_reg, %stream_reg, END + // store-variable $var, %io_reg + // %io_reg <- <...block...> + // drain %io_reg + // jump LOOP + // END: drop %io_reg + let invalid = || CompileError::InvalidKeywordCall { + keyword: "for".into(), + span: call.head, + }; + + if call.get_named_arg("numbered").is_some() { + // This is deprecated and we don't support it. + return Err(invalid()); + } + + let var_decl_arg = call.positional_nth(0).ok_or_else(invalid)?; + let var_id = var_decl_arg.as_var().ok_or_else(invalid)?; + + let in_arg = call.positional_nth(1).ok_or_else(invalid)?; + let in_expr = in_arg.as_keyword().ok_or_else(invalid)?; + + let block_arg = call.positional_nth(2).ok_or_else(invalid)?; + let block_id = block_arg.as_block().ok_or_else(invalid)?; + let block = working_set.get_block(block_id); + + // Ensure io_reg is marked so we don't use it + builder.mark_register(io_reg)?; + + let stream_reg = builder.next_register()?; + + compile_expression( + working_set, + builder, + in_expr, + RedirectModes::capture_out(in_expr.span), + None, + stream_reg, + )?; + + // Set up loop state + let loop_ = builder.begin_loop(); + builder.set_label(loop_.continue_label, builder.here())?; + + // This gets a value from the stream each time it's executed + // io_reg basically will act as our scratch register here + builder.push( + Instruction::Iterate { + dst: io_reg, + stream: stream_reg, + end_index: loop_.break_label.0, + } + .into_spanned(call.head), + )?; + builder.add_comment("for"); + + // Put the received value in the variable + builder.push( + Instruction::StoreVariable { + var_id, + src: io_reg, + } + .into_spanned(var_decl_arg.span), + )?; + + // Do the body of the block + compile_block( + working_set, + builder, + block, + RedirectModes::default(), + None, + io_reg, + )?; + + // Drain the output, just like for a semicolon + builder.drain(io_reg, call.head)?; + + // Loop back to iterate to get the next value + builder.jump(loop_.continue_label, call.head)?; + + // Set the end of the loop + builder.set_label(loop_.break_label, builder.here())?; + builder.end_loop(loop_)?; + + // We don't need stream_reg anymore, after the loop + // io_reg may or may not be empty, so be sure it is + builder.free_register(stream_reg)?; + builder.mark_register(io_reg)?; + builder.load_empty(io_reg)?; + + Ok(()) +} + +/// Compile a call to `break`. +pub(crate) fn compile_break( + _working_set: &StateWorkingSet, + builder: &mut BlockBuilder, + call: &Call, + _redirect_modes: RedirectModes, + io_reg: RegId, +) -> Result<(), CompileError> { + if builder.is_in_loop() { + builder.load_empty(io_reg)?; + builder.push_break(call.head)?; + builder.add_comment("break"); + } else { + // Fall back to calling the command if we can't find the loop target statically + builder.push( + Instruction::Call { + decl_id: call.decl_id, + src_dst: io_reg, + } + .into_spanned(call.head), + )?; + } + Ok(()) +} + +/// Compile a call to `continue`. +pub(crate) fn compile_continue( + _working_set: &StateWorkingSet, + builder: &mut BlockBuilder, + call: &Call, + _redirect_modes: RedirectModes, + io_reg: RegId, +) -> Result<(), CompileError> { + if builder.is_in_loop() { + builder.load_empty(io_reg)?; + builder.push_continue(call.head)?; + builder.add_comment("continue"); + } else { + // Fall back to calling the command if we can't find the loop target statically + builder.push( + Instruction::Call { + decl_id: call.decl_id, + src_dst: io_reg, + } + .into_spanned(call.head), + )?; + } + Ok(()) +} + +/// Compile a call to `return` as a `return-early` instruction. +/// +/// This is not strictly necessary, but it is more efficient. +pub(crate) fn compile_return( + working_set: &StateWorkingSet, + builder: &mut BlockBuilder, + call: &Call, + _redirect_modes: RedirectModes, + io_reg: RegId, +) -> Result<(), CompileError> { + // Pseudocode: + // + // %io_reg <- + // return-early %io_reg + if let Some(arg_expr) = call.positional_nth(0) { + compile_expression( + working_set, + builder, + arg_expr, + RedirectModes::capture_out(arg_expr.span), + None, + io_reg, + )?; + } else { + builder.load_empty(io_reg)?; + } + + // TODO: It would be nice if this could be `return` instead, but there is a little bit of + // behaviour remaining that still depends on `ShellError::Return` + builder.push(Instruction::ReturnEarly { src: io_reg }.into_spanned(call.head))?; + + // io_reg is supposed to remain allocated + builder.load_empty(io_reg)?; + + Ok(()) +} diff --git a/crates/nu-engine/src/compile/mod.rs b/crates/nu-engine/src/compile/mod.rs new file mode 100644 index 0000000000..8f6ae22682 --- /dev/null +++ b/crates/nu-engine/src/compile/mod.rs @@ -0,0 +1,204 @@ +use nu_protocol::{ + ast::{Block, Pipeline, PipelineRedirection, RedirectionSource, RedirectionTarget}, + engine::StateWorkingSet, + ir::{Instruction, IrBlock, RedirectMode}, + CompileError, IntoSpanned, RegId, Span, +}; + +mod builder; +mod call; +mod expression; +mod keyword; +mod operator; +mod redirect; + +use builder::BlockBuilder; +use call::*; +use expression::compile_expression; +use operator::*; +use redirect::*; + +const BLOCK_INPUT: RegId = RegId(0); + +/// Compile Nushell pipeline abstract syntax tree (AST) to internal representation (IR) instructions +/// for evaluation. +pub fn compile(working_set: &StateWorkingSet, block: &Block) -> Result { + let mut builder = BlockBuilder::new(block.span); + + let span = block.span.unwrap_or(Span::unknown()); + + compile_block( + working_set, + &mut builder, + block, + RedirectModes::caller(span), + Some(BLOCK_INPUT), + BLOCK_INPUT, + )?; + + // A complete block has to end with a `return` + builder.push(Instruction::Return { src: BLOCK_INPUT }.into_spanned(span))?; + + builder.finish() +} + +/// Compiles a [`Block`] in-place into an IR block. This can be used in a nested manner, for example +/// by [`compile_if()`], where the instructions for the blocks for the if/else are inlined into the +/// top-level IR block. +fn compile_block( + working_set: &StateWorkingSet, + builder: &mut BlockBuilder, + block: &Block, + redirect_modes: RedirectModes, + in_reg: Option, + out_reg: RegId, +) -> Result<(), CompileError> { + let span = block.span.unwrap_or(Span::unknown()); + let mut redirect_modes = Some(redirect_modes); + if !block.pipelines.is_empty() { + let last_index = block.pipelines.len() - 1; + for (index, pipeline) in block.pipelines.iter().enumerate() { + compile_pipeline( + working_set, + builder, + pipeline, + span, + // the redirect mode only applies to the last pipeline. + if index == last_index { + redirect_modes + .take() + .expect("should only take redirect_modes once") + } else { + RedirectModes::default() + }, + // input is only passed to the first pipeline. + if index == 0 { in_reg } else { None }, + out_reg, + )?; + + if index != last_index { + // Explicitly drain the out reg after each non-final pipeline, because that's how + // the semicolon functions. + if builder.is_allocated(out_reg) { + builder.push(Instruction::Drain { src: out_reg }.into_spanned(span))?; + } + builder.load_empty(out_reg)?; + } + } + Ok(()) + } else if in_reg.is_none() { + builder.load_empty(out_reg) + } else { + Ok(()) + } +} + +fn compile_pipeline( + working_set: &StateWorkingSet, + builder: &mut BlockBuilder, + pipeline: &Pipeline, + fallback_span: Span, + redirect_modes: RedirectModes, + in_reg: Option, + out_reg: RegId, +) -> Result<(), CompileError> { + let mut iter = pipeline.elements.iter().peekable(); + let mut in_reg = in_reg; + let mut redirect_modes = Some(redirect_modes); + while let Some(element) = iter.next() { + let span = element.pipe.unwrap_or(fallback_span); + + // We have to get the redirection mode from either the explicit redirection in the pipeline + // element, or from the next expression if it's specified there. If this is the last + // element, then it's from whatever is passed in as the mode to use. + + let next_redirect_modes = if let Some(next_element) = iter.peek() { + let mut modes = redirect_modes_of_expression(working_set, &next_element.expr, span)?; + + // If there's a next element with no inherent redirection we always pipe out *unless* + // this is a single redirection of stderr to pipe (e>|) + if modes.out.is_none() + && !matches!( + element.redirection, + Some(PipelineRedirection::Single { + source: RedirectionSource::Stderr, + target: RedirectionTarget::Pipe { .. } + }) + ) + { + let pipe_span = next_element.pipe.unwrap_or(next_element.expr.span); + modes.out = Some(RedirectMode::Pipe.into_spanned(pipe_span)); + } + + modes + } else { + redirect_modes + .take() + .expect("should only take redirect_modes once") + }; + + let spec_redirect_modes = match &element.redirection { + Some(PipelineRedirection::Single { source, target }) => { + let mode = redirection_target_to_mode(working_set, builder, target)?; + match source { + RedirectionSource::Stdout => RedirectModes { + out: Some(mode), + err: None, + }, + RedirectionSource::Stderr => RedirectModes { + out: None, + err: Some(mode), + }, + RedirectionSource::StdoutAndStderr => RedirectModes { + out: Some(mode), + err: Some(mode), + }, + } + } + Some(PipelineRedirection::Separate { out, err }) => { + // In this case, out and err must not both be Pipe + assert!( + !matches!( + (out, err), + ( + RedirectionTarget::Pipe { .. }, + RedirectionTarget::Pipe { .. } + ) + ), + "for Separate redirection, out and err targets must not both be Pipe" + ); + let out = redirection_target_to_mode(working_set, builder, out)?; + let err = redirection_target_to_mode(working_set, builder, err)?; + RedirectModes { + out: Some(out), + err: Some(err), + } + } + None => RedirectModes { + out: None, + err: None, + }, + }; + + let redirect_modes = RedirectModes { + out: spec_redirect_modes.out.or(next_redirect_modes.out), + err: spec_redirect_modes.err.or(next_redirect_modes.err), + }; + + compile_expression( + working_set, + builder, + &element.expr, + redirect_modes.clone(), + in_reg, + out_reg, + )?; + + // Clean up the redirection + finish_redirection(builder, redirect_modes, out_reg)?; + + // The next pipeline element takes input from this output + in_reg = Some(out_reg); + } + Ok(()) +} diff --git a/crates/nu-engine/src/compile/operator.rs b/crates/nu-engine/src/compile/operator.rs new file mode 100644 index 0000000000..a1ed3f66df --- /dev/null +++ b/crates/nu-engine/src/compile/operator.rs @@ -0,0 +1,378 @@ +use nu_protocol::{ + ast::{Assignment, Boolean, CellPath, Expr, Expression, Math, Operator, PathMember}, + engine::StateWorkingSet, + ir::{Instruction, Literal}, + IntoSpanned, RegId, Span, Spanned, ENV_VARIABLE_ID, +}; +use nu_utils::IgnoreCaseExt; + +use super::{compile_expression, BlockBuilder, CompileError, RedirectModes}; + +pub(crate) fn compile_binary_op( + working_set: &StateWorkingSet, + builder: &mut BlockBuilder, + lhs: &Expression, + op: Spanned, + rhs: &Expression, + span: Span, + out_reg: RegId, +) -> Result<(), CompileError> { + if let Operator::Assignment(assign_op) = op.item { + if let Some(decomposed_op) = decompose_assignment(assign_op) { + // Compiling an assignment that uses a binary op with the existing value + compile_binary_op( + working_set, + builder, + lhs, + decomposed_op.into_spanned(op.span), + rhs, + span, + out_reg, + )?; + } else { + // Compiling a plain assignment, where the current left-hand side value doesn't matter + compile_expression( + working_set, + builder, + rhs, + RedirectModes::capture_out(rhs.span), + None, + out_reg, + )?; + } + + compile_assignment(working_set, builder, lhs, op.span, out_reg)?; + + // Load out_reg with Nothing, as that's the result of an assignment + builder.load_literal(out_reg, Literal::Nothing.into_spanned(op.span)) + } else { + // Not an assignment: just do the binary op + let lhs_reg = out_reg; + + compile_expression( + working_set, + builder, + lhs, + RedirectModes::capture_out(lhs.span), + None, + lhs_reg, + )?; + + match op.item { + // `and` / `or` are short-circuiting, and we can get by with one register and a branch + Operator::Boolean(Boolean::And) => { + let true_label = builder.label(None); + builder.branch_if(lhs_reg, true_label, op.span)?; + + // If the branch was not taken it's false, so short circuit to load false + let false_label = builder.label(None); + builder.jump(false_label, op.span)?; + + builder.set_label(true_label, builder.here())?; + compile_expression( + working_set, + builder, + rhs, + RedirectModes::capture_out(rhs.span), + None, + lhs_reg, + )?; + + let end_label = builder.label(None); + builder.jump(end_label, op.span)?; + + // Consumed by `branch-if`, so we have to set it false again + builder.set_label(false_label, builder.here())?; + builder.load_literal(lhs_reg, Literal::Bool(false).into_spanned(lhs.span))?; + + builder.set_label(end_label, builder.here())?; + } + Operator::Boolean(Boolean::Or) => { + let true_label = builder.label(None); + builder.branch_if(lhs_reg, true_label, op.span)?; + + // If the branch was not taken it's false, so do the right-side expression + compile_expression( + working_set, + builder, + rhs, + RedirectModes::capture_out(rhs.span), + None, + lhs_reg, + )?; + + let end_label = builder.label(None); + builder.jump(end_label, op.span)?; + + // Consumed by `branch-if`, so we have to set it true again + builder.set_label(true_label, builder.here())?; + builder.load_literal(lhs_reg, Literal::Bool(true).into_spanned(lhs.span))?; + + builder.set_label(end_label, builder.here())?; + } + _ => { + // Any other operator, via `binary-op` + let rhs_reg = builder.next_register()?; + + compile_expression( + working_set, + builder, + rhs, + RedirectModes::capture_out(rhs.span), + None, + rhs_reg, + )?; + + builder.push( + Instruction::BinaryOp { + lhs_dst: lhs_reg, + op: op.item, + rhs: rhs_reg, + } + .into_spanned(op.span), + )?; + } + } + + if lhs_reg != out_reg { + builder.push( + Instruction::Move { + dst: out_reg, + src: lhs_reg, + } + .into_spanned(op.span), + )?; + } + + builder.push(Instruction::Span { src_dst: out_reg }.into_spanned(span))?; + + Ok(()) + } +} + +/// The equivalent plain operator to use for an assignment, if any +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::MinusAssign => Some(Operator::Math(Math::Minus)), + Assignment::MultiplyAssign => Some(Operator::Math(Math::Multiply)), + Assignment::DivideAssign => Some(Operator::Math(Math::Divide)), + } +} + +/// Compile assignment of the value in a register to a left-hand expression +pub(crate) fn compile_assignment( + working_set: &StateWorkingSet, + builder: &mut BlockBuilder, + lhs: &Expression, + assignment_span: Span, + rhs_reg: RegId, +) -> Result<(), CompileError> { + match lhs.expr { + Expr::Var(var_id) => { + // Double check that the variable is supposed to be mutable + if !working_set.get_variable(var_id).mutable { + return Err(CompileError::AssignmentRequiresMutableVar { span: lhs.span }); + } + + builder.push( + Instruction::StoreVariable { + var_id, + src: rhs_reg, + } + .into_spanned(assignment_span), + )?; + Ok(()) + } + Expr::FullCellPath(ref path) => match (&path.head, &path.tail) { + ( + Expression { + expr: Expr::Var(var_id), + .. + }, + _, + ) if *var_id == ENV_VARIABLE_ID => { + // This will be an assignment to an environment variable. + let Some(PathMember::String { val: key, .. }) = path.tail.first() else { + return Err(CompileError::CannotReplaceEnv { span: lhs.span }); + }; + + // Some env vars can't be set by Nushell code. + const AUTOMATIC_NAMES: &[&str] = &["PWD", "FILE_PWD", "CURRENT_FILE"]; + if AUTOMATIC_NAMES.iter().any(|name| key.eq_ignore_case(name)) { + return Err(CompileError::AutomaticEnvVarSetManually { + envvar_name: "PWD".into(), + span: lhs.span, + }); + } + + let key_data = builder.data(key)?; + + let val_reg = if path.tail.len() > 1 { + // Get the current value of the head and first tail of the path, from env + let head_reg = builder.next_register()?; + + // We could use compile_load_env, but this shares the key data... + // Always use optional, because it doesn't matter if it's already there + builder.push( + Instruction::LoadEnvOpt { + dst: head_reg, + key: key_data, + } + .into_spanned(lhs.span), + )?; + + // Default to empty record so we can do further upserts + let default_label = builder.label(None); + let upsert_label = builder.label(None); + builder.branch_if_empty(head_reg, default_label, assignment_span)?; + builder.jump(upsert_label, assignment_span)?; + + builder.set_label(default_label, builder.here())?; + builder.load_literal( + head_reg, + Literal::Record { capacity: 0 }.into_spanned(lhs.span), + )?; + + // Do the upsert on the current value to incorporate rhs + builder.set_label(upsert_label, builder.here())?; + compile_upsert_cell_path( + builder, + (&path.tail[1..]).into_spanned(lhs.span), + head_reg, + rhs_reg, + assignment_span, + )?; + + head_reg + } else { + // Path has only one tail, so we don't need the current value to do an upsert, + // just set it directly to rhs + rhs_reg + }; + + // Finally, store the modified env variable + builder.push( + Instruction::StoreEnv { + key: key_data, + src: val_reg, + } + .into_spanned(assignment_span), + )?; + Ok(()) + } + (_, tail) if tail.is_empty() => { + // If the path tail is empty, we can really just treat this as if it were an + // assignment to the head + compile_assignment(working_set, builder, &path.head, assignment_span, rhs_reg) + } + _ => { + // Just a normal assignment to some path + let head_reg = builder.next_register()?; + + // Compile getting current value of the head expression + compile_expression( + working_set, + builder, + &path.head, + RedirectModes::capture_out(path.head.span), + None, + head_reg, + )?; + + // Upsert the tail of the path into the old value of the head expression + compile_upsert_cell_path( + builder, + path.tail.as_slice().into_spanned(lhs.span), + head_reg, + rhs_reg, + assignment_span, + )?; + + // Now compile the assignment of the updated value to the head + compile_assignment(working_set, builder, &path.head, assignment_span, head_reg) + } + }, + Expr::Garbage => Err(CompileError::Garbage { span: lhs.span }), + _ => Err(CompileError::AssignmentRequiresVar { span: lhs.span }), + } +} + +/// Compile an upsert-cell-path instruction, with known literal members +pub(crate) fn compile_upsert_cell_path( + builder: &mut BlockBuilder, + members: Spanned<&[PathMember]>, + src_dst: RegId, + new_value: RegId, + span: Span, +) -> Result<(), CompileError> { + let path_reg = builder.literal( + Literal::CellPath( + CellPath { + members: members.item.to_vec(), + } + .into(), + ) + .into_spanned(members.span), + )?; + builder.push( + Instruction::UpsertCellPath { + src_dst, + path: path_reg, + new_value, + } + .into_spanned(span), + )?; + Ok(()) +} + +/// Compile the correct sequence to get an environment variable + follow a path on it +pub(crate) fn compile_load_env( + builder: &mut BlockBuilder, + span: Span, + path: &[PathMember], + out_reg: RegId, +) -> Result<(), CompileError> { + if path.is_empty() { + builder.push( + Instruction::LoadVariable { + dst: out_reg, + var_id: ENV_VARIABLE_ID, + } + .into_spanned(span), + )?; + } else { + let (key, optional) = match &path[0] { + PathMember::String { val, optional, .. } => (builder.data(val)?, *optional), + PathMember::Int { span, .. } => { + return Err(CompileError::AccessEnvByInt { span: *span }) + } + }; + let tail = &path[1..]; + + if optional { + builder.push(Instruction::LoadEnvOpt { dst: out_reg, key }.into_spanned(span))?; + } else { + builder.push(Instruction::LoadEnv { dst: out_reg, key }.into_spanned(span))?; + } + + if !tail.is_empty() { + let path = builder.literal( + Literal::CellPath(Box::new(CellPath { + members: tail.to_vec(), + })) + .into_spanned(span), + )?; + builder.push( + Instruction::FollowCellPath { + src_dst: out_reg, + path, + } + .into_spanned(span), + )?; + } + } + Ok(()) +} diff --git a/crates/nu-engine/src/compile/redirect.rs b/crates/nu-engine/src/compile/redirect.rs new file mode 100644 index 0000000000..15af1a9f8c --- /dev/null +++ b/crates/nu-engine/src/compile/redirect.rs @@ -0,0 +1,157 @@ +use nu_protocol::{ + ast::{Expression, RedirectionTarget}, + engine::StateWorkingSet, + ir::{Instruction, RedirectMode}, + IntoSpanned, OutDest, RegId, Span, Spanned, +}; + +use super::{compile_expression, BlockBuilder, CompileError}; + +#[derive(Default, Clone)] +pub(crate) struct RedirectModes { + pub(crate) out: Option>, + pub(crate) err: Option>, +} + +impl RedirectModes { + pub(crate) fn capture_out(span: Span) -> Self { + RedirectModes { + out: Some(RedirectMode::Capture.into_spanned(span)), + err: None, + } + } + + pub(crate) fn caller(span: Span) -> RedirectModes { + RedirectModes { + out: Some(RedirectMode::Caller.into_spanned(span)), + err: Some(RedirectMode::Caller.into_spanned(span)), + } + } +} + +pub(crate) fn redirection_target_to_mode( + working_set: &StateWorkingSet, + builder: &mut BlockBuilder, + target: &RedirectionTarget, +) -> Result, CompileError> { + Ok(match target { + RedirectionTarget::File { + expr, + append, + span: redir_span, + } => { + let file_num = builder.next_file_num()?; + let path_reg = builder.next_register()?; + compile_expression( + working_set, + builder, + expr, + RedirectModes::capture_out(*redir_span), + None, + path_reg, + )?; + builder.push( + Instruction::OpenFile { + file_num, + path: path_reg, + append: *append, + } + .into_spanned(*redir_span), + )?; + RedirectMode::File { file_num }.into_spanned(*redir_span) + } + RedirectionTarget::Pipe { span } => RedirectMode::Pipe.into_spanned(*span), + }) +} + +pub(crate) fn redirect_modes_of_expression( + working_set: &StateWorkingSet, + expression: &Expression, + redir_span: Span, +) -> Result { + let (out, err) = expression.expr.pipe_redirection(working_set); + Ok(RedirectModes { + out: out + .map(|r| r.into_spanned(redir_span)) + .map(out_dest_to_redirect_mode) + .transpose()?, + err: err + .map(|r| r.into_spanned(redir_span)) + .map(out_dest_to_redirect_mode) + .transpose()?, + }) +} + +/// Finish the redirection for an expression, writing to and closing files as necessary +pub(crate) fn finish_redirection( + builder: &mut BlockBuilder, + modes: RedirectModes, + out_reg: RegId, +) -> Result<(), CompileError> { + if let Some(Spanned { + item: RedirectMode::File { file_num }, + span, + }) = modes.out + { + // If out is a file and err is a pipe, we must not consume the expression result - + // that is actually the err, in that case. + if !matches!( + modes.err, + Some(Spanned { + item: RedirectMode::Pipe { .. }, + .. + }) + ) { + builder.push( + Instruction::WriteFile { + file_num, + src: out_reg, + } + .into_spanned(span), + )?; + builder.load_empty(out_reg)?; + } + builder.push(Instruction::CloseFile { file_num }.into_spanned(span))?; + } + + match modes.err { + Some(Spanned { + item: RedirectMode::File { file_num }, + span, + }) => { + // Close the file, unless it's the same as out (in which case it was already closed) + if !modes.out.is_some_and(|out_mode| match out_mode.item { + RedirectMode::File { + file_num: out_file_num, + } => file_num == out_file_num, + _ => false, + }) { + builder.push(Instruction::CloseFile { file_num }.into_spanned(span))?; + } + } + Some(Spanned { + item: RedirectMode::Pipe, + span, + }) => { + builder.push(Instruction::CheckErrRedirected { src: out_reg }.into_spanned(span))?; + } + _ => (), + } + + Ok(()) +} + +pub(crate) fn out_dest_to_redirect_mode( + out_dest: Spanned, +) -> Result, CompileError> { + let span = out_dest.span; + out_dest + .map(|out_dest| match out_dest { + OutDest::Pipe => Ok(RedirectMode::Pipe), + OutDest::Capture => Ok(RedirectMode::Capture), + OutDest::Null => Ok(RedirectMode::Null), + OutDest::Inherit => Ok(RedirectMode::Inherit), + OutDest::File(_) => Err(CompileError::InvalidRedirectMode { span }), + }) + .transpose() +} diff --git a/crates/nu-engine/src/documentation.rs b/crates/nu-engine/src/documentation.rs index a7d4950036..7840d03c47 100644 --- a/crates/nu-engine/src/documentation.rs +++ b/crates/nu-engine/src/documentation.rs @@ -45,10 +45,12 @@ fn nu_highlight_string(code_string: &str, engine_state: &EngineState, stack: &mu if let Some(highlighter) = engine_state.find_decl(b"nu-highlight", &[]) { let decl = engine_state.get_decl(highlighter); + let call = Call::new(Span::unknown()); + if let Ok(output) = decl.run( engine_state, stack, - &Call::new(Span::unknown()), + &(&call).into(), Value::string(code_string, Span::unknown()).into_pipeline_data(), ) { let result = output.into_value(Span::unknown()); @@ -269,11 +271,12 @@ fn get_documentation( let _ = write!(long_desc, "\n > {}\n", example.example); } else if let Some(highlighter) = engine_state.find_decl(b"nu-highlight", &[]) { let decl = engine_state.get_decl(highlighter); + let call = Call::new(Span::unknown()); match decl.run( engine_state, stack, - &Call::new(Span::unknown()), + &(&call).into(), Value::string(example.example, Span::unknown()).into_pipeline_data(), ) { Ok(output) => { @@ -326,7 +329,7 @@ fn get_documentation( .run( engine_state, stack, - &table_call, + &(&table_call).into(), PipelineData::Value(result.clone(), None), ) .ok() diff --git a/crates/nu-engine/src/env.rs b/crates/nu-engine/src/env.rs index 048d9bfb99..ab3a4bc50c 100644 --- a/crates/nu-engine/src/env.rs +++ b/crates/nu-engine/src/env.rs @@ -1,8 +1,8 @@ use crate::ClosureEvalOnce; use nu_path::canonicalize_with; use nu_protocol::{ - ast::{Call, Expr}, - engine::{EngineState, Stack, StateWorkingSet}, + ast::Expr, + engine::{Call, EngineState, Stack, StateWorkingSet}, Config, ShellError, Span, Value, VarId, }; use std::{ @@ -244,14 +244,15 @@ pub fn path_str( } pub const DIR_VAR_PARSER_INFO: &str = "dirs_var"; -pub fn get_dirs_var_from_call(call: &Call) -> Option { - call.get_parser_info(DIR_VAR_PARSER_INFO).and_then(|x| { - if let Expr::Var(id) = x.expr { - Some(id) - } else { - None - } - }) +pub fn get_dirs_var_from_call(stack: &Stack, call: &Call) -> Option { + call.get_parser_info(stack, DIR_VAR_PARSER_INFO) + .and_then(|x| { + if let Expr::Var(id) = x.expr { + Some(id) + } else { + None + } + }) } /// This helper function is used to find files during eval diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index b495589011..6e171eb46c 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -1,3 +1,4 @@ +use crate::eval_ir_block; #[allow(deprecated)] use crate::{current_dir, get_config, get_full_help}; use nu_path::{expand_path_with, AbsolutePathBuf}; @@ -7,7 +8,7 @@ use nu_protocol::{ PipelineRedirection, RedirectionSource, RedirectionTarget, }, debugger::DebugContext, - engine::{Closure, EngineState, Redirection, Stack}, + engine::{Closure, EngineState, Redirection, Stack, StateWorkingSet}, eval_base::Eval, ByteStreamSource, Config, FromValue, IntoPipelineData, OutDest, PipelineData, ShellError, Span, Spanned, Type, Value, VarId, ENV_VARIABLE_ID, @@ -174,7 +175,7 @@ pub fn eval_call( // We pass caller_stack here with the knowledge that internal commands // are going to be specifically looking for global state in the stack // rather than any local state. - decl.run(engine_state, caller_stack, call, input) + decl.run(engine_state, caller_stack, &call.into(), input) } } @@ -223,7 +224,7 @@ fn eval_external( } } - command.run(engine_state, stack, &call, input) + command.run(engine_state, stack, &(&call).into(), input) } pub fn eval_expression( @@ -507,6 +508,11 @@ pub fn eval_block( block: &Block, mut input: PipelineData, ) -> Result { + // Remove once IR is the default. + if stack.use_ir { + return eval_ir_block::(engine_state, stack, block, input); + } + D::enter_block(engine_state, block); let num_pipelines = block.len(); @@ -521,7 +527,7 @@ pub fn eval_block( for (i, element) in elements.iter().enumerate() { let next = elements.get(i + 1).unwrap_or(last); - let (next_out, next_err) = next.pipe_redirection(engine_state); + let (next_out, next_err) = next.pipe_redirection(&StateWorkingSet::new(engine_state)); let (stdout, stderr) = eval_element_redirection::( engine_state, stack, @@ -903,7 +909,7 @@ impl Eval for EvalRuntime { /// /// An automatic environment variable cannot be assigned to by user code. /// Current there are three of them: $env.PWD, $env.FILE_PWD, $env.CURRENT_FILE -fn is_automatic_env_var(var: &str) -> bool { +pub(crate) fn is_automatic_env_var(var: &str) -> bool { let names = ["PWD", "FILE_PWD", "CURRENT_FILE"]; names.iter().any(|&name| { if cfg!(windows) { diff --git a/crates/nu-engine/src/eval_helpers.rs b/crates/nu-engine/src/eval_helpers.rs index 66bda3e0eb..65ebc6b61d 100644 --- a/crates/nu-engine/src/eval_helpers.rs +++ b/crates/nu-engine/src/eval_helpers.rs @@ -1,6 +1,6 @@ use crate::{ eval_block, eval_block_with_early_return, eval_expression, eval_expression_with_input, - eval_subexpression, + eval_ir_block, eval_subexpression, }; use nu_protocol::{ ast::{Block, Expression}, @@ -13,6 +13,10 @@ use nu_protocol::{ pub type EvalBlockFn = fn(&EngineState, &mut Stack, &Block, PipelineData) -> Result; +/// Type of eval_ir_block() function +pub type EvalIrBlockFn = + fn(&EngineState, &mut Stack, &Block, PipelineData) -> Result; + /// Type of eval_block_with_early_return() function pub type EvalBlockWithEarlyReturnFn = fn(&EngineState, &mut Stack, &Block, PipelineData) -> Result; @@ -42,6 +46,16 @@ pub fn get_eval_block(engine_state: &EngineState) -> EvalBlockFn { } } +/// Helper function to fetch `eval_ir_block()` with the correct type parameter based on whether +/// engine_state is configured with or without a debugger. +pub fn get_eval_ir_block(engine_state: &EngineState) -> EvalIrBlockFn { + if engine_state.is_debugging() { + eval_ir_block:: + } else { + eval_ir_block:: + } +} + /// Helper function to fetch `eval_block_with_early_return()` with the correct type parameter based /// on whether engine_state is configured with or without a debugger. pub fn get_eval_block_with_early_return(engine_state: &EngineState) -> EvalBlockWithEarlyReturnFn { diff --git a/crates/nu-engine/src/eval_ir.rs b/crates/nu-engine/src/eval_ir.rs new file mode 100644 index 0000000000..a505c9be34 --- /dev/null +++ b/crates/nu-engine/src/eval_ir.rs @@ -0,0 +1,1462 @@ +use std::{borrow::Cow, fs::File, sync::Arc}; + +use nu_path::{expand_path_with, AbsolutePathBuf}; +use nu_protocol::{ + ast::{Bits, Block, Boolean, CellPath, Comparison, Math, Operator}, + debugger::DebugContext, + engine::{Argument, Closure, EngineState, ErrorHandler, Matcher, Redirection, Stack}, + ir::{Call, DataSlice, Instruction, IrAstRef, IrBlock, Literal, RedirectMode}, + record, ByteStreamSource, DeclId, ErrSpan, Flag, IntoPipelineData, IntoSpanned, ListStream, + OutDest, PipelineData, PositionalArg, Range, Record, RegId, ShellError, Signals, Signature, + Span, Spanned, Type, Value, VarId, ENV_VARIABLE_ID, +}; +use nu_utils::IgnoreCaseExt; + +use crate::{eval::is_automatic_env_var, eval_block_with_early_return}; + +/// Evaluate the compiled representation of a [`Block`]. +pub fn eval_ir_block( + engine_state: &EngineState, + stack: &mut Stack, + block: &Block, + input: PipelineData, +) -> Result { + // Rust does not check recursion limits outside of const evaluation. + // But nu programs run in the same process as the shell. + // To prevent a stack overflow in user code from crashing the shell, + // we limit the recursion depth of function calls. + let maximum_call_stack_depth: u64 = engine_state.config.recursion_limit as u64; + if stack.recursion_count > maximum_call_stack_depth { + return Err(ShellError::RecursionLimitReached { + recursion_limit: maximum_call_stack_depth, + span: block.span, + }); + } + + if let Some(ir_block) = &block.ir_block { + D::enter_block(engine_state, block); + + let args_base = stack.arguments.get_base(); + let error_handler_base = stack.error_handlers.get_base(); + + // Allocate and initialize registers. I've found that it's not really worth trying to avoid + // the heap allocation here by reusing buffers - our allocator is fast enough + let mut registers = Vec::with_capacity(ir_block.register_count as usize); + for _ in 0..ir_block.register_count { + registers.push(PipelineData::Empty); + } + + // Initialize file storage. + let mut files = vec![None; ir_block.file_count as usize]; + + let result = eval_ir_block_impl::( + &mut EvalContext { + engine_state, + stack, + data: &ir_block.data, + block_span: &block.span, + args_base, + error_handler_base, + redirect_out: None, + redirect_err: None, + matches: vec![], + registers: &mut registers[..], + files: &mut files[..], + }, + ir_block, + input, + ); + + stack.error_handlers.leave_frame(error_handler_base); + stack.arguments.leave_frame(args_base); + + D::leave_block(engine_state, block); + + result + } else { + // FIXME blocks having IR should not be optional + Err(ShellError::GenericError { + error: "Can't evaluate block in IR mode".into(), + msg: "block is missing compiled representation".into(), + span: block.span, + help: Some("the IrBlock is probably missing due to a compilation error".into()), + inner: vec![], + }) + } +} + +/// All of the pointers necessary for evaluation +struct EvalContext<'a> { + engine_state: &'a EngineState, + stack: &'a mut Stack, + data: &'a Arc<[u8]>, + /// The span of the block + block_span: &'a Option, + /// Base index on the argument stack to reset to after a call + args_base: usize, + /// Base index on the error handler stack to reset to after a call + error_handler_base: usize, + /// State set by redirect-out + redirect_out: Option, + /// State set by redirect-err + redirect_err: Option, + /// Scratch space to use for `match` + matches: Vec<(VarId, Value)>, + /// Intermediate pipeline data storage used by instructions, indexed by RegId + registers: &'a mut [PipelineData], + /// Holds open files used by redirections + files: &'a mut [Option>], +} + +impl<'a> EvalContext<'a> { + /// Replace the contents of a register with a new value + #[inline] + fn put_reg(&mut self, reg_id: RegId, new_value: PipelineData) { + // log::trace!("{reg_id} <- {new_value:?}"); + self.registers[reg_id.0 as usize] = new_value; + } + + /// Borrow the contents of a register. + #[inline] + fn borrow_reg(&self, reg_id: RegId) -> &PipelineData { + &self.registers[reg_id.0 as usize] + } + + /// Replace the contents of a register with `Empty` and then return the value that it contained + #[inline] + fn take_reg(&mut self, reg_id: RegId) -> PipelineData { + // log::trace!("<- {reg_id}"); + std::mem::replace(&mut self.registers[reg_id.0 as usize], PipelineData::Empty) + } + + /// Clone data from a register. Must be collected first. + fn clone_reg(&mut self, reg_id: RegId, error_span: Span) -> Result { + match &self.registers[reg_id.0 as usize] { + PipelineData::Empty => Ok(PipelineData::Empty), + PipelineData::Value(val, meta) => Ok(PipelineData::Value(val.clone(), meta.clone())), + _ => Err(ShellError::IrEvalError { + msg: "Must collect to value before using instruction that clones from a register" + .into(), + span: Some(error_span), + }), + } + } + + /// Clone a value from a register. Must be collected first. + fn clone_reg_value(&mut self, reg_id: RegId, fallback_span: Span) -> Result { + match self.clone_reg(reg_id, fallback_span)? { + PipelineData::Empty => Ok(Value::nothing(fallback_span)), + PipelineData::Value(val, _) => Ok(val), + _ => unreachable!("clone_reg should never return stream data"), + } + } + + /// Take and implicitly collect a register to a value + fn collect_reg(&mut self, reg_id: RegId, fallback_span: Span) -> Result { + let data = self.take_reg(reg_id); + let span = data.span().unwrap_or(fallback_span); + data.into_value(span) + } + + /// Get a string from data or produce evaluation error if it's invalid UTF-8 + fn get_str(&self, slice: DataSlice, error_span: Span) -> Result<&'a str, ShellError> { + std::str::from_utf8(&self.data[slice]).map_err(|_| ShellError::IrEvalError { + msg: format!("data slice does not refer to valid UTF-8: {slice:?}"), + span: Some(error_span), + }) + } +} + +/// Eval an IR block on the provided slice of registers. +fn eval_ir_block_impl( + ctx: &mut EvalContext<'_>, + ir_block: &IrBlock, + input: PipelineData, +) -> Result { + if !ctx.registers.is_empty() { + ctx.registers[0] = input; + } + + // Program counter, starts at zero. + let mut pc = 0; + + while pc < ir_block.instructions.len() { + let instruction = &ir_block.instructions[pc]; + let span = &ir_block.spans[pc]; + let ast = &ir_block.ast[pc]; + log::trace!( + "{pc:-4}: {}", + instruction.display(ctx.engine_state, ctx.data) + ); + match eval_instruction::(ctx, instruction, span, ast) { + Ok(InstructionResult::Continue) => { + pc += 1; + } + Ok(InstructionResult::Branch(next_pc)) => { + pc = next_pc; + } + Ok(InstructionResult::Return(reg_id)) => { + return Ok(ctx.take_reg(reg_id)); + } + Ok(InstructionResult::ExitCode(exit_code)) => { + if let Some(error_handler) = ctx.stack.error_handlers.pop(ctx.error_handler_base) { + // If an error handler is set, branch there + prepare_error_handler(ctx, error_handler, None); + pc = error_handler.handler_index; + } else { + // If not, exit the block with the exit code + return Ok(PipelineData::new_external_stream_with_only_exit_code( + exit_code, + )); + } + } + Err( + err @ (ShellError::Return { .. } + | ShellError::Continue { .. } + | ShellError::Break { .. }), + ) => { + // These block control related errors should be passed through + return Err(err); + } + Err(err) => { + if let Some(error_handler) = ctx.stack.error_handlers.pop(ctx.error_handler_base) { + // If an error handler is set, branch there + prepare_error_handler(ctx, error_handler, Some(err.into_spanned(*span))); + pc = error_handler.handler_index; + } else { + // If not, exit the block with the error + return Err(err); + } + } + } + } + + // Fell out of the loop, without encountering a Return. + Err(ShellError::IrEvalError { + msg: format!( + "Program counter out of range (pc={pc}, len={len})", + len = ir_block.instructions.len(), + ), + span: *ctx.block_span, + }) +} + +/// Prepare the context for an error handler +fn prepare_error_handler( + ctx: &mut EvalContext<'_>, + error_handler: ErrorHandler, + error: Option>, +) { + if let Some(reg_id) = error_handler.error_register { + if let Some(error) = error { + // Create the error value and put it in the register + let value = Value::record( + record! { + "msg" => Value::string(format!("{}", error.item), error.span), + "debug" => Value::string(format!("{:?}", error.item), error.span), + "raw" => Value::error(error.item, error.span), + }, + error.span, + ); + ctx.put_reg(reg_id, PipelineData::Value(value, None)); + } else { + // Set the register to empty + ctx.put_reg(reg_id, PipelineData::Empty); + } + } +} + +/// The result of performing an instruction. Describes what should happen next +#[derive(Debug)] +enum InstructionResult { + Continue, + Branch(usize), + Return(RegId), + ExitCode(i32), +} + +/// Perform an instruction +fn eval_instruction( + ctx: &mut EvalContext<'_>, + instruction: &Instruction, + span: &Span, + ast: &Option, +) -> Result { + use self::InstructionResult::*; + + // See the docs for `Instruction` for more information on what these instructions are supposed + // to do. + match instruction { + Instruction::Unreachable => Err(ShellError::IrEvalError { + msg: "Reached unreachable code".into(), + span: Some(*span), + }), + Instruction::LoadLiteral { dst, lit } => load_literal(ctx, *dst, lit, *span), + Instruction::LoadValue { dst, val } => { + ctx.put_reg(*dst, Value::clone(val).into_pipeline_data()); + Ok(Continue) + } + Instruction::Move { dst, src } => { + let val = ctx.take_reg(*src); + ctx.put_reg(*dst, val); + Ok(Continue) + } + Instruction::Clone { dst, src } => { + let data = ctx.clone_reg(*src, *span)?; + ctx.put_reg(*dst, data); + Ok(Continue) + } + Instruction::Collect { src_dst } => { + let data = ctx.take_reg(*src_dst); + let value = collect(data, *span)?; + ctx.put_reg(*src_dst, value); + Ok(Continue) + } + Instruction::Span { src_dst } => { + let data = ctx.take_reg(*src_dst); + let spanned = data.with_span(*span); + ctx.put_reg(*src_dst, spanned); + Ok(Continue) + } + Instruction::Drop { src } => { + ctx.take_reg(*src); + Ok(Continue) + } + Instruction::Drain { src } => { + let data = ctx.take_reg(*src); + drain(ctx, data) + } + Instruction::LoadVariable { dst, var_id } => { + let value = get_var(ctx, *var_id, *span)?; + ctx.put_reg(*dst, value.into_pipeline_data()); + Ok(Continue) + } + Instruction::StoreVariable { var_id, src } => { + let value = ctx.collect_reg(*src, *span)?; + ctx.stack.add_var(*var_id, value); + Ok(Continue) + } + Instruction::LoadEnv { dst, key } => { + let key = ctx.get_str(*key, *span)?; + if let Some(value) = get_env_var_case_insensitive(ctx, key) { + let new_value = value.clone().into_pipeline_data(); + ctx.put_reg(*dst, new_value); + Ok(Continue) + } else { + // FIXME: using the same span twice, shouldn't this really be + // EnvVarNotFoundAtRuntime? There are tests that depend on CantFindColumn though... + Err(ShellError::CantFindColumn { + col_name: key.into(), + span: Some(*span), + src_span: *span, + }) + } + } + Instruction::LoadEnvOpt { dst, key } => { + let key = ctx.get_str(*key, *span)?; + let value = get_env_var_case_insensitive(ctx, key) + .cloned() + .unwrap_or(Value::nothing(*span)); + ctx.put_reg(*dst, value.into_pipeline_data()); + Ok(Continue) + } + Instruction::StoreEnv { key, src } => { + let key = ctx.get_str(*key, *span)?; + let value = ctx.collect_reg(*src, *span)?; + + let key = get_env_var_name_case_insensitive(ctx, key); + + if !is_automatic_env_var(&key) { + ctx.stack.add_env_var(key.into_owned(), value); + Ok(Continue) + } else { + Err(ShellError::AutomaticEnvVarSetManually { + envvar_name: key.into(), + span: *span, + }) + } + } + Instruction::PushPositional { src } => { + let val = ctx.collect_reg(*src, *span)?.with_span(*span); + ctx.stack.arguments.push(Argument::Positional { + span: *span, + val, + ast: ast.clone().map(|ast_ref| ast_ref.0), + }); + Ok(Continue) + } + Instruction::AppendRest { src } => { + let vals = ctx.collect_reg(*src, *span)?.with_span(*span); + ctx.stack.arguments.push(Argument::Spread { + span: *span, + vals, + ast: ast.clone().map(|ast_ref| ast_ref.0), + }); + Ok(Continue) + } + Instruction::PushFlag { name } => { + let data = ctx.data.clone(); + ctx.stack.arguments.push(Argument::Flag { + data, + name: *name, + short: DataSlice::empty(), + span: *span, + }); + Ok(Continue) + } + Instruction::PushShortFlag { short } => { + let data = ctx.data.clone(); + ctx.stack.arguments.push(Argument::Flag { + data, + name: DataSlice::empty(), + short: *short, + span: *span, + }); + Ok(Continue) + } + Instruction::PushNamed { name, src } => { + let val = ctx.collect_reg(*src, *span)?.with_span(*span); + let data = ctx.data.clone(); + ctx.stack.arguments.push(Argument::Named { + data, + name: *name, + short: DataSlice::empty(), + span: *span, + val, + ast: ast.clone().map(|ast_ref| ast_ref.0), + }); + Ok(Continue) + } + Instruction::PushShortNamed { short, src } => { + let val = ctx.collect_reg(*src, *span)?.with_span(*span); + let data = ctx.data.clone(); + ctx.stack.arguments.push(Argument::Named { + data, + name: DataSlice::empty(), + short: *short, + span: *span, + val, + ast: ast.clone().map(|ast_ref| ast_ref.0), + }); + Ok(Continue) + } + Instruction::PushParserInfo { name, info } => { + let data = ctx.data.clone(); + ctx.stack.arguments.push(Argument::ParserInfo { + data, + name: *name, + info: info.clone(), + }); + Ok(Continue) + } + Instruction::RedirectOut { mode } => { + ctx.redirect_out = eval_redirection(ctx, mode, *span, RedirectionStream::Out)?; + Ok(Continue) + } + Instruction::RedirectErr { mode } => { + ctx.redirect_err = eval_redirection(ctx, mode, *span, RedirectionStream::Err)?; + Ok(Continue) + } + Instruction::CheckErrRedirected { src } => match ctx.borrow_reg(*src) { + PipelineData::ByteStream(stream, _) + if matches!(stream.source(), ByteStreamSource::Child(_)) => + { + Ok(Continue) + } + _ => Err(ShellError::GenericError { + error: "Can't redirect stderr of internal command output".into(), + msg: "piping stderr only works on external commands".into(), + span: Some(*span), + help: None, + inner: vec![], + }), + }, + Instruction::OpenFile { + file_num, + path, + append, + } => { + let path = ctx.collect_reg(*path, *span)?; + let file = open_file(ctx, &path, *append)?; + ctx.files[*file_num as usize] = Some(file); + Ok(Continue) + } + Instruction::WriteFile { file_num, src } => { + let src = ctx.take_reg(*src); + let file = ctx + .files + .get(*file_num as usize) + .cloned() + .flatten() + .ok_or_else(|| ShellError::IrEvalError { + msg: format!("Tried to write to file #{file_num}, but it is not open"), + span: Some(*span), + })?; + let result = { + let mut stack = ctx + .stack + .push_redirection(Some(Redirection::File(file)), None); + src.write_to_out_dests(ctx.engine_state, &mut stack)? + }; + // Abort execution if there's an exit code from a failed external + drain(ctx, result) + } + Instruction::CloseFile { file_num } => { + if ctx.files[*file_num as usize].take().is_some() { + Ok(Continue) + } else { + Err(ShellError::IrEvalError { + msg: format!("Tried to close file #{file_num}, but it is not open"), + span: Some(*span), + }) + } + } + Instruction::Call { decl_id, src_dst } => { + let input = ctx.take_reg(*src_dst); + let result = eval_call::(ctx, *decl_id, *span, input)?; + ctx.put_reg(*src_dst, result); + Ok(Continue) + } + Instruction::StringAppend { src_dst, val } => { + let string_value = ctx.collect_reg(*src_dst, *span)?; + let operand_value = ctx.collect_reg(*val, *span)?; + let string_span = string_value.span(); + + let mut string = string_value.into_string()?; + let operand = if let Value::String { val, .. } = operand_value { + // Small optimization, so we don't have to copy the string *again* + val + } else { + operand_value.to_expanded_string(", ", ctx.engine_state.get_config()) + }; + string.push_str(&operand); + + let new_string_value = Value::string(string, string_span); + ctx.put_reg(*src_dst, new_string_value.into_pipeline_data()); + Ok(Continue) + } + Instruction::GlobFrom { src_dst, no_expand } => { + let string_value = ctx.collect_reg(*src_dst, *span)?; + let glob_value = if matches!(string_value, Value::Glob { .. }) { + // It already is a glob, so don't touch it. + string_value + } else { + // Treat it as a string, then cast + let string = string_value.into_string()?; + Value::glob(string, *no_expand, *span) + }; + ctx.put_reg(*src_dst, glob_value.into_pipeline_data()); + Ok(Continue) + } + Instruction::ListPush { src_dst, item } => { + let list_value = ctx.collect_reg(*src_dst, *span)?; + let item = ctx.collect_reg(*item, *span)?; + let list_span = list_value.span(); + let mut list = list_value.into_list()?; + list.push(item); + ctx.put_reg(*src_dst, Value::list(list, list_span).into_pipeline_data()); + Ok(Continue) + } + Instruction::ListSpread { src_dst, items } => { + let list_value = ctx.collect_reg(*src_dst, *span)?; + let items = ctx.collect_reg(*items, *span)?; + let list_span = list_value.span(); + let items_span = items.span(); + let mut list = list_value.into_list()?; + list.extend( + items + .into_list() + .map_err(|_| ShellError::CannotSpreadAsList { span: items_span })?, + ); + ctx.put_reg(*src_dst, Value::list(list, list_span).into_pipeline_data()); + Ok(Continue) + } + Instruction::RecordInsert { src_dst, key, val } => { + let record_value = ctx.collect_reg(*src_dst, *span)?; + let key = ctx.collect_reg(*key, *span)?; + let val = ctx.collect_reg(*val, *span)?; + let record_span = record_value.span(); + let mut record = record_value.into_record()?; + + let key = key.coerce_into_string()?; + if let Some(old_value) = record.insert(&key, val) { + return Err(ShellError::ColumnDefinedTwice { + col_name: key, + second_use: *span, + first_use: old_value.span(), + }); + } + + ctx.put_reg( + *src_dst, + Value::record(record, record_span).into_pipeline_data(), + ); + Ok(Continue) + } + Instruction::RecordSpread { src_dst, items } => { + let record_value = ctx.collect_reg(*src_dst, *span)?; + let items = ctx.collect_reg(*items, *span)?; + let record_span = record_value.span(); + let items_span = items.span(); + let mut record = record_value.into_record()?; + // Not using .extend() here because it doesn't handle duplicates + for (key, val) in items + .into_record() + .map_err(|_| ShellError::CannotSpreadAsRecord { span: items_span })? + { + if let Some(first_value) = record.insert(&key, val) { + return Err(ShellError::ColumnDefinedTwice { + col_name: key, + second_use: *span, + first_use: first_value.span(), + }); + } + } + ctx.put_reg( + *src_dst, + Value::record(record, record_span).into_pipeline_data(), + ); + Ok(Continue) + } + Instruction::Not { src_dst } => { + let bool = ctx.collect_reg(*src_dst, *span)?; + let negated = !bool.as_bool()?; + ctx.put_reg( + *src_dst, + Value::bool(negated, bool.span()).into_pipeline_data(), + ); + Ok(Continue) + } + Instruction::BinaryOp { lhs_dst, op, rhs } => binary_op(ctx, *lhs_dst, op, *rhs, *span), + Instruction::FollowCellPath { src_dst, path } => { + let data = ctx.take_reg(*src_dst); + let path = ctx.take_reg(*path); + if let PipelineData::Value(Value::CellPath { val: path, .. }, _) = path { + let value = data.follow_cell_path(&path.members, *span, true)?; + ctx.put_reg(*src_dst, value.into_pipeline_data()); + Ok(Continue) + } else if let PipelineData::Value(Value::Error { error, .. }, _) = path { + Err(*error) + } else { + Err(ShellError::TypeMismatch { + err_message: "expected cell path".into(), + span: path.span().unwrap_or(*span), + }) + } + } + Instruction::CloneCellPath { dst, src, path } => { + let value = ctx.clone_reg_value(*src, *span)?; + let path = ctx.take_reg(*path); + if let PipelineData::Value(Value::CellPath { val: path, .. }, _) = path { + // TODO: make follow_cell_path() not have to take ownership, probably using Cow + let value = value.follow_cell_path(&path.members, true)?; + ctx.put_reg(*dst, value.into_pipeline_data()); + Ok(Continue) + } else if let PipelineData::Value(Value::Error { error, .. }, _) = path { + Err(*error) + } else { + Err(ShellError::TypeMismatch { + err_message: "expected cell path".into(), + span: path.span().unwrap_or(*span), + }) + } + } + Instruction::UpsertCellPath { + src_dst, + path, + new_value, + } => { + let data = ctx.take_reg(*src_dst); + let metadata = data.metadata(); + // Change the span because we're modifying it + let mut value = data.into_value(*span)?; + let path = ctx.take_reg(*path); + let new_value = ctx.collect_reg(*new_value, *span)?; + if let PipelineData::Value(Value::CellPath { val: path, .. }, _) = path { + value.upsert_data_at_cell_path(&path.members, new_value)?; + ctx.put_reg(*src_dst, value.into_pipeline_data_with_metadata(metadata)); + Ok(Continue) + } else if let PipelineData::Value(Value::Error { error, .. }, _) = path { + Err(*error) + } else { + Err(ShellError::TypeMismatch { + err_message: "expected cell path".into(), + span: path.span().unwrap_or(*span), + }) + } + } + Instruction::Jump { index } => Ok(Branch(*index)), + Instruction::BranchIf { cond, index } => { + let data = ctx.take_reg(*cond); + let data_span = data.span(); + let val = match data { + PipelineData::Value(Value::Bool { val, .. }, _) => val, + PipelineData::Value(Value::Error { error, .. }, _) => { + return Err(*error); + } + _ => { + return Err(ShellError::TypeMismatch { + err_message: "expected bool".into(), + span: data_span.unwrap_or(*span), + }); + } + }; + if val { + Ok(Branch(*index)) + } else { + Ok(Continue) + } + } + Instruction::BranchIfEmpty { src, index } => { + let is_empty = matches!( + ctx.borrow_reg(*src), + PipelineData::Empty | PipelineData::Value(Value::Nothing { .. }, _) + ); + + if is_empty { + Ok(Branch(*index)) + } else { + Ok(Continue) + } + } + Instruction::Match { + pattern, + src, + index, + } => { + let value = ctx.clone_reg_value(*src, *span)?; + ctx.matches.clear(); + if pattern.match_value(&value, &mut ctx.matches) { + // Match succeeded: set variables and branch + for (var_id, match_value) in ctx.matches.drain(..) { + ctx.stack.add_var(var_id, match_value); + } + Ok(Branch(*index)) + } else { + // Failed to match, put back original value + ctx.matches.clear(); + Ok(Continue) + } + } + Instruction::CheckMatchGuard { src } => { + if matches!( + ctx.borrow_reg(*src), + PipelineData::Value(Value::Bool { .. }, _) + ) { + Ok(Continue) + } else { + Err(ShellError::MatchGuardNotBool { span: *span }) + } + } + Instruction::Iterate { + dst, + stream, + end_index, + } => eval_iterate(ctx, *dst, *stream, *end_index), + Instruction::OnError { index } => { + ctx.stack.error_handlers.push(ErrorHandler { + handler_index: *index, + error_register: None, + }); + Ok(Continue) + } + Instruction::OnErrorInto { index, dst } => { + ctx.stack.error_handlers.push(ErrorHandler { + handler_index: *index, + error_register: Some(*dst), + }); + Ok(Continue) + } + Instruction::PopErrorHandler => { + ctx.stack.error_handlers.pop(ctx.error_handler_base); + Ok(Continue) + } + Instruction::CheckExternalFailed { dst, src } => { + let data = ctx.take_reg(*src); + let (data, failed) = data.check_external_failed()?; + ctx.put_reg(*src, data); + ctx.put_reg(*dst, Value::bool(failed, *span).into_pipeline_data()); + Ok(Continue) + } + Instruction::ReturnEarly { src } => { + let val = ctx.collect_reg(*src, *span)?; + Err(ShellError::Return { + span: *span, + value: Box::new(val), + }) + } + Instruction::Return { src } => Ok(Return(*src)), + } +} + +/// Load a literal value into a register +fn load_literal( + ctx: &mut EvalContext<'_>, + dst: RegId, + lit: &Literal, + span: Span, +) -> Result { + let value = literal_value(ctx, lit, span)?; + ctx.put_reg(dst, PipelineData::Value(value, None)); + Ok(InstructionResult::Continue) +} + +fn literal_value( + ctx: &mut EvalContext<'_>, + lit: &Literal, + span: Span, +) -> Result { + Ok(match lit { + Literal::Bool(b) => Value::bool(*b, span), + Literal::Int(i) => Value::int(*i, span), + Literal::Float(f) => Value::float(*f, span), + Literal::Filesize(q) => Value::filesize(*q, span), + Literal::Duration(q) => Value::duration(*q, span), + Literal::Binary(bin) => Value::binary(&ctx.data[*bin], span), + Literal::Block(block_id) | Literal::RowCondition(block_id) | Literal::Closure(block_id) => { + let block = ctx.engine_state.get_block(*block_id); + let captures = block + .captures + .iter() + .map(|var_id| get_var(ctx, *var_id, span).map(|val| (*var_id, val))) + .collect::, ShellError>>()?; + Value::closure( + Closure { + block_id: *block_id, + captures, + }, + span, + ) + } + Literal::Range { + start, + step, + end, + inclusion, + } => { + let start = ctx.collect_reg(*start, span)?; + let step = ctx.collect_reg(*step, span)?; + let end = ctx.collect_reg(*end, span)?; + let range = Range::new(start, step, end, *inclusion, span)?; + Value::range(range, span) + } + Literal::List { capacity } => Value::list(Vec::with_capacity(*capacity), span), + Literal::Record { capacity } => Value::record(Record::with_capacity(*capacity), span), + Literal::Filepath { + val: path, + no_expand, + } => { + let path = ctx.get_str(*path, span)?; + if *no_expand { + Value::string(path, span) + } else { + let cwd = ctx.engine_state.cwd(Some(ctx.stack))?; + let path = expand_path_with(path, cwd, true); + + Value::string(path.to_string_lossy(), span) + } + } + Literal::Directory { + val: path, + no_expand, + } => { + let path = ctx.get_str(*path, span)?; + if path == "-" { + Value::string("-", span) + } else if *no_expand { + Value::string(path, span) + } else { + let cwd = ctx + .engine_state + .cwd(Some(ctx.stack)) + .map(AbsolutePathBuf::into_std_path_buf) + .unwrap_or_default(); + let path = expand_path_with(path, cwd, true); + + Value::string(path.to_string_lossy(), span) + } + } + Literal::GlobPattern { val, no_expand } => { + Value::glob(ctx.get_str(*val, span)?, *no_expand, span) + } + Literal::String(s) => Value::string(ctx.get_str(*s, span)?, span), + Literal::RawString(s) => Value::string(ctx.get_str(*s, span)?, span), + Literal::CellPath(path) => Value::cell_path(CellPath::clone(path), span), + Literal::Date(dt) => Value::date(**dt, span), + Literal::Nothing => Value::nothing(span), + }) +} + +fn binary_op( + ctx: &mut EvalContext<'_>, + lhs_dst: RegId, + op: &Operator, + rhs: RegId, + span: Span, +) -> Result { + let lhs_val = ctx.collect_reg(lhs_dst, span)?; + let rhs_val = ctx.collect_reg(rhs, span)?; + + // Handle binary op errors early + if let Value::Error { error, .. } = lhs_val { + return Err(*error); + } + if let Value::Error { error, .. } = rhs_val { + return Err(*error); + } + + // We only have access to one span here, but the generated code usually adds a `span` + // instruction to set the output span to the right span. + let op_span = span; + + let result = match op { + Operator::Comparison(cmp) => match cmp { + Comparison::Equal => lhs_val.eq(op_span, &rhs_val, span)?, + Comparison::NotEqual => lhs_val.ne(op_span, &rhs_val, span)?, + Comparison::LessThan => lhs_val.lt(op_span, &rhs_val, span)?, + Comparison::GreaterThan => lhs_val.gt(op_span, &rhs_val, span)?, + Comparison::LessThanOrEqual => lhs_val.lte(op_span, &rhs_val, span)?, + Comparison::GreaterThanOrEqual => lhs_val.gte(op_span, &rhs_val, span)?, + Comparison::RegexMatch => { + lhs_val.regex_match(ctx.engine_state, op_span, &rhs_val, false, span)? + } + Comparison::NotRegexMatch => { + lhs_val.regex_match(ctx.engine_state, op_span, &rhs_val, true, span)? + } + Comparison::In => lhs_val.r#in(op_span, &rhs_val, span)?, + Comparison::NotIn => lhs_val.not_in(op_span, &rhs_val, span)?, + Comparison::StartsWith => lhs_val.starts_with(op_span, &rhs_val, span)?, + Comparison::EndsWith => lhs_val.ends_with(op_span, &rhs_val, span)?, + }, + 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::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)?, + Math::Modulo => lhs_val.modulo(op_span, &rhs_val, span)?, + Math::FloorDivision => lhs_val.floor_div(op_span, &rhs_val, span)?, + Math::Pow => lhs_val.pow(op_span, &rhs_val, span)?, + }, + Operator::Boolean(bl) => match bl { + Boolean::And => lhs_val.and(op_span, &rhs_val, span)?, + Boolean::Or => lhs_val.or(op_span, &rhs_val, span)?, + Boolean::Xor => lhs_val.xor(op_span, &rhs_val, span)?, + }, + Operator::Bits(bit) => match bit { + Bits::BitOr => lhs_val.bit_or(op_span, &rhs_val, span)?, + Bits::BitXor => lhs_val.bit_xor(op_span, &rhs_val, span)?, + Bits::BitAnd => lhs_val.bit_and(op_span, &rhs_val, span)?, + Bits::ShiftLeft => lhs_val.bit_shl(op_span, &rhs_val, span)?, + Bits::ShiftRight => lhs_val.bit_shr(op_span, &rhs_val, span)?, + }, + Operator::Assignment(_asg) => { + return Err(ShellError::IrEvalError { + msg: "can't eval assignment with the `binary-op` instruction".into(), + span: Some(span), + }) + } + }; + + ctx.put_reg(lhs_dst, PipelineData::Value(result, None)); + + Ok(InstructionResult::Continue) +} + +/// Evaluate a call +fn eval_call( + ctx: &mut EvalContext<'_>, + decl_id: DeclId, + head: Span, + input: PipelineData, +) -> Result { + let EvalContext { + engine_state, + stack: caller_stack, + args_base, + redirect_out, + redirect_err, + .. + } = ctx; + + let args_len = caller_stack.arguments.get_len(*args_base); + let decl = engine_state.get_decl(decl_id); + + // Set up redirect modes + let mut caller_stack = caller_stack.push_redirection(redirect_out.take(), redirect_err.take()); + + let result; + + if let Some(block_id) = decl.block_id() { + // If the decl is a custom command + let block = engine_state.get_block(block_id); + + // Set up a callee stack with the captures and move arguments from the stack into variables + let mut callee_stack = caller_stack.gather_captures(engine_state, &block.captures); + + gather_arguments( + engine_state, + block, + &mut caller_stack, + &mut callee_stack, + *args_base, + args_len, + head, + )?; + + // Add one to the recursion count, so we don't recurse too deep. Stack overflows are not + // recoverable in Rust. + callee_stack.recursion_count += 1; + + result = eval_block_with_early_return::(engine_state, &mut callee_stack, block, input); + + // Move environment variables back into the caller stack scope if requested to do so + if block.redirect_env { + redirect_env(engine_state, &mut caller_stack, &callee_stack); + } + } else { + // FIXME: precalculate this and save it somewhere + let span = Span::merge_many( + std::iter::once(head).chain( + caller_stack + .arguments + .get_args(*args_base, args_len) + .iter() + .flat_map(|arg| arg.span()), + ), + ); + + let call = Call { + decl_id, + head, + span, + args_base: *args_base, + args_len, + }; + + // Run the call + result = decl.run(engine_state, &mut caller_stack, &(&call).into(), input); + }; + + drop(caller_stack); + + // Important that this runs, to reset state post-call: + ctx.stack.arguments.leave_frame(ctx.args_base); + ctx.redirect_out = None; + ctx.redirect_err = None; + + result +} + +fn find_named_var_id( + sig: &Signature, + name: &[u8], + short: &[u8], + span: Span, +) -> Result { + sig.named + .iter() + .find(|n| { + if !n.long.is_empty() { + n.long.as_bytes() == name + } else { + // It's possible to only have a short name and no long name + n.short + .is_some_and(|s| s.encode_utf8(&mut [0; 4]).as_bytes() == short) + } + }) + .ok_or_else(|| ShellError::IrEvalError { + msg: format!( + "block does not have an argument named `{}`", + String::from_utf8_lossy(name) + ), + span: Some(span), + }) + .and_then(|flag| expect_named_var_id(flag, span)) +} + +fn expect_named_var_id(arg: &Flag, span: Span) -> Result { + arg.var_id.ok_or_else(|| ShellError::IrEvalError { + msg: format!( + "block signature is missing var id for named arg `{}`", + arg.long + ), + span: Some(span), + }) +} + +fn expect_positional_var_id(arg: &PositionalArg, span: Span) -> Result { + arg.var_id.ok_or_else(|| ShellError::IrEvalError { + msg: format!( + "block signature is missing var id for positional arg `{}`", + arg.name + ), + span: Some(span), + }) +} + +/// Move arguments from the stack into variables for a custom command +fn gather_arguments( + engine_state: &EngineState, + block: &Block, + caller_stack: &mut Stack, + callee_stack: &mut Stack, + args_base: usize, + args_len: usize, + call_head: Span, +) -> Result<(), ShellError> { + let mut positional_iter = block + .signature + .required_positional + .iter() + .map(|p| (p, true)) + .chain( + block + .signature + .optional_positional + .iter() + .map(|p| (p, false)), + ); + + // Arguments that didn't get consumed by required/optional + let mut rest = vec![]; + + // If we encounter a spread, all further positionals should go to rest + let mut always_spread = false; + + for arg in caller_stack.arguments.drain_args(args_base, args_len) { + match arg { + Argument::Positional { span, val, .. } => { + // Don't check next positional arg if we encountered a spread previously + let next = (!always_spread).then(|| positional_iter.next()).flatten(); + if let Some((positional_arg, required)) = next { + let var_id = expect_positional_var_id(positional_arg, span)?; + if required { + // By checking the type of the bound variable rather than converting the + // SyntaxShape here, we might be able to save some allocations and effort + let variable = engine_state.get_var(var_id); + check_type(&val, &variable.ty)?; + } + callee_stack.add_var(var_id, val); + } else { + rest.push(val); + } + } + Argument::Spread { vals, .. } => { + if let Value::List { vals, .. } = vals { + rest.extend(vals); + // All further positional args should go to spread + always_spread = true; + } else if let Value::Error { error, .. } = vals { + return Err(*error); + } else { + return Err(ShellError::CannotSpreadAsList { span: vals.span() }); + } + } + Argument::Flag { + data, + name, + short, + span, + } => { + let var_id = find_named_var_id(&block.signature, &data[name], &data[short], span)?; + callee_stack.add_var(var_id, Value::bool(true, span)) + } + Argument::Named { + data, + name, + short, + span, + val, + .. + } => { + let var_id = find_named_var_id(&block.signature, &data[name], &data[short], span)?; + callee_stack.add_var(var_id, val) + } + Argument::ParserInfo { .. } => (), + } + } + + // Add the collected rest of the arguments if a spread argument exists + if let Some(rest_arg) = &block.signature.rest_positional { + let rest_span = rest.first().map(|v| v.span()).unwrap_or(call_head); + let var_id = expect_positional_var_id(rest_arg, rest_span)?; + callee_stack.add_var(var_id, Value::list(rest, rest_span)); + } + + // Check for arguments that haven't yet been set and set them to their defaults + for (positional_arg, _) in positional_iter { + let var_id = expect_positional_var_id(positional_arg, call_head)?; + callee_stack.add_var( + var_id, + positional_arg + .default_value + .clone() + .unwrap_or(Value::nothing(call_head)), + ); + } + + for named_arg in &block.signature.named { + if let Some(var_id) = named_arg.var_id { + // For named arguments, we do this check by looking to see if the variable was set yet on + // the stack. This assumes that the stack's variables was previously empty, but that's a + // fair assumption for a brand new callee stack. + if !callee_stack.vars.iter().any(|(id, _)| *id == var_id) { + let val = if named_arg.arg.is_none() { + Value::bool(false, call_head) + } else if let Some(value) = &named_arg.default_value { + value.clone() + } else { + Value::nothing(call_head) + }; + callee_stack.add_var(var_id, val); + } + } + } + + Ok(()) +} + +/// Type check helper. Produces `CantConvert` error if `val` is not compatible with `ty`. +fn check_type(val: &Value, ty: &Type) -> Result<(), ShellError> { + if match val { + // An empty list is compatible with any list or table type + Value::List { vals, .. } if vals.is_empty() => { + matches!(ty, Type::Any | Type::List(_) | Type::Table(_)) + } + // FIXME: the allocation that might be required here is not great, it would be nice to be + // able to just directly check whether a value is compatible with a type + _ => val.get_type().is_subtype(ty), + } { + Ok(()) + } else { + Err(ShellError::CantConvert { + to_type: ty.to_string(), + from_type: val.get_type().to_string(), + span: val.span(), + help: None, + }) + } +} + +/// Get variable from [`Stack`] or [`EngineState`] +fn get_var(ctx: &EvalContext<'_>, var_id: VarId, span: Span) -> Result { + match var_id { + // $env + ENV_VARIABLE_ID => { + let env_vars = ctx.stack.get_env_vars(ctx.engine_state); + let env_columns = env_vars.keys(); + let env_values = env_vars.values(); + + let mut pairs = env_columns + .map(|x| x.to_string()) + .zip(env_values.cloned()) + .collect::>(); + + pairs.sort_by(|a, b| a.0.cmp(&b.0)); + + Ok(Value::record(pairs.into_iter().collect(), span)) + } + _ => ctx.stack.get_var(var_id, span).or_else(|err| { + // $nu is handled by getting constant + if let Some(const_val) = ctx.engine_state.get_constant(var_id).cloned() { + Ok(const_val.with_span(span)) + } else { + Err(err) + } + }), + } +} + +/// Get an environment variable, case-insensitively +fn get_env_var_case_insensitive<'a>(ctx: &'a mut EvalContext<'_>, key: &str) -> Option<&'a Value> { + // Read scopes in order + ctx.stack + .env_vars + .iter() + .rev() + .chain(std::iter::once(ctx.engine_state.env_vars.as_ref())) + .flat_map(|overlays| { + // Read overlays in order + ctx.stack + .active_overlays + .iter() + .rev() + .filter_map(|name| overlays.get(name)) + }) + .find_map(|map| { + // Use the hashmap first to try to be faster? + map.get(key).or_else(|| { + // Check to see if it exists at all in the map + map.iter() + .find_map(|(k, v)| k.eq_ignore_case(key).then_some(v)) + }) + }) +} + +/// Get the existing name of an environment variable, case-insensitively. This is used to implement +/// case preservation of environment variables, so that changing an environment variable that +/// already exists always uses the same case. +fn get_env_var_name_case_insensitive<'a>(ctx: &mut EvalContext<'_>, key: &'a str) -> Cow<'a, str> { + // Read scopes in order + ctx.stack + .env_vars + .iter() + .rev() + .chain(std::iter::once(ctx.engine_state.env_vars.as_ref())) + .flat_map(|overlays| { + // Read overlays in order + ctx.stack + .active_overlays + .iter() + .rev() + .filter_map(|name| overlays.get(name)) + }) + .find_map(|map| { + // Use the hashmap first to try to be faster? + if map.contains_key(key) { + Some(Cow::Borrowed(key)) + } else { + map.keys().find(|k| k.eq_ignore_case(key)).map(|k| { + // it exists, but with a different case + Cow::Owned(k.to_owned()) + }) + } + }) + // didn't exist. + .unwrap_or(Cow::Borrowed(key)) +} + +/// Helper to collect values into [`PipelineData`], preserving original span and metadata +fn collect(data: PipelineData, fallback_span: Span) -> Result { + let span = data.span().unwrap_or(fallback_span); + let metadata = data.metadata(); + let value = data.into_value(span)?; + Ok(PipelineData::Value(value, metadata)) +} + +/// Helper for drain behavior. Returns `Ok(ExitCode)` on failed external. +fn drain(ctx: &mut EvalContext<'_>, data: PipelineData) -> Result { + use self::InstructionResult::*; + let span = data.span().unwrap_or(Span::unknown()); + if let Some(exit_status) = data.drain()? { + ctx.stack.add_env_var( + "LAST_EXIT_CODE".into(), + Value::int(exit_status.code() as i64, span), + ); + if exit_status.code() == 0 { + Ok(Continue) + } else { + Ok(ExitCode(exit_status.code())) + } + } else { + Ok(Continue) + } +} + +enum RedirectionStream { + Out, + Err, +} + +/// Open a file for redirection +fn open_file(ctx: &EvalContext<'_>, path: &Value, append: bool) -> Result, ShellError> { + let path_expanded = + expand_path_with(path.as_str()?, ctx.engine_state.cwd(Some(ctx.stack))?, true); + let mut options = File::options(); + if append { + options.append(true); + } else { + options.write(true).truncate(true); + } + let file = options + .create(true) + .open(path_expanded) + .err_span(path.span())?; + Ok(Arc::new(file)) +} + +/// Set up a [`Redirection`] from a [`RedirectMode`] +fn eval_redirection( + ctx: &mut EvalContext<'_>, + mode: &RedirectMode, + span: Span, + which: RedirectionStream, +) -> Result, ShellError> { + match mode { + RedirectMode::Pipe => Ok(Some(Redirection::Pipe(OutDest::Pipe))), + RedirectMode::Capture => Ok(Some(Redirection::Pipe(OutDest::Capture))), + RedirectMode::Null => Ok(Some(Redirection::Pipe(OutDest::Null))), + RedirectMode::Inherit => Ok(Some(Redirection::Pipe(OutDest::Inherit))), + RedirectMode::File { file_num } => { + let file = ctx + .files + .get(*file_num as usize) + .cloned() + .flatten() + .ok_or_else(|| ShellError::IrEvalError { + msg: format!("Tried to redirect to file #{file_num}, but it is not open"), + span: Some(span), + })?; + Ok(Some(Redirection::File(file))) + } + RedirectMode::Caller => Ok(match which { + RedirectionStream::Out => ctx.stack.pipe_stdout().cloned().map(Redirection::Pipe), + RedirectionStream::Err => ctx.stack.pipe_stderr().cloned().map(Redirection::Pipe), + }), + } +} + +/// Do an `iterate` instruction. This can be called repeatedly to get more values from an iterable +fn eval_iterate( + ctx: &mut EvalContext<'_>, + dst: RegId, + stream: RegId, + end_index: usize, +) -> Result { + let mut data = ctx.take_reg(stream); + if let PipelineData::ListStream(list_stream, _) = &mut data { + // Modify the stream, taking one value off, and branching if it's empty + if let Some(val) = list_stream.next_value() { + ctx.put_reg(dst, val.into_pipeline_data()); + ctx.put_reg(stream, data); // put the stream back so it can be iterated on again + Ok(InstructionResult::Continue) + } else { + ctx.put_reg(dst, PipelineData::Empty); + Ok(InstructionResult::Branch(end_index)) + } + } else { + // Convert the PipelineData to an iterator, and wrap it in a ListStream so it can be + // iterated on + let metadata = data.metadata(); + let span = data.span().unwrap_or(Span::unknown()); + ctx.put_reg( + stream, + PipelineData::ListStream( + ListStream::new(data.into_iter(), span, Signals::EMPTY), + metadata, + ), + ); + eval_iterate(ctx, dst, stream, end_index) + } +} + +/// Redirect environment from the callee stack to the caller stack +fn redirect_env(engine_state: &EngineState, caller_stack: &mut Stack, callee_stack: &Stack) { + // TODO: make this more efficient + // Grab all environment variables from the callee + let caller_env_vars = caller_stack.get_env_var_names(engine_state); + + // remove env vars that are present in the caller but not in the callee + // (the callee hid them) + for var in caller_env_vars.iter() { + if !callee_stack.has_env_var(engine_state, var) { + caller_stack.remove_env_var(engine_state, var); + } + } + + // add new env vars from callee to caller + for (var, value) in callee_stack.get_stack_env_vars() { + caller_stack.add_env_var(var, value); + } +} diff --git a/crates/nu-engine/src/lib.rs b/crates/nu-engine/src/lib.rs index e3c8f8eede..7ed246e975 100644 --- a/crates/nu-engine/src/lib.rs +++ b/crates/nu-engine/src/lib.rs @@ -2,16 +2,19 @@ mod call_ext; mod closure_eval; pub mod column; pub mod command_prelude; +mod compile; pub mod documentation; pub mod env; mod eval; mod eval_helpers; +mod eval_ir; mod glob_from; pub mod scope; pub use call_ext::CallExt; pub use closure_eval::*; pub use column::get_columns; +pub use compile::compile; pub use documentation::get_full_help; pub use env::*; pub use eval::{ @@ -19,4 +22,5 @@ pub use eval::{ eval_expression_with_input, eval_subexpression, eval_variable, redirect_env, }; pub use eval_helpers::*; +pub use eval_ir::eval_ir_block; pub use glob_from::glob_from; diff --git a/crates/nu-parser/src/known_external.rs b/crates/nu-parser/src/known_external.rs index 453112aa32..a41cf3a4e8 100644 --- a/crates/nu-parser/src/known_external.rs +++ b/crates/nu-parser/src/known_external.rs @@ -1,7 +1,8 @@ use nu_engine::command_prelude::*; use nu_protocol::{ - ast::{Argument, Expr, Expression}, - engine::{CommandType, UNKNOWN_SPAN_ID}, + ast::{self, Expr, Expression}, + engine::{self, CallImpl, CommandType, UNKNOWN_SPAN_ID}, + ir::{self, DataSlice}, }; #[derive(Clone)] @@ -43,8 +44,6 @@ impl Command for KnownExternal { let command = engine_state.get_decl(decl_id); - let mut extern_call = Call::new(head_span); - let extern_name = if let Some(name_bytes) = engine_state.find_decl_name(call.decl_id, &[]) { String::from_utf8_lossy(name_bytes) } else { @@ -56,59 +55,166 @@ impl Command for KnownExternal { }; let extern_name: Vec<_> = extern_name.split(' ').collect(); - let call_head_id = engine_state - .find_span_id(call.head) - .unwrap_or(UNKNOWN_SPAN_ID); - let arg_extern_name = Expression::new_existing( - Expr::String(extern_name[0].to_string()), + match &call.inner { + CallImpl::AstRef(call) => { + let extern_call = ast_call_to_extern_call(engine_state, call, &extern_name)?; + command.run(engine_state, stack, &(&extern_call).into(), input) + } + CallImpl::AstBox(call) => { + let extern_call = ast_call_to_extern_call(engine_state, call, &extern_name)?; + command.run(engine_state, stack, &(&extern_call).into(), input) + } + CallImpl::IrRef(call) => { + let extern_call = ir_call_to_extern_call(stack, call, &extern_name)?; + command.run(engine_state, stack, &(&extern_call).into(), input) + } + CallImpl::IrBox(call) => { + let extern_call = ir_call_to_extern_call(stack, call, &extern_name)?; + command.run(engine_state, stack, &(&extern_call).into(), input) + } + } + } +} + +/// Transform the args from an `ast::Call` onto a `run-external` call +fn ast_call_to_extern_call( + engine_state: &EngineState, + call: &ast::Call, + extern_name: &[&str], +) -> Result { + let head_span = call.head; + + let mut extern_call = ast::Call::new(head_span); + + let call_head_id = engine_state + .find_span_id(call.head) + .unwrap_or(UNKNOWN_SPAN_ID); + + let arg_extern_name = Expression::new_existing( + Expr::String(extern_name[0].to_string()), + call.head, + call_head_id, + Type::String, + ); + + extern_call.add_positional(arg_extern_name); + + for subcommand in extern_name.iter().skip(1) { + extern_call.add_positional(Expression::new_existing( + Expr::String(subcommand.to_string()), call.head, call_head_id, Type::String, - ); + )); + } - extern_call.add_positional(arg_extern_name); - - for subcommand in extern_name.into_iter().skip(1) { - extern_call.add_positional(Expression::new_existing( - Expr::String(subcommand.to_string()), - call.head, - call_head_id, - Type::String, - )); - } - - for arg in &call.arguments { - match arg { - Argument::Positional(positional) => extern_call.add_positional(positional.clone()), - Argument::Named(named) => { - let named_span_id = engine_state - .find_span_id(named.0.span) - .unwrap_or(UNKNOWN_SPAN_ID); - if let Some(short) = &named.1 { - extern_call.add_positional(Expression::new_existing( - Expr::String(format!("-{}", short.item)), - named.0.span, - named_span_id, - Type::String, - )); - } else { - extern_call.add_positional(Expression::new_existing( - Expr::String(format!("--{}", named.0.item)), - named.0.span, - named_span_id, - Type::String, - )); - } - if let Some(arg) = &named.2 { - extern_call.add_positional(arg.clone()); - } + for arg in &call.arguments { + match arg { + ast::Argument::Positional(positional) => extern_call.add_positional(positional.clone()), + ast::Argument::Named(named) => { + let named_span_id = engine_state + .find_span_id(named.0.span) + .unwrap_or(UNKNOWN_SPAN_ID); + if let Some(short) = &named.1 { + extern_call.add_positional(Expression::new_existing( + Expr::String(format!("-{}", short.item)), + named.0.span, + named_span_id, + Type::String, + )); + } else { + extern_call.add_positional(Expression::new_existing( + Expr::String(format!("--{}", named.0.item)), + named.0.span, + named_span_id, + Type::String, + )); } - Argument::Unknown(unknown) => extern_call.add_unknown(unknown.clone()), - Argument::Spread(args) => extern_call.add_spread(args.clone()), + if let Some(arg) = &named.2 { + extern_call.add_positional(arg.clone()); + } + } + ast::Argument::Unknown(unknown) => extern_call.add_unknown(unknown.clone()), + ast::Argument::Spread(args) => extern_call.add_spread(args.clone()), + } + } + + Ok(extern_call) +} + +/// Transform the args from an `ir::Call` onto a `run-external` call +fn ir_call_to_extern_call( + stack: &mut Stack, + call: &ir::Call, + extern_name: &[&str], +) -> Result { + let mut extern_call = ir::Call::build(call.decl_id, call.head); + + // Add the command and subcommands + for name in extern_name { + extern_call.add_positional(stack, call.head, Value::string(*name, call.head)); + } + + // Add the arguments, reformatting named arguments into string positionals + for index in 0..call.args_len { + match &call.arguments(stack)[index] { + engine::Argument::Flag { + data, + name, + short, + span, + } => { + let name_arg = engine::Argument::Positional { + span: *span, + val: Value::string(known_external_option_name(data, *name, *short), *span), + ast: None, + }; + extern_call.add_argument(stack, name_arg); + } + engine::Argument::Named { + data, + name, + short, + span, + val, + .. + } => { + let name_arg = engine::Argument::Positional { + span: *span, + val: Value::string(known_external_option_name(data, *name, *short), *span), + ast: None, + }; + let val_arg = engine::Argument::Positional { + span: *span, + val: val.clone(), + ast: None, + }; + extern_call.add_argument(stack, name_arg); + extern_call.add_argument(stack, val_arg); + } + a @ (engine::Argument::Positional { .. } + | engine::Argument::Spread { .. } + | engine::Argument::ParserInfo { .. }) => { + let argument = a.clone(); + extern_call.add_argument(stack, argument); } } + } - command.run(engine_state, stack, &extern_call, input) + Ok(extern_call.finish()) +} + +fn known_external_option_name(data: &[u8], name: DataSlice, short: DataSlice) -> String { + if !data[name].is_empty() { + format!( + "--{}", + std::str::from_utf8(&data[name]).expect("invalid utf-8 in flag name") + ) + } else { + format!( + "-{}", + std::str::from_utf8(&data[short]).expect("invalid utf-8 in flag short name") + ) } } diff --git a/crates/nu-parser/src/parse_patterns.rs b/crates/nu-parser/src/parse_patterns.rs index 73668b7d04..dc4a64ce37 100644 --- a/crates/nu-parser/src/parse_patterns.rs +++ b/crates/nu-parser/src/parse_patterns.rs @@ -39,7 +39,7 @@ pub fn parse_pattern(working_set: &mut StateWorkingSet, span: Span) -> MatchPatt let value = parse_value(working_set, span, &SyntaxShape::Any); MatchPattern { - pattern: Pattern::Value(value), + pattern: Pattern::Value(Box::new(value)), guard: None, span, } diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 51935214f5..fc2131aad7 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -3302,6 +3302,8 @@ pub fn parse_row_condition(working_set: &mut StateWorkingSet, spans: &[Span]) -> default_value: None, }); + compile_block(working_set, &mut block); + working_set.add_block(Arc::new(block)) } }; @@ -4445,7 +4447,7 @@ pub fn parse_match_block_expression(working_set: &mut StateWorkingSet, span: Spa &SyntaxShape::MathExpression, ); - pattern.guard = Some(guard); + pattern.guard = Some(Box::new(guard)); position += if found { start + 1 } else { start }; connector = working_set.get_span_contents(output[position].span); } @@ -5298,6 +5300,8 @@ pub fn parse_expression(working_set: &mut StateWorkingSet, spans: &[Span]) -> Ex let ty = output.ty.clone(); block.pipelines = vec![Pipeline::from_vec(vec![output])]; + compile_block(working_set, &mut block); + let block_id = working_set.add_block(Arc::new(block)); let mut env_vars = vec![]; @@ -5853,9 +5857,25 @@ pub fn parse_block( working_set.parse_errors.extend_from_slice(&errors); } + // Do not try to compile blocks that are subexpressions, or when we've already had a parse + // failure as that definitely will fail to compile + if !is_subexpression && working_set.parse_errors.is_empty() { + compile_block(working_set, &mut block); + } + block } +/// Compile an IR block for the `Block`, adding a compile error on failure +fn compile_block(working_set: &mut StateWorkingSet<'_>, block: &mut Block) { + match nu_engine::compile(working_set, block) { + Ok(ir_block) => { + block.ir_block = Some(ir_block); + } + Err(err) => working_set.compile_errors.push(err), + } +} + pub fn discover_captures_in_closure( working_set: &StateWorkingSet, block: &Block, @@ -6298,12 +6318,14 @@ fn wrap_expr_with_collect(working_set: &mut StateWorkingSet, expr: &Expression) default_value: None, }); - let block = Block { + let mut block = Block { pipelines: vec![Pipeline::from_vec(vec![expr.clone()])], signature: Box::new(signature), ..Default::default() }; + compile_block(working_set, &mut block); + let block_id = working_set.add_block(Arc::new(block)); output.push(Argument::Positional(Expression::new( diff --git a/crates/nu-parser/tests/test_parser.rs b/crates/nu-parser/tests/test_parser.rs index 0784fe69d4..7762863ba1 100644 --- a/crates/nu-parser/tests/test_parser.rs +++ b/crates/nu-parser/tests/test_parser.rs @@ -1,7 +1,7 @@ use nu_parser::*; use nu_protocol::{ - ast::{Argument, Call, Expr, Expression, ExternalArgument, PathMember, Range}, - engine::{Command, EngineState, Stack, StateWorkingSet}, + ast::{Argument, Expr, Expression, ExternalArgument, PathMember, Range}, + engine::{Call, Command, EngineState, Stack, StateWorkingSet}, ParseError, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, }; use rstest::rstest; @@ -1759,10 +1759,7 @@ mod range { #[cfg(test)] mod input_types { use super::*; - use nu_protocol::{ - ast::{Argument, Call}, - Category, PipelineData, ShellError, Type, - }; + use nu_protocol::{ast::Argument, engine::Call, Category, PipelineData, ShellError, Type}; #[derive(Clone)] pub struct LsTest; diff --git a/crates/nu-plugin-engine/src/context.rs b/crates/nu-plugin-engine/src/context.rs index b026d21b23..0df533a11e 100644 --- a/crates/nu-plugin-engine/src/context.rs +++ b/crates/nu-plugin-engine/src/context.rs @@ -1,8 +1,7 @@ use crate::util::MutableCow; use nu_engine::{get_eval_block_with_early_return, get_full_help, ClosureEvalOnce}; use nu_protocol::{ - ast::Call, - engine::{Closure, EngineState, Redirection, Stack}, + engine::{Call, Closure, EngineState, Redirection, Stack}, Config, IntoSpanned, OutDest, PipelineData, PluginIdentity, ShellError, Signals, Span, Spanned, Value, }; @@ -54,7 +53,7 @@ pub struct PluginExecutionCommandContext<'a> { identity: Arc, engine_state: Cow<'a, EngineState>, stack: MutableCow<'a, Stack>, - call: Cow<'a, Call>, + call: Call<'a>, } impl<'a> PluginExecutionCommandContext<'a> { @@ -62,13 +61,13 @@ impl<'a> PluginExecutionCommandContext<'a> { identity: Arc, engine_state: &'a EngineState, stack: &'a mut Stack, - call: &'a Call, + call: &'a Call<'a>, ) -> PluginExecutionCommandContext<'a> { PluginExecutionCommandContext { identity, engine_state: Cow::Borrowed(engine_state), stack: MutableCow::Borrowed(stack), - call: Cow::Borrowed(call), + call: call.clone(), } } } @@ -217,7 +216,7 @@ impl<'a> PluginExecutionContext for PluginExecutionCommandContext<'a> { identity: self.identity.clone(), engine_state: Cow::Owned(self.engine_state.clone().into_owned()), stack: self.stack.owned(), - call: Cow::Owned(self.call.clone().into_owned()), + call: self.call.to_owned(), }) } } diff --git a/crates/nu-plugin-protocol/src/evaluated_call.rs b/crates/nu-plugin-protocol/src/evaluated_call.rs index 19f9049340..58f8987865 100644 --- a/crates/nu-plugin-protocol/src/evaluated_call.rs +++ b/crates/nu-plugin-protocol/src/evaluated_call.rs @@ -1,7 +1,7 @@ use nu_protocol::{ - ast::{Call, Expression}, - engine::{EngineState, Stack}, - FromValue, ShellError, Span, Spanned, Value, + ast::{self, Expression}, + engine::{Call, CallImpl, EngineState, Stack}, + ir, FromValue, ShellError, Span, Spanned, Value, }; use serde::{Deserialize, Serialize}; @@ -33,6 +33,24 @@ impl EvaluatedCall { engine_state: &EngineState, stack: &mut Stack, eval_expression_fn: fn(&EngineState, &mut Stack, &Expression) -> Result, + ) -> Result { + match &call.inner { + CallImpl::AstRef(call) => { + Self::try_from_ast_call(call, engine_state, stack, eval_expression_fn) + } + CallImpl::AstBox(call) => { + Self::try_from_ast_call(call, engine_state, stack, eval_expression_fn) + } + CallImpl::IrRef(call) => Self::try_from_ir_call(call, stack), + CallImpl::IrBox(call) => Self::try_from_ir_call(call, stack), + } + } + + fn try_from_ast_call( + call: &ast::Call, + engine_state: &EngineState, + stack: &mut Stack, + eval_expression_fn: fn(&EngineState, &mut Stack, &Expression) -> Result, ) -> Result { let positional = call.rest_iter_flattened(0, |expr| eval_expression_fn(engine_state, stack, expr))?; @@ -54,6 +72,22 @@ impl EvaluatedCall { }) } + fn try_from_ir_call(call: &ir::Call, stack: &Stack) -> Result { + let positional = call.rest_iter_flattened(stack, 0)?; + + let mut named = Vec::with_capacity(call.named_len(stack)); + named.extend( + call.named_iter(stack) + .map(|(name, value)| (name.map(|s| s.to_owned()), value.cloned())), + ); + + Ok(Self { + head: call.head, + positional, + named, + }) + } + /// Check if a flag (named parameter that does not take a value) is set /// Returns Ok(true) if flag is set or passed true value /// Returns Ok(false) if flag is not set or passed false value diff --git a/crates/nu-protocol/Cargo.toml b/crates/nu-protocol/Cargo.toml index ee0f5a8221..eaa861a073 100644 --- a/crates/nu-protocol/Cargo.toml +++ b/crates/nu-protocol/Cargo.toml @@ -33,6 +33,7 @@ serde = { workspace = true, default-features = false } thiserror = "1.0" typetag = "0.2" os_pipe = { workspace = true, features = ["io_safety"] } +log = { workspace = true } [target.'cfg(unix)'.dependencies] nix = { workspace = true, default-features = false, features = ["signal"] } @@ -54,4 +55,4 @@ tempfile = { workspace = true } os_pipe = { workspace = true } [package.metadata.docs.rs] -all-features = true \ No newline at end of file +all-features = true diff --git a/crates/nu-protocol/src/alias.rs b/crates/nu-protocol/src/alias.rs index 24448225d4..8f5ea43934 100644 --- a/crates/nu-protocol/src/alias.rs +++ b/crates/nu-protocol/src/alias.rs @@ -1,6 +1,6 @@ use crate::{ - ast::{Call, Expression}, - engine::{Command, CommandType, EngineState, Stack}, + ast::Expression, + engine::{Call, Command, CommandType, EngineState, Stack}, PipelineData, ShellError, Signature, }; diff --git a/crates/nu-protocol/src/ast/block.rs b/crates/nu-protocol/src/ast/block.rs index 6e3449af26..8f62ff99ba 100644 --- a/crates/nu-protocol/src/ast/block.rs +++ b/crates/nu-protocol/src/ast/block.rs @@ -1,5 +1,5 @@ use super::Pipeline; -use crate::{engine::EngineState, OutDest, Signature, Span, Type, VarId}; +use crate::{engine::StateWorkingSet, ir::IrBlock, OutDest, Signature, Span, Type, VarId}; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize)] @@ -8,6 +8,8 @@ pub struct Block { pub pipelines: Vec, pub captures: Vec, pub redirect_env: bool, + /// The block compiled to IR instructions. Not available for subexpressions. + pub ir_block: Option, pub span: Option, // None option encodes no span to avoid using test_span() } @@ -22,10 +24,10 @@ impl Block { pub fn pipe_redirection( &self, - engine_state: &EngineState, + working_set: &StateWorkingSet, ) -> (Option, Option) { if let Some(first) = self.pipelines.first() { - first.pipe_redirection(engine_state) + first.pipe_redirection(working_set) } else { (None, None) } @@ -45,6 +47,7 @@ impl Block { pipelines: vec![], captures: vec![], redirect_env: false, + ir_block: None, span: None, } } @@ -55,6 +58,7 @@ impl Block { pipelines: Vec::with_capacity(capacity), captures: vec![], redirect_env: false, + ir_block: None, span: None, } } @@ -86,6 +90,7 @@ where pipelines: pipelines.collect(), captures: vec![], redirect_env: false, + ir_block: None, span: None, } } diff --git a/crates/nu-protocol/src/ast/expr.rs b/crates/nu-protocol/src/ast/expr.rs index 0e561e5c8f..43548d39e4 100644 --- a/crates/nu-protocol/src/ast/expr.rs +++ b/crates/nu-protocol/src/ast/expr.rs @@ -5,7 +5,9 @@ use super::{ Call, CellPath, Expression, ExternalArgument, FullCellPath, Keyword, MatchPattern, Operator, Range, Table, ValueWithUnit, }; -use crate::{ast::ImportPattern, engine::EngineState, BlockId, OutDest, Signature, Span, VarId}; +use crate::{ + ast::ImportPattern, engine::StateWorkingSet, BlockId, OutDest, Signature, Span, VarId, +}; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum Expr { @@ -60,17 +62,17 @@ const _: () = assert!(std::mem::size_of::() <= 40); impl Expr { pub fn pipe_redirection( &self, - engine_state: &EngineState, + working_set: &StateWorkingSet, ) -> (Option, Option) { // Usages of `$in` will be wrapped by a `collect` call by the parser, // so we do not have to worry about that when considering // which of the expressions below may consume pipeline output. match self { - Expr::Call(call) => engine_state.get_decl(call.decl_id).pipe_redirection(), - Expr::Subexpression(block_id) | Expr::Block(block_id) => engine_state + Expr::Call(call) => working_set.get_decl(call.decl_id).pipe_redirection(), + Expr::Subexpression(block_id) | Expr::Block(block_id) => working_set .get_block(*block_id) - .pipe_redirection(engine_state), - Expr::FullCellPath(cell_path) => cell_path.head.expr.pipe_redirection(engine_state), + .pipe_redirection(working_set), + Expr::FullCellPath(cell_path) => cell_path.head.expr.pipe_redirection(working_set), Expr::Bool(_) | Expr::Int(_) | Expr::Float(_) diff --git a/crates/nu-protocol/src/ast/match_pattern.rs b/crates/nu-protocol/src/ast/match_pattern.rs index b8f87c3f63..1aafe84701 100644 --- a/crates/nu-protocol/src/ast/match_pattern.rs +++ b/crates/nu-protocol/src/ast/match_pattern.rs @@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct MatchPattern { pub pattern: Pattern, - pub guard: Option, + pub guard: Option>, pub span: Span, } @@ -19,7 +19,9 @@ impl MatchPattern { pub enum Pattern { Record(Vec<(String, MatchPattern)>), List(Vec), - Value(Expression), + // TODO: it would be nice if this didn't depend on AST + // maybe const evaluation can get us to a Value instead? + Value(Box), Variable(VarId), Or(Vec), Rest(VarId), // the ..$foo pattern diff --git a/crates/nu-protocol/src/ast/pipeline.rs b/crates/nu-protocol/src/ast/pipeline.rs index f03c016daf..3f2a216485 100644 --- a/crates/nu-protocol/src/ast/pipeline.rs +++ b/crates/nu-protocol/src/ast/pipeline.rs @@ -1,8 +1,4 @@ -use crate::{ - ast::Expression, - engine::{EngineState, StateWorkingSet}, - OutDest, Span, -}; +use crate::{ast::Expression, engine::StateWorkingSet, OutDest, Span}; use serde::{Deserialize, Serialize}; use std::fmt::Display; @@ -120,9 +116,9 @@ impl PipelineElement { pub fn pipe_redirection( &self, - engine_state: &EngineState, + working_set: &StateWorkingSet, ) -> (Option, Option) { - self.expr.expr.pipe_redirection(engine_state) + self.expr.expr.pipe_redirection(working_set) } } @@ -166,10 +162,10 @@ impl Pipeline { pub fn pipe_redirection( &self, - engine_state: &EngineState, + working_set: &StateWorkingSet, ) -> (Option, Option) { if let Some(first) = self.elements.first() { - first.pipe_redirection(engine_state) + first.pipe_redirection(working_set) } else { (None, None) } diff --git a/crates/nu-protocol/src/engine/argument.rs b/crates/nu-protocol/src/engine/argument.rs new file mode 100644 index 0000000000..043654b761 --- /dev/null +++ b/crates/nu-protocol/src/engine/argument.rs @@ -0,0 +1,124 @@ +use std::sync::Arc; + +use crate::{ast::Expression, ir::DataSlice, Span, Value}; + +/// Represents a fully evaluated argument to a call. +#[derive(Debug, Clone)] +pub enum Argument { + /// A positional argument + Positional { + span: Span, + val: Value, + ast: Option>, + }, + /// A spread argument, e.g. `...$args` + Spread { + span: Span, + vals: Value, + ast: Option>, + }, + /// A named argument with no value, e.g. `--flag` + Flag { + data: Arc<[u8]>, + name: DataSlice, + short: DataSlice, + span: Span, + }, + /// A named argument with a value, e.g. `--flag value` or `--flag=` + Named { + data: Arc<[u8]>, + name: DataSlice, + short: DataSlice, + span: Span, + val: Value, + ast: Option>, + }, + /// Information generated by the parser for use by certain keyword commands + ParserInfo { + data: Arc<[u8]>, + name: DataSlice, + // TODO: rather than `Expression`, this would probably be best served by a specific enum + // type for this purpose. + info: Box, + }, +} + +impl Argument { + /// The span encompassing the argument's usage within the call, distinct from the span of the + /// actual value of the argument. + pub fn span(&self) -> Option { + match self { + Argument::Positional { span, .. } => Some(*span), + Argument::Spread { span, .. } => Some(*span), + Argument::Flag { span, .. } => Some(*span), + Argument::Named { span, .. } => Some(*span), + // Because `ParserInfo` is generated, its span shouldn't be used + Argument::ParserInfo { .. } => None, + } + } + + /// The original AST [`Expression`] for the argument's value. This is not usually available; + /// declarations have to opt-in if they require this. + pub fn ast_expression(&self) -> Option<&Arc> { + match self { + Argument::Positional { ast, .. } => ast.as_ref(), + Argument::Spread { ast, .. } => ast.as_ref(), + Argument::Flag { .. } => None, + Argument::Named { ast, .. } => ast.as_ref(), + Argument::ParserInfo { .. } => None, + } + } +} + +/// Stores the argument context for calls in IR evaluation. +#[derive(Debug, Clone)] +pub struct ArgumentStack { + arguments: Vec, +} + +impl ArgumentStack { + /// Create a new, empty argument stack. + pub const fn new() -> Self { + ArgumentStack { arguments: vec![] } + } + + /// Returns the index of the end of the argument stack. Call and save this before adding + /// arguments. + pub fn get_base(&self) -> usize { + self.arguments.len() + } + + /// Calculates the number of arguments past the given [previously retrieved](.get_base) base + /// pointer. + pub fn get_len(&self, base: usize) -> usize { + self.arguments.len().checked_sub(base).unwrap_or_else(|| { + panic!( + "base ({}) is beyond the end of the arguments stack ({})", + base, + self.arguments.len() + ); + }) + } + + /// Push an argument onto the end of the argument stack. + pub fn push(&mut self, argument: Argument) { + self.arguments.push(argument); + } + + /// Clear all of the arguments after the given base index, to prepare for the next frame. + pub fn leave_frame(&mut self, base: usize) { + self.arguments.truncate(base); + } + + /// Get arguments for the frame based on the given [`base`](`.get_base()`) and + /// [`len`](`.get_len()`) parameters. + pub fn get_args(&self, base: usize, len: usize) -> &[Argument] { + &self.arguments[base..(base + len)] + } + + /// Move arguments for the frame based on the given [`base`](`.get_base()`) and + /// [`len`](`.get_len()`) parameters. + pub fn drain_args(&mut self, base: usize, len: usize) -> impl Iterator + '_ { + self.arguments.drain(base..(base + len)) + } +} diff --git a/crates/nu-protocol/src/engine/call.rs b/crates/nu-protocol/src/engine/call.rs new file mode 100644 index 0000000000..741e2bd87a --- /dev/null +++ b/crates/nu-protocol/src/engine/call.rs @@ -0,0 +1,223 @@ +use crate::{ + ast::{self, Expression}, + ir, DeclId, FromValue, ShellError, Span, Value, +}; + +use super::{EngineState, Stack, StateWorkingSet}; + +/// This is a HACK to help [`Command`](super::Command) support both the old AST evaluator and the +/// new IR evaluator at the same time. It should be removed once we are satisfied with the new +/// evaluator. +#[derive(Debug, Clone)] +pub struct Call<'a> { + pub head: Span, + pub decl_id: DeclId, + pub inner: CallImpl<'a>, +} + +#[derive(Debug, Clone)] +pub enum CallImpl<'a> { + AstRef(&'a ast::Call), + AstBox(Box), + IrRef(&'a ir::Call), + IrBox(Box), +} + +impl Call<'_> { + /// Returns a new AST call with the given span. This is often used by commands that need an + /// empty call to pass to a command. It's not easily possible to add anything to this. + pub fn new(span: Span) -> Self { + // this is using the boxed variant, which isn't so efficient... but this is only temporary + // anyway. + Call { + head: span, + decl_id: 0, + inner: CallImpl::AstBox(Box::new(ast::Call::new(span))), + } + } + + /// Convert the `Call` from any lifetime into `'static`, by cloning the data within onto the + /// heap. + pub fn to_owned(&self) -> Call<'static> { + Call { + head: self.head, + decl_id: self.decl_id, + inner: self.inner.to_owned(), + } + } + + /// Assert that the call is `ast::Call`, and fail with an error if it isn't. + /// + /// Provided as a stop-gap for commands that can't work with `ir::Call`, or just haven't been + /// implemented yet. Eventually these issues should be resolved and then this can be removed. + pub fn assert_ast_call(&self) -> Result<&ast::Call, ShellError> { + match &self.inner { + CallImpl::AstRef(call) => Ok(call), + CallImpl::AstBox(call) => Ok(call), + _ => Err(ShellError::NushellFailedSpanned { + msg: "Can't be used in IR context".into(), + label: "this command is not yet supported by IR evaluation".into(), + span: self.head, + }), + } + } + + /// FIXME: implementation asserts `ast::Call` and proxies to that + pub fn has_flag_const( + &self, + working_set: &StateWorkingSet, + flag_name: &str, + ) -> Result { + self.assert_ast_call()? + .has_flag_const(working_set, flag_name) + } + + /// FIXME: implementation asserts `ast::Call` and proxies to that + pub fn get_flag_const( + &self, + working_set: &StateWorkingSet, + name: &str, + ) -> Result, ShellError> { + self.assert_ast_call()?.get_flag_const(working_set, name) + } + + /// FIXME: implementation asserts `ast::Call` and proxies to that + pub fn req_const( + &self, + working_set: &StateWorkingSet, + pos: usize, + ) -> Result { + self.assert_ast_call()?.req_const(working_set, pos) + } + + /// FIXME: implementation asserts `ast::Call` and proxies to that + pub fn rest_const( + &self, + working_set: &StateWorkingSet, + starting_pos: usize, + ) -> Result, ShellError> { + self.assert_ast_call()? + .rest_const(working_set, starting_pos) + } + + /// Returns a span covering the call's arguments. + pub fn arguments_span(&self) -> Span { + match &self.inner { + CallImpl::AstRef(call) => call.arguments_span(), + CallImpl::AstBox(call) => call.arguments_span(), + CallImpl::IrRef(call) => call.arguments_span(), + CallImpl::IrBox(call) => call.arguments_span(), + } + } + + /// Returns a span covering the whole call. + pub fn span(&self) -> Span { + match &self.inner { + CallImpl::AstRef(call) => call.span(), + CallImpl::AstBox(call) => call.span(), + CallImpl::IrRef(call) => call.span(), + CallImpl::IrBox(call) => call.span(), + } + } + + /// Get a parser info argument by name. + pub fn get_parser_info<'a>(&'a self, stack: &'a Stack, name: &str) -> Option<&'a Expression> { + match &self.inner { + CallImpl::AstRef(call) => call.get_parser_info(name), + CallImpl::AstBox(call) => call.get_parser_info(name), + CallImpl::IrRef(call) => call.get_parser_info(stack, name), + CallImpl::IrBox(call) => call.get_parser_info(stack, name), + } + } + + /// Evaluator-agnostic implementation of `rest_iter_flattened()`. Evaluates or gets all of the + /// positional and spread arguments, flattens spreads, and then returns one list of values. + pub fn rest_iter_flattened( + &self, + engine_state: &EngineState, + stack: &mut Stack, + eval_expression: fn( + &EngineState, + &mut Stack, + &ast::Expression, + ) -> Result, + starting_pos: usize, + ) -> Result, ShellError> { + fn by_ast( + call: &ast::Call, + engine_state: &EngineState, + stack: &mut Stack, + eval_expression: fn( + &EngineState, + &mut Stack, + &ast::Expression, + ) -> Result, + starting_pos: usize, + ) -> Result, ShellError> { + call.rest_iter_flattened(starting_pos, |expr| { + eval_expression(engine_state, stack, expr) + }) + } + + fn by_ir( + call: &ir::Call, + stack: &Stack, + starting_pos: usize, + ) -> Result, ShellError> { + call.rest_iter_flattened(stack, starting_pos) + } + + match &self.inner { + CallImpl::AstRef(call) => { + by_ast(call, engine_state, stack, eval_expression, starting_pos) + } + CallImpl::AstBox(call) => { + by_ast(call, engine_state, stack, eval_expression, starting_pos) + } + CallImpl::IrRef(call) => by_ir(call, stack, starting_pos), + CallImpl::IrBox(call) => by_ir(call, stack, starting_pos), + } + } + + /// Get the original AST expression for a positional argument. Does not usually work for IR + /// unless the decl specified `requires_ast_for_arguments()` + pub fn positional_nth<'a>(&'a self, stack: &'a Stack, index: usize) -> Option<&'a Expression> { + match &self.inner { + CallImpl::AstRef(call) => call.positional_nth(index), + CallImpl::AstBox(call) => call.positional_nth(index), + CallImpl::IrRef(call) => call.positional_ast(stack, index).map(|arc| arc.as_ref()), + CallImpl::IrBox(call) => call.positional_ast(stack, index).map(|arc| arc.as_ref()), + } + } +} + +impl CallImpl<'_> { + pub fn to_owned(&self) -> CallImpl<'static> { + match self { + CallImpl::AstRef(call) => CallImpl::AstBox(Box::new((*call).clone())), + CallImpl::AstBox(call) => CallImpl::AstBox(call.clone()), + CallImpl::IrRef(call) => CallImpl::IrBox(Box::new((*call).clone())), + CallImpl::IrBox(call) => CallImpl::IrBox(call.clone()), + } + } +} + +impl<'a> From<&'a ast::Call> for Call<'a> { + fn from(call: &'a ast::Call) -> Self { + Call { + head: call.head, + decl_id: call.decl_id, + inner: CallImpl::AstRef(call), + } + } +} + +impl<'a> From<&'a ir::Call> for Call<'a> { + fn from(call: &'a ir::Call) -> Self { + Call { + head: call.head, + decl_id: call.decl_id, + inner: CallImpl::IrRef(call), + } + } +} diff --git a/crates/nu-protocol/src/engine/command.rs b/crates/nu-protocol/src/engine/command.rs index 043d2a66c7..48cdc4440d 100644 --- a/crates/nu-protocol/src/engine/command.rs +++ b/crates/nu-protocol/src/engine/command.rs @@ -1,5 +1,5 @@ use super::{EngineState, Stack, StateWorkingSet}; -use crate::{ast::Call, Alias, BlockId, Example, OutDest, PipelineData, ShellError, Signature}; +use crate::{engine::Call, Alias, BlockId, Example, OutDest, PipelineData, ShellError, Signature}; use std::fmt::Display; #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -124,6 +124,12 @@ pub trait Command: Send + Sync + CommandClone { fn pipe_redirection(&self) -> (Option, Option) { (None, None) } + + /// Return true if the AST nodes for the arguments are required for IR evaluation. This is + /// currently inefficient so is not generally done. + fn requires_ast_for_arguments(&self) -> bool { + false + } } pub trait CommandClone { diff --git a/crates/nu-protocol/src/engine/error_handler.rs b/crates/nu-protocol/src/engine/error_handler.rs new file mode 100644 index 0000000000..076678be20 --- /dev/null +++ b/crates/nu-protocol/src/engine/error_handler.rs @@ -0,0 +1,55 @@ +use crate::RegId; + +/// Describes an error handler stored during IR evaluation. +#[derive(Debug, Clone, Copy)] +pub struct ErrorHandler { + /// Instruction index within the block that will handle the error + pub handler_index: usize, + /// Register to put the error information into, when an error occurs + pub error_register: Option, +} + +/// Keeps track of error handlers pushed during evaluation of an IR block. +#[derive(Debug, Clone)] +pub struct ErrorHandlerStack { + handlers: Vec, +} + +impl ErrorHandlerStack { + pub const fn new() -> ErrorHandlerStack { + ErrorHandlerStack { handlers: vec![] } + } + + /// Get the current base of the stack, which establishes a frame. + pub fn get_base(&self) -> usize { + self.handlers.len() + } + + /// Push a new error handler onto the stack. + pub fn push(&mut self, handler: ErrorHandler) { + self.handlers.push(handler); + } + + /// Try to pop an error handler from the stack. Won't go below `base`, to avoid retrieving a + /// handler belonging to a parent frame. + pub fn pop(&mut self, base: usize) -> Option { + if self.handlers.len() > base { + self.handlers.pop() + } else { + None + } + } + + /// Reset the stack to the state it was in at the beginning of the frame, in preparation to + /// return control to the parent frame. + pub fn leave_frame(&mut self, base: usize) { + if self.handlers.len() >= base { + self.handlers.truncate(base); + } else { + panic!( + "ErrorHandlerStack bug: tried to leave frame at {base}, but current base is {}", + self.get_base() + ) + } + } +} diff --git a/crates/nu-protocol/src/engine/mod.rs b/crates/nu-protocol/src/engine/mod.rs index c6e71afb37..1b1762fe3c 100644 --- a/crates/nu-protocol/src/engine/mod.rs +++ b/crates/nu-protocol/src/engine/mod.rs @@ -1,8 +1,11 @@ +mod argument; mod cached_file; +mod call; mod call_info; mod capture_block; mod command; mod engine_state; +mod error_handler; mod overlay; mod pattern_match; mod stack; @@ -14,10 +17,13 @@ mod variable; pub use cached_file::CachedFile; +pub use argument::*; +pub use call::*; pub use call_info::*; pub use capture_block::*; pub use command::*; pub use engine_state::*; +pub use error_handler::*; pub use overlay::*; pub use pattern_match::*; pub use stack::*; diff --git a/crates/nu-protocol/src/engine/stack.rs b/crates/nu-protocol/src/engine/stack.rs index 19726db9c0..b289c1ae8b 100644 --- a/crates/nu-protocol/src/engine/stack.rs +++ b/crates/nu-protocol/src/engine/stack.rs @@ -1,7 +1,7 @@ use crate::{ engine::{ - EngineState, Redirection, StackCallArgGuard, StackCaptureGuard, StackIoGuard, StackOutDest, - DEFAULT_OVERLAY_NAME, + ArgumentStack, EngineState, ErrorHandlerStack, Redirection, StackCallArgGuard, + StackCaptureGuard, StackIoGuard, StackOutDest, DEFAULT_OVERLAY_NAME, }, OutDest, ShellError, Span, Value, VarId, ENV_VARIABLE_ID, NU_VARIABLE_ID, }; @@ -41,6 +41,12 @@ pub struct Stack { pub env_hidden: HashMap>, /// List of active overlays pub active_overlays: Vec, + /// Argument stack for IR evaluation + pub arguments: ArgumentStack, + /// Error handler stack for IR evaluation + pub error_handlers: ErrorHandlerStack, + /// Set true to always use IR mode + pub use_ir: bool, pub recursion_count: u64, pub parent_stack: Option>, /// Variables that have been deleted (this is used to hide values from parent stack lookups) @@ -68,6 +74,9 @@ impl Stack { env_vars: Vec::new(), env_hidden: HashMap::new(), active_overlays: vec![DEFAULT_OVERLAY_NAME.to_string()], + arguments: ArgumentStack::new(), + error_handlers: ErrorHandlerStack::new(), + use_ir: false, recursion_count: 0, parent_stack: None, parent_deletions: vec![], @@ -85,6 +94,9 @@ impl Stack { env_vars: parent.env_vars.clone(), env_hidden: parent.env_hidden.clone(), active_overlays: parent.active_overlays.clone(), + arguments: ArgumentStack::new(), + error_handlers: ErrorHandlerStack::new(), + use_ir: parent.use_ir, recursion_count: parent.recursion_count, vars: vec![], parent_deletions: vec![], @@ -254,6 +266,9 @@ impl Stack { env_vars, env_hidden: self.env_hidden.clone(), active_overlays: self.active_overlays.clone(), + arguments: ArgumentStack::new(), + error_handlers: ErrorHandlerStack::new(), + use_ir: self.use_ir, recursion_count: self.recursion_count, parent_stack: None, parent_deletions: vec![], @@ -284,6 +299,9 @@ impl Stack { env_vars, env_hidden: self.env_hidden.clone(), active_overlays: self.active_overlays.clone(), + arguments: ArgumentStack::new(), + error_handlers: ErrorHandlerStack::new(), + use_ir: self.use_ir, recursion_count: self.recursion_count, parent_stack: None, parent_deletions: vec![], diff --git a/crates/nu-protocol/src/engine/state_working_set.rs b/crates/nu-protocol/src/engine/state_working_set.rs index af950b8321..8c3968a824 100644 --- a/crates/nu-protocol/src/engine/state_working_set.rs +++ b/crates/nu-protocol/src/engine/state_working_set.rs @@ -4,8 +4,8 @@ use crate::{ usage::build_usage, CachedFile, Command, CommandType, EngineState, OverlayFrame, StateDelta, Variable, VirtualPath, Visibility, }, - BlockId, Category, Config, DeclId, FileId, GetSpan, Module, ModuleId, ParseError, ParseWarning, - Span, SpanId, Type, Value, VarId, VirtualPathId, + BlockId, Category, CompileError, Config, DeclId, FileId, GetSpan, Module, ModuleId, ParseError, + ParseWarning, Span, SpanId, Type, Value, VarId, VirtualPathId, }; use core::panic; use std::{ @@ -31,6 +31,7 @@ pub struct StateWorkingSet<'a> { pub search_predecls: bool, pub parse_errors: Vec, pub parse_warnings: Vec, + pub compile_errors: Vec, } impl<'a> StateWorkingSet<'a> { @@ -50,6 +51,7 @@ impl<'a> StateWorkingSet<'a> { search_predecls: true, parse_errors: vec![], parse_warnings: vec![], + compile_errors: vec![], } } @@ -260,6 +262,12 @@ impl<'a> StateWorkingSet<'a> { } pub fn add_block(&mut self, block: Arc) -> BlockId { + log::trace!( + "block id={} added, has IR = {:?}", + self.num_blocks(), + block.ir_block.is_some() + ); + self.delta.blocks.push(block); self.num_blocks() - 1 diff --git a/crates/nu-protocol/src/errors/cli_error.rs b/crates/nu-protocol/src/errors/cli_error.rs index 003564f933..181839b948 100644 --- a/crates/nu-protocol/src/errors/cli_error.rs +++ b/crates/nu-protocol/src/errors/cli_error.rs @@ -107,4 +107,8 @@ impl<'src> miette::Diagnostic for CliError<'src> { fn related<'a>(&'a self) -> Option + 'a>> { self.0.related() } + + fn diagnostic_source(&self) -> Option<&dyn miette::Diagnostic> { + self.0.diagnostic_source() + } } diff --git a/crates/nu-protocol/src/errors/compile_error.rs b/crates/nu-protocol/src/errors/compile_error.rs new file mode 100644 index 0000000000..cc805a73ed --- /dev/null +++ b/crates/nu-protocol/src/errors/compile_error.rs @@ -0,0 +1,238 @@ +use crate::{RegId, Span}; +use miette::Diagnostic; +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +/// An internal compiler error, generally means a Nushell bug rather than an issue with user error +/// since parsing and typechecking has already passed. +#[derive(Debug, Clone, Error, Diagnostic, PartialEq, Serialize, Deserialize)] +pub enum CompileError { + #[error("Register overflow.")] + #[diagnostic(code(nu::compile::register_overflow))] + RegisterOverflow { + #[label("the code being compiled is probably too large")] + block_span: Option, + }, + + #[error("Register {reg_id} was uninitialized when used, possibly reused.")] + #[diagnostic( + code(nu::compile::register_uninitialized), + help("this is a compiler bug. Please report it at https://github.com/nushell/nushell/issues/new\nfrom: {caller}"), + )] + RegisterUninitialized { reg_id: RegId, caller: String }, + + #[error("Register {reg_id} was uninitialized when used, possibly reused.")] + #[diagnostic( + code(nu::compile::register_uninitialized), + help("this is a compiler bug. Please report it at https://github.com/nushell/nushell/issues/new\nfrom: {caller}"), + )] + RegisterUninitializedWhilePushingInstruction { + reg_id: RegId, + caller: String, + instruction: String, + #[label("while adding this instruction: {instruction}")] + span: Span, + }, + + #[error("Block contains too much string data: maximum 4 GiB exceeded.")] + #[diagnostic( + code(nu::compile::data_overflow), + help("try loading the string data from a file instead") + )] + DataOverflow { + #[label("while compiling this block")] + block_span: Option, + }, + + #[error("Block contains too many files.")] + #[diagnostic( + code(nu::compile::register_overflow), + help("try using fewer file redirections") + )] + FileOverflow { + #[label("while compiling this block")] + block_span: Option, + }, + + #[error("Invalid redirect mode: File should not be specified by commands.")] + #[diagnostic( + code(nu::compile::invalid_redirect_mode), + help("this is a command bug. Please report it at https://github.com/nushell/nushell/issues/new") + )] + InvalidRedirectMode { + #[label("while compiling this expression")] + span: Span, + }, + + #[error("Encountered garbage, likely due to parse error.")] + #[diagnostic(code(nu::compile::garbage))] + Garbage { + #[label("garbage found here")] + span: Span, + }, + + #[error("Unsupported operator expression.")] + #[diagnostic(code(nu::compile::unsupported_operator_expression))] + UnsupportedOperatorExpression { + #[label("this expression is in operator position but is not an operator")] + span: Span, + }, + + #[error("Attempted access of $env by integer path.")] + #[diagnostic(code(nu::compile::access_env_by_int))] + AccessEnvByInt { + #[label("$env keys should be strings")] + span: Span, + }, + + #[error("Encountered invalid `{keyword}` keyword call.")] + #[diagnostic(code(nu::compile::invalid_keyword_call))] + InvalidKeywordCall { + keyword: String, + #[label("this call is not properly formed")] + span: Span, + }, + + #[error("Attempted to set branch target of non-branch instruction.")] + #[diagnostic( + code(nu::compile::set_branch_target_of_non_branch_instruction), + help("this is a compiler bug. Please report it at https://github.com/nushell/nushell/issues/new"), + )] + SetBranchTargetOfNonBranchInstruction { + instruction: String, + #[label("tried to modify: {instruction}")] + span: Span, + }, + + /// You're trying to run an unsupported external command. + /// + /// ## Resolution + /// + /// Make sure there's an appropriate `run-external` declaration for this external command. + #[error("External calls are not supported.")] + #[diagnostic( + code(nu::compile::run_external_not_found), + help("`run-external` was not found in scope") + )] + RunExternalNotFound { + #[label("can't be run in this context")] + span: Span, + }, + + /// Invalid assignment left-hand side + /// + /// ## Resolution + /// + /// Assignment requires that you assign to a variable or variable cell path. + #[error("Assignment operations require a variable.")] + #[diagnostic( + code(nu::compile::assignment_requires_variable), + help("try assigning to a variable or a cell path of a variable") + )] + AssignmentRequiresVar { + #[label("needs to be a variable")] + span: Span, + }, + + /// Invalid assignment left-hand side + /// + /// ## Resolution + /// + /// Assignment requires that you assign to a mutable variable or cell path. + #[error("Assignment to an immutable variable.")] + #[diagnostic( + code(nu::compile::assignment_requires_mutable_variable), + help("declare the variable with `mut`, or shadow it again with `let`") + )] + AssignmentRequiresMutableVar { + #[label("needs to be a mutable variable")] + span: Span, + }, + + /// This environment variable cannot be set manually. + /// + /// ## Resolution + /// + /// This environment variable is set automatically by Nushell and cannot not be set manually. + #[error("{envvar_name} cannot be set manually.")] + #[diagnostic( + code(nu::compile::automatic_env_var_set_manually), + help( + r#"The environment variable '{envvar_name}' is set automatically by Nushell and cannot be set manually."# + ) + )] + AutomaticEnvVarSetManually { + envvar_name: String, + #[label("cannot set '{envvar_name}' manually")] + span: Span, + }, + + /// It is not possible to replace the entire environment at once + /// + /// ## Resolution + /// + /// Setting the entire environment is not allowed. Change environment variables individually + /// instead. + #[error("Cannot replace environment.")] + #[diagnostic( + code(nu::compile::cannot_replace_env), + help("Assigning a value to '$env' is not allowed.") + )] + CannotReplaceEnv { + #[label("setting '$env' not allowed")] + span: Span, + }, + + #[error("Unexpected expression.")] + #[diagnostic(code(nu::compile::unexpected_expression))] + UnexpectedExpression { + expr_name: String, + #[label("{expr_name} is not allowed in this context")] + span: Span, + }, + + #[error("Missing required declaration: `{decl_name}`")] + #[diagnostic(code(nu::compile::missing_required_declaration))] + MissingRequiredDeclaration { + decl_name: String, + #[label("`{decl_name}` must be in scope to compile this expression")] + span: Span, + }, + + #[error("Invalid literal")] + #[diagnostic(code(nu::compile::invalid_literal))] + InvalidLiteral { + msg: String, + #[label("{msg}")] + span: Span, + }, + + #[error("{msg}")] + #[diagnostic(code(nu::compile::not_in_a_loop))] + NotInALoop { + msg: String, + #[label("can't be used outside of a loop")] + span: Option, + }, + + #[error("Incoherent loop state: the loop that ended was not the one we were expecting.")] + #[diagnostic( + code(nu::compile::incoherent_loop_state), + help("this is a compiler bug. Please report it at https://github.com/nushell/nushell/issues/new"), + )] + IncoherentLoopState { + #[label("while compiling this block")] + block_span: Option, + }, + + #[error("Undefined label `{label_id}`.")] + #[diagnostic( + code(nu::compile::undefined_label), + help("this is a compiler bug. Please report it at https://github.com/nushell/nushell/issues/new"), + )] + UndefinedLabel { + label_id: usize, + #[label("label was used while compiling this code")] + span: Option, + }, +} diff --git a/crates/nu-protocol/src/errors/mod.rs b/crates/nu-protocol/src/errors/mod.rs index 23006ab684..3f895cd65f 100644 --- a/crates/nu-protocol/src/errors/mod.rs +++ b/crates/nu-protocol/src/errors/mod.rs @@ -1,10 +1,12 @@ pub mod cli_error; +mod compile_error; mod labeled_error; mod parse_error; mod parse_warning; mod shell_error; pub use cli_error::{format_error, report_error, report_error_new}; +pub use compile_error::CompileError; pub use labeled_error::{ErrorLabel, LabeledError}; pub use parse_error::{DidYouMean, ParseError}; pub use parse_warning::ParseWarning; diff --git a/crates/nu-protocol/src/errors/shell_error.rs b/crates/nu-protocol/src/errors/shell_error.rs index be24fd093e..ab01ccfa54 100644 --- a/crates/nu-protocol/src/errors/shell_error.rs +++ b/crates/nu-protocol/src/errors/shell_error.rs @@ -1376,6 +1376,23 @@ On Windows, this would be %USERPROFILE%\AppData\Roaming"# help("Set XDG_CONFIG_HOME to an absolute path, or set it to an empty string to ignore it") )] InvalidXdgConfig { xdg: String, default: String }, + + /// An unexpected error occurred during IR evaluation. + /// + /// ## Resolution + /// + /// This is most likely a correctness issue with the IR compiler or evaluator. Please file a + /// bug with the minimum code needed to reproduce the issue, if possible. + #[error("IR evaluation error: {msg}")] + #[diagnostic( + code(nu::shell::ir_eval_error), + help("this is a bug, please report it at https://github.com/nushell/nushell/issues/new along with the code you were running if able") + )] + IrEvalError { + msg: String, + #[label = "while running this code"] + span: Option, + }, } // TODO: Implement as From trait diff --git a/crates/nu-protocol/src/eval_const.rs b/crates/nu-protocol/src/eval_const.rs index 87913e4ee3..5393e35e59 100644 --- a/crates/nu-protocol/src/eval_const.rs +++ b/crates/nu-protocol/src/eval_const.rs @@ -307,7 +307,7 @@ fn eval_const_call( return Err(ShellError::NotAConstHelp { span: call.head }); } - decl.run_const(working_set, call, input) + decl.run_const(working_set, &call.into(), input) } pub fn eval_const_subexpression( diff --git a/crates/nu-protocol/src/id.rs b/crates/nu-protocol/src/id.rs index 73c4f52e70..829ee8f36d 100644 --- a/crates/nu-protocol/src/id.rs +++ b/crates/nu-protocol/src/id.rs @@ -7,5 +7,19 @@ pub type ModuleId = usize; pub type OverlayId = usize; pub type FileId = usize; pub type VirtualPathId = usize; + #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] pub struct SpanId(pub usize); // more robust ID style used in the new parser + +/// An ID for an [IR](crate::ir) register. `%n` is a common shorthand for `RegId(n)`. +/// +/// Note: `%0` is allocated with the block input at the beginning of a compiled block. +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[repr(transparent)] +pub struct RegId(pub u32); + +impl std::fmt::Display for RegId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "%{}", self.0) + } +} diff --git a/crates/nu-protocol/src/ir/call.rs b/crates/nu-protocol/src/ir/call.rs new file mode 100644 index 0000000000..3d16f82eb6 --- /dev/null +++ b/crates/nu-protocol/src/ir/call.rs @@ -0,0 +1,351 @@ +use std::sync::Arc; + +use crate::{ + ast::Expression, + engine::{self, Argument, Stack}, + DeclId, ShellError, Span, Spanned, Value, +}; + +use super::DataSlice; + +/// Contains the information for a call being made to a declared command. +#[derive(Debug, Clone)] +pub struct Call { + /// The declaration ID of the command to be invoked. + pub decl_id: DeclId, + /// The span encompassing the command name, before the arguments. + pub head: Span, + /// The span encompassing the command name and all arguments. + pub span: Span, + /// The base index of the arguments for this call within the + /// [argument stack](crate::engine::ArgumentStack). + pub args_base: usize, + /// The number of [`Argument`]s for the call. Note that this just counts the number of + /// `Argument` entries on the stack, and has nothing to do with the actual number of positional + /// or spread arguments. + pub args_len: usize, +} + +impl Call { + /// Build a new call with arguments. + pub fn build(decl_id: DeclId, head: Span) -> CallBuilder { + CallBuilder { + inner: Call { + decl_id, + head, + span: head, + args_base: 0, + args_len: 0, + }, + } + } + + /// Get the arguments for this call from the arguments stack. + pub fn arguments<'a>(&self, stack: &'a Stack) -> &'a [Argument] { + stack.arguments.get_args(self.args_base, self.args_len) + } + + /// The span encompassing the arguments + /// + /// If there are no arguments the span covers where the first argument would exist + /// + /// If there are one or more arguments the span encompasses the start of the first argument to + /// end of the last argument + pub fn arguments_span(&self) -> Span { + let past = self.head.past(); + Span::new(past.start, self.span.end) + } + + /// The number of named arguments, with or without values. + pub fn named_len(&self, stack: &Stack) -> usize { + self.arguments(stack) + .iter() + .filter(|arg| matches!(arg, Argument::Named { .. } | Argument::Flag { .. })) + .count() + } + + /// Iterate through named arguments, with or without values. + pub fn named_iter<'a>( + &'a self, + stack: &'a Stack, + ) -> impl Iterator, Option<&'a Value>)> + 'a { + self.arguments(stack).iter().filter_map( + |arg: &Argument| -> Option<(Spanned<&str>, Option<&Value>)> { + match arg { + Argument::Flag { + data, name, span, .. + } => Some(( + Spanned { + item: std::str::from_utf8(&data[*name]).expect("invalid arg name"), + span: *span, + }, + None, + )), + Argument::Named { + data, + name, + span, + val, + .. + } => Some(( + Spanned { + item: std::str::from_utf8(&data[*name]).expect("invalid arg name"), + span: *span, + }, + Some(val), + )), + _ => None, + } + }, + ) + } + + /// Get a named argument's value by name. Returns [`None`] for named arguments with no value as + /// well. + pub fn get_named_arg<'a>(&self, stack: &'a Stack, flag_name: &str) -> Option<&'a Value> { + // Optimized to avoid str::from_utf8() + self.arguments(stack) + .iter() + .find_map(|arg: &Argument| -> Option> { + match arg { + Argument::Flag { data, name, .. } if &data[*name] == flag_name.as_bytes() => { + Some(None) + } + Argument::Named { + data, name, val, .. + } if &data[*name] == flag_name.as_bytes() => Some(Some(val)), + _ => None, + } + }) + .flatten() + } + + /// The number of positional arguments, excluding spread arguments. + pub fn positional_len(&self, stack: &Stack) -> usize { + self.arguments(stack) + .iter() + .filter(|arg| matches!(arg, Argument::Positional { .. })) + .count() + } + + /// Iterate through positional arguments. Does not include spread arguments. + pub fn positional_iter<'a>(&self, stack: &'a Stack) -> impl Iterator { + self.arguments(stack).iter().filter_map(|arg| match arg { + Argument::Positional { val, .. } => Some(val), + _ => None, + }) + } + + /// Get a positional argument by index. Does not include spread arguments. + pub fn positional_nth<'a>(&self, stack: &'a Stack, index: usize) -> Option<&'a Value> { + self.positional_iter(stack).nth(index) + } + + /// Get the AST node for a positional argument by index. Not usually available unless the decl + /// required it. + pub fn positional_ast<'a>( + &self, + stack: &'a Stack, + index: usize, + ) -> Option<&'a Arc> { + self.arguments(stack) + .iter() + .filter_map(|arg| match arg { + Argument::Positional { ast, .. } => Some(ast), + _ => None, + }) + .nth(index) + .and_then(|option| option.as_ref()) + } + + /// Returns every argument to the rest parameter, as well as whether each argument + /// is spread or a normal positional argument (true for spread, false for normal) + pub fn rest_iter<'a>( + &self, + stack: &'a Stack, + start: usize, + ) -> impl Iterator + 'a { + self.arguments(stack) + .iter() + .filter_map(|arg| match arg { + Argument::Positional { val, .. } => Some((val, false)), + Argument::Spread { vals, .. } => Some((vals, true)), + _ => None, + }) + .skip(start) + } + + /// Returns all of the positional arguments including and after `start`, with spread arguments + /// flattened into a single `Vec`. + pub fn rest_iter_flattened( + &self, + stack: &Stack, + start: usize, + ) -> Result, ShellError> { + let mut acc = vec![]; + for (rest_val, spread) in self.rest_iter(stack, start) { + if spread { + match rest_val { + Value::List { vals, .. } => acc.extend(vals.iter().cloned()), + Value::Error { error, .. } => return Err(ShellError::clone(error)), + _ => { + return Err(ShellError::CannotSpreadAsList { + span: rest_val.span(), + }) + } + } + } else { + acc.push(rest_val.clone()); + } + } + Ok(acc) + } + + /// Get a parser info argument by name. + pub fn get_parser_info<'a>(&self, stack: &'a Stack, name: &str) -> Option<&'a Expression> { + self.arguments(stack) + .iter() + .find_map(|argument| match argument { + Argument::ParserInfo { + data, + name: name_slice, + info: expr, + } if &data[*name_slice] == name.as_bytes() => Some(expr.as_ref()), + _ => None, + }) + } + + /// Returns a span encompassing the entire call. + pub fn span(&self) -> Span { + self.span + } + + /// Resets the [`Stack`] to its state before the call was made. + pub fn leave(&self, stack: &mut Stack) { + stack.arguments.leave_frame(self.args_base); + } +} + +/// Utility struct for building a [`Call`] with arguments on the [`Stack`]. +pub struct CallBuilder { + inner: Call, +} + +impl CallBuilder { + /// Add an argument to the [`Stack`] and reference it from the [`Call`]. + pub fn add_argument(&mut self, stack: &mut Stack, argument: Argument) -> &mut Self { + if self.inner.args_len == 0 { + self.inner.args_base = stack.arguments.get_base(); + } + self.inner.args_len += 1; + if let Some(span) = argument.span() { + self.inner.span = self.inner.span.append(span); + } + stack.arguments.push(argument); + self + } + + /// Add a positional argument to the [`Stack`] and reference it from the [`Call`]. + pub fn add_positional(&mut self, stack: &mut Stack, span: Span, val: Value) -> &mut Self { + self.add_argument( + stack, + Argument::Positional { + span, + val, + ast: None, + }, + ) + } + + /// Add a spread argument to the [`Stack`] and reference it from the [`Call`]. + pub fn add_spread(&mut self, stack: &mut Stack, span: Span, vals: Value) -> &mut Self { + self.add_argument( + stack, + Argument::Spread { + span, + vals, + ast: None, + }, + ) + } + + /// Add a flag (no-value named) argument to the [`Stack`] and reference it from the [`Call`]. + pub fn add_flag( + &mut self, + stack: &mut Stack, + name: impl AsRef, + short: impl AsRef, + span: Span, + ) -> &mut Self { + let (data, name, short) = data_from_name_and_short(name.as_ref(), short.as_ref()); + self.add_argument( + stack, + Argument::Flag { + data, + name, + short, + span, + }, + ) + } + + /// Add a named argument to the [`Stack`] and reference it from the [`Call`]. + pub fn add_named( + &mut self, + stack: &mut Stack, + name: impl AsRef, + short: impl AsRef, + span: Span, + val: Value, + ) -> &mut Self { + let (data, name, short) = data_from_name_and_short(name.as_ref(), short.as_ref()); + self.add_argument( + stack, + Argument::Named { + data, + name, + short, + span, + val, + ast: None, + }, + ) + } + + /// Produce the finished [`Call`] from the builder. + /// + /// The call should be entered / run before any other calls are constructed, because the + /// argument stack will be reset when they exit. + pub fn finish(&self) -> Call { + self.inner.clone() + } + + /// Run a closure with the [`Call`] as an [`engine::Call`] reference, and then clean up the + /// arguments that were added to the [`Stack`] after. + /// + /// For convenience. Calls [`Call::leave`] after the closure ends. + pub fn with( + self, + stack: &mut Stack, + f: impl FnOnce(&mut Stack, &engine::Call<'_>) -> T, + ) -> T { + let call = engine::Call::from(&self.inner); + let result = f(stack, &call); + self.inner.leave(stack); + result + } +} + +fn data_from_name_and_short(name: &str, short: &str) -> (Arc<[u8]>, DataSlice, DataSlice) { + let data: Vec = name.bytes().chain(short.bytes()).collect(); + let data: Arc<[u8]> = data.into(); + let name = DataSlice { + start: 0, + len: name.len().try_into().expect("flag name too big"), + }; + let short = DataSlice { + start: name.start.checked_add(name.len).expect("flag name too big"), + len: short.len().try_into().expect("flag short name too big"), + }; + (data, name, short) +} diff --git a/crates/nu-protocol/src/ir/display.rs b/crates/nu-protocol/src/ir/display.rs new file mode 100644 index 0000000000..c28323cca4 --- /dev/null +++ b/crates/nu-protocol/src/ir/display.rs @@ -0,0 +1,452 @@ +use std::fmt; + +use crate::{ast::Pattern, engine::EngineState, DeclId, VarId}; + +use super::{DataSlice, Instruction, IrBlock, Literal, RedirectMode}; + +pub struct FmtIrBlock<'a> { + pub(super) engine_state: &'a EngineState, + pub(super) ir_block: &'a IrBlock, +} + +impl<'a> fmt::Display for FmtIrBlock<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let plural = |count| if count == 1 { "" } else { "s" }; + writeln!( + f, + "# {} register{}, {} instruction{}, {} byte{} of data", + self.ir_block.register_count, + plural(self.ir_block.register_count as usize), + self.ir_block.instructions.len(), + plural(self.ir_block.instructions.len()), + self.ir_block.data.len(), + plural(self.ir_block.data.len()), + )?; + if self.ir_block.file_count > 0 { + writeln!( + f, + "# {} file{} used for redirection", + self.ir_block.file_count, + plural(self.ir_block.file_count as usize) + )?; + } + for (index, instruction) in self.ir_block.instructions.iter().enumerate() { + let formatted = format!( + "{:-4}: {}", + index, + FmtInstruction { + engine_state: self.engine_state, + instruction, + data: &self.ir_block.data, + } + ); + let comment = &self.ir_block.comments[index]; + if comment.is_empty() { + writeln!(f, "{formatted}")?; + } else { + writeln!(f, "{formatted:40} # {comment}")?; + } + } + Ok(()) + } +} + +pub struct FmtInstruction<'a> { + pub(super) engine_state: &'a EngineState, + pub(super) instruction: &'a Instruction, + pub(super) data: &'a [u8], +} + +impl<'a> fmt::Display for FmtInstruction<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + const WIDTH: usize = 22; + + match self.instruction { + Instruction::Unreachable => { + write!(f, "{:WIDTH$}", "unreachable") + } + Instruction::LoadLiteral { dst, lit } => { + let lit = FmtLiteral { + literal: lit, + data: self.data, + }; + write!(f, "{:WIDTH$} {dst}, {lit}", "load-literal") + } + Instruction::LoadValue { dst, val } => { + let val = val.to_debug_string(); + write!(f, "{:WIDTH$} {dst}, {val}", "load-value") + } + Instruction::Move { dst, src } => { + write!(f, "{:WIDTH$} {dst}, {src}", "move") + } + Instruction::Clone { dst, src } => { + write!(f, "{:WIDTH$} {dst}, {src}", "clone") + } + Instruction::Collect { src_dst } => { + write!(f, "{:WIDTH$} {src_dst}", "collect") + } + Instruction::Span { src_dst } => { + write!(f, "{:WIDTH$} {src_dst}", "span") + } + Instruction::Drop { src } => { + write!(f, "{:WIDTH$} {src}", "drop") + } + Instruction::Drain { src } => { + write!(f, "{:WIDTH$} {src}", "drain") + } + Instruction::LoadVariable { dst, var_id } => { + let var = FmtVar::new(self.engine_state, *var_id); + write!(f, "{:WIDTH$} {dst}, {var}", "load-variable") + } + Instruction::StoreVariable { var_id, src } => { + let var = FmtVar::new(self.engine_state, *var_id); + write!(f, "{:WIDTH$} {var}, {src}", "store-variable") + } + Instruction::LoadEnv { dst, key } => { + let key = FmtData(self.data, *key); + write!(f, "{:WIDTH$} {dst}, {key}", "load-env") + } + Instruction::LoadEnvOpt { dst, key } => { + let key = FmtData(self.data, *key); + write!(f, "{:WIDTH$} {dst}, {key}", "load-env-opt") + } + Instruction::StoreEnv { key, src } => { + let key = FmtData(self.data, *key); + write!(f, "{:WIDTH$} {key}, {src}", "store-env") + } + Instruction::PushPositional { src } => { + write!(f, "{:WIDTH$} {src}", "push-positional") + } + Instruction::AppendRest { src } => { + write!(f, "{:WIDTH$} {src}", "append-rest") + } + Instruction::PushFlag { name } => { + let name = FmtData(self.data, *name); + write!(f, "{:WIDTH$} {name}", "push-flag") + } + Instruction::PushShortFlag { short } => { + let short = FmtData(self.data, *short); + write!(f, "{:WIDTH$} {short}", "push-short-flag") + } + Instruction::PushNamed { name, src } => { + let name = FmtData(self.data, *name); + write!(f, "{:WIDTH$} {name}, {src}", "push-named") + } + Instruction::PushShortNamed { short, src } => { + let short = FmtData(self.data, *short); + write!(f, "{:WIDTH$} {short}, {src}", "push-short-named") + } + Instruction::PushParserInfo { name, info } => { + let name = FmtData(self.data, *name); + write!(f, "{:WIDTH$} {name}, {info:?}", "push-parser-info") + } + Instruction::RedirectOut { mode } => { + write!(f, "{:WIDTH$} {mode}", "redirect-out") + } + Instruction::RedirectErr { mode } => { + write!(f, "{:WIDTH$} {mode}", "redirect-err") + } + Instruction::CheckErrRedirected { src } => { + write!(f, "{:WIDTH$} {src}", "check-err-redirected") + } + Instruction::OpenFile { + file_num, + path, + append, + } => { + write!( + f, + "{:WIDTH$} file({file_num}), {path}, append = {append:?}", + "open-file" + ) + } + Instruction::WriteFile { file_num, src } => { + write!(f, "{:WIDTH$} file({file_num}), {src}", "write-file") + } + Instruction::CloseFile { file_num } => { + write!(f, "{:WIDTH$} file({file_num})", "close-file") + } + Instruction::Call { decl_id, src_dst } => { + let decl = FmtDecl::new(self.engine_state, *decl_id); + write!(f, "{:WIDTH$} {decl}, {src_dst}", "call") + } + Instruction::StringAppend { src_dst, val } => { + write!(f, "{:WIDTH$} {src_dst}, {val}", "string-append") + } + Instruction::GlobFrom { src_dst, no_expand } => { + let no_expand = if *no_expand { "no-expand" } else { "expand" }; + write!(f, "{:WIDTH$} {src_dst}, {no_expand}", "glob-from",) + } + Instruction::ListPush { src_dst, item } => { + write!(f, "{:WIDTH$} {src_dst}, {item}", "list-push") + } + Instruction::ListSpread { src_dst, items } => { + write!(f, "{:WIDTH$} {src_dst}, {items}", "list-spread") + } + Instruction::RecordInsert { src_dst, key, val } => { + write!(f, "{:WIDTH$} {src_dst}, {key}, {val}", "record-insert") + } + Instruction::RecordSpread { src_dst, items } => { + write!(f, "{:WIDTH$} {src_dst}, {items}", "record-spread") + } + Instruction::Not { src_dst } => { + write!(f, "{:WIDTH$} {src_dst}", "not") + } + Instruction::BinaryOp { lhs_dst, op, rhs } => { + write!(f, "{:WIDTH$} {lhs_dst}, {op:?}, {rhs}", "binary-op") + } + Instruction::FollowCellPath { src_dst, path } => { + write!(f, "{:WIDTH$} {src_dst}, {path}", "follow-cell-path") + } + Instruction::CloneCellPath { dst, src, path } => { + write!(f, "{:WIDTH$} {dst}, {src}, {path}", "clone-cell-path") + } + Instruction::UpsertCellPath { + src_dst, + path, + new_value, + } => { + write!( + f, + "{:WIDTH$} {src_dst}, {path}, {new_value}", + "upsert-cell-path" + ) + } + Instruction::Jump { index } => { + write!(f, "{:WIDTH$} {index}", "jump") + } + Instruction::BranchIf { cond, index } => { + write!(f, "{:WIDTH$} {cond}, {index}", "branch-if") + } + Instruction::BranchIfEmpty { src, index } => { + write!(f, "{:WIDTH$} {src}, {index}", "branch-if-empty") + } + Instruction::Match { + pattern, + src, + index, + } => { + let pattern = FmtPattern { + engine_state: self.engine_state, + pattern, + }; + write!(f, "{:WIDTH$} ({pattern}), {src}, {index}", "match") + } + Instruction::CheckMatchGuard { src } => { + write!(f, "{:WIDTH$} {src}", "check-match-guard") + } + Instruction::Iterate { + dst, + stream, + end_index, + } => { + write!(f, "{:WIDTH$} {dst}, {stream}, end {end_index}", "iterate") + } + Instruction::OnError { index } => { + write!(f, "{:WIDTH$} {index}", "on-error") + } + Instruction::OnErrorInto { index, dst } => { + write!(f, "{:WIDTH$} {index}, {dst}", "on-error-into") + } + Instruction::PopErrorHandler => { + write!(f, "{:WIDTH$}", "pop-error-handler") + } + Instruction::CheckExternalFailed { dst, src } => { + write!(f, "{:WIDTH$} {dst}, {src}", "check-external-failed") + } + Instruction::ReturnEarly { src } => { + write!(f, "{:WIDTH$} {src}", "return-early") + } + Instruction::Return { src } => { + write!(f, "{:WIDTH$} {src}", "return") + } + } + } +} + +struct FmtDecl<'a>(DeclId, &'a str); + +impl<'a> FmtDecl<'a> { + fn new(engine_state: &'a EngineState, decl_id: DeclId) -> Self { + FmtDecl(decl_id, engine_state.get_decl(decl_id).name()) + } +} + +impl fmt::Display for FmtDecl<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "decl {} {:?}", self.0, self.1) + } +} + +struct FmtVar<'a>(DeclId, Option<&'a str>); + +impl<'a> FmtVar<'a> { + fn new(engine_state: &'a EngineState, var_id: VarId) -> Self { + // Search for the name of the variable + let name: Option<&str> = engine_state + .active_overlays(&[]) + .flat_map(|overlay| overlay.vars.iter()) + .find(|(_, v)| **v == var_id) + .map(|(k, _)| std::str::from_utf8(k).unwrap_or("")); + FmtVar(var_id, name) + } +} + +impl fmt::Display for FmtVar<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(name) = self.1 { + write!(f, "var {} {:?}", self.0, name) + } else { + write!(f, "var {}", self.0) + } + } +} + +impl fmt::Display for RedirectMode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + RedirectMode::Pipe => write!(f, "pipe"), + RedirectMode::Capture => write!(f, "capture"), + RedirectMode::Null => write!(f, "null"), + RedirectMode::Inherit => write!(f, "inherit"), + RedirectMode::File { file_num } => write!(f, "file({file_num})"), + RedirectMode::Caller => write!(f, "caller"), + } + } +} + +struct FmtData<'a>(&'a [u8], DataSlice); + +impl<'a> fmt::Display for FmtData<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Ok(s) = std::str::from_utf8(&self.0[self.1]) { + // Write as string + write!(f, "{s:?}") + } else { + // Write as byte array + write!(f, "0x{:x?}", self.0) + } + } +} + +struct FmtLiteral<'a> { + literal: &'a Literal, + data: &'a [u8], +} + +impl<'a> fmt::Display for FmtLiteral<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.literal { + Literal::Bool(b) => write!(f, "bool({b:?})"), + Literal::Int(i) => write!(f, "int({i:?})"), + Literal::Float(fl) => write!(f, "float({fl:?})"), + Literal::Filesize(q) => write!(f, "filesize({q}b)"), + Literal::Duration(q) => write!(f, "duration({q}ns)"), + Literal::Binary(b) => write!(f, "binary({})", FmtData(self.data, *b)), + Literal::Block(id) => write!(f, "block({id})"), + Literal::Closure(id) => write!(f, "closure({id})"), + Literal::RowCondition(id) => write!(f, "row_condition({id})"), + Literal::Range { + start, + step, + end, + inclusion, + } => write!(f, "range({start}, {step}, {end}, {inclusion:?})"), + Literal::List { capacity } => write!(f, "list(capacity = {capacity})"), + Literal::Record { capacity } => write!(f, "record(capacity = {capacity})"), + Literal::Filepath { val, no_expand } => write!( + f, + "filepath({}, no_expand = {no_expand:?})", + FmtData(self.data, *val) + ), + Literal::Directory { val, no_expand } => write!( + f, + "directory({}, no_expand = {no_expand:?})", + FmtData(self.data, *val) + ), + Literal::GlobPattern { val, no_expand } => write!( + f, + "glob-pattern({}, no_expand = {no_expand:?})", + FmtData(self.data, *val) + ), + Literal::String(s) => write!(f, "string({})", FmtData(self.data, *s)), + Literal::RawString(rs) => write!(f, "raw-string({})", FmtData(self.data, *rs)), + Literal::CellPath(p) => write!(f, "cell-path({p})"), + Literal::Date(dt) => write!(f, "date({dt})"), + Literal::Nothing => write!(f, "nothing"), + } + } +} + +struct FmtPattern<'a> { + engine_state: &'a EngineState, + pattern: &'a Pattern, +} + +impl<'a> fmt::Display for FmtPattern<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.pattern { + Pattern::Record(bindings) => { + f.write_str("{")?; + for (name, pattern) in bindings { + write!( + f, + "{}: {}", + name, + FmtPattern { + engine_state: self.engine_state, + pattern: &pattern.pattern, + } + )?; + } + f.write_str("}") + } + Pattern::List(bindings) => { + f.write_str("[")?; + for pattern in bindings { + write!( + f, + "{}", + FmtPattern { + engine_state: self.engine_state, + pattern: &pattern.pattern + } + )?; + } + f.write_str("]") + } + Pattern::Value(expr) => { + let string = + String::from_utf8_lossy(self.engine_state.get_span_contents(expr.span)); + f.write_str(&string) + } + Pattern::Variable(var_id) => { + let variable = FmtVar::new(self.engine_state, *var_id); + write!(f, "{}", variable) + } + Pattern::Or(patterns) => { + for (index, pattern) in patterns.iter().enumerate() { + if index > 0 { + f.write_str(" | ")?; + } + write!( + f, + "{}", + FmtPattern { + engine_state: self.engine_state, + pattern: &pattern.pattern + } + )?; + } + Ok(()) + } + Pattern::Rest(var_id) => { + let variable = FmtVar::new(self.engine_state, *var_id); + write!(f, "..{}", variable) + } + Pattern::IgnoreRest => f.write_str(".."), + Pattern::IgnoreValue => f.write_str("_"), + Pattern::Garbage => f.write_str(""), + } + } +} diff --git a/crates/nu-protocol/src/ir/mod.rs b/crates/nu-protocol/src/ir/mod.rs new file mode 100644 index 0000000000..28677b743c --- /dev/null +++ b/crates/nu-protocol/src/ir/mod.rs @@ -0,0 +1,419 @@ +use std::{fmt, sync::Arc}; + +use crate::{ + ast::{CellPath, Expression, Operator, Pattern, RangeInclusion}, + engine::EngineState, + BlockId, DeclId, RegId, Span, Value, VarId, +}; + +use chrono::{DateTime, FixedOffset}; +use serde::{Deserialize, Serialize}; + +mod call; +mod display; + +pub use call::*; +pub use display::{FmtInstruction, FmtIrBlock}; + +#[derive(Clone, Serialize, Deserialize)] +pub struct IrBlock { + pub instructions: Vec, + pub spans: Vec, + #[serde(with = "serde_arc_u8_array")] + pub data: Arc<[u8]>, + pub ast: Vec>, + /// Additional information that can be added to help with debugging + pub comments: Vec>, + pub register_count: u32, + pub file_count: u32, +} + +impl fmt::Debug for IrBlock { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // the ast field is too verbose and doesn't add much + f.debug_struct("IrBlock") + .field("instructions", &self.instructions) + .field("spans", &self.spans) + .field("data", &self.data) + .field("comments", &self.comments) + .field("register_count", &self.register_count) + .field("file_count", &self.register_count) + .finish_non_exhaustive() + } +} + +impl IrBlock { + /// Returns a value that can be formatted with [`Display`](std::fmt::Display) to show a detailed + /// listing of the instructions contained within this [`IrBlock`]. + pub fn display<'a>(&'a self, engine_state: &'a EngineState) -> FmtIrBlock<'a> { + FmtIrBlock { + engine_state, + ir_block: self, + } + } +} + +/// A slice into the `data` array of a block. This is a compact and cache-friendly way to store +/// string data that a block uses. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +pub struct DataSlice { + pub start: u32, + pub len: u32, +} + +impl DataSlice { + /// A data slice that contains no data. This slice is always valid. + pub const fn empty() -> DataSlice { + DataSlice { start: 0, len: 0 } + } +} + +impl std::ops::Index for [u8] { + type Output = [u8]; + + fn index(&self, index: DataSlice) -> &Self::Output { + &self[index.start as usize..(index.start as usize + index.len as usize)] + } +} + +/// A possible reference into the abstract syntax tree for an instruction. This is not present for +/// most instructions and is just added when needed. +#[derive(Debug, Clone)] +pub struct IrAstRef(pub Arc); + +impl Serialize for IrAstRef { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.0.as_ref().serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for IrAstRef { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + Expression::deserialize(deserializer).map(|expr| IrAstRef(Arc::new(expr))) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum Instruction { + /// Unreachable code path (error) + Unreachable, + /// Load a literal value into the `dst` register + LoadLiteral { dst: RegId, lit: Literal }, + /// Load a clone of a boxed value into the `dst` register (e.g. from const evaluation) + LoadValue { dst: RegId, val: Box }, + /// Move a register. Value is taken from `src` (used by this instruction). + Move { dst: RegId, src: RegId }, + /// Copy a register (must be a collected value). Value is still in `src` after this instruction. + Clone { dst: RegId, src: RegId }, + /// Collect a stream in a register to a value + Collect { src_dst: RegId }, + /// Change the span of the contents of a register to the span of this instruction. + Span { src_dst: RegId }, + /// Drop the value/stream in a register, without draining + Drop { src: RegId }, + /// Drain the value/stream in a register and discard (e.g. semicolon). + /// + /// If passed a stream from an external command, sets $env.LAST_EXIT_CODE to the resulting exit + /// code, and invokes any available error handler with Empty, or if not available, returns an + /// exit-code-only stream, leaving the block. + Drain { src: RegId }, + /// Load the value of a variable into the `dst` register + LoadVariable { dst: RegId, var_id: VarId }, + /// Store the value of a variable from the `src` register + StoreVariable { var_id: VarId, src: RegId }, + /// Load the value of an environment variable into the `dst` register + LoadEnv { dst: RegId, key: DataSlice }, + /// Load the value of an environment variable into the `dst` register, or `Nothing` if it + /// doesn't exist + LoadEnvOpt { dst: RegId, key: DataSlice }, + /// Store the value of an environment variable from the `src` register + StoreEnv { key: DataSlice, src: RegId }, + /// Add a positional arg to the next (internal) call. + PushPositional { src: RegId }, + /// Add a list of args to the next (internal) call (spread/rest). + AppendRest { src: RegId }, + /// Add a named arg with no value to the next (internal) call. + PushFlag { name: DataSlice }, + /// Add a short named arg with no value to the next (internal) call. + PushShortFlag { short: DataSlice }, + /// Add a named arg with a value to the next (internal) call. + PushNamed { name: DataSlice, src: RegId }, + /// Add a short named arg with a value to the next (internal) call. + PushShortNamed { short: DataSlice, src: RegId }, + /// Add parser info to the next (internal) call. + PushParserInfo { + name: DataSlice, + info: Box, + }, + /// Set the redirection for stdout for the next call (only). + /// + /// The register for a file redirection is not consumed. + RedirectOut { mode: RedirectMode }, + /// Set the redirection for stderr for the next call (only). + /// + /// The register for a file redirection is not consumed. + RedirectErr { mode: RedirectMode }, + /// Throw an error if stderr wasn't redirected in the given stream. `src` is preserved. + CheckErrRedirected { src: RegId }, + /// Open a file for redirection, pushing it onto the file stack. + OpenFile { + file_num: u32, + path: RegId, + append: bool, + }, + /// Write data from the register to a file. This is done to finish a file redirection, in case + /// an internal command or expression was evaluated rather than an external one. + WriteFile { file_num: u32, src: RegId }, + /// Pop a file used for redirection from the file stack. + CloseFile { file_num: u32 }, + /// Make a call. The input is taken from `src_dst`, and the output is placed in `src_dst`, + /// overwriting it. The argument stack is used implicitly and cleared when the call ends. + Call { decl_id: DeclId, src_dst: RegId }, + /// Append a value onto the end of a string. Uses `to_expanded_string(", ", ...)` on the value. + /// Used for string interpolation literals. Not the same thing as the `++` operator. + StringAppend { src_dst: RegId, val: RegId }, + /// Convert a string into a glob. Used for glob interpolation and setting glob variables. If the + /// value is already a glob, it won't be modified (`no_expand` will have no effect). + GlobFrom { src_dst: RegId, no_expand: bool }, + /// Push a value onto the end of a list. Used to construct list literals. + ListPush { src_dst: RegId, item: RegId }, + /// Spread a value onto the end of a list. Used to construct list literals. + ListSpread { src_dst: RegId, items: RegId }, + /// Insert a key-value pair into a record. Used to construct record literals. Raises an error if + /// the key already existed in the record. + RecordInsert { + src_dst: RegId, + key: RegId, + val: RegId, + }, + /// Spread a record onto a record. Used to construct record literals. Any existing value for the + /// key is overwritten. + RecordSpread { src_dst: RegId, items: RegId }, + /// Negate a boolean. + Not { src_dst: RegId }, + /// Do a binary operation on `lhs_dst` (left) and `rhs` (right) and write the result to + /// `lhs_dst`. + BinaryOp { + lhs_dst: RegId, + op: Operator, + rhs: RegId, + }, + /// Follow a cell path on the value in `src_dst`, storing the result back to `src_dst` + FollowCellPath { src_dst: RegId, path: RegId }, + /// Clone the value at a cell path in `src`, storing the result to `dst`. The original value + /// remains in `src`. Must be a collected value. + CloneCellPath { dst: RegId, src: RegId, path: RegId }, + /// Update/insert a cell path to `new_value` on the value in `src_dst`, storing the modified + /// value back to `src_dst` + UpsertCellPath { + src_dst: RegId, + path: RegId, + new_value: RegId, + }, + /// Jump to an offset in this block + Jump { index: usize }, + /// Branch to an offset in this block if the value of the `cond` register is a true boolean, + /// otherwise continue execution + BranchIf { cond: RegId, index: usize }, + /// Branch to an offset in this block if the value of the `src` register is Empty or Nothing, + /// otherwise continue execution. The original value in `src` is preserved. + BranchIfEmpty { src: RegId, index: usize }, + /// Match a pattern on `src`. If the pattern matches, branch to `index` after having set any + /// variables captured by the pattern. If the pattern doesn't match, continue execution. The + /// original value is preserved in `src` through this instruction. + Match { + pattern: Box, + src: RegId, + index: usize, + }, + /// Check that a match guard is a boolean, throwing + /// [`MatchGuardNotBool`](crate::ShellError::MatchGuardNotBool) if it isn't. Preserves `src`. + CheckMatchGuard { src: RegId }, + /// Iterate on register `stream`, putting the next value in `dst` if present, or jumping to + /// `end_index` if the iterator is finished + Iterate { + dst: RegId, + stream: RegId, + end_index: usize, + }, + /// Push an error handler, without capturing the error value + OnError { index: usize }, + /// Push an error handler, capturing the error value into `dst`. If the error handler is not + /// called, the register should be freed manually. + OnErrorInto { index: usize, dst: RegId }, + /// Pop an error handler. This is not necessary when control flow is directed to the error + /// handler due to an error. + PopErrorHandler, + /// Check if an external command failed. Boolean value into `dst`. `src` is preserved, but it + /// does require waiting for the command to exit. + CheckExternalFailed { dst: RegId, src: RegId }, + /// Return early from the block, raising a `ShellError::Return` instead. + /// + /// Collecting the value is unavoidable. + ReturnEarly { src: RegId }, + /// Return from the block with the value in the register + Return { src: RegId }, +} + +impl Instruction { + /// Returns a value that can be formatted with [`Display`](std::fmt::Display) to show a detailed + /// listing of the instruction. + pub fn display<'a>( + &'a self, + engine_state: &'a EngineState, + data: &'a [u8], + ) -> FmtInstruction<'a> { + FmtInstruction { + engine_state, + instruction: self, + data, + } + } + + /// Returns the branch target index of the instruction if this is a branching instruction. + pub fn branch_target(&self) -> Option { + match self { + Instruction::Jump { index } => Some(*index), + Instruction::BranchIf { cond: _, index } => Some(*index), + Instruction::BranchIfEmpty { src: _, index } => Some(*index), + Instruction::Match { + pattern: _, + src: _, + index, + } => Some(*index), + + Instruction::Iterate { + dst: _, + stream: _, + end_index, + } => Some(*end_index), + Instruction::OnError { index } => Some(*index), + Instruction::OnErrorInto { index, dst: _ } => Some(*index), + _ => None, + } + } + + /// Sets the branch target of the instruction if this is a branching instruction. + /// + /// Returns `Err(target_index)` if it isn't a branching instruction. + pub fn set_branch_target(&mut self, target_index: usize) -> Result<(), usize> { + match self { + Instruction::Jump { index } => *index = target_index, + Instruction::BranchIf { cond: _, index } => *index = target_index, + Instruction::BranchIfEmpty { src: _, index } => *index = target_index, + Instruction::Match { + pattern: _, + src: _, + index, + } => *index = target_index, + + Instruction::Iterate { + dst: _, + stream: _, + end_index, + } => *end_index = target_index, + Instruction::OnError { index } => *index = target_index, + Instruction::OnErrorInto { index, dst: _ } => *index = target_index, + _ => return Err(target_index), + } + Ok(()) + } +} + +// This is to document/enforce the size of `Instruction` in bytes. +// We should try to avoid increasing the size of `Instruction`, +// and PRs that do so will have to change the number below so that it's noted in review. +const _: () = assert!(std::mem::size_of::() <= 24); + +/// A literal value that can be embedded in an instruction. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum Literal { + Bool(bool), + Int(i64), + Float(f64), + Filesize(i64), + Duration(i64), + Binary(DataSlice), + Block(BlockId), + Closure(BlockId), + RowCondition(BlockId), + Range { + start: RegId, + step: RegId, + end: RegId, + inclusion: RangeInclusion, + }, + List { + capacity: usize, + }, + Record { + capacity: usize, + }, + Filepath { + val: DataSlice, + no_expand: bool, + }, + Directory { + val: DataSlice, + no_expand: bool, + }, + GlobPattern { + val: DataSlice, + no_expand: bool, + }, + String(DataSlice), + RawString(DataSlice), + CellPath(Box), + Date(Box>), + Nothing, +} + +/// A redirection mode for the next call. See [`OutDest`](crate::OutDest). +/// +/// This is generated by: +/// +/// 1. Explicit redirection in a [`PipelineElement`](crate::ast::PipelineElement), or +/// 2. The [`pipe_redirection()`](crate::engine::Command::pipe_redirection) of the command being +/// piped into. +/// +/// Not setting it uses the default, determined by [`Stack`](crate::engine::Stack). +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub enum RedirectMode { + Pipe, + Capture, + Null, + Inherit, + /// Use the given numbered file. + File { + file_num: u32, + }, + /// Use the redirection mode requested by the caller, for a pre-return call. + Caller, +} + +/// Just a hack to allow `Arc<[u8]>` to be serialized and deserialized +mod serde_arc_u8_array { + use serde::{Deserialize, Serialize}; + use std::sync::Arc; + + pub fn serialize(data: &Arc<[u8]>, ser: S) -> Result + where + S: serde::Serializer, + { + data.as_ref().serialize(ser) + } + + pub fn deserialize<'de, D>(de: D) -> Result, D::Error> + where + D: serde::Deserializer<'de>, + { + let data: Vec = Deserialize::deserialize(de)?; + Ok(data.into()) + } +} diff --git a/crates/nu-protocol/src/lib.rs b/crates/nu-protocol/src/lib.rs index 9c176953d5..cae5d3fd0a 100644 --- a/crates/nu-protocol/src/lib.rs +++ b/crates/nu-protocol/src/lib.rs @@ -9,6 +9,7 @@ pub mod eval_base; pub mod eval_const; mod example; mod id; +pub mod ir; mod lev_distance; mod module; pub mod parser_path; diff --git a/crates/nu-protocol/src/pipeline/byte_stream.rs b/crates/nu-protocol/src/pipeline/byte_stream.rs index 6226f1d8db..cd62b70801 100644 --- a/crates/nu-protocol/src/pipeline/byte_stream.rs +++ b/crates/nu-protocol/src/pipeline/byte_stream.rs @@ -352,6 +352,12 @@ impl ByteStream { self.span } + /// Changes the [`Span`] associated with the [`ByteStream`]. + pub fn with_span(mut self, span: Span) -> Self { + self.span = span; + self + } + /// Returns the [`ByteStreamType`] associated with the [`ByteStream`]. pub fn type_(&self) -> ByteStreamType { self.type_ diff --git a/crates/nu-protocol/src/pipeline/list_stream.rs b/crates/nu-protocol/src/pipeline/list_stream.rs index 997cc3f77b..104bab6bcc 100644 --- a/crates/nu-protocol/src/pipeline/list_stream.rs +++ b/crates/nu-protocol/src/pipeline/list_stream.rs @@ -31,11 +31,22 @@ impl ListStream { self.span } + /// Changes the [`Span`] associated with this [`ListStream`]. + pub fn with_span(mut self, span: Span) -> Self { + self.span = span; + self + } + /// Convert a [`ListStream`] into its inner [`Value`] `Iterator`. pub fn into_inner(self) -> ValueIterator { self.stream } + /// Take a single value from the inner `Iterator`, modifying the stream. + pub fn next_value(&mut self) -> Option { + self.stream.next() + } + /// Converts each value in a [`ListStream`] into a string and then joins the strings together /// using the given separator. pub fn into_string(self, separator: &str, config: &Config) -> String { diff --git a/crates/nu-protocol/src/pipeline/pipeline_data.rs b/crates/nu-protocol/src/pipeline/pipeline_data.rs index a546e90191..a89337a6c2 100644 --- a/crates/nu-protocol/src/pipeline/pipeline_data.rs +++ b/crates/nu-protocol/src/pipeline/pipeline_data.rs @@ -96,6 +96,24 @@ impl PipelineData { } } + /// Change the span of the [`PipelineData`]. + /// + /// Returns `Value(Nothing)` with the given span if it was [`PipelineData::Empty`]. + pub fn with_span(self, span: Span) -> Self { + match self { + PipelineData::Empty => PipelineData::Value(Value::nothing(span), None), + PipelineData::Value(value, metadata) => { + PipelineData::Value(value.with_span(span), metadata) + } + PipelineData::ListStream(stream, metadata) => { + PipelineData::ListStream(stream.with_span(span), metadata) + } + PipelineData::ByteStream(stream, metadata) => { + PipelineData::ByteStream(stream.with_span(span), metadata) + } + } + } + /// Get a type that is representative of the `PipelineData`. /// /// The type returned here makes no effort to collect a stream, so it may be a different type @@ -129,7 +147,8 @@ impl PipelineData { /// without consuming input and without writing anything. /// /// For the other [`OutDest`]s, the given `PipelineData` will be completely consumed - /// and `PipelineData::Empty` will be returned. + /// and `PipelineData::Empty` will be returned, unless the data is from an external stream, + /// in which case an external stream containing only that exit code will be returned. pub fn write_to_out_dests( self, engine_state: &EngineState, @@ -137,7 +156,11 @@ impl PipelineData { ) -> Result { match (self, stack.stdout()) { (PipelineData::ByteStream(stream, ..), stdout) => { - stream.write_to_out_dests(stdout, stack.stderr())?; + if let Some(exit_status) = stream.write_to_out_dests(stdout, stack.stderr())? { + return Ok(PipelineData::new_external_stream_with_only_exit_code( + exit_status.code(), + )); + } } (data, OutDest::Pipe | OutDest::Capture) => return Ok(data), (PipelineData::Empty, ..) => {} @@ -570,7 +593,7 @@ impl PipelineData { self.write_all_and_flush(engine_state, no_newline, to_stderr) } else { let call = Call::new(Span::new(0, 0)); - let table = command.run(engine_state, stack, &call, self)?; + let table = command.run(engine_state, stack, &(&call).into(), self)?; table.write_all_and_flush(engine_state, no_newline, to_stderr) } } else { diff --git a/crates/nu-protocol/src/signature.rs b/crates/nu-protocol/src/signature.rs index 70e94b35f1..5928ce0c0c 100644 --- a/crates/nu-protocol/src/signature.rs +++ b/crates/nu-protocol/src/signature.rs @@ -1,6 +1,5 @@ use crate::{ - ast::Call, - engine::{Command, CommandType, EngineState, Stack}, + engine::{Call, Command, CommandType, EngineState, Stack}, BlockId, PipelineData, ShellError, SyntaxShape, Type, Value, VarId, }; use serde::{Deserialize, Serialize}; diff --git a/crates/nu-protocol/src/span.rs b/crates/nu-protocol/src/span.rs index 0d280eaa9d..f5bcebc543 100644 --- a/crates/nu-protocol/src/span.rs +++ b/crates/nu-protocol/src/span.rs @@ -53,6 +53,22 @@ impl Spanned { } } +impl Spanned> { + /// Move the `Result` to the outside, resulting in a spanned `Ok` or unspanned `Err`. + pub fn transpose(self) -> Result, E> { + match self { + Spanned { + item: Ok(item), + span, + } => Ok(Spanned { item, span }), + Spanned { + item: Err(err), + span: _, + } => Err(err), + } + } +} + /// Helper trait to create [`Spanned`] more ergonomically. pub trait IntoSpanned: Sized { /// Wrap items together with a span into [`Spanned`]. diff --git a/crates/nu-test-support/src/macros.rs b/crates/nu-test-support/src/macros.rs index e83b4354da..958a2453f5 100644 --- a/crates/nu-test-support/src/macros.rs +++ b/crates/nu-test-support/src/macros.rs @@ -247,6 +247,7 @@ pub struct NuOpts { pub locale: Option, pub envs: Option>, pub collapse_output: Option, + pub use_ir: Option, } pub fn nu_run_test(opts: NuOpts, commands: impl AsRef, with_std: bool) -> Outcome { @@ -296,6 +297,15 @@ pub fn nu_run_test(opts: NuOpts, commands: impl AsRef, with_std: bool) -> O .stdout(Stdio::piped()) .stderr(Stdio::piped()); + // Explicitly set NU_USE_IR + if let Some(use_ir) = opts.use_ir { + if use_ir { + command.env("NU_USE_IR", "1"); + } else { + command.env_remove("NU_USE_IR"); + } + } + // Uncomment to debug the command being run: // println!("=== command\n{command:?}\n"); @@ -373,6 +383,7 @@ where if !executable_path.exists() { executable_path = crate::fs::installed_nu_path(); } + let process = match setup_command(&executable_path, &target_cwd) .envs(envs) .arg("--commands") diff --git a/src/run.rs b/src/run.rs index 6bb02451b9..10a5043b25 100644 --- a/src/run.rs +++ b/src/run.rs @@ -26,6 +26,10 @@ pub(crate) fn run_commands( let mut stack = Stack::new(); let start_time = std::time::Instant::now(); + if stack.has_env_var(engine_state, "NU_USE_IR") { + stack.use_ir = true; + } + // if the --no-config-file(-n) option is NOT passed, load the plugin file, // load the default env file or custom (depending on parsed_nu_cli_args.env_file), // and maybe a custom config file (depending on parsed_nu_cli_args.config_file) @@ -109,6 +113,10 @@ pub(crate) fn run_file( trace!("run_file"); let mut stack = Stack::new(); + if stack.has_env_var(engine_state, "NU_USE_IR") { + stack.use_ir = true; + } + // if the --no-config-file(-n) option is NOT passed, load the plugin file, // load the default env file or custom (depending on parsed_nu_cli_args.env_file), // and maybe a custom config file (depending on parsed_nu_cli_args.config_file) @@ -184,6 +192,10 @@ pub(crate) fn run_repl( let mut stack = Stack::new(); let start_time = std::time::Instant::now(); + if stack.has_env_var(engine_state, "NU_USE_IR") { + stack.use_ir = true; + } + if parsed_nu_cli_args.no_config_file.is_none() { setup_config( engine_state, diff --git a/tests/eval/mod.rs b/tests/eval/mod.rs index f3af92376a..e9afd401e9 100644 --- a/tests/eval/mod.rs +++ b/tests/eval/mod.rs @@ -1,7 +1,8 @@ -use nu_test_support::nu; +use nu_test_support::{nu, playground::Playground}; +use regex::Regex; #[test] -fn source_file_relative_to_file() { +fn record_with_redefined_key() { let actual = nu!("{x: 1, x: 2}"); assert!(actual.err.contains("redefined")); @@ -16,3 +17,455 @@ fn run_file_parse_error() { assert!(actual.err.contains("unknown type")); } + +enum ExpectedOut<'a> { + /// Equals a string exactly + Eq(&'a str), + /// Matches a regex + Matches(&'a str), + /// Produces an error (match regex) + Error(&'a str), + /// Drops a file that contains these contents + FileEq(&'a str, &'a str), +} +use self::ExpectedOut::*; + +fn test_eval(source: &str, expected_out: ExpectedOut) { + Playground::setup("test_eval_ast", |ast_dirs, _playground| { + Playground::setup("test_eval_ir", |ir_dirs, _playground| { + let actual_ast = nu!( + cwd: ast_dirs.test(), + use_ir: false, + source, + ); + let actual_ir = nu!( + cwd: ir_dirs.test(), + use_ir: true, + source, + ); + + match expected_out { + Eq(eq) => { + assert_eq!(actual_ast.out, eq); + assert_eq!(actual_ir.out, eq); + assert!(actual_ast.status.success()); + assert!(actual_ir.status.success()); + } + Matches(regex) => { + let compiled_regex = Regex::new(regex).expect("regex failed to compile"); + assert!( + compiled_regex.is_match(&actual_ast.out), + "AST eval out does not match: {}\n{}", + regex, + actual_ast.out + ); + assert!( + compiled_regex.is_match(&actual_ir.out), + "IR eval out does not match: {}\n{}", + regex, + actual_ir.out, + ); + assert!(actual_ast.status.success()); + assert!(actual_ir.status.success()); + } + Error(regex) => { + let compiled_regex = Regex::new(regex).expect("regex failed to compile"); + assert!( + compiled_regex.is_match(&actual_ast.err), + "AST eval err does not match: {}", + regex + ); + assert!( + compiled_regex.is_match(&actual_ir.err), + "IR eval err does not match: {}", + regex + ); + assert!(!actual_ast.status.success()); + assert!(!actual_ir.status.success()); + } + FileEq(path, contents) => { + let ast_contents = std::fs::read_to_string(ast_dirs.test().join(path)) + .expect("failed to read AST file"); + let ir_contents = std::fs::read_to_string(ir_dirs.test().join(path)) + .expect("failed to read IR file"); + assert_eq!(ast_contents.trim(), contents); + assert_eq!(ir_contents.trim(), contents); + assert!(actual_ast.status.success()); + assert!(actual_ir.status.success()); + } + } + assert_eq!(actual_ast.out, actual_ir.out); + }) + }); +} + +#[test] +fn literal_bool() { + test_eval("true", Eq("true")) +} + +#[test] +fn literal_int() { + test_eval("1", Eq("1")) +} + +#[test] +fn literal_float() { + test_eval("1.5", Eq("1.5")) +} + +#[test] +fn literal_filesize() { + test_eval("30MiB", Eq("30.0 MiB")) +} + +#[test] +fn literal_duration() { + test_eval("30ms", Eq("30ms")) +} + +#[test] +fn literal_binary() { + test_eval("0x[1f 2f f0]", Matches("Length.*1f.*2f.*f0")) +} + +#[test] +fn literal_closure() { + test_eval("{||}", Matches(" hello.txt", + FileEq("hello.txt", "hello"), + ) +} + +#[test] +fn let_variable() { + test_eval("let foo = 'test'; print $foo", Eq("test")) +} + +#[test] +fn let_variable_mutate_error() { + test_eval( + "let foo = 'test'; $foo = 'bar'; print $foo", + Error("immutable"), + ) +} + +#[test] +fn constant() { + test_eval("const foo = 1 + 2; print $foo", Eq("3")) +} + +#[test] +fn constant_assign_error() { + test_eval( + "const foo = 1 + 2; $foo = 4; print $foo", + Error("immutable"), + ) +} + +#[test] +fn mut_variable() { + test_eval("mut foo = 'test'; $foo = 'bar'; print $foo", Eq("bar")) +} + +#[test] +fn mut_variable_append_assign() { + test_eval( + "mut foo = 'test'; $foo ++= 'bar'; print $foo", + Eq("testbar"), + ) +} + +#[test] +fn bind_in_variable_to_input() { + test_eval("3 | (4 + $in)", Eq("7")) +} + +#[test] +fn if_true() { + test_eval("if true { 'foo' }", Eq("foo")) +} + +#[test] +fn if_false() { + test_eval("if false { 'foo' } | describe", Eq("nothing")) +} + +#[test] +fn if_else_true() { + test_eval("if 5 > 3 { 'foo' } else { 'bar' }", Eq("foo")) +} + +#[test] +fn if_else_false() { + test_eval("if 5 < 3 { 'foo' } else { 'bar' }", Eq("bar")) +} + +#[test] +fn match_empty_fallthrough() { + test_eval("match 42 { }; 'pass'", Eq("pass")) +} + +#[test] +fn match_value() { + test_eval("match 1 { 1 => 'pass', 2 => 'fail' }", Eq("pass")) +} + +#[test] +fn match_value_default() { + test_eval( + "match 3 { 1 => 'fail1', 2 => 'fail2', _ => 'pass' }", + Eq("pass"), + ) +} + +#[test] +fn match_value_fallthrough() { + test_eval("match 3 { 1 => 'fail1', 2 => 'fail2' }", Eq("")) +} + +#[test] +fn match_variable() { + test_eval( + "match 'pass' { $s => { print $s }, _ => { print 'fail' } }", + Eq("pass"), + ) +} + +#[test] +fn match_variable_in_list() { + test_eval("match [fail pass] { [$f, $p] => { print $p } }", Eq("pass")) +} + +#[test] +fn match_passthrough_input() { + test_eval( + "'yes' | match [pass fail] { [$p, ..] => (collect { |y| $y ++ $p }) }", + Eq("yespass"), + ) +} + +#[test] +fn while_mutate_var() { + test_eval("mut x = 2; while $x > 0 { print $x; $x -= 1 }", Eq("21")) +} + +#[test] +fn for_list() { + test_eval("for v in [1 2 3] { print ($v * 2) }", Eq(r"246")) +} + +#[test] +fn for_seq() { + test_eval("for v in (seq 1 4) { print ($v * 2) }", Eq("2468")) +} + +#[test] +fn early_return() { + test_eval("do { return 'foo'; 'bar' }", Eq("foo")) +} + +#[test] +fn early_return_from_if() { + test_eval("do { if true { return 'pass' }; 'fail' }", Eq("pass")) +} + +#[test] +fn early_return_from_loop() { + test_eval("do { loop { return 'pass' } }", Eq("pass")) +} + +#[test] +fn early_return_from_while() { + test_eval( + "do { let x = true; while $x { return 'pass' } }", + Eq("pass"), + ) +} + +#[test] +fn early_return_from_for() { + test_eval("do { for x in [pass fail] { return $x } }", Eq("pass")) +} + +#[test] +fn try_no_catch() { + test_eval("try { error make { msg: foo } }; 'pass'", Eq("pass")) +} + +#[test] +fn try_catch_no_var() { + test_eval( + "try { error make { msg: foo } } catch { 'pass' }", + Eq("pass"), + ) +} + +#[test] +fn try_catch_var() { + test_eval( + "try { error make { msg: foo } } catch { |err| $err.msg }", + Eq("foo"), + ) +} + +#[test] +fn try_catch_with_non_literal_closure_no_var() { + test_eval( + r#" + let error_handler = { || "pass" } + try { error make { msg: foobar } } catch $error_handler + "#, + Eq("pass"), + ) +} + +#[test] +fn try_catch_with_non_literal_closure() { + test_eval( + r#" + let error_handler = { |err| $err.msg } + try { error make { msg: foobar } } catch $error_handler + "#, + Eq("foobar"), + ) +} + +#[test] +fn row_condition() { + test_eval( + "[[a b]; [1 2] [3 4]] | where a < 3 | to nuon", + Eq("[[a, b]; [1, 2]]"), + ) +} + +#[test] +fn custom_command() { + test_eval( + r#" + def cmd [a: int, b: string = 'fail', ...c: string, --x: int] { $"($a)($b)($c)($x)" } + cmd 42 pass foo --x 30 + "#, + Eq("42pass[foo]30"), + ) +} From 1a5bf2447a9a46f83b8cc86c5cac0ea1bc6f1122 Mon Sep 17 00:00:00 2001 From: Devyn Cairns Date: Wed, 10 Jul 2024 17:34:50 -0700 Subject: [PATCH 020/126] Use Arc for environment variables on the stack (#13333) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description This is another easy performance lift that just changes `env_vars` and `env_hidden` on `Stack` to use `Arc`. I noticed that these were being cloned on essentially every closure invocation during captures gathering, so we're paying the cost for all of that even when we don't change anything. On top of that, for `env_vars`, there's actually an entirely fresh `HashMap` created for each child scope, so it's highly unlikely that we'll modify the parent ones. Uses `Arc::make_mut` instead to take care of things when we need to mutate something, and most of the time nothing has to be cloned at all. # Benchmarks The benefits are greater the more calls there are to env-cloning functions like `captures_to_stack()`. Calling custom commands in a loop is basically best case for a performance improvement. Plain `each` with a literal block isn't so badly affected because the stack is set up once. ## random_bytes.nu ```nushell use std bench do { const SCRIPT = ../nu_scripts/benchmarks/random-bytes.nu let before_change = bench { nu $SCRIPT } let after_change = bench { target/release/nu $SCRIPT } { before: ($before_change | reject times), after: ($after_change | reject times) } } ``` ``` ╭────────┬──────────────────────────────╮ │ │ ╭──────┬───────────────────╮ │ │ before │ │ mean │ 603ms 759µs 727ns │ │ │ │ │ min │ 593ms 298µs 167ns │ │ │ │ │ max │ 648ms 612µs 291ns │ │ │ │ │ std │ 9ms 335µs 251ns │ │ │ │ ╰──────┴───────────────────╯ │ │ │ ╭──────┬───────────────────╮ │ │ after │ │ mean │ 518ms 400µs 557ns │ │ │ │ │ min │ 507ms 762µs 583ns │ │ │ │ │ max │ 566ms 695µs 166ns │ │ │ │ │ std │ 9ms 554µs 767ns │ │ │ │ ╰──────┴───────────────────╯ │ ╰────────┴──────────────────────────────╯ ``` ## gradient_benchmark_no_check.nu ```nushell use std bench do { const SCRIPT = ../nu_scripts/benchmarks/gradient_benchmark_no_check.nu let before_change = bench { nu $SCRIPT } let after_change = bench { target/release/nu $SCRIPT } { before: ($before_change | reject times), after: ($after_change | reject times) } } ``` ``` ╭────────┬──────────────────────────────╮ │ │ ╭──────┬───────────────────╮ │ │ before │ │ mean │ 146ms 543µs 380ns │ │ │ │ │ min │ 142ms 416µs 166ns │ │ │ │ │ max │ 189ms 595µs │ │ │ │ │ std │ 7ms 140µs 342ns │ │ │ │ ╰──────┴───────────────────╯ │ │ │ ╭──────┬───────────────────╮ │ │ after │ │ mean │ 134ms 211µs 678ns │ │ │ │ │ min │ 132ms 433µs 125ns │ │ │ │ │ max │ 135ms 722µs 583ns │ │ │ │ │ std │ 793µs 134ns │ │ │ │ ╰──────┴───────────────────╯ │ ╰────────┴──────────────────────────────╯ ``` # User-Facing Changes Better performance, particularly for custom commands, especially if there are a lot of environment variables. Nothing else. # Tests + Formatting All passing. --- crates/nu-engine/src/closure_eval.rs | 4 +-- crates/nu-protocol/src/engine/engine_state.rs | 2 +- crates/nu-protocol/src/engine/stack.rs | 30 ++++++++++--------- 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/crates/nu-engine/src/closure_eval.rs b/crates/nu-engine/src/closure_eval.rs index f4bc40658b..b271d90cbe 100644 --- a/crates/nu-engine/src/closure_eval.rs +++ b/crates/nu-engine/src/closure_eval.rs @@ -62,8 +62,8 @@ pub struct ClosureEval { stack: Stack, block: Arc, arg_index: usize, - env_vars: Vec, - env_hidden: HashMap>, + env_vars: Vec>, + env_hidden: Arc>>, eval: EvalBlockWithEarlyReturnFn, } diff --git a/crates/nu-protocol/src/engine/engine_state.rs b/crates/nu-protocol/src/engine/engine_state.rs index d45fbafb88..f48fde663b 100644 --- a/crates/nu-protocol/src/engine/engine_state.rs +++ b/crates/nu-protocol/src/engine/engine_state.rs @@ -304,7 +304,7 @@ impl EngineState { let mut config_updated = false; for mut scope in stack.env_vars.drain(..) { - for (overlay_name, mut env) in scope.drain() { + for (overlay_name, mut env) in Arc::make_mut(&mut scope).drain() { if let Some(env_vars) = Arc::make_mut(&mut self.env_vars).get_mut(&overlay_name) { // Updating existing overlay for (k, v) in env.drain() { diff --git a/crates/nu-protocol/src/engine/stack.rs b/crates/nu-protocol/src/engine/stack.rs index b289c1ae8b..9315744dfb 100644 --- a/crates/nu-protocol/src/engine/stack.rs +++ b/crates/nu-protocol/src/engine/stack.rs @@ -36,9 +36,9 @@ pub struct Stack { /// Variables pub vars: Vec<(VarId, Value)>, /// Environment variables arranged as a stack to be able to recover values from parent scopes - pub env_vars: Vec, + pub env_vars: Vec>, /// Tells which environment variables from engine state are hidden, per overlay. - pub env_hidden: HashMap>, + pub env_hidden: Arc>>, /// List of active overlays pub active_overlays: Vec, /// Argument stack for IR evaluation @@ -72,7 +72,7 @@ impl Stack { Self { vars: Vec::new(), env_vars: Vec::new(), - env_hidden: HashMap::new(), + env_hidden: Arc::new(HashMap::new()), active_overlays: vec![DEFAULT_OVERLAY_NAME.to_string()], arguments: ArgumentStack::new(), error_handlers: ErrorHandlerStack::new(), @@ -131,8 +131,8 @@ impl Stack { pub fn with_env( &mut self, - env_vars: &[EnvVars], - env_hidden: &HashMap>, + env_vars: &[Arc], + env_hidden: &Arc>>, ) { // Do not clone the environment if it hasn't changed if self.env_vars.iter().any(|scope| !scope.is_empty()) { @@ -219,23 +219,24 @@ impl Stack { pub fn add_env_var(&mut self, var: String, value: Value) { if let Some(last_overlay) = self.active_overlays.last() { - if let Some(env_hidden) = self.env_hidden.get_mut(last_overlay) { + if let Some(env_hidden) = Arc::make_mut(&mut self.env_hidden).get_mut(last_overlay) { // if the env var was hidden, let's activate it again env_hidden.remove(&var); } if let Some(scope) = self.env_vars.last_mut() { + let scope = Arc::make_mut(scope); if let Some(env_vars) = scope.get_mut(last_overlay) { env_vars.insert(var, value); } else { scope.insert(last_overlay.into(), [(var, value)].into_iter().collect()); } } else { - self.env_vars.push( + self.env_vars.push(Arc::new( [(last_overlay.into(), [(var, value)].into_iter().collect())] .into_iter() .collect(), - ); + )); } } else { // TODO: Remove panic @@ -257,9 +258,8 @@ impl Stack { } pub fn captures_to_stack_preserve_out_dest(&self, captures: Vec<(VarId, Value)>) -> Stack { - // FIXME: this is probably slow let mut env_vars = self.env_vars.clone(); - env_vars.push(HashMap::new()); + env_vars.push(Arc::new(HashMap::new())); Stack { vars: captures, @@ -292,7 +292,7 @@ impl Stack { } let mut env_vars = self.env_vars.clone(); - env_vars.push(HashMap::new()); + env_vars.push(Arc::new(HashMap::new())); Stack { vars, @@ -462,6 +462,7 @@ impl Stack { pub fn remove_env_var(&mut self, engine_state: &EngineState, name: &str) -> bool { for scope in self.env_vars.iter_mut().rev() { + let scope = Arc::make_mut(scope); for active_overlay in self.active_overlays.iter().rev() { if let Some(env_vars) = scope.get_mut(active_overlay) { if env_vars.remove(name).is_some() { @@ -474,10 +475,11 @@ impl Stack { for active_overlay in self.active_overlays.iter().rev() { if let Some(env_vars) = engine_state.env_vars.get(active_overlay) { if env_vars.get(name).is_some() { - if let Some(env_hidden) = self.env_hidden.get_mut(active_overlay) { - env_hidden.insert(name.into()); + let env_hidden = Arc::make_mut(&mut self.env_hidden); + if let Some(env_hidden_in_overlay) = env_hidden.get_mut(active_overlay) { + env_hidden_in_overlay.insert(name.into()); } else { - self.env_hidden + env_hidden .insert(active_overlay.into(), [name.into()].into_iter().collect()); } From ac561b1b0e65e9fd6ee6e682a08abb919983b0da Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Wed, 10 Jul 2024 20:19:06 -0500 Subject: [PATCH 021/126] quick fix up for ir pr as_refs (#13340) # Description Was having an issue compiling main after the IR pr. Talked to devyn and he led me to change a couple things real quick and we're compiling once again. --- crates/nu-engine/src/eval_ir.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/nu-engine/src/eval_ir.rs b/crates/nu-engine/src/eval_ir.rs index a505c9be34..e2ec7ccdac 100644 --- a/crates/nu-engine/src/eval_ir.rs +++ b/crates/nu-engine/src/eval_ir.rs @@ -1275,7 +1275,7 @@ fn get_env_var_case_insensitive<'a>(ctx: &'a mut EvalContext<'_>, key: &str) -> .env_vars .iter() .rev() - .chain(std::iter::once(ctx.engine_state.env_vars.as_ref())) + .chain(std::iter::once(&ctx.engine_state.env_vars)) .flat_map(|overlays| { // Read overlays in order ctx.stack @@ -1303,7 +1303,7 @@ fn get_env_var_name_case_insensitive<'a>(ctx: &mut EvalContext<'_>, key: &'a str .env_vars .iter() .rev() - .chain(std::iter::once(ctx.engine_state.env_vars.as_ref())) + .chain(std::iter::once(&ctx.engine_state.env_vars)) .flat_map(|overlays| { // Read overlays in order ctx.stack From f87cf895c2062cb5d71e97cd3221d101c825c8af Mon Sep 17 00:00:00 2001 From: Devyn Cairns Date: Wed, 10 Jul 2024 19:13:35 -0700 Subject: [PATCH 022/126] Set the capacity of the Vec used in `gather_captures()` to the number of captures expected (#13339) # Description Just more efficient allocation during `Stack::gather_captures()` so that we don't have to grow the `Vec` needlessly. # User-Facing Changes Slightly better performance. --- crates/nu-protocol/src/engine/stack.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/nu-protocol/src/engine/stack.rs b/crates/nu-protocol/src/engine/stack.rs index 9315744dfb..e963227ca8 100644 --- a/crates/nu-protocol/src/engine/stack.rs +++ b/crates/nu-protocol/src/engine/stack.rs @@ -277,7 +277,7 @@ impl Stack { } pub fn gather_captures(&self, engine_state: &EngineState, captures: &[VarId]) -> Stack { - let mut vars = vec![]; + let mut vars = Vec::with_capacity(captures.len()); let fake_span = Span::new(0, 0); From 801cfae279cb384ecef137caab0b77d899a26b44 Mon Sep 17 00:00:00 2001 From: Devyn Cairns Date: Wed, 10 Jul 2024 19:14:05 -0700 Subject: [PATCH 023/126] Avoid clone in `Signature::get_positional()` (#13338) # Description `Signature::get_positional()` was returning an owned `PositionalArg`, which contains a bunch of strings. `ClosureEval` uses this in `try_add_arg`, making all of that unnecessary cloning a little bit hot. # User-Facing Changes Slightly better performance --- crates/nu-protocol/src/signature.rs | 7 +++---- crates/nu-protocol/tests/test_signature.rs | 6 +++--- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/crates/nu-protocol/src/signature.rs b/crates/nu-protocol/src/signature.rs index 5928ce0c0c..3241b0df22 100644 --- a/crates/nu-protocol/src/signature.rs +++ b/crates/nu-protocol/src/signature.rs @@ -485,15 +485,14 @@ impl Signature { (name, s) } - pub fn get_positional(&self, position: usize) -> Option { + pub fn get_positional(&self, position: usize) -> Option<&PositionalArg> { if position < self.required_positional.len() { - self.required_positional.get(position).cloned() + self.required_positional.get(position) } else if position < (self.required_positional.len() + self.optional_positional.len()) { self.optional_positional .get(position - self.required_positional.len()) - .cloned() } else { - self.rest_positional.clone() + self.rest_positional.as_ref() } } diff --git a/crates/nu-protocol/tests/test_signature.rs b/crates/nu-protocol/tests/test_signature.rs index e22029bab9..8faf772c38 100644 --- a/crates/nu-protocol/tests/test_signature.rs +++ b/crates/nu-protocol/tests/test_signature.rs @@ -39,7 +39,7 @@ fn test_signature_chained() { assert_eq!( signature.get_positional(0), - Some(PositionalArg { + Some(&PositionalArg { name: "required".to_string(), desc: "required description".to_string(), shape: SyntaxShape::String, @@ -49,7 +49,7 @@ fn test_signature_chained() { ); assert_eq!( signature.get_positional(1), - Some(PositionalArg { + Some(&PositionalArg { name: "optional".to_string(), desc: "optional description".to_string(), shape: SyntaxShape::String, @@ -59,7 +59,7 @@ fn test_signature_chained() { ); assert_eq!( signature.get_positional(2), - Some(PositionalArg { + Some(&PositionalArg { name: "rest".to_string(), desc: "rest description".to_string(), shape: SyntaxShape::String, From d97512df8ed4277ade59dd49fc94ee32b8c5cf62 Mon Sep 17 00:00:00 2001 From: Devyn Cairns Date: Thu, 11 Jul 2024 04:00:59 -0700 Subject: [PATCH 024/126] Fix the signature of `view ir` (#13342) # Description Fix `view ir` to use `Signature::build()` rather than `new()`, which is required for `--help` to work. Also add `Category::Debug`, as that's most appropriate. --- crates/nu-command/src/debug/view_ir.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/nu-command/src/debug/view_ir.rs b/crates/nu-command/src/debug/view_ir.rs index df4f6cad6b..2bbe0db020 100644 --- a/crates/nu-command/src/debug/view_ir.rs +++ b/crates/nu-command/src/debug/view_ir.rs @@ -10,7 +10,7 @@ impl Command for ViewIr { } fn signature(&self) -> Signature { - Signature::new(self.name()) + Signature::build(self.name()) .required( "closure", SyntaxShape::Closure(None), @@ -22,6 +22,7 @@ impl Command for ViewIr { Some('j'), ) .input_output_type(Type::Nothing, Type::String) + .category(Category::Debug) } fn usage(&self) -> &str { From 9de7f931c006498817d8cb67cd21d5acfe7fabed Mon Sep 17 00:00:00 2001 From: Devyn Cairns Date: Thu, 11 Jul 2024 04:05:06 -0700 Subject: [PATCH 025/126] Add more argument types to `view ir` (#13343) # Description Add a few more options to `view ir` for finding blocks, which I found myself wanting while trying to trace through the generated code. If we end up adding support for plugins to call commands that are in scope by name, this will also make it possible for `nu_plugin_explore_ir` to just step through IR automatically (by passing the block/decl ids) without exposing too many internals. With that I could potentially add keys that allow you to step in to closures or decls with the press of a button, just by calling `view ir --json` appropriately. # User-Facing Changes - `view ir` can now take names of custom commands that are in scope. - integer arguments are treated as block IDs, which sometimes show up in IR (closure, block, row condition literals). - `--decl-id` provided to treat the argument as a decl ID instead, which is also sometimes necessary to access something that isn't in scope. --- crates/nu-command/src/debug/view_ir.rs | 101 +++++++++++++++++++++++-- 1 file changed, 94 insertions(+), 7 deletions(-) diff --git a/crates/nu-command/src/debug/view_ir.rs b/crates/nu-command/src/debug/view_ir.rs index 2bbe0db020..72136079fd 100644 --- a/crates/nu-command/src/debug/view_ir.rs +++ b/crates/nu-command/src/debug/view_ir.rs @@ -1,5 +1,4 @@ use nu_engine::command_prelude::*; -use nu_protocol::engine::Closure; #[derive(Clone)] pub struct ViewIr; @@ -12,15 +11,20 @@ impl Command for ViewIr { fn signature(&self) -> Signature { Signature::build(self.name()) .required( - "closure", - SyntaxShape::Closure(None), - "The closure to see compiled code for.", + "target", + SyntaxShape::Any, + "The name or block to view compiled code for.", ) .switch( "json", "Dump the raw block data as JSON (unstable).", Some('j'), ) + .switch( + "decl-id", + "Integer is a declaration ID rather than a block ID.", + Some('d'), + ) .input_output_type(Type::Nothing, Type::String) .category(Category::Debug) } @@ -29,6 +33,20 @@ impl Command for ViewIr { "View the compiled IR code for a block of code." } + fn extra_usage(&self) -> &str { + " +The target can be a closure, the name of a custom command, or an internal block +ID. Closure literals within IR dumps often reference the block by ID (e.g. +`closure(3231)`), so this provides an easy way to read the IR of any embedded +closures. + +The --decl-id option is provided to use a declaration ID instead, which can be +found on `call` instructions. This is sometimes better than using the name, as +the declaration may not be in scope. +" + .trim() + } + fn run( &self, engine_state: &EngineState, @@ -36,10 +54,79 @@ impl Command for ViewIr { call: &Call, _input: PipelineData, ) -> Result { - let closure: Closure = call.req(engine_state, stack, 0)?; + let target: Value = call.req(engine_state, stack, 0)?; let json = call.has_flag(engine_state, stack, "json")?; + let is_decl_id = call.has_flag(engine_state, stack, "decl-id")?; + + let block_id = match target { + Value::Closure { ref val, .. } => val.block_id, + // Decl by name + Value::String { ref val, .. } => { + if let Some(decl_id) = engine_state.find_decl(val.as_bytes(), &[]) { + let decl = engine_state.get_decl(decl_id); + decl.block_id().ok_or_else(|| ShellError::GenericError { + error: format!("Can't view IR for `{val}`"), + msg: "not a custom command".into(), + span: Some(target.span()), + help: Some("internal commands don't have Nushell source code".into()), + inner: vec![], + })? + } else { + return Err(ShellError::GenericError { + error: format!("Can't view IR for `{val}`"), + msg: "can't find a command with this name".into(), + span: Some(target.span()), + help: None, + inner: vec![], + }); + } + } + // Decl by ID - IR dump always shows name of decl, but sometimes it isn't in scope + Value::Int { val, .. } if is_decl_id => { + let decl_id = val + .try_into() + .ok() + .filter(|id| *id < engine_state.num_decls()) + .ok_or_else(|| ShellError::IncorrectValue { + msg: "not a valid decl id".into(), + val_span: target.span(), + call_span: call.head, + })?; + let decl = engine_state.get_decl(decl_id); + decl.block_id().ok_or_else(|| ShellError::GenericError { + error: format!("Can't view IR for `{}`", decl.name()), + msg: "not a custom command".into(), + span: Some(target.span()), + help: Some("internal commands don't have Nushell source code".into()), + inner: vec![], + })? + } + // Block by ID - often shows up in IR + Value::Int { val, .. } => val.try_into().map_err(|_| ShellError::IncorrectValue { + msg: "not a valid block id".into(), + val_span: target.span(), + call_span: call.head, + })?, + // Pass through errors + Value::Error { error, .. } => return Err(*error), + _ => { + return Err(ShellError::TypeMismatch { + err_message: "expected closure, string, or int".into(), + span: call.head, + }) + } + }; + + let Some(block) = engine_state.try_get_block(block_id) else { + return Err(ShellError::GenericError { + error: format!("Unknown block ID: {block_id}"), + msg: "ensure the block ID is correct and try again".into(), + span: Some(target.span()), + help: None, + inner: vec![], + }); + }; - let block = engine_state.get_block(closure.block_id); let ir_block = block .ir_block .as_ref() @@ -63,7 +150,7 @@ impl Command for ViewIr { .collect::>(); serde_json::to_string_pretty(&serde_json::json!({ - "block_id": closure.block_id, + "block_id": block_id, "span": block.span, "ir_block": ir_block, "formatted_instructions": formatted_instructions, From 076a29ae19b8b982061c27bdd93782b898b33898 Mon Sep 17 00:00:00 2001 From: Stefan Holderbach Date: Thu, 11 Jul 2024 13:30:12 +0200 Subject: [PATCH 026/126] Document public types in `nu-protocol` (#12906) - **Doc-comment public `nu-protocol` modules** - **Doccomment argument/signature/call stuff** - **Doccomment cell path types** - **Doccomment expression stuff** - **Doccomment import patterns** - **Doccomment pattern matching AST nodes** --- crates/nu-protocol/src/alias.rs | 8 ++++- crates/nu-protocol/src/ast/call.rs | 34 +++++++++++++++++-- crates/nu-protocol/src/ast/cell_path.rs | 19 +++++++++++ crates/nu-protocol/src/ast/expr.rs | 3 ++ crates/nu-protocol/src/ast/expression.rs | 1 + crates/nu-protocol/src/ast/import_pattern.rs | 6 ++++ crates/nu-protocol/src/ast/match_pattern.rs | 17 ++++++++-- crates/nu-protocol/src/ast/mod.rs | 3 +- crates/nu-protocol/src/config/mod.rs | 1 + crates/nu-protocol/src/debugger/mod.rs | 1 + crates/nu-protocol/src/engine/mod.rs | 1 + crates/nu-protocol/src/errors/cli_error.rs | 3 ++ crates/nu-protocol/src/eval_base.rs | 1 + crates/nu-protocol/src/eval_const.rs | 4 +++ .../nu-protocol/src/pipeline/byte_stream.rs | 1 + .../nu-protocol/src/pipeline/list_stream.rs | 4 +++ crates/nu-protocol/src/process/mod.rs | 1 + crates/nu-protocol/src/signature.rs | 4 +++ crates/nu-protocol/src/span.rs | 1 + crates/nu-protocol/src/value/record.rs | 1 + 20 files changed, 107 insertions(+), 7 deletions(-) diff --git a/crates/nu-protocol/src/alias.rs b/crates/nu-protocol/src/alias.rs index 8f5ea43934..701543c57b 100644 --- a/crates/nu-protocol/src/alias.rs +++ b/crates/nu-protocol/src/alias.rs @@ -4,10 +4,16 @@ use crate::{ PipelineData, ShellError, Signature, }; +/// Command wrapper of an alias. +/// +/// Our current aliases are implemented as wrapping commands +/// This has some limitations compared to text-substitution macro aliases but can reliably use more +/// of our machinery #[derive(Clone)] pub struct Alias { pub name: String, - pub command: Option>, // None if external call + /// Wrapped inner [`Command`]. `None` if alias of external call + pub command: Option>, pub wrapped_call: Expression, pub usage: String, pub extra_usage: String, diff --git a/crates/nu-protocol/src/ast/call.rs b/crates/nu-protocol/src/ast/call.rs index 6e8cc64a05..d26d0af300 100644 --- a/crates/nu-protocol/src/ast/call.rs +++ b/crates/nu-protocol/src/ast/call.rs @@ -7,12 +7,30 @@ use crate::{ ShellError, Span, Spanned, Value, }; +/// Parsed command arguments +/// +/// Primarily for internal commands #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum Argument { + /// A positional argument (that is not [`Argument::Spread`]) + /// + /// ```nushell + /// my_cmd positional + /// ``` Positional(Expression), + /// A named/flag argument that can optionally receive a [`Value`] as an [`Expression`] + /// + /// The optional second `Spanned` refers to the short-flag version if used + /// ```nushell + /// my_cmd --flag + /// my_cmd -f + /// my_cmd --flag-with-value + /// ``` Named((Spanned, Option>, Option)), - Unknown(Expression), // unknown argument used in "fall-through" signatures - Spread(Expression), // a list spread to fill in rest arguments + /// unknown argument used in "fall-through" signatures + Unknown(Expression), + /// a list spread to fill in rest arguments + Spread(Expression), } impl Argument { @@ -47,12 +65,24 @@ impl Argument { } } +/// Argument passed to an external command +/// +/// Here the parsing rules slightly differ to directly pass strings to the external process #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum ExternalArgument { + /// Expression that needs to be evaluated to turn into an external process argument Regular(Expression), + /// Occurrence of a `...` spread operator that needs to be expanded Spread(Expression), } +/// Parsed call of a `Command` +/// +/// As we also implement some internal keywords in terms of the `Command` trait, this type stores the passed arguments as [`Expression`]. +/// Some of its methods lazily evaluate those to [`Value`] while others return the underlying +/// [`Expression`]. +/// +/// For further utilities check the `nu_engine::CallExt` trait that extends [`Call`] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Call { /// identifier of the declaration to call diff --git a/crates/nu-protocol/src/ast/cell_path.rs b/crates/nu-protocol/src/ast/cell_path.rs index 3de37f3992..0880537557 100644 --- a/crates/nu-protocol/src/ast/cell_path.rs +++ b/crates/nu-protocol/src/ast/cell_path.rs @@ -3,16 +3,23 @@ use crate::Span; use serde::{Deserialize, Serialize}; use std::{cmp::Ordering, fmt::Display}; +/// One level of access of a [`CellPath`] #[derive(Debug, Clone, Serialize, Deserialize)] pub enum PathMember { + /// Accessing a member by string (i.e. columns of a table or [`Record`](crate::Record)) String { val: String, span: Span, + /// If marked as optional don't throw an error if not found but perform default handling + /// (e.g. return `Value::Nothing`) optional: bool, }, + /// Accessing a member by index (i.e. row of a table or item in a list) Int { val: usize, span: Span, + /// If marked as optional don't throw an error if not found but perform default handling + /// (e.g. return `Value::Nothing`) optional: bool, }, } @@ -143,6 +150,18 @@ impl PartialOrd for PathMember { } } +/// Represents the potentially nested access to fields/cells of a container type +/// +/// In our current implementation for table access the order of row/column is commutative. +/// This limits the number of possible rows to select in one [`CellPath`] to 1 as it could +/// otherwise be ambiguous +/// +/// ```nushell +/// col1.0 +/// 0.col1 +/// col2 +/// 42 +/// ``` #[derive(Debug, Clone, PartialEq, PartialOrd, Serialize, Deserialize)] pub struct CellPath { pub members: Vec, diff --git a/crates/nu-protocol/src/ast/expr.rs b/crates/nu-protocol/src/ast/expr.rs index 43548d39e4..fadbffc51f 100644 --- a/crates/nu-protocol/src/ast/expr.rs +++ b/crates/nu-protocol/src/ast/expr.rs @@ -9,6 +9,7 @@ use crate::{ ast::ImportPattern, engine::StateWorkingSet, BlockId, OutDest, Signature, Span, VarId, }; +/// An [`Expression`] AST node #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum Expr { Bool(bool), @@ -127,6 +128,7 @@ impl Expr { } } +/// Expressions permitted inside a record expression/literal #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum RecordItem { /// A key: val mapping @@ -135,6 +137,7 @@ pub enum RecordItem { Spread(Span, Expression), } +/// Expressions permitted inside a list expression/literal #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum ListItem { /// A normal expression diff --git a/crates/nu-protocol/src/ast/expression.rs b/crates/nu-protocol/src/ast/expression.rs index 906fce3fe1..dfd4187a90 100644 --- a/crates/nu-protocol/src/ast/expression.rs +++ b/crates/nu-protocol/src/ast/expression.rs @@ -6,6 +6,7 @@ use crate::{ use serde::{Deserialize, Serialize}; use std::sync::Arc; +/// Wrapper around [`Expr`] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Expression { pub expr: Expr, diff --git a/crates/nu-protocol/src/ast/import_pattern.rs b/crates/nu-protocol/src/ast/import_pattern.rs index 893dd9897b..ad08bfb2d1 100644 --- a/crates/nu-protocol/src/ast/import_pattern.rs +++ b/crates/nu-protocol/src/ast/import_pattern.rs @@ -3,10 +3,14 @@ use serde::{Deserialize, Serialize}; use crate::{ModuleId, Span, VarId}; use std::collections::HashSet; +/// possible patterns after the first module level in an [`ImportPattern`]. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum ImportPatternMember { + /// Wildcard import of items Glob { span: Span }, + /// single specific module or item Name { name: Vec, span: Span }, + /// list of items List { names: Vec<(Vec, Span)> }, } @@ -31,6 +35,7 @@ impl ImportPatternMember { } } +/// The first item of a `use` statement needs to specify an explicit module #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct ImportPatternHead { pub name: Vec, @@ -38,6 +43,7 @@ pub struct ImportPatternHead { pub span: Span, } +/// The pattern specifying modules in a `use` statement #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct ImportPattern { pub head: ImportPatternHead, diff --git a/crates/nu-protocol/src/ast/match_pattern.rs b/crates/nu-protocol/src/ast/match_pattern.rs index 1aafe84701..ca639270aa 100644 --- a/crates/nu-protocol/src/ast/match_pattern.rs +++ b/crates/nu-protocol/src/ast/match_pattern.rs @@ -2,6 +2,7 @@ use super::Expression; use crate::{Span, VarId}; use serde::{Deserialize, Serialize}; +/// AST Node for match arm with optional match guard #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct MatchPattern { pub pattern: Pattern, @@ -15,18 +16,28 @@ impl MatchPattern { } } +/// AST Node for pattern matching rules #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum Pattern { + /// Destructuring of records Record(Vec<(String, MatchPattern)>), + /// List destructuring List(Vec), + /// Matching against a literal // TODO: it would be nice if this didn't depend on AST // maybe const evaluation can get us to a Value instead? Value(Box), + /// binding to a variable Variable(VarId), + /// the `pattern1 \ pattern2` or-pattern Or(Vec), - Rest(VarId), // the ..$foo pattern - IgnoreRest, // the .. pattern - IgnoreValue, // the _ pattern + /// the `..$foo` pattern + Rest(VarId), + /// the `..` pattern + IgnoreRest, + /// the `_` pattern + IgnoreValue, + /// Failed parsing of a pattern Garbage, } diff --git a/crates/nu-protocol/src/ast/mod.rs b/crates/nu-protocol/src/ast/mod.rs index 7c627997fe..cb09168805 100644 --- a/crates/nu-protocol/src/ast/mod.rs +++ b/crates/nu-protocol/src/ast/mod.rs @@ -1,3 +1,4 @@ +//! Types representing parsed Nushell code (the Abstract Syntax Tree) mod block; mod call; mod cell_path; @@ -9,7 +10,7 @@ mod match_pattern; mod operator; mod pipeline; mod range; -pub mod table; +mod table; mod unit; mod value_with_unit; diff --git a/crates/nu-protocol/src/config/mod.rs b/crates/nu-protocol/src/config/mod.rs index e804e3f024..eda3d9a15e 100644 --- a/crates/nu-protocol/src/config/mod.rs +++ b/crates/nu-protocol/src/config/mod.rs @@ -1,3 +1,4 @@ +//! Module containing the internal representation of user configuration use self::completer::*; use self::helper::*; use self::hooks::*; diff --git a/crates/nu-protocol/src/debugger/mod.rs b/crates/nu-protocol/src/debugger/mod.rs index 03208fb3c7..86768a0a34 100644 --- a/crates/nu-protocol/src/debugger/mod.rs +++ b/crates/nu-protocol/src/debugger/mod.rs @@ -1,3 +1,4 @@ +//! Module containing the trait to instrument the engine for debugging and profiling pub mod debugger_trait; pub mod profiler; diff --git a/crates/nu-protocol/src/engine/mod.rs b/crates/nu-protocol/src/engine/mod.rs index 1b1762fe3c..39bb5a7ae3 100644 --- a/crates/nu-protocol/src/engine/mod.rs +++ b/crates/nu-protocol/src/engine/mod.rs @@ -1,3 +1,4 @@ +//! Representation of the engine state and many of the details that implement the scoping mod argument; mod cached_file; mod call; diff --git a/crates/nu-protocol/src/errors/cli_error.rs b/crates/nu-protocol/src/errors/cli_error.rs index 181839b948..c12c601c4d 100644 --- a/crates/nu-protocol/src/errors/cli_error.rs +++ b/crates/nu-protocol/src/errors/cli_error.rs @@ -1,3 +1,6 @@ +//! This module manages the step of turning error types into printed error messages +//! +//! Relies on the `miette` crate for pretty layout use crate::{ engine::{EngineState, StateWorkingSet}, ErrorStyle, diff --git a/crates/nu-protocol/src/eval_base.rs b/crates/nu-protocol/src/eval_base.rs index 7e1e2b0a7c..9c5878ee87 100644 --- a/crates/nu-protocol/src/eval_base.rs +++ b/crates/nu-protocol/src/eval_base.rs @@ -1,3 +1,4 @@ +//! Foundational [`Eval`] trait allowing dispatch between const-eval and regular evaluation use crate::{ ast::{ eval_operator, Assignment, Bits, Boolean, Call, Comparison, Expr, Expression, diff --git a/crates/nu-protocol/src/eval_const.rs b/crates/nu-protocol/src/eval_const.rs index 5393e35e59..da7570b43c 100644 --- a/crates/nu-protocol/src/eval_const.rs +++ b/crates/nu-protocol/src/eval_const.rs @@ -1,3 +1,7 @@ +//! Implementation of const-evaluation +//! +//! This enables you to assign `const`-constants and execute parse-time code dependent on this. +//! e.g. `source $my_const` use crate::{ ast::{Assignment, Block, Call, Expr, Expression, ExternalArgument}, debugger::{DebugContext, WithoutDebug}, diff --git a/crates/nu-protocol/src/pipeline/byte_stream.rs b/crates/nu-protocol/src/pipeline/byte_stream.rs index cd62b70801..69391b39a9 100644 --- a/crates/nu-protocol/src/pipeline/byte_stream.rs +++ b/crates/nu-protocol/src/pipeline/byte_stream.rs @@ -1,3 +1,4 @@ +//! Module managing the streaming of raw bytes between pipeline elements use crate::{ process::{ChildPipe, ChildProcess, ExitStatus}, ErrSpan, IntoSpanned, OutDest, PipelineData, ShellError, Signals, Span, Type, Value, diff --git a/crates/nu-protocol/src/pipeline/list_stream.rs b/crates/nu-protocol/src/pipeline/list_stream.rs index 104bab6bcc..55ae4bfee0 100644 --- a/crates/nu-protocol/src/pipeline/list_stream.rs +++ b/crates/nu-protocol/src/pipeline/list_stream.rs @@ -1,3 +1,7 @@ +//! Module managing the streaming of individual [`Value`]s as a [`ListStream`] between pipeline +//! elements +//! +//! For more general infos regarding our pipelining model refer to [`PipelineData`] use crate::{Config, PipelineData, ShellError, Signals, Span, Value}; use std::fmt::Debug; diff --git a/crates/nu-protocol/src/process/mod.rs b/crates/nu-protocol/src/process/mod.rs index 2fcf65f56e..6e10fdd620 100644 --- a/crates/nu-protocol/src/process/mod.rs +++ b/crates/nu-protocol/src/process/mod.rs @@ -1,3 +1,4 @@ +//! Handling of external subprocesses mod child; mod exit_status; diff --git a/crates/nu-protocol/src/signature.rs b/crates/nu-protocol/src/signature.rs index 3241b0df22..f8b83063d7 100644 --- a/crates/nu-protocol/src/signature.rs +++ b/crates/nu-protocol/src/signature.rs @@ -5,6 +5,7 @@ use crate::{ use serde::{Deserialize, Serialize}; use std::fmt::Write; +/// The signature definition of a named flag that either accepts a value or acts as a toggle flag #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Flag { pub long: String, @@ -18,6 +19,7 @@ pub struct Flag { pub default_value: Option, } +/// The signature definition for a positional argument #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct PositionalArg { pub name: String, @@ -29,6 +31,7 @@ pub struct PositionalArg { pub default_value: Option, } +/// Command categories #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum Category { Bits, @@ -102,6 +105,7 @@ impl std::fmt::Display for Category { } } +/// Signature information of a [`Command`] #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Signature { pub name: String, diff --git a/crates/nu-protocol/src/span.rs b/crates/nu-protocol/src/span.rs index f5bcebc543..cb6d96421c 100644 --- a/crates/nu-protocol/src/span.rs +++ b/crates/nu-protocol/src/span.rs @@ -1,3 +1,4 @@ +//! [`Span`] to point to sections of source code and the [`Spanned`] wrapper type use crate::SpanId; use miette::SourceSpan; use serde::{Deserialize, Serialize}; diff --git a/crates/nu-protocol/src/value/record.rs b/crates/nu-protocol/src/value/record.rs index 8b61e61f7f..f4e963737f 100644 --- a/crates/nu-protocol/src/value/record.rs +++ b/crates/nu-protocol/src/value/record.rs @@ -1,3 +1,4 @@ +//! Our insertion ordered map-type [`Record`] use std::{iter::FusedIterator, ops::RangeBounds}; use crate::{ShellError, Span, Value}; From deaa711ca62150351ec89d47d373a6cc6e0a36f7 Mon Sep 17 00:00:00 2001 From: Stefan Holderbach Date: Thu, 11 Jul 2024 14:20:28 +0200 Subject: [PATCH 027/126] Bump yanked `libc` version (#13344) Fixes #13244 --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7243a801fc..3820ff26d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2342,9 +2342,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.154" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "libflate" From f65bc97a545264e0fc0ca4ccb953a73a1cc1b07a Mon Sep 17 00:00:00 2001 From: Devyn Cairns Date: Thu, 11 Jul 2024 06:09:33 -0700 Subject: [PATCH 028/126] Update config directly at assignment (#13332) # Description Allows `Stack` to have a modified local `Config`, which is updated immediately when `$env.config` is assigned to. This means that even within a script, commands that come after `$env.config` changes will always see those changes in `Stack::get_config()`. Also fixed a lot of cases where `engine_state.get_config()` was used even when `Stack` was available. Closes #13324. # User-Facing Changes - Config changes apply immediately after the assignment is executed, rather than whenever config is read by a command that needs it. - Potentially slower performance when executing a lot of lines that change `$env.config` one after another. Recommended to get `$env.config` into a `mut` variable first and do modifications, then assign it back. - Much faster performance when executing a script that made modifications to `$env.config`, as the changes are only parsed once. # Tests + Formatting All passing. # After Submitting - [ ] release notes --- Cargo.lock | 2 + crates/nu-cli/src/eval_cmds.rs | 5 +- crates/nu-cli/src/menus/help_completions.rs | 32 ++++--- crates/nu-cli/src/nu_highlight.rs | 9 +- crates/nu-cli/src/reedline_config.rs | 54 ++++++----- crates/nu-cli/src/repl.rs | 3 +- crates/nu-cli/src/syntax_highlight.rs | 13 ++- .../nu-cmd-extra/src/extra/formats/to/html.rs | 2 +- .../src/extra/strings/format/command.rs | 36 ++++---- crates/nu-color-config/src/style_computer.rs | 4 +- .../nu-command/src/conversions/into/string.rs | 6 +- crates/nu-command/src/debug/debug_.rs | 2 +- crates/nu-command/src/filesystem/rm.rs | 2 +- crates/nu-command/src/filters/find.rs | 6 +- crates/nu-command/src/formats/to/md.rs | 4 +- crates/nu-command/src/formats/to/text.rs | 6 +- crates/nu-command/src/help/help_aliases.rs | 2 +- crates/nu-command/src/help/help_modules.rs | 2 +- crates/nu-command/src/network/url/parse.rs | 17 ++-- crates/nu-command/src/platform/ansi/ansi_.rs | 2 +- crates/nu-command/src/platform/ansi/strip.rs | 11 +-- crates/nu-command/src/platform/input/list.rs | 5 +- crates/nu-command/src/stor/insert.rs | 2 +- .../nu-command/src/strings/detect_columns.rs | 14 ++- crates/nu-command/src/system/nu_check.rs | 4 +- crates/nu-command/src/system/run_external.rs | 2 +- crates/nu-command/src/viewers/griddle.rs | 2 +- crates/nu-command/src/viewers/table.rs | 44 +++++++-- crates/nu-engine/src/documentation.rs | 92 ++++++++++++------- crates/nu-engine/src/env.rs | 14 +-- crates/nu-engine/src/eval.rs | 18 +++- crates/nu-explore/src/commands/expand.rs | 4 +- crates/nu-explore/src/explore.rs | 6 +- crates/nu-plugin-engine/Cargo.toml | 3 +- crates/nu-plugin-engine/src/context.rs | 8 +- crates/nu-plugin-engine/src/declaration.rs | 2 +- crates/nu-plugin-engine/src/interface/mod.rs | 3 +- crates/nu-plugin-protocol/src/lib.rs | 3 +- crates/nu-plugin/Cargo.toml | 3 +- crates/nu-plugin/src/plugin/interface/mod.rs | 5 +- crates/nu-plugin/src/plugin/mod.rs | 2 +- crates/nu-protocol/src/engine/engine_state.rs | 38 +++----- crates/nu-protocol/src/engine/stack.rs | 39 +++++++- .../src/engine/state_working_set.rs | 6 +- crates/nu-protocol/src/eval_base.rs | 4 +- crates/nu-protocol/src/eval_const.rs | 6 +- 46 files changed, 327 insertions(+), 222 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3820ff26d1..c7608643dc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3257,6 +3257,7 @@ dependencies = [ "nu-plugin-core", "nu-plugin-protocol", "nu-protocol", + "nu-utils", "serde", "thiserror", "typetag", @@ -3286,6 +3287,7 @@ dependencies = [ "nu-plugin-protocol", "nu-protocol", "nu-system", + "nu-utils", "serde", "typetag", "windows 0.54.0", diff --git a/crates/nu-cli/src/eval_cmds.rs b/crates/nu-cli/src/eval_cmds.rs index ad3a15304d..1459f1ef0d 100644 --- a/crates/nu-cli/src/eval_cmds.rs +++ b/crates/nu-cli/src/eval_cmds.rs @@ -53,9 +53,8 @@ pub fn evaluate_commands( // Parse the source code let (block, delta) = { if let Some(ref t_mode) = table_mode { - let mut config = engine_state.get_config().clone(); - config.table_mode = t_mode.coerce_str()?.parse().unwrap_or_default(); - engine_state.set_config(config); + Arc::make_mut(&mut engine_state.config).table_mode = + t_mode.coerce_str()?.parse().unwrap_or_default(); } let mut working_set = StateWorkingSet::new(engine_state); diff --git a/crates/nu-cli/src/menus/help_completions.rs b/crates/nu-cli/src/menus/help_completions.rs index c9c1b7bf94..ebe3fe616b 100644 --- a/crates/nu-cli/src/menus/help_completions.rs +++ b/crates/nu-cli/src/menus/help_completions.rs @@ -1,25 +1,31 @@ use nu_engine::documentation::get_flags_section; -use nu_protocol::{engine::EngineState, levenshtein_distance}; +use nu_protocol::{engine::EngineState, levenshtein_distance, Config}; use nu_utils::IgnoreCaseExt; use reedline::{Completer, Suggestion}; use std::{fmt::Write, sync::Arc}; -pub struct NuHelpCompleter(Arc); +pub struct NuHelpCompleter { + engine_state: Arc, + config: Arc, +} impl NuHelpCompleter { - pub fn new(engine_state: Arc) -> Self { - Self(engine_state) + pub fn new(engine_state: Arc, config: Arc) -> Self { + Self { + engine_state, + config, + } } fn completion_helper(&self, line: &str, pos: usize) -> Vec { let folded_line = line.to_folded_case(); let mut commands = self - .0 + .engine_state .get_decls_sorted(false) .into_iter() .filter_map(|(_, decl_id)| { - let decl = self.0.get_decl(decl_id); + let decl = self.engine_state.get_decl(decl_id); (decl.name().to_folded_case().contains(&folded_line) || decl.usage().to_folded_case().contains(&folded_line) || decl @@ -54,9 +60,12 @@ impl NuHelpCompleter { let _ = write!(long_desc, "Usage:\r\n > {}\r\n", sig.call_signature()); if !sig.named.is_empty() { - long_desc.push_str(&get_flags_section(Some(&*self.0.clone()), &sig, |v| { - v.to_parsable_string(", ", &self.0.config) - })) + long_desc.push_str(&get_flags_section( + Some(&self.engine_state), + Some(&self.config), + &sig, + |v| v.to_parsable_string(", ", &self.config), + )) } if !sig.required_positional.is_empty() @@ -71,7 +80,7 @@ impl NuHelpCompleter { let opt_suffix = if let Some(value) = &positional.default_value { format!( " (optional, default: {})", - &value.to_parsable_string(", ", &self.0.config), + &value.to_parsable_string(", ", &self.config), ) } else { (" (optional)").to_string() @@ -138,7 +147,8 @@ mod test { ) { let engine_state = nu_command::add_shell_command_context(nu_cmd_lang::create_default_context()); - let mut completer = NuHelpCompleter::new(engine_state.into()); + let config = engine_state.get_config().clone(); + let mut completer = NuHelpCompleter::new(engine_state.into(), config); let suggestions = completer.complete(line, end); assert_eq!( diff --git a/crates/nu-cli/src/nu_highlight.rs b/crates/nu-cli/src/nu_highlight.rs index f4f38296de..0b5f211838 100644 --- a/crates/nu-cli/src/nu_highlight.rs +++ b/crates/nu-cli/src/nu_highlight.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use nu_engine::command_prelude::*; use reedline::{Highlighter, StyledText}; @@ -33,13 +35,10 @@ impl Command for NuHighlight { let head = call.head; let signals = engine_state.signals(); - let engine_state = std::sync::Arc::new(engine_state.clone()); - let config = engine_state.get_config().clone(); let highlighter = crate::NuHighlighter { - engine_state, - stack: std::sync::Arc::new(stack.clone()), - config, + engine_state: Arc::new(engine_state.clone()), + stack: Arc::new(stack.clone()), }; input.map( diff --git a/crates/nu-cli/src/reedline_config.rs b/crates/nu-cli/src/reedline_config.rs index dd5a3199dc..d5d3272bc1 100644 --- a/crates/nu-cli/src/reedline_config.rs +++ b/crates/nu-cli/src/reedline_config.rs @@ -77,13 +77,19 @@ pub(crate) fn add_menus( mut line_editor: Reedline, engine_state_ref: Arc, stack: &Stack, - config: &Config, + config: Arc, ) -> Result { //log::trace!("add_menus: config: {:#?}", &config); line_editor = line_editor.clear_menus(); for menu in &config.menus { - line_editor = add_menu(line_editor, menu, engine_state_ref.clone(), stack, config)? + line_editor = add_menu( + line_editor, + menu, + engine_state_ref.clone(), + stack, + config.clone(), + )? } // Checking if the default menus have been added from the config file @@ -100,7 +106,7 @@ pub(crate) fn add_menus( if !config .menus .iter() - .any(|menu| menu.name.to_expanded_string("", config) == name) + .any(|menu| menu.name.to_expanded_string("", &config) == name) { let (block, delta) = { let mut working_set = StateWorkingSet::new(&engine_state); @@ -137,7 +143,7 @@ pub(crate) fn add_menus( &menu, new_engine_state_ref.clone(), stack, - config, + config.clone(), )?; } } @@ -151,27 +157,27 @@ fn add_menu( menu: &ParsedMenu, engine_state: Arc, stack: &Stack, - config: &Config, + config: Arc, ) -> Result { let span = menu.menu_type.span(); if let Value::Record { val, .. } = &menu.menu_type { - let layout = extract_value("layout", val, span)?.to_expanded_string("", config); + let layout = extract_value("layout", val, span)?.to_expanded_string("", &config); match layout.as_str() { - "columnar" => add_columnar_menu(line_editor, menu, engine_state, stack, config), + "columnar" => add_columnar_menu(line_editor, menu, engine_state, stack, &config), "list" => add_list_menu(line_editor, menu, engine_state, stack, config), "ide" => add_ide_menu(line_editor, menu, engine_state, stack, config), "description" => add_description_menu(line_editor, menu, engine_state, stack, config), _ => Err(ShellError::UnsupportedConfigValue { expected: "columnar, list, ide or description".to_string(), - value: menu.menu_type.to_abbreviated_string(config), + value: menu.menu_type.to_abbreviated_string(&config), span: menu.menu_type.span(), }), } } else { Err(ShellError::UnsupportedConfigValue { expected: "only record type".to_string(), - value: menu.menu_type.to_abbreviated_string(config), + value: menu.menu_type.to_abbreviated_string(&config), span: menu.menu_type.span(), }) } @@ -282,9 +288,9 @@ pub(crate) fn add_list_menu( menu: &ParsedMenu, engine_state: Arc, stack: &Stack, - config: &Config, + config: Arc, ) -> Result { - let name = menu.name.to_expanded_string("", config); + let name = menu.name.to_expanded_string("", &config); let mut list_menu = ListMenu::default().with_name(&name); let span = menu.menu_type.span(); @@ -311,7 +317,7 @@ pub(crate) fn add_list_menu( } } - let marker = menu.marker.to_expanded_string("", config); + let marker = menu.marker.to_expanded_string("", &config); list_menu = list_menu.with_marker(&marker); let only_buffer_difference = menu.only_buffer_difference.as_bool()?; @@ -337,7 +343,7 @@ pub(crate) fn add_list_menu( } _ => Err(ShellError::UnsupportedConfigValue { expected: "block or omitted value".to_string(), - value: menu.source.to_abbreviated_string(config), + value: menu.source.to_abbreviated_string(&config), span: menu.source.span(), }), } @@ -349,10 +355,10 @@ pub(crate) fn add_ide_menu( menu: &ParsedMenu, engine_state: Arc, stack: &Stack, - config: &Config, + config: Arc, ) -> Result { let span = menu.menu_type.span(); - let name = menu.name.to_expanded_string("", config); + let name = menu.name.to_expanded_string("", &config); let mut ide_menu = IdeMenu::default().with_name(&name); if let Value::Record { val, .. } = &menu.menu_type { @@ -417,7 +423,7 @@ pub(crate) fn add_ide_menu( } else { return Err(ShellError::UnsupportedConfigValue { expected: "bool or record".to_string(), - value: border.to_abbreviated_string(config), + value: border.to_abbreviated_string(&config), span: border.span(), }); } @@ -441,7 +447,7 @@ pub(crate) fn add_ide_menu( _ => { return Err(ShellError::UnsupportedConfigValue { expected: "\"left\", \"right\" or \"prefer_right\"".to_string(), - value: description_mode.to_abbreviated_string(config), + value: description_mode.to_abbreviated_string(&config), span: description_mode.span(), }); } @@ -509,7 +515,7 @@ pub(crate) fn add_ide_menu( } } - let marker = menu.marker.to_expanded_string("", config); + let marker = menu.marker.to_expanded_string("", &config); ide_menu = ide_menu.with_marker(&marker); let only_buffer_difference = menu.only_buffer_difference.as_bool()?; @@ -535,7 +541,7 @@ pub(crate) fn add_ide_menu( } _ => Err(ShellError::UnsupportedConfigValue { expected: "block or omitted value".to_string(), - value: menu.source.to_abbreviated_string(config), + value: menu.source.to_abbreviated_string(&config), span, }), } @@ -547,9 +553,9 @@ pub(crate) fn add_description_menu( menu: &ParsedMenu, engine_state: Arc, stack: &Stack, - config: &Config, + config: Arc, ) -> Result { - let name = menu.name.to_expanded_string("", config); + let name = menu.name.to_expanded_string("", &config); let mut description_menu = DescriptionMenu::default().with_name(&name); let span = menu.menu_type.span(); @@ -608,7 +614,7 @@ pub(crate) fn add_description_menu( } } - let marker = menu.marker.to_expanded_string("", config); + let marker = menu.marker.to_expanded_string("", &config); description_menu = description_menu.with_marker(&marker); let only_buffer_difference = menu.only_buffer_difference.as_bool()?; @@ -617,7 +623,7 @@ pub(crate) fn add_description_menu( let span = menu.source.span(); match &menu.source { Value::Nothing { .. } => { - let completer = Box::new(NuHelpCompleter::new(engine_state)); + let completer = Box::new(NuHelpCompleter::new(engine_state, config)); Ok(line_editor.with_menu(ReedlineMenu::WithCompleter { menu: Box::new(description_menu), completer, @@ -638,7 +644,7 @@ pub(crate) fn add_description_menu( } _ => Err(ShellError::UnsupportedConfigValue { expected: "closure or omitted value".to_string(), - value: menu.source.to_abbreviated_string(config), + value: menu.source.to_abbreviated_string(&config), span: menu.source.span(), }), } diff --git a/crates/nu-cli/src/repl.rs b/crates/nu-cli/src/repl.rs index 07272701f3..817950a445 100644 --- a/crates/nu-cli/src/repl.rs +++ b/crates/nu-cli/src/repl.rs @@ -297,7 +297,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) { perf!("env-change hook", start_time, use_color); let engine_reference = Arc::new(engine_state.clone()); - let config = engine_state.get_config(); + let config = stack.get_config(engine_state); start_time = std::time::Instant::now(); // Find the configured cursor shapes for each mode @@ -323,7 +323,6 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) { engine_state: engine_reference.clone(), // STACK-REFERENCE 1 stack: stack_arc.clone(), - config: config.clone(), })) .with_validator(Box::new(NuValidator { engine_state: engine_reference.clone(), diff --git a/crates/nu-cli/src/syntax_highlight.rs b/crates/nu-cli/src/syntax_highlight.rs index 41ef168390..08dcbb1346 100644 --- a/crates/nu-cli/src/syntax_highlight.rs +++ b/crates/nu-cli/src/syntax_highlight.rs @@ -6,7 +6,7 @@ use nu_parser::{flatten_block, parse, FlatShape}; use nu_protocol::{ ast::{Block, Expr, Expression, PipelineRedirection, RecordItem}, engine::{EngineState, Stack, StateWorkingSet}, - Config, Span, + Span, }; use reedline::{Highlighter, StyledText}; use std::sync::Arc; @@ -14,15 +14,14 @@ use std::sync::Arc; pub struct NuHighlighter { pub engine_state: Arc, pub stack: Arc, - pub config: Config, } impl Highlighter for NuHighlighter { fn highlight(&self, line: &str, _cursor: usize) -> StyledText { trace!("highlighting: {}", line); - let highlight_resolved_externals = - self.engine_state.get_config().highlight_resolved_externals; + let config = self.stack.get_config(&self.engine_state); + let highlight_resolved_externals = config.highlight_resolved_externals; let mut working_set = StateWorkingSet::new(&self.engine_state); let block = parse(&mut working_set, None, line.as_bytes(), false); let (shapes, global_span_offset) = { @@ -88,7 +87,7 @@ impl Highlighter for NuHighlighter { .to_string(); let mut add_colored_token = |shape: &FlatShape, text: String| { - output.push((get_shape_color(shape.as_str(), &self.config), text)); + output.push((get_shape_color(shape.as_str(), &config), text)); }; match shape.1 { @@ -128,9 +127,9 @@ impl Highlighter for NuHighlighter { let start = part.start - span.start; let end = part.end - span.start; let text = next_token[start..end].to_string(); - let mut style = get_shape_color(shape.as_str(), &self.config); + let mut style = get_shape_color(shape.as_str(), &config); if highlight { - style = get_matching_brackets_style(style, &self.config); + style = get_matching_brackets_style(style, &config); } output.push((style, text)); } diff --git a/crates/nu-cmd-extra/src/extra/formats/to/html.rs b/crates/nu-cmd-extra/src/extra/formats/to/html.rs index 83d0b58b3f..f02f11cf84 100644 --- a/crates/nu-cmd-extra/src/extra/formats/to/html.rs +++ b/crates/nu-cmd-extra/src/extra/formats/to/html.rs @@ -239,7 +239,7 @@ fn to_html( let partial = call.has_flag(engine_state, stack, "partial")?; let list = call.has_flag(engine_state, stack, "list")?; let theme: Option> = call.get_flag(engine_state, stack, "theme")?; - let config = engine_state.get_config(); + let config = &stack.get_config(engine_state); let vec_of_values = input.into_iter().collect::>(); let headers = merge_descriptors(&vec_of_values); diff --git a/crates/nu-cmd-extra/src/extra/strings/format/command.rs b/crates/nu-cmd-extra/src/extra/strings/format/command.rs index 08df92ef40..ef9b6f2baf 100644 --- a/crates/nu-cmd-extra/src/extra/strings/format/command.rs +++ b/crates/nu-cmd-extra/src/extra/strings/format/command.rs @@ -1,5 +1,5 @@ use nu_engine::command_prelude::*; -use nu_protocol::{ast::PathMember, engine::StateWorkingSet, ListStream}; +use nu_protocol::{ast::PathMember, engine::StateWorkingSet, Config, ListStream}; #[derive(Clone)] pub struct FormatPattern; @@ -43,6 +43,8 @@ impl Command for FormatPattern { let it_id = working_set.add_variable(b"$it".to_vec(), call.head, Type::Any, false); stack.add_var(it_id, input_val.clone()); + let config = stack.get_config(engine_state); + match specified_pattern { Err(e) => Err(e), Ok(pattern) => { @@ -56,7 +58,7 @@ impl Command for FormatPattern { string_span.start + 1, )?; - format(input_val, &ops, engine_state, call.head) + format(input_val, &ops, engine_state, &config, call.head) } } } @@ -181,33 +183,30 @@ fn format( input_data: Value, format_operations: &[FormatOperation], engine_state: &EngineState, + config: &Config, head_span: Span, ) -> Result { let data_as_value = input_data; // We can only handle a Record or a List of Records match data_as_value { - Value::Record { .. } => { - match format_record(format_operations, &data_as_value, engine_state) { - Ok(value) => Ok(PipelineData::Value(Value::string(value, head_span), None)), - Err(value) => Err(value), - } - } + Value::Record { .. } => match format_record(format_operations, &data_as_value, config) { + Ok(value) => Ok(PipelineData::Value(Value::string(value, head_span), None)), + Err(value) => Err(value), + }, Value::List { vals, .. } => { let mut list = vec![]; for val in vals.iter() { match val { - Value::Record { .. } => { - match format_record(format_operations, val, engine_state) { - Ok(value) => { - list.push(Value::string(value, head_span)); - } - Err(value) => { - return Err(value); - } + Value::Record { .. } => match format_record(format_operations, val, config) { + Ok(value) => { + list.push(Value::string(value, head_span)); } - } + Err(value) => { + return Err(value); + } + }, Value::Error { error, .. } => return Err(*error.clone()), _ => { return Err(ShellError::OnlySupportsThisInputType { @@ -237,9 +236,8 @@ fn format( fn format_record( format_operations: &[FormatOperation], data_as_value: &Value, - engine_state: &EngineState, + config: &Config, ) -> Result { - let config = engine_state.get_config(); let mut output = String::new(); for op in format_operations { diff --git a/crates/nu-color-config/src/style_computer.rs b/crates/nu-color-config/src/style_computer.rs index 91907c1428..edc8786a53 100644 --- a/crates/nu-color-config/src/style_computer.rs +++ b/crates/nu-color-config/src/style_computer.rs @@ -1,6 +1,6 @@ use crate::{color_record_to_nustyle, lookup_ansi_color_style, text_style::Alignment, TextStyle}; use nu_ansi_term::{Color, Style}; -use nu_engine::{env::get_config, ClosureEvalOnce}; +use nu_engine::ClosureEvalOnce; use nu_protocol::{ cli_error::CliError, engine::{Closure, EngineState, Stack, StateWorkingSet}, @@ -114,7 +114,7 @@ impl<'a> StyleComputer<'a> { // The main constructor. pub fn from_config(engine_state: &'a EngineState, stack: &'a Stack) -> StyleComputer<'a> { - let config = get_config(engine_state, stack); + let config = stack.get_config(engine_state); // Create the hashmap #[rustfmt::skip] diff --git a/crates/nu-command/src/conversions/into/string.rs b/crates/nu-command/src/conversions/into/string.rs index c394f9fd21..2b35272b3e 100644 --- a/crates/nu-command/src/conversions/into/string.rs +++ b/crates/nu-command/src/conversions/into/string.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use nu_cmd_base::input_handler::{operate, CmdArgument}; use nu_engine::command_prelude::*; use nu_protocol::{into_code, Config}; @@ -7,7 +9,7 @@ use num_format::ToFormattedString; struct Arguments { decimals_value: Option, cell_paths: Option>, - config: Config, + config: Arc, } impl CmdArgument for Arguments { @@ -174,7 +176,7 @@ fn string_helper( }) } } else { - let config = engine_state.get_config().clone(); + let config = stack.get_config(engine_state); let args = Arguments { decimals_value, cell_paths, diff --git a/crates/nu-command/src/debug/debug_.rs b/crates/nu-command/src/debug/debug_.rs index f4e5707491..80a9067e49 100644 --- a/crates/nu-command/src/debug/debug_.rs +++ b/crates/nu-command/src/debug/debug_.rs @@ -33,7 +33,7 @@ impl Command for Debug { input: PipelineData, ) -> Result { let head = call.head; - let config = engine_state.get_config().clone(); + let config = stack.get_config(engine_state); let raw = call.has_flag(engine_state, stack, "raw")?; // Should PipelineData::Empty result in an error here? diff --git a/crates/nu-command/src/filesystem/rm.rs b/crates/nu-command/src/filesystem/rm.rs index d67046ffe1..9eb4fabace 100644 --- a/crates/nu-command/src/filesystem/rm.rs +++ b/crates/nu-command/src/filesystem/rm.rs @@ -170,7 +170,7 @@ fn rm( } let span = call.head; - let rm_always_trash = engine_state.get_config().rm_always_trash; + let rm_always_trash = stack.get_config(engine_state).rm_always_trash; if !TRASH_SUPPORTED { if rm_always_trash { diff --git a/crates/nu-command/src/filters/find.rs b/crates/nu-command/src/filters/find.rs index 7669190f7e..9ed0c28a3c 100644 --- a/crates/nu-command/src/filters/find.rs +++ b/crates/nu-command/src/filters/find.rs @@ -213,7 +213,7 @@ fn find_with_regex( input: PipelineData, ) -> Result { let span = call.head; - let config = engine_state.get_config().clone(); + let config = stack.get_config(engine_state); let insensitive = call.has_flag(engine_state, stack, "ignore-case")?; let multiline = call.has_flag(engine_state, stack, "multiline")?; @@ -348,8 +348,8 @@ fn find_with_rest_and_highlight( input: PipelineData, ) -> Result { let span = call.head; - let config = engine_state.get_config().clone(); - let filter_config = engine_state.get_config().clone(); + let config = stack.get_config(engine_state); + let filter_config = config.clone(); let invert = call.has_flag(engine_state, stack, "invert")?; let terms = call.rest::(engine_state, stack, 0)?; let lower_terms = terms diff --git a/crates/nu-command/src/formats/to/md.rs b/crates/nu-command/src/formats/to/md.rs index e2acdd703e..737a328cd3 100644 --- a/crates/nu-command/src/formats/to/md.rs +++ b/crates/nu-command/src/formats/to/md.rs @@ -70,8 +70,8 @@ impl Command for ToMd { let head = call.head; let pretty = call.has_flag(engine_state, stack, "pretty")?; let per_element = call.has_flag(engine_state, stack, "per-element")?; - let config = engine_state.get_config(); - to_md(input, pretty, per_element, config, head) + let config = stack.get_config(engine_state); + to_md(input, pretty, per_element, &config, head) } } diff --git a/crates/nu-command/src/formats/to/text.rs b/crates/nu-command/src/formats/to/text.rs index 82b853248c..33ca19f8f2 100644 --- a/crates/nu-command/src/formats/to/text.rs +++ b/crates/nu-command/src/formats/to/text.rs @@ -31,18 +31,19 @@ impl Command for ToText { fn run( &self, engine_state: &EngineState, - _stack: &mut Stack, + stack: &mut Stack, call: &Call, input: PipelineData, ) -> Result { let span = call.head; let input = input.try_expand_range()?; + let config = stack.get_config(engine_state); match input { PipelineData::Empty => Ok(Value::string(String::new(), span) .into_pipeline_data_with_metadata(update_metadata(None))), PipelineData::Value(value, ..) => { - let str = local_into_string(value, LINE_ENDING, engine_state.get_config()); + let str = local_into_string(value, LINE_ENDING, &config); Ok( Value::string(str, span) .into_pipeline_data_with_metadata(update_metadata(None)), @@ -50,7 +51,6 @@ impl Command for ToText { } PipelineData::ListStream(stream, meta) => { let span = stream.span(); - let config = engine_state.get_config().clone(); let iter = stream.into_inner().map(move |value| { let mut str = local_into_string(value, LINE_ENDING, &config); str.push_str(LINE_ENDING); diff --git a/crates/nu-command/src/help/help_aliases.rs b/crates/nu-command/src/help/help_aliases.rs index da03fa6398..41af9da0b3 100644 --- a/crates/nu-command/src/help/help_aliases.rs +++ b/crates/nu-command/src/help/help_aliases.rs @@ -143,7 +143,7 @@ pub fn help_aliases( long_desc.push_str("\n\n"); long_desc.push_str(&format!("{G}Expansion{RESET}:\n {alias_expansion}")); - let config = engine_state.get_config(); + let config = stack.get_config(engine_state); if !config.use_ansi_coloring { long_desc = nu_utils::strip_ansi_string_likely(long_desc); } diff --git a/crates/nu-command/src/help/help_modules.rs b/crates/nu-command/src/help/help_modules.rs index 5b39133a6d..c2c00cf400 100644 --- a/crates/nu-command/src/help/help_modules.rs +++ b/crates/nu-command/src/help/help_modules.rs @@ -230,7 +230,7 @@ pub fn help_modules( )); } - let config = engine_state.get_config(); + let config = stack.get_config(engine_state); if !config.use_ansi_coloring { long_desc = nu_utils::strip_ansi_string_likely(long_desc); } diff --git a/crates/nu-command/src/network/url/parse.rs b/crates/nu-command/src/network/url/parse.rs index e71c8d472a..287c64c7b9 100644 --- a/crates/nu-command/src/network/url/parse.rs +++ b/crates/nu-command/src/network/url/parse.rs @@ -1,4 +1,5 @@ use nu_engine::command_prelude::*; +use nu_protocol::Config; use url::Url; #[derive(Clone)] @@ -38,11 +39,15 @@ impl Command for SubCommand { fn run( &self, engine_state: &EngineState, - _: &mut Stack, + stack: &mut Stack, call: &Call, input: PipelineData, ) -> Result { - parse(input.into_value(call.head)?, call.head, engine_state) + parse( + input.into_value(call.head)?, + call.head, + &stack.get_config(engine_state), + ) } fn examples(&self) -> Vec { @@ -68,12 +73,12 @@ impl Command for SubCommand { } } -fn get_url_string(value: &Value, engine_state: &EngineState) -> String { - value.to_expanded_string("", engine_state.get_config()) +fn get_url_string(value: &Value, config: &Config) -> String { + value.to_expanded_string("", config) } -fn parse(value: Value, head: Span, engine_state: &EngineState) -> Result { - let url_string = get_url_string(&value, engine_state); +fn parse(value: Value, head: Span, config: &Config) -> Result { + let url_string = get_url_string(&value, config); let result_url = Url::parse(url_string.as_str()); diff --git a/crates/nu-command/src/platform/ansi/ansi_.rs b/crates/nu-command/src/platform/ansi/ansi_.rs index 23161eb7bf..3daed5e157 100644 --- a/crates/nu-command/src/platform/ansi/ansi_.rs +++ b/crates/nu-command/src/platform/ansi/ansi_.rs @@ -653,7 +653,7 @@ Operating system commands: let list: bool = call.has_flag(engine_state, stack, "list")?; let escape: bool = call.has_flag(engine_state, stack, "escape")?; let osc: bool = call.has_flag(engine_state, stack, "osc")?; - let use_ansi_coloring = engine_state.get_config().use_ansi_coloring; + let use_ansi_coloring = stack.get_config(engine_state).use_ansi_coloring; if list { return Ok(generate_ansi_code_list( diff --git a/crates/nu-command/src/platform/ansi/strip.rs b/crates/nu-command/src/platform/ansi/strip.rs index 3d59da6a10..4f7f8ea596 100644 --- a/crates/nu-command/src/platform/ansi/strip.rs +++ b/crates/nu-command/src/platform/ansi/strip.rs @@ -1,10 +1,12 @@ +use std::sync::Arc; + use nu_cmd_base::input_handler::{operate, CmdArgument}; use nu_engine::command_prelude::*; use nu_protocol::Config; pub struct Arguments { cell_paths: Option>, - config: Config, + config: Arc, } impl CmdArgument for Arguments { @@ -51,11 +53,8 @@ impl Command for SubCommand { ) -> Result { let cell_paths: Vec = call.rest(engine_state, stack, 1)?; let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths); - let config = engine_state.get_config(); - let args = Arguments { - cell_paths, - config: config.clone(), - }; + let config = stack.get_config(engine_state); + let args = Arguments { cell_paths, config }; operate(action, args, input, call.head, engine_state.signals()) } diff --git a/crates/nu-command/src/platform/input/list.rs b/crates/nu-command/src/platform/input/list.rs index 44b21a8da4..37f16e75d7 100644 --- a/crates/nu-command/src/platform/input/list.rs +++ b/crates/nu-command/src/platform/input/list.rs @@ -79,6 +79,7 @@ impl Command for InputList { let fuzzy = call.has_flag(engine_state, stack, "fuzzy")?; let index = call.has_flag(engine_state, stack, "index")?; let display_path: Option = call.get_flag(engine_state, stack, "display")?; + let config = stack.get_config(engine_state); let options: Vec = match input { PipelineData::Value(Value::Range { .. }, ..) @@ -89,9 +90,9 @@ impl Command for InputList { let display_value = if let Some(ref cellpath) = display_path { val.clone() .follow_cell_path(&cellpath.members, false)? - .to_expanded_string(", ", engine_state.get_config()) + .to_expanded_string(", ", &config) } else { - val.to_expanded_string(", ", engine_state.get_config()) + val.to_expanded_string(", ", &config) }; Ok(Options { name: display_value, diff --git a/crates/nu-command/src/stor/insert.rs b/crates/nu-command/src/stor/insert.rs index b6b8d50906..0e5c9ec6af 100644 --- a/crates/nu-command/src/stor/insert.rs +++ b/crates/nu-command/src/stor/insert.rs @@ -65,7 +65,7 @@ impl Command for StorInsert { let span = call.head; let table_name: Option = call.get_flag(engine_state, stack, "table-name")?; let data_record: Option = call.get_flag(engine_state, stack, "data-record")?; - // let config = engine_state.get_config(); + // let config = stack.get_config(engine_state); let db = Box::new(SQLiteDatabase::new( std::path::Path::new(MEMORY_DB), Signals::empty(), diff --git a/crates/nu-command/src/strings/detect_columns.rs b/crates/nu-command/src/strings/detect_columns.rs index 2f1f713b33..37a7cfe303 100644 --- a/crates/nu-command/src/strings/detect_columns.rs +++ b/crates/nu-command/src/strings/detect_columns.rs @@ -1,7 +1,7 @@ use itertools::Itertools; use nu_engine::command_prelude::*; -use nu_protocol::Range; -use std::{io::Cursor, iter::Peekable, str::CharIndices}; +use nu_protocol::{Config, Range}; +use std::{io::Cursor, iter::Peekable, str::CharIndices, sync::Arc}; type Input<'t> = Peekable>; @@ -110,11 +110,13 @@ none 8150224 4 8150220 1% /mnt/c' | detect columns --gue let num_rows_to_skip: Option = call.get_flag(engine_state, stack, "skip")?; let noheader = call.has_flag(engine_state, stack, "no-headers")?; let range: Option = call.get_flag(engine_state, stack, "combine-columns")?; + let config = stack.get_config(engine_state); let args = Arguments { noheader, num_rows_to_skip, range, + config, }; if call.has_flag(engine_state, stack, "guess")? { @@ -133,11 +135,13 @@ none 8150224 4 8150220 1% /mnt/c' | detect columns --gue let num_rows_to_skip: Option = call.get_flag_const(working_set, "skip")?; let noheader = call.has_flag_const(working_set, "no-headers")?; let range: Option = call.get_flag_const(working_set, "combine-columns")?; + let config = working_set.get_config().clone(); let args = Arguments { noheader, num_rows_to_skip, range, + config, }; if call.has_flag_const(working_set, "guess")? { @@ -152,6 +156,7 @@ struct Arguments { num_rows_to_skip: Option, noheader: bool, range: Option, + config: Arc, } fn guess_width( @@ -163,7 +168,7 @@ fn guess_width( use super::guess_width::GuessWidth; let input_span = input.span().unwrap_or(call.head); - let mut input = input.collect_string("", engine_state.get_config())?; + let mut input = input.collect_string("", &args.config)?; if let Some(rows) = args.num_rows_to_skip { input = input.lines().skip(rows).map(|x| x.to_string()).join("\n"); } @@ -235,8 +240,7 @@ fn detect_columns( args: Arguments, ) -> Result { let name_span = call.head; - let config = engine_state.get_config(); - let input = input.collect_string("", config)?; + let input = input.collect_string("", &args.config)?; let input: Vec<_> = input .lines() diff --git a/crates/nu-command/src/system/nu_check.rs b/crates/nu-command/src/system/nu_check.rs index 334569c79e..c0c29858d5 100644 --- a/crates/nu-command/src/system/nu_check.rs +++ b/crates/nu-command/src/system/nu_check.rs @@ -1,4 +1,4 @@ -use nu_engine::{command_prelude::*, env::get_config, find_in_dirs_env, get_dirs_var_from_call}; +use nu_engine::{command_prelude::*, find_in_dirs_env, get_dirs_var_from_call}; use nu_parser::{parse, parse_module_block, parse_module_file_or_dir, unescape_unquote_string}; use nu_protocol::engine::{FileStack, StateWorkingSet}; use std::path::Path; @@ -59,7 +59,7 @@ impl Command for NuCheck { } } PipelineData::ListStream(stream, ..) => { - let config = get_config(engine_state, stack); + let config = stack.get_config(engine_state); let list_stream = stream.into_string("\n", &config); let contents = Vec::from(list_stream); diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs index a5cf343970..a7ca822011 100644 --- a/crates/nu-command/src/system/run_external.rs +++ b/crates/nu-command/src/system/run_external.rs @@ -366,7 +366,7 @@ pub fn command_not_found( stack: &mut Stack, ) -> ShellError { // Run the `command_not_found` hook if there is one. - if let Some(hook) = &engine_state.config.hooks.command_not_found { + if let Some(hook) = &stack.get_config(engine_state).hooks.command_not_found { let mut stack = stack.start_capture(); // Set a special environment variable to avoid infinite loops when the // `command_not_found` hook triggers itself. diff --git a/crates/nu-command/src/viewers/griddle.rs b/crates/nu-command/src/viewers/griddle.rs index cd73c1ca71..c3e003be39 100644 --- a/crates/nu-command/src/viewers/griddle.rs +++ b/crates/nu-command/src/viewers/griddle.rs @@ -61,7 +61,7 @@ prints out the list properly."# let width_param: Option = call.get_flag(engine_state, stack, "width")?; let color_param: bool = call.has_flag(engine_state, stack, "color")?; let separator_param: Option = call.get_flag(engine_state, stack, "separator")?; - let config = engine_state.get_config(); + let config = &stack.get_config(engine_state); let env_str = match stack.get_env_var(engine_state, "LS_COLORS") { Some(v) => Some(env_to_string("LS_COLORS", &v, engine_state, stack)?), None => None, diff --git a/crates/nu-command/src/viewers/table.rs b/crates/nu-command/src/viewers/table.rs index 190c3659af..e5fdf681f7 100644 --- a/crates/nu-command/src/viewers/table.rs +++ b/crates/nu-command/src/viewers/table.rs @@ -4,7 +4,7 @@ use lscolors::{LsColors, Style}; use nu_color_config::{color_from_hex, StyleComputer, TextStyle}; -use nu_engine::{command_prelude::*, env::get_config, env_to_string}; +use nu_engine::{command_prelude::*, env_to_string}; use nu_pretty_hex::HexConfig; use nu_protocol::{ ByteStream, Config, DataSource, ListStream, PipelineMetadata, Signals, TableMode, ValueIterator, @@ -258,7 +258,7 @@ fn parse_table_config( let flatten_separator: Option = call.get_flag(state, stack, "flatten-separator")?; let abbrivation: Option = call .get_flag(state, stack, "abbreviated")? - .or_else(|| get_config(state, stack).table_abbreviation_threshold); + .or_else(|| stack.get_config(state).table_abbreviation_threshold); let table_view = match (expand, collapse) { (false, false) => TableView::General, (_, true) => TableView::Collapsed, @@ -269,7 +269,7 @@ fn parse_table_config( }, }; let theme = - get_theme_flag(call, state, stack)?.unwrap_or_else(|| get_config(state, stack).table_mode); + get_theme_flag(call, state, stack)?.unwrap_or_else(|| stack.get_config(state).table_mode); let index = get_index_flag(call, state, stack)?; let term_width = get_width_param(width_param); @@ -493,7 +493,11 @@ fn handle_record( cfg: TableConfig, mut record: Record, ) -> Result { - let config = get_config(input.engine_state, input.stack); + let config = { + let state = input.engine_state; + let stack: &Stack = input.stack; + stack.get_config(state) + }; let span = input.data.span().unwrap_or(input.call.head); let styles = &StyleComputer::from_config(input.engine_state, input.stack); @@ -608,7 +612,11 @@ fn handle_row_stream( data_source: DataSource::Ls, .. }) => { - let config = get_config(input.engine_state, input.stack); + let config = { + let state = input.engine_state; + let stack: &Stack = input.stack; + stack.get_config(state) + }; let ls_colors_env_str = match input.stack.get_env_var(input.engine_state, "LS_COLORS") { Some(v) => Some(env_to_string( "LS_COLORS", @@ -758,7 +766,11 @@ impl PagingTableCreator { return Ok(None); } - let cfg = get_config(&self.engine_state, &self.stack); + let cfg = { + let state = &self.engine_state; + let stack = &self.stack; + stack.get_config(state) + }; let style_comp = StyleComputer::from_config(&self.engine_state, &self.stack); let opts = self.create_table_opts(&cfg, &style_comp); let view = TableView::Expanded { @@ -775,7 +787,11 @@ impl PagingTableCreator { return Ok(None); } - let cfg = get_config(&self.engine_state, &self.stack); + let cfg = { + let state = &self.engine_state; + let stack = &self.stack; + stack.get_config(state) + }; let style_comp = StyleComputer::from_config(&self.engine_state, &self.stack); let opts = self.create_table_opts(&cfg, &style_comp); @@ -783,7 +799,11 @@ impl PagingTableCreator { } fn build_general(&mut self, batch: Vec) -> StringResult { - let cfg = get_config(&self.engine_state, &self.stack); + let cfg = { + let state = &self.engine_state; + let stack = &self.stack; + stack.get_config(state) + }; let style_comp = StyleComputer::from_config(&self.engine_state, &self.stack); let opts = self.create_table_opts(&cfg, &style_comp); @@ -872,7 +892,11 @@ impl Iterator for PagingTableCreator { self.row_offset += batch_size; - let config = get_config(&self.engine_state, &self.stack); + let config = { + let state = &self.engine_state; + let stack = &self.stack; + stack.get_config(state) + }; convert_table_to_output( table, &config, @@ -1049,7 +1073,7 @@ fn create_empty_placeholder( engine_state: &EngineState, stack: &Stack, ) -> String { - let config = get_config(engine_state, stack); + let config = stack.get_config(engine_state); if !config.table_show_empty { return String::new(); } diff --git a/crates/nu-engine/src/documentation.rs b/crates/nu-engine/src/documentation.rs index 7840d03c47..a0dee3d343 100644 --- a/crates/nu-engine/src/documentation.rs +++ b/crates/nu-engine/src/documentation.rs @@ -3,8 +3,8 @@ use nu_protocol::{ ast::{Argument, Call, Expr, Expression, RecordItem}, debugger::WithoutDebug, engine::{Command, EngineState, Stack, UNKNOWN_SPAN_ID}, - record, Category, Example, IntoPipelineData, PipelineData, Signature, Span, SpanId, Spanned, - SyntaxShape, Type, Value, + record, Category, Config, Example, IntoPipelineData, PipelineData, Signature, Span, SpanId, + Spanned, SyntaxShape, Type, Value, }; use std::{collections::HashMap, fmt::Write}; @@ -13,7 +13,7 @@ pub fn get_full_help( engine_state: &EngineState, stack: &mut Stack, ) -> String { - let config = engine_state.get_config(); + let config = stack.get_config(engine_state); let doc_config = DocumentationConfig { no_subcommands: false, no_color: !config.use_ansi_coloring, @@ -70,16 +70,30 @@ fn get_documentation( config: &DocumentationConfig, is_parser_keyword: bool, ) -> String { + let nu_config = stack.get_config(engine_state); + // Create ansi colors //todo make these configurable -- pull from enginestate.config - let help_section_name: String = - get_ansi_color_for_component_or_default(engine_state, "shape_string", "\x1b[32m"); // default: green + let help_section_name: String = get_ansi_color_for_component_or_default( + engine_state, + &nu_config, + "shape_string", + "\x1b[32m", + ); // default: green - let help_subcolor_one: String = - get_ansi_color_for_component_or_default(engine_state, "shape_external", "\x1b[36m"); // default: cyan - // was const bb: &str = "\x1b[1;34m"; // bold blue - let help_subcolor_two: String = - get_ansi_color_for_component_or_default(engine_state, "shape_block", "\x1b[94m"); // default: light blue (nobold, should be bolding the *names*) + let help_subcolor_one: String = get_ansi_color_for_component_or_default( + engine_state, + &nu_config, + "shape_external", + "\x1b[36m", + ); // default: cyan + // was const bb: &str = "\x1b[1;34m"; // bold blue + let help_subcolor_two: String = get_ansi_color_for_component_or_default( + engine_state, + &nu_config, + "shape_block", + "\x1b[94m", + ); // default: light blue (nobold, should be bolding the *names*) const RESET: &str = "\x1b[0m"; // reset @@ -139,13 +153,12 @@ fn get_documentation( } if !sig.named.is_empty() { - long_desc.push_str(&get_flags_section(Some(engine_state), sig, |v| { - nu_highlight_string( - &v.to_parsable_string(", ", &engine_state.config), - engine_state, - stack, - ) - })) + long_desc.push_str(&get_flags_section( + Some(engine_state), + Some(&nu_config), + sig, + |v| nu_highlight_string(&v.to_parsable_string(", ", &nu_config), engine_state, stack), + )) } if !sig.required_positional.is_empty() @@ -189,7 +202,7 @@ fn get_documentation( format!( " (optional, default: {})", nu_highlight_string( - &value.to_parsable_string(", ", &engine_state.config), + &value.to_parsable_string(", ", &nu_config), engine_state, stack ) @@ -339,7 +352,7 @@ fn get_documentation( let _ = writeln!( long_desc, " {}", - item.to_expanded_string("", engine_state.get_config()) + item.to_expanded_string("", &nu_config) .replace('\n', "\n ") .trim() ); @@ -358,15 +371,16 @@ fn get_documentation( fn get_ansi_color_for_component_or_default( engine_state: &EngineState, + nu_config: &Config, theme_component: &str, default: &str, ) -> String { - if let Some(color) = &engine_state.get_config().color_config.get(theme_component) { + if let Some(color) = &nu_config.color_config.get(theme_component) { let caller_stack = &mut Stack::new().capture(); let span = Span::unknown(); let span_id = UNKNOWN_SPAN_ID; - let argument_opt = get_argument_for_color_value(engine_state, color, span, span_id); + let argument_opt = get_argument_for_color_value(nu_config, color, span, span_id); // Call ansi command using argument if let Some(argument) = argument_opt { @@ -394,8 +408,8 @@ fn get_ansi_color_for_component_or_default( } fn get_argument_for_color_value( - engine_state: &EngineState, - color: &&Value, + nu_config: &Config, + color: &Value, span: Span, span_id: SpanId, ) -> Option { @@ -412,9 +426,7 @@ fn get_argument_for_color_value( Type::String, ), Expression::new_existing( - Expr::String( - v.clone().to_expanded_string("", engine_state.get_config()), - ), + Expr::String(v.clone().to_expanded_string("", nu_config)), span, span_id, Type::String, @@ -456,6 +468,7 @@ pub fn document_shape(shape: SyntaxShape) -> SyntaxShape { pub fn get_flags_section( engine_state_opt: Option<&EngineState>, + nu_config_opt: Option<&Config>, signature: &Signature, mut value_formatter: F, // format default Value (because some calls cant access config or nu-highlight) ) -> String @@ -470,13 +483,26 @@ where // Sometimes we want to get the flags without engine_state // For example, in nu-plugin. In that case, we fall back on default values if let Some(engine_state) = engine_state_opt { - help_section_name = - get_ansi_color_for_component_or_default(engine_state, "shape_string", "\x1b[32m"); // default: green - help_subcolor_one = - get_ansi_color_for_component_or_default(engine_state, "shape_external", "\x1b[36m"); // default: cyan - // was const bb: &str = "\x1b[1;34m"; // bold blue - help_subcolor_two = - get_ansi_color_for_component_or_default(engine_state, "shape_block", "\x1b[94m"); + let nu_config = nu_config_opt.unwrap_or_else(|| engine_state.get_config()); + help_section_name = get_ansi_color_for_component_or_default( + engine_state, + nu_config, + "shape_string", + "\x1b[32m", + ); // default: green + help_subcolor_one = get_ansi_color_for_component_or_default( + engine_state, + nu_config, + "shape_external", + "\x1b[36m", + ); // default: cyan + // was const bb: &str = "\x1b[1;34m"; // bold blue + help_subcolor_two = get_ansi_color_for_component_or_default( + engine_state, + nu_config, + "shape_block", + "\x1b[94m", + ); // default: light blue (nobold, should be bolding the *names*) } else { help_section_name = "\x1b[32m".to_string(); diff --git a/crates/nu-engine/src/env.rs b/crates/nu-engine/src/env.rs index ab3a4bc50c..f93e78af88 100644 --- a/crates/nu-engine/src/env.rs +++ b/crates/nu-engine/src/env.rs @@ -3,7 +3,7 @@ use nu_path::canonicalize_with; use nu_protocol::{ ast::Expr, engine::{Call, EngineState, Stack, StateWorkingSet}, - Config, ShellError, Span, Value, VarId, + ShellError, Span, Value, VarId, }; use std::{ collections::HashMap, @@ -323,18 +323,6 @@ pub fn find_in_dirs_env( Ok(check_dir(lib_dirs).or_else(|| check_dir(lib_dirs_fallback))) } -/// Get config -/// -/// This combines config stored in permanent state and any runtime updates to the environment. This -/// is the canonical way to fetch config at runtime when you have Stack available. -pub fn get_config(engine_state: &EngineState, stack: &Stack) -> Config { - if let Some(mut config_record) = stack.get_env_var(engine_state, "config") { - config_record.parse_as_config(engine_state.get_config()).0 - } else { - engine_state.get_config().clone() - } -} - fn get_converted_value( engine_state: &EngineState, stack: &Stack, diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index 6e171eb46c..42d0aed288 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -1,6 +1,6 @@ use crate::eval_ir_block; #[allow(deprecated)] -use crate::{current_dir, get_config, get_full_help}; +use crate::{current_dir, get_full_help}; use nu_path::{expand_path_with, AbsolutePathBuf}; use nu_protocol::{ ast::{ @@ -14,7 +14,7 @@ use nu_protocol::{ Spanned, Type, Value, VarId, ENV_VARIABLE_ID, }; use nu_utils::IgnoreCaseExt; -use std::{borrow::Cow, fs::OpenOptions, path::PathBuf}; +use std::{fs::OpenOptions, path::PathBuf, sync::Arc}; pub fn eval_call( engine_state: &EngineState, @@ -196,6 +196,9 @@ pub fn redirect_env(engine_state: &EngineState, caller_stack: &mut Stack, callee for (var, value) in callee_stack.get_stack_env_vars() { caller_stack.add_env_var(var, value); } + + // set config to callee config, to capture any updates to that + caller_stack.config = callee_stack.config.clone(); } fn eval_external( @@ -652,8 +655,8 @@ impl Eval for EvalRuntime { type MutState = Stack; - fn get_config<'a>(engine_state: Self::State<'a>, stack: &mut Stack) -> Cow<'a, Config> { - Cow::Owned(get_config(engine_state, stack)) + fn get_config(engine_state: Self::State<'_>, stack: &mut Stack) -> Arc { + stack.get_config(engine_state) } fn eval_filepath( @@ -843,7 +846,14 @@ impl Eval for EvalRuntime { }); } + let is_config = original_key == "config"; + stack.add_env_var(original_key, value); + + // Trigger the update to config, if we modified that. + if is_config { + stack.update_config(engine_state)?; + } } else { lhs.upsert_data_at_cell_path(&cell_path.tail, rhs)?; stack.add_var(*var_id, lhs); diff --git a/crates/nu-explore/src/commands/expand.rs b/crates/nu-explore/src/commands/expand.rs index b748ce2b89..38f92c0b50 100644 --- a/crates/nu-explore/src/commands/expand.rs +++ b/crates/nu-explore/src/commands/expand.rs @@ -64,8 +64,8 @@ fn convert_value_to_string( let has_no_head = cols.is_empty() || (cols.len() == 1 && cols[0].is_empty()); let has_single_value = vals.len() == 1 && vals[0].len() == 1; if !has_no_head && has_single_value { - let config = engine_state.get_config(); - Ok(vals[0][0].to_abbreviated_string(config)) + let config = stack.get_config(engine_state); + Ok(vals[0][0].to_abbreviated_string(&config)) } else { let config = engine_state.get_config(); let style_computer = StyleComputer::from_config(engine_state, stack); diff --git a/crates/nu-explore/src/explore.rs b/crates/nu-explore/src/explore.rs index 8d59591beb..28a3f04a30 100644 --- a/crates/nu-explore/src/explore.rs +++ b/crates/nu-explore/src/explore.rs @@ -63,10 +63,10 @@ impl Command for Explore { let tail: bool = call.has_flag(engine_state, stack, "tail")?; let peek_value: bool = call.has_flag(engine_state, stack, "peek")?; - let nu_config = engine_state.get_config(); + let nu_config = stack.get_config(engine_state); let style_computer = StyleComputer::from_config(engine_state, stack); - let mut explore_config = ExploreConfig::from_nu_config(nu_config); + let mut explore_config = ExploreConfig::from_nu_config(&nu_config); explore_config.table.show_header = show_head; explore_config.table.show_index = show_index; explore_config.table.separator_style = lookup_color(&style_computer, "separator"); @@ -74,7 +74,7 @@ impl Command for Explore { let lscolors = create_lscolors(engine_state, stack); let config = PagerConfig::new( - nu_config, + &nu_config, &explore_config, &style_computer, &lscolors, diff --git a/crates/nu-plugin-engine/Cargo.toml b/crates/nu-plugin-engine/Cargo.toml index f86c1ae703..a6dfa471b8 100644 --- a/crates/nu-plugin-engine/Cargo.toml +++ b/crates/nu-plugin-engine/Cargo.toml @@ -16,6 +16,7 @@ nu-protocol = { path = "../nu-protocol", version = "0.95.1" } nu-system = { path = "../nu-system", version = "0.95.1" } nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.95.1" } nu-plugin-core = { path = "../nu-plugin-core", version = "0.95.1", default-features = false } +nu-utils = { path = "../nu-utils", version = "0.95.1" } serde = { workspace = true } log = { workspace = true } @@ -31,4 +32,4 @@ local-socket = ["nu-plugin-core/local-socket"] windows = { workspace = true, features = [ # For setting process creation flags "Win32_System_Threading", -] } \ No newline at end of file +] } diff --git a/crates/nu-plugin-engine/src/context.rs b/crates/nu-plugin-engine/src/context.rs index 0df533a11e..aa504a246a 100644 --- a/crates/nu-plugin-engine/src/context.rs +++ b/crates/nu-plugin-engine/src/context.rs @@ -20,7 +20,7 @@ pub trait PluginExecutionContext: Send + Sync { /// The pipeline externals state, for tracking the foreground process group, if present fn pipeline_externals_state(&self) -> Option<&Arc<(AtomicU32, AtomicU32)>>; /// Get engine configuration - fn get_config(&self) -> Result; + fn get_config(&self) -> Result, ShellError>; /// Get plugin configuration fn get_plugin_config(&self) -> Result, ShellError>; /// Get an environment variable from `$env` @@ -85,8 +85,8 @@ impl<'a> PluginExecutionContext for PluginExecutionCommandContext<'a> { Some(&self.engine_state.pipeline_externals_state) } - fn get_config(&self) -> Result { - Ok(nu_engine::get_config(&self.engine_state, &self.stack)) + fn get_config(&self) -> Result, ShellError> { + Ok(self.stack.get_config(&self.engine_state)) } fn get_plugin_config(&self) -> Result, ShellError> { @@ -239,7 +239,7 @@ impl PluginExecutionContext for PluginExecutionBogusContext { None } - fn get_config(&self) -> Result { + fn get_config(&self) -> Result, ShellError> { Err(ShellError::NushellFailed { msg: "get_config not implemented on bogus".into(), }) diff --git a/crates/nu-plugin-engine/src/declaration.rs b/crates/nu-plugin-engine/src/declaration.rs index d48fa39b85..745ba9a998 100644 --- a/crates/nu-plugin-engine/src/declaration.rs +++ b/crates/nu-plugin-engine/src/declaration.rs @@ -76,7 +76,7 @@ impl Command for PluginDeclaration { EvaluatedCall::try_from_call(call, engine_state, stack, eval_expression)?; // Get the engine config - let engine_config = nu_engine::get_config(engine_state, stack); + let engine_config = stack.get_config(engine_state); // Get, or start, the plugin. let plugin = self diff --git a/crates/nu-plugin-engine/src/interface/mod.rs b/crates/nu-plugin-engine/src/interface/mod.rs index 79ab7f7720..d1b722dedf 100644 --- a/crates/nu-plugin-engine/src/interface/mod.rs +++ b/crates/nu-plugin-engine/src/interface/mod.rs @@ -14,6 +14,7 @@ use nu_protocol::{ ast::Operator, CustomValue, IntoSpanned, PipelineData, PluginMetadata, PluginSignature, ShellError, Signals, Span, Spanned, Value, }; +use nu_utils::SharedCow; use std::{ collections::{btree_map, BTreeMap}, sync::{mpsc, Arc, OnceLock}, @@ -1260,7 +1261,7 @@ pub(crate) fn handle_engine_call( match call { EngineCall::GetConfig => { - let config = Box::new(context.get_config()?); + let config = SharedCow::from(context.get_config()?); Ok(EngineCallResponse::Config(config)) } EngineCall::GetPluginConfig => { diff --git a/crates/nu-plugin-protocol/src/lib.rs b/crates/nu-plugin-protocol/src/lib.rs index 2f582c3009..739366d910 100644 --- a/crates/nu-plugin-protocol/src/lib.rs +++ b/crates/nu-plugin-protocol/src/lib.rs @@ -25,6 +25,7 @@ use nu_protocol::{ ast::Operator, engine::Closure, ByteStreamType, Config, LabeledError, PipelineData, PluginMetadata, PluginSignature, ShellError, Span, Spanned, Value, }; +use nu_utils::SharedCow; use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -553,7 +554,7 @@ impl EngineCall { pub enum EngineCallResponse { Error(ShellError), PipelineData(D), - Config(Box), + Config(SharedCow), ValueMap(HashMap), } diff --git a/crates/nu-plugin/Cargo.toml b/crates/nu-plugin/Cargo.toml index a3b4455d15..d46a3cc9cb 100644 --- a/crates/nu-plugin/Cargo.toml +++ b/crates/nu-plugin/Cargo.toml @@ -15,6 +15,7 @@ nu-engine = { path = "../nu-engine", version = "0.95.1" } nu-protocol = { path = "../nu-protocol", version = "0.95.1" } nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.95.1" } nu-plugin-core = { path = "../nu-plugin-core", version = "0.95.1", default-features = false } +nu-utils = { path = "../nu-utils", version = "0.95.1" } log = { workspace = true } thiserror = "1.0" @@ -29,4 +30,4 @@ local-socket = ["nu-plugin-core/local-socket"] [target.'cfg(target_family = "unix")'.dependencies] # For setting the process group ID (EnterForeground / LeaveForeground) -nix = { workspace = true, default-features = false, features = ["process"] } \ No newline at end of file +nix = { workspace = true, default-features = false, features = ["process"] } diff --git a/crates/nu-plugin/src/plugin/interface/mod.rs b/crates/nu-plugin/src/plugin/interface/mod.rs index dcb4119dba..60c5964f26 100644 --- a/crates/nu-plugin/src/plugin/interface/mod.rs +++ b/crates/nu-plugin/src/plugin/interface/mod.rs @@ -14,6 +14,7 @@ use nu_protocol::{ engine::Closure, Config, LabeledError, PipelineData, PluginMetadata, PluginSignature, ShellError, Signals, Span, Spanned, Value, }; +use nu_utils::SharedCow; use std::{ collections::{btree_map, BTreeMap, HashMap}, sync::{mpsc, Arc}, @@ -525,9 +526,9 @@ impl EngineInterface { /// # Ok(()) /// # } /// ``` - pub fn get_config(&self) -> Result, ShellError> { + pub fn get_config(&self) -> Result, ShellError> { match self.engine_call(EngineCall::GetConfig)? { - EngineCallResponse::Config(config) => Ok(config), + EngineCallResponse::Config(config) => Ok(SharedCow::into_arc(config)), EngineCallResponse::Error(err) => Err(err), _ => Err(ShellError::PluginFailedToDecode { msg: "Received unexpected response for EngineCall::GetConfig".into(), diff --git a/crates/nu-plugin/src/plugin/mod.rs b/crates/nu-plugin/src/plugin/mod.rs index 2bf7f9fc07..d809318d68 100644 --- a/crates/nu-plugin/src/plugin/mod.rs +++ b/crates/nu-plugin/src/plugin/mod.rs @@ -670,7 +670,7 @@ fn print_help(plugin: &impl Plugin, encoder: impl PluginEncoder) { } }) .and_then(|_| { - let flags = get_flags_section(None, &signature, |v| format!("{:#?}", v)); + let flags = get_flags_section(None, None, &signature, |v| format!("{:#?}", v)); write!(help, "{flags}") }) .and_then(|_| writeln!(help, "\nParameters:")) diff --git a/crates/nu-protocol/src/engine/engine_state.rs b/crates/nu-protocol/src/engine/engine_state.rs index f48fde663b..fce24cebbd 100644 --- a/crates/nu-protocol/src/engine/engine_state.rs +++ b/crates/nu-protocol/src/engine/engine_state.rs @@ -301,28 +301,11 @@ impl EngineState { stack: &mut Stack, cwd: impl AsRef, ) -> Result<(), ShellError> { - let mut config_updated = false; - for mut scope in stack.env_vars.drain(..) { for (overlay_name, mut env) in Arc::make_mut(&mut scope).drain() { if let Some(env_vars) = Arc::make_mut(&mut self.env_vars).get_mut(&overlay_name) { // Updating existing overlay - for (k, v) in env.drain() { - if k == "config" { - // Don't insert the record as the "config" env var as-is. - // Instead, mutate a clone of it with into_config(), and put THAT in env_vars. - let mut new_record = v.clone(); - let (config, error) = new_record.parse_as_config(&self.config); - self.config = Arc::new(config); - config_updated = true; - env_vars.insert(k, new_record); - if let Some(e) = error { - return Err(e); - } - } else { - env_vars.insert(k, v); - } - } + env_vars.extend(env.drain()); } else { // Pushing a new overlay Arc::make_mut(&mut self.env_vars).insert(overlay_name, env); @@ -333,7 +316,10 @@ impl EngineState { // TODO: better error std::env::set_current_dir(cwd)?; - if config_updated { + if let Some(config) = stack.config.take() { + // If config was updated in the stack, replace it. + self.config = config; + // Make plugin GC config changes take effect immediately. #[cfg(feature = "plugin")] self.update_plugin_gc_configs(&self.config.plugin_gc); @@ -738,18 +724,24 @@ impl EngineState { &[0u8; 0] } - pub fn get_config(&self) -> &Config { + /// Get the global config from the engine state. + /// + /// Use [`Stack::get_config()`] instead whenever the `Stack` is available, as it takes into + /// account local changes to `$env.config`. + pub fn get_config(&self) -> &Arc { &self.config } - pub fn set_config(&mut self, conf: Config) { + pub fn set_config(&mut self, conf: impl Into>) { + let conf = conf.into(); + #[cfg(feature = "plugin")] if conf.plugin_gc != self.config.plugin_gc { // Make plugin GC config changes take effect immediately. self.update_plugin_gc_configs(&conf.plugin_gc); } - self.config = Arc::new(conf); + self.config = conf; } /// Fetch the configuration for a plugin @@ -1137,7 +1129,7 @@ mod engine_state_tests { let mut plugins = HashMap::new(); plugins.insert("example".into(), Value::string("value", Span::test_data())); - let mut config = engine_state.get_config().clone(); + let mut config = Config::clone(engine_state.get_config()); config.plugins = plugins; engine_state.set_config(config); diff --git a/crates/nu-protocol/src/engine/stack.rs b/crates/nu-protocol/src/engine/stack.rs index e963227ca8..fbfb586762 100644 --- a/crates/nu-protocol/src/engine/stack.rs +++ b/crates/nu-protocol/src/engine/stack.rs @@ -3,7 +3,7 @@ use crate::{ ArgumentStack, EngineState, ErrorHandlerStack, Redirection, StackCallArgGuard, StackCaptureGuard, StackIoGuard, StackOutDest, DEFAULT_OVERLAY_NAME, }, - OutDest, ShellError, Span, Value, VarId, ENV_VARIABLE_ID, NU_VARIABLE_ID, + Config, OutDest, ShellError, Span, Value, VarId, ENV_VARIABLE_ID, NU_VARIABLE_ID, }; use std::{ collections::{HashMap, HashSet}, @@ -51,6 +51,8 @@ pub struct Stack { pub parent_stack: Option>, /// Variables that have been deleted (this is used to hide values from parent stack lookups) pub parent_deletions: Vec, + /// Locally updated config. Use [`.get_config()`] to access correctly. + pub config: Option>, pub(crate) out_dest: StackOutDest, } @@ -80,6 +82,7 @@ impl Stack { recursion_count: 0, parent_stack: None, parent_deletions: vec![], + config: None, out_dest: StackOutDest::new(), } } @@ -100,6 +103,7 @@ impl Stack { recursion_count: parent.recursion_count, vars: vec![], parent_deletions: vec![], + config: parent.config.clone(), out_dest: parent.out_dest.clone(), parent_stack: Some(parent), } @@ -126,6 +130,7 @@ impl Stack { unique_stack.env_vars = child.env_vars; unique_stack.env_hidden = child.env_hidden; unique_stack.active_overlays = child.active_overlays; + unique_stack.config = child.config; unique_stack } @@ -192,6 +197,36 @@ impl Stack { } } + /// Get the local config if set, otherwise the config from the engine state. + /// + /// This is the canonical way to get [`Config`] when [`Stack`] is available. + pub fn get_config(&self, engine_state: &EngineState) -> Arc { + self.config + .clone() + .unwrap_or_else(|| engine_state.config.clone()) + } + + /// Update the local config with the config stored in the `config` environment variable. Run + /// this after assigning to `$env.config`. + /// + /// The config will be updated with successfully parsed values even if an error occurs. + pub fn update_config(&mut self, engine_state: &EngineState) -> Result<(), ShellError> { + if let Some(mut config) = self.get_env_var(engine_state, "config") { + let existing_config = self.get_config(engine_state); + let (new_config, error) = config.parse_as_config(&existing_config); + self.config = Some(new_config.into()); + // The config value is modified by the update, so we should add it again + self.add_env_var("config".into(), config); + match error { + None => Ok(()), + Some(err) => Err(err), + } + } else { + self.config = None; + Ok(()) + } + } + pub fn add_var(&mut self, var_id: VarId, value: Value) { //self.vars.insert(var_id, value); for (id, val) in &mut self.vars { @@ -272,6 +307,7 @@ impl Stack { recursion_count: self.recursion_count, parent_stack: None, parent_deletions: vec![], + config: self.config.clone(), out_dest: self.out_dest.clone(), } } @@ -305,6 +341,7 @@ impl Stack { recursion_count: self.recursion_count, parent_stack: None, parent_deletions: vec![], + config: self.config.clone(), out_dest: self.out_dest.clone(), } } diff --git a/crates/nu-protocol/src/engine/state_working_set.rs b/crates/nu-protocol/src/engine/state_working_set.rs index 8c3968a824..af1085c1ee 100644 --- a/crates/nu-protocol/src/engine/state_working_set.rs +++ b/crates/nu-protocol/src/engine/state_working_set.rs @@ -627,9 +627,9 @@ impl<'a> StateWorkingSet<'a> { /// Returns a reference to the config stored at permanent state /// - /// At runtime, you most likely want to call nu_engine::env::get_config because this method - /// does not capture environment updates during runtime. - pub fn get_config(&self) -> &Config { + /// At runtime, you most likely want to call [`Stack::get_config()`][super::Stack::get_config()] + /// because this method does not capture environment updates during runtime. + pub fn get_config(&self) -> &Arc { &self.permanent_state.config } diff --git a/crates/nu-protocol/src/eval_base.rs b/crates/nu-protocol/src/eval_base.rs index 9c5878ee87..f49d235482 100644 --- a/crates/nu-protocol/src/eval_base.rs +++ b/crates/nu-protocol/src/eval_base.rs @@ -7,7 +7,7 @@ use crate::{ debugger::DebugContext, Config, GetSpan, Range, Record, ShellError, Span, Value, VarId, ENV_VARIABLE_ID, }; -use std::{borrow::Cow, collections::HashMap}; +use std::{collections::HashMap, sync::Arc}; /// To share implementations for regular eval and const eval pub trait Eval { @@ -316,7 +316,7 @@ pub trait Eval { } } - fn get_config<'a>(state: Self::State<'a>, mut_state: &mut Self::MutState) -> Cow<'a, Config>; + fn get_config(state: Self::State<'_>, mut_state: &mut Self::MutState) -> Arc; fn eval_filepath( state: Self::State<'_>, diff --git a/crates/nu-protocol/src/eval_const.rs b/crates/nu-protocol/src/eval_const.rs index da7570b43c..cba2392338 100644 --- a/crates/nu-protocol/src/eval_const.rs +++ b/crates/nu-protocol/src/eval_const.rs @@ -11,8 +11,8 @@ use crate::{ }; use nu_system::os_info::{get_kernel_version, get_os_arch, get_os_family, get_os_name}; use std::{ - borrow::Cow, path::{Path, PathBuf}, + sync::Arc, }; /// Create a Value for `$nu`. @@ -364,8 +364,8 @@ impl Eval for EvalConst { type MutState = (); - fn get_config<'a>(state: Self::State<'a>, _: &mut ()) -> Cow<'a, Config> { - Cow::Borrowed(state.get_config()) + fn get_config(state: Self::State<'_>, _: &mut ()) -> Arc { + state.get_config().clone() } fn eval_filepath( From 9fec5883c0650836a625b69538dec92f14d450ec Mon Sep 17 00:00:00 2001 From: Stefan Holderbach Date: Thu, 11 Jul 2024 15:18:58 +0200 Subject: [PATCH 029/126] Group dependabot bumps for uutils/coreutils (#13346) Similar to #13084 for polars. This familiy of crates has shared versioning and tends to at least depend on the same `uucore` crate. --- .github/dependabot.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 2193598686..07e0b2b891 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -26,6 +26,13 @@ updates: patterns: - "polars" - "polars-*" + # uutils/coreutils also versions all their workspace crates the same at the moment + # Most of them have bleeding edge version requirements (some not) + # see: https://github.com/uutils/coreutils/blob/main/Cargo.toml + uutils: + patterns: + - "uucore" + - "uu_*" - package-ecosystem: "github-actions" directory: "/" schedule: From a7a5315638cdf34a0b0b5f27d7d95f492fa0b9d1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 11 Jul 2024 13:29:34 +0000 Subject: [PATCH 030/126] Bump crate-ci/typos from 1.23.1 to 1.23.2 (#13347) --- .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 0f1e4cb5d9..c7c153983d 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.23.1 + uses: crate-ci/typos@v1.23.2 From acd4cb83e8c9d89eefd63f82b9c0d7ce1e3c9214 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 11 Jul 2024 14:08:42 +0000 Subject: [PATCH 031/126] Bump ureq from 2.9.7 to 2.10.0 (#13348) --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c7608643dc..c8bfe4ab4e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6470,9 +6470,9 @@ checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" [[package]] name = "ureq" -version = "2.9.7" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d11a831e3c0b56e438a28308e7c810799e3c118417f342d30ecec080105395cd" +checksum = "72139d247e5f97a3eff96229a7ae85ead5328a39efe76f8bf5a06313d505b6ea" dependencies = [ "base64 0.22.1", "encoding_rs", diff --git a/Cargo.toml b/Cargo.toml index eb8a92c630..a0b3ef27f5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -164,7 +164,7 @@ trash = "3.3" umask = "2.1" unicode-segmentation = "1.11" unicode-width = "0.1" -ureq = { version = "2.9", default-features = false } +ureq = { version = "2.10", default-features = false } url = "2.2" uu_cp = "0.0.27" uu_mkdir = "0.0.27" From ccd0160c328941e895be3f01c8d06768065312b3 Mon Sep 17 00:00:00 2001 From: Devyn Cairns Date: Thu, 11 Jul 2024 10:49:46 -0700 Subject: [PATCH 032/126] Make the `store-env` IR instruction also update config (#13351) # Description Follow up fix to #13332, so that changes to config when running under IR actually happen as well. Since I merged them around the same time, I forgot about this. --- crates/nu-engine/src/eval_ir.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/nu-engine/src/eval_ir.rs b/crates/nu-engine/src/eval_ir.rs index e2ec7ccdac..56bbccee25 100644 --- a/crates/nu-engine/src/eval_ir.rs +++ b/crates/nu-engine/src/eval_ir.rs @@ -367,7 +367,11 @@ fn eval_instruction( let key = get_env_var_name_case_insensitive(ctx, key); if !is_automatic_env_var(&key) { + let is_config = key == "config"; ctx.stack.add_env_var(key.into_owned(), value); + if is_config { + ctx.stack.update_config(ctx.engine_state)?; + } Ok(Continue) } else { Err(ShellError::AutomaticEnvVarSetManually { From 4bd87d04962989f47636be9b8279b98ac85fcd5e Mon Sep 17 00:00:00 2001 From: Maxim Zhiburt Date: Thu, 11 Jul 2024 23:13:16 +0300 Subject: [PATCH 033/126] Fix unused space when truncation is used and header on border is configured (#13353) Somehow this logic was missed on my end. ( I mean I was not even thinking about it in original patch :smile: ) Please recheck Added a regression test too. close #13336 cc: @fdncred --- crates/nu-command/tests/commands/table.rs | 6 ++ crates/nu-table/src/table.rs | 89 ++++++++++++++++++----- 2 files changed, 76 insertions(+), 19 deletions(-) diff --git a/crates/nu-command/tests/commands/table.rs b/crates/nu-command/tests/commands/table.rs index 16d5eb54ab..a4869f6bfa 100644 --- a/crates/nu-command/tests/commands/table.rs +++ b/crates/nu-command/tests/commands/table.rs @@ -2895,3 +2895,9 @@ fn table_kv_header_on_separator_trim_algorithm() { let actual = nu!("$env.config.table.header_on_separator = true; {key1: '111111111111111111111111111111111111111111111111111111111111'} | table --width=60 --theme basic"); assert_eq!(actual.out, "+------+---------------------------------------------------+| key1 | 1111111111111111111111111111111111111111111111111 || | 11111111111 |+------+---------------------------------------------------+"); } + +#[test] +fn table_general_header_on_separator_trim_algorithm() { + let actual = nu!("$env.config.table.header_on_separator = true; [[a b]; ['11111111111111111111111111111111111111' 2] ] | table --width=20 --theme basic"); + assert_eq!(actual.out, "+-#-+----a-----+-b-+| 0 | 11111111 | 2 || | 11111111 | || | 11111111 | || | 11111111 | || | 111111 | |+---+----------+---+"); +} diff --git a/crates/nu-table/src/table.rs b/crates/nu-table/src/table.rs index d2cf8f4d63..0dbf1bda7e 100644 --- a/crates/nu-table/src/table.rs +++ b/crates/nu-table/src/table.rs @@ -255,7 +255,8 @@ fn draw_table( align_table(&mut table, alignments, with_index, with_header, with_footer); colorize_table(&mut table, styles, with_index, with_header, with_footer); - let width_ctrl = TableWidthCtrl::new(widths, cfg, termwidth); + let pad = indent.0 + indent.1; + let width_ctrl = TableWidthCtrl::new(widths, cfg, termwidth, pad); if with_header && border_header { set_border_head(&mut table, with_footer, width_ctrl); @@ -336,12 +337,18 @@ fn table_to_string(table: Table, termwidth: usize) -> Option { struct TableWidthCtrl { width: Vec, cfg: NuTableConfig, - max: usize, + width_max: usize, + pad: usize, } impl TableWidthCtrl { - fn new(width: Vec, cfg: NuTableConfig, max: usize) -> Self { - Self { width, cfg, max } + fn new(width: Vec, cfg: NuTableConfig, max: usize, pad: usize) -> Self { + Self { + width, + cfg, + width_max: max, + pad, + } } } @@ -354,19 +361,20 @@ impl TableOption, ColoredConfig> for ) { let total_width = get_total_width2(&self.width, cfg); - if total_width > self.max { + if total_width > self.width_max { let has_header = self.cfg.with_header && rec.count_rows() > 1; let trim_as_head = has_header && self.cfg.header_on_border; - TableTrim { - max: self.max, - strategy: self.cfg.trim, - width: self.width, + TableTrim::new( + self.width, + self.width_max, + self.cfg.trim, trim_as_head, - } + self.pad, + ) .change(rec, cfg, dim); - } else if self.cfg.expand && self.max > total_width { - Settings::new(SetDimensions(self.width), Width::increase(self.max)) + } else if self.cfg.expand && self.width_max > total_width { + Settings::new(SetDimensions(self.width), Width::increase(self.width_max)) .change(rec, cfg, dim) } else { SetDimensions(self.width).change(rec, cfg, dim); @@ -376,9 +384,28 @@ impl TableOption, ColoredConfig> for struct TableTrim { width: Vec, + width_max: usize, strategy: TrimStrategy, - max: usize, trim_as_head: bool, + pad: usize, +} + +impl TableTrim { + fn new( + width: Vec, + width_max: usize, + strategy: TrimStrategy, + trim_as_head: bool, + pad: usize, + ) -> Self { + Self { + width, + strategy, + pad, + width_max, + trim_as_head, + } + } } impl TableOption, ColoredConfig> for TableTrim { @@ -395,13 +422,37 @@ impl TableOption, ColoredConfig> for return; } + // even though it's safe to trim columns by header there might be left unused space + // so we do use it if possible prioritizing left columns + let headers = recs[0].to_owned(); - for (i, head) in headers.into_iter().enumerate() { - let head_width = CellInfo::width(&head); + let headers_widths = headers + .iter() + .map(CellInfo::width) + .map(|v| v + self.pad) + .collect::>(); + + let min_width_use = get_total_width2(&headers_widths, cfg); + + let mut free_width = self.width_max.saturating_sub(min_width_use); + + for (i, head_width) in headers_widths.into_iter().enumerate() { + let head_width = head_width - self.pad; + let column_width = self.width[i] - self.pad; // safe to assume width is bigger then paddding + + let mut use_width = head_width; + if free_width > 0 { + // it's safe to assume that column_width is always bigger or equal to head_width + debug_assert!(column_width >= head_width); + + let additional_width = min(free_width, column_width - head_width); + free_width -= additional_width; + use_width += additional_width; + } match &self.strategy { TrimStrategy::Wrap { try_to_keep_words } => { - let mut wrap = Width::wrap(head_width); + let mut wrap = Width::wrap(use_width); if *try_to_keep_words { wrap = wrap.keep_words(); } @@ -411,7 +462,7 @@ impl TableOption, ColoredConfig> for .change(recs, cfg, dims); } TrimStrategy::Truncate { suffix } => { - let mut truncate = Width::truncate(self.max); + let mut truncate = Width::truncate(use_width); if let Some(suffix) = suffix { truncate = truncate.suffix(suffix).suffix_try_color(true); } @@ -428,7 +479,7 @@ impl TableOption, ColoredConfig> for match self.strategy { TrimStrategy::Wrap { try_to_keep_words } => { - let mut wrap = Width::wrap(self.max).priority::(); + let mut wrap = Width::wrap(self.width_max).priority::(); if try_to_keep_words { wrap = wrap.keep_words(); } @@ -436,7 +487,7 @@ impl TableOption, ColoredConfig> for Settings::new(SetDimensions(self.width), wrap).change(recs, cfg, dims); } TrimStrategy::Truncate { suffix } => { - let mut truncate = Width::truncate(self.max).priority::(); + let mut truncate = Width::truncate(self.width_max).priority::(); if let Some(suffix) = suffix { truncate = truncate.suffix(suffix).suffix_try_color(true); } From d56457d63e7d053e93175ca352f18d4e6eec7301 Mon Sep 17 00:00:00 2001 From: Ian Manske Date: Fri, 12 Jul 2024 02:43:10 +0000 Subject: [PATCH 034/126] Path migration part 2: `nu-test-support` (#13329) # Description Part 2 of replacing `std::path` types with `nu_path` types added in #13115. This PR targets `nu-test-support`. --- crates/nu-cli/tests/completions/mod.rs | 2 +- .../support/completions_helpers.rs | 20 +- crates/nu-command/src/system/run_external.rs | 2 +- .../tests/commands/database/into_sqlite.rs | 2 +- crates/nu-command/tests/commands/rm.rs | 5 +- .../nu-command/tests/commands/source_env.rs | 12 +- crates/nu-command/tests/commands/ucp.rs | 8 +- crates/nu-command/tests/commands/use_.rs | 9 +- crates/nu-test-support/src/fs.rs | 227 +++--------------- crates/nu-test-support/src/macros.rs | 25 +- crates/nu-test-support/src/playground.rs | 2 +- .../src/playground/director.rs | 2 +- .../src/playground/nu_process.rs | 19 +- crates/nu-test-support/src/playground/play.rs | 124 ++++------ .../nu-test-support/src/playground/tests.rs | 11 +- tests/plugins/nu_plugin_nu_example.rs | 2 +- tests/plugins/registry_file.rs | 8 +- 17 files changed, 142 insertions(+), 338 deletions(-) diff --git a/crates/nu-cli/tests/completions/mod.rs b/crates/nu-cli/tests/completions/mod.rs index a7f88dc3b0..590979251e 100644 --- a/crates/nu-cli/tests/completions/mod.rs +++ b/crates/nu-cli/tests/completions/mod.rs @@ -721,7 +721,7 @@ fn file_completion_quoted() { "`te#st.txt`".to_string(), "`te'st.txt`".to_string(), "`te(st).txt`".to_string(), - format!("`{}`", folder("test dir".into())), + format!("`{}`", folder("test dir")), ]; match_suggestions(expected_paths, suggestions); diff --git a/crates/nu-cli/tests/completions/support/completions_helpers.rs b/crates/nu-cli/tests/completions/support/completions_helpers.rs index 027ac9d997..623d6fbc5a 100644 --- a/crates/nu-cli/tests/completions/support/completions_helpers.rs +++ b/crates/nu-cli/tests/completions/support/completions_helpers.rs @@ -1,5 +1,6 @@ use nu_engine::eval_block; use nu_parser::parse; +use nu_path::{AbsolutePathBuf, PathBuf}; use nu_protocol::{ debugger::WithoutDebug, engine::{EngineState, Stack, StateWorkingSet}, @@ -7,14 +8,14 @@ use nu_protocol::{ }; use nu_test_support::fs; use reedline::Suggestion; -use std::path::{PathBuf, MAIN_SEPARATOR}; +use std::path::MAIN_SEPARATOR; fn create_default_context() -> EngineState { nu_command::add_shell_command_context(nu_cmd_lang::create_default_context()) } // creates a new engine with the current path into the completions fixtures folder -pub fn new_engine() -> (PathBuf, String, EngineState, Stack) { +pub fn new_engine() -> (AbsolutePathBuf, String, EngineState, Stack) { // Target folder inside assets let dir = fs::fixtures().join("completions"); let dir_str = dir @@ -69,7 +70,7 @@ pub fn new_engine() -> (PathBuf, String, EngineState, Stack) { } // creates a new engine with the current path into the completions fixtures folder -pub fn new_dotnu_engine() -> (PathBuf, String, EngineState, Stack) { +pub fn new_dotnu_engine() -> (AbsolutePathBuf, String, EngineState, Stack) { // Target folder inside assets let dir = fs::fixtures().join("dotnu_completions"); let dir_str = dir @@ -114,7 +115,7 @@ pub fn new_dotnu_engine() -> (PathBuf, String, EngineState, Stack) { (dir, dir_str, engine_state, stack) } -pub fn new_quote_engine() -> (PathBuf, String, EngineState, Stack) { +pub fn new_quote_engine() -> (AbsolutePathBuf, String, EngineState, Stack) { // Target folder inside assets let dir = fs::fixtures().join("quoted_completions"); let dir_str = dir @@ -149,7 +150,7 @@ pub fn new_quote_engine() -> (PathBuf, String, EngineState, Stack) { (dir, dir_str, engine_state, stack) } -pub fn new_partial_engine() -> (PathBuf, String, EngineState, Stack) { +pub fn new_partial_engine() -> (AbsolutePathBuf, String, EngineState, Stack) { // Target folder inside assets let dir = fs::fixtures().join("partial_completions"); let dir_str = dir @@ -205,16 +206,15 @@ pub fn match_suggestions(expected: Vec, suggestions: Vec) { } // append the separator to the converted path -pub fn folder(path: PathBuf) -> String { +pub fn folder(path: impl Into) -> String { let mut converted_path = file(path); converted_path.push(MAIN_SEPARATOR); - converted_path } // convert a given path to string -pub fn file(path: PathBuf) -> String { - path.into_os_string().into_string().unwrap_or_default() +pub fn file(path: impl Into) -> String { + path.into().into_os_string().into_string().unwrap() } // merge_input executes the given input into the engine @@ -223,7 +223,7 @@ pub fn merge_input( input: &[u8], engine_state: &mut EngineState, stack: &mut Stack, - dir: PathBuf, + dir: AbsolutePathBuf, ) -> Result<(), ShellError> { let (block, delta) = { let mut working_set = StateWorkingSet::new(engine_state); diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs index a7ca822011..5e8527b45e 100644 --- a/crates/nu-command/src/system/run_external.rs +++ b/crates/nu-command/src/system/run_external.rs @@ -580,7 +580,7 @@ mod test { Playground::setup("test_expand_glob", |dirs, play| { play.with_files(&[Stub::EmptyFile("a.txt"), Stub::EmptyFile("b.txt")]); - let cwd = dirs.test(); + let cwd = dirs.test().as_std_path(); let actual = expand_glob("*.txt", cwd, Span::unknown(), &Signals::empty()).unwrap(); let expected = &["a.txt", "b.txt"]; diff --git a/crates/nu-command/tests/commands/database/into_sqlite.rs b/crates/nu-command/tests/commands/database/into_sqlite.rs index 595c88dc38..faa45040b3 100644 --- a/crates/nu-command/tests/commands/database/into_sqlite.rs +++ b/crates/nu-command/tests/commands/database/into_sqlite.rs @@ -465,7 +465,7 @@ fn make_sqlite_db(dirs: &Dirs, nu_table: &str) -> PathBuf { ); assert!(nucmd.status.success()); - testdb_path + testdb_path.into() } fn insert_test_rows(dirs: &Dirs, nu_table: &str, sql_query: Option<&str>, expected: Vec) { diff --git a/crates/nu-command/tests/commands/rm.rs b/crates/nu-command/tests/commands/rm.rs index b3a273ea86..e6cbf88cda 100644 --- a/crates/nu-command/tests/commands/rm.rs +++ b/crates/nu-command/tests/commands/rm.rs @@ -1,3 +1,4 @@ +use nu_path::AbsolutePath; use nu_test_support::fs::{files_exist_at, Stub::EmptyFile}; use nu_test_support::nu; use nu_test_support::playground::Playground; @@ -405,10 +406,10 @@ fn removes_file_after_cd() { } struct Cleanup<'a> { - dir_to_clean: &'a Path, + dir_to_clean: &'a AbsolutePath, } -fn set_dir_read_only(directory: &Path, read_only: bool) { +fn set_dir_read_only(directory: &AbsolutePath, read_only: bool) { let mut permissions = fs::metadata(directory).unwrap().permissions(); permissions.set_readonly(read_only); fs::set_permissions(directory, permissions).expect("failed to set directory permissions"); diff --git a/crates/nu-command/tests/commands/source_env.rs b/crates/nu-command/tests/commands/source_env.rs index 9dd0320a45..f9cc3fbc1c 100644 --- a/crates/nu-command/tests/commands/source_env.rs +++ b/crates/nu-command/tests/commands/source_env.rs @@ -1,4 +1,3 @@ -use nu_test_support::fs::AbsolutePath; use nu_test_support::fs::Stub::{FileWithContent, FileWithContentToBeTrimmed}; use nu_test_support::nu; use nu_test_support::pipeline; @@ -8,17 +7,18 @@ use nu_test_support::playground::Playground; #[test] fn sources_also_files_under_custom_lib_dirs_path() { Playground::setup("source_test_1", |dirs, nu| { - let file = AbsolutePath::new(dirs.test().join("config.toml")); - let library_path = AbsolutePath::new(dirs.test().join("lib")); + let file = dirs.test().join("config.toml"); + let library_path = dirs.test().join("lib"); - nu.with_config(&file); + nu.with_config(file); nu.with_files(&[FileWithContent( "config.toml", &format!( r#" - lib_dirs = ["{library_path}"] + lib_dirs = ["{}"] skip_welcome_message = true - "# + "#, + library_path.as_os_str().to_str().unwrap(), ), )]); diff --git a/crates/nu-command/tests/commands/ucp.rs b/crates/nu-command/tests/commands/ucp.rs index 728cf1dcb2..2c2b30f252 100644 --- a/crates/nu-command/tests/commands/ucp.rs +++ b/crates/nu-command/tests/commands/ucp.rs @@ -1,6 +1,6 @@ use nu_test_support::fs::file_contents; use nu_test_support::fs::{ - files_exist_at, AbsoluteFile, + files_exist_at, Stub::{EmptyFile, FileWithContent, FileWithPermission}, }; use nu_test_support::nu; @@ -55,7 +55,7 @@ fn copies_the_file_inside_directory_if_path_to_copy_is_directory() { fn copies_the_file_inside_directory_if_path_to_copy_is_directory_impl(progress: bool) { Playground::setup("ucp_test_2", |dirs, _| { - let expected_file = AbsoluteFile::new(dirs.test().join("sample.ini")); + let expected_file = dirs.test().join("sample.ini"); let progress_flag = if progress { "-p" } else { "" }; // Get the hash of the file content to check integrity after copy. @@ -64,13 +64,13 @@ fn copies_the_file_inside_directory_if_path_to_copy_is_directory_impl(progress: cwd: dirs.formats(), "cp {} ../formats/sample.ini {}", progress_flag, - expected_file.dir() + expected_file.parent().unwrap().as_os_str().to_str().unwrap(), ); assert!(dirs.test().join("sample.ini").exists()); // Check the integrity of the file. - let after_cp_hash = get_file_hash(expected_file); + let after_cp_hash = get_file_hash(expected_file.display()); assert_eq!(first_hash, after_cp_hash); }) } diff --git a/crates/nu-command/tests/commands/use_.rs b/crates/nu-command/tests/commands/use_.rs index 63b1f8391a..2895e1635f 100644 --- a/crates/nu-command/tests/commands/use_.rs +++ b/crates/nu-command/tests/commands/use_.rs @@ -1,4 +1,3 @@ -use nu_test_support::fs::AbsolutePath; use nu_test_support::fs::Stub::{FileWithContent, FileWithContentToBeTrimmed}; use nu_test_support::nu; use nu_test_support::pipeline; @@ -7,10 +6,10 @@ use nu_test_support::playground::Playground; #[test] fn use_module_file_within_block() { Playground::setup("use_test_1", |dirs, nu| { - let file = AbsolutePath::new(dirs.test().join("spam.nu")); + let file = dirs.test().join("spam.nu"); nu.with_files(&[FileWithContent( - &file.to_string(), + file.as_os_str().to_str().unwrap(), r#" export def foo [] { echo "hello world" @@ -37,10 +36,10 @@ fn use_module_file_within_block() { #[test] fn use_keeps_doc_comments() { Playground::setup("use_doc_comments", |dirs, nu| { - let file = AbsolutePath::new(dirs.test().join("spam.nu")); + let file = dirs.test().join("spam.nu"); nu.with_files(&[FileWithContent( - &file.to_string(), + file.as_os_str().to_str().unwrap(), r#" # this is my foo command export def foo [ diff --git a/crates/nu-test-support/src/fs.rs b/crates/nu-test-support/src/fs.rs index 00321e6590..eb90e16ab2 100644 --- a/crates/nu-test-support/src/fs.rs +++ b/crates/nu-test-support/src/fs.rs @@ -1,137 +1,5 @@ -use std::fmt::Display; +use nu_path::{AbsolutePath, AbsolutePathBuf, Path}; use std::io::Read; -use std::ops::Div; -use std::path::{Path, PathBuf}; - -pub struct AbsoluteFile { - inner: PathBuf, -} - -impl AbsoluteFile { - pub fn new(path: impl AsRef) -> AbsoluteFile { - let path = path.as_ref(); - - if !path.is_absolute() { - panic!( - "AbsoluteFile::new must take an absolute path :: {}", - path.display() - ) - } else if path.is_dir() { - // At the moment, this is not an invariant, but rather a way to catch bugs - // in tests. - panic!( - "AbsoluteFile::new must not take a directory :: {}", - path.display() - ) - } else { - AbsoluteFile { - inner: path.to_path_buf(), - } - } - } - - pub fn dir(&self) -> AbsolutePath { - AbsolutePath::new(if let Some(parent) = self.inner.parent() { - parent - } else { - unreachable!("Internal error: could not get parent in dir") - }) - } -} - -impl From for PathBuf { - fn from(file: AbsoluteFile) -> Self { - file.inner - } -} - -pub struct AbsolutePath { - pub inner: PathBuf, -} - -impl AbsolutePath { - pub fn new(path: impl AsRef) -> AbsolutePath { - let path = path.as_ref(); - - if path.is_absolute() { - AbsolutePath { - inner: path.to_path_buf(), - } - } else { - panic!("AbsolutePath::new must take an absolute path") - } - } -} - -impl Div<&str> for &AbsolutePath { - type Output = AbsolutePath; - - fn div(self, rhs: &str) -> Self::Output { - let parts = rhs.split('/'); - let mut result = self.inner.clone(); - - for part in parts { - result = result.join(part); - } - - AbsolutePath::new(result) - } -} - -impl AsRef for AbsolutePath { - fn as_ref(&self) -> &Path { - self.inner.as_path() - } -} - -pub struct RelativePath { - inner: PathBuf, -} - -impl RelativePath { - pub fn new(path: impl Into) -> RelativePath { - let path = path.into(); - - if path.is_relative() { - RelativePath { inner: path } - } else { - panic!("RelativePath::new must take a relative path") - } - } -} - -impl> Div for &RelativePath { - type Output = RelativePath; - - fn div(self, rhs: T) -> Self::Output { - let parts = rhs.as_ref().split('/'); - let mut result = self.inner.clone(); - - for part in parts { - result = result.join(part); - } - - RelativePath::new(result) - } -} - -impl Display for AbsoluteFile { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.inner.display()) - } -} - -impl Display for AbsolutePath { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.inner.display()) - } -} - -impl Display for RelativePath { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.inner.display()) - } -} pub enum Stub<'a> { FileWithContent(&'a str, &'a str), @@ -140,7 +8,7 @@ pub enum Stub<'a> { FileWithPermission(&'a str, bool), } -pub fn file_contents(full_path: impl AsRef) -> String { +pub fn file_contents(full_path: impl AsRef) -> String { let mut file = std::fs::File::open(full_path.as_ref()).expect("can not open file"); let mut contents = String::new(); file.read_to_string(&mut contents) @@ -148,7 +16,7 @@ pub fn file_contents(full_path: impl AsRef) -> String { contents } -pub fn file_contents_binary(full_path: impl AsRef) -> Vec { +pub fn file_contents_binary(full_path: impl AsRef) -> Vec { let mut file = std::fs::File::open(full_path.as_ref()).expect("can not open file"); let mut contents = Vec::new(); file.read_to_end(&mut contents).expect("can not read file"); @@ -167,56 +35,32 @@ pub fn line_ending() -> String { } } -pub fn delete_file_at(full_path: impl AsRef) { - let full_path = full_path.as_ref(); - - if full_path.exists() { - std::fs::remove_file(full_path).expect("can not delete file"); - } +pub fn files_exist_at(files: Vec>, path: impl AsRef) -> bool { + let path = path.as_ref(); + files.iter().all(|f| path.join(f.as_ref()).exists()) } -pub fn create_file_at(full_path: impl AsRef) -> Result<(), std::io::Error> { - let full_path = full_path.as_ref(); - - if full_path.parent().is_some() { - panic!("path exists"); - } - - std::fs::write(full_path, b"fake data") -} - -pub fn copy_file_to(source: &str, destination: &str) { - std::fs::copy(source, destination).expect("can not copy file"); -} - -pub fn files_exist_at(files: Vec>, path: impl AsRef) -> bool { - files.iter().all(|f| { - let mut loc = PathBuf::from(path.as_ref()); - loc.push(f); - loc.exists() - }) -} - -pub fn delete_directory_at(full_path: &str) { - std::fs::remove_dir_all(PathBuf::from(full_path)).expect("can not remove directory"); -} - -pub fn executable_path() -> PathBuf { +pub fn executable_path() -> AbsolutePathBuf { let mut path = binaries(); path.push("nu"); path } -pub fn installed_nu_path() -> PathBuf { +pub fn installed_nu_path() -> AbsolutePathBuf { let path = std::env::var_os(crate::NATIVE_PATH_ENV_VAR); - which::which_in("nu", path, ".").unwrap_or_else(|_| executable_path()) + if let Ok(path) = which::which_in("nu", path, ".") { + AbsolutePathBuf::try_from(path).expect("installed nushell path is absolute") + } else { + executable_path() + } } -pub fn root() -> PathBuf { +pub fn root() -> AbsolutePathBuf { let manifest_dir = if let Ok(manifest_dir) = std::env::var("CARGO_MANIFEST_DIR") { - PathBuf::from(manifest_dir) + AbsolutePathBuf::try_from(manifest_dir).expect("CARGO_MANIFEST_DIR is not an absolute path") } else { - PathBuf::from(env!("CARGO_MANIFEST_DIR")) + AbsolutePathBuf::try_from(env!("CARGO_MANIFEST_DIR")) + .expect("CARGO_MANIFEST_DIR is not an absolute path") }; let test_path = manifest_dir.join("Cargo.lock"); @@ -228,11 +72,11 @@ pub fn root() -> PathBuf { .expect("Couldn't find the debug binaries directory") .parent() .expect("Couldn't find the debug binaries directory") - .to_path_buf() + .into() } } -pub fn binaries() -> PathBuf { +pub fn binaries() -> AbsolutePathBuf { let build_target = std::env::var("CARGO_BUILD_TARGET").unwrap_or_default(); let profile = if let Ok(env_profile) = std::env::var("NUSHELL_CARGO_PROFILE") { @@ -244,27 +88,28 @@ pub fn binaries() -> PathBuf { }; std::env::var("CARGO_TARGET_DIR") - .map(PathBuf::from) - .unwrap_or_else(|_| root().join("target")) + .ok() + .and_then(|p| AbsolutePathBuf::try_from(p).ok()) + .unwrap_or_else(|| root().join("target")) .join(build_target) .join(profile) } -pub fn fixtures() -> PathBuf { - root().join("tests").join("fixtures") +pub fn fixtures() -> AbsolutePathBuf { + let mut path = root(); + path.push("tests"); + path.push("fixtures"); + path } -pub fn assets() -> PathBuf { - root().join("tests/assets") -} +// FIXME: re-enable nu_json tests +// pub fn assets() -> AbsolutePathBuf { +// let mut path = root(); +// path.push("tests"); +// path.push("assets"); +// path +// } -pub fn in_directory(str: impl AsRef) -> String { - let path = str.as_ref(); - let path = if path.is_relative() { - root().join(path) - } else { - path.to_path_buf() - }; - - path.display().to_string() +pub fn in_directory(path: impl AsRef) -> AbsolutePathBuf { + root().join(path) } diff --git a/crates/nu-test-support/src/macros.rs b/crates/nu-test-support/src/macros.rs index 958a2453f5..6fd3a8c307 100644 --- a/crates/nu-test-support/src/macros.rs +++ b/crates/nu-test-support/src/macros.rs @@ -234,16 +234,16 @@ macro_rules! nu_with_plugins { } use crate::{Outcome, NATIVE_PATH_ENV_VAR}; -use std::ffi::OsStr; +use nu_path::{AbsolutePath, AbsolutePathBuf, Path}; use std::{ - path::Path, + ffi::OsStr, process::{Command, Stdio}, }; use tempfile::tempdir; #[derive(Default)] pub struct NuOpts { - pub cwd: Option, + pub cwd: Option, pub locale: Option, pub envs: Option>, pub collapse_output: Option, @@ -251,19 +251,12 @@ pub struct NuOpts { } pub fn nu_run_test(opts: NuOpts, commands: impl AsRef, with_std: bool) -> Outcome { - let test_bins = crate::fs::binaries(); - - let cwd = std::env::current_dir().expect("Could not get current working directory."); - let test_bins = nu_path::canonicalize_with(&test_bins, cwd).unwrap_or_else(|e| { - panic!( - "Couldn't canonicalize dummy binaries path {}: {:?}", - test_bins.display(), - e - ) - }); + let test_bins = crate::fs::binaries() + .canonicalize() + .expect("Could not canonicalize dummy binaries path"); let mut paths = crate::shell_os_paths(); - paths.insert(0, test_bins); + paths.insert(0, test_bins.into()); let commands = commands.as_ref().lines().collect::>().join("; "); @@ -272,7 +265,7 @@ pub fn nu_run_test(opts: NuOpts, commands: impl AsRef, with_std: bool) -> O Err(_) => panic!("Couldn't join paths for PATH var."), }; - let target_cwd = opts.cwd.unwrap_or(".".to_string()); + let target_cwd = opts.cwd.unwrap_or_else(crate::fs::root); let locale = opts.locale.unwrap_or("en_US.UTF-8".to_string()); let executable_path = crate::fs::executable_path(); @@ -450,7 +443,7 @@ fn collapse_output(out: &str) -> String { out.replace('\n', "") } -fn setup_command(executable_path: &Path, target_cwd: &str) -> Command { +fn setup_command(executable_path: &AbsolutePath, target_cwd: &AbsolutePath) -> Command { let mut command = Command::new(executable_path); command diff --git a/crates/nu-test-support/src/playground.rs b/crates/nu-test-support/src/playground.rs index 375f192983..4d33dcebe5 100644 --- a/crates/nu-test-support/src/playground.rs +++ b/crates/nu-test-support/src/playground.rs @@ -6,5 +6,5 @@ mod play; mod tests; pub use director::Director; -pub use nu_process::{Executable, NuProcess, NuResult, Outcome}; +pub use nu_process::{Executable, NuProcess, Outcome}; pub use play::{Dirs, EnvironmentVariable, Playground}; diff --git a/crates/nu-test-support/src/playground/director.rs b/crates/nu-test-support/src/playground/director.rs index 3d04155c25..a361342495 100644 --- a/crates/nu-test-support/src/playground/director.rs +++ b/crates/nu-test-support/src/playground/director.rs @@ -83,7 +83,7 @@ impl Director { } impl Executable for Director { - fn execute(&mut self) -> NuResult { + fn execute(&mut self) -> Result { use std::process::Stdio; match self.executable() { diff --git a/crates/nu-test-support/src/playground/nu_process.rs b/crates/nu-test-support/src/playground/nu_process.rs index 0031a173e8..2eeac703c6 100644 --- a/crates/nu-test-support/src/playground/nu_process.rs +++ b/crates/nu-test-support/src/playground/nu_process.rs @@ -1,12 +1,13 @@ use super::EnvironmentVariable; use crate::fs::{binaries as test_bins_path, executable_path}; -use std::ffi::{OsStr, OsString}; -use std::fmt; -use std::path::Path; -use std::process::{Command, ExitStatus}; +use std::{ + ffi::{OsStr, OsString}, + fmt, + process::{Command, ExitStatus}, +}; pub trait Executable { - fn execute(&mut self) -> NuResult; + fn execute(&mut self) -> Result; } #[derive(Clone, Debug)] @@ -24,8 +25,6 @@ impl Outcome { } } -pub type NuResult = Result; - #[derive(Debug)] pub struct NuError { pub desc: String, @@ -69,14 +68,10 @@ impl NuProcess { self } - pub fn get_cwd(&self) -> Option<&Path> { - self.cwd.as_ref().map(Path::new) - } - pub fn construct(&self) -> Command { let mut command = Command::new(executable_path()); - if let Some(cwd) = self.get_cwd() { + if let Some(cwd) = &self.cwd { command.current_dir(cwd); } diff --git a/crates/nu-test-support/src/playground/play.rs b/crates/nu-test-support/src/playground/play.rs index 902997200e..12295c2690 100644 --- a/crates/nu-test-support/src/playground/play.rs +++ b/crates/nu-test-support/src/playground/play.rs @@ -1,8 +1,9 @@ use super::Director; -use crate::fs; -use crate::fs::Stub; +use crate::fs::{self, Stub}; use nu_glob::glob; -use std::path::{Path, PathBuf}; +#[cfg(not(target_arch = "wasm32"))] +use nu_path::Path; +use nu_path::{AbsolutePath, AbsolutePathBuf}; use std::str; use tempfile::{tempdir, TempDir}; @@ -22,46 +23,46 @@ impl EnvironmentVariable { } pub struct Playground<'a> { - root: TempDir, + _root: TempDir, tests: String, - cwd: PathBuf, - config: Option, + cwd: AbsolutePathBuf, + config: Option, environment_vars: Vec, dirs: &'a Dirs, } -#[derive(Default, Clone)] +#[derive(Clone)] pub struct Dirs { - pub root: PathBuf, - pub test: PathBuf, - pub fixtures: PathBuf, + pub root: AbsolutePathBuf, + pub test: AbsolutePathBuf, + pub fixtures: AbsolutePathBuf, } impl Dirs { - pub fn formats(&self) -> PathBuf { + pub fn formats(&self) -> AbsolutePathBuf { self.fixtures.join("formats") } - pub fn root(&self) -> &Path { - self.root.as_path() + pub fn root(&self) -> &AbsolutePath { + &self.root } - pub fn test(&self) -> &Path { - self.test.as_path() + pub fn test(&self) -> &AbsolutePath { + &self.test } } impl<'a> Playground<'a> { - pub fn root(&self) -> &Path { - self.root.path() + pub fn root(&self) -> &AbsolutePath { + &self.dirs.root } - pub fn cwd(&self) -> &Path { + pub fn cwd(&self) -> &AbsolutePath { &self.cwd } pub fn back_to_playground(&mut self) -> &mut Self { - self.cwd = PathBuf::from(self.root()).join(self.tests.clone()); + self.cwd = self.root().join(&self.tests); self } @@ -70,68 +71,46 @@ impl<'a> Playground<'a> { } pub fn setup(topic: &str, block: impl FnOnce(Dirs, &mut Playground)) { - let root = tempdir().expect("Couldn't create a tempdir"); - let nuplay_dir = root.path().join(topic); + let temp = tempdir().expect("Could not create a tempdir"); - if PathBuf::from(&nuplay_dir).exists() { - std::fs::remove_dir_all(PathBuf::from(&nuplay_dir)).expect("can not remove directory"); + let root = AbsolutePathBuf::try_from(temp.path()) + .expect("Tempdir is not an absolute path") + .canonicalize() + .expect("Could not canonicalize tempdir"); + + let test = root.join(topic); + if test.exists() { + std::fs::remove_dir_all(&test).expect("Could not remove directory"); } + std::fs::create_dir(&test).expect("Could not create directory"); + let test = test + .canonicalize() + .expect("Could not canonicalize test path"); - std::fs::create_dir(PathBuf::from(&nuplay_dir)).expect("can not create directory"); - - let fixtures = fs::fixtures(); - let cwd = std::env::current_dir().expect("Could not get current working directory."); - let fixtures = nu_path::canonicalize_with(fixtures.clone(), cwd).unwrap_or_else(|e| { - panic!( - "Couldn't canonicalize fixtures path {}: {:?}", - fixtures.display(), - e - ) - }); - - let mut playground = Playground { - root, - tests: topic.to_string(), - cwd: nuplay_dir, - config: None, - environment_vars: Vec::default(), - dirs: &Dirs::default(), - }; - - let playground_root = playground.root.path(); - - let cwd = std::env::current_dir().expect("Could not get current working directory."); - let test = - nu_path::canonicalize_with(playground_root.join(topic), cwd).unwrap_or_else(|e| { - panic!( - "Couldn't canonicalize test path {}: {:?}", - playground_root.join(topic).display(), - e - ) - }); - - let cwd = std::env::current_dir().expect("Could not get current working directory."); - let root = nu_path::canonicalize_with(playground_root, cwd).unwrap_or_else(|e| { - panic!( - "Couldn't canonicalize tests root path {}: {:?}", - playground_root.display(), - e - ) - }); + let fixtures = fs::fixtures() + .canonicalize() + .expect("Could not canonicalize fixtures path"); let dirs = Dirs { - root, - test, - fixtures, + root: root.into(), + test: test.as_path().into(), + fixtures: fixtures.into(), }; - playground.dirs = &dirs; + let mut playground = Playground { + _root: temp, + tests: topic.to_string(), + cwd: test.into(), + config: None, + environment_vars: Vec::default(), + dirs: &dirs, + }; block(dirs.clone(), &mut playground); } - pub fn with_config(&mut self, source_file: impl AsRef) -> &mut Self { - self.config = Some(source_file.as_ref().to_path_buf()); + pub fn with_config(&mut self, source_file: AbsolutePathBuf) -> &mut Self { + self.config = Some(source_file); self } @@ -205,7 +184,6 @@ impl<'a> Playground<'a> { files .iter() .map(|f| { - let mut path = PathBuf::from(&self.cwd); let mut permission_set = false; let mut write_able = true; let (file_name, contents) = match *f { @@ -227,7 +205,7 @@ impl<'a> Playground<'a> { } }; - path.push(file_name); + let path = self.cwd.join(file_name); std::fs::write(&path, contents.as_bytes()).expect("can not create file"); if permission_set { @@ -252,7 +230,7 @@ impl<'a> Playground<'a> { self } - pub fn glob_vec(pattern: &str) -> Vec { + pub fn glob_vec(pattern: &str) -> Vec { let glob = glob(pattern); glob.expect("invalid pattern") diff --git a/crates/nu-test-support/src/playground/tests.rs b/crates/nu-test-support/src/playground/tests.rs index cc4daa597b..7c2be0237e 100644 --- a/crates/nu-test-support/src/playground/tests.rs +++ b/crates/nu-test-support/src/playground/tests.rs @@ -1,11 +1,4 @@ use crate::playground::Playground; -use std::path::{Path, PathBuf}; - -fn path(p: &Path) -> PathBuf { - let cwd = std::env::current_dir().expect("Could not get current working directory."); - nu_path::canonicalize_with(p, cwd) - .unwrap_or_else(|e| panic!("Couldn't canonicalize path {}: {:?}", p.display(), e)) -} #[test] fn current_working_directory_in_sandbox_directory_created() { @@ -13,7 +6,7 @@ fn current_working_directory_in_sandbox_directory_created() { let original_cwd = dirs.test(); nu.within("some_directory_within"); - assert_eq!(path(nu.cwd()), original_cwd.join("some_directory_within")); + assert_eq!(nu.cwd(), original_cwd.join("some_directory_within")); }) } @@ -25,6 +18,6 @@ fn current_working_directory_back_to_root_from_anywhere() { nu.within("some_directory_within"); nu.back_to_playground(); - assert_eq!(path(nu.cwd()), *original_cwd); + assert_eq!(nu.cwd(), original_cwd); }) } diff --git a/tests/plugins/nu_plugin_nu_example.rs b/tests/plugins/nu_plugin_nu_example.rs index f178c64316..afeb487b60 100644 --- a/tests/plugins/nu_plugin_nu_example.rs +++ b/tests/plugins/nu_plugin_nu_example.rs @@ -4,7 +4,7 @@ use assert_cmd::Command; fn call() { // Add the `nu` binaries to the path env let path_env = std::env::join_paths( - std::iter::once(nu_test_support::fs::binaries()).chain( + std::iter::once(nu_test_support::fs::binaries().into()).chain( std::env::var_os(nu_test_support::NATIVE_PATH_ENV_VAR) .as_deref() .map(std::env::split_paths) diff --git a/tests/plugins/registry_file.rs b/tests/plugins/registry_file.rs index c0f0fa0724..2cb1473b6f 100644 --- a/tests/plugins/registry_file.rs +++ b/tests/plugins/registry_file.rs @@ -162,7 +162,7 @@ fn plugin_rm_then_restart_nu() { contents.upsert_plugin(PluginRegistryItem { name: "foo".into(), // this doesn't exist, but it should be ok - filename: dirs.test().join("nu_plugin_foo"), + filename: dirs.test().join("nu_plugin_foo").into(), shell: None, data: valid_plugin_item_data(), }); @@ -238,7 +238,7 @@ fn plugin_rm_from_custom_path() { contents.upsert_plugin(PluginRegistryItem { name: "foo".into(), // this doesn't exist, but it should be ok - filename: dirs.test().join("nu_plugin_foo"), + filename: dirs.test().join("nu_plugin_foo").into(), shell: None, data: valid_plugin_item_data(), }); @@ -286,7 +286,7 @@ fn plugin_rm_using_filename() { contents.upsert_plugin(PluginRegistryItem { name: "foo".into(), // this doesn't exist, but it should be ok - filename: dirs.test().join("nu_plugin_foo"), + filename: dirs.test().join("nu_plugin_foo").into(), shell: None, data: valid_plugin_item_data(), }); @@ -344,7 +344,7 @@ fn warning_on_invalid_plugin_item() { contents.upsert_plugin(PluginRegistryItem { name: "badtest".into(), // this doesn't exist, but it should be ok - filename: dirs.test().join("nu_plugin_badtest"), + filename: dirs.test().join("nu_plugin_badtest").into(), shell: None, data: PluginRegistryItemData::Invalid, }); From ee875bb8a3106ec445be9a36f44dd7a0e82e6e12 Mon Sep 17 00:00:00 2001 From: Ian Manske Date: Fri, 12 Jul 2024 08:23:51 +0000 Subject: [PATCH 035/126] Edit path form doc comments (#13358) # Description Fixes outdated/inaccurate doc comments for `PathForm`s in `nu_path`. --- crates/nu-path/src/form.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/crates/nu-path/src/form.rs b/crates/nu-path/src/form.rs index 4266905a20..edec72c08c 100644 --- a/crates/nu-path/src/form.rs +++ b/crates/nu-path/src/form.rs @@ -75,9 +75,6 @@ impl private::Sealed for Canonical { /// A marker trait for [`PathForm`]s that may be relative paths. /// This includes only the [`Any`] and [`Relative`] path forms. -/// -/// [`push`](crate::PathBuf::push) and [`join`](crate::Path::join) -/// operations only support [`MaybeRelative`] path forms as input. pub trait MaybeRelative: PathForm {} impl MaybeRelative for Any {} impl MaybeRelative for Relative {} @@ -149,13 +146,13 @@ impl PathSet for Any {} impl PathSet for Relative {} impl PathSet for Absolute {} -/// A marker trait for [`PathForm`]s that support pushing [`MaybeRelative`] paths. +/// A marker trait for [`PathForm`]s that support pushing paths. /// /// This includes only [`Any`] and [`Absolute`] path forms. /// Pushing onto a [`Relative`] path could cause it to become [`Absolute`], /// which is why they do not support pushing. /// In the future, a `push_rel` and/or a `try_push` method could be added as an alternative. -/// Similarly, [`Canonical`] paths may become uncanonical if a non-canonical path is pushed onto it. +/// Similarly, [`Canonical`] paths may become uncanonical if a path is pushed onto it. pub trait PathPush: PathSet {} impl PathPush for Any {} impl PathPush for Absolute {} From 0b8d0bcd7aedd4a518eb1789b0537808aaa1c61a Mon Sep 17 00:00:00 2001 From: Devyn Cairns Date: Fri, 12 Jul 2024 01:25:44 -0700 Subject: [PATCH 036/126] Fix order of I/O types in `take until` (#13356) # Description Just a quick one, but `List(Any)` has to come before `Table`, because `List(Any)` is a valid match for `Table`, so it will choose `Table` output even if the input was actually `List(Any)`. I ended up removing `Table` because it's just not needed at all anyway. Though, I'm not really totally sure this is correct - I think the parser should probably actually just have some idea of what the more specific type is, and choose the most specific type match, rather than just doing it in order. I guess this will result in the output just always being `List(Any)` for now. Still better than a bad typecheck error # User-Facing Changes Fixes the following contrived example: ```nushell def foo []: nothing -> list { seq 1 10 | # list each { |n| $n * 20 } | # this causes the type to become list take until { |x| $x < 10 } } # table is first, so now this is type table # ...but table is not compatible with list } ``` # After Submitting - [ ] make typechecker type choice more robust - [ ] release notes --- crates/nu-command/src/filters/take/take_until.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/crates/nu-command/src/filters/take/take_until.rs b/crates/nu-command/src/filters/take/take_until.rs index c7debf5dee..c8544d364a 100644 --- a/crates/nu-command/src/filters/take/take_until.rs +++ b/crates/nu-command/src/filters/take/take_until.rs @@ -11,13 +11,10 @@ impl Command for TakeUntil { fn signature(&self) -> Signature { Signature::build(self.name()) - .input_output_types(vec![ - (Type::table(), Type::table()), - ( - Type::List(Box::new(Type::Any)), - Type::List(Box::new(Type::Any)), - ), - ]) + .input_output_types(vec![( + Type::List(Box::new(Type::Any)), + Type::List(Box::new(Type::Any)), + )]) .required( "predicate", SyntaxShape::Closure(Some(vec![SyntaxShape::Any, SyntaxShape::Int])), From 8f981c1eb4a648d82d08bfd97f22e54daf1402f2 Mon Sep 17 00:00:00 2001 From: Stefan Holderbach Date: Fri, 12 Jul 2024 11:13:07 +0200 Subject: [PATCH 037/126] Use conventional generic bounds (#13360) https://rust-lang.github.io/rust-clippy/master/index.html#/multiple_bound_locations --- .../nu_plugin_query/src/query_webpage_info.rs | 52 ++++++++----------- 1 file changed, 22 insertions(+), 30 deletions(-) diff --git a/crates/nu_plugin_query/src/query_webpage_info.rs b/crates/nu_plugin_query/src/query_webpage_info.rs index e61387c8d1..f38f7241f5 100644 --- a/crates/nu_plugin_query/src/query_webpage_info.rs +++ b/crates/nu_plugin_query/src/query_webpage_info.rs @@ -158,9 +158,9 @@ impl<'a> serde::Serializer for &'a ValueSerializer { Ok(Value::nothing(self.span)) } - fn serialize_some(self, value: &T) -> Result + fn serialize_some(self, value: &T) -> Result where - T: Serialize, + T: Serialize + ?Sized, { value.serialize(self) } @@ -185,18 +185,18 @@ impl<'a> serde::Serializer for &'a ValueSerializer { Ok(Value::nothing(self.span)) } - fn serialize_newtype_struct( + fn serialize_newtype_struct( self, _name: &'static str, value: &T, ) -> Result where - T: Serialize, + T: Serialize + ?Sized, { value.serialize(self) } - fn serialize_newtype_variant( + fn serialize_newtype_variant( self, _name: &'static str, _variant_index: u32, @@ -204,7 +204,7 @@ impl<'a> serde::Serializer for &'a ValueSerializer { value: &T, ) -> Result where - T: Serialize, + T: Serialize + ?Sized, { value.serialize(self) } @@ -306,13 +306,9 @@ impl<'a> serde::ser::SerializeStruct for MapSerializer<'a> { type Ok = Value; type Error = Error; - fn serialize_field( - &mut self, - key: &'static str, - value: &T, - ) -> Result<(), Self::Error> + fn serialize_field(&mut self, key: &'static str, value: &T) -> Result<(), Self::Error> where - T: Serialize, + T: Serialize + ?Sized, { self.record .insert(key.to_owned(), value.serialize(self.serializer)?); @@ -328,9 +324,9 @@ impl<'a> serde::ser::SerializeMap for MapSerializer<'a> { type Ok = Value; type Error = Error; - fn serialize_key(&mut self, key: &T) -> Result<(), Self::Error> + fn serialize_key(&mut self, key: &T) -> Result<(), Self::Error> where - T: Serialize, + T: Serialize + ?Sized, { let value = serde_json::to_value(key).map_err(Error::new)?; let key = value @@ -341,9 +337,9 @@ impl<'a> serde::ser::SerializeMap for MapSerializer<'a> { Ok(()) } - fn serialize_value(&mut self, value: &T) -> Result<(), Self::Error> + fn serialize_value(&mut self, value: &T) -> Result<(), Self::Error> where - T: Serialize, + T: Serialize + ?Sized, { let key = self.current_key.take().ok_or(Error::new("key expected"))?; self.record.insert(key, value.serialize(self.serializer)?); @@ -359,13 +355,9 @@ impl<'a> serde::ser::SerializeStructVariant for MapSerializer<'a> { type Ok = Value; type Error = Error; - fn serialize_field( - &mut self, - key: &'static str, - value: &T, - ) -> Result<(), Self::Error> + fn serialize_field(&mut self, key: &'static str, value: &T) -> Result<(), Self::Error> where - T: Serialize, + T: Serialize + ?Sized, { self.record .insert(key.to_owned(), value.serialize(self.serializer)?); @@ -397,9 +389,9 @@ impl<'a> serde::ser::SerializeSeq for SeqSerializer<'a> { type Ok = Value; type Error = Error; - fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> + fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> where - T: Serialize, + T: Serialize + ?Sized, { self.seq.push(value.serialize(self.serializer)?); Ok(()) @@ -414,9 +406,9 @@ impl<'a> serde::ser::SerializeTuple for SeqSerializer<'a> { type Ok = Value; type Error = Error; - fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> + fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> where - T: Serialize, + T: Serialize + ?Sized, { self.seq.push(value.serialize(self.serializer)?); Ok(()) @@ -431,9 +423,9 @@ impl<'a> serde::ser::SerializeTupleStruct for SeqSerializer<'a> { type Ok = Value; type Error = Error; - fn serialize_field(&mut self, value: &T) -> Result<(), Self::Error> + fn serialize_field(&mut self, value: &T) -> Result<(), Self::Error> where - T: Serialize, + T: Serialize + ?Sized, { self.seq.push(value.serialize(self.serializer)?); Ok(()) @@ -448,9 +440,9 @@ impl<'a> serde::ser::SerializeTupleVariant for SeqSerializer<'a> { type Ok = Value; type Error = Error; - fn serialize_field(&mut self, value: &T) -> Result<(), Self::Error> + fn serialize_field(&mut self, value: &T) -> Result<(), Self::Error> where - T: Serialize, + T: Serialize + ?Sized, { self.seq.push(value.serialize(self.serializer)?); Ok(()) From 02659b1c8a3b0703bfdce4ad94fa04b9638cba41 Mon Sep 17 00:00:00 2001 From: Devyn Cairns Date: Fri, 12 Jul 2024 02:45:53 -0700 Subject: [PATCH 038/126] Mention the actual output type on an OutputMismatch error (#13355) # Description This improves the error when the determined output of a custom command doesn't match the specified output type by adding the actual determined output type. # User-Facing Changes Previous: `command doesn't output {0}` New: `expected {0}, but command outputs {1}` # Tests + Formatting Passing. # After Submitting - [ ] release notes? (minor change, but helpful) --- crates/nu-parser/src/type_check.rs | 6 +++++- crates/nu-protocol/src/errors/parse_error.rs | 8 ++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/crates/nu-parser/src/type_check.rs b/crates/nu-parser/src/type_check.rs index 6999a0469b..6cfe8f64b3 100644 --- a/crates/nu-parser/src/type_check.rs +++ b/crates/nu-parser/src/type_check.rs @@ -1055,7 +1055,11 @@ pub fn check_block_input_output(working_set: &StateWorkingSet, block: &Block) -> .span }; - output_errors.push(ParseError::OutputMismatch(output_type.clone(), span)) + output_errors.push(ParseError::OutputMismatch( + output_type.clone(), + current_output_type.clone(), + span, + )) } } diff --git a/crates/nu-protocol/src/errors/parse_error.rs b/crates/nu-protocol/src/errors/parse_error.rs index 4d59821f7d..0e20f0dcba 100644 --- a/crates/nu-protocol/src/errors/parse_error.rs +++ b/crates/nu-protocol/src/errors/parse_error.rs @@ -61,7 +61,11 @@ pub enum ParseError { #[error("Command output doesn't match {0}.")] #[diagnostic(code(nu::parser::output_type_mismatch))] - OutputMismatch(Type, #[label("command doesn't output {0}")] Span), + OutputMismatch( + Type, + Type, + #[label("expected {0}, but command outputs {1}")] Span, + ), #[error("Type mismatch during operation.")] #[diagnostic(code(nu::parser::type_mismatch))] @@ -554,7 +558,7 @@ impl ParseError { ParseError::TypeMismatch(_, _, s) => *s, ParseError::TypeMismatchHelp(_, _, s, _) => *s, ParseError::InputMismatch(_, s) => *s, - ParseError::OutputMismatch(_, s) => *s, + ParseError::OutputMismatch(_, _, s) => *s, ParseError::MissingRequiredFlag(_, s) => *s, ParseError::IncompleteMathExpression(s) => *s, ParseError::UnknownState(_, s) => *s, From 46b5e510ac32fbb746c3b8259b2e0ac4c27cb961 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Fri, 12 Jul 2024 09:48:27 -0500 Subject: [PATCH 039/126] tweak `parse` usage and examples to be more clear (#13363) # Description This PR just tweaks the `parse` command's usage and examples to make it clearer what's going on "under the hood". # User-Facing Changes # Tests + Formatting # After Submitting --- crates/nu-command/src/strings/parse.rs | 27 ++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/crates/nu-command/src/strings/parse.rs b/crates/nu-command/src/strings/parse.rs index 9bf31de73c..86b27aecbc 100644 --- a/crates/nu-command/src/strings/parse.rs +++ b/crates/nu-command/src/strings/parse.rs @@ -12,13 +12,17 @@ impl Command for Parse { } fn usage(&self) -> &str { - "Parse columns from string data using a simple pattern." + "Parse columns from string data using a simple pattern or a supplied regular expression." } fn search_terms(&self) -> Vec<&str> { vec!["pattern", "match", "regex", "str extract"] } + fn extra_usage(&self) -> &str { + "The parse command always uses regular expressions even when you use a simple pattern. If a simple pattern is supplied, parse will transform that pattern into a regular expression." + } + fn signature(&self) -> nu_protocol::Signature { Signature::build("parse") .required("pattern", SyntaxShape::String, "The pattern to match.") @@ -32,21 +36,24 @@ impl Command for Parse { } fn examples(&self) -> Vec { - let result = Value::test_list(vec![Value::test_record(record! { - "foo" => Value::test_string("hi"), - "bar" => Value::test_string("there"), - })]); - vec![ Example { description: "Parse a string into two named columns", example: "\"hi there\" | parse \"{foo} {bar}\"", - result: Some(result.clone()), + result: Some(Value::test_list( + vec![Value::test_record(record! { + "foo" => Value::test_string("hi"), + "bar" => Value::test_string("there"), + })])), }, Example { - description: "Parse a string using regex pattern", - example: "\"hi there\" | parse --regex '(?P\\w+) (?P\\w+)'", - result: Some(result), + description: "This is how the first example is interpreted in the source code", + example: "\"hi there\" | parse --regex '(?s)\\A(?P.*?) (?P.*?)\\z'", + result: Some(Value::test_list( + vec![Value::test_record(record! { + "foo" => Value::test_string("hi"), + "bar" => Value::test_string("there"), + })])), }, Example { description: "Parse a string using fancy-regex named capture group pattern", From d42cf554313955095051e5e110ac388c2b3f01c9 Mon Sep 17 00:00:00 2001 From: Devyn Cairns Date: Fri, 12 Jul 2024 19:27:23 -0700 Subject: [PATCH 040/126] fix file_count in `Debug` implementation of `IrBlock` (#13367) # Description Oops. --- crates/nu-protocol/src/ir/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/nu-protocol/src/ir/mod.rs b/crates/nu-protocol/src/ir/mod.rs index 28677b743c..a1b2439768 100644 --- a/crates/nu-protocol/src/ir/mod.rs +++ b/crates/nu-protocol/src/ir/mod.rs @@ -37,7 +37,7 @@ impl fmt::Debug for IrBlock { .field("data", &self.data) .field("comments", &self.comments) .field("register_count", &self.register_count) - .field("file_count", &self.register_count) + .field("file_count", &self.file_count) .finish_non_exhaustive() } } From a2758e6c40fee2039215be72a776602ac0ae69a1 Mon Sep 17 00:00:00 2001 From: Devyn Cairns Date: Sat, 13 Jul 2024 01:58:21 -0700 Subject: [PATCH 041/126] Add IR support to the debugger (#13345) # Description This adds tracing for each individual instruction to the `Debugger` trait. Register contents can be inspected both when entering and leaving an instruction, and if an instruction produced an error, a reference to the error is also available. It's not the full `EvalContext` but it's most of the important parts for getting an idea of what's going on. Added support for all of this to the `Profiler` / `debug profile` as well, and the output is quite incredible - super verbose, but you can see every instruction that's executed and also what the result was if it's an instruction that has a clearly defined output (many do). # User-Facing Changes - Added `--instructions` to `debug profile`, which adds the `pc` and `instruction` columns to the output. - `--expr` only works in AST mode, and `--instructions` only works in IR mode. In the wrong mode, the output for those columns is just blank. # Tests + Formatting All passing. # After Submitting - [ ] release notes --- crates/nu-command/src/debug/profile.rs | 24 ++- crates/nu-engine/src/eval_ir.rs | 17 +- .../src/debugger/debugger_trait.rs | 87 ++++++++- crates/nu-protocol/src/debugger/profiler.rs | 178 ++++++++++++++---- crates/nu-protocol/src/ir/mod.rs | 57 ++++++ 5 files changed, 313 insertions(+), 50 deletions(-) diff --git a/crates/nu-command/src/debug/profile.rs b/crates/nu-command/src/debug/profile.rs index 0cef979094..e8254563df 100644 --- a/crates/nu-command/src/debug/profile.rs +++ b/crates/nu-command/src/debug/profile.rs @@ -1,5 +1,8 @@ use nu_engine::{command_prelude::*, ClosureEvalOnce}; -use nu_protocol::{debugger::Profiler, engine::Closure}; +use nu_protocol::{ + debugger::{Profiler, ProfilerOptions}, + engine::Closure, +}; #[derive(Clone)] pub struct DebugProfile; @@ -28,6 +31,7 @@ impl Command for DebugProfile { Some('v'), ) .switch("expr", "Collect expression types", Some('x')) + .switch("instructions", "Collect IR instructions", Some('i')) .switch("lines", "Collect line numbers", Some('l')) .named( "max-depth", @@ -91,19 +95,23 @@ confusing the id/parent_id hierarchy. The --expr flag is helpful for investigati let collect_expanded_source = call.has_flag(engine_state, stack, "expanded-source")?; let collect_values = call.has_flag(engine_state, stack, "values")?; let collect_exprs = call.has_flag(engine_state, stack, "expr")?; + let collect_instructions = call.has_flag(engine_state, stack, "instructions")?; let collect_lines = call.has_flag(engine_state, stack, "lines")?; let max_depth = call .get_flag(engine_state, stack, "max-depth")? .unwrap_or(2); let profiler = Profiler::new( - max_depth, - collect_spans, - true, - collect_expanded_source, - collect_values, - collect_exprs, - collect_lines, + ProfilerOptions { + max_depth, + collect_spans, + collect_source: true, + collect_expanded_source, + collect_values, + collect_exprs, + collect_instructions, + collect_lines, + }, call.span(), ); diff --git a/crates/nu-engine/src/eval_ir.rs b/crates/nu-engine/src/eval_ir.rs index 56bbccee25..29a677bd69 100644 --- a/crates/nu-engine/src/eval_ir.rs +++ b/crates/nu-engine/src/eval_ir.rs @@ -184,11 +184,20 @@ fn eval_ir_block_impl( let instruction = &ir_block.instructions[pc]; let span = &ir_block.spans[pc]; let ast = &ir_block.ast[pc]; - log::trace!( - "{pc:-4}: {}", - instruction.display(ctx.engine_state, ctx.data) + + D::enter_instruction(ctx.engine_state, ir_block, pc, ctx.registers); + + let result = eval_instruction::(ctx, instruction, span, ast); + + D::leave_instruction( + ctx.engine_state, + ir_block, + pc, + ctx.registers, + result.as_ref().err(), ); - match eval_instruction::(ctx, instruction, span, ast) { + + match result { Ok(InstructionResult::Continue) => { pc += 1; } diff --git a/crates/nu-protocol/src/debugger/debugger_trait.rs b/crates/nu-protocol/src/debugger/debugger_trait.rs index 69d395fb98..88cc753548 100644 --- a/crates/nu-protocol/src/debugger/debugger_trait.rs +++ b/crates/nu-protocol/src/debugger/debugger_trait.rs @@ -16,6 +16,7 @@ use crate::{ ast::{Block, PipelineElement}, engine::EngineState, + ir::IrBlock, PipelineData, ShellError, Span, Value, }; use std::{fmt::Debug, ops::DerefMut}; @@ -35,11 +36,11 @@ pub trait DebugContext: Clone + Copy + Debug { #[allow(unused_variables)] fn leave_block(engine_state: &EngineState, block: &Block) {} - /// Called when the evaluator enters a pipeline element + /// Called when the AST evaluator enters a pipeline element #[allow(unused_variables)] fn enter_element(engine_state: &EngineState, element: &PipelineElement) {} - /// Called when the evaluator leaves a pipeline element + /// Called when the AST evaluator leaves a pipeline element #[allow(unused_variables)] fn leave_element( engine_state: &EngineState, @@ -47,6 +48,27 @@ pub trait DebugContext: Clone + Copy + Debug { result: &Result, ) { } + + /// Called before the IR evaluator runs an instruction + #[allow(unused_variables)] + fn enter_instruction( + engine_state: &EngineState, + ir_block: &IrBlock, + instruction_index: usize, + registers: &[PipelineData], + ) { + } + + /// Called after the IR evaluator runs an instruction + #[allow(unused_variables)] + fn leave_instruction( + engine_state: &EngineState, + ir_block: &IrBlock, + instruction_index: usize, + registers: &[PipelineData], + error: Option<&ShellError>, + ) { + } } /// Marker struct signalizing that evaluation should use a Debugger @@ -85,6 +107,40 @@ impl DebugContext for WithDebug { .leave_element(engine_state, element, result); } } + + fn enter_instruction( + engine_state: &EngineState, + ir_block: &IrBlock, + instruction_index: usize, + registers: &[PipelineData], + ) { + if let Ok(mut debugger) = engine_state.debugger.lock() { + debugger.deref_mut().enter_instruction( + engine_state, + ir_block, + instruction_index, + registers, + ) + } + } + + fn leave_instruction( + engine_state: &EngineState, + ir_block: &IrBlock, + instruction_index: usize, + registers: &[PipelineData], + error: Option<&ShellError>, + ) { + if let Ok(mut debugger) = engine_state.debugger.lock() { + debugger.deref_mut().leave_instruction( + engine_state, + ir_block, + instruction_index, + registers, + error, + ) + } + } } /// Marker struct signalizing that evaluation should NOT use a Debugger @@ -118,11 +174,11 @@ pub trait Debugger: Send + Debug { #[allow(unused_variables)] fn leave_block(&mut self, engine_state: &EngineState, block: &Block) {} - /// Called when the evaluator enters a pipeline element + /// Called when the AST evaluator enters a pipeline element #[allow(unused_variables)] fn enter_element(&mut self, engine_state: &EngineState, pipeline_element: &PipelineElement) {} - /// Called when the evaluator leaves a pipeline element + /// Called when the AST evaluator leaves a pipeline element #[allow(unused_variables)] fn leave_element( &mut self, @@ -132,6 +188,29 @@ pub trait Debugger: Send + Debug { ) { } + /// Called before the IR evaluator runs an instruction + #[allow(unused_variables)] + fn enter_instruction( + &mut self, + engine_state: &EngineState, + ir_block: &IrBlock, + instruction_index: usize, + registers: &[PipelineData], + ) { + } + + /// Called after the IR evaluator runs an instruction + #[allow(unused_variables)] + fn leave_instruction( + &mut self, + engine_state: &EngineState, + ir_block: &IrBlock, + instruction_index: usize, + registers: &[PipelineData], + error: Option<&ShellError>, + ) { + } + /// Create a final report as a Value /// /// Intended to be called after deactivate() diff --git a/crates/nu-protocol/src/debugger/profiler.rs b/crates/nu-protocol/src/debugger/profiler.rs index c3066c0a14..c61ee04d12 100644 --- a/crates/nu-protocol/src/debugger/profiler.rs +++ b/crates/nu-protocol/src/debugger/profiler.rs @@ -7,10 +7,11 @@ use crate::{ ast::{Block, Expr, PipelineElement}, debugger::Debugger, engine::EngineState, + ir::IrBlock, record, PipelineData, ShellError, Span, Value, }; -use std::io::BufRead; use std::time::Instant; +use std::{borrow::Borrow, io::BufRead}; #[derive(Debug, Clone, Copy)] struct ElementId(usize); @@ -24,6 +25,7 @@ struct ElementInfo { element_span: Span, element_output: Option, expr: Option, + instruction: Option<(usize, String)>, children: Vec, } @@ -36,57 +38,53 @@ impl ElementInfo { element_span, element_output: None, expr: None, + instruction: None, children: vec![], } } } +/// Options for [`Profiler`] +#[derive(Debug, Clone)] +pub struct ProfilerOptions { + pub max_depth: i64, + pub collect_spans: bool, + pub collect_source: bool, + pub collect_expanded_source: bool, + pub collect_values: bool, + pub collect_exprs: bool, + pub collect_instructions: bool, + pub collect_lines: bool, +} + /// Basic profiler, used in `debug profile` #[derive(Debug, Clone)] pub struct Profiler { depth: i64, - max_depth: i64, - collect_spans: bool, - collect_source: bool, - collect_expanded_source: bool, - collect_values: bool, - collect_exprs: bool, - collect_lines: bool, + opts: ProfilerOptions, elements: Vec, element_stack: Vec, } impl Profiler { #[allow(clippy::too_many_arguments)] - pub fn new( - max_depth: i64, - collect_spans: bool, - collect_source: bool, - collect_expanded_source: bool, - collect_values: bool, - collect_exprs: bool, - collect_lines: bool, - span: Span, - ) -> Self { + pub fn new(opts: ProfilerOptions, span: Span) -> Self { let first = ElementInfo { start: Instant::now(), duration_sec: 0.0, depth: 0, element_span: span, - element_output: collect_values.then(|| Value::nothing(span)), - expr: collect_exprs.then(|| "call".to_string()), + element_output: opts.collect_values.then(|| Value::nothing(span)), + expr: opts.collect_exprs.then(|| "call".to_string()), + instruction: opts + .collect_instructions + .then(|| (0, "".to_string())), children: vec![], }; Profiler { depth: 0, - max_depth, - collect_spans, - collect_source, - collect_expanded_source, - collect_values, - collect_exprs, - collect_lines, + opts, elements: vec![first], element_stack: vec![ElementId(0)], } @@ -130,7 +128,7 @@ impl Debugger for Profiler { } fn enter_element(&mut self, engine_state: &EngineState, element: &PipelineElement) { - if self.depth > self.max_depth { + if self.depth > self.opts.max_depth { return; } @@ -140,6 +138,7 @@ impl Debugger for Profiler { }; let expr_opt = self + .opts .collect_exprs .then(|| expr_to_string(engine_state, &element.expr.expr)); @@ -165,13 +164,13 @@ impl Debugger for Profiler { element: &PipelineElement, result: &Result, ) { - if self.depth > self.max_depth { + if self.depth > self.opts.max_depth { return; } let element_span = element.expr.span; - let out_opt = self.collect_values.then(|| match result { + let out_opt = self.opts.collect_values.then(|| match result { Ok(pipeline_data) => match pipeline_data { PipelineData::Value(val, ..) => val.clone(), PipelineData::ListStream(..) => Value::string("list stream", element_span), @@ -192,6 +191,91 @@ impl Debugger for Profiler { self.element_stack.pop(); } + fn enter_instruction( + &mut self, + engine_state: &EngineState, + ir_block: &IrBlock, + instruction_index: usize, + _registers: &[PipelineData], + ) { + if self.depth > self.opts.max_depth { + return; + } + + let Some(parent_id) = self.last_element_id() else { + eprintln!("Profiler Error: Missing parent element ID."); + return; + }; + + let instruction = &ir_block.instructions[instruction_index]; + let span = ir_block.spans[instruction_index]; + + let instruction_opt = self.opts.collect_instructions.then(|| { + ( + instruction_index, + instruction + .display(engine_state, &ir_block.data) + .to_string(), + ) + }); + + let new_id = ElementId(self.elements.len()); + + let mut new_element = ElementInfo::new(self.depth, span); + new_element.instruction = instruction_opt; + + self.elements.push(new_element); + + let Some(parent) = self.elements.get_mut(parent_id.0) else { + eprintln!("Profiler Error: Missing parent element."); + return; + }; + + parent.children.push(new_id); + self.element_stack.push(new_id); + } + + fn leave_instruction( + &mut self, + _engine_state: &EngineState, + ir_block: &IrBlock, + instruction_index: usize, + registers: &[PipelineData], + error: Option<&ShellError>, + ) { + if self.depth > self.opts.max_depth { + return; + } + + let instruction = &ir_block.instructions[instruction_index]; + let span = ir_block.spans[instruction_index]; + + let out_opt = self + .opts + .collect_values + .then(|| { + error + .map(Err) + .or_else(|| { + instruction + .output_register() + .map(|register| Ok(®isters[register.0 as usize])) + }) + .map(|result| format_result(&result, span)) + }) + .flatten(); + + let Some(last_element) = self.last_element_mut() else { + eprintln!("Profiler Error: Missing last element."); + return; + }; + + last_element.duration_sec = last_element.start.elapsed().as_secs_f64(); + last_element.element_output = out_opt; + + self.element_stack.pop(); + } + fn report(&self, engine_state: &EngineState, profiler_span: Span) -> Result { Ok(Value::list( collect_data( @@ -268,6 +352,21 @@ fn expr_to_string(engine_state: &EngineState, expr: &Expr) -> String { } } +fn format_result( + result: &Result, impl Borrow>, + element_span: Span, +) -> Value { + match result { + Ok(pipeline_data) => match pipeline_data.borrow() { + PipelineData::Value(val, ..) => val.clone(), + PipelineData::ListStream(..) => Value::string("list stream", element_span), + PipelineData::ByteStream(..) => Value::string("byte stream", element_span), + _ => Value::nothing(element_span), + }, + Err(e) => Value::error(e.borrow().clone(), element_span), + } +} + // Find a file name and a line number (indexed from 1) of a span fn find_file_of_span(engine_state: &EngineState, span: Span) -> Option<(&str, usize)> { for file in engine_state.files() { @@ -308,7 +407,7 @@ fn collect_data( "parent_id" => Value::int(parent_id.0 as i64, profiler_span), }; - if profiler.collect_lines { + if profiler.opts.collect_lines { if let Some((fname, line_num)) = find_file_of_span(engine_state, element.element_span) { row.push("file", Value::string(fname, profiler_span)); row.push("line", Value::int(line_num as i64, profiler_span)); @@ -318,7 +417,7 @@ fn collect_data( } } - if profiler.collect_spans { + if profiler.opts.collect_spans { let span_start = i64::try_from(element.element_span.start) .map_err(|_| profiler_error("error converting span start to i64", profiler_span))?; let span_end = i64::try_from(element.element_span.end) @@ -336,12 +435,12 @@ fn collect_data( ); } - if profiler.collect_source { + if profiler.opts.collect_source { let val = String::from_utf8_lossy(engine_state.get_span_contents(element.element_span)); let val = val.trim(); let nlines = val.lines().count(); - let fragment = if profiler.collect_expanded_source { + let fragment = if profiler.opts.collect_expanded_source { val.to_string() } else { let mut first_line = val.lines().next().unwrap_or("").to_string(); @@ -360,6 +459,17 @@ fn collect_data( row.push("expr", Value::string(expr_string.clone(), profiler_span)); } + if let Some((instruction_index, instruction)) = &element.instruction { + row.push( + "pc", + (*instruction_index) + .try_into() + .map(|index| Value::int(index, profiler_span)) + .unwrap_or(Value::nothing(profiler_span)), + ); + row.push("instruction", Value::string(instruction, profiler_span)); + } + if let Some(val) = &element.element_output { row.push("output", val.clone()); } diff --git a/crates/nu-protocol/src/ir/mod.rs b/crates/nu-protocol/src/ir/mod.rs index a1b2439768..aff5f42c8c 100644 --- a/crates/nu-protocol/src/ir/mod.rs +++ b/crates/nu-protocol/src/ir/mod.rs @@ -276,6 +276,63 @@ impl Instruction { } } + /// Get the output register, for instructions that produce some kind of immediate result. + pub fn output_register(&self) -> Option { + match *self { + Instruction::Unreachable => None, + Instruction::LoadLiteral { dst, .. } => Some(dst), + Instruction::LoadValue { dst, .. } => Some(dst), + Instruction::Move { dst, .. } => Some(dst), + Instruction::Clone { dst, .. } => Some(dst), + Instruction::Collect { src_dst } => Some(src_dst), + Instruction::Span { src_dst } => Some(src_dst), + Instruction::Drop { .. } => None, + Instruction::Drain { .. } => None, + Instruction::LoadVariable { dst, .. } => Some(dst), + Instruction::StoreVariable { .. } => None, + Instruction::LoadEnv { dst, .. } => Some(dst), + Instruction::LoadEnvOpt { dst, .. } => Some(dst), + Instruction::StoreEnv { .. } => None, + Instruction::PushPositional { .. } => None, + Instruction::AppendRest { .. } => None, + Instruction::PushFlag { .. } => None, + Instruction::PushShortFlag { .. } => None, + Instruction::PushNamed { .. } => None, + Instruction::PushShortNamed { .. } => None, + Instruction::PushParserInfo { .. } => None, + Instruction::RedirectOut { .. } => None, + Instruction::RedirectErr { .. } => None, + Instruction::CheckErrRedirected { .. } => None, + Instruction::OpenFile { .. } => None, + Instruction::WriteFile { .. } => None, + Instruction::CloseFile { .. } => None, + Instruction::Call { src_dst, .. } => Some(src_dst), + Instruction::StringAppend { src_dst, .. } => Some(src_dst), + Instruction::GlobFrom { src_dst, .. } => Some(src_dst), + Instruction::ListPush { src_dst, .. } => Some(src_dst), + Instruction::ListSpread { src_dst, .. } => Some(src_dst), + Instruction::RecordInsert { src_dst, .. } => Some(src_dst), + Instruction::RecordSpread { src_dst, .. } => Some(src_dst), + Instruction::Not { src_dst } => Some(src_dst), + Instruction::BinaryOp { lhs_dst, .. } => Some(lhs_dst), + Instruction::FollowCellPath { src_dst, .. } => Some(src_dst), + Instruction::CloneCellPath { dst, .. } => Some(dst), + Instruction::UpsertCellPath { src_dst, .. } => Some(src_dst), + Instruction::Jump { .. } => None, + Instruction::BranchIf { .. } => None, + Instruction::BranchIfEmpty { .. } => None, + Instruction::Match { .. } => None, + Instruction::CheckMatchGuard { .. } => None, + Instruction::Iterate { dst, .. } => Some(dst), + Instruction::OnError { .. } => None, + Instruction::OnErrorInto { .. } => None, + Instruction::PopErrorHandler => None, + Instruction::CheckExternalFailed { dst, .. } => Some(dst), + Instruction::ReturnEarly { .. } => None, + Instruction::Return { .. } => None, + } + } + /// Returns the branch target index of the instruction if this is a branching instruction. pub fn branch_target(&self) -> Option { match self { From b0bf54614f7f7fe3ef7637243eda49618d30f7e3 Mon Sep 17 00:00:00 2001 From: Yash Thakur <45539777+ysthakur@users.noreply.github.com> Date: Sat, 13 Jul 2024 10:52:39 -0400 Subject: [PATCH 042/126] Don't add touch command to default context twice (#13371) # Description Touch was added to the shell command context twice, once with the other filesystem commands and once with the format commands. I removed that second occurrence of touch, because I'm assuming it was only added there because "Touch" starts with "To." # User-Facing Changes None --- crates/nu-command/src/default_context.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index b270a78dce..4f35a1c711 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -290,7 +290,6 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState { ToText, ToToml, ToTsv, - Touch, Upsert, Where, ToXml, From f5bff8c9c80802d3b45b88a012e30a41bfbf7f27 Mon Sep 17 00:00:00 2001 From: Stefan Holderbach Date: Sat, 13 Jul 2024 16:54:34 +0200 Subject: [PATCH 043/126] Fix `select` cell path renaming behavior (#13361) # Description Fixes #13359 In an attempt to generate names for flat columns resulting from a nested accesses #3016 generated new column names on nested selection, out of convenience, that composed the cell path as a string (including `.`) and then simply replaced all `.` with `_`. As we permit `.` in column names as long as you quote this surprisingly alters `select`ed columns. # User-Facing Changes New columns generated by selection with nested cell paths will for now be named with a string containing the keys separated by `.` instead of `_`. We may want to reconsider the semantics for nested access. # Tests + Formatting - Alter test to breaking change on nested `select` --- crates/nu-command/src/filters/select.rs | 6 +++--- crates/nu-command/tests/commands/select.rs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/nu-command/src/filters/select.rs b/crates/nu-command/src/filters/select.rs index 2ca04b3999..2fe4da3c36 100644 --- a/crates/nu-command/src/filters/select.rs +++ b/crates/nu-command/src/filters/select.rs @@ -240,7 +240,7 @@ fn select( //FIXME: improve implementation to not clone match input_val.clone().follow_cell_path(&path.members, false) { Ok(fetcher) => { - record.push(path.to_string().replace('.', "_"), fetcher); + record.push(path.to_string(), fetcher); if !columns_with_value.contains(&path) { columns_with_value.push(path); } @@ -271,7 +271,7 @@ fn select( // FIXME: remove clone match v.clone().follow_cell_path(&cell_path.members, false) { Ok(result) => { - record.push(cell_path.to_string().replace('.', "_"), result); + record.push(cell_path.to_string(), result); } Err(e) => return Err(e), } @@ -295,7 +295,7 @@ fn select( //FIXME: improve implementation to not clone match x.clone().follow_cell_path(&path.members, false) { Ok(value) => { - record.push(path.to_string().replace('.', "_"), value); + record.push(path.to_string(), value); } Err(e) => return Err(e), } diff --git a/crates/nu-command/tests/commands/select.rs b/crates/nu-command/tests/commands/select.rs index 741386998a..004aa16d6b 100644 --- a/crates/nu-command/tests/commands/select.rs +++ b/crates/nu-command/tests/commands/select.rs @@ -50,7 +50,7 @@ fn complex_nested_columns() { r#" {sample} | select nu."0xATYKARNU" nu.committers.name nu.releases.version - | get nu_releases_version + | get "nu.releases.version" | where $it > "0.8" | get 0 "# From c5aa15c7f6b3e44b6c211d5e83617ced33424aec Mon Sep 17 00:00:00 2001 From: Stefan Holderbach Date: Sun, 14 Jul 2024 10:10:41 +0200 Subject: [PATCH 044/126] Add top-level crate documentation/READMEs (#12907) # Description Add `README.md` files to each crate in our workspace (-plugins) and also include it in the `lib.rs` documentation for (if there is no existing `lib.rs` crate documentation) In all new README I added the defensive comment that the crates are not considered stable for public consumption. If necessary we can adjust this if we deem a crate useful for plugin authors. --- crates/nu-cli/README.md | 7 +++++++ crates/nu-cli/src/lib.rs | 1 + crates/nu-cmd-base/README.md | 5 +++++ crates/nu-cmd-base/src/lib.rs | 1 + crates/nu-cmd-extra/src/lib.rs | 1 + crates/nu-cmd-lang/src/lib.rs | 1 + crates/nu-color-config/README.md | 5 +++++ crates/nu-color-config/src/lib.rs | 1 + crates/nu-command/README.md | 7 +++++++ crates/nu-command/src/lib.rs | 1 + crates/nu-engine/README.md | 9 +++++++++ crates/nu-engine/src/lib.rs | 1 + crates/nu-explore/README.md | 5 +++++ crates/nu-explore/src/lib.rs | 1 + crates/nu-json/README.md | 4 ++-- crates/nu-json/src/lib.rs | 1 + crates/nu-lsp/README.md | 7 +++++++ crates/nu-lsp/src/lib.rs | 1 + crates/nu-parser/README.md | 14 +++++++------- crates/nu-parser/src/lib.rs | 1 + crates/nu-path/src/lib.rs | 1 + crates/nu-protocol/src/lib.rs | 1 + crates/nu-protocol/src/pipeline/byte_stream.rs | 4 ++-- crates/nu-std/README.md | 2 +- crates/nu-std/src/lib.rs | 1 + crates/nu-system/README.md | 7 +++++++ crates/nu-system/src/lib.rs | 1 + crates/nu-table/README.md | 7 +++++++ crates/nu-table/src/lib.rs | 1 + crates/nu-term-grid/README.md | 5 +++++ crates/nu-term-grid/src/lib.rs | 1 + crates/nu-test-support/README.md | 7 +++++++ crates/nu-test-support/src/lib.rs | 1 + crates/nu-utils/README.md | 6 ++++++ crates/nu-utils/src/lib.rs | 1 + crates/nuon/README.md | 2 +- 36 files changed, 109 insertions(+), 13 deletions(-) create mode 100644 crates/nu-cli/README.md create mode 100644 crates/nu-cmd-base/README.md create mode 100644 crates/nu-color-config/README.md create mode 100644 crates/nu-command/README.md create mode 100644 crates/nu-engine/README.md create mode 100644 crates/nu-explore/README.md create mode 100644 crates/nu-lsp/README.md create mode 100644 crates/nu-system/README.md create mode 100644 crates/nu-table/README.md create mode 100644 crates/nu-term-grid/README.md create mode 100644 crates/nu-test-support/README.md create mode 100644 crates/nu-utils/README.md diff --git a/crates/nu-cli/README.md b/crates/nu-cli/README.md new file mode 100644 index 0000000000..e0eca3c9bd --- /dev/null +++ b/crates/nu-cli/README.md @@ -0,0 +1,7 @@ +This crate implements the core functionality of the interactive Nushell REPL and interfaces with `reedline`. +Currently implements the syntax highlighting and completions logic. +Furthermore includes a few commands that are specific to `reedline` + +## Internal Nushell crate + +This crate implements components of Nushell and is not designed to support plugin authors or other users directly. diff --git a/crates/nu-cli/src/lib.rs b/crates/nu-cli/src/lib.rs index 6f151adad1..d584802cfb 100644 --- a/crates/nu-cli/src/lib.rs +++ b/crates/nu-cli/src/lib.rs @@ -1,3 +1,4 @@ +#![doc = include_str!("../README.md")] mod commands; mod completions; mod config_files; diff --git a/crates/nu-cmd-base/README.md b/crates/nu-cmd-base/README.md new file mode 100644 index 0000000000..28d299928a --- /dev/null +++ b/crates/nu-cmd-base/README.md @@ -0,0 +1,5 @@ +Utilities used by the different `nu-command`/`nu-cmd-*` crates, should not contain any full `Command` implementations. + +## Internal Nushell crate + +This crate implements components of Nushell and is not designed to support plugin authors or other users directly. diff --git a/crates/nu-cmd-base/src/lib.rs b/crates/nu-cmd-base/src/lib.rs index 250a57d741..c2608f8466 100644 --- a/crates/nu-cmd-base/src/lib.rs +++ b/crates/nu-cmd-base/src/lib.rs @@ -1,3 +1,4 @@ +#![doc = include_str!("../README.md")] pub mod formats; pub mod hook; pub mod input_handler; diff --git a/crates/nu-cmd-extra/src/lib.rs b/crates/nu-cmd-extra/src/lib.rs index 2a3d70e6cd..f6f97c2f96 100644 --- a/crates/nu-cmd-extra/src/lib.rs +++ b/crates/nu-cmd-extra/src/lib.rs @@ -1,3 +1,4 @@ +#![doc = include_str!("../README.md")] mod example_test; pub mod extra; pub use extra::*; diff --git a/crates/nu-cmd-lang/src/lib.rs b/crates/nu-cmd-lang/src/lib.rs index 427a535d5d..41022cc231 100644 --- a/crates/nu-cmd-lang/src/lib.rs +++ b/crates/nu-cmd-lang/src/lib.rs @@ -1,3 +1,4 @@ +#![doc = include_str!("../README.md")] mod core_commands; mod default_context; pub mod example_support; diff --git a/crates/nu-color-config/README.md b/crates/nu-color-config/README.md new file mode 100644 index 0000000000..f03753685c --- /dev/null +++ b/crates/nu-color-config/README.md @@ -0,0 +1,5 @@ +Logic to resolve colors for syntax highlighting and output formatting + +## Internal Nushell crate + +This crate implements components of Nushell and is not designed to support plugin authors or other users directly. diff --git a/crates/nu-color-config/src/lib.rs b/crates/nu-color-config/src/lib.rs index 3bd4dc9e4d..77ec9d0e79 100644 --- a/crates/nu-color-config/src/lib.rs +++ b/crates/nu-color-config/src/lib.rs @@ -1,3 +1,4 @@ +#![doc = include_str!("../README.md")] mod color_config; mod matching_brackets_style; mod nu_style; diff --git a/crates/nu-command/README.md b/crates/nu-command/README.md new file mode 100644 index 0000000000..1cbb8cd9f2 --- /dev/null +++ b/crates/nu-command/README.md @@ -0,0 +1,7 @@ +This crate contains the majority of our commands + +We allow ourselves to move some of the commands in `nu-command` to `nu-cmd-*` crates as needed. + +## Internal Nushell crate + +This crate implements components of Nushell and is not designed to support plugin authors or other users directly. diff --git a/crates/nu-command/src/lib.rs b/crates/nu-command/src/lib.rs index a854fe3507..55c4974325 100644 --- a/crates/nu-command/src/lib.rs +++ b/crates/nu-command/src/lib.rs @@ -1,3 +1,4 @@ +#![doc = include_str!("../README.md")] mod bytes; mod charting; mod conversions; diff --git a/crates/nu-engine/README.md b/crates/nu-engine/README.md new file mode 100644 index 0000000000..d5198cfc3f --- /dev/null +++ b/crates/nu-engine/README.md @@ -0,0 +1,9 @@ +This crate primarily drives the evaluation of expressions. + +(Some overlap with nu-protocol) + +- Provides `CallExt` + +## Internal Nushell crate + +This crate implements components of Nushell and is not designed to support plugin authors or other users directly. diff --git a/crates/nu-engine/src/lib.rs b/crates/nu-engine/src/lib.rs index 7ed246e975..5a5bb9a5d0 100644 --- a/crates/nu-engine/src/lib.rs +++ b/crates/nu-engine/src/lib.rs @@ -1,3 +1,4 @@ +#![doc = include_str!("../README.md")] mod call_ext; mod closure_eval; pub mod column; diff --git a/crates/nu-explore/README.md b/crates/nu-explore/README.md new file mode 100644 index 0000000000..d18a576c43 --- /dev/null +++ b/crates/nu-explore/README.md @@ -0,0 +1,5 @@ +Implementation of the interactive `explore` command pager. + +## Internal Nushell crate + +This crate implements components of Nushell and is not designed to support plugin authors or other users directly. diff --git a/crates/nu-explore/src/lib.rs b/crates/nu-explore/src/lib.rs index 043c85eb02..d645572c89 100644 --- a/crates/nu-explore/src/lib.rs +++ b/crates/nu-explore/src/lib.rs @@ -1,3 +1,4 @@ +#![doc = include_str!("../README.md")] mod commands; mod default_context; mod explore; diff --git a/crates/nu-json/README.md b/crates/nu-json/README.md index acf3c6b296..c982f1a818 100644 --- a/crates/nu-json/README.md +++ b/crates/nu-json/README.md @@ -24,7 +24,7 @@ nu-json = "0.76" ## From the Commandline Add with: -``` +```sh cargo add serde cargo add nu-json ``` @@ -43,7 +43,7 @@ fn main() { let sample_text=r#" { - # specify rate in requests/second + ## specify rate in requests/second rate: 1000 array: [ diff --git a/crates/nu-json/src/lib.rs b/crates/nu-json/src/lib.rs index eead282786..1cf2c0a9fd 100644 --- a/crates/nu-json/src/lib.rs +++ b/crates/nu-json/src/lib.rs @@ -1,3 +1,4 @@ +#![doc = include_str!("../README.md")] pub use self::de::{ from_iter, from_reader, from_slice, from_str, Deserializer, StreamDeserializer, }; diff --git a/crates/nu-lsp/README.md b/crates/nu-lsp/README.md new file mode 100644 index 0000000000..d9e666d6c2 --- /dev/null +++ b/crates/nu-lsp/README.md @@ -0,0 +1,7 @@ +Implementation of the Nushell language server. + +See [the Language Server Protocol specification](https://microsoft.github.io/language-server-protocol/) + +## Internal Nushell crate + +This crate implements components of Nushell and is not designed to support plugin authors or other users directly. diff --git a/crates/nu-lsp/src/lib.rs b/crates/nu-lsp/src/lib.rs index fdeededac5..aee2ab240d 100644 --- a/crates/nu-lsp/src/lib.rs +++ b/crates/nu-lsp/src/lib.rs @@ -1,3 +1,4 @@ +#![doc = include_str!("../README.md")] use lsp_server::{Connection, IoThreads, Message, Response, ResponseError}; use lsp_types::{ request::{Completion, GotoDefinition, HoverRequest, Request}, diff --git a/crates/nu-parser/README.md b/crates/nu-parser/README.md index 02f61a31a6..cbb7729e4d 100644 --- a/crates/nu-parser/README.md +++ b/crates/nu-parser/README.md @@ -4,7 +4,7 @@ Nushell's parser is a type-directed parser, meaning that the parser will use typ Nushell's base language is whitespace-separated tokens with the command (Nushell's term for a function) name in the head position: -``` +```nushell head1 arg1 arg2 | head2 ``` @@ -12,7 +12,7 @@ head1 arg1 arg2 | head2 The first job of the parser is to a lexical analysis to find where the tokens start and end in the input. This turns the above into: -``` +```text , , , , ``` @@ -24,7 +24,7 @@ As Nushell is a language of pipelines, pipes form a key role in both separating The above tokens are converted the following during the lite parse phase: -``` +```text Pipeline: Command #1: , , @@ -45,7 +45,7 @@ Each command has a shape assigned to each of the arguments it reads in. These sh For example, if the command is written as: -```sql +```text where $x > 10 ``` @@ -53,7 +53,7 @@ When the parsing happens, the parser will look up the `where` command and find i In the above example, if the Signature of `where` said that it took three String values, the result would be: -``` +```text CallInfo: Name: `where` Args: @@ -64,7 +64,7 @@ CallInfo: Or, the Signature could state that it takes in three positional arguments: a Variable, an Operator, and a Number, which would give: -``` +```text CallInfo: Name: `where` Args: @@ -77,7 +77,7 @@ Note that in this case, each would be checked at compile time to confirm that th Finally, some Shapes can consume more than one token. In the above, if the `where` command stated it took in a single required argument, and that the Shape of this argument was a MathExpression, then the parser would treat the remaining tokens as part of the math expression. -``` +```text CallInfo: Name: `where` Args: diff --git a/crates/nu-parser/src/lib.rs b/crates/nu-parser/src/lib.rs index 3c97012e8a..8f871aa815 100644 --- a/crates/nu-parser/src/lib.rs +++ b/crates/nu-parser/src/lib.rs @@ -1,3 +1,4 @@ +#![doc = include_str!("../README.md")] mod deparse; mod exportable; mod flatten; diff --git a/crates/nu-path/src/lib.rs b/crates/nu-path/src/lib.rs index 8553495439..2cb415264a 100644 --- a/crates/nu-path/src/lib.rs +++ b/crates/nu-path/src/lib.rs @@ -1,3 +1,4 @@ +#![doc = include_str!("../README.md")] mod assert_path_eq; mod components; pub mod dots; diff --git a/crates/nu-protocol/src/lib.rs b/crates/nu-protocol/src/lib.rs index cae5d3fd0a..e143a19819 100644 --- a/crates/nu-protocol/src/lib.rs +++ b/crates/nu-protocol/src/lib.rs @@ -1,3 +1,4 @@ +#![doc = include_str!("../README.md")] mod alias; pub mod ast; pub mod config; diff --git a/crates/nu-protocol/src/pipeline/byte_stream.rs b/crates/nu-protocol/src/pipeline/byte_stream.rs index 69391b39a9..bd384e88c2 100644 --- a/crates/nu-protocol/src/pipeline/byte_stream.rs +++ b/crates/nu-protocol/src/pipeline/byte_stream.rs @@ -159,14 +159,14 @@ impl From for Type { /// Try not to use this method if possible. Rather, please use [`reader`](ByteStream::reader) /// (or [`lines`](ByteStream::lines) if it matches the situation). /// -/// Additionally, there are few methods to collect a [`Bytestream`] into memory: +/// Additionally, there are few methods to collect a [`ByteStream`] into memory: /// - [`into_bytes`](ByteStream::into_bytes): collects all bytes into a [`Vec`]. /// - [`into_string`](ByteStream::into_string): collects all bytes into a [`String`], erroring if utf-8 decoding failed. /// - [`into_value`](ByteStream::into_value): collects all bytes into a value typed appropriately /// for the [type](.type_()) of this stream. If the type is [`Unknown`](ByteStreamType::Unknown), /// it will produce a string value if the data is valid UTF-8, or a binary value otherwise. /// -/// There are also a few other methods to consume all the data of a [`Bytestream`]: +/// There are also a few other methods to consume all the data of a [`ByteStream`]: /// - [`drain`](ByteStream::drain): consumes all bytes and outputs nothing. /// - [`write_to`](ByteStream::write_to): writes all bytes to the given [`Write`] destination. /// - [`print`](ByteStream::print): a convenience wrapper around [`write_to`](ByteStream::write_to). diff --git a/crates/nu-std/README.md b/crates/nu-std/README.md index d6e4d6e9f6..23bd684a50 100644 --- a/crates/nu-std/README.md +++ b/crates/nu-std/README.md @@ -7,7 +7,7 @@ The standard library is a pure-`nushell` collection of custom commands which provide interactive utilities and building blocks for users writing casual scripts or complex applications. To see what's here: -``` +```text > use std > scope commands | select name usage | where name =~ "std " #┬───────────name────────────┬──────────────────────usage────────────────────── diff --git a/crates/nu-std/src/lib.rs b/crates/nu-std/src/lib.rs index a20e3fc5da..ff6910e4ba 100644 --- a/crates/nu-std/src/lib.rs +++ b/crates/nu-std/src/lib.rs @@ -1,3 +1,4 @@ +#![doc = include_str!("../README.md")] use log::trace; use nu_engine::eval_block; use nu_parser::parse; diff --git a/crates/nu-system/README.md b/crates/nu-system/README.md new file mode 100644 index 0000000000..d847bf4e0f --- /dev/null +++ b/crates/nu-system/README.md @@ -0,0 +1,7 @@ +Operating system specific bindings used by Nushell. + +Currently primarily wrappers around processes and ways to gather process info from the system + +## Internal Nushell crate + +This crate implements components of Nushell and is not designed to support plugin authors or other users directly. diff --git a/crates/nu-system/src/lib.rs b/crates/nu-system/src/lib.rs index 2fc97d2e7e..3633d62990 100644 --- a/crates/nu-system/src/lib.rs +++ b/crates/nu-system/src/lib.rs @@ -1,3 +1,4 @@ +#![doc = include_str!("../README.md")] mod foreground; #[cfg(target_os = "freebsd")] diff --git a/crates/nu-table/README.md b/crates/nu-table/README.md new file mode 100644 index 0000000000..945d515e62 --- /dev/null +++ b/crates/nu-table/README.md @@ -0,0 +1,7 @@ +The layout logic for Nushell's table viewer. + +See also the separate `table` command implementation + +## Internal Nushell crate + +This crate implements components of Nushell and is not designed to support plugin authors or other users directly. diff --git a/crates/nu-table/src/lib.rs b/crates/nu-table/src/lib.rs index 66cc2a3a82..182585f193 100644 --- a/crates/nu-table/src/lib.rs +++ b/crates/nu-table/src/lib.rs @@ -1,3 +1,4 @@ +#![doc = include_str!("../README.md")] mod table; mod table_theme; mod types; diff --git a/crates/nu-term-grid/README.md b/crates/nu-term-grid/README.md new file mode 100644 index 0000000000..5bdd32077d --- /dev/null +++ b/crates/nu-term-grid/README.md @@ -0,0 +1,5 @@ +Implementation of the layout engine for the `grid` command. + +## Internal Nushell crate + +This crate implements components of Nushell and is not designed to support plugin authors or other users directly. diff --git a/crates/nu-term-grid/src/lib.rs b/crates/nu-term-grid/src/lib.rs index 79b146593f..ca8ccd4e23 100644 --- a/crates/nu-term-grid/src/lib.rs +++ b/crates/nu-term-grid/src/lib.rs @@ -1,3 +1,4 @@ +#![doc = include_str!("../README.md")] pub mod grid; pub use grid::Grid; diff --git a/crates/nu-test-support/README.md b/crates/nu-test-support/README.md new file mode 100644 index 0000000000..f384b15067 --- /dev/null +++ b/crates/nu-test-support/README.md @@ -0,0 +1,7 @@ +This crate provides utilities for testing of Nushell + +Plugin authors should instead refer to `nu-plugin-test-support` + +## Internal Nushell crate + +This crate implements components of Nushell and is not designed to support plugin authors or other users directly. diff --git a/crates/nu-test-support/src/lib.rs b/crates/nu-test-support/src/lib.rs index 5319830920..c6cadbcbd8 100644 --- a/crates/nu-test-support/src/lib.rs +++ b/crates/nu-test-support/src/lib.rs @@ -1,3 +1,4 @@ +#![doc = include_str!("../README.md")] pub mod commands; pub mod fs; pub mod locale_override; diff --git a/crates/nu-utils/README.md b/crates/nu-utils/README.md new file mode 100644 index 0000000000..de50a8ae93 --- /dev/null +++ b/crates/nu-utils/README.md @@ -0,0 +1,6 @@ +Collection of small utilities that are shared across Nushell crates. + +This crate should compile early in the crate graph and thus not depend on major dependencies or core-nushell crates itself. +## Internal Nushell crate + +This crate implements components of Nushell and is not designed to support plugin authors or other users directly. diff --git a/crates/nu-utils/src/lib.rs b/crates/nu-utils/src/lib.rs index cb834e5e0f..0c021f1dc9 100644 --- a/crates/nu-utils/src/lib.rs +++ b/crates/nu-utils/src/lib.rs @@ -1,3 +1,4 @@ +#![doc = include_str!("../README.md")] mod casing; mod deansi; pub mod emoji; diff --git a/crates/nuon/README.md b/crates/nuon/README.md index d11aa8104c..158a9d2e7d 100644 --- a/crates/nuon/README.md +++ b/crates/nuon/README.md @@ -4,7 +4,7 @@ The NUON format is a superset of JSON designed to fit the feel of Nushell. Some of its extra features are - trailing commas are allowed - commas are optional in lists -- quotes are not required around keys or any _bare_ string that do not contain spaces +- quotes are not required around keys or any _bare_ string that do not contain spaces or special characters - comments are allowed, though not preserved when using [`from_nuon`] ## Example From ae40d56fc59565588ec1f004f70e5445cf54c727 Mon Sep 17 00:00:00 2001 From: Devyn Cairns Date: Sun, 14 Jul 2024 01:12:55 -0700 Subject: [PATCH 045/126] Report parse warns and compile errs when running script files (#13369) # Description Report parse warnings and compile errors when running script files. It's useful to report this information to script authors and users so they can know if they need to update something in the near future. I also found that moving some errors from eval to compile time meant some error tests can fail (in `tests/repl`) so this is a good idea to keep those passing. # User-Facing Changes - Report parse warnings when running script files - Report compile errors when running script files --- crates/nu-cli/src/eval_file.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/crates/nu-cli/src/eval_file.rs b/crates/nu-cli/src/eval_file.rs index ff6ba36fe3..bac4378d1b 100644 --- a/crates/nu-cli/src/eval_file.rs +++ b/crates/nu-cli/src/eval_file.rs @@ -76,12 +76,21 @@ pub fn evaluate_file( trace!("parsing file: {}", file_path_str); let block = parse(&mut working_set, Some(file_path_str), &file, false); + if let Some(warning) = working_set.parse_warnings.first() { + report_error(&working_set, warning); + } + // If any parse errors were found, report the first error and exit. if let Some(err) = working_set.parse_errors.first() { report_error(&working_set, err); std::process::exit(1); } + if let Some(err) = working_set.compile_errors.first() { + report_error(&working_set, err); + // Not a fatal error, for now + } + // Look for blocks whose name starts with "main" and replace it with the filename. for block in working_set.delta.blocks.iter_mut().map(Arc::make_mut) { if block.signature.name == "main" { From cf4864a9cd4e0d840fef429d45b2f724b187c233 Mon Sep 17 00:00:00 2001 From: David Date: Sun, 14 Jul 2024 09:19:09 +0100 Subject: [PATCH 046/126] JSON format output keeps braces on same line (issue #13326) (#13352) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description This is a minor breaking change to JSON output syntax/style of the `to json` command. This fixes #13326 by setting `braces_same_line` to true when creating a new `HjsonFormatter`. This then simply tells `HjsonFormatter` to keep the braces on the same line when outputting which is what I expected nu's `to json` command to do. There are almost no changes to nushell itself, all changes are contained within `nu-json` crate (minus any documentation updates). Oh, almost forgot to mention, to get the tests compiling, I added fancy_regex as a _dev_ dependency to nu-json. I could look into eliminating that if desirable. # User-Facing Changes **Breaking Change** nushell now outputs the desired result using the reproduction command from the issue: ``` echo '{"version": "v0.4.4","notes": "blablabla","pub_date": "2024-05-04T16:05:00Z","platforms":{"windows-x86_64":{"signature": "blablabla","url": "https://blablabla"}}}' | from json | to json ``` outputs: ``` { "version": "v0.4.4", "notes": "blablabla", "pub_date": "2024-05-04T16:05:00Z", "platforms": { "windows-x86_64": { "signature": "blablabla", "url": "https://blablabla" } } } ``` whereas previously it would push the opening braces onto a new line: ``` { "version": "v0.4.4", "notes": "blablabla", "pub_date": "2024-05-04T16:05:00Z", "platforms": { "windows-x86_64": { "signature": "blablabla", "url": "https://blablabla" } } } ``` # Tests + Formatting toolkit check pr mostly passes - there are regrettably some tests not passing on my windows machine _before making any changes_ (I may look into this as a separate issue) I have re-enabled the [hjson tests](https://github.com/nushell/nushell/blob/main/crates/nu-json/tests/main.rs). This is done in the second commit 🙂 They have a crucial difference to what they were previously asserting: * nu-json outputs in json syntax, not hjson syntax I think this is desirable, but I'm not aware of the history of these tests. # After Submitting I suspect there `to json` command examples will need updating to match, haven't checked yet! --- Cargo.lock | 3 +++ crates/nu-json/Cargo.toml | 13 +++++++++---- crates/nu-json/src/ser.rs | 2 +- crates/nu-json/tests/main.rs | 27 ++++++++------------------- 4 files changed, 21 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c8bfe4ab4e..2247e462fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3195,7 +3195,10 @@ dependencies = [ name = "nu-json" version = "0.95.1" dependencies = [ + "fancy-regex", "linked-hash-map", + "nu-path", + "nu-test-support", "num-traits", "serde", "serde_json", diff --git a/crates/nu-json/Cargo.toml b/crates/nu-json/Cargo.toml index 04c39bdac5..a3b61042b1 100644 --- a/crates/nu-json/Cargo.toml +++ b/crates/nu-json/Cargo.toml @@ -1,5 +1,8 @@ [package] -authors = ["The Nushell Project Developers", "Christian Zangl "] +authors = [ + "The Nushell Project Developers", + "Christian Zangl ", +] description = "Fork of serde-hjson" repository = "https://github.com/nushell/nushell/tree/main/crates/nu-json" edition = "2021" @@ -19,9 +22,11 @@ default = ["preserve_order"] [dependencies] linked-hash-map = { version = "0.5", optional = true } num-traits = { workspace = true } -serde = { workspace = true } +serde = { workspace = true } serde_json = { workspace = true } [dev-dependencies] -# nu-path = { path="../nu-path", version = "0.95.1" } -# serde_json = "1.0" \ No newline at end of file +nu-test-support = { path = "../nu-test-support", version = "0.95.1" } +nu-path = { path = "../nu-path", version = "0.95.1" } +serde_json = "1.0" +fancy-regex = "0.13.0" diff --git a/crates/nu-json/src/ser.rs b/crates/nu-json/src/ser.rs index d9df1aea56..4b15740602 100644 --- a/crates/nu-json/src/ser.rs +++ b/crates/nu-json/src/ser.rs @@ -714,7 +714,7 @@ impl<'a> HjsonFormatter<'a> { stack: Vec::new(), at_colon: false, indent, - braces_same_line: false, + braces_same_line: true, } } } diff --git a/crates/nu-json/tests/main.rs b/crates/nu-json/tests/main.rs index c209bc0a52..0ac3e403e2 100644 --- a/crates/nu-json/tests/main.rs +++ b/crates/nu-json/tests/main.rs @@ -1,7 +1,5 @@ -// FIXME: re-enable tests -/* -use nu_json::Value; use fancy_regex::Regex; +use nu_json::Value; use std::fs; use std::io; use std::path::{Path, PathBuf}; @@ -11,7 +9,7 @@ fn txt(text: &str) -> String { #[cfg(windows)] { - out.replace("\r\n", "").replace("\n", "") + out.replace("\r\n", "").replace('\n', "") } #[cfg(not(windows))] @@ -21,15 +19,7 @@ fn txt(text: &str) -> String { } fn hjson_expectations() -> PathBuf { - let assets = nu_test_support::fs::assets().join("nu_json"); - - nu_path::canonicalize(assets.clone()).unwrap_or_else(|e| { - panic!( - "Couldn't canonicalize hjson assets path {}: {:?}", - assets.display(), - e - ) - }) + nu_test_support::fs::assets().join("nu_json") } fn get_test_content(name: &str) -> io::Result { @@ -50,7 +40,7 @@ fn get_result_content(name: &str) -> io::Result<(String, String)> { let p1 = format!("{}/{}_result.json", expectations.display(), name); let p2 = format!("{}/{}_result.hjson", expectations.display(), name); - Ok((fs::read_to_string(&p1)?, fs::read_to_string(&p2)?)) + Ok((fs::read_to_string(p1)?, fs::read_to_string(p2)?)) } macro_rules! run_test { @@ -73,7 +63,8 @@ macro_rules! run_test { let actual_hjson = txt(&actual_hjson); let actual_json = $fix(serde_json::to_string_pretty(&udata).unwrap()); let actual_json = txt(&actual_json); - if rhjson != actual_hjson { + // nu_json::to_string now outputs json instead of hjson! + if rjson != actual_hjson { println!( "{:?}\n---hjson expected\n{}\n---hjson actual\n{}\n---\n", name, rhjson, actual_hjson @@ -85,7 +76,7 @@ macro_rules! run_test { name, rjson, actual_json ); } - assert!(rhjson == actual_hjson && rjson == actual_json); + assert!(rjson == actual_hjson && rjson == actual_json); } }}; } @@ -198,7 +189,7 @@ fn test_hjson() { let missing = all .into_iter() - .filter(|x| done.iter().find(|y| &x == y) == None) + .filter(|x| !done.iter().any(|y| x == y)) .collect::>(); if !missing.is_empty() { @@ -208,5 +199,3 @@ fn test_hjson() { panic!(); } } - -*/ From 3d1145e75983de00354edda0ea8dfd2629bba06f Mon Sep 17 00:00:00 2001 From: Stefan Holderbach Date: Sun, 14 Jul 2024 10:37:57 +0200 Subject: [PATCH 047/126] Fix CI test failure on main (nu-json) (#13374) Conflict resulting from #13329 and #13326 --- crates/nu-json/tests/main.rs | 2 +- crates/nu-test-support/src/fs.rs | 13 ++++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/crates/nu-json/tests/main.rs b/crates/nu-json/tests/main.rs index 0ac3e403e2..73c68f3d8e 100644 --- a/crates/nu-json/tests/main.rs +++ b/crates/nu-json/tests/main.rs @@ -19,7 +19,7 @@ fn txt(text: &str) -> String { } fn hjson_expectations() -> PathBuf { - nu_test_support::fs::assets().join("nu_json") + nu_test_support::fs::assets().join("nu_json").into() } fn get_test_content(name: &str) -> io::Result { diff --git a/crates/nu-test-support/src/fs.rs b/crates/nu-test-support/src/fs.rs index eb90e16ab2..230e5eafe3 100644 --- a/crates/nu-test-support/src/fs.rs +++ b/crates/nu-test-support/src/fs.rs @@ -102,13 +102,12 @@ pub fn fixtures() -> AbsolutePathBuf { path } -// FIXME: re-enable nu_json tests -// pub fn assets() -> AbsolutePathBuf { -// let mut path = root(); -// path.push("tests"); -// path.push("assets"); -// path -// } +pub fn assets() -> AbsolutePathBuf { + let mut path = root(); + path.push("tests"); + path.push("assets"); + path +} pub fn in_directory(path: impl AsRef) -> AbsolutePathBuf { root().join(path) From 0918050ac84145900f556c9c1bb4d3e98dfe9e48 Mon Sep 17 00:00:00 2001 From: Ian Manske Date: Tue, 16 Jul 2024 03:49:00 +0000 Subject: [PATCH 048/126] Deprecate `group` in favor of `chunks` (#13377) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description The name of the `group` command is a little unclear/ambiguous. Everything I look at it, I think of `group-by`. I think `chunks` more clearly conveys what the `group` command does. Namely, it divides the input list into chunks of a certain size. For example, [`slice::chunks`](https://doc.rust-lang.org/std/primitive.slice.html#method.chunks) has the same name. So, this PR adds a new `chunks` command to replace the now deprecated `group` command. The `chunks` command is a refactored version of `group`. As such, there is a small performance improvement: ```nushell # $data is a very large list > bench { $data | chunks 2 } --rounds 30 | get mean 474ms 921µs 190ns # deprecation warning was disabled here for fairness > bench { $data | group 2 } --rounds 30 | get mean 592ms 702µs 440ns > bench { $data | chunks 200 } --rounds 30 | get mean 374ms 188µs 318ns > bench { $data | group 200 } --rounds 30 | get mean 481ms 264µs 869ns > bench { $data | chunks 1 } --rounds 30 | get mean 642ms 574µs 42ns > bench { $data | group 1 } --rounds 30 | get mean 981ms 602µs 513ns ``` # User-Facing Changes - `group` command has been deprecated in favor of new `chunks` command. - `chunks` errors when given a chunk size of `0` whereas `group` returns chunks with one element. # Tests + Formatting Added tests for `chunks`, since `group` did not have any tests. # After Submitting Update book if necessary. --- crates/nu-command/src/default_context.rs | 1 + crates/nu-command/src/filters/chunks.rs | 153 ++++++++++++++++++ crates/nu-command/src/filters/group.rs | 13 +- crates/nu-command/src/filters/mod.rs | 2 + crates/nu-command/tests/commands/chunks.rs | 43 +++++ crates/nu-command/tests/commands/mod.rs | 1 + crates/nu-protocol/src/errors/cli_error.rs | 20 +++ crates/nu-protocol/src/errors/mod.rs | 4 +- .../nu-protocol/src/errors/parse_warning.rs | 4 +- .../nu-protocol/src/pipeline/pipeline_data.rs | 28 ++++ crates/nu-protocol/src/ty.rs | 4 + 11 files changed, 269 insertions(+), 4 deletions(-) create mode 100644 crates/nu-command/src/filters/chunks.rs create mode 100644 crates/nu-command/tests/commands/chunks.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 4f35a1c711..4adcfe9214 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -31,6 +31,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState { All, Any, Append, + Chunks, Columns, Compact, Default, diff --git a/crates/nu-command/src/filters/chunks.rs b/crates/nu-command/src/filters/chunks.rs new file mode 100644 index 0000000000..11a59c2aa9 --- /dev/null +++ b/crates/nu-command/src/filters/chunks.rs @@ -0,0 +1,153 @@ +use nu_engine::command_prelude::*; +use nu_protocol::ListStream; + +#[derive(Clone)] +pub struct Chunks; + +impl Command for Chunks { + fn name(&self) -> &str { + "chunks" + } + + fn signature(&self) -> Signature { + Signature::build("chunks") + .input_output_types(vec![ + (Type::table(), Type::list(Type::table())), + (Type::list(Type::Any), Type::list(Type::list(Type::Any))), + ]) + .required("chunk_size", SyntaxShape::Int, "The size of each chunk.") + .category(Category::Filters) + } + + fn usage(&self) -> &str { + "Divide a list or table into chunks of `chunk_size`." + } + + fn extra_usage(&self) -> &str { + "This command will error if `chunk_size` is negative or zero." + } + + fn search_terms(&self) -> Vec<&str> { + vec!["batch", "group"] + } + + fn examples(&self) -> Vec { + vec![ + Example { + example: "[1 2 3 4] | chunks 2", + description: "Chunk a list into pairs", + result: Some(Value::test_list(vec![ + Value::test_list(vec![Value::test_int(1), Value::test_int(2)]), + Value::test_list(vec![Value::test_int(3), Value::test_int(4)]), + ])), + }, + Example { + example: "[[foo bar]; [0 1] [2 3] [4 5] [6 7] [8 9]] | chunks 3", + description: "Chunk the rows of a table into triplets", + result: Some(Value::test_list(vec![ + Value::test_list(vec![ + Value::test_record(record! { + "foo" => Value::test_int(0), + "bar" => Value::test_int(1), + }), + Value::test_record(record! { + "foo" => Value::test_int(2), + "bar" => Value::test_int(3), + }), + Value::test_record(record! { + "foo" => Value::test_int(4), + "bar" => Value::test_int(5), + }), + ]), + Value::test_list(vec![ + Value::test_record(record! { + "foo" => Value::test_int(6), + "bar" => Value::test_int(7), + }), + Value::test_record(record! { + "foo" => Value::test_int(8), + "bar" => Value::test_int(9), + }), + ]), + ])), + }, + ] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + let head = call.head; + let chunk_size: Value = call.req(engine_state, stack, 0)?; + + let size = + usize::try_from(chunk_size.as_int()?).map_err(|_| ShellError::NeedsPositiveValue { + span: chunk_size.span(), + })?; + + if size == 0 { + return Err(ShellError::IncorrectValue { + msg: "`chunk_size` cannot be zero".into(), + val_span: chunk_size.span(), + call_span: head, + }); + } + + match input { + PipelineData::Value(Value::List { vals, .. }, metadata) => { + let chunks = ChunksIter::new(vals, size, head); + let stream = ListStream::new(chunks, head, engine_state.signals().clone()); + Ok(PipelineData::ListStream(stream, metadata)) + } + PipelineData::ListStream(stream, metadata) => { + let stream = stream.modify(|iter| ChunksIter::new(iter, size, head)); + Ok(PipelineData::ListStream(stream, metadata)) + } + input => Err(input.unsupported_input_error("list", head)), + } + } +} + +struct ChunksIter> { + iter: I, + size: usize, + span: Span, +} + +impl> ChunksIter { + fn new(iter: impl IntoIterator, size: usize, span: Span) -> Self { + Self { + iter: iter.into_iter(), + size, + span, + } + } +} + +impl> Iterator for ChunksIter { + type Item = Value; + + fn next(&mut self) -> Option { + let first = self.iter.next()?; + let mut chunk = Vec::with_capacity(self.size); // delay allocation to optimize for empty iter + chunk.push(first); + chunk.extend((&mut self.iter).take(self.size - 1)); + Some(Value::list(chunk, self.span)) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(Chunks {}) + } +} diff --git a/crates/nu-command/src/filters/group.rs b/crates/nu-command/src/filters/group.rs index 13b53850d2..60c720e325 100644 --- a/crates/nu-command/src/filters/group.rs +++ b/crates/nu-command/src/filters/group.rs @@ -1,5 +1,5 @@ use nu_engine::command_prelude::*; -use nu_protocol::ValueIterator; +use nu_protocol::{report_warning_new, ParseWarning, ValueIterator}; #[derive(Clone)] pub struct Group; @@ -54,6 +54,17 @@ impl Command for Group { input: PipelineData, ) -> Result { let head = call.head; + + report_warning_new( + engine_state, + &ParseWarning::DeprecatedWarning { + old_command: "group".into(), + new_suggestion: "the new `chunks` command".into(), + span: head, + url: "`help chunks`".into(), + }, + ); + let group_size: Spanned = call.req(engine_state, stack, 0)?; let metadata = input.metadata(); diff --git a/crates/nu-command/src/filters/mod.rs b/crates/nu-command/src/filters/mod.rs index f43bdb10b2..4ffb0fc6db 100644 --- a/crates/nu-command/src/filters/mod.rs +++ b/crates/nu-command/src/filters/mod.rs @@ -1,6 +1,7 @@ mod all; mod any; mod append; +mod chunks; mod columns; mod compact; mod default; @@ -58,6 +59,7 @@ mod zip; pub use all::All; pub use any::Any; pub use append::Append; +pub use chunks::Chunks; pub use columns::Columns; pub use compact::Compact; pub use default::Default; diff --git a/crates/nu-command/tests/commands/chunks.rs b/crates/nu-command/tests/commands/chunks.rs new file mode 100644 index 0000000000..eb0c580f15 --- /dev/null +++ b/crates/nu-command/tests/commands/chunks.rs @@ -0,0 +1,43 @@ +use nu_test_support::nu; + +#[test] +fn chunk_size_negative() { + let actual = nu!("[0 1 2] | chunks -1"); + assert!(actual.err.contains("positive")); +} + +#[test] +fn chunk_size_zero() { + let actual = nu!("[0 1 2] | chunks 0"); + assert!(actual.err.contains("zero")); +} + +#[test] +fn chunk_size_not_int() { + let actual = nu!("[0 1 2] | chunks (if true { 1sec })"); + assert!(actual.err.contains("can't convert")); +} + +#[test] +fn empty() { + let actual = nu!("[] | chunks 2 | is-empty"); + assert_eq!(actual.out, "true"); +} + +#[test] +fn list_stream() { + let actual = nu!("([0 1 2] | every 1 | chunks 2) == ([0 1 2] | chunks 2)"); + assert_eq!(actual.out, "true"); +} + +#[test] +fn table_stream() { + let actual = nu!("([[foo bar]; [0 1] [2 3] [4 5]] | every 1 | chunks 2) == ([[foo bar]; [0 1] [2 3] [4 5]] | chunks 2)"); + assert_eq!(actual.out, "true"); +} + +#[test] +fn no_empty_chunks() { + let actual = nu!("([0 1 2 3 4 5] | chunks 3 | length) == 2"); + assert_eq!(actual.out, "true"); +} diff --git a/crates/nu-command/tests/commands/mod.rs b/crates/nu-command/tests/commands/mod.rs index a1cb82a4b4..4c9f8e86f8 100644 --- a/crates/nu-command/tests/commands/mod.rs +++ b/crates/nu-command/tests/commands/mod.rs @@ -7,6 +7,7 @@ mod break_; mod bytes; mod cal; mod cd; +mod chunks; mod compact; mod complete; mod config_env_default; diff --git a/crates/nu-protocol/src/errors/cli_error.rs b/crates/nu-protocol/src/errors/cli_error.rs index c12c601c4d..9ce6829b7b 100644 --- a/crates/nu-protocol/src/errors/cli_error.rs +++ b/crates/nu-protocol/src/errors/cli_error.rs @@ -47,6 +47,26 @@ pub fn report_error_new( report_error(&working_set, error); } +pub fn report_warning( + working_set: &StateWorkingSet, + error: &(dyn miette::Diagnostic + Send + Sync + 'static), +) { + eprintln!("Warning: {:?}", CliError(error, working_set)); + // reset vt processing, aka ansi because illbehaved externals can break it + #[cfg(windows)] + { + let _ = nu_utils::enable_vt_processing(); + } +} + +pub fn report_warning_new( + engine_state: &EngineState, + error: &(dyn miette::Diagnostic + Send + Sync + 'static), +) { + let working_set = StateWorkingSet::new(engine_state); + report_error(&working_set, error); +} + impl std::fmt::Debug for CliError<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let config = self.1.get_config(); diff --git a/crates/nu-protocol/src/errors/mod.rs b/crates/nu-protocol/src/errors/mod.rs index 3f895cd65f..59369cd54f 100644 --- a/crates/nu-protocol/src/errors/mod.rs +++ b/crates/nu-protocol/src/errors/mod.rs @@ -5,7 +5,9 @@ mod parse_error; mod parse_warning; mod shell_error; -pub use cli_error::{format_error, report_error, report_error_new}; +pub use cli_error::{ + format_error, report_error, report_error_new, report_warning, report_warning_new, +}; pub use compile_error::CompileError; pub use labeled_error::{ErrorLabel, LabeledError}; pub use parse_error::{DidYouMean, ParseError}; diff --git a/crates/nu-protocol/src/errors/parse_warning.rs b/crates/nu-protocol/src/errors/parse_warning.rs index a30bc731d8..5f34deb20c 100644 --- a/crates/nu-protocol/src/errors/parse_warning.rs +++ b/crates/nu-protocol/src/errors/parse_warning.rs @@ -6,11 +6,11 @@ use thiserror::Error; #[derive(Clone, Debug, Error, Diagnostic, Serialize, Deserialize)] pub enum ParseWarning { #[error("Deprecated: {old_command}")] - #[diagnostic(help("for more info: {url}"))] + #[diagnostic(help("for more info see {url}"))] DeprecatedWarning { old_command: String, new_suggestion: String, - #[label("`{old_command}` is deprecated and will be removed in a future release. Please {new_suggestion} instead")] + #[label("`{old_command}` is deprecated and will be removed in a future release. Please {new_suggestion} instead.")] span: Span, url: String, }, diff --git a/crates/nu-protocol/src/pipeline/pipeline_data.rs b/crates/nu-protocol/src/pipeline/pipeline_data.rs index a89337a6c2..0d1c0b3092 100644 --- a/crates/nu-protocol/src/pipeline/pipeline_data.rs +++ b/crates/nu-protocol/src/pipeline/pipeline_data.rs @@ -635,6 +635,34 @@ impl PipelineData { Ok(None) } } + + pub fn unsupported_input_error( + self, + expected_type: impl Into, + span: Span, + ) -> ShellError { + match self { + PipelineData::Empty => ShellError::PipelineEmpty { dst_span: span }, + PipelineData::Value(value, ..) => ShellError::OnlySupportsThisInputType { + exp_input_type: expected_type.into(), + wrong_type: value.get_type().get_non_specified_string(), + dst_span: span, + src_span: value.span(), + }, + PipelineData::ListStream(stream, ..) => ShellError::OnlySupportsThisInputType { + exp_input_type: expected_type.into(), + wrong_type: "list (stream)".into(), + dst_span: span, + src_span: stream.span(), + }, + PipelineData::ByteStream(stream, ..) => ShellError::OnlySupportsThisInputType { + exp_input_type: expected_type.into(), + wrong_type: stream.type_().describe().into(), + dst_span: span, + src_span: stream.span(), + }, + } + } } enum PipelineIteratorInner { diff --git a/crates/nu-protocol/src/ty.rs b/crates/nu-protocol/src/ty.rs index d5ea8c1554..7c004d81de 100644 --- a/crates/nu-protocol/src/ty.rs +++ b/crates/nu-protocol/src/ty.rs @@ -36,6 +36,10 @@ pub enum Type { } impl Type { + pub fn list(inner: Type) -> Self { + Self::List(Box::new(inner)) + } + pub fn record() -> Self { Self::Record([].into()) } From fcb8e36caa7551313e55640884bab4190a6adea6 Mon Sep 17 00:00:00 2001 From: Bruce Weirdan Date: Tue, 16 Jul 2024 05:54:24 +0200 Subject: [PATCH 049/126] Remove `default` list-diving behaviour (#13386) # Description Before this change `default` would dive into lists and replace `null` elements with the default value. Therefore there was no easy way to process `null|list` input and receive `list` (IOW to apply the default on the top level only). However it's trivially easy to apply default values to list elements with the `each` command: ```nushell [null, "a", null] | each { default "b" } ``` So there's no need to have this behavior in `default` command. # User-Facing Changes * `default` no longer dives into lists to replace `null` elements with the default value. # Tests + Formatting Added a couple of tests for the new (and old) behavior. # After Submitting * Update docs. --- crates/nu-command/src/filters/default.rs | 14 +++----------- crates/nu-command/tests/commands/default.rs | 12 ++++++++++++ 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/crates/nu-command/src/filters/default.rs b/crates/nu-command/src/filters/default.rs index a12d78f0b7..9b08ce6331 100644 --- a/crates/nu-command/src/filters/default.rs +++ b/crates/nu-command/src/filters/default.rs @@ -51,11 +51,11 @@ impl Command for Default { description: "Get the env value of `MY_ENV` with a default value 'abc' if not present", example: "$env | get --ignore-errors MY_ENV | default 'abc'", - result: None, // Some(Value::test_string("abc")), + result: Some(Value::test_string("abc")), }, Example { description: "Replace the `null` value in a list", - example: "[1, 2, null, 4] | default 3", + example: "[1, 2, null, 4] | each { default 3 }", result: Some(Value::list( vec![ Value::test_int(1), @@ -113,15 +113,7 @@ fn default( } else if input.is_nothing() { Ok(value.into_pipeline_data()) } else { - input - .map( - move |item| match item { - Value::Nothing { .. } => value.clone(), - x => x, - }, - engine_state.signals(), - ) - .map(|x| x.set_metadata(metadata)) + Ok(input) } } diff --git a/crates/nu-command/tests/commands/default.rs b/crates/nu-command/tests/commands/default.rs index 4fd41d7ec0..6ecd14960a 100644 --- a/crates/nu-command/tests/commands/default.rs +++ b/crates/nu-command/tests/commands/default.rs @@ -32,3 +32,15 @@ fn default_after_empty_filter() { assert_eq!(actual.out, "d"); } + +#[test] +fn keeps_nulls_in_lists() { + let actual = nu!(r#"[null, 2, 3] | default [] | to json -r"#); + assert_eq!(actual.out, "[null,2,3]"); +} + +#[test] +fn replaces_null() { + let actual = nu!(r#"null | default 1"#); + assert_eq!(actual.out, "1"); +} From 1981c50c8fbef4f2fdb6f8d8d8a570394d3b8581 Mon Sep 17 00:00:00 2001 From: Ian Manske Date: Tue, 16 Jul 2024 09:28:31 +0000 Subject: [PATCH 050/126] Remove unused field in `StateWorkingSet` (#13387) # Description Removes the unused `external_commands` field from `StateWorkingSet`. --- crates/nu-protocol/src/engine/state_working_set.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/nu-protocol/src/engine/state_working_set.rs b/crates/nu-protocol/src/engine/state_working_set.rs index af1085c1ee..822bf35e3a 100644 --- a/crates/nu-protocol/src/engine/state_working_set.rs +++ b/crates/nu-protocol/src/engine/state_working_set.rs @@ -25,7 +25,6 @@ use crate::{PluginIdentity, PluginRegistryItem, RegisteredPlugin}; pub struct StateWorkingSet<'a> { pub permanent_state: &'a EngineState, pub delta: StateDelta, - pub external_commands: Vec>, pub files: FileStack, /// Whether or not predeclarations are searched when looking up a command (used with aliases) pub search_predecls: bool, @@ -46,7 +45,6 @@ impl<'a> StateWorkingSet<'a> { Self { delta: StateDelta::new(permanent_state), permanent_state, - external_commands: vec![], files, search_predecls: true, parse_errors: vec![], From b66671d339325dc34f96af4d893f9f94cafc4f0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Christian=20Gr=C3=BCnhage?= Date: Tue, 16 Jul 2024 14:16:26 +0200 Subject: [PATCH 051/126] Switch from dirs_next 2.0 to dirs 5.0 (#13384) # Description Replaces the `dirs_next` family of crates with `dirs`. `dirs_next` was born when the `dirs` crates were abandoned three years ago, but they're being maintained again and most projects depend on `dirs` nowadays. `dirs_next` has been abandoned since. This came up while working on https://github.com/nushell/nushell/pull/13382. # User-Facing Changes None. # Tests + Formatting Tests and formatter have been run. # After Submitting --- Cargo.lock | 30 +++++++++++-------- Cargo.toml | 6 ++-- crates/nu-command/Cargo.toml | 4 +-- crates/nu-command/src/system/run_external.rs | 2 +- crates/nu-command/tests/commands/cd.rs | 2 +- .../nu-command/tests/commands/run_external.rs | 2 +- crates/nu-path/Cargo.toml | 4 +-- crates/nu-path/src/helpers.rs | 8 ++--- crates/nu-path/src/tilde.rs | 4 +-- src/main.rs | 2 +- tests/repl/test_config_path.rs | 4 +-- 11 files changed, 37 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2247e462fa..0e63b83844 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1219,24 +1219,24 @@ dependencies = [ ] [[package]] -name = "dirs-next" -version = "2.0.0" +name = "dirs" +version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" dependencies = [ - "cfg-if", - "dirs-sys-next", + "dirs-sys", ] [[package]] -name = "dirs-sys-next" -version = "0.1.2" +name = "dirs-sys" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" dependencies = [ "libc", + "option-ext", "redox_users", - "winapi", + "windows-sys 0.48.0", ] [[package]] @@ -2873,7 +2873,7 @@ dependencies = [ "assert_cmd", "crossterm", "ctrlc", - "dirs-next", + "dirs", "log", "miette", "mimalloc", @@ -3047,7 +3047,7 @@ dependencies = [ "deunicode", "dialoguer", "digest", - "dirs-next", + "dirs", "dtparse", "encoding_rs", "fancy-regex", @@ -3245,7 +3245,7 @@ dependencies = [ name = "nu-path" version = "0.95.1" dependencies = [ - "dirs-next", + "dirs", "omnipath", "pwd", ] @@ -3836,6 +3836,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "ordered-multimap" version = "0.7.3" diff --git a/Cargo.toml b/Cargo.toml index a0b3ef27f5..20f41f1b49 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -83,7 +83,7 @@ ctrlc = "3.4" deunicode = "1.6.0" dialoguer = { default-features = false, version = "0.11" } digest = { default-features = false, version = "0.10" } -dirs-next = "2.0" +dirs = "5.0" dtparse = "2.0" encoding_rs = "0.8" fancy-regex = "0.13" @@ -201,7 +201,7 @@ reedline = { workspace = true, features = ["bashisms", "sqlite"] } crossterm = { workspace = true } ctrlc = { workspace = true } -dirs-next = { workspace = true } +dirs = { workspace = true } log = { workspace = true } miette = { workspace = true, features = ["fancy-no-backtrace", "fancy"] } mimalloc = { version = "0.1.42", default-features = false, optional = true } @@ -229,7 +229,7 @@ nu-test-support = { path = "./crates/nu-test-support", version = "0.95.1" } nu-plugin-protocol = { path = "./crates/nu-plugin-protocol", version = "0.95.1" } nu-plugin-core = { path = "./crates/nu-plugin-core", version = "0.95.1" } assert_cmd = "2.0" -dirs-next = { workspace = true } +dirs = { workspace = true } tango-bench = "0.5" pretty_assertions = { workspace = true } regex = { workspace = true } diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index 9c49a69ff5..ea501578b6 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -140,10 +140,10 @@ trash-support = ["trash"] nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.95.1" } nu-test-support = { path = "../nu-test-support", version = "0.95.1" } -dirs-next = { workspace = true } +dirs = { workspace = true } mockito = { workspace = true, default-features = false } quickcheck = { workspace = true } quickcheck_macros = { workspace = true } rstest = { workspace = true, default-features = false } pretty_assertions = { workspace = true } -tempfile = { workspace = true } \ No newline at end of file +tempfile = { workspace = true } diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs index 5e8527b45e..0b9f1950ea 100644 --- a/crates/nu-command/src/system/run_external.rs +++ b/crates/nu-command/src/system/run_external.rs @@ -606,7 +606,7 @@ mod test { assert_eq!(actual, expected); let actual = expand_glob("~/foo.txt", cwd, Span::unknown(), &Signals::empty()).unwrap(); - let home = dirs_next::home_dir().expect("failed to get home dir"); + let home = dirs::home_dir().expect("failed to get home dir"); let expected: Vec = vec![home.join("foo.txt").into()]; assert_eq!(actual, expected); }) diff --git a/crates/nu-command/tests/commands/cd.rs b/crates/nu-command/tests/commands/cd.rs index 87af52aa4d..f58638cc35 100644 --- a/crates/nu-command/tests/commands/cd.rs +++ b/crates/nu-command/tests/commands/cd.rs @@ -151,7 +151,7 @@ fn filesystem_change_to_home_directory() { " ); - assert_eq!(Some(PathBuf::from(actual.out)), dirs_next::home_dir()); + assert_eq!(Some(PathBuf::from(actual.out)), dirs::home_dir()); }) } diff --git a/crates/nu-command/tests/commands/run_external.rs b/crates/nu-command/tests/commands/run_external.rs index 154c31b71c..17667c9bb3 100644 --- a/crates/nu-command/tests/commands/run_external.rs +++ b/crates/nu-command/tests/commands/run_external.rs @@ -309,7 +309,7 @@ fn external_arg_expand_tilde() { "# )); - let home = dirs_next::home_dir().expect("failed to find home dir"); + let home = dirs::home_dir().expect("failed to find home dir"); assert_eq!( actual.out, diff --git a/crates/nu-path/Cargo.toml b/crates/nu-path/Cargo.toml index dc2f870a9d..62908e7cb7 100644 --- a/crates/nu-path/Cargo.toml +++ b/crates/nu-path/Cargo.toml @@ -12,10 +12,10 @@ exclude = ["/fuzz"] bench = false [dependencies] -dirs-next = { workspace = true } +dirs = { workspace = true } [target.'cfg(windows)'.dependencies] omnipath = { workspace = true } [target.'cfg(all(unix, not(target_os = "macos"), not(target_os = "android")))'.dependencies] -pwd = { workspace = true } \ No newline at end of file +pwd = { workspace = true } diff --git a/crates/nu-path/src/helpers.rs b/crates/nu-path/src/helpers.rs index 5b389410e4..a6e35bddfe 100644 --- a/crates/nu-path/src/helpers.rs +++ b/crates/nu-path/src/helpers.rs @@ -3,14 +3,14 @@ use omnipath::WinPathExt; use std::path::PathBuf; pub fn home_dir() -> Option { - dirs_next::home_dir() + dirs::home_dir() } /// Return the data directory for the current platform or XDG_DATA_HOME if specified. pub fn data_dir() -> Option { match std::env::var("XDG_DATA_HOME").map(PathBuf::from) { Ok(xdg_data) if xdg_data.is_absolute() => Some(canonicalize(&xdg_data).unwrap_or(xdg_data)), - _ => get_canonicalized_path(dirs_next::data_dir()), + _ => get_canonicalized_path(dirs::data_dir()), } } @@ -20,7 +20,7 @@ pub fn cache_dir() -> Option { Ok(xdg_cache) if xdg_cache.is_absolute() => { Some(canonicalize(&xdg_cache).unwrap_or(xdg_cache)) } - _ => get_canonicalized_path(dirs_next::cache_dir()), + _ => get_canonicalized_path(dirs::cache_dir()), } } @@ -30,7 +30,7 @@ pub fn config_dir() -> Option { Ok(xdg_config) if xdg_config.is_absolute() => { Some(canonicalize(&xdg_config).unwrap_or(xdg_config)) } - _ => get_canonicalized_path(dirs_next::config_dir()), + _ => get_canonicalized_path(dirs::config_dir()), } } diff --git a/crates/nu-path/src/tilde.rs b/crates/nu-path/src/tilde.rs index 60cc7d11eb..95c91addf8 100644 --- a/crates/nu-path/src/tilde.rs +++ b/crates/nu-path/src/tilde.rs @@ -77,7 +77,7 @@ fn user_home_dir(username: &str) -> PathBuf { fn user_home_dir(username: &str) -> PathBuf { use std::path::Component; - match dirs_next::home_dir() { + match dirs::home_dir() { None => { // Termux always has the same home directory #[cfg(target_os = "android")] @@ -145,7 +145,7 @@ fn expand_tilde_with_another_user_home(path: &Path) -> PathBuf { /// Expand tilde ("~") into a home directory if it is the first path component pub fn expand_tilde(path: impl AsRef) -> PathBuf { // TODO: Extend this to work with "~user" style of home paths - expand_tilde_with_home(path, dirs_next::home_dir()) + expand_tilde_with_home(path, dirs::home_dir()) } #[cfg(test)] diff --git a/src/main.rs b/src/main.rs index 2c0c4d5e4b..f220731d1d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -100,7 +100,7 @@ fn main() -> Result<()> { }, ); } else if let Some(old_config) = - nu_path::get_canonicalized_path(dirs_next::config_dir()).map(|p| p.join("nushell")) + nu_path::get_canonicalized_path(dirs::config_dir()).map(|p| p.join("nushell")) { let xdg_config_empty = nushell_config_path .read_dir() diff --git a/tests/repl/test_config_path.rs b/tests/repl/test_config_path.rs index 534ac38a27..895b1bd8bf 100644 --- a/tests/repl/test_config_path.rs +++ b/tests/repl/test_config_path.rs @@ -235,7 +235,7 @@ fn test_xdg_config_empty() { playground.with_env("XDG_CONFIG_HOME", ""); let actual = run(playground, "$nu.default-config-dir"); - let expected = dirs_next::config_dir().unwrap().join("nushell"); + let expected = dirs::config_dir().unwrap().join("nushell"); assert_eq!( actual, adjust_canonicalization(expected.canonicalize().unwrap_or(expected)) @@ -250,7 +250,7 @@ fn test_xdg_config_bad() { playground.with_env("XDG_CONFIG_HOME", xdg_config_home); let actual = run(playground, "$nu.default-config-dir"); - let expected = dirs_next::config_dir().unwrap().join("nushell"); + let expected = dirs::config_dir().unwrap().join("nushell"); assert_eq!( actual, adjust_canonicalization(expected.canonicalize().unwrap_or(expected)) From 5417c89387b67c3192ae9043473b556cd669ee15 Mon Sep 17 00:00:00 2001 From: Maxim Zhiburt Date: Wed, 17 Jul 2024 00:21:42 +0300 Subject: [PATCH 052/126] Fix issue with truncation when head on border is used (#13389) close #13365 @fdncred thanks for reproducible hopefully there won't be issues with it after this one :sweat_smile: --- crates/nu-command/tests/commands/table.rs | 6 ++ crates/nu-table/src/table.rs | 122 ++++++++++++---------- 2 files changed, 70 insertions(+), 58 deletions(-) diff --git a/crates/nu-command/tests/commands/table.rs b/crates/nu-command/tests/commands/table.rs index a4869f6bfa..272369a1c9 100644 --- a/crates/nu-command/tests/commands/table.rs +++ b/crates/nu-command/tests/commands/table.rs @@ -2901,3 +2901,9 @@ fn table_general_header_on_separator_trim_algorithm() { let actual = nu!("$env.config.table.header_on_separator = true; [[a b]; ['11111111111111111111111111111111111111' 2] ] | table --width=20 --theme basic"); assert_eq!(actual.out, "+-#-+----a-----+-b-+| 0 | 11111111 | 2 || | 11111111 | || | 11111111 | || | 11111111 | || | 111111 | |+---+----------+---+"); } + +#[test] +fn table_general_header_on_separator_issue1() { + let actual = nu!("$env.config.table.header_on_separator = true; [['Llll oo Bbbbbbbb' 'Bbbbbbbb Aaaa' Nnnnnn Ggggg 'Xxxxx Llllllll #' Bbb 'Pppp Ccccc' 'Rrrrrrrr Dddd' Rrrrrr 'Rrrrrr Ccccc II' 'Rrrrrr Ccccc Ppppppp II' 'Pppppp Dddddddd Tttt' 'Pppppp Dddddddd Dddd' 'Rrrrrrrrr Trrrrrr' 'Pppppp Ppppp Dddd' 'Ppppp Dddd' Hhhh]; [RRRRRRR FFFFFFFF UUUU VV 202407160001 BBB 1 '7/16/2024' '' AAA-1111 AAA-1111-11 '7 YEARS' 2555 'RRRRRRRR DDDD' '7/16/2031' '7/16/2031' NN]] | table --width=87 --theme basic"); + assert_eq!(actual.out, "+-#-+-Llll oo Bbbbbbbb-+-Bbbbbbbb Aaaa-+-Nnnnnn-+-Ggggg-+-Xxxxx Llllllll #-+-...-+| 0 | RRRRRRR | FFFFFFFF | UUUU | VV | 202407160001 | ... |+---+------------------+---------------+--------+-------+------------------+-----+"); +} diff --git a/crates/nu-table/src/table.rs b/crates/nu-table/src/table.rs index 0dbf1bda7e..bc23e48248 100644 --- a/crates/nu-table/src/table.rs +++ b/crates/nu-table/src/table.rs @@ -418,62 +418,7 @@ impl TableOption, ColoredConfig> for // we already must have been estimated that it's safe to do. // and all dims will be suffitient if self.trim_as_head { - if recs.is_empty() { - return; - } - - // even though it's safe to trim columns by header there might be left unused space - // so we do use it if possible prioritizing left columns - - let headers = recs[0].to_owned(); - let headers_widths = headers - .iter() - .map(CellInfo::width) - .map(|v| v + self.pad) - .collect::>(); - - let min_width_use = get_total_width2(&headers_widths, cfg); - - let mut free_width = self.width_max.saturating_sub(min_width_use); - - for (i, head_width) in headers_widths.into_iter().enumerate() { - let head_width = head_width - self.pad; - let column_width = self.width[i] - self.pad; // safe to assume width is bigger then paddding - - let mut use_width = head_width; - if free_width > 0 { - // it's safe to assume that column_width is always bigger or equal to head_width - debug_assert!(column_width >= head_width); - - let additional_width = min(free_width, column_width - head_width); - free_width -= additional_width; - use_width += additional_width; - } - - match &self.strategy { - TrimStrategy::Wrap { try_to_keep_words } => { - let mut wrap = Width::wrap(use_width); - if *try_to_keep_words { - wrap = wrap.keep_words(); - } - - Modify::new(Columns::single(i)) - .with(wrap) - .change(recs, cfg, dims); - } - TrimStrategy::Truncate { suffix } => { - let mut truncate = Width::truncate(use_width); - if let Some(suffix) = suffix { - truncate = truncate.suffix(suffix).suffix_try_color(true); - } - - Modify::new(Columns::single(i)) - .with(truncate) - .change(recs, cfg, dims); - } - } - } - + trim_as_header(recs, cfg, dims, self); return; } @@ -498,6 +443,67 @@ impl TableOption, ColoredConfig> for } } +fn trim_as_header( + recs: &mut VecRecords>, + cfg: &mut ColoredConfig, + dims: &mut CompleteDimensionVecRecords, + trim: TableTrim, +) { + if recs.is_empty() { + return; + } + + let headers = recs[0].to_owned(); + let headers_widths = headers + .iter() + .map(CellInfo::width) + .map(|v| v + trim.pad) + .collect::>(); + let min_width_use = get_total_width2(&headers_widths, cfg); + let mut free_width = trim.width_max.saturating_sub(min_width_use); + + // even though it's safe to trim columns by header there might be left unused space + // so we do use it if possible prioritizing left columns + + for (i, head_width) in headers_widths.into_iter().enumerate() { + let head_width = head_width - trim.pad; + let column_width = trim.width[i] - trim.pad; // safe to assume width is bigger then paddding + + let mut use_width = head_width; + if free_width > 0 { + // it's safe to assume that column_width is always bigger or equal to head_width + debug_assert!(column_width >= head_width); + + let additional_width = min(free_width, column_width - head_width); + free_width -= additional_width; + use_width += additional_width; + } + + match &trim.strategy { + TrimStrategy::Wrap { try_to_keep_words } => { + let mut wrap = Width::wrap(use_width); + if *try_to_keep_words { + wrap = wrap.keep_words(); + } + + Modify::new(Columns::single(i)) + .with(wrap) + .change(recs, cfg, dims); + } + TrimStrategy::Truncate { suffix } => { + let mut truncate = Width::truncate(use_width); + if let Some(suffix) = suffix { + truncate = truncate.suffix(suffix).suffix_try_color(true); + } + + Modify::new(Columns::single(i)) + .with(truncate) + .change(recs, cfg, dims); + } + } + } +} + fn align_table( table: &mut Table, alignments: Alignments, @@ -793,14 +799,14 @@ fn truncate_columns_by_head( let mut truncate_pos = 0; for (i, column_header) in head.iter().enumerate() { let column_header_width = Cell::width(column_header); - width += column_header_width; + width += column_header_width + pad; if i > 0 { width += has_vertical as usize; } if width >= termwidth { - width -= column_header_width + (i > 0 && has_vertical) as usize; + width -= column_header_width + (i > 0 && has_vertical) as usize + pad; break; } From 63cea4413041f8821b235d730c61588b36cd7f14 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 17 Jul 2024 09:46:59 +0800 Subject: [PATCH 053/126] Bump uuid from 1.9.1 to 1.10.0 (#13390) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [uuid](https://github.com/uuid-rs/uuid) from 1.9.1 to 1.10.0.
Release notes

Sourced from uuid's releases.

1.10.0

Deprecations

This release deprecates and renames the following functions:

  • Builder::from_rfc4122_timestamp -> Builder::from_gregorian_timestamp
  • Builder::from_sorted_rfc4122_timestamp -> Builder::from_sorted_gregorian_timestamp
  • Timestamp::from_rfc4122 -> Timestamp::from_gregorian
  • Timestamp::to_rfc4122 -> Timestamp::to_gregorian

What's Changed

New Contributors

Full Changelog: https://github.com/uuid-rs/uuid/compare/1.9.1...1.10.0

Commits
  • 4b4c590 Merge pull request #766 from uuid-rs/cargo/1.10.0
  • 68eff32 Merge pull request #765 from uuid-rs/chore/time-fn-deprecations
  • 3d5384d update docs and deprecation messages for timestamp fns
  • de50f20 renaming rfc4122 functions
  • 4a88417 prepare for 1.10.0 release
  • 66b4fce Merge pull request #764 from Vrajs16/main
  • 8896e26 Use expr instead of ident
  • 09973d6 Added changes
  • 6edf3e8 Use const identifer in uuid macro
  • See full diff in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=uuid&package-manager=cargo&previous-version=1.9.1&new-version=1.10.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- crates/nu_plugin_polars/Cargo.toml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0e63b83844..3006f933f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6638,9 +6638,9 @@ checksum = "425a23c7b7145bc7620c9c445817c37b1f78b6790aee9f208133f3c028975b60" [[package]] name = "uuid" -version = "1.9.1" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de17fd2f7da591098415cff336e12965a28061ddace43b59cb3c430179c9439" +checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" dependencies = [ "getrandom", "serde", diff --git a/Cargo.toml b/Cargo.toml index 20f41f1b49..9941d495f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -173,7 +173,7 @@ uu_mv = "0.0.27" uu_whoami = "0.0.27" uu_uname = "0.0.27" uucore = "0.0.27" -uuid = "1.9.1" +uuid = "1.10.0" v_htmlescape = "0.15.0" wax = "0.6" which = "6.0.0" diff --git a/crates/nu_plugin_polars/Cargo.toml b/crates/nu_plugin_polars/Cargo.toml index f9b4295cb9..99c88e47d6 100644 --- a/crates/nu_plugin_polars/Cargo.toml +++ b/crates/nu_plugin_polars/Cargo.toml @@ -39,7 +39,7 @@ polars-utils = { version = "0.41"} typetag = "0.2" env_logger = "0.11.3" log.workspace = true -uuid = { version = "1.9", features = ["v4", "serde"] } +uuid = { version = "1.10", features = ["v4", "serde"] } [dependencies.polars] features = [ From ac18e436035b9d1319d23432d728ad9f0711ed2c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 17 Jul 2024 09:47:09 +0800 Subject: [PATCH 054/126] Bump rust-embed from 8.4.0 to 8.5.0 (#13392) Bumps [rust-embed](https://github.com/pyros2097/rust-embed) from 8.4.0 to 8.5.0.
Changelog

Sourced from rust-embed's changelog.

[8.5.0] - 2024-07-09

  • Re-export RustEmbed as Embed #246. Thanks to krant
  • Allow users to specify a custom path to the rust_embed crate in generated code#232. Thanks to Wulf
  • Increase minimum rust-version to v1.7.0.0
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=rust-embed&package-manager=cargo&previous-version=8.4.0&new-version=8.5.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3006f933f3..2506c6bc67 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5216,9 +5216,9 @@ dependencies = [ [[package]] name = "rust-embed" -version = "8.4.0" +version = "8.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19549741604902eb99a7ed0ee177a0663ee1eda51a29f71401f166e47e77806a" +checksum = "fa66af4a4fdd5e7ebc276f115e895611a34739a9c1c01028383d612d550953c0" dependencies = [ "rust-embed-impl", "rust-embed-utils", diff --git a/Cargo.toml b/Cargo.toml index 9941d495f9..8ce82c274a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -145,7 +145,7 @@ ropey = "1.6.1" roxmltree = "0.19" rstest = { version = "0.18", default-features = false } rusqlite = "0.31" -rust-embed = "8.4.0" +rust-embed = "8.5.0" same-file = "1.0" serde = { version = "1.0", default-features = false } serde_json = "1.0" From f976c318870730a1674376ff33141626faf67a07 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 17 Jul 2024 09:47:17 +0800 Subject: [PATCH 055/126] Bump open from 5.2.0 to 5.3.0 (#13391) Bumps [open](https://github.com/Byron/open-rs) from 5.2.0 to 5.3.0.
Release notes

Sourced from open's releases.

v5.3.0

New Features

  • add GNU/Hurd support Handle it like most of the other Unix platforms (e.g. Linux, BSDs, etc).

Commit Statistics

  • 2 commits contributed to the release.
  • 7 days passed between releases.
  • 1 commit was understood as conventional.
  • 0 issues like '(#ID)' were seen in commit messages

Commit Details

  • Uncategorized
    • Merge pull request #101 from pinotree/hurd (a060608)
    • Add GNU/Hurd support (58142a6)
Changelog

Sourced from open's changelog.

5.3.0 (2024-07-10)

New Features

  • add GNU/Hurd support Handle it like most of the other Unix platforms (e.g. Linux, BSDs, etc).

Commit Statistics

  • 2 commits contributed to the release.
  • 7 days passed between releases.
  • 1 commit was understood as conventional.
  • 0 issues like '(#ID)' were seen in commit messages

Commit Details

  • Uncategorized
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=open&package-manager=cargo&previous-version=5.2.0&new-version=5.3.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2506c6bc67..49770a9a0d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3773,9 +3773,9 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "open" -version = "5.2.0" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d2c909a3fce3bd80efef4cd1c6c056bd9376a8fe06fcfdbebaf32cb485a7e37" +checksum = "61a877bf6abd716642a53ef1b89fb498923a4afca5c754f9050b4d081c05c4b3" dependencies = [ "is-wsl", "libc", diff --git a/Cargo.toml b/Cargo.toml index 8ce82c274a..335e2f0384 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -120,7 +120,7 @@ num-format = "0.4" num-traits = "0.2" omnipath = "0.1" once_cell = "1.18" -open = "5.2" +open = "5.3" os_pipe = { version = "1.2", features = ["io_safety"] } pathdiff = "0.2" percent-encoding = "2" From aa7d7d0cc3bffcf4323291a7a4163e41dfbee6dd Mon Sep 17 00:00:00 2001 From: Devyn Cairns Date: Wed, 17 Jul 2024 14:02:42 -0700 Subject: [PATCH 056/126] Overhaul `$in` expressions (#13357) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description This grew quite a bit beyond its original scope, but I've tried to make `$in` a bit more consistent and easier to work with. Instead of the parser generating calls to `collect` and creating closures, this adds `Expr::Collect` which just evaluates in the same scope and doesn't require any closure. When `$in` is detected in an expression, it is replaced with a new variable (also called `$in`) and wrapped in `Expr::Collect`. During eval, this expression is evaluated directly, with the input and with that new variable set to the collected value. Other than being faster and less prone to gotchas, it also makes it possible to typecheck the output of an expression containing `$in`, which is nice. This is a breaking change though, because of the lack of the closure and because now typechecking will actually happen. Also, I haven't attempted to typecheck the input yet. The IR generated now just looks like this: ```gas collect %in clone %tmp, %in store-variable $in, %tmp # %out <- ...expression... <- %in drop-variable $in ``` (where `$in` is the local variable created for this collection, and not `IN_VARIABLE_ID`) which is a lot better than having to create a closure and call `collect --keep-env`, dealing with all of the capture gathering and allocation that entails. Ideally we can also detect whether that input is actually needed, so maybe we don't have to clone, but I haven't tried to do that yet. Theoretically now that the variable is a unique one every time, it should be possible to give it a type - I just don't know how to determine that yet. On top of that, I've also reworked how `$in` works in pipeline-initial position. Previously, it was a little bit inconsistent. For example, this worked: ```nushell > 3 | do { let x = $in; let y = $in; print $x $y } 3 3 ``` However, this causes a runtime variable not found error on the second `$in`: ```nushell > def foo [] { let x = $in; let y = $in; print $x $y }; 3 | foo Error: nu::shell::variable_not_found × Variable not found ╭─[entry #115:1:35] 1 │ def foo [] { let x = $in; let y = $in; print $x $y }; 3 | foo · ─┬─ · ╰── variable not found ╰──── ``` I've fixed this by making the first element `$in` detection *always* happen at the block level, so if you use `$in` in pipeline-initial position anywhere in a block, it will collect with an implicit subexpression around the whole thing, and you can then use that `$in` more than once. In doing this I also rewrote `parse_pipeline()` and hopefully it's a bit more straightforward and possibly more efficient too now. Finally, I've tried to make `let` and `mut` a lot more straightforward with how they handle the rest of the pipeline, and using a redirection with `let`/`mut` now does what you'd expect if you assume that they consume the whole pipeline - the redirection is just processed as normal. These both work now: ```nushell let x = ^foo err> err.txt let y = ^foo out+err>| str length ``` It was previously possible to accomplish this with a subexpression, but it just seemed like a weird gotcha that you couldn't do it. Intuitively, `let` and `mut` just seem to take the whole line. - closes #13137 # User-Facing Changes - `$in` will behave more consistently with blocks and closures, since the entire block is now just wrapped to handle it if it appears in the first pipeline element - `$in` no longer creates a closure, so what can be done within an expression containing `$in` is less restrictive - `$in` containing expressions are now type checked, rather than just resulting in `any`. However, `$in` itself is still `any`, so this isn't quite perfect yet - Redirections are now allowed in `let` and `mut` and behave pretty much how you'd expect # Tests + Formatting Added tests to cover the new behaviour. # After Submitting - [ ] release notes (definitely breaking change) --- crates/nu-cli/src/syntax_highlight.rs | 8 + crates/nu-cmd-lang/src/example_support.rs | 7 +- crates/nu-command/src/example_test.rs | 3 +- .../nu-command/tests/commands/redirection.rs | 34 ++ crates/nu-engine/src/compile/builder.rs | 1 + crates/nu-engine/src/compile/expression.rs | 21 ++ crates/nu-engine/src/eval.rs | 58 ++- crates/nu-engine/src/eval_ir.rs | 22 +- crates/nu-parser/src/flatten.rs | 3 + crates/nu-parser/src/lite_parser.rs | 29 ++ crates/nu-parser/src/parser.rs | 355 +++++++----------- crates/nu-parser/tests/test_parser.rs | 63 +++- crates/nu-protocol/src/ast/block.rs | 13 + crates/nu-protocol/src/ast/expr.rs | 9 +- crates/nu-protocol/src/ast/expression.rs | 166 ++++++-- crates/nu-protocol/src/ast/pipeline.rs | 43 ++- crates/nu-protocol/src/debugger/profiler.rs | 1 + crates/nu-protocol/src/eval_base.rs | 10 + crates/nu-protocol/src/eval_const.rs | 9 + crates/nu-protocol/src/ir/display.rs | 4 + crates/nu-protocol/src/ir/mod.rs | 3 + crates/nuon/src/from.rs | 6 + tests/repl/test_engine.rs | 23 ++ tests/repl/test_type_check.rs | 13 + 24 files changed, 631 insertions(+), 273 deletions(-) diff --git a/crates/nu-cli/src/syntax_highlight.rs b/crates/nu-cli/src/syntax_highlight.rs index 08dcbb1346..ebd8315190 100644 --- a/crates/nu-cli/src/syntax_highlight.rs +++ b/crates/nu-cli/src/syntax_highlight.rs @@ -429,6 +429,14 @@ fn find_matching_block_end_in_expr( ) }), + Expr::Collect(_, expr) => find_matching_block_end_in_expr( + line, + working_set, + expr, + global_span_offset, + global_cursor_offset, + ), + Expr::Block(block_id) | Expr::Closure(block_id) | Expr::RowCondition(block_id) diff --git a/crates/nu-cmd-lang/src/example_support.rs b/crates/nu-cmd-lang/src/example_support.rs index bb03bbaf8c..72ea36972f 100644 --- a/crates/nu-cmd-lang/src/example_support.rs +++ b/crates/nu-cmd-lang/src/example_support.rs @@ -4,7 +4,7 @@ use nu_protocol::{ ast::Block, debugger::WithoutDebug, engine::{StateDelta, StateWorkingSet}, - Range, + report_error_new, Range, }; use std::{ sync::Arc, @@ -124,7 +124,10 @@ pub fn eval_block( nu_engine::eval_block::(engine_state, &mut stack, &block, input) .and_then(|data| data.into_value(Span::test_data())) - .unwrap_or_else(|err| panic!("test eval error in `{}`: {:?}", "TODO", err)) + .unwrap_or_else(|err| { + report_error_new(engine_state, &err); + panic!("test eval error in `{}`: {:?}", "TODO", err) + }) } pub fn check_example_evaluates_to_expected_output( diff --git a/crates/nu-command/src/example_test.rs b/crates/nu-command/src/example_test.rs index 476113aa2d..4879cab0a9 100644 --- a/crates/nu-command/src/example_test.rs +++ b/crates/nu-command/src/example_test.rs @@ -31,7 +31,7 @@ mod test_examples { check_example_evaluates_to_expected_output, check_example_input_and_output_types_match_command_signature, }; - use nu_cmd_lang::{Break, Echo, If, Let, Mut}; + use nu_cmd_lang::{Break, Describe, Echo, If, Let, Mut}; use nu_protocol::{ engine::{Command, EngineState, StateWorkingSet}, Type, @@ -81,6 +81,7 @@ mod test_examples { working_set.add_decl(Box::new(Break)); working_set.add_decl(Box::new(Date)); working_set.add_decl(Box::new(Default)); + working_set.add_decl(Box::new(Describe)); working_set.add_decl(Box::new(Each)); working_set.add_decl(Box::new(Echo)); working_set.add_decl(Box::new(Enumerate)); diff --git a/crates/nu-command/tests/commands/redirection.rs b/crates/nu-command/tests/commands/redirection.rs index 1eb57077b9..02f85a90f8 100644 --- a/crates/nu-command/tests/commands/redirection.rs +++ b/crates/nu-command/tests/commands/redirection.rs @@ -439,3 +439,37 @@ fn no_duplicate_redirection() { ); }); } + +#[rstest::rstest] +#[case("let", "out>")] +#[case("let", "err>")] +#[case("let", "out+err>")] +#[case("mut", "out>")] +#[case("mut", "err>")] +#[case("mut", "out+err>")] +fn file_redirection_in_let_and_mut(#[case] keyword: &str, #[case] redir: &str) { + Playground::setup("file redirection in let and mut", |dirs, _| { + let actual = nu!( + cwd: dirs.test(), + format!("$env.BAZ = 'foo'; {keyword} v = nu --testbin echo_env_mixed out-err BAZ BAZ {redir} result.txt") + ); + assert!(actual.status.success()); + assert!(file_contents(dirs.test().join("result.txt")).contains("foo")); + }) +} + +#[rstest::rstest] +#[case("let", "err>|", "foo3")] +#[case("let", "out+err>|", "7")] +#[case("mut", "err>|", "foo3")] +#[case("mut", "out+err>|", "7")] +fn pipe_redirection_in_let_and_mut( + #[case] keyword: &str, + #[case] redir: &str, + #[case] output: &str, +) { + let actual = nu!( + format!("$env.BAZ = 'foo'; {keyword} v = nu --testbin echo_env_mixed out-err BAZ BAZ {redir} str length; $v") + ); + assert_eq!(actual.out, output); +} diff --git a/crates/nu-engine/src/compile/builder.rs b/crates/nu-engine/src/compile/builder.rs index d77075cc3f..4294b2bd61 100644 --- a/crates/nu-engine/src/compile/builder.rs +++ b/crates/nu-engine/src/compile/builder.rs @@ -204,6 +204,7 @@ impl BlockBuilder { Instruction::Drain { src } => allocate(&[*src], &[]), Instruction::LoadVariable { dst, var_id: _ } => allocate(&[], &[*dst]), Instruction::StoreVariable { var_id: _, src } => allocate(&[*src], &[]), + Instruction::DropVariable { var_id: _ } => Ok(()), Instruction::LoadEnv { dst, key: _ } => allocate(&[], &[*dst]), Instruction::LoadEnvOpt { dst, key: _ } => allocate(&[], &[*dst]), Instruction::StoreEnv { key: _, src } => allocate(&[*src], &[]), diff --git a/crates/nu-engine/src/compile/expression.rs b/crates/nu-engine/src/compile/expression.rs index 38ee58ea26..948eb5c238 100644 --- a/crates/nu-engine/src/compile/expression.rs +++ b/crates/nu-engine/src/compile/expression.rs @@ -171,6 +171,27 @@ pub(crate) fn compile_expression( Err(CompileError::UnsupportedOperatorExpression { span: op.span }) } } + Expr::Collect(var_id, expr) => { + let store_reg = if let Some(in_reg) = in_reg { + // Collect, clone, store + builder.push(Instruction::Collect { src_dst: in_reg }.into_spanned(expr.span))?; + builder.clone_reg(in_reg, expr.span)? + } else { + // Just store nothing in the variable + builder.literal(Literal::Nothing.into_spanned(Span::unknown()))? + }; + builder.push( + Instruction::StoreVariable { + var_id: *var_id, + src: store_reg, + } + .into_spanned(expr.span), + )?; + compile_expression(working_set, builder, expr, redirect_modes, in_reg, out_reg)?; + // Clean it up afterward + builder.push(Instruction::DropVariable { var_id: *var_id }.into_spanned(expr.span))?; + Ok(()) + } Expr::Subexpression(block_id) => { let block = working_set.get_block(*block_id); compile_block(working_set, builder, block, redirect_modes, in_reg, out_reg) diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index 42d0aed288..84fa700934 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -10,8 +10,8 @@ use nu_protocol::{ debugger::DebugContext, engine::{Closure, EngineState, Redirection, Stack, StateWorkingSet}, eval_base::Eval, - ByteStreamSource, Config, FromValue, IntoPipelineData, OutDest, PipelineData, ShellError, Span, - Spanned, Type, Value, VarId, ENV_VARIABLE_ID, + ByteStreamSource, Config, DataSource, FromValue, IntoPipelineData, OutDest, PipelineData, + PipelineMetadata, ShellError, Span, Spanned, Type, Value, VarId, ENV_VARIABLE_ID, }; use nu_utils::IgnoreCaseExt; use std::{fs::OpenOptions, path::PathBuf, sync::Arc}; @@ -259,6 +259,10 @@ pub fn eval_expression_with_input( input = eval_external(engine_state, stack, head, args, input)?; } + Expr::Collect(var_id, expr) => { + input = eval_collect::(engine_state, stack, *var_id, expr, input)?; + } + Expr::Subexpression(block_id) => { let block = engine_state.get_block(*block_id); // FIXME: protect this collect with ctrl-c @@ -605,6 +609,44 @@ pub fn eval_block( Ok(input) } +pub fn eval_collect( + engine_state: &EngineState, + stack: &mut Stack, + var_id: VarId, + expr: &Expression, + input: PipelineData, +) -> Result { + // Evaluate the expression with the variable set to the collected input + let span = input.span().unwrap_or(Span::unknown()); + + let metadata = match input.metadata() { + // Remove the `FilePath` metadata, because after `collect` it's no longer necessary to + // check where some input came from. + Some(PipelineMetadata { + data_source: DataSource::FilePath(_), + content_type: None, + }) => None, + other => other, + }; + + let input = input.into_value(span)?; + + stack.add_var(var_id, input.clone()); + + let result = eval_expression_with_input::( + engine_state, + stack, + expr, + // We still have to pass it as input + input.into_pipeline_data_with_metadata(metadata), + ) + .map(|(result, _failed)| result); + + stack.remove_var(var_id); + + result +} + pub fn eval_subexpression( engine_state: &EngineState, stack: &mut Stack, @@ -729,6 +771,18 @@ impl Eval for EvalRuntime { eval_external(engine_state, stack, head, args, PipelineData::empty())?.into_value(span) } + fn eval_collect( + engine_state: &EngineState, + stack: &mut Stack, + var_id: VarId, + expr: &Expression, + ) -> Result { + // It's a little bizarre, but the expression can still have some kind of result even with + // nothing input + eval_collect::(engine_state, stack, var_id, expr, PipelineData::empty())? + .into_value(expr.span) + } + fn eval_subexpression( engine_state: &EngineState, stack: &mut Stack, diff --git a/crates/nu-engine/src/eval_ir.rs b/crates/nu-engine/src/eval_ir.rs index 29a677bd69..8550cbab99 100644 --- a/crates/nu-engine/src/eval_ir.rs +++ b/crates/nu-engine/src/eval_ir.rs @@ -6,9 +6,9 @@ use nu_protocol::{ debugger::DebugContext, engine::{Argument, Closure, EngineState, ErrorHandler, Matcher, Redirection, Stack}, ir::{Call, DataSlice, Instruction, IrAstRef, IrBlock, Literal, RedirectMode}, - record, ByteStreamSource, DeclId, ErrSpan, Flag, IntoPipelineData, IntoSpanned, ListStream, - OutDest, PipelineData, PositionalArg, Range, Record, RegId, ShellError, Signals, Signature, - Span, Spanned, Type, Value, VarId, ENV_VARIABLE_ID, + record, ByteStreamSource, DataSource, DeclId, ErrSpan, Flag, IntoPipelineData, IntoSpanned, + ListStream, OutDest, PipelineData, PipelineMetadata, PositionalArg, Range, Record, RegId, + ShellError, Signals, Signature, Span, Spanned, Type, Value, VarId, ENV_VARIABLE_ID, }; use nu_utils::IgnoreCaseExt; @@ -345,6 +345,10 @@ fn eval_instruction( ctx.stack.add_var(*var_id, value); Ok(Continue) } + Instruction::DropVariable { var_id } => { + ctx.stack.remove_var(*var_id); + Ok(Continue) + } Instruction::LoadEnv { dst, key } => { let key = ctx.get_str(*key, *span)?; if let Some(value) = get_env_var_case_insensitive(ctx, key) { @@ -1341,9 +1345,19 @@ fn get_env_var_name_case_insensitive<'a>(ctx: &mut EvalContext<'_>, key: &'a str } /// Helper to collect values into [`PipelineData`], preserving original span and metadata +/// +/// The metadata is removed if it is the file data source, as that's just meant to mark streams. fn collect(data: PipelineData, fallback_span: Span) -> Result { let span = data.span().unwrap_or(fallback_span); - let metadata = data.metadata(); + let metadata = match data.metadata() { + // Remove the `FilePath` metadata, because after `collect` it's no longer necessary to + // check where some input came from. + Some(PipelineMetadata { + data_source: DataSource::FilePath(_), + content_type: None, + }) => None, + other => other, + }; let value = data.into_value(span)?; Ok(PipelineData::Value(value, metadata)) } diff --git a/crates/nu-parser/src/flatten.rs b/crates/nu-parser/src/flatten.rs index 88638b2475..8a05edaef4 100644 --- a/crates/nu-parser/src/flatten.rs +++ b/crates/nu-parser/src/flatten.rs @@ -189,6 +189,9 @@ fn flatten_expression_into( )); flatten_expression_into(working_set, not, output); } + Expr::Collect(_, expr) => { + flatten_expression_into(working_set, expr, output); + } Expr::Closure(block_id) => { let outer_span = expr.span; diff --git a/crates/nu-parser/src/lite_parser.rs b/crates/nu-parser/src/lite_parser.rs index f04b8befdc..c9e9578698 100644 --- a/crates/nu-parser/src/lite_parser.rs +++ b/crates/nu-parser/src/lite_parser.rs @@ -2,6 +2,7 @@ //! can be parsed. use crate::{Token, TokenContents}; +use itertools::{Either, Itertools}; use nu_protocol::{ast::RedirectionSource, ParseError, Span}; use std::mem; @@ -24,6 +25,15 @@ impl LiteRedirectionTarget { | LiteRedirectionTarget::Pipe { connector } => *connector, } } + + pub fn spans(&self) -> impl Iterator { + match *self { + LiteRedirectionTarget::File { + connector, file, .. + } => Either::Left([connector, file].into_iter()), + LiteRedirectionTarget::Pipe { connector } => Either::Right(std::iter::once(connector)), + } + } } #[derive(Debug, Clone)] @@ -38,6 +48,17 @@ pub enum LiteRedirection { }, } +impl LiteRedirection { + pub fn spans(&self) -> impl Iterator { + match self { + LiteRedirection::Single { target, .. } => Either::Left(target.spans()), + LiteRedirection::Separate { out, err } => { + Either::Right(out.spans().chain(err.spans()).sorted()) + } + } + } +} + #[derive(Debug, Clone, Default)] pub struct LiteCommand { pub pipe: Option, @@ -113,6 +134,14 @@ impl LiteCommand { Ok(()) } + + pub fn parts_including_redirection(&self) -> impl Iterator + '_ { + self.parts.iter().copied().chain( + self.redirection + .iter() + .flat_map(|redirection| redirection.spans()), + ) + } } #[derive(Debug, Clone, Default)] diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index fc2131aad7..bc48b9bd0c 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -5299,6 +5299,7 @@ pub fn parse_expression(working_set: &mut StateWorkingSet, spans: &[Span]) -> Ex let mut block = Block::default(); let ty = output.ty.clone(); block.pipelines = vec![Pipeline::from_vec(vec![output])]; + block.span = Some(Span::concat(spans)); compile_block(working_set, &mut block); @@ -5393,9 +5394,19 @@ pub fn parse_builtin_commands( match name { b"def" => parse_def(working_set, lite_command, None).0, b"extern" => parse_extern(working_set, lite_command, None), - b"let" => parse_let(working_set, &lite_command.parts), + b"let" => parse_let( + working_set, + &lite_command + .parts_including_redirection() + .collect::>(), + ), b"const" => parse_const(working_set, &lite_command.parts), - b"mut" => parse_mut(working_set, &lite_command.parts), + b"mut" => parse_mut( + working_set, + &lite_command + .parts_including_redirection() + .collect::>(), + ), b"for" => { let expr = parse_for(working_set, lite_command); Pipeline::from_vec(vec![expr]) @@ -5647,169 +5658,73 @@ pub(crate) fn redirecting_builtin_error( } } -pub fn parse_pipeline( - working_set: &mut StateWorkingSet, - pipeline: &LitePipeline, - is_subexpression: bool, - pipeline_index: usize, -) -> Pipeline { +pub fn parse_pipeline(working_set: &mut StateWorkingSet, pipeline: &LitePipeline) -> Pipeline { + let first_command = pipeline.commands.first(); + let first_command_name = first_command + .and_then(|command| command.parts.first()) + .map(|span| working_set.get_span_contents(*span)); + if pipeline.commands.len() > 1 { - // Special case: allow `let` and `mut` to consume the whole pipeline, eg) `let abc = "foo" | str length` - if let Some(&first) = pipeline.commands[0].parts.first() { - let first = working_set.get_span_contents(first); - if first == b"let" || first == b"mut" { - let name = if first == b"let" { "let" } else { "mut" }; - let mut new_command = LiteCommand { - comments: vec![], - parts: pipeline.commands[0].parts.clone(), - pipe: None, - redirection: None, - }; + // Special case: allow "let" or "mut" to consume the whole pipeline, if this is a pipeline + // with multiple commands + if matches!(first_command_name, Some(b"let" | b"mut")) { + // Merge the pipeline into one command + let first_command = first_command.expect("must be Some"); - if let Some(redirection) = pipeline.commands[0].redirection.as_ref() { - working_set.error(redirecting_builtin_error(name, redirection)); - } + let remainder_span = first_command + .parts_including_redirection() + .skip(3) + .chain( + pipeline.commands[1..] + .iter() + .flat_map(|command| command.parts_including_redirection()), + ) + .reduce(Span::append); - for element in &pipeline.commands[1..] { - if let Some(redirection) = pipeline.commands[0].redirection.as_ref() { - working_set.error(redirecting_builtin_error(name, redirection)); - } else { - new_command.parts.push(element.pipe.expect("pipe span")); - new_command.comments.extend_from_slice(&element.comments); - new_command.parts.extend_from_slice(&element.parts); - } - } + let parts = first_command + .parts + .iter() + .take(3) // the let/mut start itself + .copied() + .chain(remainder_span) // everything else + .collect(); - // if the 'let' is complete enough, use it, if not, fall through for now - if new_command.parts.len() > 3 { - let rhs_span = Span::concat(&new_command.parts[3..]); + let comments = pipeline + .commands + .iter() + .flat_map(|command| command.comments.iter()) + .copied() + .collect(); - new_command.parts.truncate(3); - new_command.parts.push(rhs_span); - - let mut pipeline = parse_builtin_commands(working_set, &new_command); - - if pipeline_index == 0 { - let let_decl_id = working_set.find_decl(b"let"); - let mut_decl_id = working_set.find_decl(b"mut"); - for element in pipeline.elements.iter_mut() { - if let Expr::Call(call) = &element.expr.expr { - if Some(call.decl_id) == let_decl_id - || Some(call.decl_id) == mut_decl_id - { - // Do an expansion - if let Some(Expression { - expr: Expr::Block(block_id), - .. - }) = call.positional_iter().nth(1) - { - let block = working_set.get_block(*block_id); - - if let Some(element) = block - .pipelines - .first() - .and_then(|p| p.elements.first()) - .cloned() - { - if element.has_in_variable(working_set) { - let element = wrap_element_with_collect( - working_set, - &element, - ); - let block = working_set.get_block_mut(*block_id); - block.pipelines[0].elements[0] = element; - } - } - } - continue; - } else if element.has_in_variable(working_set) && !is_subexpression - { - *element = wrap_element_with_collect(working_set, element); - } - } else if element.has_in_variable(working_set) && !is_subexpression { - *element = wrap_element_with_collect(working_set, element); - } - } - } - - return pipeline; - } - } - } - - let mut elements = pipeline - .commands - .iter() - .map(|element| parse_pipeline_element(working_set, element)) - .collect::>(); - - if is_subexpression { - for element in elements.iter_mut().skip(1) { - if element.has_in_variable(working_set) { - *element = wrap_element_with_collect(working_set, element); - } - } + let new_command = LiteCommand { + pipe: None, + comments, + parts, + redirection: None, + }; + parse_builtin_commands(working_set, &new_command) } else { - for element in elements.iter_mut() { - if element.has_in_variable(working_set) { - *element = wrap_element_with_collect(working_set, element); - } - } - } - - Pipeline { elements } - } else { - if let Some(&first) = pipeline.commands[0].parts.first() { - let first = working_set.get_span_contents(first); - if first == b"let" || first == b"mut" { - if let Some(redirection) = pipeline.commands[0].redirection.as_ref() { - let name = if first == b"let" { "let" } else { "mut" }; - working_set.error(redirecting_builtin_error(name, redirection)); - } - } - } - - let mut pipeline = parse_builtin_commands(working_set, &pipeline.commands[0]); - - let let_decl_id = working_set.find_decl(b"let"); - let mut_decl_id = working_set.find_decl(b"mut"); - - if pipeline_index == 0 { - for element in pipeline.elements.iter_mut() { - if let Expr::Call(call) = &element.expr.expr { - if Some(call.decl_id) == let_decl_id || Some(call.decl_id) == mut_decl_id { - // Do an expansion - if let Some(Expression { - expr: Expr::Block(block_id), - .. - }) = call.positional_iter().nth(1) - { - let block = working_set.get_block(*block_id); - - if let Some(element) = block - .pipelines - .first() - .and_then(|p| p.elements.first()) - .cloned() - { - if element.has_in_variable(working_set) { - let element = wrap_element_with_collect(working_set, &element); - let block = working_set.get_block_mut(*block_id); - block.pipelines[0].elements[0] = element; - } - } - } - continue; - } else if element.has_in_variable(working_set) && !is_subexpression { - *element = wrap_element_with_collect(working_set, element); + // Parse a normal multi command pipeline + let elements: Vec<_> = pipeline + .commands + .iter() + .enumerate() + .map(|(index, element)| { + let element = parse_pipeline_element(working_set, element); + // Handle $in for pipeline elements beyond the first one + if index > 0 && element.has_in_variable(working_set) { + wrap_element_with_collect(working_set, element.clone()) + } else { + element } - } else if element.has_in_variable(working_set) && !is_subexpression { - *element = wrap_element_with_collect(working_set, element); - } - } - } + }) + .collect(); - pipeline + Pipeline { elements } + } + } else { + // If there's only one command in the pipeline, this could be a builtin command + parse_builtin_commands(working_set, &pipeline.commands[0]) } } @@ -5840,18 +5755,45 @@ pub fn parse_block( } let mut block = Block::new_with_capacity(lite_block.block.len()); + block.span = Some(span); - for (idx, lite_pipeline) in lite_block.block.iter().enumerate() { - let pipeline = parse_pipeline(working_set, lite_pipeline, is_subexpression, idx); + for lite_pipeline in &lite_block.block { + let pipeline = parse_pipeline(working_set, lite_pipeline); block.pipelines.push(pipeline); } + // If this is not a subexpression and there are any pipelines where the first element has $in, + // we can wrap the whole block in collect so that they all reference the same $in + if !is_subexpression + && block + .pipelines + .iter() + .flat_map(|pipeline| pipeline.elements.first()) + .any(|element| element.has_in_variable(working_set)) + { + // Move the block out to prepare it to become a subexpression + let inner_block = std::mem::take(&mut block); + block.span = inner_block.span; + let ty = inner_block.output_type(); + let block_id = working_set.add_block(Arc::new(inner_block)); + + // Now wrap it in a Collect expression, and put it in the block as the only pipeline + let subexpression = Expression::new(working_set, Expr::Subexpression(block_id), span, ty); + let collect = wrap_expr_with_collect(working_set, subexpression); + + block.pipelines.push(Pipeline { + elements: vec![PipelineElement { + pipe: None, + expr: collect, + redirection: None, + }], + }); + } + if scoped { working_set.exit_scope(); } - block.span = Some(span); - let errors = type_check::check_block_input_output(working_set, &block); if !errors.is_empty() { working_set.parse_errors.extend_from_slice(&errors); @@ -6220,6 +6162,10 @@ pub fn discover_captures_in_expr( discover_captures_in_expr(working_set, &match_.1, seen, seen_blocks, output)?; } } + Expr::Collect(var_id, expr) => { + seen.push(*var_id); + discover_captures_in_expr(working_set, expr, seen, seen_blocks, output)? + } Expr::RowCondition(block_id) | Expr::Subexpression(block_id) => { let block = working_set.get_block(*block_id); @@ -6270,28 +6216,28 @@ pub fn discover_captures_in_expr( fn wrap_redirection_with_collect( working_set: &mut StateWorkingSet, - target: &RedirectionTarget, + target: RedirectionTarget, ) -> RedirectionTarget { match target { RedirectionTarget::File { expr, append, span } => RedirectionTarget::File { expr: wrap_expr_with_collect(working_set, expr), - span: *span, - append: *append, + span, + append, }, - RedirectionTarget::Pipe { span } => RedirectionTarget::Pipe { span: *span }, + RedirectionTarget::Pipe { span } => RedirectionTarget::Pipe { span }, } } fn wrap_element_with_collect( working_set: &mut StateWorkingSet, - element: &PipelineElement, + element: PipelineElement, ) -> PipelineElement { PipelineElement { pipe: element.pipe, - expr: wrap_expr_with_collect(working_set, &element.expr), - redirection: element.redirection.as_ref().map(|r| match r { + expr: wrap_expr_with_collect(working_set, element.expr), + redirection: element.redirection.map(|r| match r { PipelineRedirection::Single { source, target } => PipelineRedirection::Single { - source: *source, + source, target: wrap_redirection_with_collect(working_set, target), }, PipelineRedirection::Separate { out, err } => PipelineRedirection::Separate { @@ -6302,65 +6248,24 @@ fn wrap_element_with_collect( } } -fn wrap_expr_with_collect(working_set: &mut StateWorkingSet, expr: &Expression) -> Expression { +fn wrap_expr_with_collect(working_set: &mut StateWorkingSet, expr: Expression) -> Expression { let span = expr.span; - if let Some(decl_id) = working_set.find_decl(b"collect") { - let mut output = vec![]; + // IN_VARIABLE_ID should get replaced with a unique variable, so that we don't have to + // execute as a closure + let var_id = working_set.add_variable(b"$in".into(), expr.span, Type::Any, false); + let mut expr = expr.clone(); + expr.replace_in_variable(working_set, var_id); - let var_id = IN_VARIABLE_ID; - let mut signature = Signature::new(""); - signature.required_positional.push(PositionalArg { - var_id: Some(var_id), - name: "$in".into(), - desc: String::new(), - shape: SyntaxShape::Any, - default_value: None, - }); - - let mut block = Block { - pipelines: vec![Pipeline::from_vec(vec![expr.clone()])], - signature: Box::new(signature), - ..Default::default() - }; - - compile_block(working_set, &mut block); - - let block_id = working_set.add_block(Arc::new(block)); - - output.push(Argument::Positional(Expression::new( - working_set, - Expr::Closure(block_id), - span, - Type::Any, - ))); - - output.push(Argument::Named(( - Spanned { - item: "keep-env".to_string(), - span: Span::new(0, 0), - }, - None, - None, - ))); - - // The containing, synthetic call to `collect`. - // We don't want to have a real span as it will confuse flattening - // The args are where we'll get the real info - Expression::new( - working_set, - Expr::Call(Box::new(Call { - head: Span::new(0, 0), - arguments: output, - decl_id, - parser_info: HashMap::new(), - })), - span, - Type::Any, - ) - } else { - Expression::garbage(working_set, span) - } + // Bind the custom `$in` variable for that particular expression + let ty = expr.ty.clone(); + Expression::new( + working_set, + Expr::Collect(var_id, Box::new(expr)), + span, + // We can expect it to have the same result type + ty, + ) } // Parses a vector of u8 to create an AST Block. If a file name is given, then diff --git a/crates/nu-parser/tests/test_parser.rs b/crates/nu-parser/tests/test_parser.rs index 7762863ba1..4d6778d201 100644 --- a/crates/nu-parser/tests/test_parser.rs +++ b/crates/nu-parser/tests/test_parser.rs @@ -41,6 +41,41 @@ impl Command for Let { } } +#[cfg(test)] +#[derive(Clone)] +pub struct Mut; + +#[cfg(test)] +impl Command for Mut { + fn name(&self) -> &str { + "mut" + } + + fn usage(&self) -> &str { + "Mock mut command." + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("mut") + .required("var_name", SyntaxShape::VarWithOptType, "variable name") + .required( + "initial_value", + SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::MathExpression)), + "equals sign followed by value", + ) + } + + fn run( + &self, + _engine_state: &EngineState, + _stack: &mut Stack, + _call: &Call, + _input: PipelineData, + ) -> Result { + todo!() + } +} + fn test_int( test_tag: &str, // name of sub-test test: &[u8], // input expression @@ -1149,11 +1184,29 @@ fn test_nothing_comparison_eq() { fn test_redirection_with_letmut(#[case] phase: &[u8]) { let engine_state = EngineState::new(); let mut working_set = StateWorkingSet::new(&engine_state); - let _block = parse(&mut working_set, None, phase, true); - assert!(matches!( - working_set.parse_errors.first(), - Some(ParseError::RedirectingBuiltinCommand(_, _, _)) - )); + working_set.add_decl(Box::new(Let)); + working_set.add_decl(Box::new(Mut)); + + let block = parse(&mut working_set, None, phase, true); + assert!( + working_set.parse_errors.is_empty(), + "parse errors: {:?}", + working_set.parse_errors + ); + assert_eq!(1, block.pipelines[0].elements.len()); + + let element = &block.pipelines[0].elements[0]; + assert!(element.redirection.is_none()); // it should be in the let block, not here + + if let Expr::Call(call) = &element.expr.expr { + let arg = call.positional_nth(1).expect("no positional args"); + let block_id = arg.as_block().expect("arg 1 is not a block"); + let block = working_set.get_block(block_id); + let inner_element = &block.pipelines[0].elements[0]; + assert!(inner_element.redirection.is_some()); + } else { + panic!("expected Call: {:?}", block.pipelines[0].elements[0]) + } } #[rstest] diff --git a/crates/nu-protocol/src/ast/block.rs b/crates/nu-protocol/src/ast/block.rs index 8f62ff99ba..a6e640cc15 100644 --- a/crates/nu-protocol/src/ast/block.rs +++ b/crates/nu-protocol/src/ast/block.rs @@ -78,6 +78,19 @@ impl Block { Type::Nothing } } + + /// Replace any `$in` variables in the initial element of pipelines within the block + pub fn replace_in_variable( + &mut self, + working_set: &mut StateWorkingSet<'_>, + new_var_id: VarId, + ) { + for pipeline in self.pipelines.iter_mut() { + if let Some(element) = pipeline.elements.first_mut() { + element.replace_in_variable(working_set, new_var_id); + } + } + } } impl From for Block diff --git a/crates/nu-protocol/src/ast/expr.rs b/crates/nu-protocol/src/ast/expr.rs index fadbffc51f..d6386f92dc 100644 --- a/crates/nu-protocol/src/ast/expr.rs +++ b/crates/nu-protocol/src/ast/expr.rs @@ -25,6 +25,7 @@ pub enum Expr { RowCondition(BlockId), UnaryNot(Box), BinaryOp(Box, Box, Box), //lhs, op, rhs + Collect(VarId, Box), Subexpression(BlockId), Block(BlockId), Closure(BlockId), @@ -65,11 +66,13 @@ impl Expr { &self, working_set: &StateWorkingSet, ) -> (Option, Option) { - // Usages of `$in` will be wrapped by a `collect` call by the parser, - // so we do not have to worry about that when considering - // which of the expressions below may consume pipeline output. match self { Expr::Call(call) => working_set.get_decl(call.decl_id).pipe_redirection(), + Expr::Collect(_, _) => { + // A collect expression always has default redirection, it's just going to collect + // stdout unless another type of redirection is specified + (None, None) + }, Expr::Subexpression(block_id) | Expr::Block(block_id) => working_set .get_block(*block_id) .pipe_redirection(working_set), diff --git a/crates/nu-protocol/src/ast/expression.rs b/crates/nu-protocol/src/ast/expression.rs index dfd4187a90..4818aa2db2 100644 --- a/crates/nu-protocol/src/ast/expression.rs +++ b/crates/nu-protocol/src/ast/expression.rs @@ -6,6 +6,8 @@ use crate::{ use serde::{Deserialize, Serialize}; use std::sync::Arc; +use super::ListItem; + /// Wrapper around [`Expr`] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Expression { @@ -106,37 +108,14 @@ impl Expression { left.has_in_variable(working_set) || right.has_in_variable(working_set) } Expr::UnaryNot(expr) => expr.has_in_variable(working_set), - Expr::Block(block_id) => { + Expr::Block(block_id) | Expr::Closure(block_id) => { let block = working_set.get_block(*block_id); - - if block.captures.contains(&IN_VARIABLE_ID) { - return true; - } - - if let Some(pipeline) = block.pipelines.first() { - match pipeline.elements.first() { - Some(element) => element.has_in_variable(working_set), - None => false, - } - } else { - false - } - } - Expr::Closure(block_id) => { - let block = working_set.get_block(*block_id); - - if block.captures.contains(&IN_VARIABLE_ID) { - return true; - } - - if let Some(pipeline) = block.pipelines.first() { - match pipeline.elements.first() { - Some(element) => element.has_in_variable(working_set), - None => false, - } - } else { - false - } + block.captures.contains(&IN_VARIABLE_ID) + || block + .pipelines + .iter() + .flat_map(|pipeline| pipeline.elements.first()) + .any(|element| element.has_in_variable(working_set)) } Expr::Binary(_) => false, Expr::Bool(_) => false, @@ -251,6 +230,9 @@ impl Expression { Expr::Signature(_) => false, Expr::String(_) => false, Expr::RawString(_) => false, + // A `$in` variable found within a `Collect` is local, as it's already been wrapped + // This is probably unlikely to happen anyway - the expressions are wrapped depth-first + Expr::Collect(_, _) => false, Expr::RowCondition(block_id) | Expr::Subexpression(block_id) => { let block = working_set.get_block(*block_id); @@ -414,6 +396,7 @@ impl Expression { i.replace_span(working_set, replaced, new_span) } } + Expr::Collect(_, expr) => expr.replace_span(working_set, replaced, new_span), Expr::RowCondition(block_id) | Expr::Subexpression(block_id) => { let mut block = (**working_set.get_block(*block_id)).clone(); @@ -443,6 +426,129 @@ impl Expression { } } + pub fn replace_in_variable(&mut self, working_set: &mut StateWorkingSet, new_var_id: VarId) { + match &mut self.expr { + Expr::Bool(_) => {} + Expr::Int(_) => {} + Expr::Float(_) => {} + Expr::Binary(_) => {} + Expr::Range(_) => {} + Expr::Var(var_id) | Expr::VarDecl(var_id) => { + if *var_id == IN_VARIABLE_ID { + *var_id = new_var_id; + } + } + Expr::Call(call) => { + for arg in call.arguments.iter_mut() { + match arg { + Argument::Positional(expr) + | Argument::Unknown(expr) + | Argument::Named((_, _, Some(expr))) + | Argument::Spread(expr) => { + expr.replace_in_variable(working_set, new_var_id) + } + Argument::Named((_, _, None)) => {} + } + } + for expr in call.parser_info.values_mut() { + expr.replace_in_variable(working_set, new_var_id) + } + } + Expr::ExternalCall(head, args) => { + head.replace_in_variable(working_set, new_var_id); + for arg in args.iter_mut() { + match arg { + ExternalArgument::Regular(expr) | ExternalArgument::Spread(expr) => { + expr.replace_in_variable(working_set, new_var_id) + } + } + } + } + Expr::Operator(_) => {} + // `$in` in `Collect` has already been handled, so we don't need to check further + Expr::Collect(_, _) => {} + Expr::Block(block_id) + | Expr::Closure(block_id) + | Expr::RowCondition(block_id) + | Expr::Subexpression(block_id) => { + let mut block = Block::clone(working_set.get_block(*block_id)); + block.replace_in_variable(working_set, new_var_id); + *working_set.get_block_mut(*block_id) = block; + } + Expr::UnaryNot(expr) => { + expr.replace_in_variable(working_set, new_var_id); + } + Expr::BinaryOp(lhs, op, rhs) => { + for expr in [lhs, op, rhs] { + expr.replace_in_variable(working_set, new_var_id); + } + } + Expr::MatchBlock(match_patterns) => { + for (_, expr) in match_patterns.iter_mut() { + expr.replace_in_variable(working_set, new_var_id); + } + } + Expr::List(items) => { + for item in items.iter_mut() { + match item { + ListItem::Item(expr) | ListItem::Spread(_, expr) => { + expr.replace_in_variable(working_set, new_var_id) + } + } + } + } + Expr::Table(table) => { + for col_expr in table.columns.iter_mut() { + col_expr.replace_in_variable(working_set, new_var_id); + } + for row in table.rows.iter_mut() { + for row_expr in row.iter_mut() { + row_expr.replace_in_variable(working_set, new_var_id); + } + } + } + Expr::Record(items) => { + for item in items.iter_mut() { + match item { + RecordItem::Pair(key, val) => { + key.replace_in_variable(working_set, new_var_id); + val.replace_in_variable(working_set, new_var_id); + } + RecordItem::Spread(_, expr) => { + expr.replace_in_variable(working_set, new_var_id) + } + } + } + } + Expr::Keyword(kw) => kw.expr.replace_in_variable(working_set, new_var_id), + Expr::ValueWithUnit(value_with_unit) => value_with_unit + .expr + .replace_in_variable(working_set, new_var_id), + Expr::DateTime(_) => {} + Expr::Filepath(_, _) => {} + Expr::Directory(_, _) => {} + Expr::GlobPattern(_, _) => {} + Expr::String(_) => {} + Expr::RawString(_) => {} + Expr::CellPath(_) => {} + Expr::FullCellPath(full_cell_path) => { + full_cell_path + .head + .replace_in_variable(working_set, new_var_id); + } + Expr::ImportPattern(_) => {} + Expr::Overlay(_) => {} + Expr::Signature(_) => {} + Expr::StringInterpolation(exprs) | Expr::GlobInterpolation(exprs, _) => { + for expr in exprs.iter_mut() { + expr.replace_in_variable(working_set, new_var_id); + } + } + Expr::Nothing => {} + Expr::Garbage => {} + } + } + pub fn new(working_set: &mut StateWorkingSet, expr: Expr, span: Span, ty: Type) -> Expression { let span_id = working_set.add_span(span); Expression { diff --git a/crates/nu-protocol/src/ast/pipeline.rs b/crates/nu-protocol/src/ast/pipeline.rs index 3f2a216485..0ed02c700b 100644 --- a/crates/nu-protocol/src/ast/pipeline.rs +++ b/crates/nu-protocol/src/ast/pipeline.rs @@ -1,4 +1,4 @@ -use crate::{ast::Expression, engine::StateWorkingSet, OutDest, Span}; +use crate::{ast::Expression, engine::StateWorkingSet, OutDest, Span, VarId}; use serde::{Deserialize, Serialize}; use std::fmt::Display; @@ -62,6 +62,19 @@ impl RedirectionTarget { RedirectionTarget::Pipe { .. } => {} } } + + pub fn replace_in_variable( + &mut self, + working_set: &mut StateWorkingSet<'_>, + new_var_id: VarId, + ) { + match self { + RedirectionTarget::File { expr, .. } => { + expr.replace_in_variable(working_set, new_var_id) + } + RedirectionTarget::Pipe { .. } => {} + } + } } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] @@ -75,6 +88,23 @@ pub enum PipelineRedirection { err: RedirectionTarget, }, } +impl PipelineRedirection { + pub fn replace_in_variable( + &mut self, + working_set: &mut StateWorkingSet<'_>, + new_var_id: VarId, + ) { + match self { + PipelineRedirection::Single { source: _, target } => { + target.replace_in_variable(working_set, new_var_id) + } + PipelineRedirection::Separate { out, err } => { + out.replace_in_variable(working_set, new_var_id); + err.replace_in_variable(working_set, new_var_id); + } + } + } +} #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PipelineElement { @@ -120,6 +150,17 @@ impl PipelineElement { ) -> (Option, Option) { self.expr.expr.pipe_redirection(working_set) } + + pub fn replace_in_variable( + &mut self, + working_set: &mut StateWorkingSet<'_>, + new_var_id: VarId, + ) { + self.expr.replace_in_variable(working_set, new_var_id); + if let Some(redirection) = &mut self.redirection { + redirection.replace_in_variable(working_set, new_var_id); + } + } } #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/crates/nu-protocol/src/debugger/profiler.rs b/crates/nu-protocol/src/debugger/profiler.rs index c61ee04d12..ea81a1b814 100644 --- a/crates/nu-protocol/src/debugger/profiler.rs +++ b/crates/nu-protocol/src/debugger/profiler.rs @@ -343,6 +343,7 @@ fn expr_to_string(engine_state: &EngineState, expr: &Expr) -> String { Expr::String(_) | Expr::RawString(_) => "string".to_string(), Expr::StringInterpolation(_) => "string interpolation".to_string(), Expr::GlobInterpolation(_, _) => "glob interpolation".to_string(), + Expr::Collect(_, _) => "collect".to_string(), Expr::Subexpression(_) => "subexpression".to_string(), Expr::Table(_) => "table".to_string(), Expr::UnaryNot(_) => "unary not".to_string(), diff --git a/crates/nu-protocol/src/eval_base.rs b/crates/nu-protocol/src/eval_base.rs index f49d235482..933342f577 100644 --- a/crates/nu-protocol/src/eval_base.rs +++ b/crates/nu-protocol/src/eval_base.rs @@ -159,6 +159,9 @@ pub trait Eval { Expr::ExternalCall(head, args) => { Self::eval_external_call(state, mut_state, head, args, expr_span) } + Expr::Collect(var_id, expr) => { + Self::eval_collect::(state, mut_state, *var_id, expr) + } Expr::Subexpression(block_id) => { Self::eval_subexpression::(state, mut_state, *block_id, expr_span) } @@ -356,6 +359,13 @@ pub trait Eval { span: Span, ) -> Result; + fn eval_collect( + state: Self::State<'_>, + mut_state: &mut Self::MutState, + var_id: VarId, + expr: &Expression, + ) -> Result; + fn eval_subexpression( state: Self::State<'_>, mut_state: &mut Self::MutState, diff --git a/crates/nu-protocol/src/eval_const.rs b/crates/nu-protocol/src/eval_const.rs index cba2392338..cd4780773f 100644 --- a/crates/nu-protocol/src/eval_const.rs +++ b/crates/nu-protocol/src/eval_const.rs @@ -422,6 +422,15 @@ impl Eval for EvalConst { Err(ShellError::NotAConstant { span }) } + fn eval_collect( + _: &StateWorkingSet, + _: &mut (), + _var_id: VarId, + expr: &Expression, + ) -> Result { + Err(ShellError::NotAConstant { span: expr.span }) + } + fn eval_subexpression( working_set: &StateWorkingSet, _: &mut (), diff --git a/crates/nu-protocol/src/ir/display.rs b/crates/nu-protocol/src/ir/display.rs index c28323cca4..0026e15eab 100644 --- a/crates/nu-protocol/src/ir/display.rs +++ b/crates/nu-protocol/src/ir/display.rs @@ -102,6 +102,10 @@ impl<'a> fmt::Display for FmtInstruction<'a> { let var = FmtVar::new(self.engine_state, *var_id); write!(f, "{:WIDTH$} {var}, {src}", "store-variable") } + Instruction::DropVariable { var_id } => { + let var = FmtVar::new(self.engine_state, *var_id); + write!(f, "{:WIDTH$} {var}", "drop-variable") + } Instruction::LoadEnv { dst, key } => { let key = FmtData(self.data, *key); write!(f, "{:WIDTH$} {dst}, {key}", "load-env") diff --git a/crates/nu-protocol/src/ir/mod.rs b/crates/nu-protocol/src/ir/mod.rs index aff5f42c8c..bdded28c0e 100644 --- a/crates/nu-protocol/src/ir/mod.rs +++ b/crates/nu-protocol/src/ir/mod.rs @@ -127,6 +127,8 @@ pub enum Instruction { LoadVariable { dst: RegId, var_id: VarId }, /// Store the value of a variable from the `src` register StoreVariable { var_id: VarId, src: RegId }, + /// Remove a variable from the stack, freeing up whatever resources were associated with it + DropVariable { var_id: VarId }, /// Load the value of an environment variable into the `dst` register LoadEnv { dst: RegId, key: DataSlice }, /// Load the value of an environment variable into the `dst` register, or `Nothing` if it @@ -290,6 +292,7 @@ impl Instruction { Instruction::Drain { .. } => None, Instruction::LoadVariable { dst, .. } => Some(dst), Instruction::StoreVariable { .. } => None, + Instruction::DropVariable { .. } => None, Instruction::LoadEnv { dst, .. } => Some(dst), Instruction::LoadEnvOpt { dst, .. } => Some(dst), Instruction::StoreEnv { .. } => None, diff --git a/crates/nuon/src/from.rs b/crates/nuon/src/from.rs index 2cfeaebc61..2138e50ac5 100644 --- a/crates/nuon/src/from.rs +++ b/crates/nuon/src/from.rs @@ -329,6 +329,12 @@ fn convert_to_value( msg: "glob interpolation not supported in nuon".into(), span: expr.span, }), + Expr::Collect(..) => Err(ShellError::OutsideSpannedLabeledError { + src: original_text.to_string(), + error: "Error when loading".into(), + msg: "`$in` not supported in nuon".into(), + span: expr.span, + }), Expr::Subexpression(..) => Err(ShellError::OutsideSpannedLabeledError { src: original_text.to_string(), error: "Error when loading".into(), diff --git a/tests/repl/test_engine.rs b/tests/repl/test_engine.rs index e452295f42..1b74e741f7 100644 --- a/tests/repl/test_engine.rs +++ b/tests/repl/test_engine.rs @@ -52,6 +52,29 @@ fn in_and_if_else() -> TestResult { ) } +#[test] +fn in_with_closure() -> TestResult { + // Can use $in twice + run_test(r#"3 | do { let x = $in; let y = $in; $x + $y }"#, "6") +} + +#[test] +fn in_with_custom_command() -> TestResult { + // Can use $in twice + run_test( + r#"def foo [] { let x = $in; let y = $in; $x + $y }; 3 | foo"#, + "6", + ) +} + +#[test] +fn in_used_twice_and_also_in_pipeline() -> TestResult { + run_test( + r#"3 | do { let x = $in; let y = $in; $x + $y | $in * 4 }"#, + "24", + ) +} + #[test] fn help_works_with_missing_requirements() -> TestResult { run_test(r#"each --help | lines | length"#, "72") diff --git a/tests/repl/test_type_check.rs b/tests/repl/test_type_check.rs index 87216e6672..8a00f2f486 100644 --- a/tests/repl/test_type_check.rs +++ b/tests/repl/test_type_check.rs @@ -129,3 +129,16 @@ fn transpose_into_load_env() -> TestResult { "10", ) } + +#[test] +fn in_variable_expression_correct_output_type() -> TestResult { + run_test(r#"def foo []: nothing -> string { 'foo' | $"($in)" }"#, "") +} + +#[test] +fn in_variable_expression_wrong_output_type() -> TestResult { + fail_test( + r#"def foo []: nothing -> int { 'foo' | $"($in)" }"#, + "expected int", + ) +} From 22379c9846713c3905bc3e4ef5f9187113f5a689 Mon Sep 17 00:00:00 2001 From: 132ikl <132@ikl.sh> Date: Wed, 17 Jul 2024 19:52:47 -0400 Subject: [PATCH 057/126] Make default config more consistent (#13399) # Description Fixes some minor config inconsistencies: - Add default value for `shape_glob_interpolation` to light theme, as it is missing - Add right padding space to `shape_garbage` options - Use a integer value for `footer_mode` instead of a string - Set `buffer_editor` to null instead of empty string # User-Facing Changes N/A # Tests + Formatting N/A # After Submitting N/A --- crates/nu-utils/src/sample_config/default_config.nu | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/crates/nu-utils/src/sample_config/default_config.nu b/crates/nu-utils/src/sample_config/default_config.nu index 5856e385c4..3c3e57fa95 100644 --- a/crates/nu-utils/src/sample_config/default_config.nu +++ b/crates/nu-utils/src/sample_config/default_config.nu @@ -47,7 +47,7 @@ let dark_theme = { shape_flag: blue_bold shape_float: purple_bold # shapes are used to change the cli syntax highlighting - shape_garbage: { fg: white bg: red attr: b} + shape_garbage: { fg: white bg: red attr: b } shape_glob_interpolation: cyan_bold shape_globpattern: cyan_bold shape_int: purple_bold @@ -114,7 +114,8 @@ let light_theme = { shape_flag: blue_bold shape_float: purple_bold # shapes are used to change the cli syntax highlighting - shape_garbage: { fg: white bg: red attr: b} + shape_garbage: { fg: white bg: red attr: b } + shape_glob_interpolation: cyan_bold shape_globpattern: cyan_bold shape_int: purple_bold shape_internalcall: cyan_bold @@ -226,9 +227,9 @@ $env.config = { color_config: $dark_theme # if you want a more interesting theme, you can replace the empty record with `$dark_theme`, `$light_theme` or another custom record use_grid_icons: true - footer_mode: "25" # always, never, number_of_rows, auto + footer_mode: 25 # always, never, number_of_rows, auto float_precision: 2 # the precision for displaying floats in tables - buffer_editor: "" # command that will be used to edit the current line buffer with ctrl+o, if unset fallback to $env.EDITOR and $env.VISUAL + buffer_editor: null # command that will be used to edit the current line buffer with ctrl+o, if unset fallback to $env.EDITOR and $env.VISUAL use_ansi_coloring: true bracketed_paste: true # enable bracketed paste, currently useless on windows edit_mode: emacs # emacs, vi @@ -888,4 +889,4 @@ $env.config = { event: { edit: selectall } } ] -} \ No newline at end of file +} From c19944f2916e023ef2325331ebedd4d36be5a46e Mon Sep 17 00:00:00 2001 From: Ian Manske Date: Fri, 19 Jul 2024 04:16:09 +0000 Subject: [PATCH 058/126] Refactor `window` (#13401) # Description Following from #13377, this PR refactors the related `window` command. In the case where `window` should behave exactly like `chunks`, it now reuses the same code that `chunks` does. Otherwise, the other cases have been rewritten and have resulted in a performance increase: | window size | stride | old time (ms) | new time (ms) | | -----------:| ------:| -------------:| -------------:| | 20 | 1 | 757 | 722 | | 2 | 1 | 364 | 333 | | 1 | 1 | 343 | 293 | | 20 | 20 | 90 | 63 | | 2 | 2 | 215 | 175 | | 20 | 30 | 74 | 60 | | 2 | 4 | 141 | 110 | # User-Facing Changes `window` will now error if the window size or stride is 0, which is technically a breaking change. --- crates/nu-command/src/filters/chunks.rs | 48 ++-- crates/nu-command/src/filters/window.rs | 307 +++++++++++---------- crates/nu-command/tests/commands/mod.rs | 1 + crates/nu-command/tests/commands/window.rs | 103 +++++++ 4 files changed, 297 insertions(+), 162 deletions(-) create mode 100644 crates/nu-command/tests/commands/window.rs diff --git a/crates/nu-command/src/filters/chunks.rs b/crates/nu-command/src/filters/chunks.rs index 11a59c2aa9..68d682ecd1 100644 --- a/crates/nu-command/src/filters/chunks.rs +++ b/crates/nu-command/src/filters/chunks.rs @@ -1,5 +1,6 @@ use nu_engine::command_prelude::*; use nu_protocol::ListStream; +use std::num::NonZeroUsize; #[derive(Clone)] pub struct Chunks; @@ -89,26 +90,33 @@ impl Command for Chunks { span: chunk_size.span(), })?; - if size == 0 { - return Err(ShellError::IncorrectValue { - msg: "`chunk_size` cannot be zero".into(), - val_span: chunk_size.span(), - call_span: head, - }); - } + let size = NonZeroUsize::try_from(size).map_err(|_| ShellError::IncorrectValue { + msg: "`chunk_size` cannot be zero".into(), + val_span: chunk_size.span(), + call_span: head, + })?; - match input { - PipelineData::Value(Value::List { vals, .. }, metadata) => { - let chunks = ChunksIter::new(vals, size, head); - let stream = ListStream::new(chunks, head, engine_state.signals().clone()); - Ok(PipelineData::ListStream(stream, metadata)) - } - PipelineData::ListStream(stream, metadata) => { - let stream = stream.modify(|iter| ChunksIter::new(iter, size, head)); - Ok(PipelineData::ListStream(stream, metadata)) - } - input => Err(input.unsupported_input_error("list", head)), + chunks(engine_state, input, size, head) + } +} + +pub fn chunks( + engine_state: &EngineState, + input: PipelineData, + chunk_size: NonZeroUsize, + span: Span, +) -> Result { + match input { + PipelineData::Value(Value::List { vals, .. }, metadata) => { + let chunks = ChunksIter::new(vals, chunk_size, span); + let stream = ListStream::new(chunks, span, engine_state.signals().clone()); + Ok(PipelineData::ListStream(stream, metadata)) } + PipelineData::ListStream(stream, metadata) => { + let stream = stream.modify(|iter| ChunksIter::new(iter, chunk_size, span)); + Ok(PipelineData::ListStream(stream, metadata)) + } + input => Err(input.unsupported_input_error("list", span)), } } @@ -119,10 +127,10 @@ struct ChunksIter> { } impl> ChunksIter { - fn new(iter: impl IntoIterator, size: usize, span: Span) -> Self { + fn new(iter: impl IntoIterator, size: NonZeroUsize, span: Span) -> Self { Self { iter: iter.into_iter(), - size, + size: size.into(), span, } } diff --git a/crates/nu-command/src/filters/window.rs b/crates/nu-command/src/filters/window.rs index 3c470f478d..556543118b 100644 --- a/crates/nu-command/src/filters/window.rs +++ b/crates/nu-command/src/filters/window.rs @@ -1,5 +1,6 @@ use nu_engine::command_prelude::*; -use nu_protocol::ValueIterator; +use nu_protocol::ListStream; +use std::num::NonZeroUsize; #[derive(Clone)] pub struct Window; @@ -12,8 +13,8 @@ impl Command for Window { fn signature(&self) -> Signature { Signature::build("window") .input_output_types(vec![( - Type::List(Box::new(Type::Any)), - Type::List(Box::new(Type::List(Box::new(Type::Any)))), + Type::list(Type::Any), + Type::list(Type::list(Type::Any)), )]) .required("window_size", SyntaxShape::Int, "The size of each window.") .named( @@ -34,72 +35,41 @@ impl Command for Window { "Creates a sliding window of `window_size` that slide by n rows/elements across input." } + fn extra_usage(&self) -> &str { + "This command will error if `window_size` or `stride` are negative or zero." + } + fn examples(&self) -> Vec { - let stream_test_1 = vec![ - Value::list( - vec![Value::test_int(1), Value::test_int(2)], - Span::test_data(), - ), - Value::list( - vec![Value::test_int(2), Value::test_int(3)], - Span::test_data(), - ), - Value::list( - vec![Value::test_int(3), Value::test_int(4)], - Span::test_data(), - ), - ]; - - let stream_test_2 = vec![ - Value::list( - vec![Value::test_int(1), Value::test_int(2)], - Span::test_data(), - ), - Value::list( - vec![Value::test_int(4), Value::test_int(5)], - Span::test_data(), - ), - Value::list( - vec![Value::test_int(7), Value::test_int(8)], - Span::test_data(), - ), - ]; - - let stream_test_3 = vec![ - Value::list( - vec![Value::test_int(1), Value::test_int(2), Value::test_int(3)], - Span::test_data(), - ), - Value::list( - vec![Value::test_int(4), Value::test_int(5)], - Span::test_data(), - ), - ]; - vec![ Example { example: "[1 2 3 4] | window 2", description: "A sliding window of two elements", - result: Some(Value::list( - stream_test_1, - Span::test_data(), - )), + result: Some(Value::test_list(vec![ + Value::test_list(vec![Value::test_int(1), Value::test_int(2)]), + Value::test_list(vec![Value::test_int(2), Value::test_int(3)]), + Value::test_list(vec![Value::test_int(3), Value::test_int(4)]), + ])), }, Example { example: "[1, 2, 3, 4, 5, 6, 7, 8] | window 2 --stride 3", description: "A sliding window of two elements, with a stride of 3", - result: Some(Value::list( - stream_test_2, - Span::test_data(), - )), + result: Some(Value::test_list(vec![ + Value::test_list(vec![Value::test_int(1), Value::test_int(2)]), + Value::test_list(vec![Value::test_int(4), Value::test_int(5)]), + Value::test_list(vec![Value::test_int(7), Value::test_int(8)]), + ])), }, Example { example: "[1, 2, 3, 4, 5] | window 3 --stride 3 --remainder", description: "A sliding window of equal stride that includes remainder. Equivalent to chunking", - result: Some(Value::list( - stream_test_3, - Span::test_data(), - )), + result: Some(Value::test_list(vec![ + Value::test_list(vec![ + Value::test_int(1), + Value::test_int(2), + Value::test_int(3), + ]), + Value::test_list(vec![Value::test_int(4), Value::test_int(5)]), + ])), }, ] } @@ -112,116 +82,169 @@ impl Command for Window { input: PipelineData, ) -> Result { let head = call.head; - let group_size: Spanned = call.req(engine_state, stack, 0)?; - let metadata = input.metadata(); - let stride: Option = call.get_flag(engine_state, stack, "stride")?; + let window_size: Value = call.req(engine_state, stack, 0)?; + let stride: Option = call.get_flag(engine_state, stack, "stride")?; let remainder = call.has_flag(engine_state, stack, "remainder")?; - let stride = stride.unwrap_or(1); + let size = + usize::try_from(window_size.as_int()?).map_err(|_| ShellError::NeedsPositiveValue { + span: window_size.span(), + })?; - //FIXME: add in support for external redirection when engine-q supports it generally + let size = NonZeroUsize::try_from(size).map_err(|_| ShellError::IncorrectValue { + msg: "`window_size` cannot be zero".into(), + val_span: window_size.span(), + call_span: head, + })?; - let each_group_iterator = EachWindowIterator { - group_size: group_size.item, - input: Box::new(input.into_iter()), - span: head, - previous: None, - stride, - remainder, + let stride = if let Some(stride_val) = stride { + let stride = usize::try_from(stride_val.as_int()?).map_err(|_| { + ShellError::NeedsPositiveValue { + span: stride_val.span(), + } + })?; + + NonZeroUsize::try_from(stride).map_err(|_| ShellError::IncorrectValue { + msg: "`stride` cannot be zero".into(), + val_span: stride_val.span(), + call_span: head, + })? + } else { + NonZeroUsize::MIN }; - Ok(each_group_iterator.into_pipeline_data_with_metadata( - head, - engine_state.signals().clone(), - metadata, - )) + if remainder && size == stride { + super::chunks::chunks(engine_state, input, size, head) + } else if stride >= size { + match input { + PipelineData::Value(Value::List { vals, .. }, metadata) => { + let chunks = WindowGapIter::new(vals, size, stride, remainder, head); + let stream = ListStream::new(chunks, head, engine_state.signals().clone()); + Ok(PipelineData::ListStream(stream, metadata)) + } + PipelineData::ListStream(stream, metadata) => { + let stream = stream + .modify(|iter| WindowGapIter::new(iter, size, stride, remainder, head)); + Ok(PipelineData::ListStream(stream, metadata)) + } + input => Err(input.unsupported_input_error("list", head)), + } + } else { + match input { + PipelineData::Value(Value::List { vals, .. }, metadata) => { + let chunks = WindowOverlapIter::new(vals, size, stride, remainder, head); + let stream = ListStream::new(chunks, head, engine_state.signals().clone()); + Ok(PipelineData::ListStream(stream, metadata)) + } + PipelineData::ListStream(stream, metadata) => { + let stream = stream + .modify(|iter| WindowOverlapIter::new(iter, size, stride, remainder, head)); + Ok(PipelineData::ListStream(stream, metadata)) + } + input => Err(input.unsupported_input_error("list", head)), + } + } } } -struct EachWindowIterator { - group_size: usize, - input: ValueIterator, - span: Span, - previous: Option>, +struct WindowOverlapIter> { + iter: I, + window: Vec, stride: usize, remainder: bool, + span: Span, } -impl Iterator for EachWindowIterator { +impl> WindowOverlapIter { + fn new( + iter: impl IntoIterator, + size: NonZeroUsize, + stride: NonZeroUsize, + remainder: bool, + span: Span, + ) -> Self { + Self { + iter: iter.into_iter(), + window: Vec::with_capacity(size.into()), + stride: stride.into(), + remainder, + span, + } + } +} + +impl> Iterator for WindowOverlapIter { type Item = Value; fn next(&mut self) -> Option { - let mut group = self.previous.take().unwrap_or_else(|| { - let mut vec = Vec::new(); - - // We default to a Vec of capacity size + stride as striding pushes n extra elements to the end - vec.try_reserve(self.group_size + self.stride).ok(); - - vec - }); - let mut current_count = 0; - - if group.is_empty() { - loop { - let item = self.input.next(); - - match item { - Some(v) => { - group.push(v); - - current_count += 1; - if current_count >= self.group_size { - break; - } - } - None => { - if self.remainder { - break; - } else { - return None; - } - } - } - } + let len = if self.window.is_empty() { + self.window.capacity() } else { - // our historic buffer is already full, so stride instead + self.stride + }; - loop { - let item = self.input.next(); + self.window.extend((&mut self.iter).take(len)); - match item { - Some(v) => { - group.push(v); - - current_count += 1; - if current_count >= self.stride { - break; - } - } - None => { - if self.remainder { - break; - } else { - return None; - } - } - } - } - - // We now have elements + stride in our group, and need to - // drop the skipped elements. Drain to preserve allocation and capacity - // Dropping this iterator consumes it. - group.drain(..self.stride.min(group.len())); + if self.window.len() == self.window.capacity() + || (self.remainder && !self.window.is_empty()) + { + let mut next = Vec::with_capacity(self.window.len()); + next.extend(self.window.iter().skip(self.stride).cloned()); + let window = std::mem::replace(&mut self.window, next); + Some(Value::list(window, self.span)) + } else { + None } + } +} - if group.is_empty() { - return None; +struct WindowGapIter> { + iter: I, + size: usize, + skip: usize, + first: bool, + remainder: bool, + span: Span, +} + +impl> WindowGapIter { + fn new( + iter: impl IntoIterator, + size: NonZeroUsize, + stride: NonZeroUsize, + remainder: bool, + span: Span, + ) -> Self { + let size = size.into(); + Self { + iter: iter.into_iter(), + size, + skip: stride.get() - size, + first: true, + remainder, + span, } + } +} - let return_group = group.clone(); - self.previous = Some(group); +impl> Iterator for WindowGapIter { + type Item = Value; - Some(Value::list(return_group, self.span)) + fn next(&mut self) -> Option { + let mut window = Vec::with_capacity(self.size); + window.extend( + (&mut self.iter) + .skip(if self.first { 0 } else { self.skip }) + .take(self.size), + ); + + self.first = false; + + if window.len() == self.size || (self.remainder && !window.is_empty()) { + Some(Value::list(window, self.span)) + } else { + None + } } } diff --git a/crates/nu-command/tests/commands/mod.rs b/crates/nu-command/tests/commands/mod.rs index 4c9f8e86f8..3cb468ad30 100644 --- a/crates/nu-command/tests/commands/mod.rs +++ b/crates/nu-command/tests/commands/mod.rs @@ -115,6 +115,7 @@ mod try_; mod ucp; #[cfg(unix)] mod ulimit; +mod window; mod debug; mod umkdir; diff --git a/crates/nu-command/tests/commands/window.rs b/crates/nu-command/tests/commands/window.rs new file mode 100644 index 0000000000..6e4addc6c0 --- /dev/null +++ b/crates/nu-command/tests/commands/window.rs @@ -0,0 +1,103 @@ +use nu_test_support::nu; + +#[test] +fn window_size_negative() { + let actual = nu!("[0 1 2] | window -1"); + assert!(actual.err.contains("positive")); +} + +#[test] +fn window_size_zero() { + let actual = nu!("[0 1 2] | window 0"); + assert!(actual.err.contains("zero")); +} + +#[test] +fn window_size_not_int() { + let actual = nu!("[0 1 2] | window (if true { 1sec })"); + assert!(actual.err.contains("can't convert")); +} + +#[test] +fn stride_negative() { + let actual = nu!("[0 1 2] | window 1 -s -1"); + assert!(actual.err.contains("positive")); +} + +#[test] +fn stride_zero() { + let actual = nu!("[0 1 2] | window 1 -s 0"); + assert!(actual.err.contains("zero")); +} + +#[test] +fn stride_not_int() { + let actual = nu!("[0 1 2] | window 1 -s (if true { 1sec })"); + assert!(actual.err.contains("can't convert")); +} + +#[test] +fn empty() { + let actual = nu!("[] | window 2 | is-empty"); + assert_eq!(actual.out, "true"); +} + +#[test] +fn list_stream() { + let actual = nu!("([0 1 2] | every 1 | window 2) == ([0 1 2] | window 2)"); + assert_eq!(actual.out, "true"); +} + +#[test] +fn table_stream() { + let actual = nu!("([[foo bar]; [0 1] [2 3] [4 5]] | every 1 | window 2) == ([[foo bar]; [0 1] [2 3] [4 5]] | window 2)"); + assert_eq!(actual.out, "true"); +} + +#[test] +fn no_empty_chunks() { + let actual = nu!("([0 1 2 3 4 5] | window 3 -s 3 -r | length) == 2"); + assert_eq!(actual.out, "true"); +} + +#[test] +fn same_as_chunks() { + let actual = nu!("([0 1 2 3 4] | window 2 -s 2 -r) == ([0 1 2 3 4 ] | chunks 2)"); + assert_eq!(actual.out, "true"); +} + +#[test] +fn stride_equal_to_window_size() { + let actual = nu!("([0 1 2 3] | window 2 -s 2 | flatten) == [0 1 2 3]"); + assert_eq!(actual.out, "true"); +} + +#[test] +fn stride_greater_than_window_size() { + let actual = nu!("([0 1 2 3 4] | window 2 -s 3 | flatten) == [0 1 3 4]"); + assert_eq!(actual.out, "true"); +} + +#[test] +fn stride_less_than_window_size() { + let actual = nu!("([0 1 2 3 4 5] | window 3 -s 2 | length) == 2"); + assert_eq!(actual.out, "true"); +} + +#[test] +fn stride_equal_to_window_size_remainder() { + let actual = nu!("([0 1 2 3 4] | window 2 -s 2 -r | flatten) == [0 1 2 3 4]"); + assert_eq!(actual.out, "true"); +} + +#[test] +fn stride_greater_than_window_size_remainder() { + let actual = nu!("([0 1 2 3 4] | window 2 -s 3 -r | flatten) == [0 1 3 4]"); + assert_eq!(actual.out, "true"); +} + +#[test] +fn stride_less_than_window_size_remainder() { + let actual = nu!("([0 1 2 3 4 5] | window 3 -s 2 -r | length) == 3"); + assert_eq!(actual.out, "true"); +} From f3843a61762ab4054608b2b0393a322ee01dc78d Mon Sep 17 00:00:00 2001 From: Devyn Cairns Date: Thu, 18 Jul 2024 22:54:21 -0700 Subject: [PATCH 059/126] Make plugins able to find and call other commands (#13407) # Description Adds functionality to the plugin interface to support calling internal commands from plugins. For example, using `view ir --json`: ```rust let closure: Value = call.req(0)?; let Some(decl_id) = engine.find_decl("view ir")? else { return Err(LabeledError::new("`view ir` not found")); }; let ir_json = engine.call_decl( decl_id, EvaluatedCall::new(call.head) .with_named("json".into_spanned(call.head), Value::bool(true, call.head)) .with_positional(closure), PipelineData::Empty, true, false, )?.into_value()?.into_string()?; let ir = serde_json::from_value(&ir_json); // ... ``` # User-Facing Changes Plugin developers can now use `EngineInterface::find_decl()` and `call_decl()` to call internal commands, which could be handy for formatters like `to csv` or `to nuon`, or for reflection commands that help gain insight into the engine. # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` # After Submitting - [ ] release notes - [ ] update plugin protocol documentation: `FindDecl`, `CallDecl` engine calls; `Identifier` engine call response --- crates/nu-plugin-engine/src/context.rs | 103 +++++++++++++++--- crates/nu-plugin-engine/src/interface/mod.rs | 16 +++ .../nu-plugin-protocol/src/evaluated_call.rs | 86 +++++++++++++++ crates/nu-plugin-protocol/src/lib.rs | 35 +++++- crates/nu-plugin/src/plugin/interface/mod.rs | 73 ++++++++++++- crates/nu-protocol/src/ir/call.rs | 2 +- .../src/commands/call_decl.rs | 78 +++++++++++++ crates/nu_plugin_example/src/commands/mod.rs | 2 + crates/nu_plugin_example/src/lib.rs | 1 + tests/plugins/call_decl.rs | 42 +++++++ tests/plugins/mod.rs | 1 + 11 files changed, 418 insertions(+), 21 deletions(-) create mode 100644 crates/nu_plugin_example/src/commands/call_decl.rs create mode 100644 tests/plugins/call_decl.rs diff --git a/crates/nu-plugin-engine/src/context.rs b/crates/nu-plugin-engine/src/context.rs index aa504a246a..d9023cde35 100644 --- a/crates/nu-plugin-engine/src/context.rs +++ b/crates/nu-plugin-engine/src/context.rs @@ -1,9 +1,10 @@ use crate::util::MutableCow; use nu_engine::{get_eval_block_with_early_return, get_full_help, ClosureEvalOnce}; +use nu_plugin_protocol::EvaluatedCall; use nu_protocol::{ engine::{Call, Closure, EngineState, Redirection, Stack}, - Config, IntoSpanned, OutDest, PipelineData, PluginIdentity, ShellError, Signals, Span, Spanned, - Value, + ir, Config, DeclId, IntoSpanned, OutDest, PipelineData, PluginIdentity, ShellError, Signals, + Span, Spanned, Value, }; use std::{ borrow::Cow, @@ -44,6 +45,17 @@ pub trait PluginExecutionContext: Send + Sync { redirect_stdout: bool, redirect_stderr: bool, ) -> Result; + /// Find a declaration by name + fn find_decl(&self, name: &str) -> Result, ShellError>; + /// Call a declaration with arguments and input + fn call_decl( + &mut self, + decl_id: DeclId, + call: EvaluatedCall, + input: PipelineData, + redirect_stdout: bool, + redirect_stderr: bool, + ) -> Result; /// Create an owned version of the context with `'static` lifetime fn boxed(&self) -> Box; } @@ -177,19 +189,10 @@ impl<'a> PluginExecutionContext for PluginExecutionCommandContext<'a> { .captures_to_stack(closure.item.captures) .reset_pipes(); - let stdout = if redirect_stdout { - Some(Redirection::Pipe(OutDest::Capture)) - } else { - None - }; - - let stderr = if redirect_stderr { - Some(Redirection::Pipe(OutDest::Capture)) - } else { - None - }; - - let stack = &mut stack.push_redirection(stdout, stderr); + let stack = &mut stack.push_redirection( + redirect_stdout.then_some(Redirection::Pipe(OutDest::Capture)), + redirect_stderr.then_some(Redirection::Pipe(OutDest::Capture)), + ); // Set up the positional arguments for (idx, value) in positional.into_iter().enumerate() { @@ -211,6 +214,57 @@ impl<'a> PluginExecutionContext for PluginExecutionCommandContext<'a> { eval_block_with_early_return(&self.engine_state, stack, block, input) } + fn find_decl(&self, name: &str) -> Result, ShellError> { + Ok(self.engine_state.find_decl(name.as_bytes(), &[])) + } + + fn call_decl( + &mut self, + decl_id: DeclId, + call: EvaluatedCall, + input: PipelineData, + redirect_stdout: bool, + redirect_stderr: bool, + ) -> Result { + if decl_id >= self.engine_state.num_decls() { + return Err(ShellError::GenericError { + error: "Plugin misbehaving".into(), + msg: format!("Tried to call unknown decl id: {}", decl_id), + span: Some(call.head), + help: None, + inner: vec![], + }); + } + + let decl = self.engine_state.get_decl(decl_id); + + let stack = &mut self.stack.push_redirection( + redirect_stdout.then_some(Redirection::Pipe(OutDest::Capture)), + redirect_stderr.then_some(Redirection::Pipe(OutDest::Capture)), + ); + + let mut call_builder = ir::Call::build(decl_id, call.head); + + for positional in call.positional { + call_builder.add_positional(stack, positional.span(), positional); + } + + for (name, value) in call.named { + if let Some(value) = value { + call_builder.add_named(stack, &name.item, "", name.span, value); + } else { + call_builder.add_flag(stack, &name.item, "", name.span); + } + } + + decl.run( + &self.engine_state, + stack, + &(&call_builder.finish()).into(), + input, + ) + } + fn boxed(&self) -> Box { Box::new(PluginExecutionCommandContext { identity: self.identity.clone(), @@ -298,6 +352,25 @@ impl PluginExecutionContext for PluginExecutionBogusContext { }) } + fn find_decl(&self, _name: &str) -> Result, ShellError> { + Err(ShellError::NushellFailed { + msg: "find_decl not implemented on bogus".into(), + }) + } + + fn call_decl( + &mut self, + _decl_id: DeclId, + _call: EvaluatedCall, + _input: PipelineData, + _redirect_stdout: bool, + _redirect_stderr: bool, + ) -> Result { + Err(ShellError::NushellFailed { + msg: "call_decl not implemented on bogus".into(), + }) + } + fn boxed(&self) -> Box { Box::new(PluginExecutionBogusContext) } diff --git a/crates/nu-plugin-engine/src/interface/mod.rs b/crates/nu-plugin-engine/src/interface/mod.rs index d1b722dedf..680b3b123f 100644 --- a/crates/nu-plugin-engine/src/interface/mod.rs +++ b/crates/nu-plugin-engine/src/interface/mod.rs @@ -1316,6 +1316,22 @@ pub(crate) fn handle_engine_call( } => context .eval_closure(closure, positional, input, redirect_stdout, redirect_stderr) .map(EngineCallResponse::PipelineData), + EngineCall::FindDecl(name) => context.find_decl(&name).map(|decl_id| { + if let Some(decl_id) = decl_id { + EngineCallResponse::Identifier(decl_id) + } else { + EngineCallResponse::empty() + } + }), + EngineCall::CallDecl { + decl_id, + call, + input, + redirect_stdout, + redirect_stderr, + } => context + .call_decl(decl_id, call, input, redirect_stdout, redirect_stderr) + .map(EngineCallResponse::PipelineData), } } diff --git a/crates/nu-plugin-protocol/src/evaluated_call.rs b/crates/nu-plugin-protocol/src/evaluated_call.rs index 58f8987865..5e760693a5 100644 --- a/crates/nu-plugin-protocol/src/evaluated_call.rs +++ b/crates/nu-plugin-protocol/src/evaluated_call.rs @@ -27,6 +27,82 @@ pub struct EvaluatedCall { } impl EvaluatedCall { + /// Create a new [`EvaluatedCall`] with the given head span. + pub fn new(head: Span) -> EvaluatedCall { + EvaluatedCall { + head, + positional: vec![], + named: vec![], + } + } + + /// Add a positional argument to an [`EvaluatedCall`]. + /// + /// # Example + /// + /// ```rust + /// # use nu_protocol::{Value, Span, IntoSpanned}; + /// # use nu_plugin_protocol::EvaluatedCall; + /// # let head = Span::test_data(); + /// let mut call = EvaluatedCall::new(head); + /// call.add_positional(Value::test_int(1337)); + /// ``` + pub fn add_positional(&mut self, value: Value) -> &mut Self { + self.positional.push(value); + self + } + + /// Add a named argument to an [`EvaluatedCall`]. + /// + /// # Example + /// + /// ```rust + /// # use nu_protocol::{Value, Span, IntoSpanned}; + /// # use nu_plugin_protocol::EvaluatedCall; + /// # let head = Span::test_data(); + /// let mut call = EvaluatedCall::new(head); + /// call.add_named("foo".into_spanned(head), Value::test_string("bar")); + /// ``` + pub fn add_named(&mut self, name: Spanned>, value: Value) -> &mut Self { + self.named.push((name.map(Into::into), Some(value))); + self + } + + /// Add a flag argument to an [`EvaluatedCall`]. A flag argument is a named argument with no + /// value. + /// + /// # Example + /// + /// ```rust + /// # use nu_protocol::{Value, Span, IntoSpanned}; + /// # use nu_plugin_protocol::EvaluatedCall; + /// # let head = Span::test_data(); + /// let mut call = EvaluatedCall::new(head); + /// call.add_flag("pretty".into_spanned(head)); + /// ``` + pub fn add_flag(&mut self, name: Spanned>) -> &mut Self { + self.named.push((name.map(Into::into), None)); + self + } + + /// Builder variant of [`.add_positional()`]. + pub fn with_positional(mut self, value: Value) -> Self { + self.add_positional(value); + self + } + + /// Builder variant of [`.add_named()`]. + pub fn with_named(mut self, name: Spanned>, value: Value) -> Self { + self.add_named(name, value); + self + } + + /// Builder variant of [`.add_flag()`]. + pub fn with_flag(mut self, name: Spanned>) -> Self { + self.add_flag(name); + self + } + /// Try to create an [`EvaluatedCall`] from a command `Call`. pub fn try_from_call( call: &Call, @@ -192,6 +268,16 @@ impl EvaluatedCall { Ok(false) } + /// Returns the [`Span`] of the name of an optional named argument. + /// + /// This can be used in errors for named arguments that don't take values. + pub fn get_flag_span(&self, flag_name: &str) -> Option { + self.named + .iter() + .find(|(name, _)| name.item == flag_name) + .map(|(name, _)| name.span) + } + /// Returns the [`Value`] of an optional named argument /// /// # Examples diff --git a/crates/nu-plugin-protocol/src/lib.rs b/crates/nu-plugin-protocol/src/lib.rs index 739366d910..a9196a2d8d 100644 --- a/crates/nu-plugin-protocol/src/lib.rs +++ b/crates/nu-plugin-protocol/src/lib.rs @@ -22,7 +22,7 @@ mod tests; pub mod test_util; use nu_protocol::{ - ast::Operator, engine::Closure, ByteStreamType, Config, LabeledError, PipelineData, + ast::Operator, engine::Closure, ByteStreamType, Config, DeclId, LabeledError, PipelineData, PluginMetadata, PluginSignature, ShellError, Span, Spanned, Value, }; use nu_utils::SharedCow; @@ -494,6 +494,21 @@ pub enum EngineCall { /// Whether to redirect stderr from external commands redirect_stderr: bool, }, + /// Find a declaration by name + FindDecl(String), + /// Call a declaration with args + CallDecl { + /// The id of the declaration to be called (can be found with `FindDecl`) + decl_id: DeclId, + /// Information about the call (head span, arguments, etc.) + call: EvaluatedCall, + /// Pipeline input to the call + input: D, + /// Whether to redirect stdout from external commands + redirect_stdout: bool, + /// Whether to redirect stderr from external commands + redirect_stderr: bool, + }, } impl EngineCall { @@ -511,6 +526,8 @@ impl EngineCall { EngineCall::LeaveForeground => "LeaveForeground", EngineCall::GetSpanContents(_) => "GetSpanContents", EngineCall::EvalClosure { .. } => "EvalClosure", + EngineCall::FindDecl(_) => "FindDecl", + EngineCall::CallDecl { .. } => "CallDecl", } } @@ -544,6 +561,20 @@ impl EngineCall { redirect_stdout, redirect_stderr, }, + EngineCall::FindDecl(name) => EngineCall::FindDecl(name), + EngineCall::CallDecl { + decl_id, + call, + input, + redirect_stdout, + redirect_stderr, + } => EngineCall::CallDecl { + decl_id, + call, + input: f(input)?, + redirect_stdout, + redirect_stderr, + }, }) } } @@ -556,6 +587,7 @@ pub enum EngineCallResponse { PipelineData(D), Config(SharedCow), ValueMap(HashMap), + Identifier(usize), } impl EngineCallResponse { @@ -570,6 +602,7 @@ impl EngineCallResponse { EngineCallResponse::PipelineData(data) => EngineCallResponse::PipelineData(f(data)?), EngineCallResponse::Config(config) => EngineCallResponse::Config(config), EngineCallResponse::ValueMap(map) => EngineCallResponse::ValueMap(map), + EngineCallResponse::Identifier(id) => EngineCallResponse::Identifier(id), }) } } diff --git a/crates/nu-plugin/src/plugin/interface/mod.rs b/crates/nu-plugin/src/plugin/interface/mod.rs index 60c5964f26..c20e2adc39 100644 --- a/crates/nu-plugin/src/plugin/interface/mod.rs +++ b/crates/nu-plugin/src/plugin/interface/mod.rs @@ -6,12 +6,12 @@ use nu_plugin_core::{ StreamManagerHandle, }; use nu_plugin_protocol::{ - CallInfo, CustomValueOp, EngineCall, EngineCallId, EngineCallResponse, Ordering, PluginCall, - PluginCallId, PluginCallResponse, PluginCustomValue, PluginInput, PluginOption, PluginOutput, - ProtocolInfo, + CallInfo, CustomValueOp, EngineCall, EngineCallId, EngineCallResponse, EvaluatedCall, Ordering, + PluginCall, PluginCallId, PluginCallResponse, PluginCustomValue, PluginInput, PluginOption, + PluginOutput, ProtocolInfo, }; use nu_protocol::{ - engine::Closure, Config, LabeledError, PipelineData, PluginMetadata, PluginSignature, + engine::Closure, Config, DeclId, LabeledError, PipelineData, PluginMetadata, PluginSignature, ShellError, Signals, Span, Spanned, Value, }; use nu_utils::SharedCow; @@ -872,6 +872,71 @@ impl EngineInterface { } } + /// Ask the engine for the identifier for a declaration. If found, the result can then be passed + /// to [`.call_decl()`] to call other internal commands. + /// + /// See [`.call_decl()`] for an example. + pub fn find_decl(&self, name: impl Into) -> Result, ShellError> { + let call = EngineCall::FindDecl(name.into()); + + match self.engine_call(call)? { + EngineCallResponse::Error(err) => Err(err), + EngineCallResponse::Identifier(id) => Ok(Some(id)), + EngineCallResponse::PipelineData(PipelineData::Empty) => Ok(None), + _ => Err(ShellError::PluginFailedToDecode { + msg: "Received unexpected response type for EngineCall::FindDecl".into(), + }), + } + } + + /// Ask the engine to call an internal command, using the declaration ID previously looked up + /// with [`.find_decl()`]. + /// + /// # Example + /// + /// ```rust,no_run + /// # use nu_protocol::{Value, ShellError, PipelineData}; + /// # use nu_plugin::{EngineInterface, EvaluatedCall}; + /// # fn example(engine: &EngineInterface, call: &EvaluatedCall) -> Result { + /// if let Some(decl_id) = engine.find_decl("scope commands")? { + /// let commands = engine.call_decl( + /// decl_id, + /// EvaluatedCall::new(call.head), + /// PipelineData::Empty, + /// true, + /// false, + /// )?; + /// commands.into_value(call.head) + /// } else { + /// Ok(Value::list(vec![], call.head)) + /// } + /// # } + /// ``` + pub fn call_decl( + &self, + decl_id: DeclId, + call: EvaluatedCall, + input: PipelineData, + redirect_stdout: bool, + redirect_stderr: bool, + ) -> Result { + let call = EngineCall::CallDecl { + decl_id, + call, + input, + redirect_stdout, + redirect_stderr, + }; + + match self.engine_call(call)? { + EngineCallResponse::Error(err) => Err(err), + EngineCallResponse::PipelineData(data) => Ok(data), + _ => Err(ShellError::PluginFailedToDecode { + msg: "Received unexpected response type for EngineCall::CallDecl".into(), + }), + } + } + /// Tell the engine whether to disable garbage collection for this plugin. /// /// The garbage collector is enabled by default, but plugins can turn it off (ideally diff --git a/crates/nu-protocol/src/ir/call.rs b/crates/nu-protocol/src/ir/call.rs index 3d16f82eb6..b931373017 100644 --- a/crates/nu-protocol/src/ir/call.rs +++ b/crates/nu-protocol/src/ir/call.rs @@ -239,7 +239,7 @@ impl CallBuilder { } self.inner.args_len += 1; if let Some(span) = argument.span() { - self.inner.span = self.inner.span.append(span); + self.inner.span = self.inner.span.merge(span); } stack.arguments.push(argument); self diff --git a/crates/nu_plugin_example/src/commands/call_decl.rs b/crates/nu_plugin_example/src/commands/call_decl.rs new file mode 100644 index 0000000000..d8c8206d90 --- /dev/null +++ b/crates/nu_plugin_example/src/commands/call_decl.rs @@ -0,0 +1,78 @@ +use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand}; +use nu_protocol::{ + IntoSpanned, LabeledError, PipelineData, Record, Signature, Spanned, SyntaxShape, Value, +}; + +use crate::ExamplePlugin; + +pub struct CallDecl; + +impl PluginCommand for CallDecl { + type Plugin = ExamplePlugin; + + fn name(&self) -> &str { + "example call-decl" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()) + .required( + "name", + SyntaxShape::String, + "the name of the command to call", + ) + .optional( + "named_args", + SyntaxShape::Record(vec![]), + "named arguments to pass to the command", + ) + .rest( + "positional_args", + SyntaxShape::Any, + "positional arguments to pass to the command", + ) + } + + fn usage(&self) -> &str { + "Demonstrates calling other commands from plugins using `call_decl()`." + } + + fn extra_usage(&self) -> &str { + " +The arguments will not be typechecked at parse time. This command is for +demonstration only, and should not be used for anything real. +" + .trim() + } + + fn run( + &self, + _plugin: &ExamplePlugin, + engine: &EngineInterface, + call: &EvaluatedCall, + input: PipelineData, + ) -> Result { + let name: Spanned = call.req(0)?; + let named_args: Option = call.opt(1)?; + let positional_args: Vec = call.rest(2)?; + + let decl_id = engine.find_decl(&name.item)?.ok_or_else(|| { + LabeledError::new(format!("Can't find `{}`", name.item)) + .with_label("not in scope", name.span) + })?; + + let mut new_call = EvaluatedCall::new(call.head); + + for (key, val) in named_args.into_iter().flatten() { + new_call.add_named(key.into_spanned(val.span()), val); + } + + for val in positional_args { + new_call.add_positional(val); + } + + let result = engine.call_decl(decl_id, new_call, input, true, false)?; + + Ok(result) + } +} diff --git a/crates/nu_plugin_example/src/commands/mod.rs b/crates/nu_plugin_example/src/commands/mod.rs index dd808616a9..b1703a3a47 100644 --- a/crates/nu_plugin_example/src/commands/mod.rs +++ b/crates/nu_plugin_example/src/commands/mod.rs @@ -13,11 +13,13 @@ pub use three::Three; pub use two::Two; // Engine interface demos +mod call_decl; mod config; mod disable_gc; mod env; mod view_span; +pub use call_decl::CallDecl; pub use config::Config; pub use disable_gc::DisableGc; pub use env::Env; diff --git a/crates/nu_plugin_example/src/lib.rs b/crates/nu_plugin_example/src/lib.rs index acbebf9b39..3db97659bf 100644 --- a/crates/nu_plugin_example/src/lib.rs +++ b/crates/nu_plugin_example/src/lib.rs @@ -27,6 +27,7 @@ impl Plugin for ExamplePlugin { Box::new(Env), Box::new(ViewSpan), Box::new(DisableGc), + Box::new(CallDecl), // Stream demos Box::new(CollectBytes), Box::new(Echo), diff --git a/tests/plugins/call_decl.rs b/tests/plugins/call_decl.rs new file mode 100644 index 0000000000..06aded3f42 --- /dev/null +++ b/tests/plugins/call_decl.rs @@ -0,0 +1,42 @@ +use nu_test_support::nu_with_plugins; + +#[test] +fn call_to_json() { + let result = nu_with_plugins!( + cwd: ".", + plugin: ("nu_plugin_example"), + r#" + [42] | example call-decl 'to json' {indent: 4} + "# + ); + assert!(result.status.success()); + // newlines are removed from test output + assert_eq!("[ 42]", result.out); +} + +#[test] +fn call_reduce() { + let result = nu_with_plugins!( + cwd: ".", + plugin: ("nu_plugin_example"), + r#" + [1 2 3] | example call-decl 'reduce' {fold: 10} { |it, acc| $it + $acc } + "# + ); + assert!(result.status.success()); + assert_eq!("16", result.out); +} + +#[test] +fn call_scope_variables() { + let result = nu_with_plugins!( + cwd: ".", + plugin: ("nu_plugin_example"), + r#" + let test_var = 10 + example call-decl 'scope variables' | where name == '$test_var' | length + "# + ); + assert!(result.status.success()); + assert_eq!("1", result.out); +} diff --git a/tests/plugins/mod.rs b/tests/plugins/mod.rs index 605f78b564..cdcf3a9c94 100644 --- a/tests/plugins/mod.rs +++ b/tests/plugins/mod.rs @@ -1,3 +1,4 @@ +mod call_decl; mod config; mod core_inc; mod custom_values; From e8764de3c67281a513a36457656de80c652e8b06 Mon Sep 17 00:00:00 2001 From: Wind Date: Fri, 19 Jul 2024 15:21:02 +0800 Subject: [PATCH 060/126] don't allow break/continue in `each` and `items` command (#13398) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description Fixes: #11451 # User-Facing Changes ### Before ```nushell ❯ [1 2 3] | each {|e| break; print $e } ╭────────────╮ │ empty list │ ╰────────────╯ ``` ### After ``` ❯ [1 2 3] | each {|e| break; print $e } Error: nu::shell::eval_block_with_input × Eval block failed with pipeline input ╭─[entry #9:1:2] 1 │ [1 2 3] | each {|e| break; print $e } · ┬ · ╰── source value ╰──── Error: × Break used outside of loop ╭─[entry #9:1:21] 1 │ [1 2 3] | each {|e| break; print $e } · ──┬── · ╰── used outside of loop ╰──── ``` # Tests + Formatting Removes some tests. --- crates/nu-cmd-lang/src/core_commands/break_.rs | 4 +++- .../nu-cmd-lang/src/core_commands/continue_.rs | 4 +++- crates/nu-command/src/filters/each.rs | 8 -------- crates/nu-command/src/filters/items.rs | 1 - crates/nu-command/tests/commands/break_.rs | 9 --------- crates/nu-command/tests/commands/each.rs | 16 ---------------- 6 files changed, 6 insertions(+), 36 deletions(-) diff --git a/crates/nu-cmd-lang/src/core_commands/break_.rs b/crates/nu-cmd-lang/src/core_commands/break_.rs index 90cc1a73f2..943e32a531 100644 --- a/crates/nu-cmd-lang/src/core_commands/break_.rs +++ b/crates/nu-cmd-lang/src/core_commands/break_.rs @@ -21,7 +21,9 @@ impl Command for Break { fn extra_usage(&self) -> &str { r#"This command is a parser keyword. For details, check: - https://www.nushell.sh/book/thinking_in_nu.html"# + https://www.nushell.sh/book/thinking_in_nu.html + + break can only be used in while, loop, and for loops. It can not be used with each or other filter commands"# } fn command_type(&self) -> CommandType { diff --git a/crates/nu-cmd-lang/src/core_commands/continue_.rs b/crates/nu-cmd-lang/src/core_commands/continue_.rs index cfa3e38335..b795456c1d 100644 --- a/crates/nu-cmd-lang/src/core_commands/continue_.rs +++ b/crates/nu-cmd-lang/src/core_commands/continue_.rs @@ -21,7 +21,9 @@ impl Command for Continue { fn extra_usage(&self) -> &str { r#"This command is a parser keyword. For details, check: - https://www.nushell.sh/book/thinking_in_nu.html"# + https://www.nushell.sh/book/thinking_in_nu.html + + continue can only be used in while, loop, and for loops. It can not be used with each or other filter commands"# } fn command_type(&self) -> CommandType { diff --git a/crates/nu-command/src/filters/each.rs b/crates/nu-command/src/filters/each.rs index fa1bf390b8..a7035917b3 100644 --- a/crates/nu-command/src/filters/each.rs +++ b/crates/nu-command/src/filters/each.rs @@ -132,8 +132,6 @@ with 'transpose' first."# Ok(data) => Some(data.into_value(head).unwrap_or_else(|err| { Value::error(chain_error_with_input(err, is_error, span), span) })), - Err(ShellError::Continue { span }) => Some(Value::nothing(span)), - Err(ShellError::Break { .. }) => None, Err(error) => { let error = chain_error_with_input(error, is_error, span); Some(Value::error(error, span)) @@ -149,10 +147,6 @@ with 'transpose' first."# .map_while(move |value| { let value = match value { Ok(value) => value, - Err(ShellError::Continue { span }) => { - return Some(Value::nothing(span)) - } - Err(ShellError::Break { .. }) => return None, Err(err) => return Some(Value::error(err, head)), }; @@ -163,8 +157,6 @@ with 'transpose' first."# .and_then(|data| data.into_value(head)) { Ok(value) => Some(value), - Err(ShellError::Continue { span }) => Some(Value::nothing(span)), - Err(ShellError::Break { .. }) => None, Err(error) => { let error = chain_error_with_input(error, is_error, span); Some(Value::error(error, span)) diff --git a/crates/nu-command/src/filters/items.rs b/crates/nu-command/src/filters/items.rs index 04a4c6d672..fa7b680afd 100644 --- a/crates/nu-command/src/filters/items.rs +++ b/crates/nu-command/src/filters/items.rs @@ -60,7 +60,6 @@ impl Command for Items { match result { Ok(value) => Some(value), - Err(ShellError::Break { .. }) => None, Err(err) => { let err = chain_error_with_input(err, false, span); Some(Value::error(err, head)) diff --git a/crates/nu-command/tests/commands/break_.rs b/crates/nu-command/tests/commands/break_.rs index 92a6564658..7398ac62e5 100644 --- a/crates/nu-command/tests/commands/break_.rs +++ b/crates/nu-command/tests/commands/break_.rs @@ -15,12 +15,3 @@ fn break_while_loop() { assert_eq!(actual.out, "hello"); } - -#[test] -fn break_each() { - let actual = nu!(" - [1, 2, 3, 4, 5] | each {|x| if $x > 3 { break }; $x} | math sum - "); - - assert_eq!(actual.out, "6"); -} diff --git a/crates/nu-command/tests/commands/each.rs b/crates/nu-command/tests/commands/each.rs index f8e87d5537..65b922bb6e 100644 --- a/crates/nu-command/tests/commands/each.rs +++ b/crates/nu-command/tests/commands/each.rs @@ -58,22 +58,6 @@ fn each_while_uses_enumerate_index() { assert_eq!(actual.out, "[0, 1, 2, 3]"); } -#[test] -fn each_element_continue_command() { - let actual = - nu!("[1,2,3,4,6,7] | each { |x| if ($x mod 2 == 0) {continue} else { $x }} | to nuon"); - - assert_eq!(actual.out, "[1, 3, 7]"); -} - -#[test] -fn each_element_break_command() { - let actual = - nu!("[1,2,5,4,6,7] | each { |x| if ($x mod 3 == 0) {break} else { $x }} | to nuon"); - - assert_eq!(actual.out, "[1, 2, 5, 4]"); -} - #[test] fn errors_in_nested_each_show() { let actual = nu!("[[1,2]] | each {|x| $x | each {|y| error make {msg: \"oh noes\"} } }"); From e281c03403aaf71de3cf3090bb12011dd578090d Mon Sep 17 00:00:00 2001 From: Wind Date: Fri, 19 Jul 2024 15:22:28 +0800 Subject: [PATCH 061/126] generate: switch the position of and , so the closure can have default parameters (#13393) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description Close: #12083 Close: #12084 # User-Facing Changes It's a breaking change because we have switched the position of `` and ``, after the change, initial value will be optional. So it's possible to do something like this: ```nushell > let f = {|fib = [0, 1]| {out: $fib.0, next: [$fib.1, ($fib.0 + $fib.1)]} } > generate $f | first 5 ╭───┬───╮ │ 0 │ 0 │ │ 1 │ 1 │ │ 2 │ 1 │ │ 3 │ 2 │ │ 4 │ 3 │ ╰───┴───╯ ``` It will also raise error if user don't give initial value, and the closure don't have default parameter. ```nushell ❯ let f = {|fib| {out: $fib.0, next: [$fib.1, ($fib.0 + $fib.1)]} } ❯ generate $f Error: × The initial value is missing ╭─[entry #5:1:1] 1 │ generate $f · ────┬─── · ╰── Missing intial value ╰──── help: Provide value in generate, or assigning default value to closure parameter ``` # Tests + Formatting Added some test cases. --------- Co-authored-by: Stefan Holderbach --- crates/nu-command/src/generators/generate.rs | 53 +++++++++-- crates/nu-command/tests/commands/generate.rs | 95 +++++++++++++++----- 2 files changed, 117 insertions(+), 31 deletions(-) diff --git a/crates/nu-command/src/generators/generate.rs b/crates/nu-command/src/generators/generate.rs index 8d4f48d3fb..2b2cf838f4 100644 --- a/crates/nu-command/src/generators/generate.rs +++ b/crates/nu-command/src/generators/generate.rs @@ -12,12 +12,12 @@ impl Command for Generate { fn signature(&self) -> Signature { Signature::build("generate") .input_output_types(vec![(Type::Nothing, Type::List(Box::new(Type::Any)))]) - .required("initial", SyntaxShape::Any, "Initial value.") .required( "closure", SyntaxShape::Closure(Some(vec![SyntaxShape::Any])), "Generator function.", ) + .optional("initial", SyntaxShape::Any, "Initial value.") .allow_variants_without_examples(true) .category(Category::Generators) } @@ -41,7 +41,7 @@ used as the next argument to the closure, otherwise generation stops. fn examples(&self) -> Vec { vec![ Example { - example: "generate 0 {|i| if $i <= 10 { {out: $i, next: ($i + 2)} }}", + example: "generate {|i| if $i <= 10 { {out: $i, next: ($i + 2)} }} 0", description: "Generate a sequence of numbers", result: Some(Value::list( vec![ @@ -57,10 +57,17 @@ used as the next argument to the closure, otherwise generation stops. }, Example { example: - "generate [0, 1] {|fib| {out: $fib.0, next: [$fib.1, ($fib.0 + $fib.1)]} }", + "generate {|fib| {out: $fib.0, next: [$fib.1, ($fib.0 + $fib.1)]} } [0, 1]", description: "Generate a continuous stream of Fibonacci numbers", result: None, }, + Example { + example: + "generate {|fib=[0, 1]| {out: $fib.0, next: [$fib.1, ($fib.0 + $fib.1)]} }", + description: + "Generate a continuous stream of Fibonacci numbers, using default parameters", + result: None, + }, ] } @@ -72,15 +79,15 @@ used as the next argument to the closure, otherwise generation stops. _input: PipelineData, ) -> Result { let head = call.head; - let initial: Value = call.req(engine_state, stack, 0)?; - let closure: Closure = call.req(engine_state, stack, 1)?; - + let closure: Closure = call.req(engine_state, stack, 0)?; + let initial: Option = call.opt(engine_state, stack, 1)?; + let block = engine_state.get_block(closure.block_id); let mut closure = ClosureEval::new(engine_state, stack, closure); // A type of Option is used to represent state. Invocation // will stop on None. Using Option allows functions to output // one final value before stopping. - let mut state = Some(initial); + let mut state = Some(get_initial_state(initial, &block.signature, call.head)?); let iter = std::iter::from_fn(move || { let arg = state.take()?; @@ -170,6 +177,38 @@ used as the next argument to the closure, otherwise generation stops. } } +fn get_initial_state( + initial: Option, + signature: &Signature, + span: Span, +) -> Result { + match initial { + Some(v) => Ok(v), + None => { + // the initial state should be referred from signature + if !signature.optional_positional.is_empty() + && signature.optional_positional[0].default_value.is_some() + { + Ok(signature.optional_positional[0] + .default_value + .clone() + .expect("Already checked default value")) + } else { + Err(ShellError::GenericError { + error: "The initial value is missing".to_string(), + msg: "Missing initial value".to_string(), + span: Some(span), + help: Some( + "Provide an value as an argument to generate, or assign a default value to the closure parameter" + .to_string(), + ), + inner: vec![], + }) + } + } + } +} + #[cfg(test)] mod test { use super::*; diff --git a/crates/nu-command/tests/commands/generate.rs b/crates/nu-command/tests/commands/generate.rs index 9dc2142515..9ae3b8823e 100644 --- a/crates/nu-command/tests/commands/generate.rs +++ b/crates/nu-command/tests/commands/generate.rs @@ -3,7 +3,7 @@ use nu_test_support::{nu, pipeline}; #[test] fn generate_no_next_break() { let actual = nu!( - "generate 1 {|x| if $x == 3 { {out: $x}} else { {out: $x, next: ($x + 1)} }} | to nuon" + "generate {|x| if $x == 3 { {out: $x}} else { {out: $x, next: ($x + 1)} }} 1 | to nuon" ); assert_eq!(actual.out, "[1, 2, 3]"); @@ -11,7 +11,7 @@ fn generate_no_next_break() { #[test] fn generate_null_break() { - let actual = nu!("generate 1 {|x| if $x <= 3 { {out: $x, next: ($x + 1)} }} | to nuon"); + let actual = nu!("generate {|x| if $x <= 3 { {out: $x, next: ($x + 1)} }} 1 | to nuon"); assert_eq!(actual.out, "[1, 2, 3]"); } @@ -20,13 +20,13 @@ fn generate_null_break() { fn generate_allows_empty_output() { let actual = nu!(pipeline( r#" - generate 0 {|x| + generate {|x| if $x == 1 { {next: ($x + 1)} } else if $x < 3 { {out: $x, next: ($x + 1)} } - } | to nuon + } 0 | to nuon "# )); @@ -37,11 +37,11 @@ fn generate_allows_empty_output() { fn generate_allows_no_output() { let actual = nu!(pipeline( r#" - generate 0 {|x| + generate {|x| if $x < 3 { {next: ($x + 1)} } - } | to nuon + } 0 | to nuon "# )); @@ -52,7 +52,7 @@ fn generate_allows_no_output() { fn generate_allows_null_state() { let actual = nu!(pipeline( r#" - generate 0 {|x| + generate {|x| if $x == null { {out: "done"} } else if $x < 1 { @@ -60,7 +60,7 @@ fn generate_allows_null_state() { } else { {out: "stopping", next: null} } - } | to nuon + } 0 | to nuon "# )); @@ -71,7 +71,42 @@ fn generate_allows_null_state() { fn generate_allows_null_output() { let actual = nu!(pipeline( r#" - generate 0 {|x| + generate {|x| + if $x == 3 { + {out: "done"} + } else { + {out: null, next: ($x + 1)} + } + } 0 | to nuon + "# + )); + + assert_eq!(actual.out, "[null, null, null, done]"); +} + +#[test] +fn generate_disallows_extra_keys() { + let actual = nu!("generate {|x| {foo: bar, out: $x}} 0 "); + assert!(actual.err.contains("Invalid block return")); +} + +#[test] +fn generate_disallows_list() { + let actual = nu!("generate {|x| [$x, ($x + 1)]} 0 "); + assert!(actual.err.contains("Invalid block return")); +} + +#[test] +fn generate_disallows_primitive() { + let actual = nu!("generate {|x| 1} 0"); + assert!(actual.err.contains("Invalid block return")); +} + +#[test] +fn generate_allow_default_parameter() { + let actual = nu!(pipeline( + r#" + generate {|x = 0| if $x == 3 { {out: "done"} } else { @@ -82,22 +117,34 @@ fn generate_allows_null_output() { )); assert_eq!(actual.out, "[null, null, null, done]"); + + // if initial is given, use initial value + let actual = nu!(pipeline( + r#" + generate {|x = 0| + if $x == 3 { + {out: "done"} + } else { + {out: null, next: ($x + 1)} + } + } 1 | to nuon + "# + )); + assert_eq!(actual.out, "[null, null, done]"); } #[test] -fn generate_disallows_extra_keys() { - let actual = nu!("generate 0 {|x| {foo: bar, out: $x}}"); - assert!(actual.err.contains("Invalid block return")); -} - -#[test] -fn generate_disallows_list() { - let actual = nu!("generate 0 {|x| [$x, ($x + 1)]}"); - assert!(actual.err.contains("Invalid block return")); -} - -#[test] -fn generate_disallows_primitive() { - let actual = nu!("generate 0 {|x| 1}"); - assert!(actual.err.contains("Invalid block return")); +fn generate_raise_error_on_no_default_parameter_closure_and_init_val() { + let actual = nu!(pipeline( + r#" + generate {|x| + if $x == 3 { + {out: "done"} + } else { + {out: null, next: ($x + 1)} + } + } | to nuon + "# + )); + assert!(actual.err.contains("The initial value is missing")); } From 4665323bb4ba0931c690bf743b54a27ec493822a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Christian=20Gr=C3=BCnhage?= Date: Fri, 19 Jul 2024 12:47:07 +0200 Subject: [PATCH 062/126] Use directories for autoloading (#13382) fixes https://github.com/nushell/nushell/issues/13378 # Description This PR tries to improve usage of system APIs to determine the location of vendored autoload files. # User-Facing Changes The paths listed in #13180 and #13217 are changing. This has not been part of a release yet, so arguably the user facing changes are only to unreleased features anyway. # Tests + Formatting Haven't done, but if someone wants to help me here, I'm open to doing it. I just don't know how to properly test this. # After Submitting --- Cargo.lock | 3 + Cargo.toml | 2 + crates/nu-cli/tests/completions/mod.rs | 2 +- crates/nu-protocol/Cargo.toml | 5 + crates/nu-protocol/src/eval_const.rs | 143 ++++++++++++++++--------- src/config_files.rs | 3 +- 6 files changed, 107 insertions(+), 51 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 49770a9a0d..97f81d5185 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3344,6 +3344,8 @@ dependencies = [ "chrono", "chrono-humanize", "convert_case", + "dirs", + "dirs-sys", "fancy-regex", "indexmap", "log", @@ -3367,6 +3369,7 @@ dependencies = [ "tempfile", "thiserror", "typetag", + "windows-sys 0.48.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 335e2f0384..81602fb4b1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -84,6 +84,7 @@ deunicode = "1.6.0" dialoguer = { default-features = false, version = "0.11" } digest = { default-features = false, version = "0.10" } dirs = "5.0" +dirs-sys = "0.4" dtparse = "2.0" encoding_rs = "0.8" fancy-regex = "0.13" @@ -178,6 +179,7 @@ v_htmlescape = "0.15.0" wax = "0.6" which = "6.0.0" windows = "0.54" +windows-sys = "0.48" winreg = "0.52" [dependencies] diff --git a/crates/nu-cli/tests/completions/mod.rs b/crates/nu-cli/tests/completions/mod.rs index 590979251e..aa51e5c9bb 100644 --- a/crates/nu-cli/tests/completions/mod.rs +++ b/crates/nu-cli/tests/completions/mod.rs @@ -833,7 +833,7 @@ fn variables_completions() { "plugin-path".into(), "startup-time".into(), "temp-path".into(), - "vendor-autoload-dir".into(), + "vendor-autoload-dirs".into(), ]; // Match results diff --git a/crates/nu-protocol/Cargo.toml b/crates/nu-protocol/Cargo.toml index eaa861a073..ea900bf08d 100644 --- a/crates/nu-protocol/Cargo.toml +++ b/crates/nu-protocol/Cargo.toml @@ -23,6 +23,7 @@ byte-unit = { version = "5.1", features = [ "serde" ] } chrono = { workspace = true, features = [ "serde", "std", "unstable-locales" ], default-features = false } chrono-humanize = { workspace = true } convert_case = { workspace = true } +dirs = { workspace = true } fancy-regex = { workspace = true } indexmap = { workspace = true } lru = { workspace = true } @@ -38,6 +39,10 @@ log = { workspace = true } [target.'cfg(unix)'.dependencies] nix = { workspace = true, default-features = false, features = ["signal"] } +[target.'cfg(windows)'.dependencies] +dirs-sys = { workspace = true } +windows-sys = { workspace = true } + [features] plugin = [ "brotli", diff --git a/crates/nu-protocol/src/eval_const.rs b/crates/nu-protocol/src/eval_const.rs index cd4780773f..4cb5ccc0f5 100644 --- a/crates/nu-protocol/src/eval_const.rs +++ b/crates/nu-protocol/src/eval_const.rs @@ -185,24 +185,15 @@ pub(crate) fn create_nu_constant(engine_state: &EngineState, span: Span) -> Valu }, ); - // Create a system level directory for nushell scripts, modules, completions, etc - // that can be changed by setting the NU_VENDOR_AUTOLOAD_DIR env var on any platform - // before nushell is compiled OR if NU_VENDOR_AUTOLOAD_DIR is not set for non-windows - // systems, the PREFIX env var can be set before compile and used as PREFIX/nushell/vendor/autoload record.push( - "vendor-autoload-dir", - // pseudo code - // if env var NU_VENDOR_AUTOLOAD_DIR is set, in any platform, use it - // if not, if windows, use ALLUSERPROFILE\nushell\vendor\autoload - // if not, if non-windows, if env var PREFIX is set, use PREFIX/share/nushell/vendor/autoload - // if not, use the default /usr/share/nushell/vendor/autoload - - // check to see if NU_VENDOR_AUTOLOAD_DIR env var is set, if not, use the default - if let Some(path) = get_vendor_autoload_dir(engine_state) { - Value::string(path.to_string_lossy(), span) - } else { - Value::error(ShellError::ConfigDirNotFound { span: Some(span) }, span) - }, + "vendor-autoload-dirs", + Value::list( + get_vendor_autoload_dirs(engine_state) + .iter() + .map(|path| Value::string(path.to_string_lossy(), span)) + .collect(), + span, + ), ); record.push("temp-path", { @@ -259,39 +250,95 @@ pub(crate) fn create_nu_constant(engine_state: &EngineState, span: Span) -> Valu Value::record(record, span) } -pub fn get_vendor_autoload_dir(engine_state: &EngineState) -> Option { - // pseudo code - // if env var NU_VENDOR_AUTOLOAD_DIR is set, in any platform, use it - // if not, if windows, use ALLUSERPROFILE\nushell\vendor\autoload - // if not, if non-windows, if env var PREFIX is set, use PREFIX/share/nushell/vendor/autoload - // if not, use the default /usr/share/nushell/vendor/autoload +pub fn get_vendor_autoload_dirs(_engine_state: &EngineState) -> Vec { + // load order for autoload dirs + // /Library/Application Support/nushell/vendor/autoload on macOS + // /nushell/vendor/autoload for every dir in XDG_DATA_DIRS in reverse order on platforms other than windows. If XDG_DATA_DIRS is not set, it falls back to /share if PREFIX ends in local, or /local/share:/share otherwise. If PREFIX is not set, fall back to /usr/local/share:/usr/share. + // %ProgramData%\nushell\vendor\autoload on windows + // NU_VENDOR_AUTOLOAD_DIR from compile time, if env var is set at compile time + // if on macOS, additionally check XDG_DATA_HOME, which `dirs` is only doing on Linux + // /nushell/vendor/autoload of the current user according to the `dirs` crate + // NU_VENDOR_AUTOLOAD_DIR at runtime, if env var is set - // check to see if NU_VENDOR_AUTOLOAD_DIR env var is set, if not, use the default - Some( - option_env!("NU_VENDOR_AUTOLOAD_DIR") - .map(String::from) - .unwrap_or_else(|| { - if cfg!(windows) { - let all_user_profile = match engine_state.get_env_var("ALLUSERPROFILE") { - Some(v) => format!( - "{}\\nushell\\vendor\\autoload", - v.coerce_string().unwrap_or("C:\\ProgramData".into()) - ), - None => "C:\\ProgramData\\nushell\\vendor\\autoload".into(), - }; - all_user_profile - } else { - // In non-Windows environments, if NU_VENDOR_AUTOLOAD_DIR is not set - // check to see if PREFIX env var is set, and use it as PREFIX/nushell/vendor/autoload - // otherwise default to /usr/share/nushell/vendor/autoload - option_env!("PREFIX").map(String::from).map_or_else( - || "/usr/local/share/nushell/vendor/autoload".into(), - |prefix| format!("{}/share/nushell/vendor/autoload", prefix), - ) - } + let into_autoload_path_fn = |mut path: PathBuf| { + path.push("nushell"); + path.push("vendor"); + path.push("autoload"); + path + }; + + let mut dirs = Vec::new(); + + let mut append_fn = |path: PathBuf| { + if !dirs.contains(&path) { + dirs.push(path) + } + }; + + #[cfg(target_os = "macos")] + std::iter::once("/Library/Application Support") + .map(PathBuf::from) + .map(into_autoload_path_fn) + .for_each(&mut append_fn); + #[cfg(unix)] + { + use std::os::unix::ffi::OsStrExt; + + std::env::var_os("XDG_DATA_DIRS") + .or_else(|| { + option_env!("PREFIX").map(|prefix| { + if prefix.ends_with("local") { + std::ffi::OsString::from(format!("{prefix}/share")) + } else { + std::ffi::OsString::from(format!("{prefix}/local/share:{prefix}/share")) + } + }) }) - .into(), - ) + .unwrap_or_else(|| std::ffi::OsString::from("/usr/local/share/:/usr/share/")) + .as_encoded_bytes() + .split(|b| *b == b':') + .map(|split| into_autoload_path_fn(PathBuf::from(std::ffi::OsStr::from_bytes(split)))) + .rev() + .for_each(&mut append_fn); + } + + #[cfg(target_os = "windows")] + dirs_sys::known_folder(windows_sys::Win32::UI::Shell::FOLDERID_ProgramData) + .into_iter() + .map(into_autoload_path_fn) + .for_each(&mut append_fn); + + option_env!("NU_VENDOR_AUTOLOAD_DIR") + .into_iter() + .map(PathBuf::from) + .for_each(&mut append_fn); + + #[cfg(target_os = "macos")] + std::env::var("XDG_DATA_HOME") + .ok() + .map(PathBuf::from) + .or_else(|| { + dirs::home_dir().map(|mut home| { + home.push(".local"); + home.push("share"); + home + }) + }) + .map(into_autoload_path_fn) + .into_iter() + .for_each(&mut append_fn); + + dirs::data_dir() + .into_iter() + .map(into_autoload_path_fn) + .for_each(&mut append_fn); + + std::env::var_os("NU_VENDOR_AUTOLOAD_DIR") + .into_iter() + .map(PathBuf::from) + .for_each(&mut append_fn); + + dirs } fn eval_const_call( diff --git a/src/config_files.rs b/src/config_files.rs index 30977a6d0e..cf72819600 100644 --- a/src/config_files.rs +++ b/src/config_files.rs @@ -200,8 +200,7 @@ pub(crate) fn read_vendor_autoload_files(engine_state: &mut EngineState, stack: column!() ); - // read and source vendor_autoload_files file if exists - if let Some(autoload_dir) = nu_protocol::eval_const::get_vendor_autoload_dir(engine_state) { + for autoload_dir in nu_protocol::eval_const::get_vendor_autoload_dirs(engine_state) { warn!("read_vendor_autoload_files: {}", autoload_dir.display()); if autoload_dir.exists() { From aa9a42776bd647c513815a23aa08a1693cdf0f41 Mon Sep 17 00:00:00 2001 From: Devyn Cairns Date: Fri, 19 Jul 2024 05:12:19 -0700 Subject: [PATCH 063/126] Make `ast::Call::span()` and `arguments_span()` more robust (#13412) # Description Trying to help @amtoine out here - the spans calculated by `ast::Call` by `span()` and `argument_span()` are suspicious and are not properly guarding against `end` that might be before `start`. Just using `Span::merge()` / `merge_many()` instead to try to make the behaviour as simple and consistent as possible. Hopefully then even if the arguments have some crazy spans, we don't have spans that are just totally invalid. # Tests + Formatting I did check that everything passes with this. --- crates/nu-protocol/src/ast/call.rs | 38 +++++------------------------- 1 file changed, 6 insertions(+), 32 deletions(-) diff --git a/crates/nu-protocol/src/ast/call.rs b/crates/nu-protocol/src/ast/call.rs index d26d0af300..0f7b25e21f 100644 --- a/crates/nu-protocol/src/ast/call.rs +++ b/crates/nu-protocol/src/ast/call.rs @@ -110,17 +110,11 @@ impl Call { /// If there are one or more arguments the span encompasses the start of the first argument to /// end of the last argument pub fn arguments_span(&self) -> Span { - let past = self.head.past(); - - let start = self - .arguments - .first() - .map(|a| a.span()) - .unwrap_or(past) - .start; - let end = self.arguments.last().map(|a| a.span()).unwrap_or(past).end; - - Span::new(start, end) + if self.arguments.is_empty() { + self.head.past() + } else { + Span::merge_many(self.arguments.iter().map(|a| a.span())) + } } pub fn named_iter( @@ -341,27 +335,7 @@ impl Call { } pub fn span(&self) -> Span { - let mut span = self.head; - - for positional in self.positional_iter() { - if positional.span.end > span.end { - span.end = positional.span.end; - } - } - - for (named, _, val) in self.named_iter() { - if named.span.end > span.end { - span.end = named.span.end; - } - - if let Some(val) = &val { - if val.span.end > span.end { - span.end = val.span.end; - } - } - } - - span + self.head.merge(self.arguments_span()) } } From dbd60ed4f46fd4abbb675aac83195f8668978b72 Mon Sep 17 00:00:00 2001 From: suimong Date: Sat, 20 Jul 2024 01:55:38 +0800 Subject: [PATCH 064/126] Tiny make up to the documentation of `reduce` (#13408) This tiny PR improves the documentation of the `reduce` command by explicitly stating the direction of reduction, i.e. from left to right, and adds an example for demonstration. --------- Co-authored-by: Ben Yang --- crates/nu-command/src/filters/reduce.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/crates/nu-command/src/filters/reduce.rs b/crates/nu-command/src/filters/reduce.rs index 87fbe3b23e..824e7709e5 100644 --- a/crates/nu-command/src/filters/reduce.rs +++ b/crates/nu-command/src/filters/reduce.rs @@ -36,7 +36,7 @@ impl Command for Reduce { } fn usage(&self) -> &str { - "Aggregate a list to a single value using an accumulator closure." + "Aggregate a list (starting from the left) to a single value using an accumulator closure." } fn search_terms(&self) -> Vec<&str> { @@ -50,6 +50,11 @@ impl Command for Reduce { description: "Sum values of a list (same as 'math sum')", result: Some(Value::test_int(10)), }, + Example { + example: "[ 1 2 3 4 ] | reduce {|it, acc| $acc - $it }", + description: r#"`reduce` accumulates value from left to right, equivalent to (((1 - 2) - 3) - 4)."#, + result: Some(Value::test_int(-8)), + }, Example { example: "[ 8 7 6 ] | enumerate | reduce --fold 0 {|it, acc| $acc + $it.item + $it.index }", @@ -61,6 +66,11 @@ impl Command for Reduce { description: "Sum values with a starting value (fold)", result: Some(Value::test_int(20)), }, + Example { + example: r#"[[foo baz] [baz quux]] | reduce --fold "foobar" {|it, acc| $acc | str replace $it.0 $it.1}"#, + description: "Iteratively perform string replace (from left to right): 'foobar' -> 'bazbar' -> 'quuxbar'", + result: Some(Value::test_string("quuxbar")), + }, Example { example: r#"[ i o t ] | reduce --fold "Arthur, King of the Britons" {|it, acc| $acc | str replace --all $it "X" }"#, description: "Replace selected characters in a string with 'X'", From 5db57abc7d813c7b0bb56709379ea82a4b85b4eb Mon Sep 17 00:00:00 2001 From: Zoey Hewll Date: Sat, 20 Jul 2024 18:34:27 +0800 Subject: [PATCH 065/126] add --quiet flag to `watch` command (#13415) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description Adds a `--quiet` flag to the `watch` command, silencing the message usually shown when invoking `watch`. Resolves #13411 As implemented, `--quiet` is orthogonal to `--verbose`. I'm open to improving the flag's name, behaviour and/or documentation to make it more user-friendly, as I realise this may be surprising as-is. ``` > watch path {|a b c| echo $a $b $c} Now watching files at "/home/user/path". Press ctrl+c to abort. ───┬─────────────────────── 0 │ Remove 1 │ /home/user/path 2 │ ───┴─────────────────────── ^C > watch --quiet path {|a b c| echo $a $b $c} ───┬─────────────────────── 0 │ Remove 1 │ /home/user/path 2 │ ───┴─────────────────────── ^C ``` # User-Facing Changes Adds `--quiet`/`-q` flag to `watch`, which removes the initial status message. --- crates/nu-command/src/filesystem/watch.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/nu-command/src/filesystem/watch.rs b/crates/nu-command/src/filesystem/watch.rs index c9817de250..e6d86357b8 100644 --- a/crates/nu-command/src/filesystem/watch.rs +++ b/crates/nu-command/src/filesystem/watch.rs @@ -61,6 +61,7 @@ impl Command for Watch { "Watch all directories under `` recursively. Will be ignored if `` is a file (default: true)", Some('r'), ) + .switch("quiet", "Hide the initial status message (default: false)", Some('q')) .switch("verbose", "Operate in verbose mode (default: false)", Some('v')) .category(Category::FileSystem) } @@ -94,6 +95,8 @@ impl Command for Watch { let verbose = call.has_flag(engine_state, stack, "verbose")?; + let quiet = call.has_flag(engine_state, stack, "quiet")?; + let debounce_duration_flag: Option> = call.get_flag(engine_state, stack, "debounce-ms")?; let debounce_duration = match debounce_duration_flag { @@ -161,7 +164,9 @@ impl Command for Watch { // need to cache to make sure that rename event works. debouncer.cache().add_root(&path, recursive_mode); - eprintln!("Now watching files at {path:?}. Press ctrl+c to abort."); + if !quiet { + eprintln!("Now watching files at {path:?}. Press ctrl+c to abort."); + } let mut closure = ClosureEval::new(engine_state, stack, closure); From 01891d637d7b28cff1985bc3211dc4f544996c64 Mon Sep 17 00:00:00 2001 From: Devyn Cairns Date: Sun, 21 Jul 2024 01:32:36 -0700 Subject: [PATCH 066/126] Make parsing for unknown args in known externals like normal external calls (#13414) # Description This corrects the parsing of unknown arguments provided to known externals to behave exactly like external arguments passed to normal external calls. I've done this by adding a `SyntaxShape::ExternalArgument` which triggers the same parsing rules. Because I didn't like how the highlighting looked, I modified the flattener to emit `ExternalArg` flat shapes for arguments that have that syntax shape and are plain strings/globs. This is the same behavior that external calls have. Aside from passing the tests, I've also checked manually that the completer seems to work adequately. I can confirm that specified positional arguments get completion according to their specified type (including custom completions), and then anything remaining gets filepath style completion, as you'd expect from an external command. Thanks to @OJarrisonn for originally finding this issue. # User-Facing Changes - Unknown args are now parsed according to their specified syntax shape, rather than `Any`. This may be a breaking change, though I think it's extremely unlikely in practice. - The unspecified arguments of known externals are now highlighted / flattened identically to normal external arguments, which makes it more clear how they're being interpreted, and should help the completer function properly. - Known externals now have an implicit rest arg if not specified named `args`, with a syntax shape of `ExternalArgument`. # Tests + Formatting Tests added for the new behaviour. Some old tests had to be corrected to match. - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` # After Submitting - [ ] release notes (bugfix, and debatable whether it's a breaking change) --- crates/nu-parser/src/flatten.rs | 46 +++++++++++++++++++++-- crates/nu-parser/src/parse_keywords.rs | 10 +++++ crates/nu-parser/src/parser.rs | 51 +++++++++++++++++++++----- crates/nu-protocol/src/syntax_shape.rs | 8 ++++ tests/repl/test_custom_commands.rs | 8 +++- tests/repl/test_known_external.rs | 36 ++++++++++++++++++ 6 files changed, 144 insertions(+), 15 deletions(-) diff --git a/crates/nu-parser/src/flatten.rs b/crates/nu-parser/src/flatten.rs index 8a05edaef4..2f71ccea28 100644 --- a/crates/nu-parser/src/flatten.rs +++ b/crates/nu-parser/src/flatten.rs @@ -5,7 +5,7 @@ use nu_protocol::{ RecordItem, }, engine::StateWorkingSet, - DeclId, Span, VarId, + DeclId, Span, SyntaxShape, VarId, }; use std::fmt::{Display, Formatter, Result}; @@ -166,6 +166,22 @@ fn flatten_pipeline_element_into( } } +fn flatten_positional_arg_into( + working_set: &StateWorkingSet, + positional: &Expression, + shape: &SyntaxShape, + output: &mut Vec<(Span, FlatShape)>, +) { + if matches!(shape, SyntaxShape::ExternalArgument) + && matches!(positional.expr, Expr::String(..) | Expr::GlobPattern(..)) + { + // Make known external arguments look more like external arguments + output.push((positional.span, FlatShape::ExternalArg)); + } else { + flatten_expression_into(working_set, positional, output) + } +} + fn flatten_expression_into( working_set: &StateWorkingSet, expr: &Expression, @@ -249,16 +265,40 @@ fn flatten_expression_into( } } Expr::Call(call) => { + let decl = working_set.get_decl(call.decl_id); + if call.head.end != 0 { // Make sure we don't push synthetic calls output.push((call.head, FlatShape::InternalCall(call.decl_id))); } + // Follow positional arguments from the signature. + let signature = decl.signature(); + let mut positional_args = signature + .required_positional + .iter() + .chain(&signature.optional_positional); + let arg_start = output.len(); for arg in &call.arguments { match arg { - Argument::Positional(positional) | Argument::Unknown(positional) => { - flatten_expression_into(working_set, positional, output) + Argument::Positional(positional) => { + let positional_arg = positional_args.next(); + let shape = positional_arg + .or(signature.rest_positional.as_ref()) + .map(|arg| &arg.shape) + .unwrap_or(&SyntaxShape::Any); + + flatten_positional_arg_into(working_set, positional, shape, output) + } + Argument::Unknown(positional) => { + let shape = signature + .rest_positional + .as_ref() + .map(|arg| &arg.shape) + .unwrap_or(&SyntaxShape::Any); + + flatten_positional_arg_into(working_set, positional, shape, output) } Argument::Named(named) => { if named.0.span.end != 0 { diff --git a/crates/nu-parser/src/parse_keywords.rs b/crates/nu-parser/src/parse_keywords.rs index 4ac1d20699..7c41f8a78d 100644 --- a/crates/nu-parser/src/parse_keywords.rs +++ b/crates/nu-parser/src/parse_keywords.rs @@ -783,6 +783,16 @@ pub fn parse_extern( working_set.get_block_mut(block_id).signature = signature; } } else { + if signature.rest_positional.is_none() { + // Make sure that a known external takes rest args with ExternalArgument + // shape + *signature = signature.rest( + "args", + SyntaxShape::ExternalArgument, + "all other arguments to the command", + ); + } + let decl = KnownExternal { name: external_name, usage, diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index bc48b9bd0c..1fb0aee01d 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -221,6 +221,22 @@ pub(crate) fn check_call( } } +/// Parses an unknown argument for the given signature. This handles the parsing as appropriate to +/// the rest type of the command. +fn parse_unknown_arg( + working_set: &mut StateWorkingSet, + span: Span, + signature: &Signature, +) -> Expression { + let shape = signature + .rest_positional + .as_ref() + .map(|arg| arg.shape.clone()) + .unwrap_or(SyntaxShape::Any); + + parse_value(working_set, span, &shape) +} + /// Parses a string in the arg or head position of an external call. /// /// If the string begins with `r#`, it is parsed as a raw string. If it doesn't contain any quotes @@ -427,11 +443,7 @@ fn parse_external_string(working_set: &mut StateWorkingSet, span: Span) -> Expre fn parse_external_arg(working_set: &mut StateWorkingSet, span: Span) -> ExternalArgument { let contents = working_set.get_span_contents(span); - if contents.starts_with(b"$") || contents.starts_with(b"(") { - ExternalArgument::Regular(parse_dollar_expr(working_set, span)) - } else if contents.starts_with(b"[") { - ExternalArgument::Regular(parse_list_expression(working_set, span, &SyntaxShape::Any)) - } else if contents.len() > 3 + if contents.len() > 3 && contents.starts_with(b"...") && (contents[3] == b'$' || contents[3] == b'[' || contents[3] == b'(') { @@ -441,7 +453,19 @@ fn parse_external_arg(working_set: &mut StateWorkingSet, span: Span) -> External &SyntaxShape::List(Box::new(SyntaxShape::Any)), )) } else { - ExternalArgument::Regular(parse_external_string(working_set, span)) + ExternalArgument::Regular(parse_regular_external_arg(working_set, span)) + } +} + +fn parse_regular_external_arg(working_set: &mut StateWorkingSet, span: Span) -> Expression { + let contents = working_set.get_span_contents(span); + + if contents.starts_with(b"$") || contents.starts_with(b"(") { + parse_dollar_expr(working_set, span) + } else if contents.starts_with(b"[") { + parse_list_expression(working_set, span, &SyntaxShape::Any) + } else { + parse_external_string(working_set, span) } } @@ -998,7 +1022,7 @@ pub fn parse_internal_call( && signature.allows_unknown_args { working_set.parse_errors.truncate(starting_error_count); - let arg = parse_value(working_set, arg_span, &SyntaxShape::Any); + let arg = parse_unknown_arg(working_set, arg_span, &signature); call.add_unknown(arg); } else { @@ -1040,7 +1064,7 @@ pub fn parse_internal_call( && signature.allows_unknown_args { working_set.parse_errors.truncate(starting_error_count); - let arg = parse_value(working_set, arg_span, &SyntaxShape::Any); + let arg = parse_unknown_arg(working_set, arg_span, &signature); call.add_unknown(arg); } else { @@ -1135,6 +1159,10 @@ pub fn parse_internal_call( call.add_positional(Expression::garbage(working_set, arg_span)); } else { let rest_shape = match &signature.rest_positional { + Some(arg) if matches!(arg.shape, SyntaxShape::ExternalArgument) => { + // External args aren't parsed inside lists in spread position. + SyntaxShape::Any + } Some(arg) => arg.shape.clone(), None => SyntaxShape::Any, }; @@ -1196,7 +1224,7 @@ pub fn parse_internal_call( call.add_positional(arg); positional_idx += 1; } else if signature.allows_unknown_args { - let arg = parse_value(working_set, arg_span, &SyntaxShape::Any); + let arg = parse_unknown_arg(working_set, arg_span, &signature); call.add_unknown(arg); } else { @@ -4670,7 +4698,8 @@ pub fn parse_value( | SyntaxShape::Signature | SyntaxShape::Filepath | SyntaxShape::String - | SyntaxShape::GlobPattern => {} + | SyntaxShape::GlobPattern + | SyntaxShape::ExternalArgument => {} _ => { working_set.error(ParseError::Expected("non-[] value", span)); return Expression::garbage(working_set, span); @@ -4747,6 +4776,8 @@ pub fn parse_value( Expression::garbage(working_set, span) } + SyntaxShape::ExternalArgument => parse_regular_external_arg(working_set, span), + SyntaxShape::Any => { if bytes.starts_with(b"[") { //parse_value(working_set, span, &SyntaxShape::Table) diff --git a/crates/nu-protocol/src/syntax_shape.rs b/crates/nu-protocol/src/syntax_shape.rs index bece58d59a..66bd77ddca 100644 --- a/crates/nu-protocol/src/syntax_shape.rs +++ b/crates/nu-protocol/src/syntax_shape.rs @@ -47,6 +47,12 @@ pub enum SyntaxShape { /// A general expression, eg `1 + 2` or `foo --bar` Expression, + /// A (typically) string argument that follows external command argument parsing rules. + /// + /// Filepaths are expanded if unquoted, globs are allowed, and quotes embedded within unknown + /// args are unquoted. + ExternalArgument, + /// A filepath is allowed Filepath, @@ -145,6 +151,7 @@ impl SyntaxShape { SyntaxShape::DateTime => Type::Date, SyntaxShape::Duration => Type::Duration, SyntaxShape::Expression => Type::Any, + SyntaxShape::ExternalArgument => Type::Any, SyntaxShape::Filepath => Type::String, SyntaxShape::Directory => Type::String, SyntaxShape::Float => Type::Float, @@ -238,6 +245,7 @@ impl Display for SyntaxShape { SyntaxShape::Signature => write!(f, "signature"), SyntaxShape::MatchBlock => write!(f, "match-block"), SyntaxShape::Expression => write!(f, "expression"), + SyntaxShape::ExternalArgument => write!(f, "external-argument"), SyntaxShape::Boolean => write!(f, "bool"), SyntaxShape::Error => write!(f, "error"), SyntaxShape::CompleterWrapper(x, _) => write!(f, "completable<{x}>"), diff --git a/tests/repl/test_custom_commands.rs b/tests/repl/test_custom_commands.rs index 1f36b0e90c..1710b35913 100644 --- a/tests/repl/test_custom_commands.rs +++ b/tests/repl/test_custom_commands.rs @@ -186,8 +186,12 @@ fn help_present_in_def() -> TestResult { #[test] fn help_not_present_in_extern() -> TestResult { run_test( - "module test {export extern \"git fetch\" []}; use test `git fetch`; help git fetch | ansi strip", - "Usage:\n > git fetch", + r#" + module test {export extern "git fetch" []}; + use test `git fetch`; + help git fetch | find help | to text | ansi strip + "#, + "", ) } diff --git a/tests/repl/test_known_external.rs b/tests/repl/test_known_external.rs index 96ea677d37..c0278cc863 100644 --- a/tests/repl/test_known_external.rs +++ b/tests/repl/test_known_external.rs @@ -137,3 +137,39 @@ fn known_external_aliased_subcommand_from_module() -> TestResult { String::from_utf8(output.stdout)?.trim(), ) } + +#[test] +fn known_external_arg_expansion() -> TestResult { + run_test( + r#" + extern echo []; + echo ~/foo + "#, + &dirs::home_dir() + .expect("can't find home dir") + .join("foo") + .to_string_lossy(), + ) +} + +#[test] +fn known_external_arg_quoted_no_expand() -> TestResult { + run_test( + r#" + extern echo []; + echo "~/foo" + "#, + "~/foo", + ) +} + +#[test] +fn known_external_arg_internally_quoted_options() -> TestResult { + run_test( + r#" + extern echo []; + echo --option="test" + "#, + "--option=test", + ) +} From 9ab706db627df18b19eee727c39d6473892de7dd Mon Sep 17 00:00:00 2001 From: Wenbo Chen Date: Sun, 21 Jul 2024 20:30:14 +0900 Subject: [PATCH 067/126] Fix output format borken in `char --list` (#13417) # Description Resolve #13395 When we use the `char --list` command, it also outputs the characters *bel* and *backspace*, which breaks the table's alignment. ![Screenshot from 2024-07-21 16-10-03](https://github.com/user-attachments/assets/54561cbc-f1a1-4ccd-9561-65dccc939280) I also found that the behaviour at the beginning of the table is not correct because of some line change characters, as I mentioned in the issue discussion. So, the solution is to create a list containing the characters that do not need to be output. When the name is in the list, we output the character as an empty string. Here is the behaviour after fixing. ![Screenshot from 2024-07-21 16-16-08](https://github.com/user-attachments/assets/dd3345a3-6a72-4c90-b331-bc95dc6db66a) ![Screenshot from 2024-07-21 16-16-39](https://github.com/user-attachments/assets/57dc5073-7f8d-40c4-9830-36eba23075e6) --- crates/nu-command/src/strings/char_.rs | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/crates/nu-command/src/strings/char_.rs b/crates/nu-command/src/strings/char_.rs index 7737210254..aa987438d2 100644 --- a/crates/nu-command/src/strings/char_.rs +++ b/crates/nu-command/src/strings/char_.rs @@ -3,6 +3,7 @@ use nu_engine::command_prelude::*; use nu_protocol::Signals; use once_cell::sync::Lazy; +use std::collections::HashSet; // Character used to separate directories in a Path Environment variable on windows is ";" #[cfg(target_family = "windows")] @@ -149,6 +150,24 @@ static CHAR_MAP: Lazy> = Lazy::new(|| { } }); +static NO_OUTPUT_CHARS: Lazy> = Lazy::new(|| { + [ + // If the character is in the this set, we don't output it to prevent + // the broken of `char --list` command table format and alignment. + "newline", + "enter", + "nl", + "line_feed", + "lf", + "cr", + "crlf", + "bel", + "backspace", + ] + .into_iter() + .collect() +}); + impl Command for Char { fn name(&self) -> &str { "char" @@ -297,6 +316,11 @@ fn generate_character_list(signals: Signals, call_span: Span) -> PipelineData { CHAR_MAP .iter() .map(move |(name, s)| { + let character = if NO_OUTPUT_CHARS.contains(name) { + Value::string("", call_span) + } else { + Value::string(s, call_span) + }; let unicode = Value::string( s.chars() .map(|c| format!("{:x}", c as u32)) @@ -306,7 +330,7 @@ fn generate_character_list(signals: Signals, call_span: Span) -> PipelineData { ); let record = record! { "name" => Value::string(*name, call_span), - "character" => Value::string(s, call_span), + "character" => character, "unicode" => unicode, }; From 6fcd09682cbaa1e2b2077e023c5ea351ab3a6f00 Mon Sep 17 00:00:00 2001 From: Ian Manske Date: Sun, 21 Jul 2024 21:15:36 +0000 Subject: [PATCH 068/126] Fix setting metadata on byte streams (#13416) # Description Setting metadata on a byte stream converts it to a list stream. This PR makes it so that `metadata set` does not alter its input besides the metadata. ```nushell open --raw test.json | metadata set --content-type application/json | http post https://httpbin.org/post ``` --- crates/nu-command/src/debug/metadata_set.rs | 40 +++++++++++---------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/crates/nu-command/src/debug/metadata_set.rs b/crates/nu-command/src/debug/metadata_set.rs index 96f50cfdba..c78a5df5c3 100644 --- a/crates/nu-command/src/debug/metadata_set.rs +++ b/crates/nu-command/src/debug/metadata_set.rs @@ -42,32 +42,32 @@ impl Command for MetadataSet { engine_state: &EngineState, stack: &mut Stack, call: &Call, - input: PipelineData, + mut input: PipelineData, ) -> Result { let head = call.head; let ds_fp: Option = call.get_flag(engine_state, stack, "datasource-filepath")?; let ds_ls = call.has_flag(engine_state, stack, "datasource-ls")?; let content_type: Option = call.get_flag(engine_state, stack, "content-type")?; - let signals = engine_state.signals().clone(); - let metadata = input - .metadata() - .clone() - .unwrap_or_default() - .with_content_type(content_type); + + let mut metadata = match &mut input { + PipelineData::Value(_, metadata) + | PipelineData::ListStream(_, metadata) + | PipelineData::ByteStream(_, metadata) => metadata.take().unwrap_or_default(), + PipelineData::Empty => return Err(ShellError::PipelineEmpty { dst_span: head }), + }; + + if let Some(content_type) = content_type { + metadata.content_type = Some(content_type); + } match (ds_fp, ds_ls) { - (Some(path), false) => Ok(input.into_pipeline_data_with_metadata( - head, - signals, - metadata.with_data_source(DataSource::FilePath(path.into())), - )), - (None, true) => Ok(input.into_pipeline_data_with_metadata( - head, - signals, - metadata.with_data_source(DataSource::Ls), - )), - _ => Ok(input.into_pipeline_data_with_metadata(head, signals, metadata)), + (Some(path), false) => metadata.data_source = DataSource::FilePath(path.into()), + (None, true) => metadata.data_source = DataSource::Ls, + (Some(_), true) => (), // TODO: error here + (None, false) => (), } + + Ok(input.set_metadata(Some(metadata))) } fn examples(&self) -> Vec { @@ -85,7 +85,9 @@ impl Command for MetadataSet { Example { description: "Set the metadata of a file path", example: "'crates' | metadata set --content-type text/plain | metadata", - result: Some(Value::record(record!("content_type" => Value::string("text/plain", Span::test_data())), Span::test_data())), + result: Some(Value::test_record(record! { + "content_type" => Value::test_string("text/plain"), + })), }, ] } From 366e52b76dfda146dbc2bbdfda2d710169786d9b Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Sun, 21 Jul 2024 18:42:11 -0500 Subject: [PATCH 069/126] Update query web example since wikipedia keeps changing (#13421) # Description Every so ofter wikipedia changes the column names which breaks the query example. It would be good to make query web's table extraction to be smart enough to find tables that are close. This PR fixes the example. # User-Facing Changes # Tests + Formatting # After Submitting --- crates/nu_plugin_query/src/query_web.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/nu_plugin_query/src/query_web.rs b/crates/nu_plugin_query/src/query_web.rs index 163b320e16..39f50e51af 100644 --- a/crates/nu_plugin_query/src/query_web.rs +++ b/crates/nu_plugin_query/src/query_web.rs @@ -76,7 +76,7 @@ pub fn web_examples() -> Vec> { }, Example { example: "http get https://en.wikipedia.org/wiki/List_of_cities_in_India_by_population | - query web --as-table [City 'Population(2011)[3]' 'Population(2001)[3][a]' 'State or unionterritory' 'Ref']", + query web --as-table [City 'Population(2011)[3]' 'Population(2001)[3][a]' 'State or unionterritory' 'Reference']", description: "Retrieve a html table from Wikipedia and parse it into a nushell table using table headers as guides", result: None }, From 6a62ced645598301d69d6a7c7fd32dd239b8ad9d Mon Sep 17 00:00:00 2001 From: Jack Wright <56345+ayax79@users.noreply.github.com> Date: Tue, 23 Jul 2024 15:54:54 -0700 Subject: [PATCH 070/126] Updating version for nu-ansi-term and reedline (#13432) Updating nu-answer-term to 0.50.1 and reedline to 0.33 --- Cargo.lock | 11 ++++++----- Cargo.toml | 8 ++++---- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 97f81d5185..ee3faef06d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2913,11 +2913,11 @@ dependencies = [ [[package]] name = "nu-ansi-term" -version = "0.50.0" +version = "0.50.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd2800e1520bdc966782168a627aa5d1ad92e33b984bf7c7615d31280c83ff14" +checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -5004,8 +5004,9 @@ dependencies = [ [[package]] name = "reedline" -version = "0.32.0" -source = "git+https://github.com/nushell/reedline?branch=main#480059a3f52cf919341cda88e8c544edd846bc73" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f8c676a3f3814a23c6a0fc9dff6b6c35b2e04df8134aae6f3929cc34de21a53" dependencies = [ "arboard", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 81602fb4b1..32274816f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -116,7 +116,7 @@ mockito = { version = "1.4", default-features = false } native-tls = "0.2" nix = { version = "0.28", default-features = false } notify-debouncer-full = { version = "0.3", default-features = false } -nu-ansi-term = "0.50.0" +nu-ansi-term = "0.50.1" num-format = "0.4" num-traits = "0.2" omnipath = "0.1" @@ -138,7 +138,7 @@ quote = "1.0" rand = "0.8" ratatui = "0.26" rayon = "1.10" -reedline = "0.32.0" +reedline = "0.33.0" regex = "1.9.5" rmp = "0.8" rmp-serde = "1.3" @@ -305,8 +305,8 @@ bench = false # To use a development version of a dependency please use a global override here # changing versions in each sub-crate of the workspace is tedious -[patch.crates-io] -reedline = { git = "https://github.com/nushell/reedline", branch = "main" } +# [patch.crates-io] +# reedline = { git = "https://github.com/nushell/reedline", branch = "main" } # nu-ansi-term = {git = "https://github.com/nushell/nu-ansi-term.git", branch = "main"} # Run all benchmarks with `cargo bench` From a80dfe8e807035ad8d5bb751b385315982e7aad6 Mon Sep 17 00:00:00 2001 From: Devyn Cairns Date: Tue, 23 Jul 2024 16:10:35 -0700 Subject: [PATCH 071/126] Bump version to `0.96.0` (#13433) --- Cargo.lock | 74 +++++++++---------- Cargo.toml | 42 +++++------ crates/nu-cli/Cargo.toml | 24 +++--- crates/nu-cmd-base/Cargo.toml | 10 +-- crates/nu-cmd-extra/Cargo.toml | 22 +++--- crates/nu-cmd-lang/Cargo.toml | 10 +-- crates/nu-cmd-plugin/Cargo.toml | 10 +-- crates/nu-color-config/Cargo.toml | 10 +-- crates/nu-command/Cargo.toml | 36 ++++----- crates/nu-derive-value/Cargo.toml | 2 +- crates/nu-engine/Cargo.toml | 12 +-- crates/nu-explore/Cargo.toml | 18 ++--- crates/nu-glob/Cargo.toml | 2 +- crates/nu-json/Cargo.toml | 8 +- crates/nu-lsp/Cargo.toml | 14 ++-- crates/nu-parser/Cargo.toml | 10 +-- crates/nu-path/Cargo.toml | 4 +- crates/nu-plugin-core/Cargo.toml | 6 +- crates/nu-plugin-engine/Cargo.toml | 16 ++-- crates/nu-plugin-protocol/Cargo.toml | 6 +- crates/nu-plugin-test-support/Cargo.toml | 18 ++--- crates/nu-plugin/Cargo.toml | 14 ++-- crates/nu-pretty-hex/Cargo.toml | 2 +- crates/nu-protocol/Cargo.toml | 14 ++-- crates/nu-std/Cargo.toml | 8 +- crates/nu-system/Cargo.toml | 2 +- crates/nu-table/Cargo.toml | 12 +-- crates/nu-term-grid/Cargo.toml | 4 +- crates/nu-test-support/Cargo.toml | 8 +- crates/nu-utils/Cargo.toml | 2 +- .../src/sample_config/default_config.nu | 4 +- .../nu-utils/src/sample_config/default_env.nu | 2 +- crates/nu_plugin_custom_values/Cargo.toml | 6 +- crates/nu_plugin_example/Cargo.toml | 10 +-- crates/nu_plugin_formats/Cargo.toml | 8 +- crates/nu_plugin_gstat/Cargo.toml | 6 +- crates/nu_plugin_inc/Cargo.toml | 6 +- .../nu_plugin_nu_example.nu | 2 +- crates/nu_plugin_polars/Cargo.toml | 22 +++--- .../nu_plugin_python_example.py | 4 +- crates/nu_plugin_query/Cargo.toml | 8 +- crates/nu_plugin_stress_internals/Cargo.toml | 2 +- crates/nuon/Cargo.toml | 8 +- 43 files changed, 254 insertions(+), 254 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ee3faef06d..6390b32ee3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2868,7 +2868,7 @@ dependencies = [ [[package]] name = "nu" -version = "0.95.1" +version = "0.96.0" dependencies = [ "assert_cmd", "crossterm", @@ -2922,7 +2922,7 @@ dependencies = [ [[package]] name = "nu-cli" -version = "0.95.1" +version = "0.96.0" dependencies = [ "chrono", "crossterm", @@ -2957,7 +2957,7 @@ dependencies = [ [[package]] name = "nu-cmd-base" -version = "0.95.1" +version = "0.96.0" dependencies = [ "indexmap", "miette", @@ -2969,7 +2969,7 @@ dependencies = [ [[package]] name = "nu-cmd-extra" -version = "0.95.1" +version = "0.96.0" dependencies = [ "fancy-regex", "heck 0.5.0", @@ -2994,7 +2994,7 @@ dependencies = [ [[package]] name = "nu-cmd-lang" -version = "0.95.1" +version = "0.96.0" dependencies = [ "itertools 0.12.1", "nu-engine", @@ -3006,7 +3006,7 @@ dependencies = [ [[package]] name = "nu-cmd-plugin" -version = "0.95.1" +version = "0.96.0" dependencies = [ "itertools 0.12.1", "nu-engine", @@ -3017,7 +3017,7 @@ dependencies = [ [[package]] name = "nu-color-config" -version = "0.95.1" +version = "0.96.0" dependencies = [ "nu-ansi-term", "nu-engine", @@ -3029,7 +3029,7 @@ dependencies = [ [[package]] name = "nu-command" -version = "0.95.1" +version = "0.96.0" dependencies = [ "alphanumeric-sort", "base64 0.22.1", @@ -3139,7 +3139,7 @@ dependencies = [ [[package]] name = "nu-derive-value" -version = "0.95.1" +version = "0.96.0" dependencies = [ "convert_case", "proc-macro-error", @@ -3150,7 +3150,7 @@ dependencies = [ [[package]] name = "nu-engine" -version = "0.95.1" +version = "0.96.0" dependencies = [ "log", "nu-glob", @@ -3161,7 +3161,7 @@ dependencies = [ [[package]] name = "nu-explore" -version = "0.95.1" +version = "0.96.0" dependencies = [ "ansi-str", "anyhow", @@ -3186,14 +3186,14 @@ dependencies = [ [[package]] name = "nu-glob" -version = "0.95.1" +version = "0.96.0" dependencies = [ "doc-comment", ] [[package]] name = "nu-json" -version = "0.95.1" +version = "0.96.0" dependencies = [ "fancy-regex", "linked-hash-map", @@ -3206,7 +3206,7 @@ dependencies = [ [[package]] name = "nu-lsp" -version = "0.95.1" +version = "0.96.0" dependencies = [ "assert-json-diff", "crossbeam-channel", @@ -3227,7 +3227,7 @@ dependencies = [ [[package]] name = "nu-parser" -version = "0.95.1" +version = "0.96.0" dependencies = [ "bytesize", "chrono", @@ -3243,7 +3243,7 @@ dependencies = [ [[package]] name = "nu-path" -version = "0.95.1" +version = "0.96.0" dependencies = [ "dirs", "omnipath", @@ -3252,7 +3252,7 @@ dependencies = [ [[package]] name = "nu-plugin" -version = "0.95.1" +version = "0.96.0" dependencies = [ "log", "nix", @@ -3268,7 +3268,7 @@ dependencies = [ [[package]] name = "nu-plugin-core" -version = "0.95.1" +version = "0.96.0" dependencies = [ "interprocess", "log", @@ -3282,7 +3282,7 @@ dependencies = [ [[package]] name = "nu-plugin-engine" -version = "0.95.1" +version = "0.96.0" dependencies = [ "log", "nu-engine", @@ -3298,7 +3298,7 @@ dependencies = [ [[package]] name = "nu-plugin-protocol" -version = "0.95.1" +version = "0.96.0" dependencies = [ "bincode", "nu-protocol", @@ -3310,7 +3310,7 @@ dependencies = [ [[package]] name = "nu-plugin-test-support" -version = "0.95.1" +version = "0.96.0" dependencies = [ "nu-ansi-term", "nu-cmd-lang", @@ -3328,7 +3328,7 @@ dependencies = [ [[package]] name = "nu-pretty-hex" -version = "0.95.1" +version = "0.96.0" dependencies = [ "heapless", "nu-ansi-term", @@ -3337,7 +3337,7 @@ dependencies = [ [[package]] name = "nu-protocol" -version = "0.95.1" +version = "0.96.0" dependencies = [ "brotli", "byte-unit", @@ -3374,7 +3374,7 @@ dependencies = [ [[package]] name = "nu-std" -version = "0.95.1" +version = "0.96.0" dependencies = [ "log", "miette", @@ -3385,7 +3385,7 @@ dependencies = [ [[package]] name = "nu-system" -version = "0.95.1" +version = "0.96.0" dependencies = [ "chrono", "itertools 0.12.1", @@ -3403,7 +3403,7 @@ dependencies = [ [[package]] name = "nu-table" -version = "0.95.1" +version = "0.96.0" dependencies = [ "fancy-regex", "nu-ansi-term", @@ -3417,7 +3417,7 @@ dependencies = [ [[package]] name = "nu-term-grid" -version = "0.95.1" +version = "0.96.0" dependencies = [ "nu-utils", "unicode-width", @@ -3425,7 +3425,7 @@ dependencies = [ [[package]] name = "nu-test-support" -version = "0.95.1" +version = "0.96.0" dependencies = [ "nu-glob", "nu-path", @@ -3437,7 +3437,7 @@ dependencies = [ [[package]] name = "nu-utils" -version = "0.95.1" +version = "0.96.0" dependencies = [ "crossterm_winapi", "log", @@ -3463,7 +3463,7 @@ dependencies = [ [[package]] name = "nu_plugin_example" -version = "0.95.1" +version = "0.96.0" dependencies = [ "nu-cmd-lang", "nu-plugin", @@ -3473,7 +3473,7 @@ dependencies = [ [[package]] name = "nu_plugin_formats" -version = "0.95.1" +version = "0.96.0" dependencies = [ "eml-parser", "ical", @@ -3486,7 +3486,7 @@ dependencies = [ [[package]] name = "nu_plugin_gstat" -version = "0.95.1" +version = "0.96.0" dependencies = [ "git2", "nu-plugin", @@ -3495,7 +3495,7 @@ dependencies = [ [[package]] name = "nu_plugin_inc" -version = "0.95.1" +version = "0.96.0" dependencies = [ "nu-plugin", "nu-protocol", @@ -3504,7 +3504,7 @@ dependencies = [ [[package]] name = "nu_plugin_polars" -version = "0.95.1" +version = "0.96.0" dependencies = [ "chrono", "chrono-tz 0.9.0", @@ -3538,7 +3538,7 @@ dependencies = [ [[package]] name = "nu_plugin_query" -version = "0.95.1" +version = "0.96.0" dependencies = [ "gjson", "nu-plugin", @@ -3553,7 +3553,7 @@ dependencies = [ [[package]] name = "nu_plugin_stress_internals" -version = "0.95.1" +version = "0.96.0" dependencies = [ "interprocess", "serde", @@ -3679,7 +3679,7 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] name = "nuon" -version = "0.95.1" +version = "0.96.0" dependencies = [ "chrono", "fancy-regex", diff --git a/Cargo.toml b/Cargo.toml index 32274816f1..8397c2ca46 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT" name = "nu" repository = "https://github.com/nushell/nushell" rust-version = "1.77.2" -version = "0.95.1" +version = "0.96.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -183,22 +183,22 @@ windows-sys = "0.48" winreg = "0.52" [dependencies] -nu-cli = { path = "./crates/nu-cli", version = "0.95.1" } -nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.95.1" } -nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.95.1" } -nu-cmd-plugin = { path = "./crates/nu-cmd-plugin", version = "0.95.1", optional = true } -nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.95.1" } -nu-command = { path = "./crates/nu-command", version = "0.95.1" } -nu-engine = { path = "./crates/nu-engine", version = "0.95.1" } -nu-explore = { path = "./crates/nu-explore", version = "0.95.1" } -nu-lsp = { path = "./crates/nu-lsp/", version = "0.95.1" } -nu-parser = { path = "./crates/nu-parser", version = "0.95.1" } -nu-path = { path = "./crates/nu-path", version = "0.95.1" } -nu-plugin-engine = { path = "./crates/nu-plugin-engine", optional = true, version = "0.95.1" } -nu-protocol = { path = "./crates/nu-protocol", version = "0.95.1" } -nu-std = { path = "./crates/nu-std", version = "0.95.1" } -nu-system = { path = "./crates/nu-system", version = "0.95.1" } -nu-utils = { path = "./crates/nu-utils", version = "0.95.1" } +nu-cli = { path = "./crates/nu-cli", version = "0.96.0" } +nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.96.0" } +nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.96.0" } +nu-cmd-plugin = { path = "./crates/nu-cmd-plugin", version = "0.96.0", optional = true } +nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.96.0" } +nu-command = { path = "./crates/nu-command", version = "0.96.0" } +nu-engine = { path = "./crates/nu-engine", version = "0.96.0" } +nu-explore = { path = "./crates/nu-explore", version = "0.96.0" } +nu-lsp = { path = "./crates/nu-lsp/", version = "0.96.0" } +nu-parser = { path = "./crates/nu-parser", version = "0.96.0" } +nu-path = { path = "./crates/nu-path", version = "0.96.0" } +nu-plugin-engine = { path = "./crates/nu-plugin-engine", optional = true, version = "0.96.0" } +nu-protocol = { path = "./crates/nu-protocol", version = "0.96.0" } +nu-std = { path = "./crates/nu-std", version = "0.96.0" } +nu-system = { path = "./crates/nu-system", version = "0.96.0" } +nu-utils = { path = "./crates/nu-utils", version = "0.96.0" } reedline = { workspace = true, features = ["bashisms", "sqlite"] } crossterm = { workspace = true } @@ -227,9 +227,9 @@ nix = { workspace = true, default-features = false, features = [ ] } [dev-dependencies] -nu-test-support = { path = "./crates/nu-test-support", version = "0.95.1" } -nu-plugin-protocol = { path = "./crates/nu-plugin-protocol", version = "0.95.1" } -nu-plugin-core = { path = "./crates/nu-plugin-core", version = "0.95.1" } +nu-test-support = { path = "./crates/nu-test-support", version = "0.96.0" } +nu-plugin-protocol = { path = "./crates/nu-plugin-protocol", version = "0.96.0" } +nu-plugin-core = { path = "./crates/nu-plugin-core", version = "0.96.0" } assert_cmd = "2.0" dirs = { workspace = true } tango-bench = "0.5" @@ -313,4 +313,4 @@ bench = false # Run individual benchmarks like `cargo bench -- ` e.g. `cargo bench -- parse` [[bench]] name = "benchmarks" -harness = false +harness = false \ No newline at end of file diff --git a/crates/nu-cli/Cargo.toml b/crates/nu-cli/Cargo.toml index 2ed504d502..6baafda789 100644 --- a/crates/nu-cli/Cargo.toml +++ b/crates/nu-cli/Cargo.toml @@ -5,27 +5,27 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cli" edition = "2021" license = "MIT" name = "nu-cli" -version = "0.95.1" +version = "0.96.0" [lib] bench = false [dev-dependencies] -nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.95.1" } -nu-command = { path = "../nu-command", version = "0.95.1" } -nu-test-support = { path = "../nu-test-support", version = "0.95.1" } +nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.96.0" } +nu-command = { path = "../nu-command", version = "0.96.0" } +nu-test-support = { path = "../nu-test-support", version = "0.96.0" } rstest = { workspace = true, default-features = false } tempfile = { workspace = true } [dependencies] -nu-cmd-base = { path = "../nu-cmd-base", version = "0.95.1" } -nu-engine = { path = "../nu-engine", version = "0.95.1" } -nu-path = { path = "../nu-path", version = "0.95.1" } -nu-parser = { path = "../nu-parser", version = "0.95.1" } -nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.95.1", optional = true } -nu-protocol = { path = "../nu-protocol", version = "0.95.1" } -nu-utils = { path = "../nu-utils", version = "0.95.1" } -nu-color-config = { path = "../nu-color-config", version = "0.95.1" } +nu-cmd-base = { path = "../nu-cmd-base", version = "0.96.0" } +nu-engine = { path = "../nu-engine", version = "0.96.0" } +nu-path = { path = "../nu-path", version = "0.96.0" } +nu-parser = { path = "../nu-parser", version = "0.96.0" } +nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.96.0", optional = true } +nu-protocol = { path = "../nu-protocol", version = "0.96.0" } +nu-utils = { path = "../nu-utils", version = "0.96.0" } +nu-color-config = { path = "../nu-color-config", version = "0.96.0" } nu-ansi-term = { workspace = true } reedline = { workspace = true, features = ["bashisms", "sqlite"] } diff --git a/crates/nu-cmd-base/Cargo.toml b/crates/nu-cmd-base/Cargo.toml index 2fe8610f49..031636adfc 100644 --- a/crates/nu-cmd-base/Cargo.toml +++ b/crates/nu-cmd-base/Cargo.toml @@ -5,15 +5,15 @@ edition = "2021" license = "MIT" name = "nu-cmd-base" repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-base" -version = "0.95.1" +version = "0.96.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -nu-engine = { path = "../nu-engine", version = "0.95.1" } -nu-parser = { path = "../nu-parser", version = "0.95.1" } -nu-path = { path = "../nu-path", version = "0.95.1" } -nu-protocol = { path = "../nu-protocol", version = "0.95.1" } +nu-engine = { path = "../nu-engine", version = "0.96.0" } +nu-parser = { path = "../nu-parser", version = "0.96.0" } +nu-path = { path = "../nu-path", version = "0.96.0" } +nu-protocol = { path = "../nu-protocol", version = "0.96.0" } indexmap = { workspace = true } miette = { workspace = true } diff --git a/crates/nu-cmd-extra/Cargo.toml b/crates/nu-cmd-extra/Cargo.toml index 8609adb44c..2b26a962de 100644 --- a/crates/nu-cmd-extra/Cargo.toml +++ b/crates/nu-cmd-extra/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" license = "MIT" name = "nu-cmd-extra" repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-extra" -version = "0.95.1" +version = "0.96.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -13,13 +13,13 @@ version = "0.95.1" bench = false [dependencies] -nu-cmd-base = { path = "../nu-cmd-base", version = "0.95.1" } -nu-engine = { path = "../nu-engine", version = "0.95.1" } -nu-json = { version = "0.95.1", path = "../nu-json" } -nu-parser = { path = "../nu-parser", version = "0.95.1" } -nu-pretty-hex = { version = "0.95.1", path = "../nu-pretty-hex" } -nu-protocol = { path = "../nu-protocol", version = "0.95.1" } -nu-utils = { path = "../nu-utils", version = "0.95.1" } +nu-cmd-base = { path = "../nu-cmd-base", version = "0.96.0" } +nu-engine = { path = "../nu-engine", version = "0.96.0" } +nu-json = { version = "0.96.0", path = "../nu-json" } +nu-parser = { path = "../nu-parser", version = "0.96.0" } +nu-pretty-hex = { version = "0.96.0", path = "../nu-pretty-hex" } +nu-protocol = { path = "../nu-protocol", version = "0.96.0" } +nu-utils = { path = "../nu-utils", version = "0.96.0" } # Potential dependencies for extras heck = { workspace = true } @@ -33,6 +33,6 @@ v_htmlescape = { workspace = true } itertools = { workspace = true } [dev-dependencies] -nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.95.1" } -nu-command = { path = "../nu-command", version = "0.95.1" } -nu-test-support = { path = "../nu-test-support", version = "0.95.1" } \ No newline at end of file +nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.96.0" } +nu-command = { path = "../nu-command", version = "0.96.0" } +nu-test-support = { path = "../nu-test-support", version = "0.96.0" } \ No newline at end of file diff --git a/crates/nu-cmd-lang/Cargo.toml b/crates/nu-cmd-lang/Cargo.toml index 16ac1b893a..5a9d552cba 100644 --- a/crates/nu-cmd-lang/Cargo.toml +++ b/crates/nu-cmd-lang/Cargo.toml @@ -6,16 +6,16 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-lang" edition = "2021" license = "MIT" name = "nu-cmd-lang" -version = "0.95.1" +version = "0.96.0" [lib] bench = false [dependencies] -nu-engine = { path = "../nu-engine", version = "0.95.1" } -nu-parser = { path = "../nu-parser", version = "0.95.1" } -nu-protocol = { path = "../nu-protocol", version = "0.95.1" } -nu-utils = { path = "../nu-utils", version = "0.95.1" } +nu-engine = { path = "../nu-engine", version = "0.96.0" } +nu-parser = { path = "../nu-parser", version = "0.96.0" } +nu-protocol = { path = "../nu-protocol", version = "0.96.0" } +nu-utils = { path = "../nu-utils", version = "0.96.0" } itertools = { workspace = true } shadow-rs = { version = "0.29", default-features = false } diff --git a/crates/nu-cmd-plugin/Cargo.toml b/crates/nu-cmd-plugin/Cargo.toml index 7d26fe5ddd..a73d4eef89 100644 --- a/crates/nu-cmd-plugin/Cargo.toml +++ b/crates/nu-cmd-plugin/Cargo.toml @@ -5,15 +5,15 @@ edition = "2021" license = "MIT" name = "nu-cmd-plugin" repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-plugin" -version = "0.95.1" +version = "0.96.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -nu-engine = { path = "../nu-engine", version = "0.95.1" } -nu-path = { path = "../nu-path", version = "0.95.1" } -nu-protocol = { path = "../nu-protocol", version = "0.95.1", features = ["plugin"] } -nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.95.1" } +nu-engine = { path = "../nu-engine", version = "0.96.0" } +nu-path = { path = "../nu-path", version = "0.96.0" } +nu-protocol = { path = "../nu-protocol", version = "0.96.0", features = ["plugin"] } +nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.96.0" } itertools = { workspace = true } diff --git a/crates/nu-color-config/Cargo.toml b/crates/nu-color-config/Cargo.toml index 23af09432c..f3e977f3ab 100644 --- a/crates/nu-color-config/Cargo.toml +++ b/crates/nu-color-config/Cargo.toml @@ -5,18 +5,18 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-color-confi edition = "2021" license = "MIT" name = "nu-color-config" -version = "0.95.1" +version = "0.96.0" [lib] bench = false [dependencies] -nu-protocol = { path = "../nu-protocol", version = "0.95.1" } -nu-engine = { path = "../nu-engine", version = "0.95.1" } -nu-json = { path = "../nu-json", version = "0.95.1" } +nu-protocol = { path = "../nu-protocol", version = "0.96.0" } +nu-engine = { path = "../nu-engine", version = "0.96.0" } +nu-json = { path = "../nu-json", version = "0.96.0" } nu-ansi-term = { workspace = true } serde = { workspace = true, features = ["derive"] } [dev-dependencies] -nu-test-support = { path = "../nu-test-support", version = "0.95.1" } \ No newline at end of file +nu-test-support = { path = "../nu-test-support", version = "0.96.0" } \ No newline at end of file diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index ea501578b6..f3805dac5a 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" license = "MIT" name = "nu-command" repository = "https://github.com/nushell/nushell/tree/main/crates/nu-command" -version = "0.95.1" +version = "0.96.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -13,21 +13,21 @@ version = "0.95.1" bench = false [dependencies] -nu-cmd-base = { path = "../nu-cmd-base", version = "0.95.1" } -nu-color-config = { path = "../nu-color-config", version = "0.95.1" } -nu-engine = { path = "../nu-engine", version = "0.95.1" } -nu-glob = { path = "../nu-glob", version = "0.95.1" } -nu-json = { path = "../nu-json", version = "0.95.1" } -nu-parser = { path = "../nu-parser", version = "0.95.1" } -nu-path = { path = "../nu-path", version = "0.95.1" } -nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.95.1" } -nu-protocol = { path = "../nu-protocol", version = "0.95.1" } -nu-system = { path = "../nu-system", version = "0.95.1" } -nu-table = { path = "../nu-table", version = "0.95.1" } -nu-term-grid = { path = "../nu-term-grid", version = "0.95.1" } -nu-utils = { path = "../nu-utils", version = "0.95.1" } +nu-cmd-base = { path = "../nu-cmd-base", version = "0.96.0" } +nu-color-config = { path = "../nu-color-config", version = "0.96.0" } +nu-engine = { path = "../nu-engine", version = "0.96.0" } +nu-glob = { path = "../nu-glob", version = "0.96.0" } +nu-json = { path = "../nu-json", version = "0.96.0" } +nu-parser = { path = "../nu-parser", version = "0.96.0" } +nu-path = { path = "../nu-path", version = "0.96.0" } +nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.96.0" } +nu-protocol = { path = "../nu-protocol", version = "0.96.0" } +nu-system = { path = "../nu-system", version = "0.96.0" } +nu-table = { path = "../nu-table", version = "0.96.0" } +nu-term-grid = { path = "../nu-term-grid", version = "0.96.0" } +nu-utils = { path = "../nu-utils", version = "0.96.0" } nu-ansi-term = { workspace = true } -nuon = { path = "../nuon", version = "0.95.1" } +nuon = { path = "../nuon", version = "0.96.0" } alphanumeric-sort = { workspace = true } base64 = { workspace = true } @@ -137,8 +137,8 @@ sqlite = ["rusqlite"] trash-support = ["trash"] [dev-dependencies] -nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.95.1" } -nu-test-support = { path = "../nu-test-support", version = "0.95.1" } +nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.96.0" } +nu-test-support = { path = "../nu-test-support", version = "0.96.0" } dirs = { workspace = true } mockito = { workspace = true, default-features = false } @@ -146,4 +146,4 @@ quickcheck = { workspace = true } quickcheck_macros = { workspace = true } rstest = { workspace = true, default-features = false } pretty_assertions = { workspace = true } -tempfile = { workspace = true } +tempfile = { workspace = true } \ No newline at end of file diff --git a/crates/nu-derive-value/Cargo.toml b/crates/nu-derive-value/Cargo.toml index 50ca5d2407..3ce0f00ed5 100644 --- a/crates/nu-derive-value/Cargo.toml +++ b/crates/nu-derive-value/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" license = "MIT" name = "nu-derive-value" repository = "https://github.com/nushell/nushell/tree/main/crates/nu-derive-value" -version = "0.95.1" +version = "0.96.0" [lib] proc-macro = true diff --git a/crates/nu-engine/Cargo.toml b/crates/nu-engine/Cargo.toml index c416a2f590..89ff80cf80 100644 --- a/crates/nu-engine/Cargo.toml +++ b/crates/nu-engine/Cargo.toml @@ -5,17 +5,17 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-engine" edition = "2021" license = "MIT" name = "nu-engine" -version = "0.95.1" +version = "0.96.0" [lib] bench = false [dependencies] -nu-protocol = { path = "../nu-protocol", features = ["plugin"], version = "0.95.1" } -nu-path = { path = "../nu-path", version = "0.95.1" } -nu-glob = { path = "../nu-glob", version = "0.95.1" } -nu-utils = { path = "../nu-utils", version = "0.95.1" } +nu-protocol = { path = "../nu-protocol", features = ["plugin"], version = "0.96.0" } +nu-path = { path = "../nu-path", version = "0.96.0" } +nu-glob = { path = "../nu-glob", version = "0.96.0" } +nu-utils = { path = "../nu-utils", version = "0.96.0" } log = { workspace = true } [features] -plugin = [] +plugin = [] \ No newline at end of file diff --git a/crates/nu-explore/Cargo.toml b/crates/nu-explore/Cargo.toml index 6d0816ee3e..df728365a7 100644 --- a/crates/nu-explore/Cargo.toml +++ b/crates/nu-explore/Cargo.toml @@ -5,21 +5,21 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-explore" edition = "2021" license = "MIT" name = "nu-explore" -version = "0.95.1" +version = "0.96.0" [lib] bench = false [dependencies] -nu-protocol = { path = "../nu-protocol", version = "0.95.1" } -nu-parser = { path = "../nu-parser", version = "0.95.1" } -nu-color-config = { path = "../nu-color-config", version = "0.95.1" } -nu-engine = { path = "../nu-engine", version = "0.95.1" } -nu-table = { path = "../nu-table", version = "0.95.1" } -nu-json = { path = "../nu-json", version = "0.95.1" } -nu-utils = { path = "../nu-utils", version = "0.95.1" } +nu-protocol = { path = "../nu-protocol", version = "0.96.0" } +nu-parser = { path = "../nu-parser", version = "0.96.0" } +nu-color-config = { path = "../nu-color-config", version = "0.96.0" } +nu-engine = { path = "../nu-engine", version = "0.96.0" } +nu-table = { path = "../nu-table", version = "0.96.0" } +nu-json = { path = "../nu-json", version = "0.96.0" } +nu-utils = { path = "../nu-utils", version = "0.96.0" } nu-ansi-term = { workspace = true } -nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.95.1" } +nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.96.0" } anyhow = { workspace = true } log = { workspace = true } diff --git a/crates/nu-glob/Cargo.toml b/crates/nu-glob/Cargo.toml index 932958910c..242affbdcc 100644 --- a/crates/nu-glob/Cargo.toml +++ b/crates/nu-glob/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nu-glob" -version = "0.95.1" +version = "0.96.0" authors = ["The Nushell Project Developers", "The Rust Project Developers"] license = "MIT/Apache-2.0" description = """ diff --git a/crates/nu-json/Cargo.toml b/crates/nu-json/Cargo.toml index a3b61042b1..bd73655ae5 100644 --- a/crates/nu-json/Cargo.toml +++ b/crates/nu-json/Cargo.toml @@ -8,7 +8,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-json" edition = "2021" license = "MIT" name = "nu-json" -version = "0.95.1" +version = "0.96.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -26,7 +26,7 @@ serde = { workspace = true } serde_json = { workspace = true } [dev-dependencies] -nu-test-support = { path = "../nu-test-support", version = "0.95.1" } -nu-path = { path = "../nu-path", version = "0.95.1" } +nu-test-support = { path = "../nu-test-support", version = "0.96.0" } +nu-path = { path = "../nu-path", version = "0.96.0" } serde_json = "1.0" -fancy-regex = "0.13.0" +fancy-regex = "0.13.0" \ No newline at end of file diff --git a/crates/nu-lsp/Cargo.toml b/crates/nu-lsp/Cargo.toml index 0f9ef01e69..4ebe937960 100644 --- a/crates/nu-lsp/Cargo.toml +++ b/crates/nu-lsp/Cargo.toml @@ -3,14 +3,14 @@ authors = ["The Nushell Project Developers"] description = "Nushell's integrated LSP server" repository = "https://github.com/nushell/nushell/tree/main/crates/nu-lsp" name = "nu-lsp" -version = "0.95.1" +version = "0.96.0" edition = "2021" license = "MIT" [dependencies] -nu-cli = { path = "../nu-cli", version = "0.95.1" } -nu-parser = { path = "../nu-parser", version = "0.95.1" } -nu-protocol = { path = "../nu-protocol", version = "0.95.1" } +nu-cli = { path = "../nu-cli", version = "0.96.0" } +nu-parser = { path = "../nu-parser", version = "0.96.0" } +nu-protocol = { path = "../nu-protocol", version = "0.96.0" } reedline = { workspace = true } @@ -23,8 +23,8 @@ serde = { workspace = true } serde_json = { workspace = true } [dev-dependencies] -nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.95.1" } -nu-command = { path = "../nu-command", version = "0.95.1" } -nu-test-support = { path = "../nu-test-support", version = "0.95.1" } +nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.96.0" } +nu-command = { path = "../nu-command", version = "0.96.0" } +nu-test-support = { path = "../nu-test-support", version = "0.96.0" } assert-json-diff = "2.0" \ No newline at end of file diff --git a/crates/nu-parser/Cargo.toml b/crates/nu-parser/Cargo.toml index b06e81387d..d8f978620d 100644 --- a/crates/nu-parser/Cargo.toml +++ b/crates/nu-parser/Cargo.toml @@ -5,17 +5,17 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-parser" edition = "2021" license = "MIT" name = "nu-parser" -version = "0.95.1" +version = "0.96.0" exclude = ["/fuzz"] [lib] bench = false [dependencies] -nu-engine = { path = "../nu-engine", version = "0.95.1" } -nu-path = { path = "../nu-path", version = "0.95.1" } -nu-plugin-engine = { path = "../nu-plugin-engine", optional = true, version = "0.95.1" } -nu-protocol = { path = "../nu-protocol", version = "0.95.1" } +nu-engine = { path = "../nu-engine", version = "0.96.0" } +nu-path = { path = "../nu-path", version = "0.96.0" } +nu-plugin-engine = { path = "../nu-plugin-engine", optional = true, version = "0.96.0" } +nu-protocol = { path = "../nu-protocol", version = "0.96.0" } bytesize = { workspace = true } chrono = { default-features = false, features = ['std'], workspace = true } diff --git a/crates/nu-path/Cargo.toml b/crates/nu-path/Cargo.toml index 62908e7cb7..7bddfd309f 100644 --- a/crates/nu-path/Cargo.toml +++ b/crates/nu-path/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-path" edition = "2021" license = "MIT" name = "nu-path" -version = "0.95.1" +version = "0.96.0" exclude = ["/fuzz"] [lib] @@ -18,4 +18,4 @@ dirs = { workspace = true } omnipath = { workspace = true } [target.'cfg(all(unix, not(target_os = "macos"), not(target_os = "android")))'.dependencies] -pwd = { workspace = true } +pwd = { workspace = true } \ No newline at end of file diff --git a/crates/nu-plugin-core/Cargo.toml b/crates/nu-plugin-core/Cargo.toml index 48aee13276..3b9e8b63ca 100644 --- a/crates/nu-plugin-core/Cargo.toml +++ b/crates/nu-plugin-core/Cargo.toml @@ -5,14 +5,14 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-plugin-core edition = "2021" license = "MIT" name = "nu-plugin-core" -version = "0.95.1" +version = "0.96.0" [lib] bench = false [dependencies] -nu-protocol = { path = "../nu-protocol", version = "0.95.1" } -nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.95.1", default-features = false } +nu-protocol = { path = "../nu-protocol", version = "0.96.0" } +nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.96.0", default-features = false } rmp-serde = { workspace = true } serde = { workspace = true } diff --git a/crates/nu-plugin-engine/Cargo.toml b/crates/nu-plugin-engine/Cargo.toml index a6dfa471b8..585e28de47 100644 --- a/crates/nu-plugin-engine/Cargo.toml +++ b/crates/nu-plugin-engine/Cargo.toml @@ -5,18 +5,18 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-plugin-engi edition = "2021" license = "MIT" name = "nu-plugin-engine" -version = "0.95.1" +version = "0.96.0" [lib] bench = false [dependencies] -nu-engine = { path = "../nu-engine", version = "0.95.1" } -nu-protocol = { path = "../nu-protocol", version = "0.95.1" } -nu-system = { path = "../nu-system", version = "0.95.1" } -nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.95.1" } -nu-plugin-core = { path = "../nu-plugin-core", version = "0.95.1", default-features = false } -nu-utils = { path = "../nu-utils", version = "0.95.1" } +nu-engine = { path = "../nu-engine", version = "0.96.0" } +nu-protocol = { path = "../nu-protocol", version = "0.96.0" } +nu-system = { path = "../nu-system", version = "0.96.0" } +nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.96.0" } +nu-plugin-core = { path = "../nu-plugin-core", version = "0.96.0", default-features = false } +nu-utils = { path = "../nu-utils", version = "0.96.0" } serde = { workspace = true } log = { workspace = true } @@ -32,4 +32,4 @@ local-socket = ["nu-plugin-core/local-socket"] windows = { workspace = true, features = [ # For setting process creation flags "Win32_System_Threading", -] } +] } \ No newline at end of file diff --git a/crates/nu-plugin-protocol/Cargo.toml b/crates/nu-plugin-protocol/Cargo.toml index 939f164786..feb6c51b64 100644 --- a/crates/nu-plugin-protocol/Cargo.toml +++ b/crates/nu-plugin-protocol/Cargo.toml @@ -5,14 +5,14 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-plugin-prot edition = "2021" license = "MIT" name = "nu-plugin-protocol" -version = "0.95.1" +version = "0.96.0" [lib] bench = false [dependencies] -nu-protocol = { path = "../nu-protocol", version = "0.95.1", features = ["plugin"] } -nu-utils = { path = "../nu-utils", version = "0.95.1" } +nu-protocol = { path = "../nu-protocol", version = "0.96.0", features = ["plugin"] } +nu-utils = { path = "../nu-utils", version = "0.96.0" } bincode = "1.3" serde = { workspace = true, features = ["derive"] } diff --git a/crates/nu-plugin-test-support/Cargo.toml b/crates/nu-plugin-test-support/Cargo.toml index efc76c3b93..e12fc28823 100644 --- a/crates/nu-plugin-test-support/Cargo.toml +++ b/crates/nu-plugin-test-support/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nu-plugin-test-support" -version = "0.95.1" +version = "0.96.0" edition = "2021" license = "MIT" description = "Testing support for Nushell plugins" @@ -12,14 +12,14 @@ bench = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -nu-engine = { path = "../nu-engine", version = "0.95.1", features = ["plugin"] } -nu-protocol = { path = "../nu-protocol", version = "0.95.1", features = ["plugin"] } -nu-parser = { path = "../nu-parser", version = "0.95.1", features = ["plugin"] } -nu-plugin = { path = "../nu-plugin", version = "0.95.1" } -nu-plugin-core = { path = "../nu-plugin-core", version = "0.95.1" } -nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.95.1" } -nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.95.1" } -nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.95.1" } +nu-engine = { path = "../nu-engine", version = "0.96.0", features = ["plugin"] } +nu-protocol = { path = "../nu-protocol", version = "0.96.0", features = ["plugin"] } +nu-parser = { path = "../nu-parser", version = "0.96.0", features = ["plugin"] } +nu-plugin = { path = "../nu-plugin", version = "0.96.0" } +nu-plugin-core = { path = "../nu-plugin-core", version = "0.96.0" } +nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.96.0" } +nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.96.0" } +nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.96.0" } nu-ansi-term = { workspace = true } similar = "2.5" diff --git a/crates/nu-plugin/Cargo.toml b/crates/nu-plugin/Cargo.toml index d46a3cc9cb..3b06f37fae 100644 --- a/crates/nu-plugin/Cargo.toml +++ b/crates/nu-plugin/Cargo.toml @@ -5,17 +5,17 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-plugin" edition = "2021" license = "MIT" name = "nu-plugin" -version = "0.95.1" +version = "0.96.0" [lib] bench = false [dependencies] -nu-engine = { path = "../nu-engine", version = "0.95.1" } -nu-protocol = { path = "../nu-protocol", version = "0.95.1" } -nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.95.1" } -nu-plugin-core = { path = "../nu-plugin-core", version = "0.95.1", default-features = false } -nu-utils = { path = "../nu-utils", version = "0.95.1" } +nu-engine = { path = "../nu-engine", version = "0.96.0" } +nu-protocol = { path = "../nu-protocol", version = "0.96.0" } +nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.96.0" } +nu-plugin-core = { path = "../nu-plugin-core", version = "0.96.0", default-features = false } +nu-utils = { path = "../nu-utils", version = "0.96.0" } log = { workspace = true } thiserror = "1.0" @@ -30,4 +30,4 @@ local-socket = ["nu-plugin-core/local-socket"] [target.'cfg(target_family = "unix")'.dependencies] # For setting the process group ID (EnterForeground / LeaveForeground) -nix = { workspace = true, default-features = false, features = ["process"] } +nix = { workspace = true, default-features = false, features = ["process"] } \ No newline at end of file diff --git a/crates/nu-pretty-hex/Cargo.toml b/crates/nu-pretty-hex/Cargo.toml index cd3c3502a4..e2f21d322b 100644 --- a/crates/nu-pretty-hex/Cargo.toml +++ b/crates/nu-pretty-hex/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-pretty-hex" edition = "2021" license = "MIT" name = "nu-pretty-hex" -version = "0.95.1" +version = "0.96.0" [lib] doctest = false diff --git a/crates/nu-protocol/Cargo.toml b/crates/nu-protocol/Cargo.toml index ea900bf08d..467c4a21f7 100644 --- a/crates/nu-protocol/Cargo.toml +++ b/crates/nu-protocol/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-protocol" edition = "2021" license = "MIT" name = "nu-protocol" -version = "0.95.1" +version = "0.96.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -13,10 +13,10 @@ version = "0.95.1" bench = false [dependencies] -nu-utils = { path = "../nu-utils", version = "0.95.1" } -nu-path = { path = "../nu-path", version = "0.95.1" } -nu-system = { path = "../nu-system", version = "0.95.1" } -nu-derive-value = { path = "../nu-derive-value", version = "0.95.1" } +nu-utils = { path = "../nu-utils", version = "0.96.0" } +nu-path = { path = "../nu-path", version = "0.96.0" } +nu-system = { path = "../nu-system", version = "0.96.0" } +nu-derive-value = { path = "../nu-derive-value", version = "0.96.0" } brotli = { workspace = true, optional = true } byte-unit = { version = "5.1", features = [ "serde" ] } @@ -53,11 +53,11 @@ plugin = [ serde_json = { workspace = true } strum = "0.26" strum_macros = "0.26" -nu-test-support = { path = "../nu-test-support", version = "0.95.1" } +nu-test-support = { path = "../nu-test-support", version = "0.96.0" } pretty_assertions = { workspace = true } rstest = { workspace = true } tempfile = { workspace = true } os_pipe = { workspace = true } [package.metadata.docs.rs] -all-features = true +all-features = true \ No newline at end of file diff --git a/crates/nu-std/Cargo.toml b/crates/nu-std/Cargo.toml index 9ee4113e96..408b1e0303 100644 --- a/crates/nu-std/Cargo.toml +++ b/crates/nu-std/Cargo.toml @@ -5,12 +5,12 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-std" edition = "2021" license = "MIT" name = "nu-std" -version = "0.95.1" +version = "0.96.0" [dependencies] -nu-parser = { version = "0.95.1", path = "../nu-parser" } -nu-protocol = { version = "0.95.1", path = "../nu-protocol" } -nu-engine = { version = "0.95.1", path = "../nu-engine" } +nu-parser = { version = "0.96.0", path = "../nu-parser" } +nu-protocol = { version = "0.96.0", path = "../nu-protocol" } +nu-engine = { version = "0.96.0", path = "../nu-engine" } miette = { workspace = true, features = ["fancy-no-backtrace"] } log = "0.4" \ No newline at end of file diff --git a/crates/nu-system/Cargo.toml b/crates/nu-system/Cargo.toml index 19ab1259fb..d4d6f77e9b 100644 --- a/crates/nu-system/Cargo.toml +++ b/crates/nu-system/Cargo.toml @@ -3,7 +3,7 @@ authors = ["The Nushell Project Developers", "procs creators"] description = "Nushell system querying" repository = "https://github.com/nushell/nushell/tree/main/crates/nu-system" name = "nu-system" -version = "0.95.1" +version = "0.96.0" edition = "2021" license = "MIT" diff --git a/crates/nu-table/Cargo.toml b/crates/nu-table/Cargo.toml index 9f00c4d1fd..982fd08369 100644 --- a/crates/nu-table/Cargo.toml +++ b/crates/nu-table/Cargo.toml @@ -5,20 +5,20 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-table" edition = "2021" license = "MIT" name = "nu-table" -version = "0.95.1" +version = "0.96.0" [lib] bench = false [dependencies] -nu-protocol = { path = "../nu-protocol", version = "0.95.1" } -nu-utils = { path = "../nu-utils", version = "0.95.1" } -nu-engine = { path = "../nu-engine", version = "0.95.1" } -nu-color-config = { path = "../nu-color-config", version = "0.95.1" } +nu-protocol = { path = "../nu-protocol", version = "0.96.0" } +nu-utils = { path = "../nu-utils", version = "0.96.0" } +nu-engine = { path = "../nu-engine", version = "0.96.0" } +nu-color-config = { path = "../nu-color-config", version = "0.96.0" } nu-ansi-term = { workspace = true } once_cell = { workspace = true } fancy-regex = { workspace = true } tabled = { workspace = true, features = ["color"], default-features = false } [dev-dependencies] -# nu-test-support = { path="../nu-test-support", version = "0.95.1" } \ No newline at end of file +# nu-test-support = { path="../nu-test-support", version = "0.96.0" } \ No newline at end of file diff --git a/crates/nu-term-grid/Cargo.toml b/crates/nu-term-grid/Cargo.toml index 0229f1f0f0..0be10b4ef1 100644 --- a/crates/nu-term-grid/Cargo.toml +++ b/crates/nu-term-grid/Cargo.toml @@ -5,12 +5,12 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-term-grid" edition = "2021" license = "MIT" name = "nu-term-grid" -version = "0.95.1" +version = "0.96.0" [lib] bench = false [dependencies] -nu-utils = { path = "../nu-utils", version = "0.95.1" } +nu-utils = { path = "../nu-utils", version = "0.96.0" } unicode-width = { workspace = true } \ No newline at end of file diff --git a/crates/nu-test-support/Cargo.toml b/crates/nu-test-support/Cargo.toml index 1b5f5df7ba..c41bf6b8a2 100644 --- a/crates/nu-test-support/Cargo.toml +++ b/crates/nu-test-support/Cargo.toml @@ -5,16 +5,16 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-test-suppor edition = "2021" license = "MIT" name = "nu-test-support" -version = "0.95.1" +version = "0.96.0" [lib] doctest = false bench = false [dependencies] -nu-path = { path = "../nu-path", version = "0.95.1" } -nu-glob = { path = "../nu-glob", version = "0.95.1" } -nu-utils = { path = "../nu-utils", version = "0.95.1" } +nu-path = { path = "../nu-path", version = "0.96.0" } +nu-glob = { path = "../nu-glob", version = "0.96.0" } +nu-utils = { path = "../nu-utils", version = "0.96.0" } num-format = { workspace = true } which = { workspace = true } diff --git a/crates/nu-utils/Cargo.toml b/crates/nu-utils/Cargo.toml index 60b4cb21aa..aa9f591a2d 100644 --- a/crates/nu-utils/Cargo.toml +++ b/crates/nu-utils/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" license = "MIT" name = "nu-utils" repository = "https://github.com/nushell/nushell/tree/main/crates/nu-utils" -version = "0.95.1" +version = "0.96.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [[bin]] diff --git a/crates/nu-utils/src/sample_config/default_config.nu b/crates/nu-utils/src/sample_config/default_config.nu index 3c3e57fa95..dda33cace0 100644 --- a/crates/nu-utils/src/sample_config/default_config.nu +++ b/crates/nu-utils/src/sample_config/default_config.nu @@ -1,6 +1,6 @@ # Nushell Config File # -# version = "0.95.1" +# version = "0.96.0" # For more information on defining custom themes, see # https://www.nushell.sh/book/coloring_and_theming.html @@ -889,4 +889,4 @@ $env.config = { event: { edit: selectall } } ] -} +} \ No newline at end of file diff --git a/crates/nu-utils/src/sample_config/default_env.nu b/crates/nu-utils/src/sample_config/default_env.nu index a686f6f99a..5d6cab2687 100644 --- a/crates/nu-utils/src/sample_config/default_env.nu +++ b/crates/nu-utils/src/sample_config/default_env.nu @@ -1,6 +1,6 @@ # Nushell Environment Config File # -# version = "0.95.1" +# version = "0.96.0" def create_left_prompt [] { let dir = match (do --ignore-shell-errors { $env.PWD | path relative-to $nu.home-path }) { diff --git a/crates/nu_plugin_custom_values/Cargo.toml b/crates/nu_plugin_custom_values/Cargo.toml index e5f8951429..c3f3a7cec1 100644 --- a/crates/nu_plugin_custom_values/Cargo.toml +++ b/crates/nu_plugin_custom_values/Cargo.toml @@ -10,10 +10,10 @@ name = "nu_plugin_custom_values" bench = false [dependencies] -nu-plugin = { path = "../nu-plugin", version = "0.95.1" } -nu-protocol = { path = "../nu-protocol", version = "0.95.1", features = ["plugin"] } +nu-plugin = { path = "../nu-plugin", version = "0.96.0" } +nu-protocol = { path = "../nu-protocol", version = "0.96.0", features = ["plugin"] } serde = { workspace = true, default-features = false } typetag = "0.2" [dev-dependencies] -nu-plugin-test-support = { path = "../nu-plugin-test-support", version = "0.95.1" } \ No newline at end of file +nu-plugin-test-support = { path = "../nu-plugin-test-support", version = "0.96.0" } \ No newline at end of file diff --git a/crates/nu_plugin_example/Cargo.toml b/crates/nu_plugin_example/Cargo.toml index 08ba3e1566..9df31e46b6 100644 --- a/crates/nu_plugin_example/Cargo.toml +++ b/crates/nu_plugin_example/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu_plugin_exam edition = "2021" license = "MIT" name = "nu_plugin_example" -version = "0.95.1" +version = "0.96.0" [[bin]] name = "nu_plugin_example" @@ -15,9 +15,9 @@ bench = false bench = false [dependencies] -nu-plugin = { path = "../nu-plugin", version = "0.95.1" } -nu-protocol = { path = "../nu-protocol", version = "0.95.1", features = ["plugin"] } +nu-plugin = { path = "../nu-plugin", version = "0.96.0" } +nu-protocol = { path = "../nu-protocol", version = "0.96.0", features = ["plugin"] } [dev-dependencies] -nu-plugin-test-support = { path = "../nu-plugin-test-support", version = "0.95.1" } -nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.95.1" } \ No newline at end of file +nu-plugin-test-support = { path = "../nu-plugin-test-support", version = "0.96.0" } +nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.96.0" } \ No newline at end of file diff --git a/crates/nu_plugin_formats/Cargo.toml b/crates/nu_plugin_formats/Cargo.toml index 29e8953f24..535cd69c17 100644 --- a/crates/nu_plugin_formats/Cargo.toml +++ b/crates/nu_plugin_formats/Cargo.toml @@ -5,12 +5,12 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu_plugin_form edition = "2021" license = "MIT" name = "nu_plugin_formats" -version = "0.95.1" +version = "0.96.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -nu-plugin = { path = "../nu-plugin", version = "0.95.1" } -nu-protocol = { path = "../nu-protocol", version = "0.95.1", features = ["plugin"] } +nu-plugin = { path = "../nu-plugin", version = "0.96.0" } +nu-protocol = { path = "../nu-protocol", version = "0.96.0", features = ["plugin"] } indexmap = { workspace = true } eml-parser = "0.1" @@ -18,4 +18,4 @@ ical = "0.11" rust-ini = "0.21.0" [dev-dependencies] -nu-plugin-test-support = { path = "../nu-plugin-test-support", version = "0.95.1" } \ No newline at end of file +nu-plugin-test-support = { path = "../nu-plugin-test-support", version = "0.96.0" } \ No newline at end of file diff --git a/crates/nu_plugin_gstat/Cargo.toml b/crates/nu_plugin_gstat/Cargo.toml index da1c13a88e..6a7d6411c8 100644 --- a/crates/nu_plugin_gstat/Cargo.toml +++ b/crates/nu_plugin_gstat/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu_plugin_gsta edition = "2021" license = "MIT" name = "nu_plugin_gstat" -version = "0.95.1" +version = "0.96.0" [lib] doctest = false @@ -16,7 +16,7 @@ name = "nu_plugin_gstat" bench = false [dependencies] -nu-plugin = { path = "../nu-plugin", version = "0.95.1" } -nu-protocol = { path = "../nu-protocol", version = "0.95.1" } +nu-plugin = { path = "../nu-plugin", version = "0.96.0" } +nu-protocol = { path = "../nu-protocol", version = "0.96.0" } git2 = "0.19" \ No newline at end of file diff --git a/crates/nu_plugin_inc/Cargo.toml b/crates/nu_plugin_inc/Cargo.toml index 0479a47843..4b3f63345b 100644 --- a/crates/nu_plugin_inc/Cargo.toml +++ b/crates/nu_plugin_inc/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu_plugin_inc" edition = "2021" license = "MIT" name = "nu_plugin_inc" -version = "0.95.1" +version = "0.96.0" [lib] doctest = false @@ -16,7 +16,7 @@ name = "nu_plugin_inc" bench = false [dependencies] -nu-plugin = { path = "../nu-plugin", version = "0.95.1" } -nu-protocol = { path = "../nu-protocol", version = "0.95.1", features = ["plugin"] } +nu-plugin = { path = "../nu-plugin", version = "0.96.0" } +nu-protocol = { path = "../nu-protocol", version = "0.96.0", features = ["plugin"] } semver = "1.0" \ No newline at end of file diff --git a/crates/nu_plugin_nu_example/nu_plugin_nu_example.nu b/crates/nu_plugin_nu_example/nu_plugin_nu_example.nu index d19cd36c14..75069e27e3 100755 --- a/crates/nu_plugin_nu_example/nu_plugin_nu_example.nu +++ b/crates/nu_plugin_nu_example/nu_plugin_nu_example.nu @@ -6,7 +6,7 @@ # it also allows us to test the plugin interface with something manually implemented in a scripting # language without adding any extra dependencies to our tests. -const NUSHELL_VERSION = "0.95.1" +const NUSHELL_VERSION = "0.96.0" const PLUGIN_VERSION = "0.1.0" # bump if you change commands! def main [--stdio] { diff --git a/crates/nu_plugin_polars/Cargo.toml b/crates/nu_plugin_polars/Cargo.toml index 99c88e47d6..40ce054ecd 100644 --- a/crates/nu_plugin_polars/Cargo.toml +++ b/crates/nu_plugin_polars/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" license = "MIT" name = "nu_plugin_polars" repository = "https://github.com/nushell/nushell/tree/main/crates/nu_plugin_polars" -version = "0.95.1" +version = "0.96.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -17,10 +17,10 @@ bench = false bench = false [dependencies] -nu-protocol = { path = "../nu-protocol", version = "0.95.1" } -nu-plugin = { path = "../nu-plugin", version = "0.95.1" } -nu-path = { path = "../nu-path", version = "0.95.1" } -nu-utils = { path = "../nu-utils", version = "0.95.1" } +nu-protocol = { path = "../nu-protocol", version = "0.96.0" } +nu-plugin = { path = "../nu-plugin", version = "0.96.0" } +nu-path = { path = "../nu-path", version = "0.96.0" } +nu-utils = { path = "../nu-utils", version = "0.96.0" } # Potential dependencies for extras chrono = { workspace = true, features = ["std", "unstable-locales"], default-features = false } @@ -76,9 +76,9 @@ optional = false version = "0.41" [dev-dependencies] -nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.95.1" } -nu-engine = { path = "../nu-engine", version = "0.95.1" } -nu-parser = { path = "../nu-parser", version = "0.95.1" } -nu-command = { path = "../nu-command", version = "0.95.1" } -nu-plugin-test-support = { path = "../nu-plugin-test-support", version = "0.95.1" } -tempfile.workspace = true +nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.96.0" } +nu-engine = { path = "../nu-engine", version = "0.96.0" } +nu-parser = { path = "../nu-parser", version = "0.96.0" } +nu-command = { path = "../nu-command", version = "0.96.0" } +nu-plugin-test-support = { path = "../nu-plugin-test-support", version = "0.96.0" } +tempfile.workspace = true \ No newline at end of file diff --git a/crates/nu_plugin_python/nu_plugin_python_example.py b/crates/nu_plugin_python/nu_plugin_python_example.py index 993e619366..ad128704f6 100755 --- a/crates/nu_plugin_python/nu_plugin_python_example.py +++ b/crates/nu_plugin_python/nu_plugin_python_example.py @@ -27,7 +27,7 @@ import sys import json -NUSHELL_VERSION = "0.95.1" +NUSHELL_VERSION = "0.96.0" PLUGIN_VERSION = "0.1.0" # bump if you change commands! @@ -258,4 +258,4 @@ if __name__ == "__main__": if len(sys.argv) == 2 and sys.argv[1] == "--stdio": plugin() else: - print("Run me from inside nushell!") + print("Run me from inside nushell!") \ No newline at end of file diff --git a/crates/nu_plugin_query/Cargo.toml b/crates/nu_plugin_query/Cargo.toml index 551610e390..29e99b28f8 100644 --- a/crates/nu_plugin_query/Cargo.toml +++ b/crates/nu_plugin_query/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu_plugin_quer edition = "2021" license = "MIT" name = "nu_plugin_query" -version = "0.95.1" +version = "0.96.0" [lib] doctest = false @@ -16,8 +16,8 @@ name = "nu_plugin_query" bench = false [dependencies] -nu-plugin = { path = "../nu-plugin", version = "0.95.1" } -nu-protocol = { path = "../nu-protocol", version = "0.95.1" } +nu-plugin = { path = "../nu-plugin", version = "0.96.0" } +nu-protocol = { path = "../nu-protocol", version = "0.96.0" } gjson = "0.8" scraper = { default-features = false, version = "0.19" } @@ -25,4 +25,4 @@ sxd-document = "0.3" sxd-xpath = "0.4" webpage = { version = "2.0.1", features = ["serde"] } serde_json.workspace = true -serde.workspace = true +serde.workspace = true \ No newline at end of file diff --git a/crates/nu_plugin_stress_internals/Cargo.toml b/crates/nu_plugin_stress_internals/Cargo.toml index 2a53eb6749..9be71d55ca 100644 --- a/crates/nu_plugin_stress_internals/Cargo.toml +++ b/crates/nu_plugin_stress_internals/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu_plugin_stre edition = "2021" license = "MIT" name = "nu_plugin_stress_internals" -version = "0.95.1" +version = "0.96.0" [[bin]] name = "nu_plugin_stress_internals" diff --git a/crates/nuon/Cargo.toml b/crates/nuon/Cargo.toml index bbb2dfe73c..33dd5e6b7d 100644 --- a/crates/nuon/Cargo.toml +++ b/crates/nuon/Cargo.toml @@ -5,14 +5,14 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nuon" edition = "2021" license = "MIT" name = "nuon" -version = "0.95.1" +version = "0.96.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -nu-parser = { path = "../nu-parser", version = "0.95.1" } -nu-protocol = { path = "../nu-protocol", version = "0.95.1" } -nu-engine = { path = "../nu-engine", version = "0.95.1" } +nu-parser = { path = "../nu-parser", version = "0.96.0" } +nu-protocol = { path = "../nu-protocol", version = "0.96.0" } +nu-engine = { path = "../nu-engine", version = "0.96.0" } once_cell = { workspace = true } fancy-regex = { workspace = true } From 5c2439abc07c845a6c15ff74e2bef07e600c30d1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 24 Jul 2024 13:11:50 +0800 Subject: [PATCH 072/126] Bump softprops/action-gh-release from 2.0.6 to 2.0.8 (#13438) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.0.6 to 2.0.8.
Release notes

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

v2.0.8

What's Changed

Other Changes 🔄

Full Changelog: https://github.com/softprops/action-gh-release/compare/v2...v2.0.8

v2.0.7

What's Changed

Bug fixes 🐛

Other Changes 🔄

New Contributors

Full Changelog: https://github.com/softprops/action-gh-release/compare/v2.0.6...v2.0.7

Changelog

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

2.0.8

Other Changes 🔄

2.0.7

Bug fixes 🐛

Other Changes 🔄

Commits
  • c062e08 release 2.0.8
  • 380635c chore(deps): bump @​actions/github from 5.1.1 to 6.0.0 (#470)
  • 20adb42 refactor: write jest config in ts (#485)
  • f808f15 chore(deps): bump glob from 10.4.2 to 11.0.0 (#477)
  • 6145241 chore(deps): bump @​octokit/plugin-throttling from 9.3.0 to 9.3.1 (#484)
  • 4ac522d chore(deps): bump @​types/node from 20.14.9 to 20.14.11 (#483)
  • 25849b1 chore(deps): bump prettier from 2.8.0 to 3.3.3 (#480)
  • 6206056 chore: update dependabot commit msg
  • 39aadf1 chore: run frizbee actions .github/workflows/
  • 6f3ab65 chore: update dist file
  • Additional commits viewable in compare view

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

Sourced from crate-ci/typos's releases.

v1.23.3

[1.23.3] - 2024-07-22

Fixes

  • Fix Dockerfile
Changelog

Sourced from crate-ci/typos's changelog.

[1.23.3] - 2024-07-22

Fixes

  • Fix Dockerfile
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=crate-ci/typos&package-manager=github_actions&previous-version=1.23.2&new-version=1.23.3)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .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 c7c153983d..2484787cb5 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.23.2 + uses: crate-ci/typos@v1.23.3 From 9f90d611e167d020d9e3b7767916cfda0e869317 Mon Sep 17 00:00:00 2001 From: Devyn Cairns Date: Thu, 25 Jul 2024 03:28:18 -0700 Subject: [PATCH 074/126] Bump version to `0.96.1` (#13439) (Post-release bump.) --- Cargo.lock | 74 +++++++++---------- Cargo.toml | 40 +++++----- crates/nu-cli/Cargo.toml | 24 +++--- crates/nu-cmd-base/Cargo.toml | 10 +-- crates/nu-cmd-extra/Cargo.toml | 22 +++--- crates/nu-cmd-lang/Cargo.toml | 10 +-- crates/nu-cmd-plugin/Cargo.toml | 10 +-- crates/nu-color-config/Cargo.toml | 10 +-- crates/nu-command/Cargo.toml | 34 ++++----- crates/nu-derive-value/Cargo.toml | 2 +- crates/nu-engine/Cargo.toml | 10 +-- crates/nu-explore/Cargo.toml | 18 ++--- crates/nu-glob/Cargo.toml | 2 +- crates/nu-json/Cargo.toml | 6 +- crates/nu-lsp/Cargo.toml | 14 ++-- crates/nu-parser/Cargo.toml | 10 +-- crates/nu-path/Cargo.toml | 2 +- crates/nu-plugin-core/Cargo.toml | 6 +- crates/nu-plugin-engine/Cargo.toml | 14 ++-- crates/nu-plugin-protocol/Cargo.toml | 6 +- crates/nu-plugin-test-support/Cargo.toml | 18 ++--- crates/nu-plugin/Cargo.toml | 12 +-- crates/nu-pretty-hex/Cargo.toml | 2 +- crates/nu-protocol/Cargo.toml | 12 +-- crates/nu-std/Cargo.toml | 8 +- crates/nu-system/Cargo.toml | 2 +- crates/nu-table/Cargo.toml | 12 +-- crates/nu-term-grid/Cargo.toml | 4 +- crates/nu-test-support/Cargo.toml | 8 +- crates/nu-utils/Cargo.toml | 2 +- .../src/sample_config/default_config.nu | 2 +- .../nu-utils/src/sample_config/default_env.nu | 2 +- crates/nu_plugin_custom_values/Cargo.toml | 6 +- crates/nu_plugin_example/Cargo.toml | 10 +-- crates/nu_plugin_formats/Cargo.toml | 8 +- crates/nu_plugin_gstat/Cargo.toml | 6 +- crates/nu_plugin_inc/Cargo.toml | 6 +- .../nu_plugin_nu_example.nu | 2 +- crates/nu_plugin_polars/Cargo.toml | 20 ++--- .../nu_plugin_python_example.py | 2 +- crates/nu_plugin_query/Cargo.toml | 6 +- crates/nu_plugin_stress_internals/Cargo.toml | 2 +- crates/nuon/Cargo.toml | 8 +- 43 files changed, 242 insertions(+), 242 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6390b32ee3..5a8de1b4ac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2868,7 +2868,7 @@ dependencies = [ [[package]] name = "nu" -version = "0.96.0" +version = "0.96.1" dependencies = [ "assert_cmd", "crossterm", @@ -2922,7 +2922,7 @@ dependencies = [ [[package]] name = "nu-cli" -version = "0.96.0" +version = "0.96.1" dependencies = [ "chrono", "crossterm", @@ -2957,7 +2957,7 @@ dependencies = [ [[package]] name = "nu-cmd-base" -version = "0.96.0" +version = "0.96.1" dependencies = [ "indexmap", "miette", @@ -2969,7 +2969,7 @@ dependencies = [ [[package]] name = "nu-cmd-extra" -version = "0.96.0" +version = "0.96.1" dependencies = [ "fancy-regex", "heck 0.5.0", @@ -2994,7 +2994,7 @@ dependencies = [ [[package]] name = "nu-cmd-lang" -version = "0.96.0" +version = "0.96.1" dependencies = [ "itertools 0.12.1", "nu-engine", @@ -3006,7 +3006,7 @@ dependencies = [ [[package]] name = "nu-cmd-plugin" -version = "0.96.0" +version = "0.96.1" dependencies = [ "itertools 0.12.1", "nu-engine", @@ -3017,7 +3017,7 @@ dependencies = [ [[package]] name = "nu-color-config" -version = "0.96.0" +version = "0.96.1" dependencies = [ "nu-ansi-term", "nu-engine", @@ -3029,7 +3029,7 @@ dependencies = [ [[package]] name = "nu-command" -version = "0.96.0" +version = "0.96.1" dependencies = [ "alphanumeric-sort", "base64 0.22.1", @@ -3139,7 +3139,7 @@ dependencies = [ [[package]] name = "nu-derive-value" -version = "0.96.0" +version = "0.96.1" dependencies = [ "convert_case", "proc-macro-error", @@ -3150,7 +3150,7 @@ dependencies = [ [[package]] name = "nu-engine" -version = "0.96.0" +version = "0.96.1" dependencies = [ "log", "nu-glob", @@ -3161,7 +3161,7 @@ dependencies = [ [[package]] name = "nu-explore" -version = "0.96.0" +version = "0.96.1" dependencies = [ "ansi-str", "anyhow", @@ -3186,14 +3186,14 @@ dependencies = [ [[package]] name = "nu-glob" -version = "0.96.0" +version = "0.96.1" dependencies = [ "doc-comment", ] [[package]] name = "nu-json" -version = "0.96.0" +version = "0.96.1" dependencies = [ "fancy-regex", "linked-hash-map", @@ -3206,7 +3206,7 @@ dependencies = [ [[package]] name = "nu-lsp" -version = "0.96.0" +version = "0.96.1" dependencies = [ "assert-json-diff", "crossbeam-channel", @@ -3227,7 +3227,7 @@ dependencies = [ [[package]] name = "nu-parser" -version = "0.96.0" +version = "0.96.1" dependencies = [ "bytesize", "chrono", @@ -3243,7 +3243,7 @@ dependencies = [ [[package]] name = "nu-path" -version = "0.96.0" +version = "0.96.1" dependencies = [ "dirs", "omnipath", @@ -3252,7 +3252,7 @@ dependencies = [ [[package]] name = "nu-plugin" -version = "0.96.0" +version = "0.96.1" dependencies = [ "log", "nix", @@ -3268,7 +3268,7 @@ dependencies = [ [[package]] name = "nu-plugin-core" -version = "0.96.0" +version = "0.96.1" dependencies = [ "interprocess", "log", @@ -3282,7 +3282,7 @@ dependencies = [ [[package]] name = "nu-plugin-engine" -version = "0.96.0" +version = "0.96.1" dependencies = [ "log", "nu-engine", @@ -3298,7 +3298,7 @@ dependencies = [ [[package]] name = "nu-plugin-protocol" -version = "0.96.0" +version = "0.96.1" dependencies = [ "bincode", "nu-protocol", @@ -3310,7 +3310,7 @@ dependencies = [ [[package]] name = "nu-plugin-test-support" -version = "0.96.0" +version = "0.96.1" dependencies = [ "nu-ansi-term", "nu-cmd-lang", @@ -3328,7 +3328,7 @@ dependencies = [ [[package]] name = "nu-pretty-hex" -version = "0.96.0" +version = "0.96.1" dependencies = [ "heapless", "nu-ansi-term", @@ -3337,7 +3337,7 @@ dependencies = [ [[package]] name = "nu-protocol" -version = "0.96.0" +version = "0.96.1" dependencies = [ "brotli", "byte-unit", @@ -3374,7 +3374,7 @@ dependencies = [ [[package]] name = "nu-std" -version = "0.96.0" +version = "0.96.1" dependencies = [ "log", "miette", @@ -3385,7 +3385,7 @@ dependencies = [ [[package]] name = "nu-system" -version = "0.96.0" +version = "0.96.1" dependencies = [ "chrono", "itertools 0.12.1", @@ -3403,7 +3403,7 @@ dependencies = [ [[package]] name = "nu-table" -version = "0.96.0" +version = "0.96.1" dependencies = [ "fancy-regex", "nu-ansi-term", @@ -3417,7 +3417,7 @@ dependencies = [ [[package]] name = "nu-term-grid" -version = "0.96.0" +version = "0.96.1" dependencies = [ "nu-utils", "unicode-width", @@ -3425,7 +3425,7 @@ dependencies = [ [[package]] name = "nu-test-support" -version = "0.96.0" +version = "0.96.1" dependencies = [ "nu-glob", "nu-path", @@ -3437,7 +3437,7 @@ dependencies = [ [[package]] name = "nu-utils" -version = "0.96.0" +version = "0.96.1" dependencies = [ "crossterm_winapi", "log", @@ -3463,7 +3463,7 @@ dependencies = [ [[package]] name = "nu_plugin_example" -version = "0.96.0" +version = "0.96.1" dependencies = [ "nu-cmd-lang", "nu-plugin", @@ -3473,7 +3473,7 @@ dependencies = [ [[package]] name = "nu_plugin_formats" -version = "0.96.0" +version = "0.96.1" dependencies = [ "eml-parser", "ical", @@ -3486,7 +3486,7 @@ dependencies = [ [[package]] name = "nu_plugin_gstat" -version = "0.96.0" +version = "0.96.1" dependencies = [ "git2", "nu-plugin", @@ -3495,7 +3495,7 @@ dependencies = [ [[package]] name = "nu_plugin_inc" -version = "0.96.0" +version = "0.96.1" dependencies = [ "nu-plugin", "nu-protocol", @@ -3504,7 +3504,7 @@ dependencies = [ [[package]] name = "nu_plugin_polars" -version = "0.96.0" +version = "0.96.1" dependencies = [ "chrono", "chrono-tz 0.9.0", @@ -3538,7 +3538,7 @@ dependencies = [ [[package]] name = "nu_plugin_query" -version = "0.96.0" +version = "0.96.1" dependencies = [ "gjson", "nu-plugin", @@ -3553,7 +3553,7 @@ dependencies = [ [[package]] name = "nu_plugin_stress_internals" -version = "0.96.0" +version = "0.96.1" dependencies = [ "interprocess", "serde", @@ -3679,7 +3679,7 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] name = "nuon" -version = "0.96.0" +version = "0.96.1" dependencies = [ "chrono", "fancy-regex", diff --git a/Cargo.toml b/Cargo.toml index 8397c2ca46..3d8e3a9385 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT" name = "nu" repository = "https://github.com/nushell/nushell" rust-version = "1.77.2" -version = "0.96.0" +version = "0.96.1" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -183,22 +183,22 @@ windows-sys = "0.48" winreg = "0.52" [dependencies] -nu-cli = { path = "./crates/nu-cli", version = "0.96.0" } -nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.96.0" } -nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.96.0" } -nu-cmd-plugin = { path = "./crates/nu-cmd-plugin", version = "0.96.0", optional = true } -nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.96.0" } -nu-command = { path = "./crates/nu-command", version = "0.96.0" } -nu-engine = { path = "./crates/nu-engine", version = "0.96.0" } -nu-explore = { path = "./crates/nu-explore", version = "0.96.0" } -nu-lsp = { path = "./crates/nu-lsp/", version = "0.96.0" } -nu-parser = { path = "./crates/nu-parser", version = "0.96.0" } -nu-path = { path = "./crates/nu-path", version = "0.96.0" } -nu-plugin-engine = { path = "./crates/nu-plugin-engine", optional = true, version = "0.96.0" } -nu-protocol = { path = "./crates/nu-protocol", version = "0.96.0" } -nu-std = { path = "./crates/nu-std", version = "0.96.0" } -nu-system = { path = "./crates/nu-system", version = "0.96.0" } -nu-utils = { path = "./crates/nu-utils", version = "0.96.0" } +nu-cli = { path = "./crates/nu-cli", version = "0.96.1" } +nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.96.1" } +nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.96.1" } +nu-cmd-plugin = { path = "./crates/nu-cmd-plugin", version = "0.96.1", optional = true } +nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.96.1" } +nu-command = { path = "./crates/nu-command", version = "0.96.1" } +nu-engine = { path = "./crates/nu-engine", version = "0.96.1" } +nu-explore = { path = "./crates/nu-explore", version = "0.96.1" } +nu-lsp = { path = "./crates/nu-lsp/", version = "0.96.1" } +nu-parser = { path = "./crates/nu-parser", version = "0.96.1" } +nu-path = { path = "./crates/nu-path", version = "0.96.1" } +nu-plugin-engine = { path = "./crates/nu-plugin-engine", optional = true, version = "0.96.1" } +nu-protocol = { path = "./crates/nu-protocol", version = "0.96.1" } +nu-std = { path = "./crates/nu-std", version = "0.96.1" } +nu-system = { path = "./crates/nu-system", version = "0.96.1" } +nu-utils = { path = "./crates/nu-utils", version = "0.96.1" } reedline = { workspace = true, features = ["bashisms", "sqlite"] } crossterm = { workspace = true } @@ -227,9 +227,9 @@ nix = { workspace = true, default-features = false, features = [ ] } [dev-dependencies] -nu-test-support = { path = "./crates/nu-test-support", version = "0.96.0" } -nu-plugin-protocol = { path = "./crates/nu-plugin-protocol", version = "0.96.0" } -nu-plugin-core = { path = "./crates/nu-plugin-core", version = "0.96.0" } +nu-test-support = { path = "./crates/nu-test-support", version = "0.96.1" } +nu-plugin-protocol = { path = "./crates/nu-plugin-protocol", version = "0.96.1" } +nu-plugin-core = { path = "./crates/nu-plugin-core", version = "0.96.1" } assert_cmd = "2.0" dirs = { workspace = true } tango-bench = "0.5" diff --git a/crates/nu-cli/Cargo.toml b/crates/nu-cli/Cargo.toml index 6baafda789..1be1470daf 100644 --- a/crates/nu-cli/Cargo.toml +++ b/crates/nu-cli/Cargo.toml @@ -5,27 +5,27 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cli" edition = "2021" license = "MIT" name = "nu-cli" -version = "0.96.0" +version = "0.96.1" [lib] bench = false [dev-dependencies] -nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.96.0" } -nu-command = { path = "../nu-command", version = "0.96.0" } -nu-test-support = { path = "../nu-test-support", version = "0.96.0" } +nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.96.1" } +nu-command = { path = "../nu-command", version = "0.96.1" } +nu-test-support = { path = "../nu-test-support", version = "0.96.1" } rstest = { workspace = true, default-features = false } tempfile = { workspace = true } [dependencies] -nu-cmd-base = { path = "../nu-cmd-base", version = "0.96.0" } -nu-engine = { path = "../nu-engine", version = "0.96.0" } -nu-path = { path = "../nu-path", version = "0.96.0" } -nu-parser = { path = "../nu-parser", version = "0.96.0" } -nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.96.0", optional = true } -nu-protocol = { path = "../nu-protocol", version = "0.96.0" } -nu-utils = { path = "../nu-utils", version = "0.96.0" } -nu-color-config = { path = "../nu-color-config", version = "0.96.0" } +nu-cmd-base = { path = "../nu-cmd-base", version = "0.96.1" } +nu-engine = { path = "../nu-engine", version = "0.96.1" } +nu-path = { path = "../nu-path", version = "0.96.1" } +nu-parser = { path = "../nu-parser", version = "0.96.1" } +nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.96.1", optional = true } +nu-protocol = { path = "../nu-protocol", version = "0.96.1" } +nu-utils = { path = "../nu-utils", version = "0.96.1" } +nu-color-config = { path = "../nu-color-config", version = "0.96.1" } nu-ansi-term = { workspace = true } reedline = { workspace = true, features = ["bashisms", "sqlite"] } diff --git a/crates/nu-cmd-base/Cargo.toml b/crates/nu-cmd-base/Cargo.toml index 031636adfc..0fdb51eca8 100644 --- a/crates/nu-cmd-base/Cargo.toml +++ b/crates/nu-cmd-base/Cargo.toml @@ -5,15 +5,15 @@ edition = "2021" license = "MIT" name = "nu-cmd-base" repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-base" -version = "0.96.0" +version = "0.96.1" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -nu-engine = { path = "../nu-engine", version = "0.96.0" } -nu-parser = { path = "../nu-parser", version = "0.96.0" } -nu-path = { path = "../nu-path", version = "0.96.0" } -nu-protocol = { path = "../nu-protocol", version = "0.96.0" } +nu-engine = { path = "../nu-engine", version = "0.96.1" } +nu-parser = { path = "../nu-parser", version = "0.96.1" } +nu-path = { path = "../nu-path", version = "0.96.1" } +nu-protocol = { path = "../nu-protocol", version = "0.96.1" } indexmap = { workspace = true } miette = { workspace = true } diff --git a/crates/nu-cmd-extra/Cargo.toml b/crates/nu-cmd-extra/Cargo.toml index 2b26a962de..f9cc539c92 100644 --- a/crates/nu-cmd-extra/Cargo.toml +++ b/crates/nu-cmd-extra/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" license = "MIT" name = "nu-cmd-extra" repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-extra" -version = "0.96.0" +version = "0.96.1" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -13,13 +13,13 @@ version = "0.96.0" bench = false [dependencies] -nu-cmd-base = { path = "../nu-cmd-base", version = "0.96.0" } -nu-engine = { path = "../nu-engine", version = "0.96.0" } -nu-json = { version = "0.96.0", path = "../nu-json" } -nu-parser = { path = "../nu-parser", version = "0.96.0" } -nu-pretty-hex = { version = "0.96.0", path = "../nu-pretty-hex" } -nu-protocol = { path = "../nu-protocol", version = "0.96.0" } -nu-utils = { path = "../nu-utils", version = "0.96.0" } +nu-cmd-base = { path = "../nu-cmd-base", version = "0.96.1" } +nu-engine = { path = "../nu-engine", version = "0.96.1" } +nu-json = { version = "0.96.1", path = "../nu-json" } +nu-parser = { path = "../nu-parser", version = "0.96.1" } +nu-pretty-hex = { version = "0.96.1", path = "../nu-pretty-hex" } +nu-protocol = { path = "../nu-protocol", version = "0.96.1" } +nu-utils = { path = "../nu-utils", version = "0.96.1" } # Potential dependencies for extras heck = { workspace = true } @@ -33,6 +33,6 @@ v_htmlescape = { workspace = true } itertools = { workspace = true } [dev-dependencies] -nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.96.0" } -nu-command = { path = "../nu-command", version = "0.96.0" } -nu-test-support = { path = "../nu-test-support", version = "0.96.0" } \ No newline at end of file +nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.96.1" } +nu-command = { path = "../nu-command", version = "0.96.1" } +nu-test-support = { path = "../nu-test-support", version = "0.96.1" } \ No newline at end of file diff --git a/crates/nu-cmd-lang/Cargo.toml b/crates/nu-cmd-lang/Cargo.toml index 5a9d552cba..7a92894f54 100644 --- a/crates/nu-cmd-lang/Cargo.toml +++ b/crates/nu-cmd-lang/Cargo.toml @@ -6,16 +6,16 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-lang" edition = "2021" license = "MIT" name = "nu-cmd-lang" -version = "0.96.0" +version = "0.96.1" [lib] bench = false [dependencies] -nu-engine = { path = "../nu-engine", version = "0.96.0" } -nu-parser = { path = "../nu-parser", version = "0.96.0" } -nu-protocol = { path = "../nu-protocol", version = "0.96.0" } -nu-utils = { path = "../nu-utils", version = "0.96.0" } +nu-engine = { path = "../nu-engine", version = "0.96.1" } +nu-parser = { path = "../nu-parser", version = "0.96.1" } +nu-protocol = { path = "../nu-protocol", version = "0.96.1" } +nu-utils = { path = "../nu-utils", version = "0.96.1" } itertools = { workspace = true } shadow-rs = { version = "0.29", default-features = false } diff --git a/crates/nu-cmd-plugin/Cargo.toml b/crates/nu-cmd-plugin/Cargo.toml index a73d4eef89..7a2c28e69f 100644 --- a/crates/nu-cmd-plugin/Cargo.toml +++ b/crates/nu-cmd-plugin/Cargo.toml @@ -5,15 +5,15 @@ edition = "2021" license = "MIT" name = "nu-cmd-plugin" repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-plugin" -version = "0.96.0" +version = "0.96.1" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -nu-engine = { path = "../nu-engine", version = "0.96.0" } -nu-path = { path = "../nu-path", version = "0.96.0" } -nu-protocol = { path = "../nu-protocol", version = "0.96.0", features = ["plugin"] } -nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.96.0" } +nu-engine = { path = "../nu-engine", version = "0.96.1" } +nu-path = { path = "../nu-path", version = "0.96.1" } +nu-protocol = { path = "../nu-protocol", version = "0.96.1", features = ["plugin"] } +nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.96.1" } itertools = { workspace = true } diff --git a/crates/nu-color-config/Cargo.toml b/crates/nu-color-config/Cargo.toml index f3e977f3ab..7dd1edf899 100644 --- a/crates/nu-color-config/Cargo.toml +++ b/crates/nu-color-config/Cargo.toml @@ -5,18 +5,18 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-color-confi edition = "2021" license = "MIT" name = "nu-color-config" -version = "0.96.0" +version = "0.96.1" [lib] bench = false [dependencies] -nu-protocol = { path = "../nu-protocol", version = "0.96.0" } -nu-engine = { path = "../nu-engine", version = "0.96.0" } -nu-json = { path = "../nu-json", version = "0.96.0" } +nu-protocol = { path = "../nu-protocol", version = "0.96.1" } +nu-engine = { path = "../nu-engine", version = "0.96.1" } +nu-json = { path = "../nu-json", version = "0.96.1" } nu-ansi-term = { workspace = true } serde = { workspace = true, features = ["derive"] } [dev-dependencies] -nu-test-support = { path = "../nu-test-support", version = "0.96.0" } \ No newline at end of file +nu-test-support = { path = "../nu-test-support", version = "0.96.1" } \ No newline at end of file diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index f3805dac5a..61ae01d69b 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" license = "MIT" name = "nu-command" repository = "https://github.com/nushell/nushell/tree/main/crates/nu-command" -version = "0.96.0" +version = "0.96.1" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -13,21 +13,21 @@ version = "0.96.0" bench = false [dependencies] -nu-cmd-base = { path = "../nu-cmd-base", version = "0.96.0" } -nu-color-config = { path = "../nu-color-config", version = "0.96.0" } -nu-engine = { path = "../nu-engine", version = "0.96.0" } -nu-glob = { path = "../nu-glob", version = "0.96.0" } -nu-json = { path = "../nu-json", version = "0.96.0" } -nu-parser = { path = "../nu-parser", version = "0.96.0" } -nu-path = { path = "../nu-path", version = "0.96.0" } -nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.96.0" } -nu-protocol = { path = "../nu-protocol", version = "0.96.0" } -nu-system = { path = "../nu-system", version = "0.96.0" } -nu-table = { path = "../nu-table", version = "0.96.0" } -nu-term-grid = { path = "../nu-term-grid", version = "0.96.0" } -nu-utils = { path = "../nu-utils", version = "0.96.0" } +nu-cmd-base = { path = "../nu-cmd-base", version = "0.96.1" } +nu-color-config = { path = "../nu-color-config", version = "0.96.1" } +nu-engine = { path = "../nu-engine", version = "0.96.1" } +nu-glob = { path = "../nu-glob", version = "0.96.1" } +nu-json = { path = "../nu-json", version = "0.96.1" } +nu-parser = { path = "../nu-parser", version = "0.96.1" } +nu-path = { path = "../nu-path", version = "0.96.1" } +nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.96.1" } +nu-protocol = { path = "../nu-protocol", version = "0.96.1" } +nu-system = { path = "../nu-system", version = "0.96.1" } +nu-table = { path = "../nu-table", version = "0.96.1" } +nu-term-grid = { path = "../nu-term-grid", version = "0.96.1" } +nu-utils = { path = "../nu-utils", version = "0.96.1" } nu-ansi-term = { workspace = true } -nuon = { path = "../nuon", version = "0.96.0" } +nuon = { path = "../nuon", version = "0.96.1" } alphanumeric-sort = { workspace = true } base64 = { workspace = true } @@ -137,8 +137,8 @@ sqlite = ["rusqlite"] trash-support = ["trash"] [dev-dependencies] -nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.96.0" } -nu-test-support = { path = "../nu-test-support", version = "0.96.0" } +nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.96.1" } +nu-test-support = { path = "../nu-test-support", version = "0.96.1" } dirs = { workspace = true } mockito = { workspace = true, default-features = false } diff --git a/crates/nu-derive-value/Cargo.toml b/crates/nu-derive-value/Cargo.toml index 3ce0f00ed5..b4d9fe341f 100644 --- a/crates/nu-derive-value/Cargo.toml +++ b/crates/nu-derive-value/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" license = "MIT" name = "nu-derive-value" repository = "https://github.com/nushell/nushell/tree/main/crates/nu-derive-value" -version = "0.96.0" +version = "0.96.1" [lib] proc-macro = true diff --git a/crates/nu-engine/Cargo.toml b/crates/nu-engine/Cargo.toml index 89ff80cf80..44b049f38a 100644 --- a/crates/nu-engine/Cargo.toml +++ b/crates/nu-engine/Cargo.toml @@ -5,16 +5,16 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-engine" edition = "2021" license = "MIT" name = "nu-engine" -version = "0.96.0" +version = "0.96.1" [lib] bench = false [dependencies] -nu-protocol = { path = "../nu-protocol", features = ["plugin"], version = "0.96.0" } -nu-path = { path = "../nu-path", version = "0.96.0" } -nu-glob = { path = "../nu-glob", version = "0.96.0" } -nu-utils = { path = "../nu-utils", version = "0.96.0" } +nu-protocol = { path = "../nu-protocol", features = ["plugin"], version = "0.96.1" } +nu-path = { path = "../nu-path", version = "0.96.1" } +nu-glob = { path = "../nu-glob", version = "0.96.1" } +nu-utils = { path = "../nu-utils", version = "0.96.1" } log = { workspace = true } [features] diff --git a/crates/nu-explore/Cargo.toml b/crates/nu-explore/Cargo.toml index df728365a7..26087b98ec 100644 --- a/crates/nu-explore/Cargo.toml +++ b/crates/nu-explore/Cargo.toml @@ -5,21 +5,21 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-explore" edition = "2021" license = "MIT" name = "nu-explore" -version = "0.96.0" +version = "0.96.1" [lib] bench = false [dependencies] -nu-protocol = { path = "../nu-protocol", version = "0.96.0" } -nu-parser = { path = "../nu-parser", version = "0.96.0" } -nu-color-config = { path = "../nu-color-config", version = "0.96.0" } -nu-engine = { path = "../nu-engine", version = "0.96.0" } -nu-table = { path = "../nu-table", version = "0.96.0" } -nu-json = { path = "../nu-json", version = "0.96.0" } -nu-utils = { path = "../nu-utils", version = "0.96.0" } +nu-protocol = { path = "../nu-protocol", version = "0.96.1" } +nu-parser = { path = "../nu-parser", version = "0.96.1" } +nu-color-config = { path = "../nu-color-config", version = "0.96.1" } +nu-engine = { path = "../nu-engine", version = "0.96.1" } +nu-table = { path = "../nu-table", version = "0.96.1" } +nu-json = { path = "../nu-json", version = "0.96.1" } +nu-utils = { path = "../nu-utils", version = "0.96.1" } nu-ansi-term = { workspace = true } -nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.96.0" } +nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.96.1" } anyhow = { workspace = true } log = { workspace = true } diff --git a/crates/nu-glob/Cargo.toml b/crates/nu-glob/Cargo.toml index 242affbdcc..acfaf2fbb3 100644 --- a/crates/nu-glob/Cargo.toml +++ b/crates/nu-glob/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nu-glob" -version = "0.96.0" +version = "0.96.1" authors = ["The Nushell Project Developers", "The Rust Project Developers"] license = "MIT/Apache-2.0" description = """ diff --git a/crates/nu-json/Cargo.toml b/crates/nu-json/Cargo.toml index bd73655ae5..d4d64a26e3 100644 --- a/crates/nu-json/Cargo.toml +++ b/crates/nu-json/Cargo.toml @@ -8,7 +8,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-json" edition = "2021" license = "MIT" name = "nu-json" -version = "0.96.0" +version = "0.96.1" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -26,7 +26,7 @@ serde = { workspace = true } serde_json = { workspace = true } [dev-dependencies] -nu-test-support = { path = "../nu-test-support", version = "0.96.0" } -nu-path = { path = "../nu-path", version = "0.96.0" } +nu-test-support = { path = "../nu-test-support", version = "0.96.1" } +nu-path = { path = "../nu-path", version = "0.96.1" } serde_json = "1.0" fancy-regex = "0.13.0" \ No newline at end of file diff --git a/crates/nu-lsp/Cargo.toml b/crates/nu-lsp/Cargo.toml index 4ebe937960..fc2b1d76fa 100644 --- a/crates/nu-lsp/Cargo.toml +++ b/crates/nu-lsp/Cargo.toml @@ -3,14 +3,14 @@ authors = ["The Nushell Project Developers"] description = "Nushell's integrated LSP server" repository = "https://github.com/nushell/nushell/tree/main/crates/nu-lsp" name = "nu-lsp" -version = "0.96.0" +version = "0.96.1" edition = "2021" license = "MIT" [dependencies] -nu-cli = { path = "../nu-cli", version = "0.96.0" } -nu-parser = { path = "../nu-parser", version = "0.96.0" } -nu-protocol = { path = "../nu-protocol", version = "0.96.0" } +nu-cli = { path = "../nu-cli", version = "0.96.1" } +nu-parser = { path = "../nu-parser", version = "0.96.1" } +nu-protocol = { path = "../nu-protocol", version = "0.96.1" } reedline = { workspace = true } @@ -23,8 +23,8 @@ serde = { workspace = true } serde_json = { workspace = true } [dev-dependencies] -nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.96.0" } -nu-command = { path = "../nu-command", version = "0.96.0" } -nu-test-support = { path = "../nu-test-support", version = "0.96.0" } +nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.96.1" } +nu-command = { path = "../nu-command", version = "0.96.1" } +nu-test-support = { path = "../nu-test-support", version = "0.96.1" } assert-json-diff = "2.0" \ No newline at end of file diff --git a/crates/nu-parser/Cargo.toml b/crates/nu-parser/Cargo.toml index d8f978620d..da8f63d42b 100644 --- a/crates/nu-parser/Cargo.toml +++ b/crates/nu-parser/Cargo.toml @@ -5,17 +5,17 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-parser" edition = "2021" license = "MIT" name = "nu-parser" -version = "0.96.0" +version = "0.96.1" exclude = ["/fuzz"] [lib] bench = false [dependencies] -nu-engine = { path = "../nu-engine", version = "0.96.0" } -nu-path = { path = "../nu-path", version = "0.96.0" } -nu-plugin-engine = { path = "../nu-plugin-engine", optional = true, version = "0.96.0" } -nu-protocol = { path = "../nu-protocol", version = "0.96.0" } +nu-engine = { path = "../nu-engine", version = "0.96.1" } +nu-path = { path = "../nu-path", version = "0.96.1" } +nu-plugin-engine = { path = "../nu-plugin-engine", optional = true, version = "0.96.1" } +nu-protocol = { path = "../nu-protocol", version = "0.96.1" } bytesize = { workspace = true } chrono = { default-features = false, features = ['std'], workspace = true } diff --git a/crates/nu-path/Cargo.toml b/crates/nu-path/Cargo.toml index 7bddfd309f..ff49140bd2 100644 --- a/crates/nu-path/Cargo.toml +++ b/crates/nu-path/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-path" edition = "2021" license = "MIT" name = "nu-path" -version = "0.96.0" +version = "0.96.1" exclude = ["/fuzz"] [lib] diff --git a/crates/nu-plugin-core/Cargo.toml b/crates/nu-plugin-core/Cargo.toml index 3b9e8b63ca..21119a442c 100644 --- a/crates/nu-plugin-core/Cargo.toml +++ b/crates/nu-plugin-core/Cargo.toml @@ -5,14 +5,14 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-plugin-core edition = "2021" license = "MIT" name = "nu-plugin-core" -version = "0.96.0" +version = "0.96.1" [lib] bench = false [dependencies] -nu-protocol = { path = "../nu-protocol", version = "0.96.0" } -nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.96.0", default-features = false } +nu-protocol = { path = "../nu-protocol", version = "0.96.1" } +nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.96.1", default-features = false } rmp-serde = { workspace = true } serde = { workspace = true } diff --git a/crates/nu-plugin-engine/Cargo.toml b/crates/nu-plugin-engine/Cargo.toml index 585e28de47..189b9b0713 100644 --- a/crates/nu-plugin-engine/Cargo.toml +++ b/crates/nu-plugin-engine/Cargo.toml @@ -5,18 +5,18 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-plugin-engi edition = "2021" license = "MIT" name = "nu-plugin-engine" -version = "0.96.0" +version = "0.96.1" [lib] bench = false [dependencies] -nu-engine = { path = "../nu-engine", version = "0.96.0" } -nu-protocol = { path = "../nu-protocol", version = "0.96.0" } -nu-system = { path = "../nu-system", version = "0.96.0" } -nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.96.0" } -nu-plugin-core = { path = "../nu-plugin-core", version = "0.96.0", default-features = false } -nu-utils = { path = "../nu-utils", version = "0.96.0" } +nu-engine = { path = "../nu-engine", version = "0.96.1" } +nu-protocol = { path = "../nu-protocol", version = "0.96.1" } +nu-system = { path = "../nu-system", version = "0.96.1" } +nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.96.1" } +nu-plugin-core = { path = "../nu-plugin-core", version = "0.96.1", default-features = false } +nu-utils = { path = "../nu-utils", version = "0.96.1" } serde = { workspace = true } log = { workspace = true } diff --git a/crates/nu-plugin-protocol/Cargo.toml b/crates/nu-plugin-protocol/Cargo.toml index feb6c51b64..68b859f213 100644 --- a/crates/nu-plugin-protocol/Cargo.toml +++ b/crates/nu-plugin-protocol/Cargo.toml @@ -5,14 +5,14 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-plugin-prot edition = "2021" license = "MIT" name = "nu-plugin-protocol" -version = "0.96.0" +version = "0.96.1" [lib] bench = false [dependencies] -nu-protocol = { path = "../nu-protocol", version = "0.96.0", features = ["plugin"] } -nu-utils = { path = "../nu-utils", version = "0.96.0" } +nu-protocol = { path = "../nu-protocol", version = "0.96.1", features = ["plugin"] } +nu-utils = { path = "../nu-utils", version = "0.96.1" } bincode = "1.3" serde = { workspace = true, features = ["derive"] } diff --git a/crates/nu-plugin-test-support/Cargo.toml b/crates/nu-plugin-test-support/Cargo.toml index e12fc28823..f33d997939 100644 --- a/crates/nu-plugin-test-support/Cargo.toml +++ b/crates/nu-plugin-test-support/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nu-plugin-test-support" -version = "0.96.0" +version = "0.96.1" edition = "2021" license = "MIT" description = "Testing support for Nushell plugins" @@ -12,14 +12,14 @@ bench = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -nu-engine = { path = "../nu-engine", version = "0.96.0", features = ["plugin"] } -nu-protocol = { path = "../nu-protocol", version = "0.96.0", features = ["plugin"] } -nu-parser = { path = "../nu-parser", version = "0.96.0", features = ["plugin"] } -nu-plugin = { path = "../nu-plugin", version = "0.96.0" } -nu-plugin-core = { path = "../nu-plugin-core", version = "0.96.0" } -nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.96.0" } -nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.96.0" } -nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.96.0" } +nu-engine = { path = "../nu-engine", version = "0.96.1", features = ["plugin"] } +nu-protocol = { path = "../nu-protocol", version = "0.96.1", features = ["plugin"] } +nu-parser = { path = "../nu-parser", version = "0.96.1", features = ["plugin"] } +nu-plugin = { path = "../nu-plugin", version = "0.96.1" } +nu-plugin-core = { path = "../nu-plugin-core", version = "0.96.1" } +nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.96.1" } +nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.96.1" } +nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.96.1" } nu-ansi-term = { workspace = true } similar = "2.5" diff --git a/crates/nu-plugin/Cargo.toml b/crates/nu-plugin/Cargo.toml index 3b06f37fae..b7fff9bf72 100644 --- a/crates/nu-plugin/Cargo.toml +++ b/crates/nu-plugin/Cargo.toml @@ -5,17 +5,17 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-plugin" edition = "2021" license = "MIT" name = "nu-plugin" -version = "0.96.0" +version = "0.96.1" [lib] bench = false [dependencies] -nu-engine = { path = "../nu-engine", version = "0.96.0" } -nu-protocol = { path = "../nu-protocol", version = "0.96.0" } -nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.96.0" } -nu-plugin-core = { path = "../nu-plugin-core", version = "0.96.0", default-features = false } -nu-utils = { path = "../nu-utils", version = "0.96.0" } +nu-engine = { path = "../nu-engine", version = "0.96.1" } +nu-protocol = { path = "../nu-protocol", version = "0.96.1" } +nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.96.1" } +nu-plugin-core = { path = "../nu-plugin-core", version = "0.96.1", default-features = false } +nu-utils = { path = "../nu-utils", version = "0.96.1" } log = { workspace = true } thiserror = "1.0" diff --git a/crates/nu-pretty-hex/Cargo.toml b/crates/nu-pretty-hex/Cargo.toml index e2f21d322b..7db01ca2cc 100644 --- a/crates/nu-pretty-hex/Cargo.toml +++ b/crates/nu-pretty-hex/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-pretty-hex" edition = "2021" license = "MIT" name = "nu-pretty-hex" -version = "0.96.0" +version = "0.96.1" [lib] doctest = false diff --git a/crates/nu-protocol/Cargo.toml b/crates/nu-protocol/Cargo.toml index 467c4a21f7..ff8ef86c9e 100644 --- a/crates/nu-protocol/Cargo.toml +++ b/crates/nu-protocol/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-protocol" edition = "2021" license = "MIT" name = "nu-protocol" -version = "0.96.0" +version = "0.96.1" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -13,10 +13,10 @@ version = "0.96.0" bench = false [dependencies] -nu-utils = { path = "../nu-utils", version = "0.96.0" } -nu-path = { path = "../nu-path", version = "0.96.0" } -nu-system = { path = "../nu-system", version = "0.96.0" } -nu-derive-value = { path = "../nu-derive-value", version = "0.96.0" } +nu-utils = { path = "../nu-utils", version = "0.96.1" } +nu-path = { path = "../nu-path", version = "0.96.1" } +nu-system = { path = "../nu-system", version = "0.96.1" } +nu-derive-value = { path = "../nu-derive-value", version = "0.96.1" } brotli = { workspace = true, optional = true } byte-unit = { version = "5.1", features = [ "serde" ] } @@ -53,7 +53,7 @@ plugin = [ serde_json = { workspace = true } strum = "0.26" strum_macros = "0.26" -nu-test-support = { path = "../nu-test-support", version = "0.96.0" } +nu-test-support = { path = "../nu-test-support", version = "0.96.1" } pretty_assertions = { workspace = true } rstest = { workspace = true } tempfile = { workspace = true } diff --git a/crates/nu-std/Cargo.toml b/crates/nu-std/Cargo.toml index 408b1e0303..d52ab617c1 100644 --- a/crates/nu-std/Cargo.toml +++ b/crates/nu-std/Cargo.toml @@ -5,12 +5,12 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-std" edition = "2021" license = "MIT" name = "nu-std" -version = "0.96.0" +version = "0.96.1" [dependencies] -nu-parser = { version = "0.96.0", path = "../nu-parser" } -nu-protocol = { version = "0.96.0", path = "../nu-protocol" } -nu-engine = { version = "0.96.0", path = "../nu-engine" } +nu-parser = { version = "0.96.1", path = "../nu-parser" } +nu-protocol = { version = "0.96.1", path = "../nu-protocol" } +nu-engine = { version = "0.96.1", path = "../nu-engine" } miette = { workspace = true, features = ["fancy-no-backtrace"] } log = "0.4" \ No newline at end of file diff --git a/crates/nu-system/Cargo.toml b/crates/nu-system/Cargo.toml index d4d6f77e9b..1e1efba651 100644 --- a/crates/nu-system/Cargo.toml +++ b/crates/nu-system/Cargo.toml @@ -3,7 +3,7 @@ authors = ["The Nushell Project Developers", "procs creators"] description = "Nushell system querying" repository = "https://github.com/nushell/nushell/tree/main/crates/nu-system" name = "nu-system" -version = "0.96.0" +version = "0.96.1" edition = "2021" license = "MIT" diff --git a/crates/nu-table/Cargo.toml b/crates/nu-table/Cargo.toml index 982fd08369..1f02ccb1c3 100644 --- a/crates/nu-table/Cargo.toml +++ b/crates/nu-table/Cargo.toml @@ -5,20 +5,20 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-table" edition = "2021" license = "MIT" name = "nu-table" -version = "0.96.0" +version = "0.96.1" [lib] bench = false [dependencies] -nu-protocol = { path = "../nu-protocol", version = "0.96.0" } -nu-utils = { path = "../nu-utils", version = "0.96.0" } -nu-engine = { path = "../nu-engine", version = "0.96.0" } -nu-color-config = { path = "../nu-color-config", version = "0.96.0" } +nu-protocol = { path = "../nu-protocol", version = "0.96.1" } +nu-utils = { path = "../nu-utils", version = "0.96.1" } +nu-engine = { path = "../nu-engine", version = "0.96.1" } +nu-color-config = { path = "../nu-color-config", version = "0.96.1" } nu-ansi-term = { workspace = true } once_cell = { workspace = true } fancy-regex = { workspace = true } tabled = { workspace = true, features = ["color"], default-features = false } [dev-dependencies] -# nu-test-support = { path="../nu-test-support", version = "0.96.0" } \ No newline at end of file +# nu-test-support = { path="../nu-test-support", version = "0.96.1" } \ No newline at end of file diff --git a/crates/nu-term-grid/Cargo.toml b/crates/nu-term-grid/Cargo.toml index 0be10b4ef1..348adb03af 100644 --- a/crates/nu-term-grid/Cargo.toml +++ b/crates/nu-term-grid/Cargo.toml @@ -5,12 +5,12 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-term-grid" edition = "2021" license = "MIT" name = "nu-term-grid" -version = "0.96.0" +version = "0.96.1" [lib] bench = false [dependencies] -nu-utils = { path = "../nu-utils", version = "0.96.0" } +nu-utils = { path = "../nu-utils", version = "0.96.1" } unicode-width = { workspace = true } \ No newline at end of file diff --git a/crates/nu-test-support/Cargo.toml b/crates/nu-test-support/Cargo.toml index c41bf6b8a2..4a43d6b933 100644 --- a/crates/nu-test-support/Cargo.toml +++ b/crates/nu-test-support/Cargo.toml @@ -5,16 +5,16 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-test-suppor edition = "2021" license = "MIT" name = "nu-test-support" -version = "0.96.0" +version = "0.96.1" [lib] doctest = false bench = false [dependencies] -nu-path = { path = "../nu-path", version = "0.96.0" } -nu-glob = { path = "../nu-glob", version = "0.96.0" } -nu-utils = { path = "../nu-utils", version = "0.96.0" } +nu-path = { path = "../nu-path", version = "0.96.1" } +nu-glob = { path = "../nu-glob", version = "0.96.1" } +nu-utils = { path = "../nu-utils", version = "0.96.1" } num-format = { workspace = true } which = { workspace = true } diff --git a/crates/nu-utils/Cargo.toml b/crates/nu-utils/Cargo.toml index aa9f591a2d..4c1caddbbd 100644 --- a/crates/nu-utils/Cargo.toml +++ b/crates/nu-utils/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" license = "MIT" name = "nu-utils" repository = "https://github.com/nushell/nushell/tree/main/crates/nu-utils" -version = "0.96.0" +version = "0.96.1" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [[bin]] diff --git a/crates/nu-utils/src/sample_config/default_config.nu b/crates/nu-utils/src/sample_config/default_config.nu index dda33cace0..779c2e900c 100644 --- a/crates/nu-utils/src/sample_config/default_config.nu +++ b/crates/nu-utils/src/sample_config/default_config.nu @@ -1,6 +1,6 @@ # Nushell Config File # -# version = "0.96.0" +# version = "0.96.1" # For more information on defining custom themes, see # https://www.nushell.sh/book/coloring_and_theming.html diff --git a/crates/nu-utils/src/sample_config/default_env.nu b/crates/nu-utils/src/sample_config/default_env.nu index 5d6cab2687..5d5cc46190 100644 --- a/crates/nu-utils/src/sample_config/default_env.nu +++ b/crates/nu-utils/src/sample_config/default_env.nu @@ -1,6 +1,6 @@ # Nushell Environment Config File # -# version = "0.96.0" +# version = "0.96.1" def create_left_prompt [] { let dir = match (do --ignore-shell-errors { $env.PWD | path relative-to $nu.home-path }) { diff --git a/crates/nu_plugin_custom_values/Cargo.toml b/crates/nu_plugin_custom_values/Cargo.toml index c3f3a7cec1..9643bf337e 100644 --- a/crates/nu_plugin_custom_values/Cargo.toml +++ b/crates/nu_plugin_custom_values/Cargo.toml @@ -10,10 +10,10 @@ name = "nu_plugin_custom_values" bench = false [dependencies] -nu-plugin = { path = "../nu-plugin", version = "0.96.0" } -nu-protocol = { path = "../nu-protocol", version = "0.96.0", features = ["plugin"] } +nu-plugin = { path = "../nu-plugin", version = "0.96.1" } +nu-protocol = { path = "../nu-protocol", version = "0.96.1", features = ["plugin"] } serde = { workspace = true, default-features = false } typetag = "0.2" [dev-dependencies] -nu-plugin-test-support = { path = "../nu-plugin-test-support", version = "0.96.0" } \ No newline at end of file +nu-plugin-test-support = { path = "../nu-plugin-test-support", version = "0.96.1" } \ No newline at end of file diff --git a/crates/nu_plugin_example/Cargo.toml b/crates/nu_plugin_example/Cargo.toml index 9df31e46b6..8b24911026 100644 --- a/crates/nu_plugin_example/Cargo.toml +++ b/crates/nu_plugin_example/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu_plugin_exam edition = "2021" license = "MIT" name = "nu_plugin_example" -version = "0.96.0" +version = "0.96.1" [[bin]] name = "nu_plugin_example" @@ -15,9 +15,9 @@ bench = false bench = false [dependencies] -nu-plugin = { path = "../nu-plugin", version = "0.96.0" } -nu-protocol = { path = "../nu-protocol", version = "0.96.0", features = ["plugin"] } +nu-plugin = { path = "../nu-plugin", version = "0.96.1" } +nu-protocol = { path = "../nu-protocol", version = "0.96.1", features = ["plugin"] } [dev-dependencies] -nu-plugin-test-support = { path = "../nu-plugin-test-support", version = "0.96.0" } -nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.96.0" } \ No newline at end of file +nu-plugin-test-support = { path = "../nu-plugin-test-support", version = "0.96.1" } +nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.96.1" } \ No newline at end of file diff --git a/crates/nu_plugin_formats/Cargo.toml b/crates/nu_plugin_formats/Cargo.toml index 535cd69c17..ac65f05080 100644 --- a/crates/nu_plugin_formats/Cargo.toml +++ b/crates/nu_plugin_formats/Cargo.toml @@ -5,12 +5,12 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu_plugin_form edition = "2021" license = "MIT" name = "nu_plugin_formats" -version = "0.96.0" +version = "0.96.1" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -nu-plugin = { path = "../nu-plugin", version = "0.96.0" } -nu-protocol = { path = "../nu-protocol", version = "0.96.0", features = ["plugin"] } +nu-plugin = { path = "../nu-plugin", version = "0.96.1" } +nu-protocol = { path = "../nu-protocol", version = "0.96.1", features = ["plugin"] } indexmap = { workspace = true } eml-parser = "0.1" @@ -18,4 +18,4 @@ ical = "0.11" rust-ini = "0.21.0" [dev-dependencies] -nu-plugin-test-support = { path = "../nu-plugin-test-support", version = "0.96.0" } \ No newline at end of file +nu-plugin-test-support = { path = "../nu-plugin-test-support", version = "0.96.1" } \ No newline at end of file diff --git a/crates/nu_plugin_gstat/Cargo.toml b/crates/nu_plugin_gstat/Cargo.toml index 6a7d6411c8..9bc09fc6c0 100644 --- a/crates/nu_plugin_gstat/Cargo.toml +++ b/crates/nu_plugin_gstat/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu_plugin_gsta edition = "2021" license = "MIT" name = "nu_plugin_gstat" -version = "0.96.0" +version = "0.96.1" [lib] doctest = false @@ -16,7 +16,7 @@ name = "nu_plugin_gstat" bench = false [dependencies] -nu-plugin = { path = "../nu-plugin", version = "0.96.0" } -nu-protocol = { path = "../nu-protocol", version = "0.96.0" } +nu-plugin = { path = "../nu-plugin", version = "0.96.1" } +nu-protocol = { path = "../nu-protocol", version = "0.96.1" } git2 = "0.19" \ No newline at end of file diff --git a/crates/nu_plugin_inc/Cargo.toml b/crates/nu_plugin_inc/Cargo.toml index 4b3f63345b..e82168463c 100644 --- a/crates/nu_plugin_inc/Cargo.toml +++ b/crates/nu_plugin_inc/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu_plugin_inc" edition = "2021" license = "MIT" name = "nu_plugin_inc" -version = "0.96.0" +version = "0.96.1" [lib] doctest = false @@ -16,7 +16,7 @@ name = "nu_plugin_inc" bench = false [dependencies] -nu-plugin = { path = "../nu-plugin", version = "0.96.0" } -nu-protocol = { path = "../nu-protocol", version = "0.96.0", features = ["plugin"] } +nu-plugin = { path = "../nu-plugin", version = "0.96.1" } +nu-protocol = { path = "../nu-protocol", version = "0.96.1", features = ["plugin"] } semver = "1.0" \ No newline at end of file diff --git a/crates/nu_plugin_nu_example/nu_plugin_nu_example.nu b/crates/nu_plugin_nu_example/nu_plugin_nu_example.nu index 75069e27e3..e7c4f77365 100755 --- a/crates/nu_plugin_nu_example/nu_plugin_nu_example.nu +++ b/crates/nu_plugin_nu_example/nu_plugin_nu_example.nu @@ -6,7 +6,7 @@ # it also allows us to test the plugin interface with something manually implemented in a scripting # language without adding any extra dependencies to our tests. -const NUSHELL_VERSION = "0.96.0" +const NUSHELL_VERSION = "0.96.1" const PLUGIN_VERSION = "0.1.0" # bump if you change commands! def main [--stdio] { diff --git a/crates/nu_plugin_polars/Cargo.toml b/crates/nu_plugin_polars/Cargo.toml index 40ce054ecd..ad1181f2fe 100644 --- a/crates/nu_plugin_polars/Cargo.toml +++ b/crates/nu_plugin_polars/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" license = "MIT" name = "nu_plugin_polars" repository = "https://github.com/nushell/nushell/tree/main/crates/nu_plugin_polars" -version = "0.96.0" +version = "0.96.1" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -17,10 +17,10 @@ bench = false bench = false [dependencies] -nu-protocol = { path = "../nu-protocol", version = "0.96.0" } -nu-plugin = { path = "../nu-plugin", version = "0.96.0" } -nu-path = { path = "../nu-path", version = "0.96.0" } -nu-utils = { path = "../nu-utils", version = "0.96.0" } +nu-protocol = { path = "../nu-protocol", version = "0.96.1" } +nu-plugin = { path = "../nu-plugin", version = "0.96.1" } +nu-path = { path = "../nu-path", version = "0.96.1" } +nu-utils = { path = "../nu-utils", version = "0.96.1" } # Potential dependencies for extras chrono = { workspace = true, features = ["std", "unstable-locales"], default-features = false } @@ -76,9 +76,9 @@ optional = false version = "0.41" [dev-dependencies] -nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.96.0" } -nu-engine = { path = "../nu-engine", version = "0.96.0" } -nu-parser = { path = "../nu-parser", version = "0.96.0" } -nu-command = { path = "../nu-command", version = "0.96.0" } -nu-plugin-test-support = { path = "../nu-plugin-test-support", version = "0.96.0" } +nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.96.1" } +nu-engine = { path = "../nu-engine", version = "0.96.1" } +nu-parser = { path = "../nu-parser", version = "0.96.1" } +nu-command = { path = "../nu-command", version = "0.96.1" } +nu-plugin-test-support = { path = "../nu-plugin-test-support", version = "0.96.1" } tempfile.workspace = true \ No newline at end of file diff --git a/crates/nu_plugin_python/nu_plugin_python_example.py b/crates/nu_plugin_python/nu_plugin_python_example.py index ad128704f6..c0c530219f 100755 --- a/crates/nu_plugin_python/nu_plugin_python_example.py +++ b/crates/nu_plugin_python/nu_plugin_python_example.py @@ -27,7 +27,7 @@ import sys import json -NUSHELL_VERSION = "0.96.0" +NUSHELL_VERSION = "0.96.1" PLUGIN_VERSION = "0.1.0" # bump if you change commands! diff --git a/crates/nu_plugin_query/Cargo.toml b/crates/nu_plugin_query/Cargo.toml index 29e99b28f8..bb7e0f6bec 100644 --- a/crates/nu_plugin_query/Cargo.toml +++ b/crates/nu_plugin_query/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu_plugin_quer edition = "2021" license = "MIT" name = "nu_plugin_query" -version = "0.96.0" +version = "0.96.1" [lib] doctest = false @@ -16,8 +16,8 @@ name = "nu_plugin_query" bench = false [dependencies] -nu-plugin = { path = "../nu-plugin", version = "0.96.0" } -nu-protocol = { path = "../nu-protocol", version = "0.96.0" } +nu-plugin = { path = "../nu-plugin", version = "0.96.1" } +nu-protocol = { path = "../nu-protocol", version = "0.96.1" } gjson = "0.8" scraper = { default-features = false, version = "0.19" } diff --git a/crates/nu_plugin_stress_internals/Cargo.toml b/crates/nu_plugin_stress_internals/Cargo.toml index 9be71d55ca..872623f4ba 100644 --- a/crates/nu_plugin_stress_internals/Cargo.toml +++ b/crates/nu_plugin_stress_internals/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu_plugin_stre edition = "2021" license = "MIT" name = "nu_plugin_stress_internals" -version = "0.96.0" +version = "0.96.1" [[bin]] name = "nu_plugin_stress_internals" diff --git a/crates/nuon/Cargo.toml b/crates/nuon/Cargo.toml index 33dd5e6b7d..b1ec4a2a69 100644 --- a/crates/nuon/Cargo.toml +++ b/crates/nuon/Cargo.toml @@ -5,14 +5,14 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nuon" edition = "2021" license = "MIT" name = "nuon" -version = "0.96.0" +version = "0.96.1" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -nu-parser = { path = "../nu-parser", version = "0.96.0" } -nu-protocol = { path = "../nu-protocol", version = "0.96.0" } -nu-engine = { path = "../nu-engine", version = "0.96.0" } +nu-parser = { path = "../nu-parser", version = "0.96.1" } +nu-protocol = { path = "../nu-protocol", version = "0.96.1" } +nu-engine = { path = "../nu-engine", version = "0.96.1" } once_cell = { workspace = true } fancy-regex = { workspace = true } From 6446f26283e574898d90da4a91f77cc0ea210de9 Mon Sep 17 00:00:00 2001 From: Devyn Cairns Date: Thu, 25 Jul 2024 03:28:44 -0700 Subject: [PATCH 075/126] Fix `$in` in range expressions (#13447) # Description Fixes #13441. I must have forgotten that `Expr::Range` can contain other expressions, so I wasn't searching for `$in` to replace within it. Easy fix. # User-Facing Changes Bug fix, ranges like `6 | 3..$in` work as expected now. # Tests + Formatting Added regression test. --- crates/nu-protocol/src/ast/expression.rs | 12 +++++++++++- tests/repl/test_engine.rs | 10 ++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/crates/nu-protocol/src/ast/expression.rs b/crates/nu-protocol/src/ast/expression.rs index 4818aa2db2..faf47d42ec 100644 --- a/crates/nu-protocol/src/ast/expression.rs +++ b/crates/nu-protocol/src/ast/expression.rs @@ -432,7 +432,17 @@ impl Expression { Expr::Int(_) => {} Expr::Float(_) => {} Expr::Binary(_) => {} - Expr::Range(_) => {} + Expr::Range(range) => { + if let Some(from) = &mut range.from { + from.replace_in_variable(working_set, new_var_id); + } + if let Some(next) = &mut range.next { + next.replace_in_variable(working_set, new_var_id); + } + if let Some(to) = &mut range.to { + to.replace_in_variable(working_set, new_var_id); + } + } Expr::Var(var_id) | Expr::VarDecl(var_id) => { if *var_id == IN_VARIABLE_ID { *var_id = new_var_id; diff --git a/tests/repl/test_engine.rs b/tests/repl/test_engine.rs index 1b74e741f7..90dd1e86b0 100644 --- a/tests/repl/test_engine.rs +++ b/tests/repl/test_engine.rs @@ -75,6 +75,16 @@ fn in_used_twice_and_also_in_pipeline() -> TestResult { ) } +// #13441 +#[test] +fn in_used_in_range_from() -> TestResult { + run_test(r#"6 | $in..10 | math sum"#, "40") +} +#[test] +fn in_used_in_range_to() -> TestResult { + run_test(r#"6 | 3..$in | math sum"#, "18") +} + #[test] fn help_works_with_missing_requirements() -> TestResult { run_test(r#"each --help | lines | length"#, "72") From 4a7d4401b86e77170a71325089746c8f3d65c81f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 25 Jul 2024 11:35:19 +0000 Subject: [PATCH 076/126] Bump openssl from 0.10.64 to 0.10.66 (#13426) --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5a8de1b4ac..bf665fc8b9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3787,9 +3787,9 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.64" +version = "0.10.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" +checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" dependencies = [ "bitflags 2.5.0", "cfg-if", @@ -3828,9 +3828,9 @@ dependencies = [ [[package]] name = "openssl-sys" -version = "0.9.102" +version = "0.9.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" +checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" dependencies = [ "cc", "libc", From e2d0514bb57ae0b071631d880acbcb695858f67c Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Thu, 25 Jul 2024 08:50:59 -0500 Subject: [PATCH 077/126] update release-pkg.nu with working url for less license (#13451) # Description When running a wix/msi build, I ran into a problem where the less license would not download. The fix for that is in this PR along with some further comments. # User-Facing Changes # Tests + Formatting # After Submitting --- .github/workflows/release-pkg.nu | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release-pkg.nu b/.github/workflows/release-pkg.nu index da2779f6d1..562f42b4a4 100755 --- a/.github/workflows/release-pkg.nu +++ b/.github/workflows/release-pkg.nu @@ -161,8 +161,12 @@ if $os in ['macos-latest'] or $USE_UBUNTU { let releaseStem = $'($bin)-($version)-($target)' print $'(char nl)Download less related stuffs...'; hr-line + # todo: less-v661 is out but is released as a zip file. maybe we should switch to that and extract it? aria2c https://github.com/jftuga/less-Windows/releases/download/less-v608/less.exe -o less.exe - aria2c https://raw.githubusercontent.com/jftuga/less-Windows/master/LICENSE -o LICENSE-for-less.txt + # the below was renamed because it was failing to download for darren. it should work but it wasn't + # todo: maybe we should get rid of this aria2c dependency and just use http get? + #aria2c https://raw.githubusercontent.com/jftuga/less-Windows/master/LICENSE -o LICENSE-for-less.txt + aria2c https://github.com/jftuga/less-Windows/blob/master/LICENSE -o LICENSE-for-less.txt # Create Windows msi release package if (get-env _EXTRA_) == 'msi' { From e68f744dda0a13c496a5de0a52716709a630a402 Mon Sep 17 00:00:00 2001 From: NotTheDr01ds <32344964+NotTheDr01ds@users.noreply.github.com> Date: Thu, 25 Jul 2024 16:01:46 -0400 Subject: [PATCH 078/126] Update query-web example to use new chunks (#13429) # Description A `query web` example uses the (soon to be deprecated) `group` command. Updated it to use `chunks` replacement. --- crates/nu_plugin_query/src/query_web.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/nu_plugin_query/src/query_web.rs b/crates/nu_plugin_query/src/query_web.rs index 39f50e51af..2d8b9e4aac 100644 --- a/crates/nu_plugin_query/src/query_web.rs +++ b/crates/nu_plugin_query/src/query_web.rs @@ -81,7 +81,7 @@ pub fn web_examples() -> Vec> { result: None }, Example { - example: "http get https://www.nushell.sh | query web --query 'h2, h2 + p' | each {str join} | group 2 | each {rotate --ccw tagline description} | flatten", + example: "http get https://www.nushell.sh | query web --query 'h2, h2 + p' | each {str join} | chunks 2 | each {rotate --ccw tagline description} | flatten", description: "Pass multiple css selectors to extract several elements within single query, group the query results together and rotate them to create a table", result: None, }, From 53fbf624934916a9527737305d410794d1472a48 Mon Sep 17 00:00:00 2001 From: Devyn Cairns Date: Fri, 26 Jul 2024 01:03:05 -0700 Subject: [PATCH 079/126] Fix `keybindings list` being empty by default (#13456) # Description Made a mistake when fixing this for IR. The default behavior with no options set is to list everything. Restored that. This should go in the 0.96.1 patch release. # Tests + Formatting Added regression test. --- crates/nu-cli/src/commands/keybindings_list.rs | 4 +++- crates/nu-cli/tests/commands/keybindings_list.rs | 7 +++++++ crates/nu-cli/tests/commands/mod.rs | 1 + 3 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 crates/nu-cli/tests/commands/keybindings_list.rs diff --git a/crates/nu-cli/src/commands/keybindings_list.rs b/crates/nu-cli/src/commands/keybindings_list.rs index 350df7b820..abb5909a97 100644 --- a/crates/nu-cli/src/commands/keybindings_list.rs +++ b/crates/nu-cli/src/commands/keybindings_list.rs @@ -61,10 +61,12 @@ impl Command for KeybindingsList { .map(|option| call.has_flag(engine_state, stack, option)) .collect::, ShellError>>()?; + let no_option_specified = presence.iter().all(|present| !*present); + let records = all_options .iter() .zip(presence) - .filter(|(_, present)| *present) + .filter(|(_, present)| no_option_specified || *present) .flat_map(|(option, _)| get_records(option, call.head)) .collect(); diff --git a/crates/nu-cli/tests/commands/keybindings_list.rs b/crates/nu-cli/tests/commands/keybindings_list.rs new file mode 100644 index 0000000000..4ab9f3bacf --- /dev/null +++ b/crates/nu-cli/tests/commands/keybindings_list.rs @@ -0,0 +1,7 @@ +use nu_test_support::nu; + +#[test] +fn not_empty() { + let result = nu!("keybindings list | is-not-empty"); + assert_eq!(result.out, "true"); +} diff --git a/crates/nu-cli/tests/commands/mod.rs b/crates/nu-cli/tests/commands/mod.rs index 00488f0b9e..087791302e 100644 --- a/crates/nu-cli/tests/commands/mod.rs +++ b/crates/nu-cli/tests/commands/mod.rs @@ -1 +1,2 @@ +mod keybindings_list; mod nu_highlight; From 5f7afafe51727b08996b27d1b9eb68e82bc019b4 Mon Sep 17 00:00:00 2001 From: Devyn Cairns Date: Sat, 27 Jul 2024 19:38:57 -0700 Subject: [PATCH 080/126] IR: fix incorrect capturing of subexpressions (#13467) # Description [Discovered](https://discord.com/channels/601130461678272522/614593951969574961/1266503282554179604) by `@warp` on Discord. The IR compiler was not properly setting redirect modes for subexpressions because `FullCellPath` was always being compiled with capture-out redirection. This is the correct behavior if there is a tail to the `FullCellPath`, as we need the value in order to try to extract anything from it (although this is unlikely to work) - however, the parser also generates `FullCellPath`s with an empty tail quite often, including for bare subexpressions. Because of this, the following did not behave as expected: ```nushell (docker run -it --rm alpine) ``` Capturing the output meant that `docker` didn't have direct access to the terminal as a TTY. As this is a minor bug fix, it should be okay to include in the 0.96.1 patch release. # User-Facing Changes - Fixes the bug as described when running with IR evaluation enabled. # Tests + Formatting I added a test for this, though we're not currently running all tests with IR on the CI, but it should ensure this behaviour is consistent. The equivalent minimum repro I could find was: ```nushell (nu --testbin cococo); null ``` as this should cause the `cococo` message to appear on stdout, and if Nushell is capturing the output, it would be discarded instead. --- crates/nu-engine/src/compile/expression.rs | 10 +++++++++- tests/shell/pipeline/commands/external.rs | 6 ++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/crates/nu-engine/src/compile/expression.rs b/crates/nu-engine/src/compile/expression.rs index 948eb5c238..80fc4fb3fb 100644 --- a/crates/nu-engine/src/compile/expression.rs +++ b/crates/nu-engine/src/compile/expression.rs @@ -444,7 +444,15 @@ pub(crate) fn compile_expression( working_set, builder, &full_cell_path.head, - RedirectModes::capture_out(expr.span), + // Only capture the output if there is a tail. This was a bit of a headscratcher + // as the parser emits a FullCellPath with no tail for subexpressions in + // general, which shouldn't be captured any differently than they otherwise + // would be. + if !full_cell_path.tail.is_empty() { + RedirectModes::capture_out(expr.span) + } else { + redirect_modes + }, in_reg, out_reg, )?; diff --git a/tests/shell/pipeline/commands/external.rs b/tests/shell/pipeline/commands/external.rs index a6efe2b6c9..ff851a3293 100644 --- a/tests/shell/pipeline/commands/external.rs +++ b/tests/shell/pipeline/commands/external.rs @@ -209,6 +209,12 @@ fn run_glob_if_pass_variable_to_external() { }) } +#[test] +fn subexpression_does_not_implicitly_capture() { + let actual = nu!("(nu --testbin cococo); null"); + assert_eq!(actual.out, "cococo"); +} + mod it_evaluation { use super::nu; use nu_test_support::fs::Stub::{EmptyFile, FileWithContent, FileWithContentToBeTrimmed}; From d80de68665f9c779e17a113bbe2be271c94bc92b Mon Sep 17 00:00:00 2001 From: Devyn Cairns Date: Sat, 27 Jul 2024 19:39:17 -0700 Subject: [PATCH 081/126] Clean up arguments added to stack after `CallDecl` engine call (#13469) # Description Just realized I hadn't been cleaning up the arguments added to the `Stack` after the `CallDecl` engine call was finished, so there could be a bit of a memory leak if a plugin made many calls during the duration of a single plugin call. This is a quick patch to that. I'm probably going to revise how this all works at some point soon because I think it is a bit of a pitfall. It would be good to make it much more difficult to make a mistake with it, perhaps with a guard like Ian did for the redirection stuff. # After Submitting - [ ] release with 0.96.1 --- crates/nu-plugin-engine/src/context.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/crates/nu-plugin-engine/src/context.rs b/crates/nu-plugin-engine/src/context.rs index d9023cde35..160ce802b2 100644 --- a/crates/nu-plugin-engine/src/context.rs +++ b/crates/nu-plugin-engine/src/context.rs @@ -257,12 +257,9 @@ impl<'a> PluginExecutionContext for PluginExecutionCommandContext<'a> { } } - decl.run( - &self.engine_state, - stack, - &(&call_builder.finish()).into(), - input, - ) + call_builder.with(stack, |stack, call| { + decl.run(&self.engine_state, stack, call, input) + }) } fn boxed(&self) -> Box { From d618fd0527c589ca66ea4c8af813e2bbca32ee88 Mon Sep 17 00:00:00 2001 From: Devyn Cairns Date: Sat, 27 Jul 2024 19:39:29 -0700 Subject: [PATCH 082/126] Fix bad method links in docstrings (#13471) # Description Seems like I developed a bit of a bad habit of trying to link ```rust /// [`.foo()`] ``` in docstrings, and this just doesn't work automatically; you have to do ```rust /// [`.foo()`](Self::foo) ``` if you want it to actually link. I think I found and replaced all of these. # User-Facing Changes Just docs. --- crates/nu-engine/src/compile/builder.rs | 2 +- crates/nu-plugin-core/src/communication_mode/mod.rs | 4 ++-- crates/nu-plugin-core/src/interface/mod.rs | 2 +- crates/nu-plugin-core/src/interface/stream/mod.rs | 10 +++++----- crates/nu-plugin-core/src/util/waitable.rs | 2 +- crates/nu-plugin-engine/src/gc.rs | 4 ++-- crates/nu-plugin-engine/src/source.rs | 2 +- crates/nu-plugin-protocol/src/evaluated_call.rs | 6 +++--- crates/nu-plugin/src/plugin/command.rs | 10 +++++----- crates/nu-plugin/src/plugin/interface/mod.rs | 13 +++++++------ crates/nu-protocol/src/engine/engine_state.rs | 6 +++--- crates/nu-protocol/src/engine/stack.rs | 2 +- crates/nu-protocol/src/errors/labeled_error.rs | 3 ++- crates/nu-protocol/src/pipeline/pipeline_data.rs | 3 ++- crates/nu-system/src/foreground.rs | 8 ++++---- 15 files changed, 40 insertions(+), 37 deletions(-) diff --git a/crates/nu-engine/src/compile/builder.rs b/crates/nu-engine/src/compile/builder.rs index 4294b2bd61..2a443b7bde 100644 --- a/crates/nu-engine/src/compile/builder.rs +++ b/crates/nu-engine/src/compile/builder.rs @@ -423,7 +423,7 @@ impl BlockBuilder { self.push(Instruction::Jump { index: label_id.0 }.into_spanned(span)) } - /// The index that the next instruction [`.push()`]ed will have. + /// The index that the next instruction [`.push()`](Self::push)ed will have. pub(crate) fn here(&self) -> usize { self.instructions.len() } diff --git a/crates/nu-plugin-core/src/communication_mode/mod.rs b/crates/nu-plugin-core/src/communication_mode/mod.rs index 1eb72d6ac8..576cfe866c 100644 --- a/crates/nu-plugin-core/src/communication_mode/mod.rs +++ b/crates/nu-plugin-core/src/communication_mode/mod.rs @@ -122,11 +122,11 @@ impl CommunicationMode { /// The result of [`CommunicationMode::serve()`], which acts as an intermediate stage for /// communication modes that require some kind of socket binding to occur before the client process -/// can be started. Call [`.connect()`] once the client process has been started. +/// can be started. Call [`.connect()`](Self::connect) once the client process has been started. /// /// The socket may be cleaned up on `Drop` if applicable. pub enum PreparedServerCommunication { - /// Will take stdin and stdout from the process on [`.connect()`]. + /// Will take stdin and stdout from the process on [`.connect()`](Self::connect). Stdio, /// Contains the listener to accept connections on. On Unix, the socket is unlinked on `Drop`. #[cfg(feature = "local-socket")] diff --git a/crates/nu-plugin-core/src/interface/mod.rs b/crates/nu-plugin-core/src/interface/mod.rs index 89aa2b15e5..d282eddd8b 100644 --- a/crates/nu-plugin-core/src/interface/mod.rs +++ b/crates/nu-plugin-core/src/interface/mod.rs @@ -145,7 +145,7 @@ pub trait InterfaceManager { /// Consume an input message. /// - /// When implementing, call [`.consume_stream_message()`] for any encapsulated + /// When implementing, call [`.consume_stream_message()`](Self::consume_stream_message) for any encapsulated /// [`StreamMessage`]s received. fn consume(&mut self, input: Self::Input) -> Result<(), ShellError>; diff --git a/crates/nu-plugin-core/src/interface/stream/mod.rs b/crates/nu-plugin-core/src/interface/stream/mod.rs index 541e1176a0..cfe44fabb4 100644 --- a/crates/nu-plugin-core/src/interface/stream/mod.rs +++ b/crates/nu-plugin-core/src/interface/stream/mod.rs @@ -189,14 +189,14 @@ where } /// Check if the stream was dropped from the other end. Recommended to do this before calling - /// [`.write()`], especially in a loop. + /// [`.write()`](Self::write), especially in a loop. pub fn is_dropped(&self) -> Result { self.signal.is_dropped() } /// Write a single piece of data to the stream. /// - /// Error if something failed with the write, or if [`.end()`] was already called + /// Error if something failed with the write, or if [`.end()`](Self::end) was already called /// previously. pub fn write(&mut self, data: impl Into) -> Result<(), ShellError> { if !self.ended { @@ -228,7 +228,7 @@ where } /// Write a full iterator to the stream. Note that this doesn't end the stream, so you should - /// still call [`.end()`]. + /// still call [`.end()`](Self::end). /// /// If the stream is dropped from the other end, the iterator will not be fully consumed, and /// writing will terminate. @@ -341,8 +341,8 @@ impl StreamWriterSignal { } /// Track that a message has been sent. Returns `Ok(true)` if more messages can be sent, - /// or `Ok(false)` if the high pressure mark has been reached and [`.wait_for_drain()`] should - /// be called to block. + /// or `Ok(false)` if the high pressure mark has been reached and + /// [`.wait_for_drain()`](Self::wait_for_drain) should be called to block. pub fn notify_sent(&self) -> Result { let mut state = self.lock()?; state.unacknowledged = diff --git a/crates/nu-plugin-core/src/util/waitable.rs b/crates/nu-plugin-core/src/util/waitable.rs index aaefa6f1b5..0f4cb30de3 100644 --- a/crates/nu-plugin-core/src/util/waitable.rs +++ b/crates/nu-plugin-core/src/util/waitable.rs @@ -45,7 +45,7 @@ fn fail_if_poisoned<'a, T>( } impl WaitableMut { - /// Create a new empty `WaitableMut`. Call [`.reader()`] to get [`Waitable`]. + /// Create a new empty `WaitableMut`. Call [`.reader()`](Self::reader) to get [`Waitable`]. pub fn new() -> WaitableMut { WaitableMut { shared: Arc::new(WaitableShared { diff --git a/crates/nu-plugin-engine/src/gc.rs b/crates/nu-plugin-engine/src/gc.rs index bd9c8de06f..555c57e47d 100644 --- a/crates/nu-plugin-engine/src/gc.rs +++ b/crates/nu-plugin-engine/src/gc.rs @@ -80,8 +80,8 @@ impl PluginGc { /// /// The reason the plugin tells the GC rather than just stopping itself via `source` is that /// it can't guarantee that the plugin currently pointed to by `source` is itself, but if the - /// GC is still running, it hasn't received [`.stop_tracking()`] yet, which means it should be - /// the right plugin. + /// GC is still running, it hasn't received [`.stop_tracking()`](Self::stop_tracking) yet, which + /// means it should be the right plugin. pub fn exited(&self) { let _ = self.sender.send(PluginGcMsg::Exited); } diff --git a/crates/nu-plugin-engine/src/source.rs b/crates/nu-plugin-engine/src/source.rs index 9b388576c5..56043eccf5 100644 --- a/crates/nu-plugin-engine/src/source.rs +++ b/crates/nu-plugin-engine/src/source.rs @@ -26,7 +26,7 @@ impl PluginSource { /// Create a new fake source with a fake identity, for testing /// - /// Warning: [`.persistent()`] will always return an error. + /// Warning: [`.persistent()`](Self::persistent) will always return an error. pub fn new_fake(name: &str) -> PluginSource { PluginSource { identity: PluginIdentity::new_fake(name).into(), diff --git a/crates/nu-plugin-protocol/src/evaluated_call.rs b/crates/nu-plugin-protocol/src/evaluated_call.rs index 5e760693a5..c5cbcc5ba0 100644 --- a/crates/nu-plugin-protocol/src/evaluated_call.rs +++ b/crates/nu-plugin-protocol/src/evaluated_call.rs @@ -85,19 +85,19 @@ impl EvaluatedCall { self } - /// Builder variant of [`.add_positional()`]. + /// Builder variant of [`.add_positional()`](Self::add_positional). pub fn with_positional(mut self, value: Value) -> Self { self.add_positional(value); self } - /// Builder variant of [`.add_named()`]. + /// Builder variant of [`.add_named()`](Self::add_named). pub fn with_named(mut self, name: Spanned>, value: Value) -> Self { self.add_named(name, value); self } - /// Builder variant of [`.add_flag()`]. + /// Builder variant of [`.add_flag()`](Self::add_flag). pub fn with_flag(mut self, name: Spanned>) -> Self { self.add_flag(name); self diff --git a/crates/nu-plugin/src/plugin/command.rs b/crates/nu-plugin/src/plugin/command.rs index a90555ac40..8e8adc9578 100644 --- a/crates/nu-plugin/src/plugin/command.rs +++ b/crates/nu-plugin/src/plugin/command.rs @@ -75,8 +75,8 @@ use crate::{EngineInterface, EvaluatedCall, Plugin}; pub trait PluginCommand: Sync { /// The type of plugin this command runs on. /// - /// Since [`.run()`] takes a reference to the plugin, it is necessary to define the type of - /// plugin that the command expects here. + /// Since [`.run()`](Self::run) takes a reference to the plugin, it is necessary to define the + /// type of plugin that the command expects here. type Plugin: Plugin; /// The name of the command from within Nu. @@ -96,9 +96,9 @@ pub trait PluginCommand: Sync { /// Additional documentation for usage of the command. /// - /// This is optional - any arguments documented by [`.signature()`] will be shown in the help - /// page automatically. However, this can be useful for explaining things that would be too - /// brief to include in [`.usage()`] and may span multiple lines. + /// This is optional - any arguments documented by [`.signature()`](Self::signature) will be + /// shown in the help page automatically. However, this can be useful for explaining things that + /// would be too brief to include in [`.usage()`](Self::usage) and may span multiple lines. fn extra_usage(&self) -> &str { "" } diff --git a/crates/nu-plugin/src/plugin/interface/mod.rs b/crates/nu-plugin/src/plugin/interface/mod.rs index c20e2adc39..10f787b222 100644 --- a/crates/nu-plugin/src/plugin/interface/mod.rs +++ b/crates/nu-plugin/src/plugin/interface/mod.rs @@ -618,8 +618,9 @@ impl EngineInterface { /// Get all environment variables from the engine. /// - /// Since this is quite a large map that has to be sent, prefer to use [`.get_env_var()`] if - /// the variables needed are known ahead of time and there are only a small number needed. + /// Since this is quite a large map that has to be sent, prefer to use + /// [`.get_env_var()`] (Self::get_env_var) if the variables needed are known ahead of time + /// and there are only a small number needed. /// /// # Example /// ```rust,no_run @@ -873,9 +874,9 @@ impl EngineInterface { } /// Ask the engine for the identifier for a declaration. If found, the result can then be passed - /// to [`.call_decl()`] to call other internal commands. + /// to [`.call_decl()`](Self::call_decl) to call other internal commands. /// - /// See [`.call_decl()`] for an example. + /// See [`.call_decl()`](Self::call_decl) for an example. pub fn find_decl(&self, name: impl Into) -> Result, ShellError> { let call = EngineCall::FindDecl(name.into()); @@ -890,7 +891,7 @@ impl EngineInterface { } /// Ask the engine to call an internal command, using the declaration ID previously looked up - /// with [`.find_decl()`]. + /// with [`.find_decl()`](Self::find_decl). /// /// # Example /// @@ -1008,7 +1009,7 @@ impl Interface for EngineInterface { /// Keeps the plugin in the foreground as long as it is alive. /// -/// Use [`.leave()`] to leave the foreground without ignoring the error. +/// Use [`.leave()`](Self::leave) to leave the foreground without ignoring the error. pub struct ForegroundGuard(Option); impl ForegroundGuard { diff --git a/crates/nu-protocol/src/engine/engine_state.rs b/crates/nu-protocol/src/engine/engine_state.rs index fce24cebbd..8579295c32 100644 --- a/crates/nu-protocol/src/engine/engine_state.rs +++ b/crates/nu-protocol/src/engine/engine_state.rs @@ -832,9 +832,9 @@ impl EngineState { /// Optionally get a block by id, if it exists /// - /// Prefer to use [`.get_block()`] in most cases - `BlockId`s that don't exist are normally a - /// compiler error. This only exists to stop plugins from crashing the engine if they send us - /// something invalid. + /// Prefer to use [`.get_block()`](Self::get_block) in most cases - `BlockId`s that don't exist + /// are normally a compiler error. This only exists to stop plugins from crashing the engine if + /// they send us something invalid. pub fn try_get_block(&self, block_id: BlockId) -> Option<&Arc> { self.blocks.get(block_id) } diff --git a/crates/nu-protocol/src/engine/stack.rs b/crates/nu-protocol/src/engine/stack.rs index fbfb586762..7ba8268985 100644 --- a/crates/nu-protocol/src/engine/stack.rs +++ b/crates/nu-protocol/src/engine/stack.rs @@ -51,7 +51,7 @@ pub struct Stack { pub parent_stack: Option>, /// Variables that have been deleted (this is used to hide values from parent stack lookups) pub parent_deletions: Vec, - /// Locally updated config. Use [`.get_config()`] to access correctly. + /// Locally updated config. Use [`.get_config()`](Self::get_config) to access correctly. pub config: Option>, pub(crate) out_dest: StackOutDest, } diff --git a/crates/nu-protocol/src/errors/labeled_error.rs b/crates/nu-protocol/src/errors/labeled_error.rs index 20ac9cff71..c76a6c78dd 100644 --- a/crates/nu-protocol/src/errors/labeled_error.rs +++ b/crates/nu-protocol/src/errors/labeled_error.rs @@ -37,7 +37,8 @@ pub struct LabeledError { impl LabeledError { /// Create a new plain [`LabeledError`] with the given message. /// - /// This is usually used builder-style with methods like [`.with_label()`] to build an error. + /// This is usually used builder-style with methods like [`.with_label()`](Self::with_label) to + /// build an error. /// /// # Example /// diff --git a/crates/nu-protocol/src/pipeline/pipeline_data.rs b/crates/nu-protocol/src/pipeline/pipeline_data.rs index 0d1c0b3092..2ae8027f06 100644 --- a/crates/nu-protocol/src/pipeline/pipeline_data.rs +++ b/crates/nu-protocol/src/pipeline/pipeline_data.rs @@ -117,7 +117,8 @@ impl PipelineData { /// Get a type that is representative of the `PipelineData`. /// /// The type returned here makes no effort to collect a stream, so it may be a different type - /// than would be returned by [`Value::get_type()`] on the result of [`.into_value()`]. + /// 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 /// the fully complete [`list`](Type::List) type (which would require knowing the contents), diff --git a/crates/nu-system/src/foreground.rs b/crates/nu-system/src/foreground.rs index 2fe3c4fb29..bd78f616aa 100644 --- a/crates/nu-system/src/foreground.rs +++ b/crates/nu-system/src/foreground.rs @@ -105,10 +105,10 @@ impl Drop for ForegroundChild { /// /// If there is already a foreground external process running, spawned with [`ForegroundChild`], /// this expects the process ID to remain in the process group created by the [`ForegroundChild`] -/// for the lifetime of the guard, and keeps the terminal controlling process group set to that. If -/// there is no foreground external process running, this sets the foreground process group to the -/// plugin's process ID. The process group that is expected can be retrieved with [`.pgrp()`] if -/// different from the plugin process ID. +/// for the lifetime of the guard, and keeps the terminal controlling process group set to that. +/// If there is no foreground external process running, this sets the foreground process group to +/// the plugin's process ID. The process group that is expected can be retrieved with +/// [`.pgrp()`](Self::pgrp) if different from the plugin process ID. /// /// ## Other systems /// From f7d6c28a001f94f224e459e21df0da2ab5bdc923 Mon Sep 17 00:00:00 2001 From: Devyn Cairns Date: Mon, 29 Jul 2024 16:31:36 -0700 Subject: [PATCH 083/126] Release 0.96.1 From c31291753c0ab5383edcd2d3f93bc7d9eab69658 Mon Sep 17 00:00:00 2001 From: Devyn Cairns Date: Mon, 29 Jul 2024 17:20:55 -0700 Subject: [PATCH 084/126] Bump version to `0.96.2` (#13485) This should be the new development version. We most likely don't need a 0.96.2 patch release. Should be free to merge PRs after this. --- Cargo.lock | 74 +++++++++---------- Cargo.toml | 40 +++++----- crates/nu-cli/Cargo.toml | 24 +++--- crates/nu-cmd-base/Cargo.toml | 10 +-- crates/nu-cmd-extra/Cargo.toml | 22 +++--- crates/nu-cmd-lang/Cargo.toml | 10 +-- crates/nu-cmd-plugin/Cargo.toml | 10 +-- crates/nu-color-config/Cargo.toml | 10 +-- crates/nu-command/Cargo.toml | 34 ++++----- crates/nu-derive-value/Cargo.toml | 2 +- crates/nu-engine/Cargo.toml | 10 +-- crates/nu-explore/Cargo.toml | 18 ++--- crates/nu-glob/Cargo.toml | 2 +- crates/nu-json/Cargo.toml | 6 +- crates/nu-lsp/Cargo.toml | 14 ++-- crates/nu-parser/Cargo.toml | 10 +-- crates/nu-path/Cargo.toml | 2 +- crates/nu-plugin-core/Cargo.toml | 6 +- crates/nu-plugin-engine/Cargo.toml | 14 ++-- crates/nu-plugin-protocol/Cargo.toml | 6 +- crates/nu-plugin-test-support/Cargo.toml | 18 ++--- crates/nu-plugin/Cargo.toml | 12 +-- crates/nu-pretty-hex/Cargo.toml | 2 +- crates/nu-protocol/Cargo.toml | 12 +-- crates/nu-std/Cargo.toml | 8 +- crates/nu-system/Cargo.toml | 2 +- crates/nu-table/Cargo.toml | 12 +-- crates/nu-term-grid/Cargo.toml | 4 +- crates/nu-test-support/Cargo.toml | 8 +- crates/nu-utils/Cargo.toml | 2 +- .../src/sample_config/default_config.nu | 2 +- .../nu-utils/src/sample_config/default_env.nu | 2 +- crates/nu_plugin_custom_values/Cargo.toml | 6 +- crates/nu_plugin_example/Cargo.toml | 10 +-- crates/nu_plugin_formats/Cargo.toml | 8 +- crates/nu_plugin_gstat/Cargo.toml | 6 +- crates/nu_plugin_inc/Cargo.toml | 6 +- .../nu_plugin_nu_example.nu | 2 +- crates/nu_plugin_polars/Cargo.toml | 20 ++--- .../nu_plugin_python_example.py | 2 +- crates/nu_plugin_query/Cargo.toml | 6 +- crates/nu_plugin_stress_internals/Cargo.toml | 2 +- crates/nuon/Cargo.toml | 8 +- 43 files changed, 242 insertions(+), 242 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bf665fc8b9..576cb960a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2868,7 +2868,7 @@ dependencies = [ [[package]] name = "nu" -version = "0.96.1" +version = "0.96.2" dependencies = [ "assert_cmd", "crossterm", @@ -2922,7 +2922,7 @@ dependencies = [ [[package]] name = "nu-cli" -version = "0.96.1" +version = "0.96.2" dependencies = [ "chrono", "crossterm", @@ -2957,7 +2957,7 @@ dependencies = [ [[package]] name = "nu-cmd-base" -version = "0.96.1" +version = "0.96.2" dependencies = [ "indexmap", "miette", @@ -2969,7 +2969,7 @@ dependencies = [ [[package]] name = "nu-cmd-extra" -version = "0.96.1" +version = "0.96.2" dependencies = [ "fancy-regex", "heck 0.5.0", @@ -2994,7 +2994,7 @@ dependencies = [ [[package]] name = "nu-cmd-lang" -version = "0.96.1" +version = "0.96.2" dependencies = [ "itertools 0.12.1", "nu-engine", @@ -3006,7 +3006,7 @@ dependencies = [ [[package]] name = "nu-cmd-plugin" -version = "0.96.1" +version = "0.96.2" dependencies = [ "itertools 0.12.1", "nu-engine", @@ -3017,7 +3017,7 @@ dependencies = [ [[package]] name = "nu-color-config" -version = "0.96.1" +version = "0.96.2" dependencies = [ "nu-ansi-term", "nu-engine", @@ -3029,7 +3029,7 @@ dependencies = [ [[package]] name = "nu-command" -version = "0.96.1" +version = "0.96.2" dependencies = [ "alphanumeric-sort", "base64 0.22.1", @@ -3139,7 +3139,7 @@ dependencies = [ [[package]] name = "nu-derive-value" -version = "0.96.1" +version = "0.96.2" dependencies = [ "convert_case", "proc-macro-error", @@ -3150,7 +3150,7 @@ dependencies = [ [[package]] name = "nu-engine" -version = "0.96.1" +version = "0.96.2" dependencies = [ "log", "nu-glob", @@ -3161,7 +3161,7 @@ dependencies = [ [[package]] name = "nu-explore" -version = "0.96.1" +version = "0.96.2" dependencies = [ "ansi-str", "anyhow", @@ -3186,14 +3186,14 @@ dependencies = [ [[package]] name = "nu-glob" -version = "0.96.1" +version = "0.96.2" dependencies = [ "doc-comment", ] [[package]] name = "nu-json" -version = "0.96.1" +version = "0.96.2" dependencies = [ "fancy-regex", "linked-hash-map", @@ -3206,7 +3206,7 @@ dependencies = [ [[package]] name = "nu-lsp" -version = "0.96.1" +version = "0.96.2" dependencies = [ "assert-json-diff", "crossbeam-channel", @@ -3227,7 +3227,7 @@ dependencies = [ [[package]] name = "nu-parser" -version = "0.96.1" +version = "0.96.2" dependencies = [ "bytesize", "chrono", @@ -3243,7 +3243,7 @@ dependencies = [ [[package]] name = "nu-path" -version = "0.96.1" +version = "0.96.2" dependencies = [ "dirs", "omnipath", @@ -3252,7 +3252,7 @@ dependencies = [ [[package]] name = "nu-plugin" -version = "0.96.1" +version = "0.96.2" dependencies = [ "log", "nix", @@ -3268,7 +3268,7 @@ dependencies = [ [[package]] name = "nu-plugin-core" -version = "0.96.1" +version = "0.96.2" dependencies = [ "interprocess", "log", @@ -3282,7 +3282,7 @@ dependencies = [ [[package]] name = "nu-plugin-engine" -version = "0.96.1" +version = "0.96.2" dependencies = [ "log", "nu-engine", @@ -3298,7 +3298,7 @@ dependencies = [ [[package]] name = "nu-plugin-protocol" -version = "0.96.1" +version = "0.96.2" dependencies = [ "bincode", "nu-protocol", @@ -3310,7 +3310,7 @@ dependencies = [ [[package]] name = "nu-plugin-test-support" -version = "0.96.1" +version = "0.96.2" dependencies = [ "nu-ansi-term", "nu-cmd-lang", @@ -3328,7 +3328,7 @@ dependencies = [ [[package]] name = "nu-pretty-hex" -version = "0.96.1" +version = "0.96.2" dependencies = [ "heapless", "nu-ansi-term", @@ -3337,7 +3337,7 @@ dependencies = [ [[package]] name = "nu-protocol" -version = "0.96.1" +version = "0.96.2" dependencies = [ "brotli", "byte-unit", @@ -3374,7 +3374,7 @@ dependencies = [ [[package]] name = "nu-std" -version = "0.96.1" +version = "0.96.2" dependencies = [ "log", "miette", @@ -3385,7 +3385,7 @@ dependencies = [ [[package]] name = "nu-system" -version = "0.96.1" +version = "0.96.2" dependencies = [ "chrono", "itertools 0.12.1", @@ -3403,7 +3403,7 @@ dependencies = [ [[package]] name = "nu-table" -version = "0.96.1" +version = "0.96.2" dependencies = [ "fancy-regex", "nu-ansi-term", @@ -3417,7 +3417,7 @@ dependencies = [ [[package]] name = "nu-term-grid" -version = "0.96.1" +version = "0.96.2" dependencies = [ "nu-utils", "unicode-width", @@ -3425,7 +3425,7 @@ dependencies = [ [[package]] name = "nu-test-support" -version = "0.96.1" +version = "0.96.2" dependencies = [ "nu-glob", "nu-path", @@ -3437,7 +3437,7 @@ dependencies = [ [[package]] name = "nu-utils" -version = "0.96.1" +version = "0.96.2" dependencies = [ "crossterm_winapi", "log", @@ -3463,7 +3463,7 @@ dependencies = [ [[package]] name = "nu_plugin_example" -version = "0.96.1" +version = "0.96.2" dependencies = [ "nu-cmd-lang", "nu-plugin", @@ -3473,7 +3473,7 @@ dependencies = [ [[package]] name = "nu_plugin_formats" -version = "0.96.1" +version = "0.96.2" dependencies = [ "eml-parser", "ical", @@ -3486,7 +3486,7 @@ dependencies = [ [[package]] name = "nu_plugin_gstat" -version = "0.96.1" +version = "0.96.2" dependencies = [ "git2", "nu-plugin", @@ -3495,7 +3495,7 @@ dependencies = [ [[package]] name = "nu_plugin_inc" -version = "0.96.1" +version = "0.96.2" dependencies = [ "nu-plugin", "nu-protocol", @@ -3504,7 +3504,7 @@ dependencies = [ [[package]] name = "nu_plugin_polars" -version = "0.96.1" +version = "0.96.2" dependencies = [ "chrono", "chrono-tz 0.9.0", @@ -3538,7 +3538,7 @@ dependencies = [ [[package]] name = "nu_plugin_query" -version = "0.96.1" +version = "0.96.2" dependencies = [ "gjson", "nu-plugin", @@ -3553,7 +3553,7 @@ dependencies = [ [[package]] name = "nu_plugin_stress_internals" -version = "0.96.1" +version = "0.96.2" dependencies = [ "interprocess", "serde", @@ -3679,7 +3679,7 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] name = "nuon" -version = "0.96.1" +version = "0.96.2" dependencies = [ "chrono", "fancy-regex", diff --git a/Cargo.toml b/Cargo.toml index 3d8e3a9385..ce3624d873 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT" name = "nu" repository = "https://github.com/nushell/nushell" rust-version = "1.77.2" -version = "0.96.1" +version = "0.96.2" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -183,22 +183,22 @@ windows-sys = "0.48" winreg = "0.52" [dependencies] -nu-cli = { path = "./crates/nu-cli", version = "0.96.1" } -nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.96.1" } -nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.96.1" } -nu-cmd-plugin = { path = "./crates/nu-cmd-plugin", version = "0.96.1", optional = true } -nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.96.1" } -nu-command = { path = "./crates/nu-command", version = "0.96.1" } -nu-engine = { path = "./crates/nu-engine", version = "0.96.1" } -nu-explore = { path = "./crates/nu-explore", version = "0.96.1" } -nu-lsp = { path = "./crates/nu-lsp/", version = "0.96.1" } -nu-parser = { path = "./crates/nu-parser", version = "0.96.1" } -nu-path = { path = "./crates/nu-path", version = "0.96.1" } -nu-plugin-engine = { path = "./crates/nu-plugin-engine", optional = true, version = "0.96.1" } -nu-protocol = { path = "./crates/nu-protocol", version = "0.96.1" } -nu-std = { path = "./crates/nu-std", version = "0.96.1" } -nu-system = { path = "./crates/nu-system", version = "0.96.1" } -nu-utils = { path = "./crates/nu-utils", version = "0.96.1" } +nu-cli = { path = "./crates/nu-cli", version = "0.96.2" } +nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.96.2" } +nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.96.2" } +nu-cmd-plugin = { path = "./crates/nu-cmd-plugin", version = "0.96.2", optional = true } +nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.96.2" } +nu-command = { path = "./crates/nu-command", version = "0.96.2" } +nu-engine = { path = "./crates/nu-engine", version = "0.96.2" } +nu-explore = { path = "./crates/nu-explore", version = "0.96.2" } +nu-lsp = { path = "./crates/nu-lsp/", version = "0.96.2" } +nu-parser = { path = "./crates/nu-parser", version = "0.96.2" } +nu-path = { path = "./crates/nu-path", version = "0.96.2" } +nu-plugin-engine = { path = "./crates/nu-plugin-engine", optional = true, version = "0.96.2" } +nu-protocol = { path = "./crates/nu-protocol", version = "0.96.2" } +nu-std = { path = "./crates/nu-std", version = "0.96.2" } +nu-system = { path = "./crates/nu-system", version = "0.96.2" } +nu-utils = { path = "./crates/nu-utils", version = "0.96.2" } reedline = { workspace = true, features = ["bashisms", "sqlite"] } crossterm = { workspace = true } @@ -227,9 +227,9 @@ nix = { workspace = true, default-features = false, features = [ ] } [dev-dependencies] -nu-test-support = { path = "./crates/nu-test-support", version = "0.96.1" } -nu-plugin-protocol = { path = "./crates/nu-plugin-protocol", version = "0.96.1" } -nu-plugin-core = { path = "./crates/nu-plugin-core", version = "0.96.1" } +nu-test-support = { path = "./crates/nu-test-support", version = "0.96.2" } +nu-plugin-protocol = { path = "./crates/nu-plugin-protocol", version = "0.96.2" } +nu-plugin-core = { path = "./crates/nu-plugin-core", version = "0.96.2" } assert_cmd = "2.0" dirs = { workspace = true } tango-bench = "0.5" diff --git a/crates/nu-cli/Cargo.toml b/crates/nu-cli/Cargo.toml index 1be1470daf..54af510916 100644 --- a/crates/nu-cli/Cargo.toml +++ b/crates/nu-cli/Cargo.toml @@ -5,27 +5,27 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cli" edition = "2021" license = "MIT" name = "nu-cli" -version = "0.96.1" +version = "0.96.2" [lib] bench = false [dev-dependencies] -nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.96.1" } -nu-command = { path = "../nu-command", version = "0.96.1" } -nu-test-support = { path = "../nu-test-support", version = "0.96.1" } +nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.96.2" } +nu-command = { path = "../nu-command", version = "0.96.2" } +nu-test-support = { path = "../nu-test-support", version = "0.96.2" } rstest = { workspace = true, default-features = false } tempfile = { workspace = true } [dependencies] -nu-cmd-base = { path = "../nu-cmd-base", version = "0.96.1" } -nu-engine = { path = "../nu-engine", version = "0.96.1" } -nu-path = { path = "../nu-path", version = "0.96.1" } -nu-parser = { path = "../nu-parser", version = "0.96.1" } -nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.96.1", optional = true } -nu-protocol = { path = "../nu-protocol", version = "0.96.1" } -nu-utils = { path = "../nu-utils", version = "0.96.1" } -nu-color-config = { path = "../nu-color-config", version = "0.96.1" } +nu-cmd-base = { path = "../nu-cmd-base", version = "0.96.2" } +nu-engine = { path = "../nu-engine", version = "0.96.2" } +nu-path = { path = "../nu-path", version = "0.96.2" } +nu-parser = { path = "../nu-parser", version = "0.96.2" } +nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.96.2", optional = true } +nu-protocol = { path = "../nu-protocol", version = "0.96.2" } +nu-utils = { path = "../nu-utils", version = "0.96.2" } +nu-color-config = { path = "../nu-color-config", version = "0.96.2" } nu-ansi-term = { workspace = true } reedline = { workspace = true, features = ["bashisms", "sqlite"] } diff --git a/crates/nu-cmd-base/Cargo.toml b/crates/nu-cmd-base/Cargo.toml index 0fdb51eca8..568633d481 100644 --- a/crates/nu-cmd-base/Cargo.toml +++ b/crates/nu-cmd-base/Cargo.toml @@ -5,15 +5,15 @@ edition = "2021" license = "MIT" name = "nu-cmd-base" repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-base" -version = "0.96.1" +version = "0.96.2" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -nu-engine = { path = "../nu-engine", version = "0.96.1" } -nu-parser = { path = "../nu-parser", version = "0.96.1" } -nu-path = { path = "../nu-path", version = "0.96.1" } -nu-protocol = { path = "../nu-protocol", version = "0.96.1" } +nu-engine = { path = "../nu-engine", version = "0.96.2" } +nu-parser = { path = "../nu-parser", version = "0.96.2" } +nu-path = { path = "../nu-path", version = "0.96.2" } +nu-protocol = { path = "../nu-protocol", version = "0.96.2" } indexmap = { workspace = true } miette = { workspace = true } diff --git a/crates/nu-cmd-extra/Cargo.toml b/crates/nu-cmd-extra/Cargo.toml index f9cc539c92..4e76ea679b 100644 --- a/crates/nu-cmd-extra/Cargo.toml +++ b/crates/nu-cmd-extra/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" license = "MIT" name = "nu-cmd-extra" repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-extra" -version = "0.96.1" +version = "0.96.2" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -13,13 +13,13 @@ version = "0.96.1" bench = false [dependencies] -nu-cmd-base = { path = "../nu-cmd-base", version = "0.96.1" } -nu-engine = { path = "../nu-engine", version = "0.96.1" } -nu-json = { version = "0.96.1", path = "../nu-json" } -nu-parser = { path = "../nu-parser", version = "0.96.1" } -nu-pretty-hex = { version = "0.96.1", path = "../nu-pretty-hex" } -nu-protocol = { path = "../nu-protocol", version = "0.96.1" } -nu-utils = { path = "../nu-utils", version = "0.96.1" } +nu-cmd-base = { path = "../nu-cmd-base", version = "0.96.2" } +nu-engine = { path = "../nu-engine", version = "0.96.2" } +nu-json = { version = "0.96.2", path = "../nu-json" } +nu-parser = { path = "../nu-parser", version = "0.96.2" } +nu-pretty-hex = { version = "0.96.2", path = "../nu-pretty-hex" } +nu-protocol = { path = "../nu-protocol", version = "0.96.2" } +nu-utils = { path = "../nu-utils", version = "0.96.2" } # Potential dependencies for extras heck = { workspace = true } @@ -33,6 +33,6 @@ v_htmlescape = { workspace = true } itertools = { workspace = true } [dev-dependencies] -nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.96.1" } -nu-command = { path = "../nu-command", version = "0.96.1" } -nu-test-support = { path = "../nu-test-support", version = "0.96.1" } \ No newline at end of file +nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.96.2" } +nu-command = { path = "../nu-command", version = "0.96.2" } +nu-test-support = { path = "../nu-test-support", version = "0.96.2" } \ No newline at end of file diff --git a/crates/nu-cmd-lang/Cargo.toml b/crates/nu-cmd-lang/Cargo.toml index 7a92894f54..e6a15f7bf9 100644 --- a/crates/nu-cmd-lang/Cargo.toml +++ b/crates/nu-cmd-lang/Cargo.toml @@ -6,16 +6,16 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-lang" edition = "2021" license = "MIT" name = "nu-cmd-lang" -version = "0.96.1" +version = "0.96.2" [lib] bench = false [dependencies] -nu-engine = { path = "../nu-engine", version = "0.96.1" } -nu-parser = { path = "../nu-parser", version = "0.96.1" } -nu-protocol = { path = "../nu-protocol", version = "0.96.1" } -nu-utils = { path = "../nu-utils", version = "0.96.1" } +nu-engine = { path = "../nu-engine", version = "0.96.2" } +nu-parser = { path = "../nu-parser", version = "0.96.2" } +nu-protocol = { path = "../nu-protocol", version = "0.96.2" } +nu-utils = { path = "../nu-utils", version = "0.96.2" } itertools = { workspace = true } shadow-rs = { version = "0.29", default-features = false } diff --git a/crates/nu-cmd-plugin/Cargo.toml b/crates/nu-cmd-plugin/Cargo.toml index 7a2c28e69f..490e61a755 100644 --- a/crates/nu-cmd-plugin/Cargo.toml +++ b/crates/nu-cmd-plugin/Cargo.toml @@ -5,15 +5,15 @@ edition = "2021" license = "MIT" name = "nu-cmd-plugin" repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-plugin" -version = "0.96.1" +version = "0.96.2" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -nu-engine = { path = "../nu-engine", version = "0.96.1" } -nu-path = { path = "../nu-path", version = "0.96.1" } -nu-protocol = { path = "../nu-protocol", version = "0.96.1", features = ["plugin"] } -nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.96.1" } +nu-engine = { path = "../nu-engine", version = "0.96.2" } +nu-path = { path = "../nu-path", version = "0.96.2" } +nu-protocol = { path = "../nu-protocol", version = "0.96.2", features = ["plugin"] } +nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.96.2" } itertools = { workspace = true } diff --git a/crates/nu-color-config/Cargo.toml b/crates/nu-color-config/Cargo.toml index 7dd1edf899..87f282e752 100644 --- a/crates/nu-color-config/Cargo.toml +++ b/crates/nu-color-config/Cargo.toml @@ -5,18 +5,18 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-color-confi edition = "2021" license = "MIT" name = "nu-color-config" -version = "0.96.1" +version = "0.96.2" [lib] bench = false [dependencies] -nu-protocol = { path = "../nu-protocol", version = "0.96.1" } -nu-engine = { path = "../nu-engine", version = "0.96.1" } -nu-json = { path = "../nu-json", version = "0.96.1" } +nu-protocol = { path = "../nu-protocol", version = "0.96.2" } +nu-engine = { path = "../nu-engine", version = "0.96.2" } +nu-json = { path = "../nu-json", version = "0.96.2" } nu-ansi-term = { workspace = true } serde = { workspace = true, features = ["derive"] } [dev-dependencies] -nu-test-support = { path = "../nu-test-support", version = "0.96.1" } \ No newline at end of file +nu-test-support = { path = "../nu-test-support", version = "0.96.2" } \ No newline at end of file diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index 61ae01d69b..d1defd4327 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" license = "MIT" name = "nu-command" repository = "https://github.com/nushell/nushell/tree/main/crates/nu-command" -version = "0.96.1" +version = "0.96.2" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -13,21 +13,21 @@ version = "0.96.1" bench = false [dependencies] -nu-cmd-base = { path = "../nu-cmd-base", version = "0.96.1" } -nu-color-config = { path = "../nu-color-config", version = "0.96.1" } -nu-engine = { path = "../nu-engine", version = "0.96.1" } -nu-glob = { path = "../nu-glob", version = "0.96.1" } -nu-json = { path = "../nu-json", version = "0.96.1" } -nu-parser = { path = "../nu-parser", version = "0.96.1" } -nu-path = { path = "../nu-path", version = "0.96.1" } -nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.96.1" } -nu-protocol = { path = "../nu-protocol", version = "0.96.1" } -nu-system = { path = "../nu-system", version = "0.96.1" } -nu-table = { path = "../nu-table", version = "0.96.1" } -nu-term-grid = { path = "../nu-term-grid", version = "0.96.1" } -nu-utils = { path = "../nu-utils", version = "0.96.1" } +nu-cmd-base = { path = "../nu-cmd-base", version = "0.96.2" } +nu-color-config = { path = "../nu-color-config", version = "0.96.2" } +nu-engine = { path = "../nu-engine", version = "0.96.2" } +nu-glob = { path = "../nu-glob", version = "0.96.2" } +nu-json = { path = "../nu-json", version = "0.96.2" } +nu-parser = { path = "../nu-parser", version = "0.96.2" } +nu-path = { path = "../nu-path", version = "0.96.2" } +nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.96.2" } +nu-protocol = { path = "../nu-protocol", version = "0.96.2" } +nu-system = { path = "../nu-system", version = "0.96.2" } +nu-table = { path = "../nu-table", version = "0.96.2" } +nu-term-grid = { path = "../nu-term-grid", version = "0.96.2" } +nu-utils = { path = "../nu-utils", version = "0.96.2" } nu-ansi-term = { workspace = true } -nuon = { path = "../nuon", version = "0.96.1" } +nuon = { path = "../nuon", version = "0.96.2" } alphanumeric-sort = { workspace = true } base64 = { workspace = true } @@ -137,8 +137,8 @@ sqlite = ["rusqlite"] trash-support = ["trash"] [dev-dependencies] -nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.96.1" } -nu-test-support = { path = "../nu-test-support", version = "0.96.1" } +nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.96.2" } +nu-test-support = { path = "../nu-test-support", version = "0.96.2" } dirs = { workspace = true } mockito = { workspace = true, default-features = false } diff --git a/crates/nu-derive-value/Cargo.toml b/crates/nu-derive-value/Cargo.toml index b4d9fe341f..0e76564308 100644 --- a/crates/nu-derive-value/Cargo.toml +++ b/crates/nu-derive-value/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" license = "MIT" name = "nu-derive-value" repository = "https://github.com/nushell/nushell/tree/main/crates/nu-derive-value" -version = "0.96.1" +version = "0.96.2" [lib] proc-macro = true diff --git a/crates/nu-engine/Cargo.toml b/crates/nu-engine/Cargo.toml index 44b049f38a..be544265fe 100644 --- a/crates/nu-engine/Cargo.toml +++ b/crates/nu-engine/Cargo.toml @@ -5,16 +5,16 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-engine" edition = "2021" license = "MIT" name = "nu-engine" -version = "0.96.1" +version = "0.96.2" [lib] bench = false [dependencies] -nu-protocol = { path = "../nu-protocol", features = ["plugin"], version = "0.96.1" } -nu-path = { path = "../nu-path", version = "0.96.1" } -nu-glob = { path = "../nu-glob", version = "0.96.1" } -nu-utils = { path = "../nu-utils", version = "0.96.1" } +nu-protocol = { path = "../nu-protocol", features = ["plugin"], version = "0.96.2" } +nu-path = { path = "../nu-path", version = "0.96.2" } +nu-glob = { path = "../nu-glob", version = "0.96.2" } +nu-utils = { path = "../nu-utils", version = "0.96.2" } log = { workspace = true } [features] diff --git a/crates/nu-explore/Cargo.toml b/crates/nu-explore/Cargo.toml index 26087b98ec..1d0c081760 100644 --- a/crates/nu-explore/Cargo.toml +++ b/crates/nu-explore/Cargo.toml @@ -5,21 +5,21 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-explore" edition = "2021" license = "MIT" name = "nu-explore" -version = "0.96.1" +version = "0.96.2" [lib] bench = false [dependencies] -nu-protocol = { path = "../nu-protocol", version = "0.96.1" } -nu-parser = { path = "../nu-parser", version = "0.96.1" } -nu-color-config = { path = "../nu-color-config", version = "0.96.1" } -nu-engine = { path = "../nu-engine", version = "0.96.1" } -nu-table = { path = "../nu-table", version = "0.96.1" } -nu-json = { path = "../nu-json", version = "0.96.1" } -nu-utils = { path = "../nu-utils", version = "0.96.1" } +nu-protocol = { path = "../nu-protocol", version = "0.96.2" } +nu-parser = { path = "../nu-parser", version = "0.96.2" } +nu-color-config = { path = "../nu-color-config", version = "0.96.2" } +nu-engine = { path = "../nu-engine", version = "0.96.2" } +nu-table = { path = "../nu-table", version = "0.96.2" } +nu-json = { path = "../nu-json", version = "0.96.2" } +nu-utils = { path = "../nu-utils", version = "0.96.2" } nu-ansi-term = { workspace = true } -nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.96.1" } +nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.96.2" } anyhow = { workspace = true } log = { workspace = true } diff --git a/crates/nu-glob/Cargo.toml b/crates/nu-glob/Cargo.toml index acfaf2fbb3..31d88e841e 100644 --- a/crates/nu-glob/Cargo.toml +++ b/crates/nu-glob/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nu-glob" -version = "0.96.1" +version = "0.96.2" authors = ["The Nushell Project Developers", "The Rust Project Developers"] license = "MIT/Apache-2.0" description = """ diff --git a/crates/nu-json/Cargo.toml b/crates/nu-json/Cargo.toml index d4d64a26e3..9cfc35a1c8 100644 --- a/crates/nu-json/Cargo.toml +++ b/crates/nu-json/Cargo.toml @@ -8,7 +8,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-json" edition = "2021" license = "MIT" name = "nu-json" -version = "0.96.1" +version = "0.96.2" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -26,7 +26,7 @@ serde = { workspace = true } serde_json = { workspace = true } [dev-dependencies] -nu-test-support = { path = "../nu-test-support", version = "0.96.1" } -nu-path = { path = "../nu-path", version = "0.96.1" } +nu-test-support = { path = "../nu-test-support", version = "0.96.2" } +nu-path = { path = "../nu-path", version = "0.96.2" } serde_json = "1.0" fancy-regex = "0.13.0" \ No newline at end of file diff --git a/crates/nu-lsp/Cargo.toml b/crates/nu-lsp/Cargo.toml index fc2b1d76fa..46a9276126 100644 --- a/crates/nu-lsp/Cargo.toml +++ b/crates/nu-lsp/Cargo.toml @@ -3,14 +3,14 @@ authors = ["The Nushell Project Developers"] description = "Nushell's integrated LSP server" repository = "https://github.com/nushell/nushell/tree/main/crates/nu-lsp" name = "nu-lsp" -version = "0.96.1" +version = "0.96.2" edition = "2021" license = "MIT" [dependencies] -nu-cli = { path = "../nu-cli", version = "0.96.1" } -nu-parser = { path = "../nu-parser", version = "0.96.1" } -nu-protocol = { path = "../nu-protocol", version = "0.96.1" } +nu-cli = { path = "../nu-cli", version = "0.96.2" } +nu-parser = { path = "../nu-parser", version = "0.96.2" } +nu-protocol = { path = "../nu-protocol", version = "0.96.2" } reedline = { workspace = true } @@ -23,8 +23,8 @@ serde = { workspace = true } serde_json = { workspace = true } [dev-dependencies] -nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.96.1" } -nu-command = { path = "../nu-command", version = "0.96.1" } -nu-test-support = { path = "../nu-test-support", version = "0.96.1" } +nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.96.2" } +nu-command = { path = "../nu-command", version = "0.96.2" } +nu-test-support = { path = "../nu-test-support", version = "0.96.2" } assert-json-diff = "2.0" \ No newline at end of file diff --git a/crates/nu-parser/Cargo.toml b/crates/nu-parser/Cargo.toml index da8f63d42b..d929deef08 100644 --- a/crates/nu-parser/Cargo.toml +++ b/crates/nu-parser/Cargo.toml @@ -5,17 +5,17 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-parser" edition = "2021" license = "MIT" name = "nu-parser" -version = "0.96.1" +version = "0.96.2" exclude = ["/fuzz"] [lib] bench = false [dependencies] -nu-engine = { path = "../nu-engine", version = "0.96.1" } -nu-path = { path = "../nu-path", version = "0.96.1" } -nu-plugin-engine = { path = "../nu-plugin-engine", optional = true, version = "0.96.1" } -nu-protocol = { path = "../nu-protocol", version = "0.96.1" } +nu-engine = { path = "../nu-engine", version = "0.96.2" } +nu-path = { path = "../nu-path", version = "0.96.2" } +nu-plugin-engine = { path = "../nu-plugin-engine", optional = true, version = "0.96.2" } +nu-protocol = { path = "../nu-protocol", version = "0.96.2" } bytesize = { workspace = true } chrono = { default-features = false, features = ['std'], workspace = true } diff --git a/crates/nu-path/Cargo.toml b/crates/nu-path/Cargo.toml index ff49140bd2..c1e368aa7e 100644 --- a/crates/nu-path/Cargo.toml +++ b/crates/nu-path/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-path" edition = "2021" license = "MIT" name = "nu-path" -version = "0.96.1" +version = "0.96.2" exclude = ["/fuzz"] [lib] diff --git a/crates/nu-plugin-core/Cargo.toml b/crates/nu-plugin-core/Cargo.toml index 21119a442c..0bdafbbdf5 100644 --- a/crates/nu-plugin-core/Cargo.toml +++ b/crates/nu-plugin-core/Cargo.toml @@ -5,14 +5,14 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-plugin-core edition = "2021" license = "MIT" name = "nu-plugin-core" -version = "0.96.1" +version = "0.96.2" [lib] bench = false [dependencies] -nu-protocol = { path = "../nu-protocol", version = "0.96.1" } -nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.96.1", default-features = false } +nu-protocol = { path = "../nu-protocol", version = "0.96.2" } +nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.96.2", default-features = false } rmp-serde = { workspace = true } serde = { workspace = true } diff --git a/crates/nu-plugin-engine/Cargo.toml b/crates/nu-plugin-engine/Cargo.toml index 189b9b0713..0a58c1af72 100644 --- a/crates/nu-plugin-engine/Cargo.toml +++ b/crates/nu-plugin-engine/Cargo.toml @@ -5,18 +5,18 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-plugin-engi edition = "2021" license = "MIT" name = "nu-plugin-engine" -version = "0.96.1" +version = "0.96.2" [lib] bench = false [dependencies] -nu-engine = { path = "../nu-engine", version = "0.96.1" } -nu-protocol = { path = "../nu-protocol", version = "0.96.1" } -nu-system = { path = "../nu-system", version = "0.96.1" } -nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.96.1" } -nu-plugin-core = { path = "../nu-plugin-core", version = "0.96.1", default-features = false } -nu-utils = { path = "../nu-utils", version = "0.96.1" } +nu-engine = { path = "../nu-engine", version = "0.96.2" } +nu-protocol = { path = "../nu-protocol", version = "0.96.2" } +nu-system = { path = "../nu-system", version = "0.96.2" } +nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.96.2" } +nu-plugin-core = { path = "../nu-plugin-core", version = "0.96.2", default-features = false } +nu-utils = { path = "../nu-utils", version = "0.96.2" } serde = { workspace = true } log = { workspace = true } diff --git a/crates/nu-plugin-protocol/Cargo.toml b/crates/nu-plugin-protocol/Cargo.toml index 68b859f213..60226f3674 100644 --- a/crates/nu-plugin-protocol/Cargo.toml +++ b/crates/nu-plugin-protocol/Cargo.toml @@ -5,14 +5,14 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-plugin-prot edition = "2021" license = "MIT" name = "nu-plugin-protocol" -version = "0.96.1" +version = "0.96.2" [lib] bench = false [dependencies] -nu-protocol = { path = "../nu-protocol", version = "0.96.1", features = ["plugin"] } -nu-utils = { path = "../nu-utils", version = "0.96.1" } +nu-protocol = { path = "../nu-protocol", version = "0.96.2", features = ["plugin"] } +nu-utils = { path = "../nu-utils", version = "0.96.2" } bincode = "1.3" serde = { workspace = true, features = ["derive"] } diff --git a/crates/nu-plugin-test-support/Cargo.toml b/crates/nu-plugin-test-support/Cargo.toml index f33d997939..344ccb1c2d 100644 --- a/crates/nu-plugin-test-support/Cargo.toml +++ b/crates/nu-plugin-test-support/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nu-plugin-test-support" -version = "0.96.1" +version = "0.96.2" edition = "2021" license = "MIT" description = "Testing support for Nushell plugins" @@ -12,14 +12,14 @@ bench = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -nu-engine = { path = "../nu-engine", version = "0.96.1", features = ["plugin"] } -nu-protocol = { path = "../nu-protocol", version = "0.96.1", features = ["plugin"] } -nu-parser = { path = "../nu-parser", version = "0.96.1", features = ["plugin"] } -nu-plugin = { path = "../nu-plugin", version = "0.96.1" } -nu-plugin-core = { path = "../nu-plugin-core", version = "0.96.1" } -nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.96.1" } -nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.96.1" } -nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.96.1" } +nu-engine = { path = "../nu-engine", version = "0.96.2", features = ["plugin"] } +nu-protocol = { path = "../nu-protocol", version = "0.96.2", features = ["plugin"] } +nu-parser = { path = "../nu-parser", version = "0.96.2", features = ["plugin"] } +nu-plugin = { path = "../nu-plugin", version = "0.96.2" } +nu-plugin-core = { path = "../nu-plugin-core", version = "0.96.2" } +nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.96.2" } +nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.96.2" } +nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.96.2" } nu-ansi-term = { workspace = true } similar = "2.5" diff --git a/crates/nu-plugin/Cargo.toml b/crates/nu-plugin/Cargo.toml index b7fff9bf72..80c9f74471 100644 --- a/crates/nu-plugin/Cargo.toml +++ b/crates/nu-plugin/Cargo.toml @@ -5,17 +5,17 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-plugin" edition = "2021" license = "MIT" name = "nu-plugin" -version = "0.96.1" +version = "0.96.2" [lib] bench = false [dependencies] -nu-engine = { path = "../nu-engine", version = "0.96.1" } -nu-protocol = { path = "../nu-protocol", version = "0.96.1" } -nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.96.1" } -nu-plugin-core = { path = "../nu-plugin-core", version = "0.96.1", default-features = false } -nu-utils = { path = "../nu-utils", version = "0.96.1" } +nu-engine = { path = "../nu-engine", version = "0.96.2" } +nu-protocol = { path = "../nu-protocol", version = "0.96.2" } +nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.96.2" } +nu-plugin-core = { path = "../nu-plugin-core", version = "0.96.2", default-features = false } +nu-utils = { path = "../nu-utils", version = "0.96.2" } log = { workspace = true } thiserror = "1.0" diff --git a/crates/nu-pretty-hex/Cargo.toml b/crates/nu-pretty-hex/Cargo.toml index 7db01ca2cc..bf018dcf38 100644 --- a/crates/nu-pretty-hex/Cargo.toml +++ b/crates/nu-pretty-hex/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-pretty-hex" edition = "2021" license = "MIT" name = "nu-pretty-hex" -version = "0.96.1" +version = "0.96.2" [lib] doctest = false diff --git a/crates/nu-protocol/Cargo.toml b/crates/nu-protocol/Cargo.toml index ff8ef86c9e..197131b3f4 100644 --- a/crates/nu-protocol/Cargo.toml +++ b/crates/nu-protocol/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-protocol" edition = "2021" license = "MIT" name = "nu-protocol" -version = "0.96.1" +version = "0.96.2" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -13,10 +13,10 @@ version = "0.96.1" bench = false [dependencies] -nu-utils = { path = "../nu-utils", version = "0.96.1" } -nu-path = { path = "../nu-path", version = "0.96.1" } -nu-system = { path = "../nu-system", version = "0.96.1" } -nu-derive-value = { path = "../nu-derive-value", version = "0.96.1" } +nu-utils = { path = "../nu-utils", version = "0.96.2" } +nu-path = { path = "../nu-path", version = "0.96.2" } +nu-system = { path = "../nu-system", version = "0.96.2" } +nu-derive-value = { path = "../nu-derive-value", version = "0.96.2" } brotli = { workspace = true, optional = true } byte-unit = { version = "5.1", features = [ "serde" ] } @@ -53,7 +53,7 @@ plugin = [ serde_json = { workspace = true } strum = "0.26" strum_macros = "0.26" -nu-test-support = { path = "../nu-test-support", version = "0.96.1" } +nu-test-support = { path = "../nu-test-support", version = "0.96.2" } pretty_assertions = { workspace = true } rstest = { workspace = true } tempfile = { workspace = true } diff --git a/crates/nu-std/Cargo.toml b/crates/nu-std/Cargo.toml index d52ab617c1..f69cdc9180 100644 --- a/crates/nu-std/Cargo.toml +++ b/crates/nu-std/Cargo.toml @@ -5,12 +5,12 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-std" edition = "2021" license = "MIT" name = "nu-std" -version = "0.96.1" +version = "0.96.2" [dependencies] -nu-parser = { version = "0.96.1", path = "../nu-parser" } -nu-protocol = { version = "0.96.1", path = "../nu-protocol" } -nu-engine = { version = "0.96.1", path = "../nu-engine" } +nu-parser = { version = "0.96.2", path = "../nu-parser" } +nu-protocol = { version = "0.96.2", path = "../nu-protocol" } +nu-engine = { version = "0.96.2", path = "../nu-engine" } miette = { workspace = true, features = ["fancy-no-backtrace"] } log = "0.4" \ No newline at end of file diff --git a/crates/nu-system/Cargo.toml b/crates/nu-system/Cargo.toml index 1e1efba651..2059eb1c8c 100644 --- a/crates/nu-system/Cargo.toml +++ b/crates/nu-system/Cargo.toml @@ -3,7 +3,7 @@ authors = ["The Nushell Project Developers", "procs creators"] description = "Nushell system querying" repository = "https://github.com/nushell/nushell/tree/main/crates/nu-system" name = "nu-system" -version = "0.96.1" +version = "0.96.2" edition = "2021" license = "MIT" diff --git a/crates/nu-table/Cargo.toml b/crates/nu-table/Cargo.toml index 1f02ccb1c3..eafff13e81 100644 --- a/crates/nu-table/Cargo.toml +++ b/crates/nu-table/Cargo.toml @@ -5,20 +5,20 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-table" edition = "2021" license = "MIT" name = "nu-table" -version = "0.96.1" +version = "0.96.2" [lib] bench = false [dependencies] -nu-protocol = { path = "../nu-protocol", version = "0.96.1" } -nu-utils = { path = "../nu-utils", version = "0.96.1" } -nu-engine = { path = "../nu-engine", version = "0.96.1" } -nu-color-config = { path = "../nu-color-config", version = "0.96.1" } +nu-protocol = { path = "../nu-protocol", version = "0.96.2" } +nu-utils = { path = "../nu-utils", version = "0.96.2" } +nu-engine = { path = "../nu-engine", version = "0.96.2" } +nu-color-config = { path = "../nu-color-config", version = "0.96.2" } nu-ansi-term = { workspace = true } once_cell = { workspace = true } fancy-regex = { workspace = true } tabled = { workspace = true, features = ["color"], default-features = false } [dev-dependencies] -# nu-test-support = { path="../nu-test-support", version = "0.96.1" } \ No newline at end of file +# nu-test-support = { path="../nu-test-support", version = "0.96.2" } \ No newline at end of file diff --git a/crates/nu-term-grid/Cargo.toml b/crates/nu-term-grid/Cargo.toml index 348adb03af..a07740905c 100644 --- a/crates/nu-term-grid/Cargo.toml +++ b/crates/nu-term-grid/Cargo.toml @@ -5,12 +5,12 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-term-grid" edition = "2021" license = "MIT" name = "nu-term-grid" -version = "0.96.1" +version = "0.96.2" [lib] bench = false [dependencies] -nu-utils = { path = "../nu-utils", version = "0.96.1" } +nu-utils = { path = "../nu-utils", version = "0.96.2" } unicode-width = { workspace = true } \ No newline at end of file diff --git a/crates/nu-test-support/Cargo.toml b/crates/nu-test-support/Cargo.toml index 4a43d6b933..25328f7c25 100644 --- a/crates/nu-test-support/Cargo.toml +++ b/crates/nu-test-support/Cargo.toml @@ -5,16 +5,16 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-test-suppor edition = "2021" license = "MIT" name = "nu-test-support" -version = "0.96.1" +version = "0.96.2" [lib] doctest = false bench = false [dependencies] -nu-path = { path = "../nu-path", version = "0.96.1" } -nu-glob = { path = "../nu-glob", version = "0.96.1" } -nu-utils = { path = "../nu-utils", version = "0.96.1" } +nu-path = { path = "../nu-path", version = "0.96.2" } +nu-glob = { path = "../nu-glob", version = "0.96.2" } +nu-utils = { path = "../nu-utils", version = "0.96.2" } num-format = { workspace = true } which = { workspace = true } diff --git a/crates/nu-utils/Cargo.toml b/crates/nu-utils/Cargo.toml index 4c1caddbbd..72c02fb538 100644 --- a/crates/nu-utils/Cargo.toml +++ b/crates/nu-utils/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" license = "MIT" name = "nu-utils" repository = "https://github.com/nushell/nushell/tree/main/crates/nu-utils" -version = "0.96.1" +version = "0.96.2" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [[bin]] diff --git a/crates/nu-utils/src/sample_config/default_config.nu b/crates/nu-utils/src/sample_config/default_config.nu index 779c2e900c..0c78cf9ca1 100644 --- a/crates/nu-utils/src/sample_config/default_config.nu +++ b/crates/nu-utils/src/sample_config/default_config.nu @@ -1,6 +1,6 @@ # Nushell Config File # -# version = "0.96.1" +# version = "0.96.2" # For more information on defining custom themes, see # https://www.nushell.sh/book/coloring_and_theming.html diff --git a/crates/nu-utils/src/sample_config/default_env.nu b/crates/nu-utils/src/sample_config/default_env.nu index 5d5cc46190..68e1a4006b 100644 --- a/crates/nu-utils/src/sample_config/default_env.nu +++ b/crates/nu-utils/src/sample_config/default_env.nu @@ -1,6 +1,6 @@ # Nushell Environment Config File # -# version = "0.96.1" +# version = "0.96.2" def create_left_prompt [] { let dir = match (do --ignore-shell-errors { $env.PWD | path relative-to $nu.home-path }) { diff --git a/crates/nu_plugin_custom_values/Cargo.toml b/crates/nu_plugin_custom_values/Cargo.toml index 9643bf337e..03811f7fca 100644 --- a/crates/nu_plugin_custom_values/Cargo.toml +++ b/crates/nu_plugin_custom_values/Cargo.toml @@ -10,10 +10,10 @@ name = "nu_plugin_custom_values" bench = false [dependencies] -nu-plugin = { path = "../nu-plugin", version = "0.96.1" } -nu-protocol = { path = "../nu-protocol", version = "0.96.1", features = ["plugin"] } +nu-plugin = { path = "../nu-plugin", version = "0.96.2" } +nu-protocol = { path = "../nu-protocol", version = "0.96.2", features = ["plugin"] } serde = { workspace = true, default-features = false } typetag = "0.2" [dev-dependencies] -nu-plugin-test-support = { path = "../nu-plugin-test-support", version = "0.96.1" } \ No newline at end of file +nu-plugin-test-support = { path = "../nu-plugin-test-support", version = "0.96.2" } \ No newline at end of file diff --git a/crates/nu_plugin_example/Cargo.toml b/crates/nu_plugin_example/Cargo.toml index 8b24911026..cdb5a727c9 100644 --- a/crates/nu_plugin_example/Cargo.toml +++ b/crates/nu_plugin_example/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu_plugin_exam edition = "2021" license = "MIT" name = "nu_plugin_example" -version = "0.96.1" +version = "0.96.2" [[bin]] name = "nu_plugin_example" @@ -15,9 +15,9 @@ bench = false bench = false [dependencies] -nu-plugin = { path = "../nu-plugin", version = "0.96.1" } -nu-protocol = { path = "../nu-protocol", version = "0.96.1", features = ["plugin"] } +nu-plugin = { path = "../nu-plugin", version = "0.96.2" } +nu-protocol = { path = "../nu-protocol", version = "0.96.2", features = ["plugin"] } [dev-dependencies] -nu-plugin-test-support = { path = "../nu-plugin-test-support", version = "0.96.1" } -nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.96.1" } \ No newline at end of file +nu-plugin-test-support = { path = "../nu-plugin-test-support", version = "0.96.2" } +nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.96.2" } \ No newline at end of file diff --git a/crates/nu_plugin_formats/Cargo.toml b/crates/nu_plugin_formats/Cargo.toml index ac65f05080..dbf1b43657 100644 --- a/crates/nu_plugin_formats/Cargo.toml +++ b/crates/nu_plugin_formats/Cargo.toml @@ -5,12 +5,12 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu_plugin_form edition = "2021" license = "MIT" name = "nu_plugin_formats" -version = "0.96.1" +version = "0.96.2" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -nu-plugin = { path = "../nu-plugin", version = "0.96.1" } -nu-protocol = { path = "../nu-protocol", version = "0.96.1", features = ["plugin"] } +nu-plugin = { path = "../nu-plugin", version = "0.96.2" } +nu-protocol = { path = "../nu-protocol", version = "0.96.2", features = ["plugin"] } indexmap = { workspace = true } eml-parser = "0.1" @@ -18,4 +18,4 @@ ical = "0.11" rust-ini = "0.21.0" [dev-dependencies] -nu-plugin-test-support = { path = "../nu-plugin-test-support", version = "0.96.1" } \ No newline at end of file +nu-plugin-test-support = { path = "../nu-plugin-test-support", version = "0.96.2" } \ No newline at end of file diff --git a/crates/nu_plugin_gstat/Cargo.toml b/crates/nu_plugin_gstat/Cargo.toml index 9bc09fc6c0..b53560473e 100644 --- a/crates/nu_plugin_gstat/Cargo.toml +++ b/crates/nu_plugin_gstat/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu_plugin_gsta edition = "2021" license = "MIT" name = "nu_plugin_gstat" -version = "0.96.1" +version = "0.96.2" [lib] doctest = false @@ -16,7 +16,7 @@ name = "nu_plugin_gstat" bench = false [dependencies] -nu-plugin = { path = "../nu-plugin", version = "0.96.1" } -nu-protocol = { path = "../nu-protocol", version = "0.96.1" } +nu-plugin = { path = "../nu-plugin", version = "0.96.2" } +nu-protocol = { path = "../nu-protocol", version = "0.96.2" } git2 = "0.19" \ No newline at end of file diff --git a/crates/nu_plugin_inc/Cargo.toml b/crates/nu_plugin_inc/Cargo.toml index e82168463c..6462f96c0c 100644 --- a/crates/nu_plugin_inc/Cargo.toml +++ b/crates/nu_plugin_inc/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu_plugin_inc" edition = "2021" license = "MIT" name = "nu_plugin_inc" -version = "0.96.1" +version = "0.96.2" [lib] doctest = false @@ -16,7 +16,7 @@ name = "nu_plugin_inc" bench = false [dependencies] -nu-plugin = { path = "../nu-plugin", version = "0.96.1" } -nu-protocol = { path = "../nu-protocol", version = "0.96.1", features = ["plugin"] } +nu-plugin = { path = "../nu-plugin", version = "0.96.2" } +nu-protocol = { path = "../nu-protocol", version = "0.96.2", features = ["plugin"] } semver = "1.0" \ No newline at end of file diff --git a/crates/nu_plugin_nu_example/nu_plugin_nu_example.nu b/crates/nu_plugin_nu_example/nu_plugin_nu_example.nu index e7c4f77365..68f87eaca0 100755 --- a/crates/nu_plugin_nu_example/nu_plugin_nu_example.nu +++ b/crates/nu_plugin_nu_example/nu_plugin_nu_example.nu @@ -6,7 +6,7 @@ # it also allows us to test the plugin interface with something manually implemented in a scripting # language without adding any extra dependencies to our tests. -const NUSHELL_VERSION = "0.96.1" +const NUSHELL_VERSION = "0.96.2" const PLUGIN_VERSION = "0.1.0" # bump if you change commands! def main [--stdio] { diff --git a/crates/nu_plugin_polars/Cargo.toml b/crates/nu_plugin_polars/Cargo.toml index ad1181f2fe..095f33915a 100644 --- a/crates/nu_plugin_polars/Cargo.toml +++ b/crates/nu_plugin_polars/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" license = "MIT" name = "nu_plugin_polars" repository = "https://github.com/nushell/nushell/tree/main/crates/nu_plugin_polars" -version = "0.96.1" +version = "0.96.2" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -17,10 +17,10 @@ bench = false bench = false [dependencies] -nu-protocol = { path = "../nu-protocol", version = "0.96.1" } -nu-plugin = { path = "../nu-plugin", version = "0.96.1" } -nu-path = { path = "../nu-path", version = "0.96.1" } -nu-utils = { path = "../nu-utils", version = "0.96.1" } +nu-protocol = { path = "../nu-protocol", version = "0.96.2" } +nu-plugin = { path = "../nu-plugin", version = "0.96.2" } +nu-path = { path = "../nu-path", version = "0.96.2" } +nu-utils = { path = "../nu-utils", version = "0.96.2" } # Potential dependencies for extras chrono = { workspace = true, features = ["std", "unstable-locales"], default-features = false } @@ -76,9 +76,9 @@ optional = false version = "0.41" [dev-dependencies] -nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.96.1" } -nu-engine = { path = "../nu-engine", version = "0.96.1" } -nu-parser = { path = "../nu-parser", version = "0.96.1" } -nu-command = { path = "../nu-command", version = "0.96.1" } -nu-plugin-test-support = { path = "../nu-plugin-test-support", version = "0.96.1" } +nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.96.2" } +nu-engine = { path = "../nu-engine", version = "0.96.2" } +nu-parser = { path = "../nu-parser", version = "0.96.2" } +nu-command = { path = "../nu-command", version = "0.96.2" } +nu-plugin-test-support = { path = "../nu-plugin-test-support", version = "0.96.2" } tempfile.workspace = true \ No newline at end of file diff --git a/crates/nu_plugin_python/nu_plugin_python_example.py b/crates/nu_plugin_python/nu_plugin_python_example.py index c0c530219f..5a0357e9ca 100755 --- a/crates/nu_plugin_python/nu_plugin_python_example.py +++ b/crates/nu_plugin_python/nu_plugin_python_example.py @@ -27,7 +27,7 @@ import sys import json -NUSHELL_VERSION = "0.96.1" +NUSHELL_VERSION = "0.96.2" PLUGIN_VERSION = "0.1.0" # bump if you change commands! diff --git a/crates/nu_plugin_query/Cargo.toml b/crates/nu_plugin_query/Cargo.toml index bb7e0f6bec..e3ed5ca2eb 100644 --- a/crates/nu_plugin_query/Cargo.toml +++ b/crates/nu_plugin_query/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu_plugin_quer edition = "2021" license = "MIT" name = "nu_plugin_query" -version = "0.96.1" +version = "0.96.2" [lib] doctest = false @@ -16,8 +16,8 @@ name = "nu_plugin_query" bench = false [dependencies] -nu-plugin = { path = "../nu-plugin", version = "0.96.1" } -nu-protocol = { path = "../nu-protocol", version = "0.96.1" } +nu-plugin = { path = "../nu-plugin", version = "0.96.2" } +nu-protocol = { path = "../nu-protocol", version = "0.96.2" } gjson = "0.8" scraper = { default-features = false, version = "0.19" } diff --git a/crates/nu_plugin_stress_internals/Cargo.toml b/crates/nu_plugin_stress_internals/Cargo.toml index 872623f4ba..5871246587 100644 --- a/crates/nu_plugin_stress_internals/Cargo.toml +++ b/crates/nu_plugin_stress_internals/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu_plugin_stre edition = "2021" license = "MIT" name = "nu_plugin_stress_internals" -version = "0.96.1" +version = "0.96.2" [[bin]] name = "nu_plugin_stress_internals" diff --git a/crates/nuon/Cargo.toml b/crates/nuon/Cargo.toml index b1ec4a2a69..296f3cc962 100644 --- a/crates/nuon/Cargo.toml +++ b/crates/nuon/Cargo.toml @@ -5,14 +5,14 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nuon" edition = "2021" license = "MIT" name = "nuon" -version = "0.96.1" +version = "0.96.2" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -nu-parser = { path = "../nu-parser", version = "0.96.1" } -nu-protocol = { path = "../nu-protocol", version = "0.96.1" } -nu-engine = { path = "../nu-engine", version = "0.96.1" } +nu-parser = { path = "../nu-parser", version = "0.96.2" } +nu-protocol = { path = "../nu-protocol", version = "0.96.2" } +nu-engine = { path = "../nu-engine", version = "0.96.2" } once_cell = { workspace = true } fancy-regex = { workspace = true } From e3f78b87937df2f16f32a905358f13a18785faaa Mon Sep 17 00:00:00 2001 From: Lee Wonjoon Date: Tue, 30 Jul 2024 13:28:41 +0000 Subject: [PATCH 085/126] Keep forward slash when autocomplete on Windows (#13321) Related #7044 # Description When autocomplete path with `/` on Windows, paths keep with slash instead of backslash(`\`). If mixed both, path completion uses a last path seperator. ![image](https://github.com/nushell/nushell/assets/4014016/b09633fd-0e4a-4cd9-9c14-2bca30625804) ![image](https://github.com/nushell/nushell/assets/4014016/e1f228f8-4cce-43eb-a34a-dfa54efd2ebb) ![image](https://github.com/nushell/nushell/assets/4014016/0694443a-3017-4828-be60-5f39ffd96440) # User-Facing Changes ![completion](https://github.com/nushell/nushell/assets/4014016/03626544-6a14-4d8b-a607-21a4472f8037) # Tests + Formatting # After Submitting --- .../src/completions/completion_common.rs | 20 +- crates/nu-cli/tests/completions/mod.rs | 229 +++++++++++++----- .../support/completions_helpers.rs | 16 +- 3 files changed, 186 insertions(+), 79 deletions(-) diff --git a/crates/nu-cli/src/completions/completion_common.rs b/crates/nu-cli/src/completions/completion_common.rs index b3bca778a9..d6882da57b 100644 --- a/crates/nu-cli/src/completions/completion_common.rs +++ b/crates/nu-cli/src/completions/completion_common.rs @@ -10,9 +10,7 @@ use nu_protocol::{ levenshtein_distance, Span, }; use nu_utils::get_ls_colors; -use std::path::{ - is_separator, Component, Path, PathBuf, MAIN_SEPARATOR as SEP, MAIN_SEPARATOR_STR, -}; +use std::path::{is_separator, Component, Path, PathBuf, MAIN_SEPARATOR as SEP}; use super::SortBy; @@ -93,16 +91,16 @@ enum OriginalCwd { } impl OriginalCwd { - fn apply(&self, mut p: PathBuiltFromString) -> String { + fn apply(&self, mut p: PathBuiltFromString, path_separator: char) -> String { match self { Self::None => {} Self::Home => p.parts.insert(0, "~".to_string()), Self::Prefix(s) => p.parts.insert(0, s.clone()), }; - let mut ret = p.parts.join(MAIN_SEPARATOR_STR); + let mut ret = p.parts.join(&path_separator.to_string()); if p.isdir { - ret.push(SEP); + ret.push(path_separator); } ret } @@ -133,6 +131,14 @@ pub fn complete_item( ) -> Vec<(nu_protocol::Span, String, Option