Allow recursive module dirs; Require mod.nu in dirs (#9185)

This commit is contained in:
Jakub Žádník 2023-05-13 01:20:33 +03:00 committed by GitHub
parent 92c1051143
commit 8d8304cf91
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 85 additions and 43 deletions

View File

@ -1825,14 +1825,22 @@ pub fn parse_module_file_or_dir(
return None; return None;
}; };
let mut file_paths = vec![]; let mod_nu_path = module_path.join("mod.nu");
if !(mod_nu_path.exists() && mod_nu_path.is_file()) {
working_set.error(ParseError::ModuleMissingModNuFile(path_span));
return None;
}
let mut paths = vec![];
for entry in dir_contents.flatten() { for entry in dir_contents.flatten() {
let entry_path = entry.path(); let entry_path = entry.path();
if entry_path.is_file() if (entry_path.is_file()
&& entry_path.extension() == Some(OsStr::new("nu")) && entry_path.extension() == Some(OsStr::new("nu"))
&& entry_path.file_stem() != Some(OsStr::new("mod")) && entry_path.file_stem() != Some(OsStr::new("mod")))
|| (entry_path.is_dir() && entry_path.join("mod.nu").exists())
{ {
if entry_path.file_stem() == Some(OsStr::new(&module_name)) { if entry_path.file_stem() == Some(OsStr::new(&module_name)) {
working_set.error(ParseError::InvalidModuleFileName( working_set.error(ParseError::InvalidModuleFileName(
@ -1843,64 +1851,54 @@ pub fn parse_module_file_or_dir(
return None; return None;
} }
file_paths.push(entry_path); paths.push(entry_path);
} }
} }
file_paths.sort(); paths.sort();
// working_set.enter_scope(); // working_set.enter_scope();
let mut submodules = vec![]; let mut submodules = vec![];
for file_path in file_paths { for p in paths {
if let Some(submodule_id) = if let Some(submodule_id) = parse_module_file_or_dir(
parse_module_file(working_set, file_path, path_span, None) working_set,
{ p.to_string_lossy().as_bytes(),
path_span,
None,
) {
let submodule_name = working_set.get_module(submodule_id).name(); let submodule_name = working_set.get_module(submodule_id).name();
submodules.push((submodule_name, submodule_id)); submodules.push((submodule_name, submodule_id));
} }
} }
let mod_nu_path = module_path.join("mod.nu"); if let Some(module_id) = parse_module_file(
working_set,
if mod_nu_path.exists() && mod_nu_path.is_file() { mod_nu_path,
if let Some(module_id) = parse_module_file( path_span,
working_set, name_override.or(Some(module_name)),
mod_nu_path, ) {
path_span, let mut module = working_set.get_module(module_id).clone();
name_override.or(Some(module_name)),
) {
let mut module = working_set.get_module(module_id).clone();
for (submodule_name, submodule_id) in submodules {
module.add_submodule(submodule_name, submodule_id);
}
let module_name = String::from_utf8_lossy(&module.name).to_string();
let module_comments =
if let Some(comments) = working_set.get_module_comments(module_id) {
comments.to_vec()
} else {
vec![]
};
let new_module_id =
working_set.add_module(&module_name, module, module_comments);
Some(new_module_id)
} else {
None
}
} else {
let mut module = Module::new(module_name.as_bytes().to_vec());
for (submodule_name, submodule_id) in submodules { for (submodule_name, submodule_id) in submodules {
module.add_submodule(submodule_name, submodule_id); module.add_submodule(submodule_name, submodule_id);
} }
Some(working_set.add_module(&module_name, module, vec![])) let module_name = String::from_utf8_lossy(&module.name).to_string();
let module_comments =
if let Some(comments) = working_set.get_module_comments(module_id) {
comments.to_vec()
} else {
vec![]
};
let new_module_id = working_set.add_module(&module_name, module, module_comments);
Some(new_module_id)
} else {
None
} }
} else { } else {
working_set.error(ParseError::ModuleNotFound(path_span)); working_set.error(ParseError::ModuleNotFound(path_span));

View File

@ -193,6 +193,13 @@ pub enum ParseError {
)] )]
ModuleNotFound(#[label = "module not found"] Span), ModuleNotFound(#[label = "module not found"] Span),
#[error("Missing mod.nu file.")]
#[diagnostic(
code(nu::parser::module_missing_mod_nu_file),
help("When importing a directory as a Nushell module, it needs to contain a mod.nu file (can be empty). Alternatively, you can use .nu files in the directory as modules individually.")
)]
ModuleMissingModNuFile(#[label = "module directory is missing a mod.nu file"] Span),
#[error("Cyclical module import.")] #[error("Cyclical module import.")]
#[diagnostic(code(nu::parser::cyclical_module_import), help("{0}"))] #[diagnostic(code(nu::parser::cyclical_module_import), help("{0}"))]
CyclicalModuleImport(String, #[label = "detected cyclical module import"] Span), CyclicalModuleImport(String, #[label = "detected cyclical module import"] Span),
@ -484,6 +491,7 @@ 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::ModuleMissingModNuFile(s) => *s,
ParseError::NamedAsModule(_, _, _, s) => *s, ParseError::NamedAsModule(_, _, _, s) => *s,
ParseError::ModuleDoubleMain(_, s) => *s, ParseError::ModuleDoubleMain(_, s) => *s,
ParseError::InvalidModuleFileName(_, _, s) => *s, ParseError::InvalidModuleFileName(_, _, s) => *s,

View File

@ -641,6 +641,27 @@ fn module_dir() {
assert_eq!(actual.out, "spambaz"); assert_eq!(actual.out, "spambaz");
} }
#[test]
fn module_dir_deep() {
let import = "use samples/spam";
let inp = &[import, "spam bacon"];
let actual_repl = nu!(cwd: "tests/modules", pipeline(&inp.join("; ")));
assert_eq!(actual_repl.out, "bacon");
let inp = &[import, "spam bacon foo"];
let actual_repl = nu!(cwd: "tests/modules", pipeline(&inp.join("; ")));
assert_eq!(actual_repl.out, "bacon foo");
let inp = &[import, "spam bacon beans"];
let actual_repl = nu!(cwd: "tests/modules", pipeline(&inp.join("; ")));
assert_eq!(actual_repl.out, "beans");
let inp = &[import, "spam bacon beans foo"];
let actual_repl = nu!(cwd: "tests/modules", pipeline(&inp.join("; ")));
assert_eq!(actual_repl.out, "beans foo");
}
#[test] #[test]
fn module_dir_import_twice_no_panic() { fn module_dir_import_twice_no_panic() {
let import = "use samples/spam"; let import = "use samples/spam";
@ -656,6 +677,13 @@ fn not_allowed_submodule_file() {
assert!(actual.err.contains("invalid_module_file_name")); assert!(actual.err.contains("invalid_module_file_name"));
} }
#[test]
fn module_dir_missing_mod_nu() {
let inp = &["use samples/missing_mod_nu"];
let actual = nu!(cwd: "tests/modules", pipeline(&inp.join("; ")));
assert!(actual.err.contains("module_missing_mod_nu_file"));
}
#[test] #[test]
fn allowed_local_module() { fn allowed_local_module() {
let inp = &["module spam { module spam {} }"]; let inp = &["module spam { module spam {} }"];

View File

View File

@ -0,0 +1 @@
export def main [] { 'beans foo' }

View File

@ -0,0 +1,3 @@
export def test [] { 'test' }
export def main [] { 'beans' }

View File

@ -0,0 +1 @@
export def main [] { 'bacon foo' }

View File

@ -0,0 +1,3 @@
export def test [] { 'test' }
export def main [] { 'bacon' }