From d38c1add13580d163ba211b4ce6d6d0e9e18b14a Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Thu, 30 May 2024 12:28:54 -0500 Subject: [PATCH] Allow listing recipes in submodules with `--list-submodules` (#2113) --- completions/just.bash | 2 +- completions/just.elvish | 1 + completions/just.fish | 1 + completions/just.powershell | 1 + completions/just.zsh | 1 + src/config.rs | 14 +++- src/subcommand.rs | 38 ++++++++--- tests/list.rs | 130 ++++++++++++++++++++++++++++++++++++ tests/test.rs | 2 +- 9 files changed, 176 insertions(+), 14 deletions(-) diff --git a/completions/just.bash b/completions/just.bash index c539779..e2513b0 100644 --- a/completions/just.bash +++ b/completions/just.bash @@ -30,7 +30,7 @@ _just() { case "${cmd}" in just) - opts="-n -f -q -u -v -d -c -e -l -s -E -g -h -V --check --chooser --color --command-color --yes --dry-run --dump-format --highlight --list-heading --list-prefix --no-aliases --no-deps --no-dotenv --no-highlight --justfile --quiet --set --shell --shell-arg --shell-command --clear-shell-args --unsorted --unstable --verbose --working-directory --changelog --choose --command --completions --dump --edit --evaluate --fmt --init --list --groups --man --show --summary --variables --dotenv-filename --dotenv-path --global-justfile --timestamp --timestamp-format --help --version [ARGUMENTS]..." + opts="-n -f -q -u -v -d -c -e -l -s -E -g -h -V --check --chooser --color --command-color --yes --dry-run --dump-format --highlight --list-heading --list-prefix --list-submodules --no-aliases --no-deps --no-dotenv --no-highlight --justfile --quiet --set --shell --shell-arg --shell-command --clear-shell-args --unsorted --unstable --verbose --working-directory --changelog --choose --command --completions --dump --edit --evaluate --fmt --init --list --groups --man --show --summary --variables --dotenv-filename --dotenv-path --global-justfile --timestamp --timestamp-format --help --version [ARGUMENTS]..." if [[ ${cur} == -* ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 diff --git a/completions/just.elvish b/completions/just.elvish index 18382ea..9655fe4 100644 --- a/completions/just.elvish +++ b/completions/just.elvish @@ -46,6 +46,7 @@ set edit:completion:arg-completer[just] = {|@words| cand -n 'Print what just would do without doing it' cand --dry-run 'Print what just would do without doing it' cand --highlight 'Highlight echoed recipe lines in bold' + cand --list-submodules 'List recipes in submodules' cand --no-aliases 'Don''t show aliases in list' cand --no-deps 'Don''t run recipe dependencies' cand --no-dotenv 'Don''t load `.env` file' diff --git a/completions/just.fish b/completions/just.fish index 7b5a1b4..4a49db2 100644 --- a/completions/just.fish +++ b/completions/just.fish @@ -57,6 +57,7 @@ complete -c just -l check -d 'Run `--fmt` in \'check\' mode. Exits with 0 if jus complete -c just -l yes -d 'Automatically confirm all recipes.' complete -c just -s n -l dry-run -d 'Print what just would do without doing it' complete -c just -l highlight -d 'Highlight echoed recipe lines in bold' +complete -c just -l list-submodules -d 'List recipes in submodules' complete -c just -l no-aliases -d 'Don\'t show aliases in list' complete -c just -l no-deps -d 'Don\'t run recipe dependencies' complete -c just -l no-dotenv -d 'Don\'t load `.env` file' diff --git a/completions/just.powershell b/completions/just.powershell index 5413bb1..b39d367 100644 --- a/completions/just.powershell +++ b/completions/just.powershell @@ -49,6 +49,7 @@ Register-ArgumentCompleter -Native -CommandName 'just' -ScriptBlock { [CompletionResult]::new('-n', 'n', [CompletionResultType]::ParameterName, 'Print what just would do without doing it') [CompletionResult]::new('--dry-run', 'dry-run', [CompletionResultType]::ParameterName, 'Print what just would do without doing it') [CompletionResult]::new('--highlight', 'highlight', [CompletionResultType]::ParameterName, 'Highlight echoed recipe lines in bold') + [CompletionResult]::new('--list-submodules', 'list-submodules', [CompletionResultType]::ParameterName, 'List recipes in submodules') [CompletionResult]::new('--no-aliases', 'no-aliases', [CompletionResultType]::ParameterName, 'Don''t show aliases in list') [CompletionResult]::new('--no-deps', 'no-deps', [CompletionResultType]::ParameterName, 'Don''t run recipe dependencies') [CompletionResult]::new('--no-dotenv', 'no-dotenv', [CompletionResultType]::ParameterName, 'Don''t load `.env` file') diff --git a/completions/just.zsh b/completions/just.zsh index 1c1fe92..1c7dab0 100644 --- a/completions/just.zsh +++ b/completions/just.zsh @@ -44,6 +44,7 @@ _just() { '(-q --quiet)-n[Print what just would do without doing it]' \ '(-q --quiet)--dry-run[Print what just would do without doing it]' \ '--highlight[Highlight echoed recipe lines in bold]' \ +'--list-submodules[List recipes in submodules]' \ '--no-aliases[Don'\''t show aliases in list]' \ '--no-deps[Don'\''t run recipe dependencies]' \ '--no-dotenv[Don'\''t load \`.env\` file]' \ diff --git a/src/config.rs b/src/config.rs index cfe569c..6c19c70 100644 --- a/src/config.rs +++ b/src/config.rs @@ -32,6 +32,7 @@ pub(crate) struct Config { pub(crate) invocation_directory: PathBuf, pub(crate) list_heading: String, pub(crate) list_prefix: String, + pub(crate) list_submodules: bool, pub(crate) load_dotenv: bool, pub(crate) no_aliases: bool, pub(crate) no_dependencies: bool, @@ -97,11 +98,12 @@ mod arg { pub(crate) const DOTENV_PATH: &str = "DOTENV-PATH"; pub(crate) const DRY_RUN: &str = "DRY-RUN"; pub(crate) const DUMP_FORMAT: &str = "DUMP-FORMAT"; - pub(crate) const GLOBAL_JUSTFILE: &str = "GLOBAL_JUSTFILE"; + pub(crate) const GLOBAL_JUSTFILE: &str = "GLOBAL-JUSTFILE"; pub(crate) const HIGHLIGHT: &str = "HIGHLIGHT"; pub(crate) const JUSTFILE: &str = "JUSTFILE"; pub(crate) const LIST_HEADING: &str = "LIST-HEADING"; pub(crate) const LIST_PREFIX: &str = "LIST-PREFIX"; + pub(crate) const LIST_SUBMODULES: &str = "LIST-SUBMODULES"; pub(crate) const NO_ALIASES: &str = "NO-ALIASES"; pub(crate) const NO_DEPS: &str = "NO-DEPS"; pub(crate) const NO_DOTENV: &str = "NO-DOTENV"; @@ -112,7 +114,7 @@ mod arg { pub(crate) const SHELL_ARG: &str = "SHELL-ARG"; pub(crate) const SHELL_COMMAND: &str = "SHELL-COMMAND"; pub(crate) const TIMESTAMP: &str = "TIMESTAMP"; - pub(crate) const TIMESTAMP_FORMAT: &str = "TIMESTAMP_FORMAT"; + pub(crate) const TIMESTAMP_FORMAT: &str = "TIMESTAMP-FORMAT"; pub(crate) const UNSORTED: &str = "UNSORTED"; pub(crate) const UNSTABLE: &str = "UNSTABLE"; pub(crate) const VERBOSE: &str = "VERBOSE"; @@ -236,6 +238,13 @@ impl Config { .value_name("TEXT") .action(ArgAction::Set), ) + .arg( + Arg::new(arg::LIST_SUBMODULES) + .long("list-submodules") + .help("List recipes in submodules") + .action(ArgAction::SetTrue) + .env("JUST_LIST_SUBMODULES"), + ) .arg( Arg::new(arg::NO_ALIASES) .long("no-aliases") @@ -754,6 +763,7 @@ impl Config { list_prefix: matches .get_one::(arg::LIST_PREFIX) .map_or_else(|| " ".into(), Into::into), + list_submodules: matches.get_flag(arg::LIST_SUBMODULES), load_dotenv: !matches.get_flag(arg::NO_DOTENV), no_aliases: matches.get_flag(arg::NO_ALIASES), no_dependencies: matches.get_flag(arg::NO_DEPS), diff --git a/src/subcommand.rs b/src/subcommand.rs index 09aacbe..77658ac 100644 --- a/src/subcommand.rs +++ b/src/subcommand.rs @@ -488,6 +488,12 @@ impl Subcommand { .ok_or_else(|| Error::UnknownSubmodule { path: path.clone() })?; } + Self::list_module(config, module, 0); + + Ok(()) + } + + fn list_module(config: &Config, module: &Justfile, depth: usize) { let aliases = if config.no_aliases { BTreeMap::new() } else { @@ -532,7 +538,11 @@ impl Subcommand { .max() .unwrap_or(0); - print!("{}", config.list_heading); + let list_prefix = config.list_prefix.repeat(depth + 1); + + if depth == 0 { + print!("{}", config.list_heading); + } let groups = { let mut groups = BTreeMap::, Vec<&Recipe>>::new(); @@ -557,7 +567,7 @@ impl Subcommand { let no_groups = groups.contains_key(&None) && groups.len() == 1; if !no_groups { - print!("{}", config.list_prefix); + print!("{list_prefix}"); if let Some(group_name) = group { println!("[{group_name}]"); } else { @@ -580,8 +590,7 @@ impl Subcommand { if doc.lines().count() > 1 { for line in doc.lines() { println!( - "{}{} {}", - config.list_prefix, + "{list_prefix}{} {}", config.color.stdout().doc().paint("#"), config.color.stdout().doc().paint(line), ); @@ -590,8 +599,7 @@ impl Subcommand { } print!( - "{}{}", - config.list_prefix, + "{list_prefix}{}", RecipeSignature { name, recipe }.color_display(config.color.stdout()) ); @@ -611,11 +619,21 @@ impl Subcommand { } } - for submodule in module.modules(config) { - println!("{}{} ...", config.list_prefix, submodule.name(),); - } + if config.list_submodules { + for (i, submodule) in module.modules(config).into_iter().enumerate() { + if i + groups.len() > 0 { + println!(); + } - Ok(()) + println!("{list_prefix}{}:", submodule.name()); + + Self::list_module(config, submodule, depth + 1); + } + } else { + for submodule in module.modules(config) { + println!("{list_prefix}{} ...", submodule.name(),); + } + } } fn show<'src>( diff --git a/tests/list.rs b/tests/list.rs index e8002b1..7c8a26b 100644 --- a/tests/list.rs +++ b/tests/list.rs @@ -223,3 +223,133 @@ fn list_unknown_submodule() { .status(1) .run(); } + +#[test] +fn list_with_groups_in_modules() { + Test::new() + .justfile( + " + [group('FOO')] + foo: + + mod bar + ", + ) + .write("bar.just", "[group('BAZ')]\nbaz:") + .test_round_trip(false) + .args(["--unstable", "--list", "--list-submodules"]) + .stdout( + " + Available recipes: + [FOO] + foo + + bar: + [BAZ] + baz + ", + ) + .run(); +} + +#[test] +fn list_displays_recipes_in_submodules() { + Test::new() + .write("foo.just", "bar:\n @echo FOO") + .justfile( + " + mod foo + ", + ) + .test_round_trip(false) + .args(["--unstable", "--list", "--list-submodules"]) + .stdout( + " + Available recipes: + foo: + bar + ", + ) + .run(); +} + +#[test] +fn modules_are_space_separated_in_output() { + Test::new() + .write("foo.just", "foo:") + .write("bar.just", "bar:") + .justfile( + " + mod foo + + mod bar + ", + ) + .test_round_trip(false) + .args(["--unstable", "--list", "--list-submodules"]) + .stdout( + " + Available recipes: + bar: + bar + + foo: + foo + ", + ) + .run(); +} + +#[test] +fn module_recipe_list_alignment_ignores_private_recipes() { + Test::new() + .write( + "foo.just", + " +# foos +foo: + @echo FOO + +[private] +barbarbar: + @echo BAR + +@_bazbazbaz: + @echo BAZ + ", + ) + .justfile("mod foo") + .test_round_trip(false) + .args(["--unstable", "--list", "--list-submodules"]) + .stdout( + " + Available recipes: + foo: + foo # foos + ", + ) + .run(); +} + +#[test] +fn nested_modules_are_properly_indented() { + Test::new() + .write("foo.just", "mod bar") + .write("bar.just", "baz:\n @echo FOO") + .justfile( + " + mod foo + ", + ) + .test_round_trip(false) + .args(["--unstable", "--list", "--list-submodules"]) + .stdout( + " + Available recipes: + foo: + bar: + baz + ", + ) + .run(); +} diff --git a/tests/test.rs b/tests/test.rs index 16b3b0e..c57a4c4 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -258,7 +258,7 @@ impl Test { } } - if !compare("status", output.status.code().unwrap(), self.status) + if !compare("status", output.status.code(), Some(self.status)) | (self.stdout_regex.is_none() && !compare("stdout", output_stdout, &stdout)) | (self.stderr_regex.is_none() && !compare("stderr", output_stderr, &stderr)) {