Repository: casey/just (rust)

Issue:
Improve `--list` output with modules
Collecting feedback from #929.

To sum up:
1. [ ] `--list` output with modules is too verbose and nested
2. [x] It should be possible to do `--list SUBMODULE` and only see `--list` output for that submodule, and iteratively descend into submodules with

For 1, I think I agree. The current output is very verbose and gets unwieldy quickly if there are a lot of submodules, and gets quite nested if the module hierarchy is deep. I think that, by default, `--list` should not print out the recipes in submodules, but only the submodules themselves.

So something like:

```
$ just --list
Available recipes:
  foo
  bar

Available submodules:
  baz
  bob
```

If there are no recipes at the top level, it would just be:

```
$ just --list
Available submodules:
  baz
  bob
```

I'm undecided about whether to completely remove the nested output, or require `--verbose` or `--nested` or whatever to get it.

For 2, I'm working on a patch that allows `--list` to take a module path, which are space- or `::`- separated module names, and will descend into that module, and only print the `--list` output for that module.

@gl-yziquel and @valscion what do you think?

Pull request description:
Don't display submodule recipes in `--list`
Fixes #2107.

Patch:
diff --git a/src/subcommand.rs b/src/subcommand.rs
index 9cf24e3d..c2ec213c 100644
--- a/src/subcommand.rs
+++ b/src/subcommand.rs
@@ -90,7 +90,7 @@ impl Subcommand {
       Dump => Self::dump(config, ast, justfile)?,
       Format => Self::format(config, &search, src, ast)?,
       Groups => Self::groups(config, justfile),
-      List { path } => Self::list_module(config, justfile, path)?,
+      List { path } => Self::list(config, justfile, path)?,
       Show { path } => Self::show(config, justfile, path)?,
       Summary => Self::summary(config, justfile),
       Variables => Self::variables(justfile),
@@ -479,11 +479,7 @@ impl Subcommand {
     Ok(())
   }
 
-  fn list_module(
-    config: &Config,
-    mut module: &Justfile,
-    path: &ModulePath,
-  ) -> Result<(), Error<'static>> {
+  fn list(config: &Config, mut module: &Justfile, path: &ModulePath) -> Result<(), Error<'static>> {
     for name in &path.path {
       module = module
         .modules
@@ -491,21 +487,11 @@ impl Subcommand {
         .ok_or_else(|| Error::UnknownSubmodule { path: path.clone() })?;
     }
 
-    Self::list(config, 0, module);
-
-    Ok(())
-  }
-
-  fn list(config: &Config, level: usize, justfile: &Justfile) {
     let aliases = if config.no_aliases {
       BTreeMap::new()
     } else {
       let mut aliases = BTreeMap::<&str, Vec<&str>>::new();
-      for alias in justfile
-        .aliases
-        .values()
-        .filter(|alias| !alias.is_private())
-      {
+      for alias in module.aliases.values().filter(|alias| !alias.is_private()) {
         aliases
           .entry(alias.target.name.lexeme())
           .or_default()
@@ -517,7 +503,7 @@ impl Subcommand {
     let signature_widths = {
       let mut signature_widths: BTreeMap<&str, usize> = BTreeMap::new();
 
-      for (name, recipe) in &justfile.recipes {
+      for (name, recipe) in &module.recipes {
         if !recipe.is_public() {
           continue;
         }
@@ -545,13 +531,11 @@ impl Subcommand {
       .max()
       .unwrap_or(0);
 
-    if level == 0 {
-      print!("{}", config.list_heading);
-    }
+    print!("{}", config.list_heading);
 
     let groups = {
       let mut groups = BTreeMap::<Option<String>, Vec<&Recipe>>::new();
-      for recipe in justfile.public_recipes(config) {
+      for recipe in module.public_recipes(config) {
         let recipe_groups = recipe.groups();
         if recipe_groups.is_empty() {
           groups.entry(None).or_default().push(recipe);
@@ -572,7 +556,7 @@ impl Subcommand {
       let no_groups = groups.contains_key(&None) && groups.len() == 1;
 
       if !no_groups {
-        print!("{}", config.list_prefix.repeat(level + 1));
+        print!("{}", config.list_prefix);
         if let Some(group_name) = group {
           println!("[{group_name}]");
         } else {
@@ -596,7 +580,7 @@ impl Subcommand {
               for line in doc.lines() {
                 println!(
                   "{}{} {}",
-                  config.list_prefix.repeat(level + 1),
+                  config.list_prefix,
                   config.color.stdout().doc().paint("#"),
                   config.color.stdout().doc().paint(line),
                 );
@@ -606,7 +590,7 @@ impl Subcommand {
 
           print!(
             "{}{}",
-            config.list_prefix.repeat(level + 1),
+            config.list_prefix,
             RecipeSignature { name, recipe }.color_display(config.color.stdout())
           );
 
@@ -626,14 +610,11 @@ impl Subcommand {
       }
     }
 
-    for (i, module) in justfile.modules(config).into_iter().enumerate() {
-      if i + groups.len() > 0 {
-        println!();
-      }
-
-      println!("{}{}:", config.list_prefix.repeat(level + 1), module.name());
-      Self::list(config, level + 1, module);
+    for submodule in module.modules(config) {
+      println!("{}{} ...", config.list_prefix, submodule.name(),);
     }
+
+    Ok(())
   }
 
   fn show<'src>(


Test patch:
diff --git a/tests/groups.rs b/tests/groups.rs
index 630d780f..6c072e86 100644
--- a/tests/groups.rs
+++ b/tests/groups.rs
@@ -144,31 +144,3 @@ fn list_groups_with_custom_prefix() {
     )
     .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"])
-    .stdout(
-      "
-        Available recipes:
-            [FOO]
-            foo
-
-            bar:
-                [BAZ]
-                baz
-      ",
-    )
-    .run();
-}
diff --git a/tests/list.rs b/tests/list.rs
index 7ee5d46d..e8002b13 100644
--- a/tests/list.rs
+++ b/tests/list.rs
@@ -1,54 +1,5 @@
 use super::*;
 
-#[test]
-fn list_displays_recipes_in_submodules() {
-  Test::new()
-    .write("foo.just", "bar:\n @echo FOO")
-    .justfile(
-      "
-        mod foo
-      ",
-    )
-    .test_round_trip(false)
-    .arg("--unstable")
-    .arg("--list")
-    .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"])
-    .stdout(
-      "
-      Available recipes:
-          bar:
-              bar
-
-          foo:
-              foo
-    ",
-    )
-    .run();
-}
-
 #[test]
 fn modules_unsorted() {
   Test::new()
@@ -66,72 +17,13 @@ fn modules_unsorted() {
     .stdout(
       "
         Available recipes:
-            foo:
-                foo
-
-            bar:
-                bar
-      ",
-    )
-    .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)
-    .arg("--unstable")
-    .arg("--list")
-    .stdout(
-      "
-        Available recipes:
-            foo:
-                foo # foos
+            foo ...
+            bar ...
       ",
     )
     .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)
-    .arg("--unstable")
-    .arg("--list")
-    .stdout(
-      "
-      Available recipes:
-          foo:
-              bar:
-                  baz
-    ",
-    )
-    .run();
-}
-
 #[test]
 fn unsorted_list_order() {
   Test::new()