diff --git a/crates/nu-command/src/core_commands/export_alias.rs b/crates/nu-command/src/core_commands/export_alias.rs new file mode 100644 index 0000000000..9aa9d861a9 --- /dev/null +++ b/crates/nu-command/src/core_commands/export_alias.rs @@ -0,0 +1,45 @@ +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{Category, Example, PipelineData, Signature, SyntaxShape}; + +#[derive(Clone)] +pub struct ExportAlias; + +impl Command for ExportAlias { + fn name(&self) -> &str { + "export alias" + } + + fn usage(&self) -> &str { + "Define an alias and export it from a module" + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("export def") + .required("name", SyntaxShape::String, "name of the alias") + .required( + "initial_value", + SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::Expression)), + "equals sign followed by value", + ) + .category(Category::Core) + } + + fn run( + &self, + _engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + Ok(PipelineData::new(call.head)) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "export an alias of ll to ls -l, from a module", + example: "export alias ll = ls -l", + result: None, + }] + } +} diff --git a/crates/nu-command/src/core_commands/export_extern.rs b/crates/nu-command/src/core_commands/export_extern.rs new file mode 100644 index 0000000000..7cc4489e77 --- /dev/null +++ b/crates/nu-command/src/core_commands/export_extern.rs @@ -0,0 +1,41 @@ +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{Category, Example, PipelineData, Signature, SyntaxShape}; + +#[derive(Clone)] +pub struct ExportExtern; + +impl Command for ExportExtern { + fn name(&self) -> &str { + "export extern" + } + + fn usage(&self) -> &str { + "Define an extern and export it from a module" + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("export extern") + .required("def_name", SyntaxShape::String, "definition name") + .required("params", SyntaxShape::Signature, "parameters") + .category(Category::Core) + } + + fn run( + &self, + _engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + Ok(PipelineData::new(call.head)) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Export the signature for an external command", + example: r#"export extern echo [text: string]"#, + result: None, + }] + } +} diff --git a/crates/nu-command/src/core_commands/mod.rs b/crates/nu-command/src/core_commands/mod.rs index 097c3a28df..a8d135826f 100644 --- a/crates/nu-command/src/core_commands/mod.rs +++ b/crates/nu-command/src/core_commands/mod.rs @@ -7,9 +7,11 @@ mod do_; mod echo; mod error_make; mod export; +mod export_alias; mod export_def; mod export_def_env; mod export_env; +mod export_extern; mod extern_; mod for_; mod help; @@ -34,9 +36,11 @@ pub use do_::Do; pub use echo::Echo; pub use error_make::ErrorMake; pub use export::ExportCommand; +pub use export_alias::ExportAlias; pub use export_def::ExportDef; pub use export_def_env::ExportDefEnv; pub use export_env::ExportEnv; +pub use export_extern::ExportExtern; pub use extern_::Extern; pub use for_::For; pub use help::Help; diff --git a/crates/nu-command/src/core_commands/use_.rs b/crates/nu-command/src/core_commands/use_.rs index 4a662dcb1d..0dae9b8bc8 100644 --- a/crates/nu-command/src/core_commands/use_.rs +++ b/crates/nu-command/src/core_commands/use_.rs @@ -57,7 +57,7 @@ impl Command for Use { if let Some(id) = overlay.get_env_var_id(name) { output.push((name.clone(), id)); - } else if !overlay.has_decl(name) { + } else if !overlay.has_decl(name) && !overlay.has_alias(name) { return Err(ShellError::EnvVarNotFoundAtRuntime( String::from_utf8_lossy(name).into(), *span, diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 4681db9839..201b047c68 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -34,10 +34,12 @@ pub fn create_default_context(cwd: impl AsRef) -> EngineState { Du, Echo, ErrorMake, + ExportAlias, ExportCommand, ExportDef, ExportDefEnv, ExportEnv, + ExportExtern, Extern, For, Help, diff --git a/crates/nu-parser/src/parse_keywords.rs b/crates/nu-parser/src/parse_keywords.rs index c9378fdd8c..24d7fc33b4 100644 --- a/crates/nu-parser/src/parse_keywords.rs +++ b/crates/nu-parser/src/parse_keywords.rs @@ -547,6 +547,16 @@ pub fn parse_alias( working_set.add_alias(alias_name, replacement); } + let err = if spans.len() < 4 { + Some(ParseError::IncorrectValue( + "Incomplete alias".into(), + spans[0], + "incomplete alias".into(), + )) + } else { + None + }; + return ( Pipeline::from_vec(vec![Expression { expr: Expr::Call(call), @@ -554,7 +564,7 @@ pub fn parse_alias( ty: Type::Unknown, custom_completion: None, }]), - None, + err, ); } } @@ -744,6 +754,125 @@ pub fn parse_export( None } } + b"extern" => { + let lite_command = LiteCommand { + comments: lite_command.comments.clone(), + parts: spans[1..].to_vec(), + }; + let (pipeline, err) = + parse_extern(working_set, &lite_command, expand_aliases_denylist); + error = error.or(err); + + let export_def_decl_id = if let Some(id) = working_set.find_decl(b"export extern") { + id + } else { + return ( + garbage_pipeline(spans), + None, + Some(ParseError::InternalError( + "missing 'export extern' command".into(), + export_span, + )), + ); + }; + + // Trying to warp the 'def' call into the 'export def' in a very clumsy way + if let Some(Expression { + expr: Expr::Call(ref def_call), + .. + }) = pipeline.expressions.get(0) + { + call = def_call.clone(); + + call.head = span(&spans[0..=1]); + call.decl_id = export_def_decl_id; + } else { + error = error.or_else(|| { + Some(ParseError::InternalError( + "unexpected output from parsing a definition".into(), + span(&spans[1..]), + )) + }); + }; + + if error.is_none() { + let decl_name = working_set.get_span_contents(spans[2]); + let decl_name = trim_quotes(decl_name); + if let Some(decl_id) = working_set.find_decl(decl_name) { + Some(Exportable::Decl(decl_id)) + } else { + error = error.or_else(|| { + Some(ParseError::InternalError( + "failed to find added declaration".into(), + span(&spans[1..]), + )) + }); + None + } + } else { + None + } + } + b"alias" => { + let lite_command = LiteCommand { + comments: lite_command.comments.clone(), + parts: spans[1..].to_vec(), + }; + let (pipeline, err) = + parse_alias(working_set, &lite_command.parts, expand_aliases_denylist); + error = error.or(err); + + let export_alias_decl_id = if let Some(id) = working_set.find_decl(b"export alias") + { + id + } else { + return ( + garbage_pipeline(spans), + None, + Some(ParseError::InternalError( + "missing 'export alias' command".into(), + export_span, + )), + ); + }; + + // Trying to warp the 'alias' call into the 'export alias' in a very clumsy way + if let Some(Expression { + expr: Expr::Call(ref alias_call), + .. + }) = pipeline.expressions.get(0) + { + call = alias_call.clone(); + + call.head = span(&spans[0..=1]); + call.decl_id = export_alias_decl_id; + } else { + error = error.or_else(|| { + Some(ParseError::InternalError( + "unexpected output from parsing a definition".into(), + span(&spans[1..]), + )) + }); + }; + + if error.is_none() { + let alias_name = working_set.get_span_contents(spans[2]); + let alias_name = trim_quotes(alias_name); + if let Some(alias_id) = working_set.find_alias(alias_name) { + Some(Exportable::Alias(alias_id)) + } else { + error = error.or_else(|| { + Some(ParseError::InternalError( + "failed to find added alias".into(), + span(&spans[1..]), + )) + }); + None + } + } else { + None + } + } b"env" => { if let Some(id) = working_set.find_decl(b"export env") { call.decl_id = id; @@ -833,7 +962,7 @@ pub fn parse_export( error = error.or_else(|| { Some(ParseError::Expected( // TODO: Fill in more keywords as they come - "def or env keyword".into(), + "def, def-env, alias, or env keyword".into(), spans[1], )) }); @@ -844,12 +973,12 @@ pub fn parse_export( } else { error = error.or_else(|| { Some(ParseError::MissingPositional( - "def or env keyword".into(), // TODO: keep filling more keywords as they come + "def, def-env, alias, or env keyword".into(), // TODO: keep filling more keywords as they come Span { start: export_span.end, end: export_span.end, }, - "'def' or 'env' keyword.".to_string(), + "'def', `def-env`, `alias`, or 'env' keyword.".to_string(), )) }); @@ -921,6 +1050,15 @@ pub fn parse_module_block( (pipeline, err) } + b"alias" => { + let (pipeline, err) = parse_alias( + working_set, + &pipeline.commands[0].parts, + expand_aliases_denylist, + ); + + (pipeline, err) + } // TODO: Currently, it is not possible to define a private env var. // TODO: Exported env vars are usable iside the module only if correctly // exported by the user. For example: @@ -948,6 +1086,9 @@ pub fn parse_module_block( Some(Exportable::EnvVar(block_id)) => { overlay.add_env_var(name, block_id); } + Some(Exportable::Alias(alias_id)) => { + overlay.add_alias(name, alias_id); + } None => {} // None should always come with error from parse_export() } } @@ -1224,41 +1365,51 @@ pub fn parse_use( } }; - let decls_to_use = if import_pattern.members.is_empty() { - overlay.decls_with_head(&import_pattern.head.name) + let (decls_to_use, aliases_to_use) = if import_pattern.members.is_empty() { + ( + overlay.decls_with_head(&import_pattern.head.name), + overlay.aliases_with_head(&import_pattern.head.name), + ) } else { match &import_pattern.members[0] { - ImportPatternMember::Glob { .. } => overlay.decls(), + ImportPatternMember::Glob { .. } => (overlay.decls(), overlay.aliases()), ImportPatternMember::Name { name, span } => { - let mut output = vec![]; + let mut decl_output = vec![]; + let mut alias_output = vec![]; if let Some(id) = overlay.get_decl_id(name) { - output.push((name.clone(), id)); + decl_output.push((name.clone(), id)); + } else if let Some(id) = overlay.get_alias_id(name) { + alias_output.push((name.clone(), id)); } else if !overlay.has_env_var(name) { error = error.or(Some(ParseError::ExportNotFound(*span))) } - output + (decl_output, alias_output) } ImportPatternMember::List { names } => { - let mut output = vec![]; + let mut decl_output = vec![]; + let mut alias_output = vec![]; for (name, span) in names { if let Some(id) = overlay.get_decl_id(name) { - output.push((name.clone(), id)); + decl_output.push((name.clone(), id)); + } else if let Some(id) = overlay.get_alias_id(name) { + alias_output.push((name.clone(), id)); } else if !overlay.has_env_var(name) { error = error.or(Some(ParseError::ExportNotFound(*span))); break; } } - output + (decl_output, alias_output) } } }; // Extend the current scope with the module's overlay working_set.use_decls(decls_to_use); + working_set.use_aliases(aliases_to_use); // Create a new Use command call to pass the new import pattern let import_pattern_expr = Expression { diff --git a/crates/nu-protocol/src/engine/engine_state.rs b/crates/nu-protocol/src/engine/engine_state.rs index c5dd87c1d0..0ed443f6d9 100644 --- a/crates/nu-protocol/src/engine/engine_state.rs +++ b/crates/nu-protocol/src/engine/engine_state.rs @@ -770,6 +770,19 @@ impl<'a> StateWorkingSet<'a> { } } + pub fn use_aliases(&mut self, aliases: Vec<(Vec, AliasId)>) { + let scope_frame = self + .delta + .scope + .last_mut() + .expect("internal error: missing required scope frame"); + + for (name, alias_id) in aliases { + scope_frame.aliases.insert(name, alias_id); + scope_frame.visibility.use_decl_id(&alias_id); + } + } + pub fn add_predecl(&mut self, decl: Box) -> Option { let name = decl.name().as_bytes().to_vec(); diff --git a/crates/nu-protocol/src/exportable.rs b/crates/nu-protocol/src/exportable.rs index 5323d60bf9..bc472d71c5 100644 --- a/crates/nu-protocol/src/exportable.rs +++ b/crates/nu-protocol/src/exportable.rs @@ -1,6 +1,7 @@ -use crate::{BlockId, DeclId}; +use crate::{AliasId, BlockId, DeclId}; pub enum Exportable { Decl(DeclId), + Alias(AliasId), EnvVar(BlockId), } diff --git a/crates/nu-protocol/src/overlay.rs b/crates/nu-protocol/src/overlay.rs index dd0299d9c1..34520a89db 100644 --- a/crates/nu-protocol/src/overlay.rs +++ b/crates/nu-protocol/src/overlay.rs @@ -116,6 +116,18 @@ impl Overlay { .collect() } + pub fn aliases_with_head(&self, head: &[u8]) -> Vec<(Vec, AliasId)> { + self.aliases + .iter() + .map(|(name, id)| { + let mut new_name = head.to_vec(); + new_name.push(b' '); + new_name.extend(name); + (new_name, *id) + }) + .collect() + } + pub fn alias_names_with_head(&self, head: &[u8]) -> Vec> { self.aliases .keys() diff --git a/src/tests/test_modules.rs b/src/tests/test_modules.rs index 4c3bdca560..bbb340a7c0 100644 --- a/src/tests/test_modules.rs +++ b/src/tests/test_modules.rs @@ -119,3 +119,11 @@ fn multi_word_imports() -> TestResult { "10", ) } + +#[test] +fn export_alias() -> TestResult { + run_test( + r#"module foo { export alias hi = echo hello }; use foo hi; hi"#, + "hello", + ) +}