mirror of
https://github.com/nushell/nushell.git
synced 2025-04-24 05:08:29 +02:00
Allow modules to use
other modules (#6162)
* Allow private imports inside modules Can call `use ...` inside modules now. * Add more tests * Add a leak test * Refactor exportables; Prepare for 'export use' * Fix description * Implement 'export use' command This allows re-exporting module's commands and aliases from another module. * Add more tests; Fix import pattern list strings The import pattern strings didn't trim the surrounding quotes. * Add ignored test
This commit is contained in:
parent
cf2e9cf481
commit
2cffff0c1b
@ -18,7 +18,7 @@ impl Command for ExportCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Export custom commands or environment variables from a module."
|
"Export definitions or environment variables from a module."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extra_usage(&self) -> &str {
|
fn extra_usage(&self) -> &str {
|
||||||
|
56
crates/nu-command/src/core_commands/export_use.rs
Normal file
56
crates/nu-command/src/core_commands/export_use.rs
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::{Category, Example, PipelineData, Signature, Span, SyntaxShape, Value};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ExportUse;
|
||||||
|
|
||||||
|
impl Command for ExportUse {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"export use"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Use definitions from a module and export them from this module"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
|
Signature::build("export use")
|
||||||
|
.required("pattern", SyntaxShape::ImportPattern, "import pattern")
|
||||||
|
.category(Category::Core)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extra_usage(&self) -> &str {
|
||||||
|
r#"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<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
|
Ok(PipelineData::new(call.head))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![Example {
|
||||||
|
description: "Re-export a command from another module",
|
||||||
|
example: r#"module spam { export def foo [] { "foo" } }
|
||||||
|
module eggs { export use spam foo }
|
||||||
|
use eggs foo
|
||||||
|
foo
|
||||||
|
"#,
|
||||||
|
result: Some(Value::String {
|
||||||
|
val: "foo".to_string(),
|
||||||
|
span: Span::test_data(),
|
||||||
|
}),
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
@ -12,6 +12,7 @@ mod export_def;
|
|||||||
mod export_def_env;
|
mod export_def_env;
|
||||||
mod export_env;
|
mod export_env;
|
||||||
mod export_extern;
|
mod export_extern;
|
||||||
|
mod export_use;
|
||||||
mod extern_;
|
mod extern_;
|
||||||
mod for_;
|
mod for_;
|
||||||
pub mod help;
|
pub mod help;
|
||||||
@ -40,6 +41,7 @@ pub use export_def::ExportDef;
|
|||||||
pub use export_def_env::ExportDefEnv;
|
pub use export_def_env::ExportDefEnv;
|
||||||
pub use export_env::ExportEnv;
|
pub use export_env::ExportEnv;
|
||||||
pub use export_extern::ExportExtern;
|
pub use export_extern::ExportExtern;
|
||||||
|
pub use export_use::ExportUse;
|
||||||
pub use extern_::Extern;
|
pub use extern_::Extern;
|
||||||
pub use for_::For;
|
pub use for_::For;
|
||||||
pub use help::Help;
|
pub use help::Help;
|
||||||
|
@ -42,6 +42,7 @@ pub fn create_default_context() -> EngineState {
|
|||||||
ExportDefEnv,
|
ExportDefEnv,
|
||||||
ExportEnv,
|
ExportEnv,
|
||||||
ExportExtern,
|
ExportExtern,
|
||||||
|
ExportUse,
|
||||||
Extern,
|
Extern,
|
||||||
For,
|
For,
|
||||||
Help,
|
Help,
|
||||||
|
@ -606,7 +606,7 @@ pub fn parse_export(
|
|||||||
working_set: &mut StateWorkingSet,
|
working_set: &mut StateWorkingSet,
|
||||||
lite_command: &LiteCommand,
|
lite_command: &LiteCommand,
|
||||||
expand_aliases_denylist: &[usize],
|
expand_aliases_denylist: &[usize],
|
||||||
) -> (Pipeline, Option<Exportable>, Option<ParseError>) {
|
) -> (Pipeline, Vec<Exportable>, Option<ParseError>) {
|
||||||
let spans = &lite_command.parts[..];
|
let spans = &lite_command.parts[..];
|
||||||
let mut error = None;
|
let mut error = None;
|
||||||
|
|
||||||
@ -614,7 +614,7 @@ pub fn parse_export(
|
|||||||
if working_set.get_span_contents(*sp) != b"export" {
|
if working_set.get_span_contents(*sp) != b"export" {
|
||||||
return (
|
return (
|
||||||
garbage_pipeline(spans),
|
garbage_pipeline(spans),
|
||||||
None,
|
vec![],
|
||||||
Some(ParseError::UnknownState(
|
Some(ParseError::UnknownState(
|
||||||
"expected export statement".into(),
|
"expected export statement".into(),
|
||||||
span(spans),
|
span(spans),
|
||||||
@ -626,7 +626,7 @@ pub fn parse_export(
|
|||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
garbage_pipeline(spans),
|
garbage_pipeline(spans),
|
||||||
None,
|
vec![],
|
||||||
Some(ParseError::UnknownState(
|
Some(ParseError::UnknownState(
|
||||||
"got empty input for parsing export statement".into(),
|
"got empty input for parsing export statement".into(),
|
||||||
span(spans),
|
span(spans),
|
||||||
@ -639,7 +639,7 @@ pub fn parse_export(
|
|||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
garbage_pipeline(spans),
|
garbage_pipeline(spans),
|
||||||
None,
|
vec![],
|
||||||
Some(ParseError::InternalError(
|
Some(ParseError::InternalError(
|
||||||
"missing export command".into(),
|
"missing export command".into(),
|
||||||
export_span,
|
export_span,
|
||||||
@ -655,7 +655,7 @@ pub fn parse_export(
|
|||||||
redirect_stderr: false,
|
redirect_stderr: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
let exportable = if let Some(kw_span) = spans.get(1) {
|
let exportables = if let Some(kw_span) = spans.get(1) {
|
||||||
let kw_name = working_set.get_span_contents(*kw_span);
|
let kw_name = working_set.get_span_contents(*kw_span);
|
||||||
match kw_name {
|
match kw_name {
|
||||||
b"def" => {
|
b"def" => {
|
||||||
@ -673,7 +673,7 @@ pub fn parse_export(
|
|||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
garbage_pipeline(spans),
|
garbage_pipeline(spans),
|
||||||
None,
|
vec![],
|
||||||
Some(ParseError::InternalError(
|
Some(ParseError::InternalError(
|
||||||
"missing 'export def' command".into(),
|
"missing 'export def' command".into(),
|
||||||
export_span,
|
export_span,
|
||||||
@ -700,11 +700,16 @@ pub fn parse_export(
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
if error.is_none() {
|
let mut result = vec![];
|
||||||
|
|
||||||
let decl_name = working_set.get_span_contents(spans[2]);
|
let decl_name = working_set.get_span_contents(spans[2]);
|
||||||
let decl_name = trim_quotes(decl_name);
|
let decl_name = trim_quotes(decl_name);
|
||||||
|
|
||||||
if let Some(decl_id) = working_set.find_decl(decl_name, &Type::Any) {
|
if let Some(decl_id) = working_set.find_decl(decl_name, &Type::Any) {
|
||||||
Some(Exportable::Decl(decl_id))
|
result.push(Exportable::Decl {
|
||||||
|
name: decl_name.to_vec(),
|
||||||
|
id: decl_id,
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
error = error.or_else(|| {
|
error = error.or_else(|| {
|
||||||
Some(ParseError::InternalError(
|
Some(ParseError::InternalError(
|
||||||
@ -712,11 +717,9 @@ pub fn parse_export(
|
|||||||
span(&spans[1..]),
|
span(&spans[1..]),
|
||||||
))
|
))
|
||||||
});
|
});
|
||||||
None
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
result
|
||||||
}
|
}
|
||||||
b"def-env" => {
|
b"def-env" => {
|
||||||
let lite_command = LiteCommand {
|
let lite_command = LiteCommand {
|
||||||
@ -733,7 +736,7 @@ pub fn parse_export(
|
|||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
garbage_pipeline(spans),
|
garbage_pipeline(spans),
|
||||||
None,
|
vec![],
|
||||||
Some(ParseError::InternalError(
|
Some(ParseError::InternalError(
|
||||||
"missing 'export def-env' command".into(),
|
"missing 'export def-env' command".into(),
|
||||||
export_span,
|
export_span,
|
||||||
@ -760,11 +763,16 @@ pub fn parse_export(
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
if error.is_none() {
|
let mut result = vec![];
|
||||||
|
|
||||||
let decl_name = working_set.get_span_contents(spans[2]);
|
let decl_name = working_set.get_span_contents(spans[2]);
|
||||||
let decl_name = trim_quotes(decl_name);
|
let decl_name = trim_quotes(decl_name);
|
||||||
|
|
||||||
if let Some(decl_id) = working_set.find_decl(decl_name, &Type::Any) {
|
if let Some(decl_id) = working_set.find_decl(decl_name, &Type::Any) {
|
||||||
Some(Exportable::Decl(decl_id))
|
result.push(Exportable::Decl {
|
||||||
|
name: decl_name.to_vec(),
|
||||||
|
id: decl_id,
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
error = error.or_else(|| {
|
error = error.or_else(|| {
|
||||||
Some(ParseError::InternalError(
|
Some(ParseError::InternalError(
|
||||||
@ -772,11 +780,9 @@ pub fn parse_export(
|
|||||||
span(&spans[1..]),
|
span(&spans[1..]),
|
||||||
))
|
))
|
||||||
});
|
});
|
||||||
None
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
result
|
||||||
}
|
}
|
||||||
b"extern" => {
|
b"extern" => {
|
||||||
let lite_command = LiteCommand {
|
let lite_command = LiteCommand {
|
||||||
@ -793,7 +799,7 @@ pub fn parse_export(
|
|||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
garbage_pipeline(spans),
|
garbage_pipeline(spans),
|
||||||
None,
|
vec![],
|
||||||
Some(ParseError::InternalError(
|
Some(ParseError::InternalError(
|
||||||
"missing 'export extern' command".into(),
|
"missing 'export extern' command".into(),
|
||||||
export_span,
|
export_span,
|
||||||
@ -820,11 +826,16 @@ pub fn parse_export(
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
if error.is_none() {
|
let mut result = vec![];
|
||||||
|
|
||||||
let decl_name = working_set.get_span_contents(spans[2]);
|
let decl_name = working_set.get_span_contents(spans[2]);
|
||||||
let decl_name = trim_quotes(decl_name);
|
let decl_name = trim_quotes(decl_name);
|
||||||
|
|
||||||
if let Some(decl_id) = working_set.find_decl(decl_name, &Type::Any) {
|
if let Some(decl_id) = working_set.find_decl(decl_name, &Type::Any) {
|
||||||
Some(Exportable::Decl(decl_id))
|
result.push(Exportable::Decl {
|
||||||
|
name: decl_name.to_vec(),
|
||||||
|
id: decl_id,
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
error = error.or_else(|| {
|
error = error.or_else(|| {
|
||||||
Some(ParseError::InternalError(
|
Some(ParseError::InternalError(
|
||||||
@ -832,11 +843,9 @@ pub fn parse_export(
|
|||||||
span(&spans[1..]),
|
span(&spans[1..]),
|
||||||
))
|
))
|
||||||
});
|
});
|
||||||
None
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
result
|
||||||
}
|
}
|
||||||
b"alias" => {
|
b"alias" => {
|
||||||
let lite_command = LiteCommand {
|
let lite_command = LiteCommand {
|
||||||
@ -853,7 +862,7 @@ pub fn parse_export(
|
|||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
garbage_pipeline(spans),
|
garbage_pipeline(spans),
|
||||||
None,
|
vec![],
|
||||||
Some(ParseError::InternalError(
|
Some(ParseError::InternalError(
|
||||||
"missing 'export alias' command".into(),
|
"missing 'export alias' command".into(),
|
||||||
export_span,
|
export_span,
|
||||||
@ -880,11 +889,16 @@ pub fn parse_export(
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
if error.is_none() {
|
let mut result = vec![];
|
||||||
|
|
||||||
let alias_name = working_set.get_span_contents(spans[2]);
|
let alias_name = working_set.get_span_contents(spans[2]);
|
||||||
let alias_name = trim_quotes(alias_name);
|
let alias_name = trim_quotes(alias_name);
|
||||||
|
|
||||||
if let Some(alias_id) = working_set.find_alias(alias_name) {
|
if let Some(alias_id) = working_set.find_alias(alias_name) {
|
||||||
Some(Exportable::Alias(alias_id))
|
result.push(Exportable::Alias {
|
||||||
|
name: alias_name.to_vec(),
|
||||||
|
id: alias_id,
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
error = error.or_else(|| {
|
error = error.or_else(|| {
|
||||||
Some(ParseError::InternalError(
|
Some(ParseError::InternalError(
|
||||||
@ -892,11 +906,53 @@ pub fn parse_export(
|
|||||||
span(&spans[1..]),
|
span(&spans[1..]),
|
||||||
))
|
))
|
||||||
});
|
});
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
b"use" => {
|
||||||
|
let lite_command = LiteCommand {
|
||||||
|
comments: lite_command.comments.clone(),
|
||||||
|
parts: spans[1..].to_vec(),
|
||||||
|
};
|
||||||
|
let (pipeline, exportables, err) =
|
||||||
|
parse_use(working_set, &lite_command.parts, expand_aliases_denylist);
|
||||||
|
error = error.or(err);
|
||||||
|
|
||||||
|
let export_use_decl_id =
|
||||||
|
if let Some(id) = working_set.find_decl(b"export use", &Type::Any) {
|
||||||
|
id
|
||||||
} else {
|
} else {
|
||||||
None
|
return (
|
||||||
}
|
garbage_pipeline(spans),
|
||||||
|
vec![],
|
||||||
|
Some(ParseError::InternalError(
|
||||||
|
"missing 'export use' command".into(),
|
||||||
|
export_span,
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Trying to warp the 'use' call into the 'export use' in a very clumsy way
|
||||||
|
if let Some(Expression {
|
||||||
|
expr: Expr::Call(ref use_call),
|
||||||
|
..
|
||||||
|
}) = pipeline.expressions.get(0)
|
||||||
|
{
|
||||||
|
call = use_call.clone();
|
||||||
|
|
||||||
|
call.head = span(&spans[0..=1]);
|
||||||
|
call.decl_id = export_use_decl_id;
|
||||||
|
} else {
|
||||||
|
error = error.or_else(|| {
|
||||||
|
Some(ParseError::InternalError(
|
||||||
|
"unexpected output from parsing a definition".into(),
|
||||||
|
span(&spans[1..]),
|
||||||
|
))
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exportables
|
||||||
}
|
}
|
||||||
b"env" => {
|
b"env" => {
|
||||||
if let Some(id) = working_set.find_decl(b"export env", &Type::Any) {
|
if let Some(id) = working_set.find_decl(b"export env", &Type::Any) {
|
||||||
@ -904,7 +960,7 @@ pub fn parse_export(
|
|||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
garbage_pipeline(spans),
|
garbage_pipeline(spans),
|
||||||
None,
|
vec![],
|
||||||
Some(ParseError::InternalError(
|
Some(ParseError::InternalError(
|
||||||
"missing 'export env' command".into(),
|
"missing 'export env' command".into(),
|
||||||
export_span,
|
export_span,
|
||||||
@ -917,12 +973,16 @@ pub fn parse_export(
|
|||||||
|
|
||||||
call.head = span(&spans[0..=1]);
|
call.head = span(&spans[0..=1]);
|
||||||
|
|
||||||
|
let mut result = vec![];
|
||||||
|
|
||||||
if let Some(name_span) = spans.get(2) {
|
if let Some(name_span) = spans.get(2) {
|
||||||
let (name_expr, err) =
|
let (name_expr, err) =
|
||||||
parse_string(working_set, *name_span, expand_aliases_denylist);
|
parse_string(working_set, *name_span, expand_aliases_denylist);
|
||||||
error = error.or(err);
|
error = error.or(err);
|
||||||
call.add_positional(name_expr);
|
call.add_positional(name_expr);
|
||||||
|
|
||||||
|
let env_var_name = working_set.get_span_contents(*name_span).to_vec();
|
||||||
|
|
||||||
if let Some(block_span) = spans.get(3) {
|
if let Some(block_span) = spans.get(3) {
|
||||||
let (block_expr, err) = parse_block_expression(
|
let (block_expr, err) = parse_block_expression(
|
||||||
working_set,
|
working_set,
|
||||||
@ -932,12 +992,15 @@ pub fn parse_export(
|
|||||||
);
|
);
|
||||||
error = error.or(err);
|
error = error.or(err);
|
||||||
|
|
||||||
let exportable = if let Expression {
|
if let Expression {
|
||||||
expr: Expr::Block(block_id),
|
expr: Expr::Block(block_id),
|
||||||
..
|
..
|
||||||
} = block_expr
|
} = block_expr
|
||||||
{
|
{
|
||||||
Some(Exportable::EnvVar(block_id))
|
result.push(Exportable::EnvVar {
|
||||||
|
name: env_var_name,
|
||||||
|
id: block_id,
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
error = error.or_else(|| {
|
error = error.or_else(|| {
|
||||||
Some(ParseError::InternalError(
|
Some(ParseError::InternalError(
|
||||||
@ -945,12 +1008,9 @@ pub fn parse_export(
|
|||||||
*block_span,
|
*block_span,
|
||||||
))
|
))
|
||||||
});
|
});
|
||||||
None
|
}
|
||||||
};
|
|
||||||
|
|
||||||
call.add_positional(block_expr);
|
call.add_positional(block_expr);
|
||||||
|
|
||||||
exportable
|
|
||||||
} else {
|
} else {
|
||||||
let err_span = Span {
|
let err_span = Span {
|
||||||
start: name_span.end,
|
start: name_span.end,
|
||||||
@ -964,8 +1024,6 @@ pub fn parse_export(
|
|||||||
call_signature,
|
call_signature,
|
||||||
))
|
))
|
||||||
});
|
});
|
||||||
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let err_span = Span {
|
let err_span = Span {
|
||||||
@ -980,20 +1038,20 @@ pub fn parse_export(
|
|||||||
call_signature,
|
call_signature,
|
||||||
))
|
))
|
||||||
});
|
});
|
||||||
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
result
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
error = error.or_else(|| {
|
error = error.or_else(|| {
|
||||||
Some(ParseError::Expected(
|
Some(ParseError::Expected(
|
||||||
// TODO: Fill in more keywords as they come
|
// TODO: Fill in more keywords as they come
|
||||||
"def, def-env, alias, or env keyword".into(),
|
"def, def-env, alias, use, or env keyword".into(),
|
||||||
spans[1],
|
spans[1],
|
||||||
))
|
))
|
||||||
});
|
});
|
||||||
|
|
||||||
None
|
vec![]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -1008,7 +1066,7 @@ pub fn parse_export(
|
|||||||
))
|
))
|
||||||
});
|
});
|
||||||
|
|
||||||
None
|
vec![]
|
||||||
};
|
};
|
||||||
|
|
||||||
(
|
(
|
||||||
@ -1018,7 +1076,7 @@ pub fn parse_export(
|
|||||||
ty: Type::Any,
|
ty: Type::Any,
|
||||||
custom_completion: None,
|
custom_completion: None,
|
||||||
}]),
|
}]),
|
||||||
exportable,
|
exportables,
|
||||||
error,
|
error,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -1085,6 +1143,15 @@ pub fn parse_module_block(
|
|||||||
|
|
||||||
(pipeline, err)
|
(pipeline, err)
|
||||||
}
|
}
|
||||||
|
b"use" => {
|
||||||
|
let (pipeline, _, err) = parse_use(
|
||||||
|
working_set,
|
||||||
|
&pipeline.commands[0].parts,
|
||||||
|
expand_aliases_denylist,
|
||||||
|
);
|
||||||
|
|
||||||
|
(pipeline, err)
|
||||||
|
}
|
||||||
// TODO: Currently, it is not possible to define a private env var.
|
// TODO: Currently, it is not possible to define a private env var.
|
||||||
// TODO: Exported env vars are usable iside the module only if correctly
|
// TODO: Exported env vars are usable iside the module only if correctly
|
||||||
// exported by the user. For example:
|
// exported by the user. For example:
|
||||||
@ -1094,28 +1161,25 @@ pub fn parse_module_block(
|
|||||||
// will work only if you call `use foo *; b` but not with `use foo; foo b`
|
// will work only if you call `use foo *; b` but not with `use foo; foo b`
|
||||||
// since in the second case, the name of the env var would be $env."foo a".
|
// since in the second case, the name of the env var would be $env."foo a".
|
||||||
b"export" => {
|
b"export" => {
|
||||||
let (pipe, exportable, err) = parse_export(
|
let (pipe, exportables, err) = parse_export(
|
||||||
working_set,
|
working_set,
|
||||||
&pipeline.commands[0],
|
&pipeline.commands[0],
|
||||||
expand_aliases_denylist,
|
expand_aliases_denylist,
|
||||||
);
|
);
|
||||||
|
|
||||||
if err.is_none() {
|
if err.is_none() {
|
||||||
let name_span = pipeline.commands[0].parts[2];
|
for exportable in exportables {
|
||||||
let name = working_set.get_span_contents(name_span);
|
|
||||||
let name = trim_quotes(name);
|
|
||||||
|
|
||||||
match exportable {
|
match exportable {
|
||||||
Some(Exportable::Decl(decl_id)) => {
|
Exportable::Decl { name, id } => {
|
||||||
module.add_decl(name, decl_id);
|
module.add_decl(name, id);
|
||||||
}
|
}
|
||||||
Some(Exportable::EnvVar(block_id)) => {
|
Exportable::Alias { name, id } => {
|
||||||
module.add_env_var(name, block_id);
|
module.add_alias(name, id);
|
||||||
|
}
|
||||||
|
Exportable::EnvVar { name, id } => {
|
||||||
|
module.add_env_var(name, id);
|
||||||
}
|
}
|
||||||
Some(Exportable::Alias(alias_id)) => {
|
|
||||||
module.add_alias(name, alias_id);
|
|
||||||
}
|
}
|
||||||
None => {} // None should always come with error from parse_export()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1242,10 +1306,11 @@ pub fn parse_use(
|
|||||||
working_set: &mut StateWorkingSet,
|
working_set: &mut StateWorkingSet,
|
||||||
spans: &[Span],
|
spans: &[Span],
|
||||||
expand_aliases_denylist: &[usize],
|
expand_aliases_denylist: &[usize],
|
||||||
) -> (Pipeline, Option<ParseError>) {
|
) -> (Pipeline, Vec<Exportable>, Option<ParseError>) {
|
||||||
if working_set.get_span_contents(spans[0]) != b"use" {
|
if working_set.get_span_contents(spans[0]) != b"use" {
|
||||||
return (
|
return (
|
||||||
garbage_pipeline(spans),
|
garbage_pipeline(spans),
|
||||||
|
vec![],
|
||||||
Some(ParseError::UnknownState(
|
Some(ParseError::UnknownState(
|
||||||
"internal error: Wrong call name for 'use' command".into(),
|
"internal error: Wrong call name for 'use' command".into(),
|
||||||
span(spans),
|
span(spans),
|
||||||
@ -1279,6 +1344,7 @@ pub fn parse_use(
|
|||||||
ty: output,
|
ty: output,
|
||||||
custom_completion: None,
|
custom_completion: None,
|
||||||
}]),
|
}]),
|
||||||
|
vec![],
|
||||||
err,
|
err,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -1288,6 +1354,7 @@ pub fn parse_use(
|
|||||||
None => {
|
None => {
|
||||||
return (
|
return (
|
||||||
garbage_pipeline(spans),
|
garbage_pipeline(spans),
|
||||||
|
vec![],
|
||||||
Some(ParseError::UnknownState(
|
Some(ParseError::UnknownState(
|
||||||
"internal error: 'use' declaration not found".into(),
|
"internal error: 'use' declaration not found".into(),
|
||||||
span(spans),
|
span(spans),
|
||||||
@ -1302,6 +1369,7 @@ pub fn parse_use(
|
|||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
garbage_pipeline(spans),
|
garbage_pipeline(spans),
|
||||||
|
vec![],
|
||||||
Some(ParseError::UnknownState(
|
Some(ParseError::UnknownState(
|
||||||
"internal error: Import pattern positional is not import pattern".into(),
|
"internal error: Import pattern positional is not import pattern".into(),
|
||||||
expr.span,
|
expr.span,
|
||||||
@ -1311,6 +1379,7 @@ pub fn parse_use(
|
|||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
garbage_pipeline(spans),
|
garbage_pipeline(spans),
|
||||||
|
vec![],
|
||||||
Some(ParseError::UnknownState(
|
Some(ParseError::UnknownState(
|
||||||
"internal error: Missing required positional after call parsing".into(),
|
"internal error: Missing required positional after call parsing".into(),
|
||||||
call_span,
|
call_span,
|
||||||
@ -1348,6 +1417,7 @@ pub fn parse_use(
|
|||||||
ty: Type::Any,
|
ty: Type::Any,
|
||||||
custom_completion: None,
|
custom_completion: None,
|
||||||
}]),
|
}]),
|
||||||
|
vec![],
|
||||||
Some(ParseError::ModuleNotFound(spans[1])),
|
Some(ParseError::ModuleNotFound(spans[1])),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -1401,6 +1471,7 @@ pub fn parse_use(
|
|||||||
ty: Type::Any,
|
ty: Type::Any,
|
||||||
custom_completion: None,
|
custom_completion: None,
|
||||||
}]),
|
}]),
|
||||||
|
vec![],
|
||||||
Some(ParseError::ModuleNotFound(spans[1])),
|
Some(ParseError::ModuleNotFound(spans[1])),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -1413,7 +1484,11 @@ pub fn parse_use(
|
|||||||
(import_pattern, Module::new())
|
(import_pattern, Module::new())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return (garbage_pipeline(spans), Some(ParseError::NonUtf8(spans[1])));
|
return (
|
||||||
|
garbage_pipeline(spans),
|
||||||
|
vec![],
|
||||||
|
Some(ParseError::NonUtf8(spans[1])),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1459,6 +1534,22 @@ pub fn parse_use(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let exportables = decls_to_use
|
||||||
|
.iter()
|
||||||
|
.map(|(name, decl_id)| Exportable::Decl {
|
||||||
|
name: name.clone(),
|
||||||
|
id: *decl_id,
|
||||||
|
})
|
||||||
|
.chain(
|
||||||
|
aliases_to_use
|
||||||
|
.iter()
|
||||||
|
.map(|(name, alias_id)| Exportable::Alias {
|
||||||
|
name: name.clone(),
|
||||||
|
id: *alias_id,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.collect();
|
||||||
|
|
||||||
// Extend the current scope with the module's exportables
|
// Extend the current scope with the module's exportables
|
||||||
working_set.use_decls(decls_to_use);
|
working_set.use_decls(decls_to_use);
|
||||||
working_set.use_aliases(aliases_to_use);
|
working_set.use_aliases(aliases_to_use);
|
||||||
@ -1486,6 +1577,7 @@ pub fn parse_use(
|
|||||||
ty: Type::Any,
|
ty: Type::Any,
|
||||||
custom_completion: None,
|
custom_completion: None,
|
||||||
}]),
|
}]),
|
||||||
|
exportables,
|
||||||
error,
|
error,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -1588,13 +1680,13 @@ pub fn parse_hide(
|
|||||||
if let Some(id) = working_set.find_alias(&import_pattern.head.name) {
|
if let Some(id) = working_set.find_alias(&import_pattern.head.name) {
|
||||||
// an alias,
|
// an alias,
|
||||||
let mut module = Module::new();
|
let mut module = Module::new();
|
||||||
module.add_alias(&import_pattern.head.name, id);
|
module.add_alias(import_pattern.head.name.clone(), id);
|
||||||
|
|
||||||
(false, module)
|
(false, module)
|
||||||
} else if let Some(id) = working_set.find_decl(&import_pattern.head.name, &Type::Any) {
|
} else if let Some(id) = working_set.find_decl(&import_pattern.head.name, &Type::Any) {
|
||||||
// a custom command,
|
// a custom command,
|
||||||
let mut module = Module::new();
|
let mut module = Module::new();
|
||||||
module.add_decl(&import_pattern.head.name, id);
|
module.add_decl(import_pattern.head.name.clone(), id);
|
||||||
|
|
||||||
(false, module)
|
(false, module)
|
||||||
} else {
|
} else {
|
||||||
|
@ -2812,9 +2812,9 @@ pub fn parse_import_pattern(
|
|||||||
expr: Expr::List(list),
|
expr: Expr::List(list),
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
for l in list {
|
for expr in list {
|
||||||
let contents = working_set.get_span_contents(l.span);
|
let contents = working_set.get_span_contents(expr.span);
|
||||||
output.push((contents.to_vec(), l.span));
|
output.push((trim_quotes(contents).to_vec(), expr.span));
|
||||||
}
|
}
|
||||||
|
|
||||||
(
|
(
|
||||||
@ -4786,7 +4786,11 @@ pub fn parse_builtin_commands(
|
|||||||
}
|
}
|
||||||
b"alias" => parse_alias(working_set, &lite_command.parts, expand_aliases_denylist),
|
b"alias" => parse_alias(working_set, &lite_command.parts, expand_aliases_denylist),
|
||||||
b"module" => parse_module(working_set, &lite_command.parts, expand_aliases_denylist),
|
b"module" => parse_module(working_set, &lite_command.parts, expand_aliases_denylist),
|
||||||
b"use" => parse_use(working_set, &lite_command.parts, expand_aliases_denylist),
|
b"use" => {
|
||||||
|
let (pipeline, _, err) =
|
||||||
|
parse_use(working_set, &lite_command.parts, expand_aliases_denylist);
|
||||||
|
(pipeline, err)
|
||||||
|
}
|
||||||
b"overlay" => parse_overlay(working_set, &lite_command.parts, expand_aliases_denylist),
|
b"overlay" => parse_overlay(working_set, &lite_command.parts, expand_aliases_denylist),
|
||||||
b"source" => parse_source(working_set, &lite_command.parts, expand_aliases_denylist),
|
b"source" => parse_source(working_set, &lite_command.parts, expand_aliases_denylist),
|
||||||
b"export" => {
|
b"export" => {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use crate::{AliasId, BlockId, DeclId};
|
use crate::{AliasId, BlockId, DeclId};
|
||||||
|
|
||||||
pub enum Exportable {
|
pub enum Exportable {
|
||||||
Decl(DeclId),
|
Decl { name: Vec<u8>, id: DeclId },
|
||||||
Alias(AliasId),
|
Alias { name: Vec<u8>, id: AliasId },
|
||||||
EnvVar(BlockId),
|
EnvVar { name: Vec<u8>, id: BlockId },
|
||||||
}
|
}
|
||||||
|
@ -33,16 +33,16 @@ impl Module {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_decl(&mut self, name: &[u8], decl_id: DeclId) -> Option<DeclId> {
|
pub fn add_decl(&mut self, name: Vec<u8>, decl_id: DeclId) -> Option<DeclId> {
|
||||||
self.decls.insert(name.to_vec(), decl_id)
|
self.decls.insert(name, decl_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_alias(&mut self, name: &[u8], alias_id: AliasId) -> Option<AliasId> {
|
pub fn add_alias(&mut self, name: Vec<u8>, alias_id: AliasId) -> Option<AliasId> {
|
||||||
self.aliases.insert(name.to_vec(), alias_id)
|
self.aliases.insert(name, alias_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_env_var(&mut self, name: &[u8], block_id: BlockId) -> Option<BlockId> {
|
pub fn add_env_var(&mut self, name: Vec<u8>, block_id: BlockId) -> Option<BlockId> {
|
||||||
self.env_vars.insert(name.to_vec(), block_id)
|
self.env_vars.insert(name, block_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn extend(&mut self, other: &Module) {
|
pub fn extend(&mut self, other: &Module) {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
extern crate nu_test_support;
|
extern crate nu_test_support;
|
||||||
|
|
||||||
mod hooks;
|
mod hooks;
|
||||||
|
mod modules;
|
||||||
mod nu_repl;
|
mod nu_repl;
|
||||||
mod overlays;
|
mod overlays;
|
||||||
mod parsing;
|
mod parsing;
|
||||||
|
347
tests/modules/mod.rs
Normal file
347
tests/modules/mod.rs
Normal file
@ -0,0 +1,347 @@
|
|||||||
|
use nu_test_support::fs::Stub::FileWithContentToBeTrimmed;
|
||||||
|
use nu_test_support::playground::Playground;
|
||||||
|
use nu_test_support::{nu, pipeline};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn module_private_import_decl() {
|
||||||
|
Playground::setup("module_private_import_decl", |dirs, sandbox| {
|
||||||
|
sandbox
|
||||||
|
.with_files(vec![FileWithContentToBeTrimmed(
|
||||||
|
"main.nu",
|
||||||
|
r#"
|
||||||
|
use spam.nu foo-helper
|
||||||
|
|
||||||
|
export def foo [] { foo-helper }
|
||||||
|
"#,
|
||||||
|
)])
|
||||||
|
.with_files(vec![FileWithContentToBeTrimmed(
|
||||||
|
"spam.nu",
|
||||||
|
r#"
|
||||||
|
def get-foo [] { "foo" }
|
||||||
|
export def foo-helper [] { get-foo }
|
||||||
|
"#,
|
||||||
|
)]);
|
||||||
|
|
||||||
|
let inp = &[r#"use main.nu foo"#, r#"foo"#];
|
||||||
|
|
||||||
|
let actual = nu!(cwd: dirs.test(), pipeline(&inp.join("; ")));
|
||||||
|
|
||||||
|
assert_eq!(actual.out, "foo");
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn module_private_import_alias() {
|
||||||
|
Playground::setup("module_private_import_alias", |dirs, sandbox| {
|
||||||
|
sandbox
|
||||||
|
.with_files(vec![FileWithContentToBeTrimmed(
|
||||||
|
"main.nu",
|
||||||
|
r#"
|
||||||
|
use spam.nu foo-helper
|
||||||
|
|
||||||
|
export def foo [] { foo-helper }
|
||||||
|
"#,
|
||||||
|
)])
|
||||||
|
.with_files(vec![FileWithContentToBeTrimmed(
|
||||||
|
"spam.nu",
|
||||||
|
r#"
|
||||||
|
export alias foo-helper = "foo"
|
||||||
|
"#,
|
||||||
|
)]);
|
||||||
|
|
||||||
|
let inp = &[r#"use main.nu foo"#, r#"foo"#];
|
||||||
|
|
||||||
|
let actual = nu!(cwd: dirs.test(), pipeline(&inp.join("; ")));
|
||||||
|
|
||||||
|
assert_eq!(actual.out, "foo");
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn module_private_import_decl_not_public() {
|
||||||
|
Playground::setup("module_private_import_decl_not_public", |dirs, sandbox| {
|
||||||
|
sandbox
|
||||||
|
.with_files(vec![FileWithContentToBeTrimmed(
|
||||||
|
"main.nu",
|
||||||
|
r#"
|
||||||
|
use spam.nu foo-helper
|
||||||
|
"#,
|
||||||
|
)])
|
||||||
|
.with_files(vec![FileWithContentToBeTrimmed(
|
||||||
|
"spam.nu",
|
||||||
|
r#"
|
||||||
|
def get-foo [] { "foo" }
|
||||||
|
export def foo-helper [] { get-foo }
|
||||||
|
"#,
|
||||||
|
)]);
|
||||||
|
|
||||||
|
let inp = &[r#"use main.nu foo"#, r#"foo-helper"#];
|
||||||
|
|
||||||
|
let actual = nu!(cwd: dirs.test(), pipeline(&inp.join("; ")));
|
||||||
|
|
||||||
|
assert!(!actual.err.is_empty());
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO -- doesn't work because modules are never evaluated
|
||||||
|
#[ignore]
|
||||||
|
#[test]
|
||||||
|
fn module_private_import_env() {
|
||||||
|
Playground::setup("module_private_import_env", |dirs, sandbox| {
|
||||||
|
sandbox
|
||||||
|
.with_files(vec![FileWithContentToBeTrimmed(
|
||||||
|
"main.nu",
|
||||||
|
r#"
|
||||||
|
use spam.nu FOO_HELPER
|
||||||
|
|
||||||
|
export def foo [] { $env.FOO_HELPER }
|
||||||
|
"#,
|
||||||
|
)])
|
||||||
|
.with_files(vec![FileWithContentToBeTrimmed(
|
||||||
|
"spam.nu",
|
||||||
|
r#"
|
||||||
|
export env FOO_HELPER { "foo" }
|
||||||
|
"#,
|
||||||
|
)]);
|
||||||
|
|
||||||
|
let inp = &[r#"use main.nu foo"#, r#"foo"#];
|
||||||
|
|
||||||
|
let actual = nu!(cwd: dirs.test(), pipeline(&inp.join("; ")));
|
||||||
|
|
||||||
|
assert_eq!(actual.out, "foo");
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn module_public_import_decl() {
|
||||||
|
Playground::setup("module_public_import_decl", |dirs, sandbox| {
|
||||||
|
sandbox
|
||||||
|
.with_files(vec![FileWithContentToBeTrimmed(
|
||||||
|
"main.nu",
|
||||||
|
r#"
|
||||||
|
export use spam.nu foo
|
||||||
|
"#,
|
||||||
|
)])
|
||||||
|
.with_files(vec![FileWithContentToBeTrimmed(
|
||||||
|
"spam.nu",
|
||||||
|
r#"
|
||||||
|
def foo-helper [] { "foo" }
|
||||||
|
export def foo [] { foo-helper }
|
||||||
|
"#,
|
||||||
|
)]);
|
||||||
|
|
||||||
|
let inp = &[r#"use main.nu foo"#, r#"foo"#];
|
||||||
|
|
||||||
|
let actual = nu!(cwd: dirs.test(), pipeline(&inp.join("; ")));
|
||||||
|
|
||||||
|
assert_eq!(actual.out, "foo");
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn module_public_import_alias() {
|
||||||
|
Playground::setup("module_public_import_alias", |dirs, sandbox| {
|
||||||
|
sandbox
|
||||||
|
.with_files(vec![FileWithContentToBeTrimmed(
|
||||||
|
"main.nu",
|
||||||
|
r#"
|
||||||
|
export use spam.nu foo
|
||||||
|
"#,
|
||||||
|
)])
|
||||||
|
.with_files(vec![FileWithContentToBeTrimmed(
|
||||||
|
"spam.nu",
|
||||||
|
r#"
|
||||||
|
export alias foo = "foo"
|
||||||
|
"#,
|
||||||
|
)]);
|
||||||
|
|
||||||
|
let inp = &[r#"use main.nu foo"#, r#"foo"#];
|
||||||
|
|
||||||
|
let actual = nu!(cwd: dirs.test(), pipeline(&inp.join("; ")));
|
||||||
|
|
||||||
|
assert_eq!(actual.out, "foo");
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO -- doesn't work because modules are never evaluated
|
||||||
|
#[ignore]
|
||||||
|
#[test]
|
||||||
|
fn module_public_import_env() {
|
||||||
|
Playground::setup("module_public_import_decl", |dirs, sandbox| {
|
||||||
|
sandbox
|
||||||
|
.with_files(vec![FileWithContentToBeTrimmed(
|
||||||
|
"main.nu",
|
||||||
|
r#"
|
||||||
|
export use spam.nu FOO
|
||||||
|
"#,
|
||||||
|
)])
|
||||||
|
.with_files(vec![FileWithContentToBeTrimmed(
|
||||||
|
"spam.nu",
|
||||||
|
r#"
|
||||||
|
export env FOO { "foo" }
|
||||||
|
"#,
|
||||||
|
)]);
|
||||||
|
|
||||||
|
let inp = &[r#"use main.nu FOO"#, r#"$env.FOO"#];
|
||||||
|
|
||||||
|
let actual = nu!(cwd: dirs.test(), pipeline(&inp.join("; ")));
|
||||||
|
|
||||||
|
assert_eq!(actual.out, "foo");
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn module_nested_imports() {
|
||||||
|
Playground::setup("module_nested_imports", |dirs, sandbox| {
|
||||||
|
sandbox
|
||||||
|
.with_files(vec![FileWithContentToBeTrimmed(
|
||||||
|
"main.nu",
|
||||||
|
r#"
|
||||||
|
export use spam.nu [ foo bar ]
|
||||||
|
"#,
|
||||||
|
)])
|
||||||
|
.with_files(vec![FileWithContentToBeTrimmed(
|
||||||
|
"spam.nu",
|
||||||
|
r#"
|
||||||
|
export use spam2.nu [ foo bar ]
|
||||||
|
"#,
|
||||||
|
)])
|
||||||
|
.with_files(vec![FileWithContentToBeTrimmed(
|
||||||
|
"spam2.nu",
|
||||||
|
r#"
|
||||||
|
export use spam3.nu [ foo bar ]
|
||||||
|
"#,
|
||||||
|
)])
|
||||||
|
.with_files(vec![FileWithContentToBeTrimmed(
|
||||||
|
"spam3.nu",
|
||||||
|
r#"
|
||||||
|
export def foo [] { "foo" }
|
||||||
|
export alias bar = "bar"
|
||||||
|
"#,
|
||||||
|
)]);
|
||||||
|
|
||||||
|
let inp1 = &[r#"use main.nu foo"#, r#"foo"#];
|
||||||
|
let inp2 = &[r#"use main.nu bar"#, r#"bar"#];
|
||||||
|
|
||||||
|
let actual = nu!(cwd: dirs.test(), pipeline(&inp1.join("; ")));
|
||||||
|
assert_eq!(actual.out, "foo");
|
||||||
|
|
||||||
|
let actual = nu!(cwd: dirs.test(), pipeline(&inp2.join("; ")));
|
||||||
|
assert_eq!(actual.out, "bar");
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn module_nested_imports_in_dirs() {
|
||||||
|
Playground::setup("module_nested_imports_in_dirs", |dirs, sandbox| {
|
||||||
|
sandbox
|
||||||
|
.mkdir("spam")
|
||||||
|
.mkdir("spam/spam2")
|
||||||
|
.mkdir("spam/spam3")
|
||||||
|
.with_files(vec![FileWithContentToBeTrimmed(
|
||||||
|
"main.nu",
|
||||||
|
r#"
|
||||||
|
export use spam/spam.nu [ foo bar ]
|
||||||
|
"#,
|
||||||
|
)])
|
||||||
|
.with_files(vec![FileWithContentToBeTrimmed(
|
||||||
|
"spam/spam.nu",
|
||||||
|
r#"
|
||||||
|
export use spam2/spam2.nu [ foo bar ]
|
||||||
|
"#,
|
||||||
|
)])
|
||||||
|
.with_files(vec![FileWithContentToBeTrimmed(
|
||||||
|
"spam/spam2/spam2.nu",
|
||||||
|
r#"
|
||||||
|
export use ../spam3/spam3.nu [ foo bar ]
|
||||||
|
"#,
|
||||||
|
)])
|
||||||
|
.with_files(vec![FileWithContentToBeTrimmed(
|
||||||
|
"spam/spam3/spam3.nu",
|
||||||
|
r#"
|
||||||
|
export def foo [] { "foo" }
|
||||||
|
export alias bar = "bar"
|
||||||
|
"#,
|
||||||
|
)]);
|
||||||
|
|
||||||
|
let inp1 = &[r#"use main.nu foo"#, r#"foo"#];
|
||||||
|
let inp2 = &[r#"use main.nu bar"#, r#"bar"#];
|
||||||
|
|
||||||
|
let actual = nu!(cwd: dirs.test(), pipeline(&inp1.join("; ")));
|
||||||
|
assert_eq!(actual.out, "foo");
|
||||||
|
|
||||||
|
let actual = nu!(cwd: dirs.test(), pipeline(&inp2.join("; ")));
|
||||||
|
assert_eq!(actual.out, "bar");
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn module_public_import_decl_prefixed() {
|
||||||
|
Playground::setup("module_public_import_decl", |dirs, sandbox| {
|
||||||
|
sandbox
|
||||||
|
.with_files(vec![FileWithContentToBeTrimmed(
|
||||||
|
"main.nu",
|
||||||
|
r#"
|
||||||
|
export use spam.nu
|
||||||
|
"#,
|
||||||
|
)])
|
||||||
|
.with_files(vec![FileWithContentToBeTrimmed(
|
||||||
|
"spam.nu",
|
||||||
|
r#"
|
||||||
|
def foo-helper [] { "foo" }
|
||||||
|
export def foo [] { foo-helper }
|
||||||
|
"#,
|
||||||
|
)]);
|
||||||
|
|
||||||
|
let inp = &[r#"use main.nu"#, r#"main spam foo"#];
|
||||||
|
|
||||||
|
let actual = nu!(cwd: dirs.test(), pipeline(&inp.join("; ")));
|
||||||
|
|
||||||
|
assert_eq!(actual.out, "foo");
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn module_nested_imports_in_dirs_prefixed() {
|
||||||
|
Playground::setup("module_nested_imports_in_dirs", |dirs, sandbox| {
|
||||||
|
sandbox
|
||||||
|
.mkdir("spam")
|
||||||
|
.mkdir("spam/spam2")
|
||||||
|
.mkdir("spam/spam3")
|
||||||
|
.with_files(vec![FileWithContentToBeTrimmed(
|
||||||
|
"main.nu",
|
||||||
|
r#"
|
||||||
|
export use spam/spam.nu [ "spam2 foo" "spam2 spam3 bar" ]
|
||||||
|
"#,
|
||||||
|
)])
|
||||||
|
.with_files(vec![FileWithContentToBeTrimmed(
|
||||||
|
"spam/spam.nu",
|
||||||
|
r#"
|
||||||
|
export use spam2/spam2.nu
|
||||||
|
"#,
|
||||||
|
)])
|
||||||
|
.with_files(vec![FileWithContentToBeTrimmed(
|
||||||
|
"spam/spam2/spam2.nu",
|
||||||
|
r#"
|
||||||
|
export use ../spam3/spam3.nu
|
||||||
|
export use ../spam3/spam3.nu foo
|
||||||
|
"#,
|
||||||
|
)])
|
||||||
|
.with_files(vec![FileWithContentToBeTrimmed(
|
||||||
|
"spam/spam3/spam3.nu",
|
||||||
|
r#"
|
||||||
|
export def foo [] { "foo" }
|
||||||
|
export alias bar = "bar"
|
||||||
|
"#,
|
||||||
|
)]);
|
||||||
|
|
||||||
|
let inp1 = &[r#"use main.nu"#, r#"main spam2 foo"#];
|
||||||
|
let inp2 = &[r#"use main.nu "spam2 spam3 bar""#, r#"spam2 spam3 bar"#];
|
||||||
|
|
||||||
|
let actual = nu!(cwd: dirs.test(), pipeline(&inp1.join("; ")));
|
||||||
|
assert_eq!(actual.out, "foo");
|
||||||
|
|
||||||
|
let actual = nu!(cwd: dirs.test(), pipeline(&inp2.join("; ")));
|
||||||
|
assert_eq!(actual.out, "bar");
|
||||||
|
})
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user