Skip to content

Commit 20a852c

Browse files
author
Jacob Woliver
committed
feat: allow groups on submodules (#2243)
1 parent 51a5d47 commit 20a852c

File tree

8 files changed

+420
-69
lines changed

8 files changed

+420
-69
lines changed

src/analyzer.rs

+8-1
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,20 @@ impl<'src> Analyzer<'src> {
1111
pub(crate) fn analyze(
1212
asts: &HashMap<PathBuf, Ast<'src>>,
1313
doc: Option<String>,
14+
groups: &[String],
1415
loaded: &[PathBuf],
1516
name: Option<Name<'src>>,
1617
paths: &HashMap<PathBuf, PathBuf>,
1718
root: &Path,
1819
) -> CompileResult<'src, Justfile<'src>> {
19-
Self::default().justfile(asts, doc, loaded, name, paths, root)
20+
Self::default().justfile(asts, doc, groups, loaded, name, paths, root)
2021
}
2122

2223
fn justfile(
2324
mut self,
2425
asts: &HashMap<PathBuf, Ast<'src>>,
2526
doc: Option<String>,
27+
groups: &[String],
2628
loaded: &[PathBuf],
2729
name: Option<Name<'src>>,
2830
paths: &HashMap<PathBuf, PathBuf>,
@@ -94,9 +96,12 @@ impl<'src> Analyzer<'src> {
9496
..
9597
} => {
9698
let mut doc_attr: Option<&str> = None;
99+
let mut groups = Vec::new();
97100
for attribute in attributes {
98101
if let Attribute::Doc(ref doc) = attribute {
99102
doc_attr = Some(doc.as_ref().map(|s| s.cooked.as_ref()).unwrap_or_default());
103+
} else if let Attribute::Group(ref group) = attribute {
104+
groups.push(group.cooked.clone());
100105
} else {
101106
return Err(name.token.error(InvalidAttribute {
102107
item_kind: "Module",
@@ -111,6 +116,7 @@ impl<'src> Analyzer<'src> {
111116
modules.insert(Self::analyze(
112117
asts,
113118
doc_attr.or(*doc).map(ToOwned::to_owned),
119+
groups.as_slice(),
114120
loaded,
115121
Some(*name),
116122
paths,
@@ -216,6 +222,7 @@ impl<'src> Analyzer<'src> {
216222
}),
217223
}),
218224
doc,
225+
groups: groups.into(),
219226
loaded: loaded.into(),
220227
modules,
221228
name,

src/compiler.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ impl Compiler {
9696
asts.insert(current.path, ast.clone());
9797
}
9898

99-
let justfile = Analyzer::analyze(&asts, None, &loaded, None, &paths, root)?;
99+
let justfile = Analyzer::analyze(&asts, None, &[], &loaded, None, &paths, root)?;
100100

101101
Ok(Compilation {
102102
asts,
@@ -229,7 +229,7 @@ impl Compiler {
229229
asts.insert(root.clone(), ast);
230230
let mut paths: HashMap<PathBuf, PathBuf> = HashMap::new();
231231
paths.insert(root.clone(), root.clone());
232-
Analyzer::analyze(&asts, None, &[], None, &paths, &root)
232+
Analyzer::analyze(&asts, None, &[], &[], None, &paths, &root)
233233
}
234234
}
235235

src/justfile.rs

+13-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ pub(crate) struct Justfile<'src> {
1818
pub(crate) default: Option<Rc<Recipe<'src>>>,
1919
#[serde(skip)]
2020
pub(crate) loaded: Vec<PathBuf>,
21+
pub(crate) groups: Vec<String>,
2122
pub(crate) modules: Table<'src, Justfile<'src>>,
2223
#[serde(skip)]
2324
pub(crate) name: Option<Name<'src>>,
@@ -395,17 +396,28 @@ impl<'src> Justfile<'src> {
395396
recipes
396397
}
397398

399+
pub(crate) fn groups(&self) -> &[String] {
400+
&self.groups
401+
}
402+
398403
pub(crate) fn public_groups(&self, config: &Config) -> Vec<String> {
399404
let mut groups = Vec::new();
400405

401406
for recipe in self.recipes.values() {
402407
if recipe.is_public() {
403408
for group in recipe.groups() {
404-
groups.push((&recipe.import_offsets, recipe.name.offset, group));
409+
groups.push((recipe.import_offsets.as_slice(), recipe.name.offset, group));
405410
}
406411
}
407412
}
408413

414+
for submodule in self.modules.values() {
415+
for group in submodule.groups() {
416+
// submodule.name.unwrap() is safe here because any non-root justfile will have a name set
417+
groups.push((&[], submodule.name.unwrap().offset, group.to_string()));
418+
}
419+
}
420+
409421
if config.unsorted {
410422
groups.sort();
411423
} else {

src/subcommand.rs

+78-58
Original file line numberDiff line numberDiff line change
@@ -520,7 +520,7 @@ impl Subcommand {
520520
print!("{}", config.list_heading);
521521
}
522522

523-
let groups = {
523+
let recipe_groups = {
524524
let mut groups = BTreeMap::<Option<String>, Vec<&Recipe>>::new();
525525
for recipe in module.public_recipes(config) {
526526
let recipe_groups = recipe.groups();
@@ -535,17 +535,40 @@ impl Subcommand {
535535
groups
536536
};
537537

538+
let submodule_groups = {
539+
let mut groups = BTreeMap::<Option<String>, Vec<&Justfile>>::new();
540+
for submodule in module.modules(config) {
541+
let submodule_groups = submodule.groups();
542+
if submodule_groups.is_empty() {
543+
groups.entry(None).or_default().push(submodule);
544+
} else {
545+
for group in submodule_groups {
546+
groups
547+
.entry(Some(group.to_string()))
548+
.or_default()
549+
.push(submodule);
550+
}
551+
}
552+
}
553+
groups
554+
};
555+
556+
// ordered contains groups from both recipes and submodules by calling .public_groups()
538557
let mut ordered = module
539558
.public_groups(config)
540559
.into_iter()
541560
.map(Some)
542561
.collect::<Vec<Option<String>>>();
543562

544-
if groups.contains_key(&None) {
563+
if recipe_groups.contains_key(&None) || submodule_groups.contains_key(&None) {
545564
ordered.insert(0, None);
546565
}
547566

548-
let no_groups = groups.contains_key(&None) && groups.len() == 1;
567+
let no_groups = ordered.len() == 1 && ordered.first() == Some(&None);
568+
let mut groups_count = 0;
569+
if !no_groups {
570+
groups_count = ordered.len();
571+
}
549572

550573
for (i, group) in ordered.into_iter().enumerate() {
551574
if i > 0 {
@@ -561,69 +584,66 @@ impl Subcommand {
561584
}
562585
}
563586

564-
for recipe in groups.get(&group).unwrap() {
565-
for (i, name) in iter::once(&recipe.name())
566-
.chain(aliases.get(recipe.name()).unwrap_or(&Vec::new()))
567-
.enumerate()
568-
{
569-
let doc = if i == 0 {
570-
recipe.doc().map(Cow::Borrowed)
571-
} else {
572-
Some(Cow::Owned(format!("alias for `{}`", recipe.name)))
573-
};
574-
575-
if let Some(doc) = &doc {
576-
if doc.lines().count() > 1 {
577-
for line in doc.lines() {
578-
println!(
579-
"{list_prefix}{} {}",
580-
config.color.stdout().doc().paint("#"),
581-
config.color.stdout().doc().paint(line),
582-
);
587+
if let Some(recipes) = recipe_groups.get(&group) {
588+
for recipe in recipes {
589+
for (i, name) in iter::once(&recipe.name())
590+
.chain(aliases.get(recipe.name()).unwrap_or(&Vec::new()))
591+
.enumerate()
592+
{
593+
let doc = if i == 0 {
594+
recipe.doc().map(Cow::Borrowed)
595+
} else {
596+
Some(Cow::Owned(format!("alias for `{}`", recipe.name)))
597+
};
598+
599+
if let Some(doc) = &doc {
600+
if doc.lines().count() > 1 {
601+
for line in doc.lines() {
602+
println!(
603+
"{list_prefix}{} {}",
604+
config.color.stdout().doc().paint("#"),
605+
config.color.stdout().doc().paint(line),
606+
);
607+
}
583608
}
584609
}
585-
}
586-
587-
print!(
588-
"{list_prefix}{}",
589-
RecipeSignature { name, recipe }.color_display(config.color.stdout())
590-
);
591610

592-
format_doc(
593-
config,
594-
name,
595-
doc.as_deref(),
596-
max_signature_width,
597-
&signature_widths,
598-
);
611+
print!(
612+
"{list_prefix}{}",
613+
RecipeSignature { name, recipe }.color_display(config.color.stdout())
614+
);
615+
616+
format_doc(
617+
config,
618+
name,
619+
doc.as_deref(),
620+
max_signature_width,
621+
&signature_widths,
622+
);
623+
}
599624
}
600625
}
601-
}
602-
603-
if config.list_submodules {
604-
for (i, submodule) in module.modules(config).into_iter().enumerate() {
605-
if i + groups.len() > 0 {
606-
println!();
607-
}
608626

609-
println!("{list_prefix}{}:", submodule.name());
627+
if let Some(submodules) = submodule_groups.get(&group) {
628+
for (i, submodule) in submodules.iter().enumerate() {
629+
if config.list_submodules {
630+
if no_groups && (i + groups_count > 0) {
631+
println!();
632+
}
633+
println!("{list_prefix}{}:", submodule.name());
610634

611-
Self::list_module(config, submodule, depth + 1);
612-
}
613-
} else {
614-
for (i, submodule) in module.modules(config).into_iter().enumerate() {
615-
if !no_groups && !groups.is_empty() && i == 0 {
616-
println!();
635+
Self::list_module(config, submodule, depth + 1);
636+
} else {
637+
print!("{list_prefix}{} ...", submodule.name());
638+
format_doc(
639+
config,
640+
submodule.name(),
641+
submodule.doc.as_deref(),
642+
max_signature_width,
643+
&signature_widths,
644+
);
645+
}
617646
}
618-
619-
print!("{list_prefix}{} ...", submodule.name());
620-
format_doc(
621-
config,
622-
submodule.name(),
623-
submodule.doc.as_deref(),
624-
max_signature_width,
625-
&signature_widths,
626-
);
627647
}
628648
}
629649
}

src/testing.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ pub(crate) fn analysis_error(
7777
let mut paths: HashMap<PathBuf, PathBuf> = HashMap::new();
7878
paths.insert("justfile".into(), "justfile".into());
7979

80-
match Analyzer::analyze(&asts, None, &[], None, &paths, &root) {
80+
match Analyzer::analyze(&asts, None, &[], &[], None, &paths, &root) {
8181
Ok(_) => panic!("Analysis unexpectedly succeeded"),
8282
Err(have) => {
8383
let want = CompileError {

0 commit comments

Comments
 (0)