Add export alias and export extern (#4878)

* export alias

* export extern
This commit is contained in:
JT 2022-03-20 07:58:01 +13:00 committed by GitHub
parent 285f91e67a
commit f3bb1d11d3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 292 additions and 15 deletions

View 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,
}]
}
}

View 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,
}]
}
}

View File

@ -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;

View File

@ -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,

View File

@ -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,

View File

@ -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 {

View File

@ -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();

View File

@ -1,6 +1,7 @@
use crate::{BlockId, DeclId};
use crate::{AliasId, BlockId, DeclId};
pub enum Exportable {
Decl(DeclId),
Alias(AliasId),
EnvVar(BlockId),
}

View File

@ -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()

View File

@ -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",
)
}