mirror of
https://github.com/nushell/nushell
synced 2024-12-27 05:23:11 +00:00
Add export alias
and export extern
(#4878)
* export alias * export extern
This commit is contained in:
parent
285f91e67a
commit
f3bb1d11d3
10 changed files with 292 additions and 15 deletions
45
crates/nu-command/src/core_commands/export_alias.rs
Normal file
45
crates/nu-command/src/core_commands/export_alias.rs
Normal file
|
@ -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<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
Ok(PipelineData::new(call.head))
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "export an alias of ll to ls -l, from a module",
|
||||
example: "export alias ll = ls -l",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
41
crates/nu-command/src/core_commands/export_extern.rs
Normal file
41
crates/nu-command/src/core_commands/export_extern.rs
Normal file
|
@ -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<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
Ok(PipelineData::new(call.head))
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Export the signature for an external command",
|
||||
example: r#"export extern echo [text: string]"#,
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -34,10 +34,12 @@ pub fn create_default_context(cwd: impl AsRef<Path>) -> EngineState {
|
|||
Du,
|
||||
Echo,
|
||||
ErrorMake,
|
||||
ExportAlias,
|
||||
ExportCommand,
|
||||
ExportDef,
|
||||
ExportDefEnv,
|
||||
ExportEnv,
|
||||
ExportExtern,
|
||||
Extern,
|
||||
For,
|
||||
Help,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -770,6 +770,19 @@ impl<'a> StateWorkingSet<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn use_aliases(&mut self, aliases: Vec<(Vec<u8>, 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<dyn Command>) -> Option<DeclId> {
|
||||
let name = decl.name().as_bytes().to_vec();
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use crate::{BlockId, DeclId};
|
||||
use crate::{AliasId, BlockId, DeclId};
|
||||
|
||||
pub enum Exportable {
|
||||
Decl(DeclId),
|
||||
Alias(AliasId),
|
||||
EnvVar(BlockId),
|
||||
}
|
||||
|
|
|
@ -116,6 +116,18 @@ impl Overlay {
|
|||
.collect()
|
||||
}
|
||||
|
||||
pub fn aliases_with_head(&self, head: &[u8]) -> Vec<(Vec<u8>, 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<Vec<u8>> {
|
||||
self.aliases
|
||||
.keys()
|
||||
|
|
|
@ -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",
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue