diff --git a/crates/nu-command/src/core_commands/overlay/mod.rs b/crates/nu-command/src/core_commands/overlay/mod.rs index 9fbaab377..b9ba725d7 100644 --- a/crates/nu-command/src/core_commands/overlay/mod.rs +++ b/crates/nu-command/src/core_commands/overlay/mod.rs @@ -1,9 +1,11 @@ mod add; mod command; mod list; +mod new; mod remove; pub use add::OverlayAdd; pub use command::Overlay; pub use list::OverlayList; +pub use new::OverlayNew; pub use remove::OverlayRemove; diff --git a/crates/nu-command/src/core_commands/overlay/new.rs b/crates/nu-command/src/core_commands/overlay/new.rs new file mode 100644 index 000000000..48956e4c0 --- /dev/null +++ b/crates/nu-command/src/core_commands/overlay/new.rs @@ -0,0 +1,74 @@ +use nu_engine::CallExt; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape}; + +#[derive(Clone)] +pub struct OverlayNew; + +impl Command for OverlayNew { + fn name(&self) -> &str { + "overlay new" + } + + fn usage(&self) -> &str { + "Create an empty overlay" + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("overlay new") + .required("name", SyntaxShape::String, "Name of the overlay") + // TODO: + // .switch( + // "prefix", + // "Prepend module name to the imported symbols", + // Some('p'), + // ) + .category(Category::Core) + } + + fn extra_usage(&self) -> &str { + r#"The command will first create an empty module, then add it as an overlay. + +This command is a parser keyword. For details, check: + https://www.nushell.sh/book/thinking_in_nushell.html"# + } + + fn is_parser_keyword(&self) -> bool { + true + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + let name_arg: Spanned = call.req(engine_state, stack, 0)?; + + stack.add_overlay(name_arg.item); + + Ok(PipelineData::new(call.head)) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Create an empty overlay", + example: r#"overlay new spam"#, + result: None, + }] + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(OverlayNew {}) + } +} diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index ccf42f1e7..4877a801d 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -55,6 +55,7 @@ pub fn create_default_context(cwd: impl AsRef) -> EngineState { Overlay, OverlayAdd, OverlayList, + OverlayNew, OverlayRemove, Let, Metadata, diff --git a/crates/nu-parser/src/parse_keywords.rs b/crates/nu-parser/src/parse_keywords.rs index 9ada7f47c..71b0c009f 100644 --- a/crates/nu-parser/src/parse_keywords.rs +++ b/crates/nu-parser/src/parse_keywords.rs @@ -1745,6 +1745,9 @@ pub fn parse_overlay( None, ); } + b"new" => { + return parse_overlay_new(working_set, spans, expand_aliases_denylist); + } b"remove" => { return parse_overlay_remove(working_set, spans, expand_aliases_denylist); } @@ -1802,6 +1805,96 @@ pub fn parse_overlay( ) } +pub fn parse_overlay_new( + working_set: &mut StateWorkingSet, + spans: &[Span], + expand_aliases_denylist: &[usize], +) -> (Pipeline, Option) { + if spans.len() > 1 && working_set.get_span_contents(span(&spans[0..2])) != b"overlay new" { + return ( + garbage_pipeline(spans), + Some(ParseError::UnknownState( + "internal error: Wrong call name for 'overlay new' command".into(), + span(spans), + )), + ); + } + + let (call, call_span) = match working_set.find_decl(b"overlay new") { + Some(decl_id) => { + let (call, mut err) = parse_internal_call( + working_set, + span(&spans[0..2]), + &spans[2..], + decl_id, + expand_aliases_denylist, + ); + let decl = working_set.get_decl(decl_id); + + let call_span = span(spans); + + err = check_call(call_span, &decl.signature(), &call).or(err); + if err.is_some() || call.has_flag("help") { + return ( + Pipeline::from_vec(vec![Expression { + expr: Expr::Call(call), + span: call_span, + ty: Type::Any, + custom_completion: None, + }]), + err, + ); + } + + (call, call_span) + } + None => { + return ( + garbage_pipeline(spans), + Some(ParseError::UnknownState( + "internal error: 'overlay new' declaration not found".into(), + span(spans), + )), + ) + } + }; + + let (overlay_name, _) = if let Some(expr) = call.positional_nth(0) { + if let Some(s) = expr.as_string() { + (s, expr.span) + } else { + return ( + garbage_pipeline(spans), + Some(ParseError::UnknownState( + "internal error: Module name not a string".into(), + expr.span, + )), + ); + } + } else { + return ( + garbage_pipeline(spans), + Some(ParseError::UnknownState( + "internal error: Missing required positional after call parsing".into(), + call_span, + )), + ); + }; + + let pipeline = Pipeline::from_vec(vec![Expression { + expr: Expr::Call(call), + span: span(spans), + ty: Type::Any, + custom_completion: None, + }]); + + let module_id = working_set.add_module(&overlay_name, Module::new()); + + working_set.add_overlay(overlay_name.as_bytes().to_vec(), module_id, vec![], vec![]); + + (pipeline, None) +} + pub fn parse_overlay_add( working_set: &mut StateWorkingSet, spans: &[Span], diff --git a/tests/overlays/mod.rs b/tests/overlays/mod.rs index d8772738a..4175d22e2 100644 --- a/tests/overlays/mod.rs +++ b/tests/overlays/mod.rs @@ -496,3 +496,14 @@ fn reset_overrides() { assert_eq!(actual.out, "foo"); assert_eq!(actual_repl.out, "foo"); } + +#[test] +fn overlay_new() { + let inp = &[r#"overlay new spam"#, r#"overlay list | last"#]; + + let actual = nu!(cwd: "tests/overlays", pipeline(&inp.join("; "))); + let actual_repl = nu_repl("tests/overlays", inp); + + assert_eq!(actual.out, "spam"); + assert_eq!(actual_repl.out, "spam"); +}