mirror of
https://github.com/nushell/nushell
synced 2024-12-27 05:23:11 +00:00
Allow creating modules from directories (#9066)
This commit is contained in:
parent
6dc7ff2335
commit
a2a346e39c
20 changed files with 1220 additions and 440 deletions
75
crates/nu-cmd-lang/src/core_commands/export_module.rs
Normal file
75
crates/nu-cmd-lang/src/core_commands/export_module.rs
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::{
|
||||||
|
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ExportModule;
|
||||||
|
|
||||||
|
impl Command for ExportModule {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"export module"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Export a custom module from a module."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
|
Signature::build("export module")
|
||||||
|
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||||
|
.allow_variants_without_examples(true)
|
||||||
|
.required("module", SyntaxShape::String, "module name or module path")
|
||||||
|
.optional(
|
||||||
|
"block",
|
||||||
|
SyntaxShape::Block,
|
||||||
|
"body of the module if 'module' parameter is not a path",
|
||||||
|
)
|
||||||
|
.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_nu.html"#
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_parser_keyword(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
_engine_state: &EngineState,
|
||||||
|
_stack: &mut Stack,
|
||||||
|
_call: &Call,
|
||||||
|
_input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
Ok(PipelineData::empty())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![Example {
|
||||||
|
description: "Define a custom command in a submodule of a module and call it",
|
||||||
|
example: r#"module spam {
|
||||||
|
export module eggs {
|
||||||
|
export def foo [] { "foo" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
use spam eggs
|
||||||
|
eggs foo"#,
|
||||||
|
result: Some(Value::test_string("foo")),
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(ExportModule {})
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,6 +14,7 @@ mod export_alias;
|
||||||
mod export_def;
|
mod export_def;
|
||||||
mod export_def_env;
|
mod export_def_env;
|
||||||
mod export_extern;
|
mod export_extern;
|
||||||
|
mod export_module;
|
||||||
mod export_use;
|
mod export_use;
|
||||||
mod extern_;
|
mod extern_;
|
||||||
mod for_;
|
mod for_;
|
||||||
|
@ -55,6 +56,7 @@ pub use export_alias::ExportAlias;
|
||||||
pub use export_def::ExportDef;
|
pub use export_def::ExportDef;
|
||||||
pub use export_def_env::ExportDefEnv;
|
pub use export_def_env::ExportDefEnv;
|
||||||
pub use export_extern::ExportExtern;
|
pub use export_extern::ExportExtern;
|
||||||
|
pub use export_module::ExportModule;
|
||||||
pub use export_use::ExportUse;
|
pub use export_use::ExportUse;
|
||||||
pub use extern_::Extern;
|
pub use extern_::Extern;
|
||||||
pub use for_::For;
|
pub use for_::For;
|
||||||
|
|
|
@ -19,8 +19,13 @@ impl Command for Module {
|
||||||
fn signature(&self) -> nu_protocol::Signature {
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
Signature::build("module")
|
Signature::build("module")
|
||||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||||
.required("module_name", SyntaxShape::String, "module name")
|
.allow_variants_without_examples(true)
|
||||||
.required("block", SyntaxShape::Block, "body of the module")
|
.required("module", SyntaxShape::String, "module name or module path")
|
||||||
|
.optional(
|
||||||
|
"block",
|
||||||
|
SyntaxShape::Block,
|
||||||
|
"body of the module if 'module' parameter is not a module path",
|
||||||
|
)
|
||||||
.category(Category::Core)
|
.category(Category::Core)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ impl Command for Use {
|
||||||
Signature::build("use")
|
Signature::build("use")
|
||||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||||
.required("module", SyntaxShape::String, "Module or module file")
|
.required("module", SyntaxShape::String, "Module or module file")
|
||||||
.optional(
|
.rest(
|
||||||
"members",
|
"members",
|
||||||
SyntaxShape::Any,
|
SyntaxShape::Any,
|
||||||
"Which members of the module to import",
|
"Which members of the module to import",
|
||||||
|
|
|
@ -33,6 +33,7 @@ pub fn create_default_context() -> EngineState {
|
||||||
ExportDefEnv,
|
ExportDefEnv,
|
||||||
ExportExtern,
|
ExportExtern,
|
||||||
ExportUse,
|
ExportUse,
|
||||||
|
ExportModule,
|
||||||
Extern,
|
Extern,
|
||||||
For,
|
For,
|
||||||
Help,
|
Help,
|
||||||
|
|
|
@ -13,11 +13,12 @@ mod test_examples {
|
||||||
check_example_evaluates_to_expected_output,
|
check_example_evaluates_to_expected_output,
|
||||||
check_example_input_and_output_types_match_command_signature,
|
check_example_input_and_output_types_match_command_signature,
|
||||||
};
|
};
|
||||||
use crate::{Break, Collect, Describe, Mut};
|
use crate::{
|
||||||
use crate::{Echo, If, Let};
|
Break, Collect, Def, Describe, Echo, ExportCommand, ExportDef, If, Let, Module, Mut, Use,
|
||||||
|
};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{Command, EngineState, StateWorkingSet},
|
engine::{Command, EngineState, StateWorkingSet},
|
||||||
Type,
|
Type, Value,
|
||||||
};
|
};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
@ -55,18 +56,28 @@ mod test_examples {
|
||||||
|
|
||||||
fn make_engine_state(cmd: Box<dyn Command>) -> Box<EngineState> {
|
fn make_engine_state(cmd: Box<dyn Command>) -> Box<EngineState> {
|
||||||
let mut engine_state = Box::new(EngineState::new());
|
let mut engine_state = Box::new(EngineState::new());
|
||||||
|
let cwd = std::env::current_dir()
|
||||||
|
.expect("Could not get current working directory.")
|
||||||
|
.to_string_lossy()
|
||||||
|
.to_string();
|
||||||
|
engine_state.add_env_var("PWD".to_string(), Value::test_string(cwd));
|
||||||
|
|
||||||
let delta = {
|
let delta = {
|
||||||
// Base functions that are needed for testing
|
// Base functions that are needed for testing
|
||||||
// Try to keep this working set small to keep tests running as fast as possible
|
// Try to keep this working set small to keep tests running as fast as possible
|
||||||
let mut working_set = StateWorkingSet::new(&engine_state);
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
||||||
working_set.add_decl(Box::new(Break));
|
working_set.add_decl(Box::new(Break));
|
||||||
|
working_set.add_decl(Box::new(Collect));
|
||||||
|
working_set.add_decl(Box::new(Def));
|
||||||
working_set.add_decl(Box::new(Describe));
|
working_set.add_decl(Box::new(Describe));
|
||||||
working_set.add_decl(Box::new(Echo));
|
working_set.add_decl(Box::new(Echo));
|
||||||
|
working_set.add_decl(Box::new(ExportCommand));
|
||||||
|
working_set.add_decl(Box::new(ExportDef));
|
||||||
working_set.add_decl(Box::new(If));
|
working_set.add_decl(Box::new(If));
|
||||||
working_set.add_decl(Box::new(Let));
|
working_set.add_decl(Box::new(Let));
|
||||||
|
working_set.add_decl(Box::new(Module));
|
||||||
working_set.add_decl(Box::new(Mut));
|
working_set.add_decl(Box::new(Mut));
|
||||||
working_set.add_decl(Box::new(Collect));
|
working_set.add_decl(Box::new(Use));
|
||||||
|
|
||||||
// Adding the command that is being tested to the working set
|
// Adding the command that is being tested to the working set
|
||||||
working_set.add_decl(cmd);
|
working_set.add_decl(cmd);
|
||||||
|
|
|
@ -6,10 +6,11 @@ use nu_protocol::{
|
||||||
ImportPatternMember, Pipeline, PipelineElement,
|
ImportPatternMember, Pipeline, PipelineElement,
|
||||||
},
|
},
|
||||||
engine::{StateWorkingSet, DEFAULT_OVERLAY_NAME},
|
engine::{StateWorkingSet, DEFAULT_OVERLAY_NAME},
|
||||||
span, Alias, BlockId, Exportable, Module, ParseError, PositionalArg, Span, Spanned,
|
span, Alias, BlockId, Exportable, Module, ModuleId, ParseError, PositionalArg,
|
||||||
SyntaxShape, Type, VarId,
|
ResolvedImportPattern, Span, Spanned, SyntaxShape, Type, VarId,
|
||||||
};
|
};
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
use std::ffi::OsStr;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
pub const LIB_DIRS_VAR: &str = "NU_LIB_DIRS";
|
pub const LIB_DIRS_VAR: &str = "NU_LIB_DIRS";
|
||||||
|
@ -408,6 +409,7 @@ pub fn parse_def(
|
||||||
working_set.error(ParseError::NamedAsModule(
|
working_set.error(ParseError::NamedAsModule(
|
||||||
"command".to_string(),
|
"command".to_string(),
|
||||||
name,
|
name,
|
||||||
|
"main".to_string(),
|
||||||
name_expr_span,
|
name_expr_span,
|
||||||
));
|
));
|
||||||
return Pipeline::from_vec(vec![Expression {
|
return Pipeline::from_vec(vec![Expression {
|
||||||
|
@ -530,6 +532,7 @@ pub fn parse_extern(
|
||||||
working_set.error(ParseError::NamedAsModule(
|
working_set.error(ParseError::NamedAsModule(
|
||||||
"known external".to_string(),
|
"known external".to_string(),
|
||||||
name.clone(),
|
name.clone(),
|
||||||
|
"main".to_string(),
|
||||||
name_expr_span,
|
name_expr_span,
|
||||||
));
|
));
|
||||||
return Pipeline::from_vec(vec![Expression {
|
return Pipeline::from_vec(vec![Expression {
|
||||||
|
@ -730,6 +733,7 @@ pub fn parse_alias(
|
||||||
working_set.error(ParseError::NamedAsModule(
|
working_set.error(ParseError::NamedAsModule(
|
||||||
"alias".to_string(),
|
"alias".to_string(),
|
||||||
alias_name,
|
alias_name,
|
||||||
|
"main".to_string(),
|
||||||
spans[split_id],
|
spans[split_id],
|
||||||
));
|
));
|
||||||
|
|
||||||
|
@ -861,7 +865,9 @@ pub fn parse_export_in_block(
|
||||||
let full_name = if lite_command.parts.len() > 1 {
|
let full_name = if lite_command.parts.len() > 1 {
|
||||||
let sub = working_set.get_span_contents(lite_command.parts[1]);
|
let sub = working_set.get_span_contents(lite_command.parts[1]);
|
||||||
match sub {
|
match sub {
|
||||||
b"alias" | b"def" | b"def-env" | b"extern" | b"use" => [b"export ", sub].concat(),
|
b"alias" | b"def" | b"def-env" | b"extern" | b"use" | b"module" => {
|
||||||
|
[b"export ", sub].concat()
|
||||||
|
}
|
||||||
_ => b"export".to_vec(),
|
_ => b"export".to_vec(),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -923,6 +929,7 @@ pub fn parse_export_in_block(
|
||||||
let (pipeline, _) = parse_use(working_set, &lite_command.parts);
|
let (pipeline, _) = parse_use(working_set, &lite_command.parts);
|
||||||
pipeline
|
pipeline
|
||||||
}
|
}
|
||||||
|
b"export module" => parse_module(working_set, lite_command, None),
|
||||||
b"export extern" => parse_extern(working_set, lite_command, None),
|
b"export extern" => parse_extern(working_set, lite_command, None),
|
||||||
_ => {
|
_ => {
|
||||||
working_set.error(ParseError::UnexpectedKeyword(
|
working_set.error(ParseError::UnexpectedKeyword(
|
||||||
|
@ -961,7 +968,9 @@ pub fn parse_export_in_module(
|
||||||
return (garbage_pipeline(spans), vec![]);
|
return (garbage_pipeline(spans), vec![]);
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some(export_decl_id) = working_set.find_decl(b"export", &Type::Any) else {
|
let export_decl_id = if let Some(id) = working_set.find_decl(b"export", &Type::Any) {
|
||||||
|
id
|
||||||
|
} else {
|
||||||
working_set.error(ParseError::InternalError(
|
working_set.error(ParseError::InternalError(
|
||||||
"missing export command".into(),
|
"missing export command".into(),
|
||||||
export_span,
|
export_span,
|
||||||
|
@ -1260,10 +1269,64 @@ pub fn parse_export_in_module(
|
||||||
|
|
||||||
exportables
|
exportables
|
||||||
}
|
}
|
||||||
|
b"module" => {
|
||||||
|
let pipeline = parse_module(working_set, lite_command, Some(module_name));
|
||||||
|
|
||||||
|
let export_module_decl_id =
|
||||||
|
if let Some(id) = working_set.find_decl(b"export module", &Type::Any) {
|
||||||
|
id
|
||||||
|
} else {
|
||||||
|
working_set.error(ParseError::InternalError(
|
||||||
|
"missing 'export module' command".into(),
|
||||||
|
export_span,
|
||||||
|
));
|
||||||
|
return (garbage_pipeline(spans), vec![]);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Trying to warp the 'module' call into the 'export module' in a very clumsy way
|
||||||
|
if let Some(PipelineElement::Expression(
|
||||||
|
_,
|
||||||
|
Expression {
|
||||||
|
expr: Expr::Call(ref module_call),
|
||||||
|
..
|
||||||
|
},
|
||||||
|
)) = pipeline.elements.get(0)
|
||||||
|
{
|
||||||
|
call = module_call.clone();
|
||||||
|
|
||||||
|
call.head = span(&spans[0..=1]);
|
||||||
|
call.decl_id = export_module_decl_id;
|
||||||
|
} else {
|
||||||
|
working_set.error(ParseError::InternalError(
|
||||||
|
"unexpected output from parsing a definition".into(),
|
||||||
|
span(&spans[1..]),
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut result = vec![];
|
||||||
|
|
||||||
|
if let Some(module_name_span) = spans.get(2) {
|
||||||
|
let module_name = working_set.get_span_contents(*module_name_span);
|
||||||
|
let module_name = trim_quotes(module_name);
|
||||||
|
|
||||||
|
if let Some(module_id) = working_set.find_module(module_name) {
|
||||||
|
result.push(Exportable::Module {
|
||||||
|
name: module_name.to_vec(),
|
||||||
|
id: module_id,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
working_set.error(ParseError::InternalError(
|
||||||
|
"failed to find added module".into(),
|
||||||
|
span(&spans[1..]),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
working_set.error(ParseError::Expected(
|
working_set.error(ParseError::Expected(
|
||||||
// TODO: Fill in more keywords as they come
|
"def, def-env, alias, use, module, or extern keyword".into(),
|
||||||
"def, def-env, alias, use, or extern keyword".into(),
|
|
||||||
spans[1],
|
spans[1],
|
||||||
));
|
));
|
||||||
|
|
||||||
|
@ -1272,9 +1335,9 @@ pub fn parse_export_in_module(
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
working_set.error(ParseError::MissingPositional(
|
working_set.error(ParseError::MissingPositional(
|
||||||
"def, def-env, alias, use, or extern keyword".into(), // TODO: keep filling more keywords as they come
|
"def, def-env, extern, alias, use, or module keyword".into(),
|
||||||
Span::new(export_span.end, export_span.end),
|
Span::new(export_span.end, export_span.end),
|
||||||
"`def`, `def-env`, `alias`, use, or `extern` keyword.".to_string(),
|
"def, def-env, extern, alias, use, or module keyword.".to_string(),
|
||||||
));
|
));
|
||||||
|
|
||||||
vec![]
|
vec![]
|
||||||
|
@ -1465,6 +1528,15 @@ pub fn parse_module_block(
|
||||||
|
|
||||||
block.pipelines.push(pipeline)
|
block.pipelines.push(pipeline)
|
||||||
}
|
}
|
||||||
|
b"module" => {
|
||||||
|
let pipeline = parse_module(
|
||||||
|
working_set,
|
||||||
|
command,
|
||||||
|
None, // using modules named as the module locally is OK
|
||||||
|
);
|
||||||
|
|
||||||
|
block.pipelines.push(pipeline)
|
||||||
|
}
|
||||||
b"export" => {
|
b"export" => {
|
||||||
let (pipe, exportables) =
|
let (pipe, exportables) =
|
||||||
parse_export_in_module(working_set, command, module_name);
|
parse_export_in_module(working_set, command, module_name);
|
||||||
|
@ -1473,11 +1545,96 @@ pub fn parse_module_block(
|
||||||
match exportable {
|
match exportable {
|
||||||
Exportable::Decl { name, id } => {
|
Exportable::Decl { name, id } => {
|
||||||
if &name == b"main" {
|
if &name == b"main" {
|
||||||
|
if module.main.is_some() {
|
||||||
|
let err_span = if !pipe.elements.is_empty() {
|
||||||
|
if let PipelineElement::Expression(
|
||||||
|
_,
|
||||||
|
Expression {
|
||||||
|
expr: Expr::Call(call),
|
||||||
|
..
|
||||||
|
},
|
||||||
|
) = &pipe.elements[0]
|
||||||
|
{
|
||||||
|
call.head
|
||||||
|
} else {
|
||||||
|
pipe.elements[0].span()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
span
|
||||||
|
};
|
||||||
|
working_set.error(ParseError::ModuleDoubleMain(
|
||||||
|
String::from_utf8_lossy(module_name)
|
||||||
|
.to_string(),
|
||||||
|
err_span,
|
||||||
|
));
|
||||||
|
} else {
|
||||||
module.main = Some(id);
|
module.main = Some(id);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
module.add_decl(name, id);
|
module.add_decl(name, id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Exportable::Module { name, id } => {
|
||||||
|
if &name == b"mod" {
|
||||||
|
let (
|
||||||
|
submodule_main,
|
||||||
|
submodule_decls,
|
||||||
|
submodule_submodules,
|
||||||
|
) = {
|
||||||
|
let submodule = working_set.get_module(id);
|
||||||
|
(
|
||||||
|
submodule.main,
|
||||||
|
submodule.decls(),
|
||||||
|
submodule.submodules(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add submodule's decls to the parent module
|
||||||
|
for (decl_name, decl_id) in submodule_decls {
|
||||||
|
module.add_decl(decl_name, decl_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add submodule's main command to the parent module
|
||||||
|
if let Some(main_decl_id) = submodule_main {
|
||||||
|
if module.main.is_some() {
|
||||||
|
let err_span = if !pipe.elements.is_empty() {
|
||||||
|
if let PipelineElement::Expression(
|
||||||
|
_,
|
||||||
|
Expression {
|
||||||
|
expr: Expr::Call(call),
|
||||||
|
..
|
||||||
|
},
|
||||||
|
) = &pipe.elements[0]
|
||||||
|
{
|
||||||
|
call.head
|
||||||
|
} else {
|
||||||
|
pipe.elements[0].span()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
span
|
||||||
|
};
|
||||||
|
working_set.error(
|
||||||
|
ParseError::ModuleDoubleMain(
|
||||||
|
String::from_utf8_lossy(module_name)
|
||||||
|
.to_string(),
|
||||||
|
err_span,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
module.main = Some(main_decl_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add submodule's submodules to the parent module
|
||||||
|
for (submodule_name, submodule_id) in
|
||||||
|
submodule_submodules
|
||||||
|
{
|
||||||
|
module.add_submodule(submodule_name, submodule_id);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
module.add_submodule(name, id);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1495,7 +1652,7 @@ pub fn parse_module_block(
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
working_set.error(ParseError::ExpectedKeyword(
|
working_set.error(ParseError::ExpectedKeyword(
|
||||||
"def or export keyword".into(),
|
"def, def-env, extern, alias, use, module, export or export-env keyword".into(),
|
||||||
command.parts[0],
|
command.parts[0],
|
||||||
));
|
));
|
||||||
|
|
||||||
|
@ -1521,23 +1678,312 @@ pub fn parse_module_block(
|
||||||
(block, module, module_comments)
|
(block, module, module_comments)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_module(working_set: &mut StateWorkingSet, lite_command: &LiteCommand) -> Pipeline {
|
fn parse_module_file(
|
||||||
|
working_set: &mut StateWorkingSet,
|
||||||
|
path: PathBuf,
|
||||||
|
path_span: Span,
|
||||||
|
name_override: Option<String>,
|
||||||
|
) -> Option<ModuleId> {
|
||||||
|
if let Some(i) = working_set
|
||||||
|
.parsed_module_files
|
||||||
|
.iter()
|
||||||
|
.rposition(|p| p == &path)
|
||||||
|
{
|
||||||
|
let mut files: Vec<String> = working_set
|
||||||
|
.parsed_module_files
|
||||||
|
.split_off(i)
|
||||||
|
.iter()
|
||||||
|
.map(|p| p.to_string_lossy().to_string())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
files.push(path.to_string_lossy().to_string());
|
||||||
|
|
||||||
|
let msg = files.join("\nuses ");
|
||||||
|
|
||||||
|
working_set.error(ParseError::CyclicalModuleImport(msg, path_span));
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let module_name = if let Some(name) = name_override {
|
||||||
|
name
|
||||||
|
} else if let Some(stem) = path.file_stem() {
|
||||||
|
stem.to_string_lossy().to_string()
|
||||||
|
} else {
|
||||||
|
working_set.error(ParseError::ModuleNotFound(path_span));
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
let contents = if let Ok(contents) = std::fs::read(&path) {
|
||||||
|
contents
|
||||||
|
} else {
|
||||||
|
working_set.error(ParseError::ModuleNotFound(path_span));
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
let file_id = working_set.add_file(path.to_string_lossy().to_string(), &contents);
|
||||||
|
let new_span = working_set.get_span_for_file(file_id);
|
||||||
|
|
||||||
|
// Change the currently parsed directory
|
||||||
|
let prev_currently_parsed_cwd = if let Some(parent) = path.parent() {
|
||||||
|
let prev = working_set.currently_parsed_cwd.clone();
|
||||||
|
|
||||||
|
working_set.currently_parsed_cwd = Some(parent.into());
|
||||||
|
|
||||||
|
prev
|
||||||
|
} else {
|
||||||
|
working_set.currently_parsed_cwd.clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add the file to the stack of parsed module files
|
||||||
|
working_set.parsed_module_files.push(path);
|
||||||
|
|
||||||
|
// Parse the module
|
||||||
|
let (block, module, module_comments) =
|
||||||
|
parse_module_block(working_set, new_span, module_name.as_bytes());
|
||||||
|
|
||||||
|
// Remove the file from the stack of parsed module files
|
||||||
|
working_set.parsed_module_files.pop();
|
||||||
|
|
||||||
|
// Restore the currently parsed directory back
|
||||||
|
working_set.currently_parsed_cwd = prev_currently_parsed_cwd;
|
||||||
|
|
||||||
|
let _ = working_set.add_block(block);
|
||||||
|
let module_id = working_set.add_module(&module_name, module, module_comments);
|
||||||
|
|
||||||
|
Some(module_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_module_file_or_dir(
|
||||||
|
working_set: &mut StateWorkingSet,
|
||||||
|
path: &[u8],
|
||||||
|
path_span: Span,
|
||||||
|
name_override: Option<String>,
|
||||||
|
) -> Option<ModuleId> {
|
||||||
|
let (module_path_str, err) = unescape_unquote_string(path, path_span);
|
||||||
|
if let Some(err) = err {
|
||||||
|
working_set.error(err);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let cwd = working_set.get_cwd();
|
||||||
|
|
||||||
|
let module_path =
|
||||||
|
if let Some(path) = find_in_dirs(&module_path_str, working_set, &cwd, LIB_DIRS_VAR) {
|
||||||
|
path
|
||||||
|
} else {
|
||||||
|
working_set.error(ParseError::ModuleNotFound(path_span));
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
if module_path.is_dir() {
|
||||||
|
if let Ok(dir_contents) = std::fs::read_dir(&module_path) {
|
||||||
|
let module_name = if let Some(stem) = module_path.file_stem() {
|
||||||
|
stem.to_string_lossy().to_string()
|
||||||
|
} else {
|
||||||
|
working_set.error(ParseError::ModuleNotFound(path_span));
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut file_paths = vec![];
|
||||||
|
|
||||||
|
for entry in dir_contents.flatten() {
|
||||||
|
let entry_path = entry.path();
|
||||||
|
|
||||||
|
if entry_path.is_file()
|
||||||
|
&& entry_path.extension() == Some(OsStr::new("nu"))
|
||||||
|
&& entry_path.file_stem() != Some(OsStr::new("mod"))
|
||||||
|
{
|
||||||
|
if entry_path.file_stem() == Some(OsStr::new(&module_name)) {
|
||||||
|
working_set.error(ParseError::InvalidModuleFileName(
|
||||||
|
module_path.to_string_lossy().to_string(),
|
||||||
|
module_name,
|
||||||
|
path_span,
|
||||||
|
));
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
file_paths.push(entry_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
file_paths.sort();
|
||||||
|
|
||||||
|
// working_set.enter_scope();
|
||||||
|
|
||||||
|
let mut submodules = vec![];
|
||||||
|
|
||||||
|
for file_path in file_paths {
|
||||||
|
if let Some(submodule_id) =
|
||||||
|
parse_module_file(working_set, file_path, path_span, None)
|
||||||
|
{
|
||||||
|
let submodule_name = working_set.get_module(submodule_id).name();
|
||||||
|
submodules.push((submodule_name, submodule_id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mod_nu_path = module_path.join("mod.nu");
|
||||||
|
|
||||||
|
if mod_nu_path.exists() && mod_nu_path.is_file() {
|
||||||
|
if let Some(module_id) = parse_module_file(
|
||||||
|
working_set,
|
||||||
|
mod_nu_path,
|
||||||
|
path_span,
|
||||||
|
name_override.or(Some(module_name)),
|
||||||
|
) {
|
||||||
|
let module = working_set.get_module_mut(module_id);
|
||||||
|
|
||||||
|
for (submodule_name, submodule_id) in submodules {
|
||||||
|
module.add_submodule(submodule_name, submodule_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(module_id)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let mut module = Module::new(module_name.as_bytes().to_vec());
|
||||||
|
|
||||||
|
for (submodule_name, submodule_id) in submodules {
|
||||||
|
module.add_submodule(submodule_name, submodule_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(working_set.add_module(&module_name, module, vec![]))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
working_set.error(ParseError::ModuleNotFound(path_span));
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else if module_path.is_file() {
|
||||||
|
parse_module_file(working_set, module_path, path_span, name_override)
|
||||||
|
} else {
|
||||||
|
working_set.error(ParseError::ModuleNotFound(path_span));
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_module(
|
||||||
|
working_set: &mut StateWorkingSet,
|
||||||
|
lite_command: &LiteCommand,
|
||||||
|
module_name: Option<&[u8]>,
|
||||||
|
) -> Pipeline {
|
||||||
// TODO: Currently, module is closing over its parent scope (i.e., defs in the parent scope are
|
// TODO: Currently, module is closing over its parent scope (i.e., defs in the parent scope are
|
||||||
// visible and usable in this module's scope). We want to disable that for files.
|
// visible and usable in this module's scope). We want to disable that for files.
|
||||||
|
|
||||||
let spans = &lite_command.parts;
|
let spans = &lite_command.parts;
|
||||||
let mut module_comments = lite_command.comments.clone();
|
let mut module_comments = lite_command.comments.clone();
|
||||||
|
|
||||||
let bytes = working_set.get_span_contents(spans[0]);
|
let split_id = if spans.len() > 1 && working_set.get_span_contents(spans[0]) == b"export" {
|
||||||
|
2
|
||||||
|
} else {
|
||||||
|
1
|
||||||
|
};
|
||||||
|
|
||||||
if bytes == b"module" && spans.len() >= 3 {
|
let (call, call_span) = match working_set.find_decl(b"module", &Type::Any) {
|
||||||
let module_name_expr = parse_string(working_set, spans[1]);
|
Some(decl_id) => {
|
||||||
|
let (command_spans, rest_spans) = spans.split_at(split_id);
|
||||||
|
|
||||||
let module_name = module_name_expr
|
let ParsedInternalCall { call, output } =
|
||||||
.as_string()
|
parse_internal_call(working_set, span(command_spans), rest_spans, decl_id);
|
||||||
.expect("internal error: module name is not a string");
|
let decl = working_set.get_decl(decl_id);
|
||||||
|
|
||||||
let block_span = spans[2];
|
let call_span = span(spans);
|
||||||
|
|
||||||
|
let starting_error_count = working_set.parse_errors.len();
|
||||||
|
check_call(working_set, call_span, &decl.signature(), &call);
|
||||||
|
if starting_error_count != working_set.parse_errors.len() || call.has_flag("help") {
|
||||||
|
return Pipeline::from_vec(vec![Expression {
|
||||||
|
expr: Expr::Call(call),
|
||||||
|
span: call_span,
|
||||||
|
ty: output,
|
||||||
|
custom_completion: None,
|
||||||
|
}]);
|
||||||
|
}
|
||||||
|
|
||||||
|
(call, call_span)
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
working_set.error(ParseError::UnknownState(
|
||||||
|
"internal error: 'module' or 'export module' declaration not found".into(),
|
||||||
|
span(spans),
|
||||||
|
));
|
||||||
|
return garbage_pipeline(spans);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let (module_name_or_path, module_name_or_path_span, module_name_or_path_expr) =
|
||||||
|
if let Some(name) = call.positional_nth(0) {
|
||||||
|
if let Some(s) = name.as_string() {
|
||||||
|
if let Some(mod_name) = module_name {
|
||||||
|
if s.as_bytes() == mod_name {
|
||||||
|
working_set.error(ParseError::NamedAsModule(
|
||||||
|
"module".to_string(),
|
||||||
|
s,
|
||||||
|
"mod".to_string(),
|
||||||
|
name.span,
|
||||||
|
));
|
||||||
|
return Pipeline::from_vec(vec![Expression {
|
||||||
|
expr: Expr::Call(call),
|
||||||
|
span: call_span,
|
||||||
|
ty: Type::Any,
|
||||||
|
custom_completion: None,
|
||||||
|
}]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(s, name.span, name.clone())
|
||||||
|
} else {
|
||||||
|
working_set.error(ParseError::UnknownState(
|
||||||
|
"internal error: name not a string".into(),
|
||||||
|
span(spans),
|
||||||
|
));
|
||||||
|
return garbage_pipeline(spans);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
working_set.error(ParseError::UnknownState(
|
||||||
|
"internal error: missing positional".into(),
|
||||||
|
span(spans),
|
||||||
|
));
|
||||||
|
return garbage_pipeline(spans);
|
||||||
|
};
|
||||||
|
|
||||||
|
let pipeline = Pipeline::from_vec(vec![Expression {
|
||||||
|
expr: Expr::Call(call),
|
||||||
|
span: call_span,
|
||||||
|
ty: Type::Any,
|
||||||
|
custom_completion: None,
|
||||||
|
}]);
|
||||||
|
|
||||||
|
if spans.len() == split_id + 1 {
|
||||||
|
let cwd = working_set.get_cwd();
|
||||||
|
|
||||||
|
if let Some(module_path) =
|
||||||
|
find_in_dirs(&module_name_or_path, working_set, &cwd, LIB_DIRS_VAR)
|
||||||
|
{
|
||||||
|
let path_str = module_path.to_string_lossy().to_string();
|
||||||
|
let _ = parse_module_file_or_dir(
|
||||||
|
working_set,
|
||||||
|
path_str.as_bytes(),
|
||||||
|
module_name_or_path_span,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
return pipeline;
|
||||||
|
} else {
|
||||||
|
working_set.error(ParseError::ModuleNotFound(module_name_or_path_span));
|
||||||
|
return pipeline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if spans.len() < split_id + 2 {
|
||||||
|
working_set.error(ParseError::UnknownState(
|
||||||
|
"Expected structure: module <name> or module <name> <block>".into(),
|
||||||
|
span(spans),
|
||||||
|
));
|
||||||
|
|
||||||
|
return garbage_pipeline(spans);
|
||||||
|
}
|
||||||
|
|
||||||
|
let module_name = module_name_or_path;
|
||||||
|
|
||||||
|
let block_span = spans[split_id + 1];
|
||||||
let block_bytes = working_set.get_span_contents(block_span);
|
let block_bytes = working_set.get_span_contents(block_span);
|
||||||
let mut start = block_span.start;
|
let mut start = block_span.start;
|
||||||
let mut end = block_span.end;
|
let mut end = block_span.end;
|
||||||
|
@ -1577,10 +2023,10 @@ pub fn parse_module(working_set: &mut StateWorkingSet, lite_command: &LiteComman
|
||||||
.expect("internal error: missing module command");
|
.expect("internal error: missing module command");
|
||||||
|
|
||||||
let call = Box::new(Call {
|
let call = Box::new(Call {
|
||||||
head: spans[0],
|
head: span(&spans[..split_id]),
|
||||||
decl_id: module_decl_id,
|
decl_id: module_decl_id,
|
||||||
arguments: vec![
|
arguments: vec![
|
||||||
Argument::Positional(module_name_expr),
|
Argument::Positional(module_name_or_path_expr),
|
||||||
Argument::Positional(block_expr),
|
Argument::Positional(block_expr),
|
||||||
],
|
],
|
||||||
redirect_stdout: true,
|
redirect_stdout: true,
|
||||||
|
@ -1594,14 +2040,6 @@ pub fn parse_module(working_set: &mut StateWorkingSet, lite_command: &LiteComman
|
||||||
ty: Type::Any,
|
ty: Type::Any,
|
||||||
custom_completion: None,
|
custom_completion: None,
|
||||||
}])
|
}])
|
||||||
} else {
|
|
||||||
working_set.error(ParseError::UnknownState(
|
|
||||||
"Expected structure: module <name> {}".into(),
|
|
||||||
span(spans),
|
|
||||||
));
|
|
||||||
|
|
||||||
garbage_pipeline(spans)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_use(working_set: &mut StateWorkingSet, spans: &[Span]) -> (Pipeline, Vec<Exportable>) {
|
pub fn parse_use(working_set: &mut StateWorkingSet, spans: &[Span]) -> (Pipeline, Vec<Exportable>) {
|
||||||
|
@ -1680,110 +2118,12 @@ pub fn parse_use(working_set: &mut StateWorkingSet, spans: &[Span]) -> (Pipeline
|
||||||
return (garbage_pipeline(spans), vec![]);
|
return (garbage_pipeline(spans), vec![]);
|
||||||
};
|
};
|
||||||
|
|
||||||
let cwd = working_set.get_cwd();
|
let (import_pattern, module, module_id) = if let Some(module_id) = import_pattern.head.id {
|
||||||
|
let module = working_set.get_module(module_id).clone();
|
||||||
// TODO: Add checking for importing too long import patterns, e.g.:
|
|
||||||
// > use spam foo non existent names here do not throw error
|
|
||||||
let (import_pattern, module) = if let Some(module_id) = import_pattern.head.id {
|
|
||||||
(import_pattern, working_set.get_module(module_id).clone())
|
|
||||||
} else {
|
|
||||||
// It could be a file
|
|
||||||
// TODO: Do not close over when loading module from file?
|
|
||||||
|
|
||||||
let starting_error_count = working_set.parse_errors.len();
|
|
||||||
let (module_filename, err) =
|
|
||||||
unescape_unquote_string(&import_pattern.head.name, import_pattern.head.span);
|
|
||||||
if let Some(err) = err {
|
|
||||||
working_set.error(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
if starting_error_count == working_set.parse_errors.len() {
|
|
||||||
if let Some(module_path) =
|
|
||||||
find_in_dirs(&module_filename, working_set, &cwd, LIB_DIRS_VAR)
|
|
||||||
{
|
|
||||||
if let Some(i) = working_set
|
|
||||||
.parsed_module_files
|
|
||||||
.iter()
|
|
||||||
.rposition(|p| p == &module_path)
|
|
||||||
{
|
|
||||||
let mut files: Vec<String> = working_set
|
|
||||||
.parsed_module_files
|
|
||||||
.split_off(i)
|
|
||||||
.iter()
|
|
||||||
.map(|p| p.to_string_lossy().to_string())
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
files.push(module_path.to_string_lossy().to_string());
|
|
||||||
|
|
||||||
let msg = files.join("\nuses ");
|
|
||||||
|
|
||||||
working_set.error(ParseError::CyclicalModuleImport(
|
|
||||||
msg,
|
|
||||||
import_pattern.head.span,
|
|
||||||
));
|
|
||||||
return (
|
|
||||||
Pipeline::from_vec(vec![Expression {
|
|
||||||
expr: Expr::Call(call),
|
|
||||||
span: call_span,
|
|
||||||
ty: Type::Any,
|
|
||||||
custom_completion: None,
|
|
||||||
}]),
|
|
||||||
vec![],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let module_name = if let Some(stem) = module_path.file_stem() {
|
|
||||||
stem.to_string_lossy().to_string()
|
|
||||||
} else {
|
|
||||||
working_set.error(ParseError::ModuleNotFound(import_pattern.head.span));
|
|
||||||
return (
|
|
||||||
Pipeline::from_vec(vec![Expression {
|
|
||||||
expr: Expr::Call(call),
|
|
||||||
span: call_span,
|
|
||||||
ty: Type::Any,
|
|
||||||
custom_completion: None,
|
|
||||||
}]),
|
|
||||||
vec![],
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Ok(contents) = std::fs::read(&module_path) {
|
|
||||||
let file_id =
|
|
||||||
working_set.add_file(module_path.to_string_lossy().to_string(), &contents);
|
|
||||||
let new_span = working_set.get_span_for_file(file_id);
|
|
||||||
|
|
||||||
// Change the currently parsed directory
|
|
||||||
let prev_currently_parsed_cwd = if let Some(parent) = module_path.parent() {
|
|
||||||
let prev = working_set.currently_parsed_cwd.clone();
|
|
||||||
|
|
||||||
working_set.currently_parsed_cwd = Some(parent.into());
|
|
||||||
|
|
||||||
prev
|
|
||||||
} else {
|
|
||||||
working_set.currently_parsed_cwd.clone()
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add the file to the stack of parsed module files
|
|
||||||
working_set.parsed_module_files.push(module_path);
|
|
||||||
|
|
||||||
// Parse the module
|
|
||||||
let (block, module, module_comments) =
|
|
||||||
parse_module_block(working_set, new_span, module_name.as_bytes());
|
|
||||||
|
|
||||||
// Remove the file from the stack of parsed module files
|
|
||||||
working_set.parsed_module_files.pop();
|
|
||||||
|
|
||||||
// Restore the currently parsed directory back
|
|
||||||
working_set.currently_parsed_cwd = prev_currently_parsed_cwd;
|
|
||||||
|
|
||||||
let _ = working_set.add_block(block);
|
|
||||||
let module_id =
|
|
||||||
working_set.add_module(&module_name, module.clone(), module_comments);
|
|
||||||
|
|
||||||
(
|
(
|
||||||
ImportPattern {
|
ImportPattern {
|
||||||
head: ImportPatternHead {
|
head: ImportPatternHead {
|
||||||
name: module_name.into(),
|
name: module.name.clone(),
|
||||||
id: Some(module_id),
|
id: Some(module_id),
|
||||||
span: import_pattern.head.span,
|
span: import_pattern.head.span,
|
||||||
},
|
},
|
||||||
|
@ -1791,6 +2131,27 @@ pub fn parse_use(working_set: &mut StateWorkingSet, spans: &[Span]) -> (Pipeline
|
||||||
hidden: HashSet::new(),
|
hidden: HashSet::new(),
|
||||||
},
|
},
|
||||||
module,
|
module,
|
||||||
|
module_id,
|
||||||
|
)
|
||||||
|
} else if let Some(module_id) = parse_module_file_or_dir(
|
||||||
|
working_set,
|
||||||
|
&import_pattern.head.name,
|
||||||
|
import_pattern.head.span,
|
||||||
|
None,
|
||||||
|
) {
|
||||||
|
let module = working_set.get_module(module_id).clone();
|
||||||
|
(
|
||||||
|
ImportPattern {
|
||||||
|
head: ImportPatternHead {
|
||||||
|
name: module.name.clone(),
|
||||||
|
id: Some(module_id),
|
||||||
|
span: import_pattern.head.span,
|
||||||
|
},
|
||||||
|
members: import_pattern.members,
|
||||||
|
hidden: HashSet::new(),
|
||||||
|
},
|
||||||
|
module,
|
||||||
|
module_id,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
working_set.error(ParseError::ModuleNotFound(import_pattern.head.span));
|
working_set.error(ParseError::ModuleNotFound(import_pattern.head.span));
|
||||||
|
@ -1803,82 +2164,35 @@ pub fn parse_use(working_set: &mut StateWorkingSet, spans: &[Span]) -> (Pipeline
|
||||||
}]),
|
}]),
|
||||||
vec![],
|
vec![],
|
||||||
);
|
);
|
||||||
}
|
|
||||||
} else {
|
|
||||||
working_set.error(ParseError::ModuleNotFound(import_pattern.head.span));
|
|
||||||
return (
|
|
||||||
Pipeline::from_vec(vec![Expression {
|
|
||||||
expr: Expr::Call(call),
|
|
||||||
span: span(spans),
|
|
||||||
ty: Type::Any,
|
|
||||||
custom_completion: None,
|
|
||||||
}]),
|
|
||||||
vec![],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
working_set.error(ParseError::NonUtf8(import_pattern.head.span));
|
|
||||||
return (garbage_pipeline(spans), vec![]);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let decls_to_use = if import_pattern.members.is_empty() {
|
let (definitions, errors) =
|
||||||
module.decls_with_head(&import_pattern.head.name)
|
module.resolve_import_pattern(working_set, module_id, &import_pattern.members, None);
|
||||||
} else {
|
working_set.parse_errors.extend(errors);
|
||||||
match &import_pattern.members[0] {
|
|
||||||
ImportPatternMember::Glob { .. } => module.decls(),
|
|
||||||
ImportPatternMember::Name { name, span } => {
|
|
||||||
let mut decl_output = vec![];
|
|
||||||
|
|
||||||
if name == b"main" {
|
let exportables = definitions
|
||||||
if let Some(id) = &module.main {
|
.decls
|
||||||
decl_output.push((import_pattern.head.name.clone(), *id));
|
|
||||||
} else {
|
|
||||||
working_set.error(ParseError::ExportNotFound(*span));
|
|
||||||
}
|
|
||||||
} else if let Some(id) = module.get_decl_id(name) {
|
|
||||||
decl_output.push((name.clone(), id));
|
|
||||||
} else {
|
|
||||||
working_set.error(ParseError::ExportNotFound(*span));
|
|
||||||
}
|
|
||||||
|
|
||||||
decl_output
|
|
||||||
}
|
|
||||||
ImportPatternMember::List { names } => {
|
|
||||||
let mut decl_output = vec![];
|
|
||||||
|
|
||||||
for (name, span) in names {
|
|
||||||
if name == b"main" {
|
|
||||||
if let Some(id) = &module.main {
|
|
||||||
decl_output.push((import_pattern.head.name.clone(), *id));
|
|
||||||
} else {
|
|
||||||
working_set.error(ParseError::ExportNotFound(*span));
|
|
||||||
}
|
|
||||||
} else if let Some(id) = module.get_decl_id(name) {
|
|
||||||
decl_output.push((name.clone(), id));
|
|
||||||
} else {
|
|
||||||
working_set.error(ParseError::ExportNotFound(*span));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
decl_output
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let exportables = decls_to_use
|
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(name, decl_id)| Exportable::Decl {
|
.map(|(name, decl_id)| Exportable::Decl {
|
||||||
name: name.clone(),
|
name: name.clone(),
|
||||||
id: *decl_id,
|
id: *decl_id,
|
||||||
})
|
})
|
||||||
|
.chain(
|
||||||
|
definitions
|
||||||
|
.modules
|
||||||
|
.iter()
|
||||||
|
.map(|(name, module_id)| Exportable::Module {
|
||||||
|
name: name.clone(),
|
||||||
|
id: *module_id,
|
||||||
|
}),
|
||||||
|
)
|
||||||
.collect();
|
.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(definitions.decls);
|
||||||
|
working_set.use_modules(definitions.modules);
|
||||||
|
|
||||||
// Create a new Use command call to pass the new import pattern
|
// Create a new Use command call to pass the import pattern as parser info
|
||||||
let import_pattern_expr = Expression {
|
let import_pattern_expr = Expression {
|
||||||
expr: Expr::ImportPattern(import_pattern),
|
expr: Expr::ImportPattern(import_pattern),
|
||||||
span: span(args_spans),
|
span: span(args_spans),
|
||||||
|
@ -2112,7 +2426,13 @@ pub fn parse_overlay_new(working_set: &mut StateWorkingSet, call: Box<Call>) ->
|
||||||
vec![],
|
vec![],
|
||||||
);
|
);
|
||||||
|
|
||||||
working_set.add_overlay(overlay_name.as_bytes().to_vec(), module_id, vec![], false);
|
working_set.add_overlay(
|
||||||
|
overlay_name.as_bytes().to_vec(),
|
||||||
|
module_id,
|
||||||
|
vec![],
|
||||||
|
vec![],
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
pipeline
|
pipeline
|
||||||
}
|
}
|
||||||
|
@ -2181,8 +2501,6 @@ pub fn parse_overlay_use(working_set: &mut StateWorkingSet, call: Box<Call>) ->
|
||||||
custom_completion: None,
|
custom_completion: None,
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
let cwd = working_set.get_cwd();
|
|
||||||
|
|
||||||
let (final_overlay_name, origin_module, origin_module_id, is_module_updated) =
|
let (final_overlay_name, origin_module, origin_module_id, is_module_updated) =
|
||||||
if let Some(overlay_frame) = working_set.find_overlay(overlay_name.as_bytes()) {
|
if let Some(overlay_frame) = working_set.find_overlay(overlay_name.as_bytes()) {
|
||||||
// Activate existing overlay
|
// Activate existing overlay
|
||||||
|
@ -2243,7 +2561,7 @@ pub fn parse_overlay_use(working_set: &mut StateWorkingSet, call: Box<Call>) ->
|
||||||
(overlay_name, Module::new(module_name), module_id, true)
|
(overlay_name, Module::new(module_name), module_id, true)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Create a new overlay from a module
|
// Create a new overlay
|
||||||
if let Some(module_id) =
|
if let Some(module_id) =
|
||||||
// the name is a module
|
// the name is a module
|
||||||
working_set.find_module(overlay_name.as_bytes())
|
working_set.find_module(overlay_name.as_bytes())
|
||||||
|
@ -2254,89 +2572,61 @@ pub fn parse_overlay_use(working_set: &mut StateWorkingSet, call: Box<Call>) ->
|
||||||
module_id,
|
module_id,
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
} else {
|
} else if let Some(module_id) = parse_module_file_or_dir(
|
||||||
// try if the name is a file
|
working_set,
|
||||||
if let Ok(module_filename) =
|
overlay_name.as_bytes(),
|
||||||
String::from_utf8(trim_quotes(overlay_name.as_bytes()).to_vec())
|
overlay_name_span,
|
||||||
{
|
new_name.as_ref().map(|spanned| spanned.item.clone()),
|
||||||
if let Some(module_path) =
|
) {
|
||||||
find_in_dirs(&module_filename, working_set, &cwd, LIB_DIRS_VAR)
|
// try file or directory
|
||||||
{
|
let new_module = working_set.get_module(module_id).clone();
|
||||||
let overlay_name = if let Some(stem) = module_path.file_stem() {
|
|
||||||
stem.to_string_lossy().to_string()
|
|
||||||
} else {
|
|
||||||
working_set
|
|
||||||
.error(ParseError::ModuleOrOverlayNotFound(overlay_name_span));
|
|
||||||
return pipeline;
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Ok(contents) = std::fs::read(&module_path) {
|
|
||||||
let file_id = working_set.add_file(module_filename, &contents);
|
|
||||||
let new_span = working_set.get_span_for_file(file_id);
|
|
||||||
|
|
||||||
// Change currently parsed directory
|
|
||||||
let prev_currently_parsed_cwd =
|
|
||||||
if let Some(parent) = module_path.parent() {
|
|
||||||
let prev = working_set.currently_parsed_cwd.clone();
|
|
||||||
|
|
||||||
working_set.currently_parsed_cwd = Some(parent.into());
|
|
||||||
|
|
||||||
prev
|
|
||||||
} else {
|
|
||||||
working_set.currently_parsed_cwd.clone()
|
|
||||||
};
|
|
||||||
|
|
||||||
let (block, module, module_comments) =
|
|
||||||
parse_module_block(working_set, new_span, overlay_name.as_bytes());
|
|
||||||
|
|
||||||
// Restore the currently parsed directory back
|
|
||||||
working_set.currently_parsed_cwd = prev_currently_parsed_cwd;
|
|
||||||
|
|
||||||
let _ = working_set.add_block(block);
|
|
||||||
let module_id = working_set.add_module(
|
|
||||||
&overlay_name,
|
|
||||||
module.clone(),
|
|
||||||
module_comments,
|
|
||||||
);
|
|
||||||
|
|
||||||
(
|
(
|
||||||
new_name.map(|spanned| spanned.item).unwrap_or(overlay_name),
|
new_name
|
||||||
module,
|
.map(|spanned| spanned.item)
|
||||||
|
.unwrap_or(String::from_utf8_lossy(&new_module.name).to_string()),
|
||||||
|
new_module,
|
||||||
module_id,
|
module_id,
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
} else {
|
|
||||||
working_set
|
|
||||||
.error(ParseError::ModuleOrOverlayNotFound(overlay_name_span));
|
|
||||||
return pipeline;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
working_set.error(ParseError::ModuleOrOverlayNotFound(overlay_name_span));
|
working_set.error(ParseError::ModuleOrOverlayNotFound(overlay_name_span));
|
||||||
return pipeline;
|
return pipeline;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
working_set.error(ParseError::NonUtf8(overlay_name_span));
|
|
||||||
return garbage_pipeline(&[call_span]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let decls_to_lay = if is_module_updated {
|
let (definitions, errors) = if is_module_updated {
|
||||||
if has_prefix {
|
if has_prefix {
|
||||||
origin_module.decls_with_head(final_overlay_name.as_bytes())
|
origin_module.resolve_import_pattern(
|
||||||
|
working_set,
|
||||||
|
origin_module_id,
|
||||||
|
&[],
|
||||||
|
Some(final_overlay_name.as_bytes()),
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
origin_module.decls()
|
origin_module.resolve_import_pattern(
|
||||||
|
working_set,
|
||||||
|
origin_module_id,
|
||||||
|
&[ImportPatternMember::Glob {
|
||||||
|
span: overlay_name_span,
|
||||||
|
}],
|
||||||
|
Some(final_overlay_name.as_bytes()),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
vec![]
|
(ResolvedImportPattern::new(vec![], vec![]), vec![])
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if errors.is_empty() {
|
||||||
working_set.add_overlay(
|
working_set.add_overlay(
|
||||||
final_overlay_name.as_bytes().to_vec(),
|
final_overlay_name.as_bytes().to_vec(),
|
||||||
origin_module_id,
|
origin_module_id,
|
||||||
decls_to_lay,
|
definitions.decls,
|
||||||
|
definitions.modules,
|
||||||
has_prefix,
|
has_prefix,
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
working_set.parse_errors.extend(errors);
|
||||||
|
}
|
||||||
|
|
||||||
// Change the call argument to include the Overlay expression with the module ID
|
// Change the call argument to include the Overlay expression with the module ID
|
||||||
let mut call = call;
|
let mut call = call;
|
||||||
|
|
|
@ -3009,7 +3009,10 @@ pub fn parse_type(_working_set: &StateWorkingSet, bytes: &[u8]) -> Type {
|
||||||
|
|
||||||
pub fn parse_import_pattern(working_set: &mut StateWorkingSet, spans: &[Span]) -> Expression {
|
pub fn parse_import_pattern(working_set: &mut StateWorkingSet, spans: &[Span]) -> Expression {
|
||||||
let Some(head_span) = spans.get(0) else {
|
let Some(head_span) = spans.get(0) else {
|
||||||
working_set.error(ParseError::WrongImportPattern(span(spans)));
|
working_set.error(ParseError::WrongImportPattern(
|
||||||
|
"needs at least one component of import pattern".to_string(),
|
||||||
|
span(spans),
|
||||||
|
));
|
||||||
return garbage(span(spans));
|
return garbage(span(spans));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -3029,98 +3032,87 @@ pub fn parse_import_pattern(working_set: &mut StateWorkingSet, spans: &[Span]) -
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let (import_pattern, err) = if let Some(tail_span) = spans.get(1) {
|
let mut import_pattern = ImportPattern {
|
||||||
// FIXME: expand this to handle deeper imports once we support module imports
|
|
||||||
let tail = working_set.get_span_contents(*tail_span);
|
|
||||||
if tail == b"*" {
|
|
||||||
(
|
|
||||||
ImportPattern {
|
|
||||||
head: ImportPatternHead {
|
head: ImportPatternHead {
|
||||||
name: head_name,
|
name: head_name,
|
||||||
id: maybe_module_id,
|
id: maybe_module_id,
|
||||||
span: *head_span,
|
span: *head_span,
|
||||||
},
|
},
|
||||||
members: vec![ImportPatternMember::Glob { span: *tail_span }],
|
members: vec![],
|
||||||
hidden: HashSet::new(),
|
hidden: HashSet::new(),
|
||||||
},
|
};
|
||||||
None,
|
|
||||||
)
|
if spans.len() > 1 {
|
||||||
|
let mut leaf_member_span = None;
|
||||||
|
|
||||||
|
for tail_span in spans[1..].iter() {
|
||||||
|
if let Some(prev_span) = leaf_member_span {
|
||||||
|
let what = if working_set.get_span_contents(prev_span) == b"*" {
|
||||||
|
"glob"
|
||||||
|
} else {
|
||||||
|
"list"
|
||||||
|
};
|
||||||
|
working_set.error(ParseError::WrongImportPattern(
|
||||||
|
format!(
|
||||||
|
"{} member can be only at the end of an import pattern",
|
||||||
|
what
|
||||||
|
),
|
||||||
|
prev_span,
|
||||||
|
));
|
||||||
|
return Expression {
|
||||||
|
expr: Expr::ImportPattern(import_pattern),
|
||||||
|
span: prev_span,
|
||||||
|
ty: Type::List(Box::new(Type::String)),
|
||||||
|
custom_completion: None,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let tail = working_set.get_span_contents(*tail_span);
|
||||||
|
|
||||||
|
if tail == b"*" {
|
||||||
|
import_pattern
|
||||||
|
.members
|
||||||
|
.push(ImportPatternMember::Glob { span: *tail_span });
|
||||||
|
|
||||||
|
leaf_member_span = Some(*tail_span);
|
||||||
} else if tail.starts_with(b"[") {
|
} else if tail.starts_with(b"[") {
|
||||||
let result = parse_list_expression(working_set, *tail_span, &SyntaxShape::String);
|
let result = parse_list_expression(working_set, *tail_span, &SyntaxShape::String);
|
||||||
|
|
||||||
let mut output = vec![];
|
let mut output = vec![];
|
||||||
|
|
||||||
match result {
|
if let Expression {
|
||||||
Expression {
|
|
||||||
expr: Expr::List(list),
|
expr: Expr::List(list),
|
||||||
..
|
..
|
||||||
} => {
|
} = result
|
||||||
|
{
|
||||||
for expr in list {
|
for expr in list {
|
||||||
let contents = working_set.get_span_contents(expr.span);
|
let contents = working_set.get_span_contents(expr.span);
|
||||||
output.push((trim_quotes(contents).to_vec(), expr.span));
|
output.push((trim_quotes(contents).to_vec(), expr.span));
|
||||||
}
|
}
|
||||||
|
|
||||||
(
|
import_pattern
|
||||||
ImportPattern {
|
.members
|
||||||
head: ImportPatternHead {
|
.push(ImportPatternMember::List { names: output });
|
||||||
name: head_name,
|
} else {
|
||||||
id: maybe_module_id,
|
working_set.error(ParseError::ExportNotFound(result.span));
|
||||||
span: *head_span,
|
return Expression {
|
||||||
},
|
expr: Expr::ImportPattern(import_pattern),
|
||||||
members: vec![ImportPatternMember::List { names: output }],
|
span: span(spans),
|
||||||
hidden: HashSet::new(),
|
ty: Type::List(Box::new(Type::String)),
|
||||||
},
|
custom_completion: None,
|
||||||
None,
|
};
|
||||||
)
|
|
||||||
}
|
|
||||||
_ => (
|
|
||||||
ImportPattern {
|
|
||||||
head: ImportPatternHead {
|
|
||||||
name: head_name,
|
|
||||||
id: maybe_module_id,
|
|
||||||
span: *head_span,
|
|
||||||
},
|
|
||||||
members: vec![],
|
|
||||||
hidden: HashSet::new(),
|
|
||||||
},
|
|
||||||
Some(ParseError::ExportNotFound(result.span)),
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
leaf_member_span = Some(*tail_span);
|
||||||
} else {
|
} else {
|
||||||
let tail = trim_quotes(tail);
|
let tail = trim_quotes(tail);
|
||||||
(
|
|
||||||
ImportPattern {
|
import_pattern.members.push(ImportPatternMember::Name {
|
||||||
head: ImportPatternHead {
|
|
||||||
name: head_name,
|
|
||||||
id: maybe_module_id,
|
|
||||||
span: *head_span,
|
|
||||||
},
|
|
||||||
members: vec![ImportPatternMember::Name {
|
|
||||||
name: tail.to_vec(),
|
name: tail.to_vec(),
|
||||||
span: *tail_span,
|
span: *tail_span,
|
||||||
}],
|
});
|
||||||
hidden: HashSet::new(),
|
}
|
||||||
},
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
(
|
|
||||||
ImportPattern {
|
|
||||||
head: ImportPatternHead {
|
|
||||||
name: head_name,
|
|
||||||
id: maybe_module_id,
|
|
||||||
span: *head_span,
|
|
||||||
},
|
|
||||||
members: vec![],
|
|
||||||
hidden: HashSet::new(),
|
|
||||||
},
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(err) = err {
|
|
||||||
working_set.error(err);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Expression {
|
Expression {
|
||||||
|
@ -5206,7 +5198,7 @@ pub fn parse_builtin_commands(
|
||||||
Pipeline::from_vec(vec![expr])
|
Pipeline::from_vec(vec![expr])
|
||||||
}
|
}
|
||||||
b"alias" => parse_alias(working_set, lite_command, None),
|
b"alias" => parse_alias(working_set, lite_command, None),
|
||||||
b"module" => parse_module(working_set, lite_command),
|
b"module" => parse_module(working_set, lite_command, None),
|
||||||
b"use" => {
|
b"use" => {
|
||||||
let (pipeline, _) = parse_use(working_set, &lite_command.parts);
|
let (pipeline, _) = parse_use(working_set, &lite_command.parts);
|
||||||
pipeline
|
pipeline
|
||||||
|
|
|
@ -1202,6 +1202,15 @@ impl<'a> StateWorkingSet<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn use_modules(&mut self, modules: Vec<(Vec<u8>, ModuleId)>) {
|
||||||
|
let overlay_frame = self.last_overlay_mut();
|
||||||
|
|
||||||
|
for (name, module_id) in modules {
|
||||||
|
overlay_frame.insert_module(name, module_id);
|
||||||
|
// overlay_frame.visibility.use_module_id(&module_id); // TODO: Add hiding modules
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn add_predecl(&mut self, decl: Box<dyn Command>) -> Option<DeclId> {
|
pub fn add_predecl(&mut self, decl: Box<dyn Command>) -> Option<DeclId> {
|
||||||
let name = decl.name().as_bytes().to_vec();
|
let name = decl.name().as_bytes().to_vec();
|
||||||
|
|
||||||
|
@ -1770,6 +1779,18 @@ impl<'a> StateWorkingSet<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_module_mut(&mut self, module_id: ModuleId) -> &mut Module {
|
||||||
|
let num_permanent_modules = self.permanent_state.num_modules();
|
||||||
|
if module_id < num_permanent_modules {
|
||||||
|
panic!("Attempt to mutate a module that is in the permanent (immutable) state")
|
||||||
|
} else {
|
||||||
|
self.delta
|
||||||
|
.modules
|
||||||
|
.get_mut(module_id - num_permanent_modules)
|
||||||
|
.expect("internal error: missing module")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_block_mut(&mut self, block_id: BlockId) -> &mut Block {
|
pub fn get_block_mut(&mut self, block_id: BlockId) -> &mut Block {
|
||||||
let num_permanent_blocks = self.permanent_state.num_blocks();
|
let num_permanent_blocks = self.permanent_state.num_blocks();
|
||||||
if block_id < num_permanent_blocks {
|
if block_id < num_permanent_blocks {
|
||||||
|
@ -1848,7 +1869,7 @@ impl<'a> StateWorkingSet<'a> {
|
||||||
let name = self.last_overlay_name().to_vec();
|
let name = self.last_overlay_name().to_vec();
|
||||||
let origin = overlay_frame.origin;
|
let origin = overlay_frame.origin;
|
||||||
let prefixed = overlay_frame.prefixed;
|
let prefixed = overlay_frame.prefixed;
|
||||||
self.add_overlay(name, origin, vec![], prefixed);
|
self.add_overlay(name, origin, vec![], vec![], prefixed);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.delta
|
self.delta
|
||||||
|
@ -1886,6 +1907,7 @@ impl<'a> StateWorkingSet<'a> {
|
||||||
name: Vec<u8>,
|
name: Vec<u8>,
|
||||||
origin: ModuleId,
|
origin: ModuleId,
|
||||||
decls: Vec<(Vec<u8>, DeclId)>,
|
decls: Vec<(Vec<u8>, DeclId)>,
|
||||||
|
modules: Vec<(Vec<u8>, ModuleId)>,
|
||||||
prefixed: bool,
|
prefixed: bool,
|
||||||
) {
|
) {
|
||||||
let last_scope_frame = self.delta.last_scope_frame_mut();
|
let last_scope_frame = self.delta.last_scope_frame_mut();
|
||||||
|
@ -1913,6 +1935,7 @@ impl<'a> StateWorkingSet<'a> {
|
||||||
self.move_predecls_to_overlay();
|
self.move_predecls_to_overlay();
|
||||||
|
|
||||||
self.use_decls(decls);
|
self.use_decls(decls);
|
||||||
|
self.use_modules(modules);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_overlay(&mut self, name: &[u8], keep_custom: bool) {
|
pub fn remove_overlay(&mut self, name: &[u8], keep_custom: bool) {
|
||||||
|
|
|
@ -206,6 +206,10 @@ impl OverlayFrame {
|
||||||
self.decls.insert((name, input), decl_id)
|
self.decls.insert((name, input), decl_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn insert_module(&mut self, name: Vec<u8>, module_id: ModuleId) -> Option<ModuleId> {
|
||||||
|
self.modules.insert(name, module_id)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_decl(&self, name: &[u8], input: &Type) -> Option<DeclId> {
|
pub fn get_decl(&self, name: &[u8], input: &Type) -> Option<DeclId> {
|
||||||
if let Some(decl) = self.decls.get(&(name, input) as &dyn DeclKey) {
|
if let Some(decl) = self.decls.get(&(name, input) as &dyn DeclKey) {
|
||||||
Some(*decl)
|
Some(*decl)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::DeclId;
|
use crate::{DeclId, ModuleId};
|
||||||
|
|
||||||
pub enum Exportable {
|
pub enum Exportable {
|
||||||
Decl { name: Vec<u8>, id: DeclId },
|
Decl { name: Vec<u8>, id: DeclId },
|
||||||
|
Module { name: Vec<u8>, id: ModuleId },
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,26 @@
|
||||||
use crate::{BlockId, DeclId, Span};
|
use crate::{
|
||||||
|
ast::ImportPatternMember, engine::StateWorkingSet, BlockId, DeclId, ModuleId, ParseError, Span,
|
||||||
|
};
|
||||||
|
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
|
|
||||||
|
pub struct ResolvedImportPattern {
|
||||||
|
pub decls: Vec<(Vec<u8>, DeclId)>,
|
||||||
|
pub modules: Vec<(Vec<u8>, ModuleId)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ResolvedImportPattern {
|
||||||
|
pub fn new(decls: Vec<(Vec<u8>, DeclId)>, modules: Vec<(Vec<u8>, ModuleId)>) -> Self {
|
||||||
|
ResolvedImportPattern { decls, modules }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Collection of definitions that can be exported from a module
|
/// Collection of definitions that can be exported from a module
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Module {
|
pub struct Module {
|
||||||
pub name: Vec<u8>,
|
pub name: Vec<u8>,
|
||||||
pub decls: IndexMap<Vec<u8>, DeclId>,
|
pub decls: IndexMap<Vec<u8>, DeclId>,
|
||||||
|
pub submodules: IndexMap<Vec<u8>, ModuleId>,
|
||||||
pub env_block: Option<BlockId>, // `export-env { ... }` block
|
pub env_block: Option<BlockId>, // `export-env { ... }` block
|
||||||
pub main: Option<DeclId>, // `export def main`
|
pub main: Option<DeclId>, // `export def main`
|
||||||
pub span: Option<Span>,
|
pub span: Option<Span>,
|
||||||
|
@ -17,6 +31,7 @@ impl Module {
|
||||||
Module {
|
Module {
|
||||||
name,
|
name,
|
||||||
decls: IndexMap::new(),
|
decls: IndexMap::new(),
|
||||||
|
submodules: IndexMap::new(),
|
||||||
env_block: None,
|
env_block: None,
|
||||||
main: None,
|
main: None,
|
||||||
span: None,
|
span: None,
|
||||||
|
@ -27,32 +42,29 @@ impl Module {
|
||||||
Module {
|
Module {
|
||||||
name,
|
name,
|
||||||
decls: IndexMap::new(),
|
decls: IndexMap::new(),
|
||||||
|
submodules: IndexMap::new(),
|
||||||
env_block: None,
|
env_block: None,
|
||||||
main: None,
|
main: None,
|
||||||
span: Some(span),
|
span: Some(span),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn name(&self) -> Vec<u8> {
|
||||||
|
self.name.clone()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn add_decl(&mut self, name: Vec<u8>, decl_id: DeclId) -> Option<DeclId> {
|
pub fn add_decl(&mut self, name: Vec<u8>, decl_id: DeclId) -> Option<DeclId> {
|
||||||
self.decls.insert(name, decl_id)
|
self.decls.insert(name, decl_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn add_submodule(&mut self, name: Vec<u8>, module_id: ModuleId) -> Option<ModuleId> {
|
||||||
|
self.submodules.insert(name, module_id)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn add_env_block(&mut self, block_id: BlockId) {
|
pub fn add_env_block(&mut self, block_id: BlockId) {
|
||||||
self.env_block = Some(block_id);
|
self.env_block = Some(block_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn extend(&mut self, other: &Module) {
|
|
||||||
self.decls.extend(other.decls.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_empty(&self) -> bool {
|
|
||||||
self.decls.is_empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_decl_id(&self, name: &[u8]) -> Option<DeclId> {
|
|
||||||
self.decls.get(name).copied()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn has_decl(&self, name: &[u8]) -> bool {
|
pub fn has_decl(&self, name: &[u8]) -> bool {
|
||||||
if name == self.name && self.main.is_some() {
|
if name == self.name && self.main.is_some() {
|
||||||
return true;
|
return true;
|
||||||
|
@ -61,6 +73,120 @@ impl Module {
|
||||||
self.decls.contains_key(name)
|
self.decls.contains_key(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn resolve_import_pattern(
|
||||||
|
&self,
|
||||||
|
working_set: &StateWorkingSet,
|
||||||
|
self_id: ModuleId,
|
||||||
|
members: &[ImportPatternMember],
|
||||||
|
name_override: Option<&[u8]>, // name under the module was stored (doesn't have to be the
|
||||||
|
// same as self.name)
|
||||||
|
) -> (ResolvedImportPattern, Vec<ParseError>) {
|
||||||
|
let final_name = name_override.unwrap_or(&self.name).to_vec();
|
||||||
|
|
||||||
|
let (head, rest) = if let Some((head, rest)) = members.split_first() {
|
||||||
|
(head, rest)
|
||||||
|
} else {
|
||||||
|
// Import pattern was just name without any members
|
||||||
|
let mut results = vec![];
|
||||||
|
let mut errors = vec![];
|
||||||
|
|
||||||
|
for (_, id) in &self.submodules {
|
||||||
|
let submodule = working_set.get_module(*id);
|
||||||
|
let (sub_results, sub_errors) =
|
||||||
|
submodule.resolve_import_pattern(working_set, *id, &[], None);
|
||||||
|
errors.extend(sub_errors);
|
||||||
|
|
||||||
|
for (sub_name, sub_decl_id) in sub_results.decls {
|
||||||
|
let mut new_name = final_name.clone();
|
||||||
|
new_name.push(b' ');
|
||||||
|
new_name.extend(sub_name);
|
||||||
|
|
||||||
|
results.push((new_name, sub_decl_id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
results.extend(self.decls_with_head(&final_name));
|
||||||
|
|
||||||
|
return (
|
||||||
|
ResolvedImportPattern::new(results, vec![(final_name, self_id)]),
|
||||||
|
errors,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
match head {
|
||||||
|
ImportPatternMember::Name { name, span } => {
|
||||||
|
if name == b"main" {
|
||||||
|
if let Some(main_decl_id) = self.main {
|
||||||
|
(
|
||||||
|
ResolvedImportPattern::new(vec![(final_name, main_decl_id)], vec![]),
|
||||||
|
vec![],
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
ResolvedImportPattern::new(vec![], vec![]),
|
||||||
|
vec![ParseError::ExportNotFound(*span)],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else if let Some(decl_id) = self.decls.get(name) {
|
||||||
|
(
|
||||||
|
ResolvedImportPattern::new(vec![(name.clone(), *decl_id)], vec![]),
|
||||||
|
vec![],
|
||||||
|
)
|
||||||
|
} else if let Some(submodule_id) = self.submodules.get(name) {
|
||||||
|
let submodule = working_set.get_module(*submodule_id);
|
||||||
|
submodule.resolve_import_pattern(working_set, *submodule_id, rest, None)
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
ResolvedImportPattern::new(vec![], vec![]),
|
||||||
|
vec![ParseError::ExportNotFound(*span)],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImportPatternMember::Glob { .. } => {
|
||||||
|
let mut decls = vec![];
|
||||||
|
let mut submodules = vec![];
|
||||||
|
let mut errors = vec![];
|
||||||
|
|
||||||
|
for (_, id) in &self.submodules {
|
||||||
|
let submodule = working_set.get_module(*id);
|
||||||
|
let (sub_results, sub_errors) =
|
||||||
|
submodule.resolve_import_pattern(working_set, *id, &[], None);
|
||||||
|
decls.extend(sub_results.decls);
|
||||||
|
submodules.extend(sub_results.modules);
|
||||||
|
errors.extend(sub_errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
decls.extend(self.decls());
|
||||||
|
submodules.extend(self.submodules());
|
||||||
|
|
||||||
|
(ResolvedImportPattern::new(decls, submodules), errors)
|
||||||
|
}
|
||||||
|
ImportPatternMember::List { names } => {
|
||||||
|
let mut decls = vec![];
|
||||||
|
let mut submodules = vec![];
|
||||||
|
let mut errors = vec![];
|
||||||
|
|
||||||
|
for (name, span) in names {
|
||||||
|
if name == b"main" {
|
||||||
|
if let Some(main_decl_id) = self.main {
|
||||||
|
decls.push((final_name.clone(), main_decl_id));
|
||||||
|
} else {
|
||||||
|
errors.push(ParseError::ExportNotFound(*span));
|
||||||
|
}
|
||||||
|
} else if let Some(decl_id) = self.decls.get(name) {
|
||||||
|
decls.push((name.clone(), *decl_id));
|
||||||
|
} else if let Some(submodule_id) = self.submodules.get(name) {
|
||||||
|
submodules.push((name.clone(), *submodule_id));
|
||||||
|
} else {
|
||||||
|
errors.push(ParseError::ExportNotFound(*span));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(ResolvedImportPattern::new(decls, submodules), errors)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn decl_name_with_head(&self, name: &[u8], head: &[u8]) -> Option<Vec<u8>> {
|
pub fn decl_name_with_head(&self, name: &[u8], head: &[u8]) -> Option<Vec<u8>> {
|
||||||
if self.has_decl(name) {
|
if self.has_decl(name) {
|
||||||
let mut new_name = head.to_vec();
|
let mut new_name = head.to_vec();
|
||||||
|
@ -124,6 +250,13 @@ impl Module {
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn submodules(&self) -> Vec<(Vec<u8>, ModuleId)> {
|
||||||
|
self.submodules
|
||||||
|
.iter()
|
||||||
|
.map(|(name, id)| (name.clone(), *id))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn decl_names(&self) -> Vec<Vec<u8>> {
|
pub fn decl_names(&self) -> Vec<Vec<u8>> {
|
||||||
let mut result: Vec<Vec<u8>> = self.decls.keys().cloned().collect();
|
let mut result: Vec<Vec<u8>> = self.decls.keys().cloned().collect();
|
||||||
|
|
||||||
|
|
|
@ -200,14 +200,36 @@ pub enum ParseError {
|
||||||
#[error("Can't export {0} named same as the module.")]
|
#[error("Can't export {0} named same as the module.")]
|
||||||
#[diagnostic(
|
#[diagnostic(
|
||||||
code(nu::parser::named_as_module),
|
code(nu::parser::named_as_module),
|
||||||
help("Module {1} can't export {0} named the same as the module. Either change the module name, or export `main` custom command.")
|
help("Module {1} can't export {0} named the same as the module. Either change the module name, or export `{2}` {0}.")
|
||||||
)]
|
)]
|
||||||
NamedAsModule(
|
NamedAsModule(
|
||||||
|
String,
|
||||||
String,
|
String,
|
||||||
String,
|
String,
|
||||||
#[label = "can't export from module {1}"] Span,
|
#[label = "can't export from module {1}"] Span,
|
||||||
),
|
),
|
||||||
|
|
||||||
|
#[error("Module already contains 'main' command.")]
|
||||||
|
#[diagnostic(
|
||||||
|
code(nu::parser::module_double_main),
|
||||||
|
help("Tried to add 'main' command to module '{0}' but it has already been added.")
|
||||||
|
)]
|
||||||
|
ModuleDoubleMain(
|
||||||
|
String,
|
||||||
|
#[label = "module '{0}' already contains 'main'"] Span,
|
||||||
|
),
|
||||||
|
|
||||||
|
#[error("Invalid module file name")]
|
||||||
|
#[diagnostic(
|
||||||
|
code(nu::parser::invalid_module_file_name),
|
||||||
|
help("File {0} resolves to module name {1} which is the same as the parent module. Either rename the file or, save it as 'mod.nu' to define the parent module.")
|
||||||
|
)]
|
||||||
|
InvalidModuleFileName(
|
||||||
|
String,
|
||||||
|
String,
|
||||||
|
#[label = "submodule can't have the same name as the parent module"] Span,
|
||||||
|
),
|
||||||
|
|
||||||
#[error("Can't export alias defined as 'main'.")]
|
#[error("Can't export alias defined as 'main'.")]
|
||||||
#[diagnostic(
|
#[diagnostic(
|
||||||
code(nu::parser::export_main_alias_not_allowed),
|
code(nu::parser::export_main_alias_not_allowed),
|
||||||
|
@ -371,7 +393,7 @@ pub enum ParseError {
|
||||||
|
|
||||||
#[error("Wrong import pattern structure.")]
|
#[error("Wrong import pattern structure.")]
|
||||||
#[diagnostic(code(nu::parser::missing_import_pattern))]
|
#[diagnostic(code(nu::parser::missing_import_pattern))]
|
||||||
WrongImportPattern(#[label = "invalid import pattern structure"] Span),
|
WrongImportPattern(String, #[label = "{0}"] Span),
|
||||||
|
|
||||||
#[error("Export not found.")]
|
#[error("Export not found.")]
|
||||||
#[diagnostic(code(nu::parser::export_not_found))]
|
#[diagnostic(code(nu::parser::export_not_found))]
|
||||||
|
@ -452,7 +474,9 @@ impl ParseError {
|
||||||
ParseError::AliasNotValid(s) => *s,
|
ParseError::AliasNotValid(s) => *s,
|
||||||
ParseError::CommandDefNotValid(s) => *s,
|
ParseError::CommandDefNotValid(s) => *s,
|
||||||
ParseError::ModuleNotFound(s) => *s,
|
ParseError::ModuleNotFound(s) => *s,
|
||||||
ParseError::NamedAsModule(_, _, s) => *s,
|
ParseError::NamedAsModule(_, _, _, s) => *s,
|
||||||
|
ParseError::ModuleDoubleMain(_, s) => *s,
|
||||||
|
ParseError::InvalidModuleFileName(_, _, s) => *s,
|
||||||
ParseError::ExportMainAliasNotAllowed(s) => *s,
|
ParseError::ExportMainAliasNotAllowed(s) => *s,
|
||||||
ParseError::CyclicalModuleImport(_, s) => *s,
|
ParseError::CyclicalModuleImport(_, s) => *s,
|
||||||
ParseError::ModuleOrOverlayNotFound(s) => *s,
|
ParseError::ModuleOrOverlayNotFound(s) => *s,
|
||||||
|
@ -486,7 +510,7 @@ impl ParseError {
|
||||||
ParseError::MissingColumns(_, s) => *s,
|
ParseError::MissingColumns(_, s) => *s,
|
||||||
ParseError::AssignmentMismatch(_, _, s) => *s,
|
ParseError::AssignmentMismatch(_, _, s) => *s,
|
||||||
ParseError::MissingImportPattern(s) => *s,
|
ParseError::MissingImportPattern(s) => *s,
|
||||||
ParseError::WrongImportPattern(s) => *s,
|
ParseError::WrongImportPattern(_, s) => *s,
|
||||||
ParseError::ExportNotFound(s) => *s,
|
ParseError::ExportNotFound(s) => *s,
|
||||||
ParseError::SourcedFileNotFound(_, s) => *s,
|
ParseError::SourcedFileNotFound(_, s) => *s,
|
||||||
ParseError::RegisteredFileNotFound(_, s) => *s,
|
ParseError::RegisteredFileNotFound(_, s) => *s,
|
||||||
|
|
|
@ -563,3 +563,154 @@ fn main_inside_module_is_main() {
|
||||||
|
|
||||||
assert_eq!(actual.out, "foo");
|
assert_eq!(actual.out, "foo");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn module_as_file() {
|
||||||
|
let inp = &[r#"module samples/spam.nu"#, "use spam foo", "foo"];
|
||||||
|
|
||||||
|
let actual = nu!(cwd: "tests/modules", pipeline(&inp.join("; ")));
|
||||||
|
|
||||||
|
assert_eq!(actual.out, "foo");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn export_module_as_file() {
|
||||||
|
let inp = &[r#"export module samples/spam.nu"#, "use spam foo", "foo"];
|
||||||
|
|
||||||
|
let actual = nu!(cwd: "tests/modules", pipeline(&inp.join("; ")));
|
||||||
|
|
||||||
|
assert_eq!(actual.out, "foo");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deep_import_patterns() {
|
||||||
|
let module_decl = r#"
|
||||||
|
module spam {
|
||||||
|
export module eggs {
|
||||||
|
export module beans {
|
||||||
|
export def foo [] { 'foo' };
|
||||||
|
export def bar [] { 'bar' }
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let inp = &[module_decl, "use spam", "spam eggs beans foo"];
|
||||||
|
let actual = nu!(cwd: ".", pipeline(&inp.join("; ")));
|
||||||
|
assert_eq!(actual.out, "foo");
|
||||||
|
|
||||||
|
let inp = &[module_decl, "use spam eggs", "eggs beans foo"];
|
||||||
|
let actual = nu!(cwd: ".", pipeline(&inp.join("; ")));
|
||||||
|
assert_eq!(actual.out, "foo");
|
||||||
|
|
||||||
|
let inp = &[module_decl, "use spam eggs beans", "beans foo"];
|
||||||
|
let actual = nu!(cwd: ".", pipeline(&inp.join("; ")));
|
||||||
|
assert_eq!(actual.out, "foo");
|
||||||
|
|
||||||
|
let inp = &[module_decl, "use spam eggs beans foo", "foo"];
|
||||||
|
let actual = nu!(cwd: ".", pipeline(&inp.join("; ")));
|
||||||
|
assert_eq!(actual.out, "foo");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn module_dir() {
|
||||||
|
let import = "use samples/spam";
|
||||||
|
|
||||||
|
let inp = &[import, "spam"];
|
||||||
|
let actual = nu!(cwd: "tests/modules", pipeline(&inp.join("; ")));
|
||||||
|
assert_eq!(actual.out, "spam");
|
||||||
|
|
||||||
|
let inp = &[import, "spam foo"];
|
||||||
|
let actual = nu!(cwd: "tests/modules", pipeline(&inp.join("; ")));
|
||||||
|
assert_eq!(actual.out, "foo");
|
||||||
|
|
||||||
|
let inp = &[import, "spam bar"];
|
||||||
|
let actual = nu!(cwd: "tests/modules", pipeline(&inp.join("; ")));
|
||||||
|
assert_eq!(actual.out, "bar");
|
||||||
|
|
||||||
|
let inp = &[import, "spam foo baz"];
|
||||||
|
let actual = nu!(cwd: "tests/modules", pipeline(&inp.join("; ")));
|
||||||
|
assert_eq!(actual.out, "foobaz");
|
||||||
|
|
||||||
|
let inp = &[import, "spam bar baz"];
|
||||||
|
let actual = nu!(cwd: "tests/modules", pipeline(&inp.join("; ")));
|
||||||
|
assert_eq!(actual.out, "barbaz");
|
||||||
|
|
||||||
|
let inp = &[import, "spam baz"];
|
||||||
|
let actual = nu!(cwd: "tests/modules", pipeline(&inp.join("; ")));
|
||||||
|
assert_eq!(actual.out, "spambaz");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn not_allowed_submodule_file() {
|
||||||
|
let inp = &["use samples/not_allowed"];
|
||||||
|
let actual = nu!(cwd: "tests/modules", pipeline(&inp.join("; ")));
|
||||||
|
assert!(actual.err.contains("invalid_module_file_name"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn allowed_local_module() {
|
||||||
|
let inp = &["module spam { module spam {} }"];
|
||||||
|
let actual = nu!(cwd: "tests/modules", pipeline(&inp.join("; ")));
|
||||||
|
assert!(actual.err.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn not_allowed_submodule() {
|
||||||
|
let inp = &["module spam { export module spam {} }"];
|
||||||
|
let actual = nu!(cwd: "tests/modules", pipeline(&inp.join("; ")));
|
||||||
|
assert!(actual.err.contains("named_as_module"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn module_self_name() {
|
||||||
|
let inp = &[
|
||||||
|
"module spam { export module mod { export def main [] { 'spam' } } }",
|
||||||
|
"use spam",
|
||||||
|
"spam",
|
||||||
|
];
|
||||||
|
let actual = nu!(cwd: "tests/modules", pipeline(&inp.join("; ")));
|
||||||
|
assert_eq!(actual.out, "spam");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn module_self_name_main_not_allowed() {
|
||||||
|
let inp = &[
|
||||||
|
r#"module spam {
|
||||||
|
export def main [] { 'main spam' };
|
||||||
|
|
||||||
|
export module mod {
|
||||||
|
export def main [] { 'mod spam' }
|
||||||
|
}
|
||||||
|
}"#,
|
||||||
|
"use spam",
|
||||||
|
"spam",
|
||||||
|
];
|
||||||
|
let actual = nu!(cwd: "tests/modules", pipeline(&inp.join("; ")));
|
||||||
|
assert!(actual.err.contains("module_double_main"));
|
||||||
|
|
||||||
|
let inp = &[
|
||||||
|
r#"module spam {
|
||||||
|
export module mod {
|
||||||
|
export def main [] { 'mod spam' }
|
||||||
|
};
|
||||||
|
|
||||||
|
export def main [] { 'main spam' }
|
||||||
|
}"#,
|
||||||
|
"use spam",
|
||||||
|
"spam",
|
||||||
|
];
|
||||||
|
let actual = nu!(cwd: "tests/modules", pipeline(&inp.join("; ")));
|
||||||
|
assert!(actual.err.contains("module_double_main"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn module_main_not_found() {
|
||||||
|
let inp = &["module spam {}", "use spam main"];
|
||||||
|
let actual = nu!(cwd: "tests/modules", pipeline(&inp.join("; ")));
|
||||||
|
assert!(actual.err.contains("export_not_found"));
|
||||||
|
|
||||||
|
let inp = &["module spam {}", "use spam [ main ]"];
|
||||||
|
let actual = nu!(cwd: "tests/modules", pipeline(&inp.join("; ")));
|
||||||
|
assert!(actual.err.contains("export_not_found"));
|
||||||
|
}
|
||||||
|
|
0
tests/modules/samples/not_allowed/not_allowed.nu
Normal file
0
tests/modules/samples/not_allowed/not_allowed.nu
Normal file
1
tests/modules/samples/spam.nu
Normal file
1
tests/modules/samples/spam.nu
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export def foo [] { 'foo' }
|
3
tests/modules/samples/spam/bar.nu
Normal file
3
tests/modules/samples/spam/bar.nu
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export def main [] { 'bar' }
|
||||||
|
|
||||||
|
export def baz [] { 'barbaz' }
|
3
tests/modules/samples/spam/foo.nu
Normal file
3
tests/modules/samples/spam/foo.nu
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export def main [] { 'foo' }
|
||||||
|
|
||||||
|
export def baz [] { 'foobaz' }
|
3
tests/modules/samples/spam/mod.nu
Normal file
3
tests/modules/samples/spam/mod.nu
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export def main [] { 'spam' }
|
||||||
|
|
||||||
|
export def baz [] { 'spambaz' }
|
|
@ -1313,6 +1313,64 @@ fn alias_overlay_new() {
|
||||||
assert_eq!(actual_repl.out, "eggs");
|
assert_eq!(actual_repl.out, "eggs");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn overlay_use_module_dir() {
|
||||||
|
let import = "overlay use samples/spam";
|
||||||
|
|
||||||
|
let inp = &[import, "spam"];
|
||||||
|
let actual = nu!(cwd: "tests/modules", pipeline(&inp.join("; ")));
|
||||||
|
assert_eq!(actual.out, "spam");
|
||||||
|
|
||||||
|
let inp = &[import, "foo"];
|
||||||
|
let actual = nu!(cwd: "tests/modules", pipeline(&inp.join("; ")));
|
||||||
|
assert_eq!(actual.out, "foo");
|
||||||
|
|
||||||
|
let inp = &[import, "bar"];
|
||||||
|
let actual = nu!(cwd: "tests/modules", pipeline(&inp.join("; ")));
|
||||||
|
assert_eq!(actual.out, "bar");
|
||||||
|
|
||||||
|
let inp = &[import, "foo baz"];
|
||||||
|
let actual = nu!(cwd: "tests/modules", pipeline(&inp.join("; ")));
|
||||||
|
assert_eq!(actual.out, "foobaz");
|
||||||
|
|
||||||
|
let inp = &[import, "bar baz"];
|
||||||
|
let actual = nu!(cwd: "tests/modules", pipeline(&inp.join("; ")));
|
||||||
|
assert_eq!(actual.out, "barbaz");
|
||||||
|
|
||||||
|
let inp = &[import, "baz"];
|
||||||
|
let actual = nu!(cwd: "tests/modules", pipeline(&inp.join("; ")));
|
||||||
|
assert_eq!(actual.out, "spambaz");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn overlay_use_module_dir_prefix() {
|
||||||
|
let import = "overlay use samples/spam --prefix";
|
||||||
|
|
||||||
|
let inp = &[import, "spam"];
|
||||||
|
let actual = nu!(cwd: "tests/modules", pipeline(&inp.join("; ")));
|
||||||
|
assert_eq!(actual.out, "spam");
|
||||||
|
|
||||||
|
let inp = &[import, "spam foo"];
|
||||||
|
let actual = nu!(cwd: "tests/modules", pipeline(&inp.join("; ")));
|
||||||
|
assert_eq!(actual.out, "foo");
|
||||||
|
|
||||||
|
let inp = &[import, "spam bar"];
|
||||||
|
let actual = nu!(cwd: "tests/modules", pipeline(&inp.join("; ")));
|
||||||
|
assert_eq!(actual.out, "bar");
|
||||||
|
|
||||||
|
let inp = &[import, "spam foo baz"];
|
||||||
|
let actual = nu!(cwd: "tests/modules", pipeline(&inp.join("; ")));
|
||||||
|
assert_eq!(actual.out, "foobaz");
|
||||||
|
|
||||||
|
let inp = &[import, "spam bar baz"];
|
||||||
|
let actual = nu!(cwd: "tests/modules", pipeline(&inp.join("; ")));
|
||||||
|
assert_eq!(actual.out, "barbaz");
|
||||||
|
|
||||||
|
let inp = &[import, "spam baz"];
|
||||||
|
let actual = nu!(cwd: "tests/modules", pipeline(&inp.join("; ")));
|
||||||
|
assert_eq!(actual.out, "spambaz");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn overlay_help_no_error() {
|
fn overlay_help_no_error() {
|
||||||
let actual = nu!(cwd: ".", "overlay hide -h");
|
let actual = nu!(cwd: ".", "overlay hide -h");
|
||||||
|
|
Loading…
Reference in a new issue