From 1b89ccf25b7fc0698125b8751716053f9a5e2c92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Mon, 27 Sep 2021 20:36:44 +0300 Subject: [PATCH 01/26] Add comment --- crates/nu-parser/src/parse_keywords.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/nu-parser/src/parse_keywords.rs b/crates/nu-parser/src/parse_keywords.rs index e516da309b..088bf57ac5 100644 --- a/crates/nu-parser/src/parse_keywords.rs +++ b/crates/nu-parser/src/parse_keywords.rs @@ -309,6 +309,7 @@ pub fn parse_module( if err.is_none() { let decl_name = + // parts[1] is safe since it's checked in parse_def already working_set.get_span_contents(pipeline.commands[0].parts[1]); let decl_id = working_set From 561feff36550d00ad77545d865d271380eb55433 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Tue, 28 Sep 2021 20:29:38 +0300 Subject: [PATCH 02/26] Introduce 'export' keyword --- crates/nu-parser/src/parse_keywords.rs | 53 ++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/crates/nu-parser/src/parse_keywords.rs b/crates/nu-parser/src/parse_keywords.rs index 088bf57ac5..7c8ed36b6c 100644 --- a/crates/nu-parser/src/parse_keywords.rs +++ b/crates/nu-parser/src/parse_keywords.rs @@ -16,6 +16,13 @@ use crate::{ pub fn parse_def_predecl(working_set: &mut StateWorkingSet, spans: &[Span]) { let name = working_set.get_span_contents(spans[0]); + // handle "export def" same as "def" + let (name, spans) = if name == b"export" && spans.len() >= 2 { + (working_set.get_span_contents(spans[1]), &spans[1..]) + } else { + (name, spans) + }; + if name == b"def" && spans.len() >= 4 { let (name_expr, ..) = parse_string(working_set, spans[1]); let name = name_expr.as_string(); @@ -218,6 +225,38 @@ pub fn parse_alias( ) } +pub fn parse_export( + working_set: &mut StateWorkingSet, + spans: &[Span], +) -> (Statement, Option) { + let bytes = working_set.get_span_contents(spans[0]); + + if bytes == b"export" && spans.len() >= 3 { + let export_name = working_set.get_span_contents(spans[1]); + + match export_name { + b"def" => parse_def(working_set, &spans[1..]), + _ => ( + garbage_statement(spans), + Some(ParseError::Expected( + // TODO: Fill in more as they come + "def keyword".into(), + spans[1], + )), + ), + } + } else { + ( + garbage_statement(spans), + Some(ParseError::UnknownState( + // TODO: fill in more as they come + "Expected structure: export def [] {}".into(), + span(spans), + )), + ) + } +} + pub fn parse_module( working_set: &mut StateWorkingSet, spans: &[Span], @@ -307,16 +346,21 @@ pub fn parse_module( b"def" => { let (stmt, err) = parse_def(working_set, &pipeline.commands[0].parts); + (stmt, err) + } + b"export" => { + let (stmt, err) = + parse_export(working_set, &pipeline.commands[0].parts); + if err.is_none() { let decl_name = - // parts[1] is safe since it's checked in parse_def already - working_set.get_span_contents(pipeline.commands[0].parts[1]); + // parts[2] is safe since it's checked in parse_def already + working_set.get_span_contents(pipeline.commands[0].parts[2]); let decl_id = working_set .find_decl(decl_name) .expect("internal error: failed to find added declaration"); - // TODO: Later, we want to put this behind 'export' exports.push((decl_name.into(), decl_id)); } @@ -325,7 +369,8 @@ pub fn parse_module( _ => ( garbage_statement(&pipeline.commands[0].parts), Some(ParseError::Expected( - "def".into(), + // TODO: Fill in more as they com + "def or export keyword".into(), pipeline.commands[0].parts[0], )), ), From 93521da9d8c64827d950147b4f2138033420f92d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Tue, 28 Sep 2021 21:03:53 +0300 Subject: [PATCH 03/26] Add 'export def' command --- crates/nu-command/src/default_context.rs | 10 ++++++- crates/nu-command/src/export_def.rs | 35 ++++++++++++++++++++++++ crates/nu-parser/src/parse_keywords.rs | 35 +++++++++++++++++++++++- 3 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 crates/nu-command/src/export_def.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 609d0cc683..4140ca1a4d 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -6,7 +6,7 @@ use nu_protocol::{ }; use crate::{ - Alias, Benchmark, BuildString, Def, Do, Each, External, For, From, FromJson, Git, GitCheckout, + Alias, Benchmark, BuildString, Def, Do, Each, ExportDef, External, For, From, FromJson, Git, GitCheckout, If, Length, Let, LetEnv, Lines, ListGitBranches, Ls, Module, Sys, Table, Use, Where, }; @@ -20,12 +20,20 @@ pub fn create_default_context() -> Rc> { Signature::build("where").required("cond", SyntaxShape::RowCondition, "condition"); working_set.add_decl(sig.predeclare()); + working_set.add_decl(Box::new(If)); + + working_set.add_decl(Box::new(Let)); + + working_set.add_decl(Box::new(LetEnv)); + working_set.add_decl(Box::new(Alias)); working_set.add_decl(Box::new(Benchmark)); working_set.add_decl(Box::new(BuildString)); + working_set.add_decl(Box::new(Def)); working_set.add_decl(Box::new(Do)); working_set.add_decl(Box::new(Each)); + working_set.add_decl(Box::new(ExportDef)); working_set.add_decl(Box::new(External)); working_set.add_decl(Box::new(For)); working_set.add_decl(Box::new(From)); diff --git a/crates/nu-command/src/export_def.rs b/crates/nu-command/src/export_def.rs new file mode 100644 index 0000000000..b82418c486 --- /dev/null +++ b/crates/nu-command/src/export_def.rs @@ -0,0 +1,35 @@ +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EvaluationContext}; +use nu_protocol::{Signature, SyntaxShape, Value}; + +pub struct ExportDef; + +impl Command for ExportDef { + fn name(&self) -> &str { + "export def" + } + + fn usage(&self) -> &str { + "Define a custom command and export it from a module" + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("export def") + .required("target", SyntaxShape::String, "definition name") + .required("params", SyntaxShape::Signature, "parameters") + .required( + "block", + SyntaxShape::Block(Some(vec![])), + "body of the definition", + ) + } + + fn run( + &self, + _context: &EvaluationContext, + call: &Call, + _input: Value, + ) -> Result { + Ok(Value::Nothing { span: call.head }) + } +} diff --git a/crates/nu-parser/src/parse_keywords.rs b/crates/nu-parser/src/parse_keywords.rs index 7c8ed36b6c..787de1b53b 100644 --- a/crates/nu-parser/src/parse_keywords.rs +++ b/crates/nu-parser/src/parse_keywords.rs @@ -235,7 +235,40 @@ pub fn parse_export( let export_name = working_set.get_span_contents(spans[1]); match export_name { - b"def" => parse_def(working_set, &spans[1..]), + b"def" => { + let (stmt, err) = parse_def(working_set, &spans[1..]); + + let export_def_decl_id = working_set + .find_decl(b"export def") + .expect("internal error: missing 'export def' command"); + + // Trying to warp the 'def' call into the 'export def' in a very clumsy way + let stmt = if let Statement::Pipeline(ref pipe) = stmt { + if !pipe.expressions.is_empty() { + if let Expr::Call(ref call) = pipe.expressions[0].expr { + let mut call = call.clone(); + + call.head = span(&spans[0..=1]); + call.decl_id = export_def_decl_id; + + Statement::Pipeline(Pipeline::from_vec(vec![Expression { + expr: Expr::Call(call), + span: span(spans), + ty: Type::Unknown, + custom_completion: None, + }])) + } else { + stmt + } + } else { + stmt + } + } else { + stmt + }; + + (stmt, err) + } _ => ( garbage_statement(spans), Some(ParseError::Expected( From 3cbf99053f938c0ab23cdb84ff65b81da1465b51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Tue, 28 Sep 2021 21:12:46 +0300 Subject: [PATCH 04/26] Throw an error if using export outside of module --- crates/nu-parser/src/errors.rs | 8 ++++++++ crates/nu-parser/src/parser.rs | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/crates/nu-parser/src/errors.rs b/crates/nu-parser/src/errors.rs index 58123aac0d..f55fed2da7 100644 --- a/crates/nu-parser/src/errors.rs +++ b/crates/nu-parser/src/errors.rs @@ -57,6 +57,14 @@ pub enum ParseError { #[diagnostic(code(nu::parser::expected_keyword), url(docsrs))] ExpectedKeyword(String, #[label("expected {0}")] Span), + #[error("Unexpected keyword.")] + #[diagnostic( + code(nu::parser::unexpected_keyword), + url(docsrs), + help("'export' keyword is allowed only in a module.") + )] + UnexpectedKeyword(String, #[label("unexpected {0}")] Span), + #[error("Multiple rest params.")] #[diagnostic(code(nu::parser::multiple_rest_params), url(docsrs))] MultipleRestParams(#[label = "multiple rest params"] Span), diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 59508cdfe2..daff7c6054 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -2585,6 +2585,10 @@ pub fn parse_statement( b"alias" => parse_alias(working_set, spans), b"module" => parse_module(working_set, spans), b"use" => parse_use(working_set, spans), + b"export" => ( + garbage_statement(spans), + Some(ParseError::UnexpectedKeyword("export".into(), spans[0])), + ), _ => { let (expr, err) = parse_expression(working_set, spans); (Statement::Pipeline(Pipeline::from_vec(vec![expr])), err) From 7488254cca6ff6140f8fe4c8439f6a6295da204e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Tue, 28 Sep 2021 23:18:48 +0300 Subject: [PATCH 05/26] Implement a rough version of 'hide' 'hide' command is used to undefine custom commands --- crates/nu-command/src/core_commands/use_.rs | 2 +- crates/nu-command/src/default_context.rs | 9 +- crates/nu-parser/src/parse_keywords.rs | 63 +++++++++++++- crates/nu-parser/src/parser.rs | 3 +- crates/nu-protocol/src/engine/engine_state.rs | 86 ++++++++++++++++++- 5 files changed, 150 insertions(+), 13 deletions(-) diff --git a/crates/nu-command/src/core_commands/use_.rs b/crates/nu-command/src/core_commands/use_.rs index 30b5e3d0b0..3cfae2e49d 100644 --- a/crates/nu-command/src/core_commands/use_.rs +++ b/crates/nu-command/src/core_commands/use_.rs @@ -14,7 +14,7 @@ impl Command for Use { } fn signature(&self) -> nu_protocol::Signature { - Signature::build("use").required("module_name", SyntaxShape::String, "module name") + Signature::build("use").required("pattern", SyntaxShape::String, "import pattern") } fn run( diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 4140ca1a4d..42b30cf877 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -7,7 +7,7 @@ use nu_protocol::{ use crate::{ Alias, Benchmark, BuildString, Def, Do, Each, ExportDef, External, For, From, FromJson, Git, GitCheckout, - If, Length, Let, LetEnv, Lines, ListGitBranches, Ls, Module, Sys, Table, Use, Where, + Hide, If, Length, Let, LetEnv, Lines, ListGitBranches, Ls, Module, Sys, Table, Use, Where, }; pub fn create_default_context() -> Rc> { @@ -29,8 +29,8 @@ pub fn create_default_context() -> Rc> { working_set.add_decl(Box::new(Alias)); working_set.add_decl(Box::new(Benchmark)); working_set.add_decl(Box::new(BuildString)); - - working_set.add_decl(Box::new(Def)); + + working_set.add_decl(Box::new(Def)); working_set.add_decl(Box::new(Do)); working_set.add_decl(Box::new(Each)); working_set.add_decl(Box::new(ExportDef)); @@ -38,6 +38,7 @@ pub fn create_default_context() -> Rc> { working_set.add_decl(Box::new(For)); working_set.add_decl(Box::new(From)); working_set.add_decl(Box::new(FromJson)); + working_set.add_decl(Box::new(Hide)); working_set.add_decl(Box::new(If)); working_set.add_decl(Box::new(Length)); working_set.add_decl(Box::new(Let)); @@ -48,7 +49,7 @@ pub fn create_default_context() -> Rc> { working_set.add_decl(Box::new(Sys)); working_set.add_decl(Box::new(Table)); working_set.add_decl(Box::new(Use)); - working_set.add_decl(Box::new(Where)); + working_set.add_decl(Box::new(Where)); // This is a WIP proof of concept working_set.add_decl(Box::new(ListGitBranches)); diff --git a/crates/nu-parser/src/parse_keywords.rs b/crates/nu-parser/src/parse_keywords.rs index 787de1b53b..c27c66d637 100644 --- a/crates/nu-parser/src/parse_keywords.rs +++ b/crates/nu-parser/src/parse_keywords.rs @@ -472,8 +472,6 @@ pub fn parse_use( let mut error = None; let bytes = working_set.get_span_contents(spans[0]); - // TODO: Currently, this directly imports the module's definitions into the current scope. - // Later, we want to put them behind the module's name and add selective importing if bytes == b"use" && spans.len() >= 2 { let (module_name_expr, err) = parse_string(working_set, spans[1]); error = error.or(err); @@ -482,8 +480,6 @@ pub fn parse_use( error = error.or(err); let exports = if let Some(block_id) = working_set.find_module(&import_pattern.head) { - // TODO: Since we don't use the Block at all, we might just as well create a separate - // Module that holds only the exports, without having Blocks in the way. working_set.get_block(block_id).exports.clone() } else { return ( @@ -571,6 +567,65 @@ pub fn parse_use( } } +pub fn parse_hide( + working_set: &mut StateWorkingSet, + spans: &[Span], +) -> (Statement, Option) { + let mut error = None; + let bytes = working_set.get_span_contents(spans[0]); + + if bytes == b"hide" && spans.len() >= 2 { + let (name_expr, err) = parse_string(working_set, spans[1]); + error = error.or(err); + + let name_bytes: Vec = working_set.get_span_contents(spans[1]).into(); + + // TODO: Do the import pattern stuff for bulk-hiding + // TODO: move this error into error = error.or pattern + let _decl_id = if let Some(id) = working_set.find_decl(&name_bytes) { + id + } else { + return ( + garbage_statement(spans), + Some(ParseError::UnknownCommand(spans[1])), + ); + }; + + // Hide the definitions + working_set.hide_decl(name_bytes); + + // Create the Hide command call + let hide_decl_id = working_set + .find_decl(b"hide") + .expect("internal error: missing hide command"); + + let call = Box::new(Call { + head: spans[0], + decl_id: hide_decl_id, + positional: vec![name_expr], + named: vec![], + }); + + ( + Statement::Pipeline(Pipeline::from_vec(vec![Expression { + expr: Expr::Call(call), + span: span(spans), + ty: Type::Unknown, + custom_completion: None, + }])), + error, + ) + } else { + ( + garbage_statement(spans), + Some(ParseError::UnknownState( + "Expected structure: hide ".into(), + span(spans), + )), + ) + } +} + pub fn parse_let( working_set: &mut StateWorkingSet, spans: &[Span], diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index daff7c6054..d3464869f4 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -14,7 +14,7 @@ use nu_protocol::{ }; use crate::parse_keywords::{ - parse_alias, parse_def, parse_def_predecl, parse_let, parse_module, parse_use, + parse_alias, parse_def, parse_def_predecl, parse_hide, parse_let, parse_module, parse_use, }; #[derive(Debug, Clone)] @@ -2589,6 +2589,7 @@ pub fn parse_statement( garbage_statement(spans), Some(ParseError::UnexpectedKeyword("export".into(), spans[0])), ), + b"hide" => parse_hide(working_set, spans), _ => { let (expr, err) = parse_expression(working_set, spans); (Statement::Pipeline(Pipeline::from_vec(vec![expr])), err) diff --git a/crates/nu-protocol/src/engine/engine_state.rs b/crates/nu-protocol/src/engine/engine_state.rs index f466318970..9ddb768449 100644 --- a/crates/nu-protocol/src/engine/engine_state.rs +++ b/crates/nu-protocol/src/engine/engine_state.rs @@ -18,6 +18,7 @@ pub struct ScopeFrame { decls: HashMap, DeclId>, aliases: HashMap, Vec>, modules: HashMap, BlockId>, + hiding: HashMap, usize>, // defines what is being hidden and its "hiding strength" } impl ScopeFrame { @@ -27,6 +28,7 @@ impl ScopeFrame { decls: HashMap::new(), aliases: HashMap::new(), modules: HashMap::new(), + hiding: HashMap::new(), } } @@ -81,6 +83,9 @@ impl EngineState { for item in first.modules.into_iter() { last.modules.insert(item.0, item.1); } + for item in first.hiding.into_iter() { + last.hiding.insert(item.0, item.1); + } } } @@ -124,9 +129,23 @@ impl EngineState { } pub fn find_decl(&self, name: &[u8]) -> Option { + let mut hiding_strength = 0; + // println!("state: starting finding {}", String::from_utf8_lossy(&name)); + for scope in self.scope.iter().rev() { + // println!("hiding map: {:?}", scope.hiding); + // check if we're hiding the declin this scope + if let Some(strength) = scope.hiding.get(name) { + hiding_strength += strength; + } + if let Some(decl_id) = scope.decls.get(name) { - return Some(*decl_id); + // if we're hiding this decl, do not return it and reduce the hiding strength + if hiding_strength > 0 { + hiding_strength -= 1; + } else { + return Some(*decl_id); + } } } @@ -243,10 +262,12 @@ impl StateDelta { } pub fn enter_scope(&mut self) { + // println!("enter scope"); self.scope.push(ScopeFrame::new()); } pub fn exit_scope(&mut self) { + // println!("exit scope"); self.scope.pop(); } } @@ -280,6 +301,7 @@ impl<'a> StateWorkingSet<'a> { pub fn add_decl(&mut self, decl: Box) -> DeclId { let name = decl.name().as_bytes().to_vec(); + // println!("adding {}", String::from_utf8_lossy(&name)); self.delta.decls.push(decl); let decl_id = self.num_decls() - 1; @@ -289,11 +311,36 @@ impl<'a> StateWorkingSet<'a> { .scope .last_mut() .expect("internal error: missing required scope frame"); + + // reset "hiding strength" to 0 => not hidden + if let Some(strength) = scope_frame.hiding.get_mut(&name) { + *strength = 0; + // println!(" strength: {}", strength); + } + scope_frame.decls.insert(name, decl_id); decl_id } + pub fn hide_decl(&mut self, name: Vec) { + let scope_frame = self + .delta + .scope + .last_mut() + .expect("internal error: missing required scope frame"); + + if let Some(strength) = scope_frame.hiding.get_mut(&name) { + *strength += 1; + // println!("hiding {}, strength: {}", String::from_utf8_lossy(&name), strength); + } else { + // println!("hiding {}, strength: 1", String::from_utf8_lossy(&name)); + scope_frame.hiding.insert(name, 1); + } + + // println!("hiding map: {:?}", scope_frame.hiding); + } + pub fn add_block(&mut self, block: Block) -> BlockId { self.delta.blocks.push(block); @@ -401,15 +448,48 @@ impl<'a> StateWorkingSet<'a> { } pub fn find_decl(&self, name: &[u8]) -> Option { + let mut hiding_strength = 0; + // println!("set: starting finding {}", String::from_utf8_lossy(&name)); + for scope in self.delta.scope.iter().rev() { + // println!("delta frame"); + // println!("hiding map: {:?}", scope.hiding); + // check if we're hiding the declin this scope + if let Some(strength) = scope.hiding.get(name) { + hiding_strength += strength; + // println!(" was hiding, strength {}", hiding_strength); + } + if let Some(decl_id) = scope.decls.get(name) { - return Some(*decl_id); + // if we're hiding this decl, do not return it and reduce the hiding strength + if hiding_strength > 0 { + hiding_strength -= 1; + // println!(" decl found, strength {}", hiding_strength); + } else { + // println!(" decl found, return"); + return Some(*decl_id); + } } } for scope in self.permanent_state.scope.iter().rev() { + // println!("perma frame"); + // println!("hiding map: {:?}", scope.hiding); + // check if we're hiding the declin this scope + if let Some(strength) = scope.hiding.get(name) { + hiding_strength += strength; + // println!(" was hiding, strength {}", hiding_strength); + } + if let Some(decl_id) = scope.decls.get(name) { - return Some(*decl_id); + // if we're hiding this decl, do not return it and reduce the hiding strength + if hiding_strength > 0 { + hiding_strength -= 1; + // println!(" decl found, strength {}", hiding_strength); + } else { + // println!(" decl found, return"); + return Some(*decl_id); + } } } From 244289c9012e1936b525f7c74ad7186176b9f1d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Tue, 28 Sep 2021 23:32:15 +0300 Subject: [PATCH 06/26] Add missing file --- crates/nu-command/src/hide.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 crates/nu-command/src/hide.rs diff --git a/crates/nu-command/src/hide.rs b/crates/nu-command/src/hide.rs new file mode 100644 index 0000000000..9c9d611e12 --- /dev/null +++ b/crates/nu-command/src/hide.rs @@ -0,0 +1,28 @@ +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EvaluationContext}; +use nu_protocol::{Signature, SyntaxShape, Value}; + +pub struct Hide; + +impl Command for Hide { + fn name(&self) -> &str { + "hide" + } + + fn usage(&self) -> &str { + "Hide definitions in the current scope" + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("hide").required("pattern", SyntaxShape::String, "import pattern") + } + + fn run( + &self, + _context: &EvaluationContext, + call: &Call, + _input: Value, + ) -> Result { + Ok(Value::Nothing { span: call.head }) + } +} From 8ed6afe1e568e18efc3e27cf6aba38d049f7f7a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Tue, 28 Sep 2021 23:40:03 +0300 Subject: [PATCH 07/26] Fix tests failing without export --- src/tests.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/tests.rs b/src/tests.rs index f47daaee27..77e888a893 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -346,7 +346,7 @@ fn better_block_types() -> TestResult { #[test] fn module_imports_1() -> TestResult { run_test( - r#"module foo { def a [] { 1 }; def b [] { 2 } }; use foo; foo.a"#, + r#"module foo { export def a [] { 1 }; def b [] { 2 } }; use foo; foo.a"#, "1", ) } @@ -354,7 +354,7 @@ fn module_imports_1() -> TestResult { #[test] fn module_imports_2() -> TestResult { run_test( - r#"module foo { def a [] { 1 }; def b [] { 2 } }; use foo.a; a"#, + r#"module foo { export def a [] { 1 }; def b [] { 2 } }; use foo.a; a"#, "1", ) } @@ -362,7 +362,7 @@ fn module_imports_2() -> TestResult { #[test] fn module_imports_3() -> TestResult { run_test( - r#"module foo { def a [] { 1 }; def b [] { 2 } }; use foo.*; b"#, + r#"module foo { export def a [] { 1 }; export def b [] { 2 } }; use foo.*; b"#, "2", ) } @@ -370,7 +370,7 @@ fn module_imports_3() -> TestResult { #[test] fn module_imports_4() -> TestResult { fail_test( - r#"module foo { def a [] { 1 }; def b [] { 2 } }; use foo.c"#, + r#"module foo { export def a [] { 1 }; export def b [] { 2 } }; use foo.c"#, "not find import", ) } @@ -378,7 +378,7 @@ fn module_imports_4() -> TestResult { #[test] fn module_imports_5() -> TestResult { run_test( - r#"module foo { def a [] { 1 }; def b [] { 2 }; def c [] { 3 } }; use foo.[a, c]; c"#, + r#"module foo { export def a [] { 1 }; def b [] { 2 }; export def c [] { 3 } }; use foo.[a, c]; c"#, "3", ) } From aa06a71e1ffd7e7babf46a886ebdd1744817d04d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Wed, 29 Sep 2021 21:56:59 +0300 Subject: [PATCH 08/26] Move new commands to the new structure --- crates/nu-command/src/{ => core_commands}/export_def.rs | 0 crates/nu-command/src/{ => core_commands}/hide.rs | 0 crates/nu-command/src/core_commands/mod.rs | 4 ++++ 3 files changed, 4 insertions(+) rename crates/nu-command/src/{ => core_commands}/export_def.rs (100%) rename crates/nu-command/src/{ => core_commands}/hide.rs (100%) diff --git a/crates/nu-command/src/export_def.rs b/crates/nu-command/src/core_commands/export_def.rs similarity index 100% rename from crates/nu-command/src/export_def.rs rename to crates/nu-command/src/core_commands/export_def.rs diff --git a/crates/nu-command/src/hide.rs b/crates/nu-command/src/core_commands/hide.rs similarity index 100% rename from crates/nu-command/src/hide.rs rename to crates/nu-command/src/core_commands/hide.rs diff --git a/crates/nu-command/src/core_commands/mod.rs b/crates/nu-command/src/core_commands/mod.rs index ac2d4af449..a16b748afd 100644 --- a/crates/nu-command/src/core_commands/mod.rs +++ b/crates/nu-command/src/core_commands/mod.rs @@ -1,6 +1,8 @@ mod alias; mod def; mod do_; +mod export_def; +mod hide; mod if_; mod let_; mod module; @@ -9,6 +11,8 @@ mod use_; pub use alias::Alias; pub use def::Def; pub use do_::Do; +pub use export_def::ExportDef; +pub use hide::Hide; pub use if_::If; pub use let_::Let; pub use module::Module; From 2af8116f509832112eb88953bda3f365769001c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Fri, 1 Oct 2021 22:29:24 +0300 Subject: [PATCH 09/26] Fix hiding logic; Fix hiding with predecls * Hiding logic is simplified and fixed so you can hide and unhide the same def repeatedly. * Separates predeclared ids into its own data structure to protect them from hiding. Otherwise, you could hide the predeclared variable and the actual def would panic. --- crates/nu-parser/src/parse_keywords.rs | 35 +++-- crates/nu-protocol/src/engine/engine_state.rs | 135 +++++++++--------- 2 files changed, 89 insertions(+), 81 deletions(-) diff --git a/crates/nu-parser/src/parse_keywords.rs b/crates/nu-parser/src/parse_keywords.rs index c27c66d637..3195acd785 100644 --- a/crates/nu-parser/src/parse_keywords.rs +++ b/crates/nu-parser/src/parse_keywords.rs @@ -42,7 +42,7 @@ pub fn parse_def_predecl(working_set: &mut StateWorkingSet, spans: &[Span]) { signature.name = name; let decl = signature.predeclare(); - working_set.add_decl(decl); + working_set.add_predecl(decl); } } } @@ -95,15 +95,15 @@ pub fn parse_def( call.positional.push(block); if let (Some(name), Some(mut signature), Some(block_id)) = - (name, signature, block_id) + (&name, signature, block_id) { let decl_id = working_set - .find_decl(name.as_bytes()) + .find_predecl(name.as_bytes()) .expect("internal error: predeclaration failed to add definition"); let declaration = working_set.get_decl_mut(decl_id); - signature.name = name; + signature.name = name.clone(); *declaration = signature.into_block_command(block_id); } @@ -118,6 +118,19 @@ pub fn parse_def( } working_set.exit_scope(); + if let Some(name) = name { + // It's OK if it returns None: The decl was already merged in previous parse + // pass. + working_set.merge_predecl(name.as_bytes()); + } else { + error = error.or_else(|| { + Some(ParseError::UnknownState( + "Could not get string from string expression".into(), + *name_span, + )) + }); + } + call } else { let err_span = Span { @@ -581,18 +594,10 @@ pub fn parse_hide( let name_bytes: Vec = working_set.get_span_contents(spans[1]).into(); // TODO: Do the import pattern stuff for bulk-hiding - // TODO: move this error into error = error.or pattern - let _decl_id = if let Some(id) = working_set.find_decl(&name_bytes) { - id - } else { - return ( - garbage_statement(spans), - Some(ParseError::UnknownCommand(spans[1])), - ); - }; - // Hide the definitions - working_set.hide_decl(name_bytes); + if working_set.hide_decl(&name_bytes).is_none() { + error = error.or_else(|| Some(ParseError::UnknownCommand(spans[1]))); + } // Create the Hide command call let hide_decl_id = working_set diff --git a/crates/nu-protocol/src/engine/engine_state.rs b/crates/nu-protocol/src/engine/engine_state.rs index 9ddb768449..e1dcb7024d 100644 --- a/crates/nu-protocol/src/engine/engine_state.rs +++ b/crates/nu-protocol/src/engine/engine_state.rs @@ -1,7 +1,10 @@ use super::Command; use crate::{ast::Block, BlockId, DeclId, Span, Type, VarId}; use core::panic; -use std::{collections::HashMap, slice::Iter}; +use std::{ + collections::{HashMap, HashSet}, + slice::Iter, +}; pub struct EngineState { files: Vec<(String, usize, usize)>, @@ -18,7 +21,7 @@ pub struct ScopeFrame { decls: HashMap, DeclId>, aliases: HashMap, Vec>, modules: HashMap, BlockId>, - hiding: HashMap, usize>, // defines what is being hidden and its "hiding strength" + hiding: HashSet, } impl ScopeFrame { @@ -28,7 +31,7 @@ impl ScopeFrame { decls: HashMap::new(), aliases: HashMap::new(), modules: HashMap::new(), - hiding: HashMap::new(), + hiding: HashSet::new(), } } @@ -84,7 +87,7 @@ impl EngineState { last.modules.insert(item.0, item.1); } for item in first.hiding.into_iter() { - last.hiding.insert(item.0, item.1); + last.hiding.insert(item); } } } @@ -129,21 +132,13 @@ impl EngineState { } pub fn find_decl(&self, name: &[u8]) -> Option { - let mut hiding_strength = 0; - // println!("state: starting finding {}", String::from_utf8_lossy(&name)); + let mut hiding: HashSet = HashSet::new(); for scope in self.scope.iter().rev() { - // println!("hiding map: {:?}", scope.hiding); - // check if we're hiding the declin this scope - if let Some(strength) = scope.hiding.get(name) { - hiding_strength += strength; - } + hiding.extend(&scope.hiding); if let Some(decl_id) = scope.decls.get(name) { - // if we're hiding this decl, do not return it and reduce the hiding strength - if hiding_strength > 0 { - hiding_strength -= 1; - } else { + if !hiding.contains(decl_id) { return Some(*decl_id); } } @@ -242,9 +237,10 @@ pub struct StateWorkingSet<'a> { pub struct StateDelta { files: Vec<(String, usize, usize)>, pub(crate) file_contents: Vec, - vars: Vec, // indexed by VarId - decls: Vec>, // indexed by DeclId - blocks: Vec, // indexed by BlockId + vars: Vec, // indexed by VarId + decls: Vec>, // indexed by DeclId + blocks: Vec, // indexed by BlockId + predecls: HashMap, DeclId>, // this should get erased after every def call pub scope: Vec, } @@ -262,12 +258,10 @@ impl StateDelta { } pub fn enter_scope(&mut self) { - // println!("enter scope"); self.scope.push(ScopeFrame::new()); } pub fn exit_scope(&mut self) { - // println!("exit scope"); self.scope.pop(); } } @@ -280,6 +274,7 @@ impl<'a> StateWorkingSet<'a> { file_contents: vec![], vars: vec![], decls: vec![], + predecls: HashMap::new(), blocks: vec![], scope: vec![ScopeFrame::new()], }, @@ -301,7 +296,6 @@ impl<'a> StateWorkingSet<'a> { pub fn add_decl(&mut self, decl: Box) -> DeclId { let name = decl.name().as_bytes().to_vec(); - // println!("adding {}", String::from_utf8_lossy(&name)); self.delta.decls.push(decl); let decl_id = self.num_decls() - 1; @@ -312,33 +306,63 @@ impl<'a> StateWorkingSet<'a> { .last_mut() .expect("internal error: missing required scope frame"); - // reset "hiding strength" to 0 => not hidden - if let Some(strength) = scope_frame.hiding.get_mut(&name) { - *strength = 0; - // println!(" strength: {}", strength); - } - scope_frame.decls.insert(name, decl_id); decl_id } - pub fn hide_decl(&mut self, name: Vec) { - let scope_frame = self + pub fn add_predecl(&mut self, decl: Box) { + let name = decl.name().as_bytes().to_vec(); + + self.delta.decls.push(decl); + let decl_id = self.num_decls() - 1; + + self.delta.predecls.insert(name, decl_id); + } + + pub fn find_predecl(&mut self, name: &[u8]) -> Option { + self.delta.predecls.get(name).copied() + } + + pub fn merge_predecl(&mut self, name: &[u8]) -> Option { + if let Some(decl_id) = self.delta.predecls.remove(name) { + let scope_frame = self + .delta + .scope + .last_mut() + .expect("internal error: missing required scope frame"); + + scope_frame.decls.insert(name.into(), decl_id); + + return Some(decl_id); + } + + None + } + + pub fn hide_decl(&mut self, name: &[u8]) -> Option { + // Since we can mutate scope frames in delta, remove the id directly + for scope in self.delta.scope.iter_mut().rev() { + if let Some(decl_id) = scope.decls.remove(name) { + return Some(decl_id); + } + } + + // We cannot mutate the permanent state => store the information in the current scope frame + let last_scope_frame = self .delta .scope .last_mut() .expect("internal error: missing required scope frame"); - if let Some(strength) = scope_frame.hiding.get_mut(&name) { - *strength += 1; - // println!("hiding {}, strength: {}", String::from_utf8_lossy(&name), strength); - } else { - // println!("hiding {}, strength: 1", String::from_utf8_lossy(&name)); - scope_frame.hiding.insert(name, 1); + for scope in self.permanent_state.scope.iter().rev() { + if let Some(decl_id) = scope.decls.get(name) { + last_scope_frame.hiding.insert(*decl_id); + return Some(*decl_id); + } } - // println!("hiding map: {:?}", scope_frame.hiding); + None } pub fn add_block(&mut self, block: Block) -> BlockId { @@ -448,46 +472,25 @@ impl<'a> StateWorkingSet<'a> { } pub fn find_decl(&self, name: &[u8]) -> Option { - let mut hiding_strength = 0; - // println!("set: starting finding {}", String::from_utf8_lossy(&name)); + let mut hiding: HashSet = HashSet::new(); + + if let Some(decl_id) = self.delta.predecls.get(name) { + return Some(*decl_id); + } for scope in self.delta.scope.iter().rev() { - // println!("delta frame"); - // println!("hiding map: {:?}", scope.hiding); - // check if we're hiding the declin this scope - if let Some(strength) = scope.hiding.get(name) { - hiding_strength += strength; - // println!(" was hiding, strength {}", hiding_strength); - } + hiding.extend(&scope.hiding); if let Some(decl_id) = scope.decls.get(name) { - // if we're hiding this decl, do not return it and reduce the hiding strength - if hiding_strength > 0 { - hiding_strength -= 1; - // println!(" decl found, strength {}", hiding_strength); - } else { - // println!(" decl found, return"); - return Some(*decl_id); - } + return Some(*decl_id); } } for scope in self.permanent_state.scope.iter().rev() { - // println!("perma frame"); - // println!("hiding map: {:?}", scope.hiding); - // check if we're hiding the declin this scope - if let Some(strength) = scope.hiding.get(name) { - hiding_strength += strength; - // println!(" was hiding, strength {}", hiding_strength); - } + hiding.extend(&scope.hiding); if let Some(decl_id) = scope.decls.get(name) { - // if we're hiding this decl, do not return it and reduce the hiding strength - if hiding_strength > 0 { - hiding_strength -= 1; - // println!(" decl found, strength {}", hiding_strength); - } else { - // println!(" decl found, return"); + if !hiding.contains(decl_id) { return Some(*decl_id); } } From 25b05dec9e612da9116b09f0c2e234e7eef8f2b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Fri, 1 Oct 2021 23:16:27 +0300 Subject: [PATCH 10/26] Fix panic on double def; Tests; Double def error * Fixes a panic with defining two commands with the same name caused by declaration not found after predeclaration. * Adds a new error if a custom command is defined more than once in one block. * Add some tests --- crates/nu-parser/src/errors.rs | 4 ++++ crates/nu-parser/src/parse_keywords.rs | 10 +++++++--- crates/nu-parser/src/parser.rs | 8 +++++--- crates/nu-protocol/src/engine/engine_state.rs | 8 ++------ 4 files changed, 18 insertions(+), 12 deletions(-) diff --git a/crates/nu-parser/src/errors.rs b/crates/nu-parser/src/errors.rs index f55fed2da7..444873d1ae 100644 --- a/crates/nu-parser/src/errors.rs +++ b/crates/nu-parser/src/errors.rs @@ -77,6 +77,10 @@ pub enum ParseError { #[diagnostic(code(nu::parser::module_not_found), url(docsrs))] ModuleNotFound(#[label = "module not found"] Span), + #[error("Duplicate command definition within a block.")] + #[diagnostic(code(nu::parser::duplicate_command_def), url(docsrs))] + DuplicateCommandDef(#[label = "defined more than once"] Span), + #[error("Unknown command.")] #[diagnostic( code(nu::parser::unknown_command), diff --git a/crates/nu-parser/src/parse_keywords.rs b/crates/nu-parser/src/parse_keywords.rs index 3195acd785..6eb40daad0 100644 --- a/crates/nu-parser/src/parse_keywords.rs +++ b/crates/nu-parser/src/parse_keywords.rs @@ -13,7 +13,7 @@ use crate::{ ParseError, }; -pub fn parse_def_predecl(working_set: &mut StateWorkingSet, spans: &[Span]) { +pub fn parse_def_predecl(working_set: &mut StateWorkingSet, spans: &[Span]) -> Option { let name = working_set.get_span_contents(spans[0]); // handle "export def" same as "def" @@ -42,9 +42,13 @@ pub fn parse_def_predecl(working_set: &mut StateWorkingSet, spans: &[Span]) { signature.name = name; let decl = signature.predeclare(); - working_set.add_predecl(decl); + if working_set.add_predecl(decl).is_some() { + return Some(ParseError::DuplicateCommandDef(spans[1])); + } } } + + None } pub fn parse_def( @@ -98,7 +102,7 @@ pub fn parse_def( (&name, signature, block_id) { let decl_id = working_set - .find_predecl(name.as_bytes()) + .find_decl(name.as_bytes()) .expect("internal error: predeclaration failed to add definition"); let declaration = working_set.get_decl_mut(decl_id); diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index d3464869f4..79213fdf7b 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -2606,16 +2606,18 @@ pub fn parse_block( working_set.enter_scope(); } + let mut error = None; + // Pre-declare any definition so that definitions // that share the same block can see each other for pipeline in &lite_block.block { if pipeline.commands.len() == 1 { - parse_def_predecl(working_set, &pipeline.commands[0].parts); + if let Some(err) = parse_def_predecl(working_set, &pipeline.commands[0].parts) { + error = error.or(Some(err)); + } } } - let mut error = None; - let block: Block = lite_block .block .iter() diff --git a/crates/nu-protocol/src/engine/engine_state.rs b/crates/nu-protocol/src/engine/engine_state.rs index e1dcb7024d..0653ed73c9 100644 --- a/crates/nu-protocol/src/engine/engine_state.rs +++ b/crates/nu-protocol/src/engine/engine_state.rs @@ -311,17 +311,13 @@ impl<'a> StateWorkingSet<'a> { decl_id } - pub fn add_predecl(&mut self, decl: Box) { + pub fn add_predecl(&mut self, decl: Box) -> Option { let name = decl.name().as_bytes().to_vec(); self.delta.decls.push(decl); let decl_id = self.num_decls() - 1; - self.delta.predecls.insert(name, decl_id); - } - - pub fn find_predecl(&mut self, name: &[u8]) -> Option { - self.delta.predecls.get(name).copied() + self.delta.predecls.insert(name, decl_id) } pub fn merge_predecl(&mut self, name: &[u8]) -> Option { From 891d79d2aae3f9479223ce26dee39add166c7bdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Fri, 1 Oct 2021 23:25:48 +0300 Subject: [PATCH 11/26] Fmt and misc fixes after rebase --- crates/nu-command/src/default_context.rs | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 42b30cf877..a2c888e269 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -6,8 +6,9 @@ use nu_protocol::{ }; use crate::{ - Alias, Benchmark, BuildString, Def, Do, Each, ExportDef, External, For, From, FromJson, Git, GitCheckout, - Hide, If, Length, Let, LetEnv, Lines, ListGitBranches, Ls, Module, Sys, Table, Use, Where, + Alias, Benchmark, BuildString, Def, Do, Each, ExportDef, External, For, From, FromJson, Git, + GitCheckout, Hide, If, Length, Let, LetEnv, Lines, ListGitBranches, Ls, Module, Sys, Table, + Use, Where, }; pub fn create_default_context() -> Rc> { @@ -20,17 +21,10 @@ pub fn create_default_context() -> Rc> { Signature::build("where").required("cond", SyntaxShape::RowCondition, "condition"); working_set.add_decl(sig.predeclare()); - working_set.add_decl(Box::new(If)); - - working_set.add_decl(Box::new(Let)); - - working_set.add_decl(Box::new(LetEnv)); - working_set.add_decl(Box::new(Alias)); working_set.add_decl(Box::new(Benchmark)); working_set.add_decl(Box::new(BuildString)); - - working_set.add_decl(Box::new(Def)); + working_set.add_decl(Box::new(Def)); working_set.add_decl(Box::new(Do)); working_set.add_decl(Box::new(Each)); working_set.add_decl(Box::new(ExportDef)); @@ -49,7 +43,7 @@ pub fn create_default_context() -> Rc> { working_set.add_decl(Box::new(Sys)); working_set.add_decl(Box::new(Table)); working_set.add_decl(Box::new(Use)); - working_set.add_decl(Box::new(Where)); + working_set.add_decl(Box::new(Where)); // This is a WIP proof of concept working_set.add_decl(Box::new(ListGitBranches)); From fb0f83e5742103a53a28f312b1d0f94f03a0004d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Sat, 2 Oct 2021 00:12:30 +0300 Subject: [PATCH 12/26] Disallow hiding the same def twice; Add tests Tests got removed after rebase. --- crates/nu-protocol/src/engine/engine_state.rs | 13 +++- src/tests.rs | 64 +++++++++++++++++++ 2 files changed, 75 insertions(+), 2 deletions(-) diff --git a/crates/nu-protocol/src/engine/engine_state.rs b/crates/nu-protocol/src/engine/engine_state.rs index 0653ed73c9..ea75692ebd 100644 --- a/crates/nu-protocol/src/engine/engine_state.rs +++ b/crates/nu-protocol/src/engine/engine_state.rs @@ -337,8 +337,12 @@ impl<'a> StateWorkingSet<'a> { } pub fn hide_decl(&mut self, name: &[u8]) -> Option { + let mut hiding: HashSet = HashSet::new(); + // Since we can mutate scope frames in delta, remove the id directly for scope in self.delta.scope.iter_mut().rev() { + hiding.extend(&scope.hiding); + if let Some(decl_id) = scope.decls.remove(name) { return Some(decl_id); } @@ -352,9 +356,14 @@ impl<'a> StateWorkingSet<'a> { .expect("internal error: missing required scope frame"); for scope in self.permanent_state.scope.iter().rev() { + hiding.extend(&scope.hiding); + if let Some(decl_id) = scope.decls.get(name) { - last_scope_frame.hiding.insert(*decl_id); - return Some(*decl_id); + if !hiding.contains(decl_id) { + // Do not hide already hidden decl + last_scope_frame.hiding.insert(*decl_id); + return Some(*decl_id); + } } } diff --git a/src/tests.rs b/src/tests.rs index 77e888a893..c947d1a227 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -383,6 +383,70 @@ fn module_imports_5() -> TestResult { ) } +#[test] +fn module_import_uses_internal_command() -> TestResult { + run_test( + r#"module foo { def b [] { 2 }; export def a [] { b } }; use foo; foo.a"#, + "2", + ) +} + +#[test] +fn hides_def() -> TestResult { + fail_test( + r#"def foo [] { "foo" }; hide foo; foo"#, + "command not found", + ) +} + +#[test] +fn hides_def_then_redefines() -> TestResult { + fail_test( + r#"def foo [] { "foo" }; hide foo; def foo [] { "bar" }; foo"#, + "defined more than once", + ) +} + +#[test] +fn hides_def_in_scope_1() -> TestResult { + fail_test( + r#"def foo [] { "foo" }; do { hide foo; foo }"#, + "command not found", + ) +} + +#[test] +fn hides_def_in_scope_2() -> TestResult { + run_test( + r#"def foo [] { "foo" }; do { def foo [] { "bar" }; hide foo; foo }"#, + "foo", + ) +} + +#[test] +fn hides_def_in_scope_3() -> TestResult { + fail_test( + r#"def foo [] { "foo" }; do { hide foo; def foo [] { "bar" }; hide foo; foo }"#, + "command not found", + ) +} + +#[test] +fn hides_def_in_scope_4() -> TestResult { + fail_test( + r#"def foo [] { "foo" }; do { def foo [] { "bar" }; hide foo; hide foo; foo }"#, + "command not found", + ) +} + +#[test] +fn hide_twice_not_allowed() -> TestResult { + fail_test( + r#"def foo [] { "foo" }; hide foo; hide foo"#, + "unknown command", + ) +} + #[test] fn from_json_1() -> TestResult { run_test(r#"('{"name": "Fred"}' | from json).name"#, "Fred") From 2c1b074bdc72aec60f29fe962525fd24b895d92d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Sat, 2 Oct 2021 00:17:02 +0300 Subject: [PATCH 13/26] Add test for double def --- src/tests.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/tests.rs b/src/tests.rs index c947d1a227..056cac1a93 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -447,6 +447,14 @@ fn hide_twice_not_allowed() -> TestResult { ) } +#[test] +fn def_twice_should_fail() -> TestResult { + fail_test( + r#"def foo [] { "foo" }; def foo [] { "bar" }"#, + "defined more than once", + ) +} + #[test] fn from_json_1() -> TestResult { run_test(r#"('{"name": "Fred"}' | from json).name"#, "Fred") From 6595c0659891235946ffa995e91cabe383a6caa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Sat, 2 Oct 2021 03:42:35 +0300 Subject: [PATCH 14/26] Relax panic into error Convert the panic when declaration cannot find predeclaration into an error. This error is already covered and reported in the predeclaration phase. --- crates/nu-parser/src/parse_keywords.rs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/crates/nu-parser/src/parse_keywords.rs b/crates/nu-parser/src/parse_keywords.rs index 6eb40daad0..033a21e738 100644 --- a/crates/nu-parser/src/parse_keywords.rs +++ b/crates/nu-parser/src/parse_keywords.rs @@ -101,15 +101,20 @@ pub fn parse_def( if let (Some(name), Some(mut signature), Some(block_id)) = (&name, signature, block_id) { - let decl_id = working_set - .find_decl(name.as_bytes()) - .expect("internal error: predeclaration failed to add definition"); + if let Some(decl_id) = working_set.find_decl(name.as_bytes()) { + let declaration = working_set.get_decl_mut(decl_id); - let declaration = working_set.get_decl_mut(decl_id); + signature.name = name.clone(); - signature.name = name.clone(); - - *declaration = signature.into_block_command(block_id); + *declaration = signature.into_block_command(block_id); + } else { + error = error.or_else(|| { + Some(ParseError::UnknownState( + "Could not define hidden command".into(), + spans[1], + )) + }); + }; } } else { let err_span = Span { From 5843acec026d078022b46e2a2ba7c36b5591521e Mon Sep 17 00:00:00 2001 From: JT Date: Sat, 2 Oct 2021 15:59:11 +1300 Subject: [PATCH 15/26] Add wrap and get and cell_path parsing --- crates/nu-command/src/default_context.rs | 7 +- crates/nu-command/src/filesystem/ls.rs | 4 +- crates/nu-command/src/filters/get.rs | 35 ++++++ crates/nu-command/src/filters/mod.rs | 4 + crates/nu-command/src/filters/wrap.rs | 59 ++++++++++ crates/nu-command/src/system/sys.rs | 9 +- crates/nu-command/src/viewers/table.rs | 2 +- crates/nu-engine/src/call_ext.rs | 33 ++++++ crates/nu-engine/src/eval.rs | 4 + crates/nu-engine/src/from_value.rs | 32 +++--- crates/nu-parser/src/flatten.rs | 10 ++ crates/nu-parser/src/parser.rs | 132 ++++++++++++++--------- crates/nu-protocol/src/ast/call.rs | 4 + crates/nu-protocol/src/ast/cell_path.rs | 5 +- crates/nu-protocol/src/ast/expr.rs | 3 +- crates/nu-protocol/src/value/mod.rs | 11 +- crates/nu-table/src/table.rs | 4 + src/tests.rs | 13 +++ 18 files changed, 290 insertions(+), 81 deletions(-) create mode 100644 crates/nu-command/src/filters/get.rs create mode 100644 crates/nu-command/src/filters/wrap.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index ac13fbe56c..818d278ff0 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -5,10 +5,7 @@ use nu_protocol::{ Signature, }; -use crate::{ - Alias, Benchmark, BuildString, Def, Do, Each, External, For, From, FromJson, Git, GitCheckout, - Help, If, Length, Let, LetEnv, Lines, ListGitBranches, Ls, Module, Ps, Sys, Table, Use, Where, -}; +use crate::*; pub fn create_default_context() -> Rc> { let engine_state = Rc::new(RefCell::new(EngineState::new())); @@ -26,6 +23,7 @@ pub fn create_default_context() -> Rc> { working_set.add_decl(Box::new(For)); working_set.add_decl(Box::new(From)); working_set.add_decl(Box::new(FromJson)); + working_set.add_decl(Box::new(Get)); working_set.add_decl(Box::new(Help)); working_set.add_decl(Box::new(If)); working_set.add_decl(Box::new(Length)); @@ -39,6 +37,7 @@ pub fn create_default_context() -> Rc> { working_set.add_decl(Box::new(Table)); working_set.add_decl(Box::new(Use)); working_set.add_decl(Box::new(Where)); + working_set.add_decl(Box::new(Wrap)); // This is a WIP proof of concept working_set.add_decl(Box::new(ListGitBranches)); diff --git a/crates/nu-command/src/filesystem/ls.rs b/crates/nu-command/src/filesystem/ls.rs index c8b00a8fb5..2e243e05f4 100644 --- a/crates/nu-command/src/filesystem/ls.rs +++ b/crates/nu-command/src/filesystem/ls.rs @@ -63,8 +63,8 @@ impl Command for Ls { } else { Value::Nothing { span: call_span } }, - Value::Int { - val: filesize as i64, + Value::Filesize { + val: filesize, span: call_span, }, ], diff --git a/crates/nu-command/src/filters/get.rs b/crates/nu-command/src/filters/get.rs new file mode 100644 index 0000000000..c06badbb26 --- /dev/null +++ b/crates/nu-command/src/filters/get.rs @@ -0,0 +1,35 @@ +use nu_engine::CallExt; +use nu_protocol::ast::{Call, CellPath}; +use nu_protocol::engine::{Command, EvaluationContext}; +use nu_protocol::{Signature, SyntaxShape, Value}; + +pub struct Get; + +impl Command for Get { + fn name(&self) -> &str { + "get" + } + + fn usage(&self) -> &str { + "Extract data using a cell path." + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("wrap").required( + "cell_path", + SyntaxShape::CellPath, + "the cell path to the data", + ) + } + + fn run( + &self, + context: &EvaluationContext, + call: &Call, + input: Value, + ) -> Result { + let cell_path: CellPath = call.req(context, 0)?; + + input.follow_cell_path(&cell_path.members) + } +} diff --git a/crates/nu-command/src/filters/mod.rs b/crates/nu-command/src/filters/mod.rs index 143a57b65d..ba3508fd6d 100644 --- a/crates/nu-command/src/filters/mod.rs +++ b/crates/nu-command/src/filters/mod.rs @@ -1,11 +1,15 @@ mod each; mod for_; +mod get; mod length; mod lines; mod where_; +mod wrap; pub use each::Each; pub use for_::For; +pub use get::Get; pub use length::Length; pub use lines::Lines; pub use where_::Where; +pub use wrap::Wrap; diff --git a/crates/nu-command/src/filters/wrap.rs b/crates/nu-command/src/filters/wrap.rs new file mode 100644 index 0000000000..f23624ad23 --- /dev/null +++ b/crates/nu-command/src/filters/wrap.rs @@ -0,0 +1,59 @@ +use nu_engine::CallExt; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EvaluationContext}; +use nu_protocol::{IntoValueStream, Signature, SyntaxShape, Value}; + +pub struct Wrap; + +impl Command for Wrap { + fn name(&self) -> &str { + "wrap" + } + + fn usage(&self) -> &str { + "Wrap the value into a column." + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("wrap").required("name", SyntaxShape::String, "the name of the column") + } + + fn run( + &self, + context: &EvaluationContext, + call: &Call, + input: Value, + ) -> Result { + let span = call.head; + let name: String = call.req(context, 0)?; + + match input { + Value::List { vals, .. } => Ok(Value::List { + vals: vals + .into_iter() + .map(move |x| Value::Record { + cols: vec![name.clone()], + vals: vec![x], + span, + }) + .collect(), + span, + }), + Value::Stream { stream, .. } => Ok(Value::Stream { + stream: stream + .map(move |x| Value::Record { + cols: vec![name.clone()], + vals: vec![x], + span, + }) + .into_value_stream(), + span, + }), + _ => Ok(Value::Record { + cols: vec![name], + vals: vec![input], + span, + }), + } + } +} diff --git a/crates/nu-command/src/system/sys.rs b/crates/nu-command/src/system/sys.rs index 0ceab55abe..c3bf8591dd 100644 --- a/crates/nu-command/src/system/sys.rs +++ b/crates/nu-command/src/system/sys.rs @@ -274,10 +274,11 @@ pub fn host(sys: &mut System, span: Span) -> Option { span, }); } - // dict.insert_untagged( - // "uptime", - // UntaggedValue::duration(1000000000 * sys.uptime() as i64), - // ); + cols.push("uptime".into()); + vals.push(Value::Duration { + val: 1000000000 * sys.uptime() as u64, + span, + }); let mut users = vec![]; for user in sys.users() { diff --git a/crates/nu-command/src/viewers/table.rs b/crates/nu-command/src/viewers/table.rs index 2a43bcee26..e3b64e34ee 100644 --- a/crates/nu-command/src/viewers/table.rs +++ b/crates/nu-command/src/viewers/table.rs @@ -63,7 +63,7 @@ impl Command for Table { output.push(vec![ StyledString { contents: c, - style: nu_table::TextStyle::default_header(), + style: nu_table::TextStyle::default_field(), }, StyledString { contents: v.into_string(), diff --git a/crates/nu-engine/src/call_ext.rs b/crates/nu-engine/src/call_ext.rs index 279a47b38c..9e311600a2 100644 --- a/crates/nu-engine/src/call_ext.rs +++ b/crates/nu-engine/src/call_ext.rs @@ -14,6 +14,14 @@ pub trait CallExt { context: &EvaluationContext, starting_pos: usize, ) -> Result, ShellError>; + + fn opt( + &self, + context: &EvaluationContext, + pos: usize, + ) -> Result, ShellError>; + + fn req(&self, context: &EvaluationContext, pos: usize) -> Result; } impl CallExt for Call { @@ -44,4 +52,29 @@ impl CallExt for Call { Ok(output) } + + fn opt( + &self, + context: &EvaluationContext, + pos: usize, + ) -> Result, ShellError> { + if let Some(expr) = self.nth(pos) { + let result = eval_expression(context, &expr)?; + FromValue::from_value(&result).map(Some) + } else { + Ok(None) + } + } + + fn req(&self, context: &EvaluationContext, pos: usize) -> Result { + if let Some(expr) = self.nth(pos) { + let result = eval_expression(context, &expr)?; + FromValue::from_value(&result) + } else { + Err(ShellError::AccessBeyondEnd( + self.positional.len(), + self.head, + )) + } + } } diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index 2cfe44b379..04057e540e 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -156,6 +156,10 @@ pub fn eval_expression( Expr::Var(var_id) => context .get_var(*var_id) .map_err(move |_| ShellError::VariableNotFoundAtRuntime(expr.span)), + Expr::CellPath(cell_path) => Ok(Value::CellPath { + val: cell_path.clone(), + span: expr.span, + }), Expr::FullCellPath(cell_path) => { let value = eval_expression(context, &cell_path.head)?; diff --git a/crates/nu-engine/src/from_value.rs b/crates/nu-engine/src/from_value.rs index b65f9a5ec5..ceb92187b4 100644 --- a/crates/nu-engine/src/from_value.rs +++ b/crates/nu-engine/src/from_value.rs @@ -1,6 +1,7 @@ // use std::path::PathBuf; // use nu_path::expand_path; +use nu_protocol::ast::CellPath; use nu_protocol::ShellError; use nu_protocol::{Range, Spanned, Value}; @@ -110,28 +111,25 @@ impl FromValue for ColumnPath { } } -impl FromValue for bool { +*/ + +impl FromValue for CellPath { fn from_value(v: &Value) -> Result { match v { - Value { - value: UntaggedValue::Primitive(Primitive::Boolean(b)), - .. - } => Ok(*b), - Value { - value: UntaggedValue::Row(_), - .. - } => { - let mut shell_error = ShellError::type_error("boolean", v.spanned_type_name()); - shell_error.notes.push( - "Note: you can access columns using dot. eg) $it.column or (ls).column".into(), - ); - Err(shell_error) - } - v => Err(ShellError::type_error("boolean", v.spanned_type_name())), + Value::CellPath { val, .. } => Ok(val.clone()), + v => Err(ShellError::CantConvert("cell path".into(), v.span())), + } + } +} + +impl FromValue for bool { + fn from_value(v: &Value) -> Result { + match v { + Value::Bool { val, .. } => Ok(*val), + v => Err(ShellError::CantConvert("bool".into(), v.span())), } } } -*/ impl FromValue for Spanned { fn from_value(v: &Value) -> Result { diff --git a/crates/nu-parser/src/flatten.rs b/crates/nu-parser/src/flatten.rs index 7b94d007c9..fffb5626ec 100644 --- a/crates/nu-parser/src/flatten.rs +++ b/crates/nu-parser/src/flatten.rs @@ -79,6 +79,16 @@ pub fn flatten_expression( Expr::Float(_) => { vec![(expr.span, FlatShape::Float)] } + Expr::CellPath(cell_path) => { + let mut output = vec![]; + for path_element in &cell_path.members { + match path_element { + PathMember::String { span, .. } => output.push((*span, FlatShape::String)), + PathMember::Int { span, .. } => output.push((*span, FlatShape::Int)), + } + } + output + } Expr::FullCellPath(cell_path) => { let mut output = vec![]; output.extend(flatten_expression(working_set, &cell_path.head)); diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 59508cdfe2..db973c46cf 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -6,8 +6,8 @@ use crate::{ use nu_protocol::{ ast::{ - Block, Call, Expr, Expression, FullCellPath, ImportPattern, ImportPatternMember, Operator, - PathMember, Pipeline, RangeInclusion, RangeOperator, Statement, + Block, Call, CellPath, Expr, Expression, FullCellPath, ImportPattern, ImportPatternMember, + Operator, PathMember, Pipeline, RangeInclusion, RangeOperator, Statement, }, engine::StateWorkingSet, span, Flag, PositionalArg, Signature, Span, SyntaxShape, Type, VarId, @@ -1157,6 +1157,62 @@ pub fn parse_variable_expr( } } +pub fn parse_cell_path( + working_set: &mut StateWorkingSet, + tokens: impl Iterator, + mut expect_dot: bool, + span: Span, +) -> (Vec, Option) { + let mut error = None; + let mut tail = vec![]; + + for path_element in tokens { + let bytes = working_set.get_span_contents(path_element.span); + + if expect_dot { + expect_dot = false; + if bytes.len() != 1 || bytes[0] != b'.' { + error = error.or_else(|| Some(ParseError::Expected('.'.into(), path_element.span))); + } + } else { + expect_dot = true; + + match parse_int(bytes, path_element.span) { + ( + Expression { + expr: Expr::Int(val), + span, + .. + }, + None, + ) => tail.push(PathMember::Int { + val: val as usize, + span, + }), + _ => { + let (result, err) = parse_string(working_set, path_element.span); + error = error.or(err); + match result { + Expression { + expr: Expr::String(string), + span, + .. + } => { + tail.push(PathMember::String { val: string, span }); + } + _ => { + error = + error.or_else(|| Some(ParseError::Expected("string".into(), span))); + } + } + } + } + } + } + + (tail, error) +} + pub fn parse_full_cell_path( working_set: &mut StateWorkingSet, implicit_head: Option, @@ -1173,7 +1229,7 @@ pub fn parse_full_cell_path( let mut tokens = tokens.into_iter().peekable(); if let Some(head) = tokens.peek() { let bytes = working_set.get_span_contents(head.span); - let (head, mut expect_dot) = if bytes.starts_with(b"(") { + let (head, expect_dot) = if bytes.starts_with(b"(") { let mut start = head.span.start; let mut end = head.span.end; @@ -1247,52 +1303,8 @@ pub fn parse_full_cell_path( ); }; - let mut tail = vec![]; - - for path_element in tokens { - let bytes = working_set.get_span_contents(path_element.span); - - if expect_dot { - expect_dot = false; - if bytes.len() != 1 || bytes[0] != b'.' { - error = - error.or_else(|| Some(ParseError::Expected('.'.into(), path_element.span))); - } - } else { - expect_dot = true; - - match parse_int(bytes, path_element.span) { - ( - Expression { - expr: Expr::Int(val), - span, - .. - }, - None, - ) => tail.push(PathMember::Int { - val: val as usize, - span, - }), - _ => { - let (result, err) = parse_string(working_set, path_element.span); - error = error.or(err); - match result { - Expression { - expr: Expr::String(string), - span, - .. - } => { - tail.push(PathMember::String { val: string, span }); - } - _ => { - error = error - .or_else(|| Some(ParseError::Expected("string".into(), span))); - } - } - } - } - } - } + let (tail, err) = parse_cell_path(working_set, tokens, expect_dot, span); + error = error.or(err); ( Expression { @@ -2351,6 +2363,28 @@ pub fn parse_value( ) } } + SyntaxShape::CellPath => { + let source = working_set.get_span_contents(span); + let mut error = None; + + let (tokens, err) = lex(source, span.start, &[b'\n'], &[b'.']); + error = error.or(err); + + let tokens = tokens.into_iter().peekable(); + + let (cell_path, err) = parse_cell_path(working_set, tokens, false, span); + error = error.or(err); + + ( + Expression { + expr: Expr::CellPath(CellPath { members: cell_path }), + span, + ty: Type::CellPath, + custom_completion: None, + }, + error, + ) + } SyntaxShape::Any => { if bytes.starts_with(b"[") { parse_value(working_set, span, &SyntaxShape::Table) diff --git a/crates/nu-protocol/src/ast/call.rs b/crates/nu-protocol/src/ast/call.rs index c5ba21f68b..1f2a4870e7 100644 --- a/crates/nu-protocol/src/ast/call.rs +++ b/crates/nu-protocol/src/ast/call.rs @@ -45,4 +45,8 @@ impl Call { None } + + pub fn nth(&self, pos: usize) -> Option { + self.positional.get(pos).cloned() + } } diff --git a/crates/nu-protocol/src/ast/cell_path.rs b/crates/nu-protocol/src/ast/cell_path.rs index 26cefd8556..b6071b644c 100644 --- a/crates/nu-protocol/src/ast/cell_path.rs +++ b/crates/nu-protocol/src/ast/cell_path.rs @@ -1,13 +1,14 @@ use super::Expression; use crate::Span; +use serde::{Deserialize, Serialize}; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub enum PathMember { String { val: String, span: Span }, Int { val: usize, span: Span }, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, 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 62d7a25c8f..449f4687df 100644 --- a/crates/nu-protocol/src/ast/expr.rs +++ b/crates/nu-protocol/src/ast/expr.rs @@ -1,4 +1,4 @@ -use super::{Call, Expression, FullCellPath, Operator, RangeOperator}; +use super::{Call, CellPath, Expression, FullCellPath, Operator, RangeOperator}; use crate::{BlockId, Signature, Span, VarId}; #[derive(Debug, Clone)] @@ -24,6 +24,7 @@ pub enum Expr { Table(Vec, Vec>), Keyword(Vec, Span, Box), String(String), // FIXME: improve this in the future? + CellPath(CellPath), FullCellPath(Box), Signature(Box), Garbage, diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index 11730ccdec..aed1aa832a 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -9,7 +9,7 @@ pub use stream::*; use std::fmt::Debug; -use crate::ast::PathMember; +use crate::ast::{CellPath, PathMember}; use crate::{span, BlockId, Span, Type}; use crate::ShellError; @@ -72,6 +72,10 @@ pub enum Value { val: Vec, span: Span, }, + CellPath { + val: CellPath, + span: Span, + }, } impl Value { @@ -99,6 +103,7 @@ impl Value { Value::Stream { span, .. } => *span, Value::Nothing { span, .. } => *span, Value::Binary { span, .. } => *span, + Value::CellPath { span, .. } => *span, } } @@ -119,6 +124,7 @@ impl Value { Value::Nothing { span, .. } => *span = new_span, Value::Error { .. } => {} Value::Binary { span, .. } => *span = new_span, + Value::CellPath { span, .. } => *span = new_span, } self @@ -143,6 +149,7 @@ impl Value { Value::Stream { .. } => Type::ValueStream, Value::Error { .. } => Type::Error, Value::Binary { .. } => Type::Binary, + Value::CellPath { .. } => Type::CellPath, } } @@ -184,6 +191,7 @@ impl Value { Value::Nothing { .. } => String::new(), Value::Error { error } => format!("{:?}", error), Value::Binary { val, .. } => format!("{:?}", val), + Value::CellPath { val, .. } => format!("{:?}", val), } } @@ -215,6 +223,7 @@ impl Value { Value::Nothing { .. } => String::new(), Value::Error { error } => format!("{:?}", error), Value::Binary { val, .. } => format!("{:?}", val), + Value::CellPath { val, .. } => format!("{:?}", val), } } diff --git a/crates/nu-table/src/table.rs b/crates/nu-table/src/table.rs index 3110e2751d..0d731a6a1e 100644 --- a/crates/nu-table/src/table.rs +++ b/crates/nu-table/src/table.rs @@ -240,6 +240,10 @@ impl TextStyle { .bold(Some(true)) } + pub fn default_field() -> TextStyle { + TextStyle::new().fg(Color::Green).bold(Some(true)) + } + pub fn with_attributes(bo: bool, al: Alignment, co: Color) -> TextStyle { TextStyle::new().alignment(al).fg(co).bold(Some(bo)) } diff --git a/src/tests.rs b/src/tests.rs index f47daaee27..37adcb6095 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -396,3 +396,16 @@ fn from_json_2() -> TestResult { "Sally", ) } + +#[test] +fn wrap() -> TestResult { + run_test(r#"([1, 2, 3] | wrap foo).foo.1"#, "2") +} + +#[test] +fn get() -> TestResult { + run_test( + r#"[[name, grade]; [Alice, A], [Betty, B]] | get grade.1"#, + "B", + ) +} From 6b76dd7cd7edc28ecc25bac62a9a9210effd17c6 Mon Sep 17 00:00:00 2001 From: JT Date: Sat, 2 Oct 2021 17:55:05 +1300 Subject: [PATCH 16/26] Add select --- crates/nu-command/src/default_context.rs | 1 + crates/nu-command/src/filters/mod.rs | 2 + crates/nu-command/src/filters/select.rs | 182 +++++++++++++++++++++++ crates/nu-protocol/src/ast/cell_path.rs | 18 +++ crates/nu-protocol/src/value/mod.rs | 4 +- src/tests.rs | 8 + 6 files changed, 213 insertions(+), 2 deletions(-) create mode 100644 crates/nu-command/src/filters/select.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 818d278ff0..dc119edc5d 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -33,6 +33,7 @@ pub fn create_default_context() -> Rc> { working_set.add_decl(Box::new(Ls)); working_set.add_decl(Box::new(Module)); working_set.add_decl(Box::new(Ps)); + working_set.add_decl(Box::new(Select)); working_set.add_decl(Box::new(Sys)); working_set.add_decl(Box::new(Table)); working_set.add_decl(Box::new(Use)); diff --git a/crates/nu-command/src/filters/mod.rs b/crates/nu-command/src/filters/mod.rs index ba3508fd6d..c905721569 100644 --- a/crates/nu-command/src/filters/mod.rs +++ b/crates/nu-command/src/filters/mod.rs @@ -3,6 +3,7 @@ mod for_; mod get; mod length; mod lines; +mod select; mod where_; mod wrap; @@ -11,5 +12,6 @@ pub use for_::For; pub use get::Get; pub use length::Length; pub use lines::Lines; +pub use select::Select; pub use where_::Where; pub use wrap::Wrap; diff --git a/crates/nu-command/src/filters/select.rs b/crates/nu-command/src/filters/select.rs new file mode 100644 index 0000000000..8611517e4d --- /dev/null +++ b/crates/nu-command/src/filters/select.rs @@ -0,0 +1,182 @@ +use nu_engine::CallExt; +use nu_protocol::ast::{Call, CellPath}; +use nu_protocol::engine::{Command, EvaluationContext}; +use nu_protocol::{Example, IntoValueStream, ShellError, Signature, Span, SyntaxShape, Value}; + +pub struct Select; + +impl Command for Select { + fn name(&self) -> &str { + "select" + } + + fn signature(&self) -> Signature { + Signature::build("select").rest( + "rest", + SyntaxShape::CellPath, + "the columns to select from the table", + ) + } + + fn usage(&self) -> &str { + "Down-select table to only these columns." + } + + fn run( + &self, + context: &EvaluationContext, + call: &Call, + input: Value, + ) -> Result { + let columns: Vec = call.rest(context, 0)?; + let span = call.head; + + select(span, columns, input) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Select just the name column", + example: "ls | select name", + result: None, + }, + Example { + description: "Select the name and size columns", + example: "ls | select name size", + result: None, + }, + ] + } +} + +fn select(span: Span, columns: Vec, input: Value) -> Result { + if columns.is_empty() { + return Err(ShellError::CantFindColumn(span)); + } + + match input { + Value::List { + vals: input_vals, + span, + } => { + let mut output = vec![]; + + for input_val in input_vals { + let mut cols = vec![]; + let mut vals = vec![]; + for path in &columns { + //FIXME: improve implementation to not clone + let fetcher = input_val.clone().follow_cell_path(&path.members)?; + + cols.push(path.into_string()); + vals.push(fetcher); + } + + output.push(Value::Record { cols, vals, span }) + } + + Ok(Value::List { vals: output, span }) + } + Value::Stream { stream, span } => Ok(Value::Stream { + stream: stream + .map(move |x| { + let mut cols = vec![]; + let mut vals = vec![]; + for path in &columns { + //FIXME: improve implementation to not clone + match x.clone().follow_cell_path(&path.members) { + Ok(value) => { + cols.push(path.into_string()); + vals.push(value); + } + Err(error) => { + cols.push(path.into_string()); + vals.push(Value::Error { error }); + } + } + } + + Value::Record { cols, vals, span } + }) + .into_value_stream(), + span, + }), + v => { + let mut cols = vec![]; + let mut vals = vec![]; + + for cell_path in columns { + // FIXME: remove clone + let result = v.clone().follow_cell_path(&cell_path.members)?; + + cols.push(cell_path.into_string()); + vals.push(result); + } + + Ok(Value::Record { cols, vals, span }) + } + } +} + +// #[cfg(test)] +// mod tests { +// use nu_protocol::ColumnPath; +// use nu_source::Span; +// use nu_source::SpannedItem; +// use nu_source::Tag; +// use nu_stream::InputStream; +// use nu_test_support::value::nothing; +// use nu_test_support::value::row; +// use nu_test_support::value::string; + +// use super::select; +// use super::Command; +// use super::ShellError; + +// #[test] +// fn examples_work_as_expected() -> Result<(), ShellError> { +// use crate::examples::test as test_examples; + +// test_examples(Command {}) +// } + +// #[test] +// fn select_using_sparse_table() { +// // Create a sparse table with 3 rows: +// // col_foo | col_bar +// // ----------------- +// // foo | +// // | bar +// // foo | +// let input = vec![ +// row(indexmap! {"col_foo".into() => string("foo")}), +// row(indexmap! {"col_bar".into() => string("bar")}), +// row(indexmap! {"col_foo".into() => string("foo")}), +// ]; + +// let expected = vec![ +// row( +// indexmap! {"col_none".into() => nothing(), "col_foo".into() => string("foo"), "col_bar".into() => nothing()}, +// ), +// row( +// indexmap! {"col_none".into() => nothing(), "col_foo".into() => nothing(), "col_bar".into() => string("bar")}, +// ), +// row( +// indexmap! {"col_none".into() => nothing(), "col_foo".into() => string("foo"), "col_bar".into() => nothing()}, +// ), +// ]; + +// let actual = select( +// Tag::unknown(), +// vec![ +// ColumnPath::build(&"col_none".to_string().spanned(Span::unknown())), +// ColumnPath::build(&"col_foo".to_string().spanned(Span::unknown())), +// ColumnPath::build(&"col_bar".to_string().spanned(Span::unknown())), +// ], +// input.into(), +// ); + +// assert_eq!(Ok(expected), actual.map(InputStream::into_vec)); +// } +// } diff --git a/crates/nu-protocol/src/ast/cell_path.rs b/crates/nu-protocol/src/ast/cell_path.rs index b6071b644c..078b64919f 100644 --- a/crates/nu-protocol/src/ast/cell_path.rs +++ b/crates/nu-protocol/src/ast/cell_path.rs @@ -13,6 +13,24 @@ pub struct CellPath { pub members: Vec, } +impl CellPath { + pub fn into_string(&self) -> String { + let mut output = String::new(); + + for (idx, elem) in self.members.iter().enumerate() { + if idx > 0 { + output.push('.'); + } + match elem { + PathMember::Int { val, .. } => output.push_str(&format!("{}", val)), + PathMember::String { val, .. } => output.push_str(val), + } + } + + output + } +} + #[derive(Debug, Clone)] pub struct FullCellPath { pub head: Expression, diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index aed1aa832a..eae64b8dc8 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -191,7 +191,7 @@ impl Value { Value::Nothing { .. } => String::new(), Value::Error { error } => format!("{:?}", error), Value::Binary { val, .. } => format!("{:?}", val), - Value::CellPath { val, .. } => format!("{:?}", val), + Value::CellPath { val, .. } => val.into_string(), } } @@ -223,7 +223,7 @@ impl Value { Value::Nothing { .. } => String::new(), Value::Error { error } => format!("{:?}", error), Value::Binary { val, .. } => format!("{:?}", val), - Value::CellPath { val, .. } => format!("{:?}", val), + Value::CellPath { .. } => self.into_string(), } } diff --git a/src/tests.rs b/src/tests.rs index 37adcb6095..914f0da519 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -409,3 +409,11 @@ fn get() -> TestResult { "B", ) } + +#[test] +fn select() -> TestResult { + run_test( + r#"([[name, age]; [a, 1], [b, 2]]) | select name | get 1 | get name"#, + "b", + ) +} From 63a0aa60885f968581da3fbe9576c4d63939c92a Mon Sep 17 00:00:00 2001 From: JT Date: Sat, 2 Oct 2021 18:43:43 +1300 Subject: [PATCH 17/26] Let strings be cell paths --- crates/nu-engine/src/from_value.rs | 9 ++++++++- src/tests.rs | 8 ++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/crates/nu-engine/src/from_value.rs b/crates/nu-engine/src/from_value.rs index ceb92187b4..b356bbf6fd 100644 --- a/crates/nu-engine/src/from_value.rs +++ b/crates/nu-engine/src/from_value.rs @@ -1,7 +1,7 @@ // use std::path::PathBuf; // use nu_path::expand_path; -use nu_protocol::ast::CellPath; +use nu_protocol::ast::{CellPath, PathMember}; use nu_protocol::ShellError; use nu_protocol::{Range, Spanned, Value}; @@ -115,8 +115,15 @@ impl FromValue for ColumnPath { impl FromValue for CellPath { fn from_value(v: &Value) -> Result { + let span = v.span(); match v { Value::CellPath { val, .. } => Ok(val.clone()), + Value::String { val, .. } => Ok(CellPath { + members: vec![PathMember::String { + val: val.clone(), + span, + }], + }), v => Err(ShellError::CantConvert("cell path".into(), v.span())), } } diff --git a/src/tests.rs b/src/tests.rs index 914f0da519..3ac7dc6817 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -417,3 +417,11 @@ fn select() -> TestResult { "b", ) } + +#[test] +fn string_cell_path() -> TestResult { + run_test( + r#"let x = "name"; [["name", "score"]; [a, b], [c, d]] | get $x | get 1"#, + "c", + ) +} From 03339beae1528a41e63ddafad875fa76a2a138a6 Mon Sep 17 00:00:00 2001 From: Fernando Herrera Date: Sat, 2 Oct 2021 14:10:28 +0100 Subject: [PATCH 18/26] prompt with env variable --- crates/nu-cli/src/lib.rs | 2 + crates/nu-cli/src/prompt.rs | 90 +++++++++++++++++++ .../src/engine/evaluation_context.rs | 4 + src/main.rs | 85 ++++++++++++++++-- 4 files changed, 174 insertions(+), 7 deletions(-) create mode 100644 crates/nu-cli/src/prompt.rs diff --git a/crates/nu-cli/src/lib.rs b/crates/nu-cli/src/lib.rs index f2643bdfbd..748faa7c7b 100644 --- a/crates/nu-cli/src/lib.rs +++ b/crates/nu-cli/src/lib.rs @@ -1,9 +1,11 @@ mod completions; mod errors; +mod prompt; mod syntax_highlight; mod validation; pub use completions::NuCompleter; pub use errors::report_error; +pub use prompt::NushellPrompt; pub use syntax_highlight::NuHighlighter; pub use validation::NuValidator; diff --git a/crates/nu-cli/src/prompt.rs b/crates/nu-cli/src/prompt.rs new file mode 100644 index 0000000000..61cfc3ccbe --- /dev/null +++ b/crates/nu-cli/src/prompt.rs @@ -0,0 +1,90 @@ +use { + reedline::{ + Prompt, PromptEditMode, PromptHistorySearch, PromptHistorySearchStatus, PromptViMode, + }, + std::borrow::Cow, +}; + +/// Nushell prompt definition +#[derive(Clone)] +pub struct NushellPrompt { + prompt_command: String, + prompt_string: String, + // These are part of the struct definition in case we want to later allow + // further customization to the shell status + default_prompt_indicator: String, + default_vi_insert_prompt_indicator: String, + default_vi_visual_prompt_indicator: String, + default_multiline_indicator: String, +} + +impl Default for NushellPrompt { + fn default() -> Self { + NushellPrompt::new() + } +} + +impl NushellPrompt { + /// Constructor for the default prompt, which takes the amount of spaces required between the left and right-hand sides of the prompt + pub fn new() -> NushellPrompt { + NushellPrompt { + prompt_command: "".to_string(), + prompt_string: "".to_string(), + default_prompt_indicator: "〉".to_string(), + default_vi_insert_prompt_indicator: ": ".to_string(), + default_vi_visual_prompt_indicator: "v ".to_string(), + default_multiline_indicator: "::: ".to_string(), + } + } + + pub fn is_new_prompt(&self, prompt_command: &str) -> bool { + self.prompt_command != prompt_command + } + + pub fn update_prompt(&mut self, prompt_command: String, prompt_string: String) { + self.prompt_command = prompt_command; + self.prompt_string = prompt_string; + } + + fn default_wrapped_custom_string(&self, str: String) -> String { + format!("({})", str) + } +} + +impl Prompt for NushellPrompt { + fn render_prompt(&self, _: usize) -> Cow { + self.prompt_string.as_str().into() + } + + fn render_prompt_indicator(&self, edit_mode: PromptEditMode) -> Cow { + match edit_mode { + PromptEditMode::Default => self.default_prompt_indicator.as_str().into(), + PromptEditMode::Emacs => self.default_prompt_indicator.as_str().into(), + PromptEditMode::Vi(vi_mode) => match vi_mode { + PromptViMode::Normal => self.default_prompt_indicator.as_str().into(), + PromptViMode::Insert => self.default_vi_insert_prompt_indicator.as_str().into(), + PromptViMode::Visual => self.default_vi_visual_prompt_indicator.as_str().into(), + }, + PromptEditMode::Custom(str) => self.default_wrapped_custom_string(str).into(), + } + } + + fn render_prompt_multiline_indicator(&self) -> Cow { + Cow::Borrowed(self.default_multiline_indicator.as_str()) + } + + fn render_prompt_history_search_indicator( + &self, + history_search: PromptHistorySearch, + ) -> Cow { + let prefix = match history_search.status { + PromptHistorySearchStatus::Passing => "", + PromptHistorySearchStatus::Failing => "failing ", + }; + + Cow::Owned(format!( + "({}reverse-search: {})", + prefix, history_search.term + )) + } +} diff --git a/crates/nu-protocol/src/engine/evaluation_context.rs b/crates/nu-protocol/src/engine/evaluation_context.rs index 9a0c1c6855..721c21c811 100644 --- a/crates/nu-protocol/src/engine/evaluation_context.rs +++ b/crates/nu-protocol/src/engine/evaluation_context.rs @@ -112,6 +112,10 @@ impl Stack { self.0.borrow().env_vars.clone() } + pub fn get_env_var(&self, name: &str) -> Option { + self.0.borrow().env_vars.get(name).cloned() + } + pub fn print_stack(&self) { println!("===frame==="); println!("vars:"); diff --git a/src/main.rs b/src/main.rs index c10bf2c805..c821e30e8c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,20 +1,23 @@ -use std::io::Write; +use std::{cell::RefCell, io::Write, rc::Rc}; use miette::{IntoDiagnostic, Result}; -use nu_cli::{report_error, NuCompleter, NuHighlighter, NuValidator}; +use nu_cli::{report_error, NuCompleter, NuHighlighter, NuValidator, NushellPrompt}; use nu_command::create_default_context; use nu_engine::eval_block; use nu_parser::parse; use nu_protocol::{ ast::Call, - engine::{EngineState, EvaluationContext, StateWorkingSet}, + engine::{EngineState, EvaluationContext, Stack, StateWorkingSet}, ShellError, Value, }; -use reedline::DefaultCompletionActionHandler; +use reedline::{DefaultPrompt, Prompt}; #[cfg(test)] mod tests; +// Name of environment variable where the prompt could be stored +const PROMPT_COMMAND: &str = "PROMPT_COMMAND"; + fn main() -> Result<()> { miette::set_panic_hook(); let miette_hook = std::panic::take_hook(); @@ -63,7 +66,7 @@ fn main() -> Result<()> { Ok(()) } else { - use reedline::{DefaultPrompt, FileBackedHistory, Reedline, Signal}; + use reedline::{DefaultCompletionActionHandler, FileBackedHistory, Reedline, Signal}; let completer = NuCompleter::new(engine_state.clone()); let mut entry_num = 0; @@ -84,13 +87,22 @@ fn main() -> Result<()> { engine_state: engine_state.clone(), })); - let prompt = DefaultPrompt::new(1); + let default_prompt = DefaultPrompt::new(1); + let mut nu_prompt = NushellPrompt::new(); let stack = nu_protocol::engine::Stack::new(); loop { + let prompt = update_prompt( + PROMPT_COMMAND, + engine_state.clone(), + &stack, + &mut nu_prompt, + &default_prompt, + ); + entry_num += 1; - let input = line_editor.read_line(&prompt); + let input = line_editor.read_line(prompt); match input { Ok(Signal::Success(s)) => { if s.trim() == "exit" { @@ -189,3 +201,62 @@ fn print_value(value: Value, state: &EvaluationContext) -> Result<(), ShellError Ok(()) } + +fn update_prompt<'prompt>( + env_variable: &str, + engine_state: Rc>, + stack: &Stack, + nu_prompt: &'prompt mut NushellPrompt, + default_prompt: &'prompt DefaultPrompt, +) -> &'prompt dyn Prompt { + let prompt_command = match stack.get_env_var(env_variable) { + Some(prompt) => prompt, + None => return default_prompt as &dyn Prompt, + }; + + // Checking if the PROMPT_COMMAND is the same to avoid evaluating constantly + // the same command, thus saturating the contents in the EngineState + if !nu_prompt.is_new_prompt(prompt_command.as_str()) { + return nu_prompt as &dyn Prompt; + } + + let (block, delta) = { + let ref_engine_state = engine_state.borrow(); + let mut working_set = StateWorkingSet::new(&ref_engine_state); + let (output, err) = parse(&mut working_set, None, prompt_command.as_bytes(), false); + if let Some(err) = err { + report_error(&working_set, &err); + return default_prompt as &dyn Prompt; + } + (output, working_set.render()) + }; + + EngineState::merge_delta(&mut *engine_state.borrow_mut(), delta); + + let state = nu_protocol::engine::EvaluationContext { + engine_state: engine_state.clone(), + stack: stack.clone(), + }; + + let evaluated_prompt = match eval_block(&state, &block, Value::nothing()) { + Ok(value) => match value.as_string() { + Ok(prompt) => prompt, + Err(err) => { + let engine_state = engine_state.borrow(); + let working_set = StateWorkingSet::new(&*engine_state); + report_error(&working_set, &err); + return default_prompt as &dyn Prompt; + } + }, + Err(err) => { + let engine_state = engine_state.borrow(); + let working_set = StateWorkingSet::new(&*engine_state); + report_error(&working_set, &err); + return default_prompt as &dyn Prompt; + } + }; + + nu_prompt.update_prompt(prompt_command, evaluated_prompt); + + nu_prompt as &dyn Prompt +} From 6f4df319279fc56608ee74e937a5cd1cf2f83378 Mon Sep 17 00:00:00 2001 From: Fernando Herrera Date: Sat, 2 Oct 2021 14:16:37 +0100 Subject: [PATCH 19/26] removed comments --- crates/nu-cli/src/prompt.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/nu-cli/src/prompt.rs b/crates/nu-cli/src/prompt.rs index 61cfc3ccbe..5e748f8a35 100644 --- a/crates/nu-cli/src/prompt.rs +++ b/crates/nu-cli/src/prompt.rs @@ -10,7 +10,7 @@ use { pub struct NushellPrompt { prompt_command: String, prompt_string: String, - // These are part of the struct definition in case we want to later allow + // These are part of the struct definition in case we want to allow // further customization to the shell status default_prompt_indicator: String, default_vi_insert_prompt_indicator: String, @@ -25,7 +25,6 @@ impl Default for NushellPrompt { } impl NushellPrompt { - /// Constructor for the default prompt, which takes the amount of spaces required between the left and right-hand sides of the prompt pub fn new() -> NushellPrompt { NushellPrompt { prompt_command: "".to_string(), From be68b84473f5f0cdcc02b420921e8615f26e5177 Mon Sep 17 00:00:00 2001 From: Michael Angerman Date: Sat, 2 Oct 2021 10:02:11 -0700 Subject: [PATCH 20/26] add serde derive feature to Cargo.toml so nu-protocol compiles stand alone --- crates/nu-protocol/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/nu-protocol/Cargo.toml b/crates/nu-protocol/Cargo.toml index d2f3a7f915..a4fbcd6bf8 100644 --- a/crates/nu-protocol/Cargo.toml +++ b/crates/nu-protocol/Cargo.toml @@ -8,4 +8,4 @@ edition = "2018" [dependencies] thiserror = "1.0.29" miette = "3.0.0" -serde = "1.0.130" \ No newline at end of file +serde = {version = "1.0.130", features = ["derive"]} From 0cc121876b3b1a0e8f71941d74a6d50323cb5447 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Sun, 3 Oct 2021 06:12:05 +1300 Subject: [PATCH 21/26] Update tests.rs Update test errors to be more portable --- src/tests.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/tests.rs b/src/tests.rs index 844df53c35..5436f16b84 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -395,7 +395,7 @@ fn module_import_uses_internal_command() -> TestResult { fn hides_def() -> TestResult { fail_test( r#"def foo [] { "foo" }; hide foo; foo"#, - "command not found", + "not found", ) } @@ -411,7 +411,7 @@ fn hides_def_then_redefines() -> TestResult { fn hides_def_in_scope_1() -> TestResult { fail_test( r#"def foo [] { "foo" }; do { hide foo; foo }"#, - "command not found", + "not found", ) } @@ -427,7 +427,7 @@ fn hides_def_in_scope_2() -> TestResult { fn hides_def_in_scope_3() -> TestResult { fail_test( r#"def foo [] { "foo" }; do { hide foo; def foo [] { "bar" }; hide foo; foo }"#, - "command not found", + "not found", ) } @@ -435,7 +435,7 @@ fn hides_def_in_scope_3() -> TestResult { fn hides_def_in_scope_4() -> TestResult { fail_test( r#"def foo [] { "foo" }; do { def foo [] { "bar" }; hide foo; hide foo; foo }"#, - "command not found", + "not found", ) } From b5ec9e0360a9a971ac016b13d78f53a45a1fc7f0 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Sun, 3 Oct 2021 06:16:02 +1300 Subject: [PATCH 22/26] Update mod.rs --- crates/nu-command/src/core_commands/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/nu-command/src/core_commands/mod.rs b/crates/nu-command/src/core_commands/mod.rs index 384ed11e8c..3472f4d8b3 100644 --- a/crates/nu-command/src/core_commands/mod.rs +++ b/crates/nu-command/src/core_commands/mod.rs @@ -2,8 +2,8 @@ mod alias; mod def; mod do_; mod export_def; -mod hide; mod help; +mod hide; mod if_; mod let_; mod module; @@ -13,8 +13,8 @@ pub use alias::Alias; pub use def::Def; pub use do_::Do; pub use export_def::ExportDef; -pub use hide::Hide; pub use help::Help; +pub use hide::Hide; pub use if_::If; pub use let_::Let; pub use module::Module; From eba3484611707fcb0246622a392f63745ffca606 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Sun, 3 Oct 2021 06:17:51 +1300 Subject: [PATCH 23/26] Update tests.rs --- src/tests.rs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/tests.rs b/src/tests.rs index 5436f16b84..7416aecb6f 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -393,10 +393,7 @@ fn module_import_uses_internal_command() -> TestResult { #[test] fn hides_def() -> TestResult { - fail_test( - r#"def foo [] { "foo" }; hide foo; foo"#, - "not found", - ) + fail_test(r#"def foo [] { "foo" }; hide foo; foo"#, "not found") } #[test] @@ -409,10 +406,7 @@ fn hides_def_then_redefines() -> TestResult { #[test] fn hides_def_in_scope_1() -> TestResult { - fail_test( - r#"def foo [] { "foo" }; do { hide foo; foo }"#, - "not found", - ) + fail_test(r#"def foo [] { "foo" }; do { hide foo; foo }"#, "not found") } #[test] From 5bf51b5a7a685f64b82f54532fdabf243c31dc33 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Sun, 3 Oct 2021 08:33:30 +1300 Subject: [PATCH 24/26] Update TODO.md --- TODO.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/TODO.md b/TODO.md index fdbf9c3020..0a051b75b9 100644 --- a/TODO.md +++ b/TODO.md @@ -23,7 +23,8 @@ - [x] Signature needs to make parameters visible in scope before block is parsed - [x] Externals - [x] Modules and imports -- [ ] Exports +- [x] Exports +- [ ] Input/output types - [ ] Support for `$in` - [ ] Value serialization - [ ] Handling rows with missing columns during a cell path From 91090e1db1cad7bc74d07e42e15a820c60d13368 Mon Sep 17 00:00:00 2001 From: JT Date: Sun, 3 Oct 2021 09:16:37 +1300 Subject: [PATCH 25/26] Add simple cd --- Cargo.lock | 1 + crates/nu-command/Cargo.toml | 1 + crates/nu-command/src/default_context.rs | 1 + crates/nu-command/src/filesystem/cd.rs | 46 ++++++++++++++++++++++++ crates/nu-command/src/filesystem/mod.rs | 2 ++ crates/nu-protocol/src/syntax_shape.rs | 2 +- 6 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 crates/nu-command/src/filesystem/cd.rs diff --git a/Cargo.lock b/Cargo.lock index c66b470877..cf1052648a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -499,6 +499,7 @@ dependencies = [ "glob", "nu-engine", "nu-json", + "nu-path", "nu-protocol", "nu-table", "sysinfo", diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index 2c8ddfee89..c6bcd32f9b 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -8,6 +8,7 @@ edition = "2018" [dependencies] nu-engine = { path = "../nu-engine" } nu-json = { path = "../nu-json" } +nu-path = { path = "../nu-path" } nu-protocol = { path = "../nu-protocol" } nu-table = { path = "../nu-table" } diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index dc119edc5d..34763d4f98 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -16,6 +16,7 @@ pub fn create_default_context() -> Rc> { working_set.add_decl(Box::new(Alias)); working_set.add_decl(Box::new(Benchmark)); working_set.add_decl(Box::new(BuildString)); + working_set.add_decl(Box::new(Cd)); working_set.add_decl(Box::new(Def)); working_set.add_decl(Box::new(Do)); working_set.add_decl(Box::new(Each)); diff --git a/crates/nu-command/src/filesystem/cd.rs b/crates/nu-command/src/filesystem/cd.rs new file mode 100644 index 0000000000..94282c75e2 --- /dev/null +++ b/crates/nu-command/src/filesystem/cd.rs @@ -0,0 +1,46 @@ +use nu_engine::CallExt; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EvaluationContext}; +use nu_protocol::{Signature, SyntaxShape, Value}; + +pub struct Cd; + +impl Command for Cd { + fn name(&self) -> &str { + "cd" + } + + fn usage(&self) -> &str { + "Change directory." + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("cd").optional("path", SyntaxShape::FilePath, "the path to change to") + } + + fn run( + &self, + context: &EvaluationContext, + call: &Call, + _input: Value, + ) -> Result { + let path: Option = call.opt(context, 0)?; + + let path = match path { + Some(path) => { + let path = nu_path::expand_tilde(path); + path.to_string_lossy().to_string() + } + None => { + let path = nu_path::expand_tilde("~"); + path.to_string_lossy().to_string() + } + }; + let _ = std::env::set_current_dir(&path); + + //FIXME: this only changes the current scope, but instead this environment variable + //should probably be a block that loads the information from the state in the overlay + context.add_env_var("PWD".into(), path); + Ok(Value::Nothing { span: call.head }) + } +} diff --git a/crates/nu-command/src/filesystem/mod.rs b/crates/nu-command/src/filesystem/mod.rs index d7d2f30464..13148b5358 100644 --- a/crates/nu-command/src/filesystem/mod.rs +++ b/crates/nu-command/src/filesystem/mod.rs @@ -1,3 +1,5 @@ +mod cd; mod ls; +pub use cd::Cd; pub use ls::Ls; diff --git a/crates/nu-protocol/src/syntax_shape.rs b/crates/nu-protocol/src/syntax_shape.rs index daed48c0c3..8b752461e7 100644 --- a/crates/nu-protocol/src/syntax_shape.rs +++ b/crates/nu-protocol/src/syntax_shape.rs @@ -86,7 +86,7 @@ impl SyntaxShape { SyntaxShape::Custom(custom, _) => custom.to_type(), SyntaxShape::Duration => Type::Duration, SyntaxShape::Expression => Type::Unknown, - SyntaxShape::FilePath => Type::FilePath, + SyntaxShape::FilePath => Type::String, SyntaxShape::Filesize => Type::Filesize, SyntaxShape::FullCellPath => Type::Unknown, SyntaxShape::GlobPattern => Type::String, From 75e323ee35861a331dc141efe9fecfcc6f5f6433 Mon Sep 17 00:00:00 2001 From: JT Date: Sun, 3 Oct 2021 10:56:11 +1300 Subject: [PATCH 26/26] Lines shouldn't trim --- crates/nu-command/src/filters/lines.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/nu-command/src/filters/lines.rs b/crates/nu-command/src/filters/lines.rs index 74333ff3ef..f3517c8e65 100644 --- a/crates/nu-command/src/filters/lines.rs +++ b/crates/nu-command/src/filters/lines.rs @@ -62,7 +62,7 @@ impl Command for Lines { .filter_map(|s| { if !s.is_empty() { Some(Value::String { - val: s.trim().into(), + val: s.into(), span, }) } else {