Allow main command to define top-level module command (#7764)

This commit is contained in:
Jakub Žádník
2023-01-22 21:34:15 +02:00
committed by GitHub
parent 8d5165c449
commit 3552d03f6c
13 changed files with 633 additions and 135 deletions

View File

@ -151,15 +151,11 @@ pub fn help_modules(
long_desc.push_str(&format!("{G}Module{RESET}: {C}{name}{RESET}"));
long_desc.push_str("\n\n");
if !module.decls.is_empty() {
if !module.decls.is_empty() || module.main.is_some() {
let commands: Vec<(Vec<u8>, DeclId)> = engine_state.get_decls_sorted(false).collect();
let mut module_commands: Vec<(&[u8], DeclId)> = module
.decls
.iter()
.map(|(name, id)| (name.as_ref(), *id))
.collect();
module_commands.sort_by(|a, b| a.0.cmp(b.0));
let mut module_commands = module.decls();
module_commands.sort_by(|a, b| a.0.cmp(&b.0));
let commands_str = module_commands
.iter()

View File

@ -302,12 +302,14 @@ fn parse_module(
is_debug: bool,
span: Span,
) -> Result<PipelineData, ShellError> {
let filename = filename.unwrap_or_else(|| "empty".to_string());
let start = working_set.next_span_start();
working_set.add_file(filename.unwrap_or_else(|| "empty".to_string()), contents);
working_set.add_file(filename.clone(), contents);
let end = working_set.next_span_start();
let new_span = Span::new(start, end);
let (_, _, _, err) = parse_module_block(working_set, new_span, &[]);
let (_, _, _, err) = parse_module_block(working_set, new_span, filename.as_bytes(), &[]);
if err.is_some() {
if is_debug {

View File

@ -314,3 +314,31 @@ fn help_usage_extra_usage() {
assert!(!actual.out.contains("alias_line2"));
})
}
#[test]
fn help_modules_main_1() {
let inp = &[
r#"module spam {
export def main [] { 'foo' };
}"#,
"help spam",
];
let actual = nu!(cwd: ".", pipeline(&inp.join("; ")));
assert!(actual.out.contains(" spam"));
}
#[test]
fn help_modules_main_2() {
let inp = &[
r#"module spam {
export def main [] { 'foo' };
}"#,
"help modules | where name == spam | get 0.commands.0",
];
let actual = nu!(cwd: ".", pipeline(&inp.join("; ")));
assert_eq!(actual.out, "spam");
}

View File

@ -211,3 +211,96 @@ fn use_module_creates_accurate_did_you_mean_2() {
"command 'foo' was not found but it exists in module 'spam'; try importing it with `use`"
));
}
#[test]
fn use_main_1() {
let inp = &[
r#"module spam { export def main [] { "spam" } }"#,
r#"use spam"#,
r#"spam"#,
];
let actual = nu!(cwd: ".", pipeline(&inp.join("; ")));
assert_eq!(actual.out, "spam");
}
#[test]
fn use_main_2() {
let inp = &[
r#"module spam { export def main [] { "spam" } }"#,
r#"use spam main"#,
r#"spam"#,
];
let actual = nu!(cwd: ".", pipeline(&inp.join("; ")));
assert_eq!(actual.out, "spam");
}
#[test]
fn use_main_3() {
let inp = &[
r#"module spam { export def main [] { "spam" } }"#,
r#"use spam [ main ]"#,
r#"spam"#,
];
let actual = nu!(cwd: ".", pipeline(&inp.join("; ")));
assert_eq!(actual.out, "spam");
}
#[test]
fn use_main_4() {
let inp = &[
r#"module spam { export def main [] { "spam" } }"#,
r#"use spam *"#,
r#"spam"#,
];
let actual = nu!(cwd: ".", pipeline(&inp.join("; ")));
assert_eq!(actual.out, "spam");
}
#[test]
fn use_main_def_env() {
let inp = &[
r#"module spam { export def-env main [] { let-env SPAM = "spam" } }"#,
r#"use spam"#,
r#"spam"#,
r#"$env.SPAM"#,
];
let actual = nu!(cwd: ".", pipeline(&inp.join("; ")));
assert_eq!(actual.out, "spam");
}
#[test]
fn use_main_def_known_external() {
// note: requires installed cargo
let inp = &[
r#"module cargo { export extern main [] }"#,
r#"use cargo"#,
r#"cargo --version"#,
];
let actual = nu!(cwd: ".", pipeline(&inp.join("; ")));
assert!(actual.out.contains("cargo"));
}
#[test]
fn use_main_not_exported() {
let inp = &[
r#"module spam { def main [] { "spam" } }"#,
r#"use spam"#,
r#"spam"#,
];
let actual = nu!(cwd: ".", pipeline(&inp.join("; ")));
assert!(actual.err.contains("external_command"));
}