diff --git a/README.md b/README.md index 5253c8e..d369682 100644 --- a/README.md +++ b/README.md @@ -1861,6 +1861,8 @@ Recipe groups: rust recipes ``` +Use `just --groups --unsorted` to print groups in their justfile order. + ### Command Evaluation Using Backticks Backticks can be used to store the result of commands: diff --git a/src/assignment.rs b/src/assignment.rs index 6dab1a9..eb50f51 100644 --- a/src/assignment.rs +++ b/src/assignment.rs @@ -4,7 +4,7 @@ use super::*; pub(crate) type Assignment<'src> = Binding<'src, Expression<'src>>; impl<'src> Display for Assignment<'src> { - fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { if self.export { write!(f, "export ")?; } diff --git a/src/ast.rs b/src/ast.rs index 18c3e45..f2e0122 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -12,7 +12,7 @@ pub(crate) struct Ast<'src> { } impl<'src> Display for Ast<'src> { - fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { let mut iter = self.items.iter().peekable(); while let Some(item) = iter.next() { diff --git a/src/attribute.rs b/src/attribute.rs index 5e83891..5176364 100644 --- a/src/attribute.rs +++ b/src/attribute.rs @@ -110,7 +110,7 @@ impl<'src> Attribute<'src> { } impl<'src> Display for Attribute<'src> { - fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { write!(f, "{}", self.name())?; if let Some(argument) = self.argument() { write!(f, "({argument})")?; diff --git a/src/compile_error.rs b/src/compile_error.rs index b60835c..6d22704 100644 --- a/src/compile_error.rs +++ b/src/compile_error.rs @@ -28,7 +28,7 @@ fn capitalize(s: &str) -> String { } impl Display for CompileError<'_> { - fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { use CompileErrorKind::*; match &*self.kind { diff --git a/src/condition.rs b/src/condition.rs index a103a2a..8c2a8ad 100644 --- a/src/condition.rs +++ b/src/condition.rs @@ -8,7 +8,7 @@ pub(crate) struct Condition<'src> { } impl<'src> Display for Condition<'src> { - fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { write!(f, "{} {} {}", self.lhs, self.operator, self.rhs) } } diff --git a/src/dependency.rs b/src/dependency.rs index 2d1da11..5f6dce5 100644 --- a/src/dependency.rs +++ b/src/dependency.rs @@ -8,7 +8,7 @@ pub(crate) struct Dependency<'src> { } impl<'src> Display for Dependency<'src> { - fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { if self.arguments.is_empty() { write!(f, "{}", self.recipe.name()) } else { diff --git a/src/expression.rs b/src/expression.rs index 202fafe..c1e4b5e 100644 --- a/src/expression.rs +++ b/src/expression.rs @@ -51,7 +51,7 @@ impl<'src> Expression<'src> { } impl<'src> Display for Expression<'src> { - fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { Self::Assert { condition, error } => write!(f, "assert({condition}, {error})"), Self::Backtick { token, .. } => write!(f, "{}", token.lexeme()), diff --git a/src/justfile.rs b/src/justfile.rs index e108eb9..77e089c 100644 --- a/src/justfile.rs +++ b/src/justfile.rs @@ -365,13 +365,13 @@ impl<'src> Justfile<'src> { modules } - pub(crate) fn public_recipes(&self, config: &Config) -> Vec<&Recipe<'src, Dependency>> { + pub(crate) fn public_recipes(&self, config: &Config) -> Vec<&Recipe> { let mut recipes = self .recipes .values() .map(AsRef::as_ref) .filter(|recipe| recipe.is_public()) - .collect::>>(); + .collect::>(); if config.unsorted { recipes.sort_by_key(|recipe| (&recipe.import_offsets, recipe.name.offset)); @@ -380,19 +380,33 @@ impl<'src> Justfile<'src> { recipes } - pub(crate) fn public_groups(&self) -> BTreeSet { - self - .recipes - .values() - .map(AsRef::as_ref) - .filter(|recipe| recipe.is_public()) - .flat_map(Recipe::groups) - .collect() + pub(crate) fn public_groups(&self, config: &Config) -> Vec { + let mut groups = Vec::new(); + + for recipe in self.recipes.values() { + if recipe.is_public() { + for group in recipe.groups() { + groups.push((&recipe.import_offsets, recipe.name.offset, group)); + } + } + } + + if config.unsorted { + groups.sort(); + } else { + groups.sort_by(|(_, _, a), (_, _, b)| a.cmp(b)); + } + + let mut seen = HashSet::new(); + + groups.retain(|(_, _, group)| seen.insert(group.clone())); + + groups.into_iter().map(|(_, _, group)| group).collect() } } impl<'src> ColorDisplay for Justfile<'src> { - fn fmt(&self, f: &mut Formatter, color: Color) -> Result<(), fmt::Error> { + fn fmt(&self, f: &mut Formatter, color: Color) -> fmt::Result { let mut items = self.recipes.len() + self.assignments.len() + self.aliases.len(); for (name, assignment) in &self.assignments { if assignment.export { diff --git a/src/output_error.rs b/src/output_error.rs index 797d3a1..b1a1655 100644 --- a/src/output_error.rs +++ b/src/output_error.rs @@ -15,7 +15,7 @@ pub(crate) enum OutputError { } impl Display for OutputError { - fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { match *self { Self::Code(code) => write!(f, "Process exited with status code {code}"), Self::Io(ref io_error) => write!(f, "Error executing process: {io_error}"), diff --git a/src/parameter.rs b/src/parameter.rs index e065534..359e15d 100644 --- a/src/parameter.rs +++ b/src/parameter.rs @@ -14,7 +14,7 @@ pub(crate) struct Parameter<'src> { } impl<'src> ColorDisplay for Parameter<'src> { - fn fmt(&self, f: &mut Formatter, color: Color) -> Result<(), fmt::Error> { + fn fmt(&self, f: &mut Formatter, color: Color) -> fmt::Result { if let Some(prefix) = self.kind.prefix() { write!(f, "{}", color.annotation().paint(prefix))?; } diff --git a/src/recipe.rs b/src/recipe.rs index e27cc2e..a41ce93 100644 --- a/src/recipe.rs +++ b/src/recipe.rs @@ -476,7 +476,7 @@ impl<'src, D> Recipe<'src, D> { } impl<'src, D: Display> ColorDisplay for Recipe<'src, D> { - fn fmt(&self, f: &mut Formatter, color: Color) -> Result<(), fmt::Error> { + fn fmt(&self, f: &mut Formatter, color: Color) -> fmt::Result { if let Some(doc) = self.doc { writeln!(f, "# {doc}")?; } diff --git a/src/set.rs b/src/set.rs index 45e5808..5958578 100644 --- a/src/set.rs +++ b/src/set.rs @@ -13,7 +13,7 @@ impl<'src> Keyed<'src> for Set<'src> { } impl<'src> Display for Set<'src> { - fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { write!(f, "set {} := {}", self.name, self.value) } } diff --git a/src/setting.rs b/src/setting.rs index 8bf98f2..e6cb515 100644 --- a/src/setting.rs +++ b/src/setting.rs @@ -20,7 +20,7 @@ pub(crate) enum Setting<'src> { } impl<'src> Display for Setting<'src> { - fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { Self::AllowDuplicateRecipes(value) | Self::AllowDuplicateVariables(value) diff --git a/src/shell.rs b/src/shell.rs index e6430f6..f637a7e 100644 --- a/src/shell.rs +++ b/src/shell.rs @@ -7,7 +7,7 @@ pub(crate) struct Shell<'src> { } impl<'src> Display for Shell<'src> { - fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { write!(f, "[{}", self.command)?; for argument in &self.arguments { diff --git a/src/subcommand.rs b/src/subcommand.rs index 68b3109..a595249 100644 --- a/src/subcommand.rs +++ b/src/subcommand.rs @@ -97,7 +97,7 @@ impl Subcommand { fn groups(config: &Config, justfile: &Justfile) { println!("Recipe groups:"); - for group in justfile.public_groups() { + for group in justfile.public_groups(config) { println!("{}{group}", config.list_prefix); } } @@ -215,7 +215,7 @@ impl Subcommand { overrides: &BTreeMap, chooser: Option<&str>, ) -> Result<(), Error<'src>> { - let mut recipes = Vec::<&Recipe>::new(); + let mut recipes = Vec::<&Recipe>::new(); let mut stack = vec![justfile]; while let Some(module) = stack.pop() { recipes.extend( diff --git a/src/token_kind.rs b/src/token_kind.rs index bb91589..0db15d2 100644 --- a/src/token_kind.rs +++ b/src/token_kind.rs @@ -39,7 +39,7 @@ pub(crate) enum TokenKind { } impl Display for TokenKind { - fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { use TokenKind::*; write!( f, diff --git a/src/unresolved_dependency.rs b/src/unresolved_dependency.rs index 97f69d6..3ea49a7 100644 --- a/src/unresolved_dependency.rs +++ b/src/unresolved_dependency.rs @@ -7,7 +7,7 @@ pub(crate) struct UnresolvedDependency<'src> { } impl<'src> Display for UnresolvedDependency<'src> { - fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { if self.arguments.is_empty() { write!(f, "{}", self.recipe) } else { diff --git a/tests/groups.rs b/tests/groups.rs index 5834333..0812852 100644 --- a/tests/groups.rs +++ b/tests/groups.rs @@ -157,12 +157,89 @@ fn list_groups_with_shorthand_syntax() { bar: ", ) - .args(["--groups", "--list-prefix", "..."]) + .arg("--groups") .stdout( " Recipe groups: - ...A - ...B + A + B + ", + ) + .run(); +} + +#[test] +fn list_groups_unsorted() { + Test::new() + .justfile( + " + [group: 'Z'] + baz: + + [group: 'B'] + foo: + + [group: 'A', group: 'B'] + bar: + ", + ) + .args(["--groups", "--unsorted"]) + .stdout( + " + Recipe groups: + Z + B + A + ", + ) + .run(); +} + +#[test] +fn list_groups_private_unsorted() { + Test::new() + .justfile( + " + [private] + [group: 'A'] + foo: + + [group: 'B'] + bar: + + [group: 'A'] + baz: + ", + ) + .args(["--groups", "--unsorted"]) + .stdout( + " + Recipe groups: + B + A + ", + ) + .run(); +} + +#[test] +fn list_groups_private() { + Test::new() + .justfile( + " + [private] + [group: 'A'] + foo: + + [group: 'B'] + bar: + ", + ) + .args(["--groups", "--unsorted"]) + .stdout( + " + Recipe groups: + B ", ) .run();