Skip to content

Commit 9a1119c

Browse files
authored
Add [default] attribute (#2878)
1 parent f5ffec5 commit 9a1119c

File tree

7 files changed

+90
-14
lines changed

7 files changed

+90
-14
lines changed

README.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -723,9 +723,12 @@ Features
723723

724724
### The Default Recipe
725725

726-
When `just` is invoked without a recipe, it runs the first recipe in the
727-
`justfile`. This recipe might be the most frequently run command in the
728-
project, like running the tests:
726+
When `just` is invoked without a recipe, it runs the recipe with the
727+
`[default]` attribute, or the first recipe in the `justfile` if no recipe has
728+
the `[default]` attribute.
729+
730+
This recipe might be the most frequently run command in the project, like
731+
running the tests:
729732

730733
```just
731734
test:
@@ -2107,6 +2110,7 @@ change their behavior.
21072110
|------|------|-------------|
21082111
| `[confirm]`<sup>1.17.0</sup> | recipe | Require confirmation prior to executing recipe. |
21092112
| `[confirm(PROMPT)]`<sup>1.23.0</sup> | recipe | Require confirmation prior to executing recipe with a custom prompt. |
2113+
| `[default]`<sup>master</sup> | recipe | Use recipe as module's default recipe. |
21102114
| `[doc(DOC)]`<sup>1.27.0</sup> | module, recipe | Set recipe or module's [documentation comment](#documentation-comments) to `DOC`. |
21112115
| `[extension(EXT)]`<sup>1.32.0</sup> | recipe | Set shebang recipe script's file extension to `EXT`. `EXT` should include a period if one is desired. |
21122116
| `[group(NAME)]`<sup>1.27.0</sup> | module, recipe | Put recipe or module in in [group](#groups) `NAME`. |

src/analyzer.rs

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ impl<'run, 'src> Analyzer<'run, 'src> {
9494
}
9595
Item::Unexport { name } => {
9696
if !self.unexports.insert(name.lexeme().to_string()) {
97-
return Err(name.token.error(DuplicateUnexport {
97+
return Err(name.error(DuplicateUnexport {
9898
variable: name.lexeme(),
9999
}));
100100
}
@@ -112,7 +112,7 @@ impl<'run, 'src> Analyzer<'run, 'src> {
112112
let variable = assignment.name.lexeme();
113113

114114
if !settings.allow_duplicate_variables && assignments.contains_key(variable) {
115-
return Err(assignment.name.token.error(DuplicateVariable { variable }));
115+
return Err(assignment.name.error(DuplicateVariable { variable }));
116116
}
117117

118118
if assignments.get(variable).map_or(true, |original| {
@@ -122,7 +122,7 @@ impl<'run, 'src> Analyzer<'run, 'src> {
122122
}
123123

124124
if self.unexports.contains(variable) {
125-
return Err(assignment.name.token.error(ExportUnexported { variable }));
125+
return Err(assignment.name.error(ExportUnexported { variable }));
126126
}
127127
}
128128

@@ -172,10 +172,21 @@ impl<'run, 'src> Analyzer<'run, 'src> {
172172
let source = root.to_owned();
173173
let root = paths.get(root).unwrap();
174174

175-
Ok(Justfile {
176-
aliases,
177-
assignments,
178-
default: recipes
175+
let mut default = None;
176+
for recipe in recipes.values() {
177+
if recipe.attributes.contains(AttributeDiscriminant::Default) {
178+
if default.is_some() {
179+
return Err(recipe.name.error(CompileErrorKind::DuplicateDefault {
180+
recipe: recipe.name.lexeme(),
181+
}));
182+
}
183+
184+
default = Some(Arc::clone(recipe));
185+
}
186+
}
187+
188+
let default = default.or_else(|| {
189+
recipes
179190
.values()
180191
.filter(|recipe| recipe.name.path == root)
181192
.fold(None, |accumulator, next| match accumulator {
@@ -185,7 +196,13 @@ impl<'run, 'src> Analyzer<'run, 'src> {
185196
} else {
186197
Arc::clone(next)
187198
}),
188-
}),
199+
})
200+
});
201+
202+
Ok(Justfile {
203+
aliases,
204+
assignments,
205+
default,
189206
doc: doc.filter(|doc| !doc.is_empty()),
190207
groups: groups.into(),
191208
loaded: loaded.into(),
@@ -236,7 +253,7 @@ impl<'run, 'src> Analyzer<'run, 'src> {
236253

237254
for parameter in &recipe.parameters {
238255
if parameters.contains(parameter.name.lexeme()) {
239-
return Err(parameter.name.token.error(DuplicateParameter {
256+
return Err(parameter.name.error(DuplicateParameter {
240257
recipe: recipe.name.lexeme(),
241258
parameter: parameter.name.lexeme(),
242259
}));
@@ -304,7 +321,7 @@ impl<'run, 'src> Analyzer<'run, 'src> {
304321
) -> CompileResult<'src, Alias<'src>> {
305322
match Self::resolve_recipe(&alias.target, modules, recipes) {
306323
Some(target) => Ok(alias.resolve(target)),
307-
None => Err(alias.name.token.error(UnknownAliasTarget {
324+
None => Err(alias.name.error(UnknownAliasTarget {
308325
alias: alias.name.lexeme(),
309326
target: alias.target,
310327
})),

src/attribute.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use super::*;
1010
#[strum_discriminants(strum(serialize_all = "kebab-case"))]
1111
pub(crate) enum Attribute<'src> {
1212
Confirm(Option<StringLiteral<'src>>),
13+
Default,
1314
Doc(Option<StringLiteral<'src>>),
1415
ExitMessage,
1516
Extension(StringLiteral<'src>),
@@ -34,7 +35,8 @@ impl AttributeDiscriminant {
3435
fn argument_range(self) -> RangeInclusive<usize> {
3536
match self {
3637
Self::Confirm | Self::Doc => 0..=1,
37-
Self::ExitMessage
38+
Self::Default
39+
| Self::ExitMessage
3840
| Self::Linux
3941
| Self::Macos
4042
| Self::NoCd
@@ -83,6 +85,7 @@ impl<'src> Attribute<'src> {
8385

8486
Ok(match discriminant {
8587
AttributeDiscriminant::Confirm => Self::Confirm(arguments.into_iter().next()),
88+
AttributeDiscriminant::Default => Self::Default,
8689
AttributeDiscriminant::Doc => Self::Doc(arguments.into_iter().next()),
8790
AttributeDiscriminant::ExitMessage => Self::ExitMessage,
8891
AttributeDiscriminant::Extension => Self::Extension(arguments.into_iter().next().unwrap()),
@@ -131,6 +134,7 @@ impl Display for Attribute<'_> {
131134

132135
match self {
133136
Self::Confirm(None)
137+
| Self::Default
134138
| Self::Doc(None)
135139
| Self::ExitMessage
136140
| Self::Linux

src/compile_error.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,10 @@ impl Display for CompileError<'_> {
103103
first.ordinal(),
104104
self.token.line.ordinal(),
105105
),
106+
DuplicateDefault { recipe } => write!(
107+
f,
108+
"Recipe `{recipe}` has duplicate `[default]` attribute, which may only appear once per module",
109+
),
106110
DuplicateParameter { recipe, parameter } => {
107111
write!(f, "Recipe `{recipe}` has duplicate parameter `{parameter}`")
108112
}

src/compile_error_kind.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ pub(crate) enum CompileErrorKind<'src> {
2727
attribute: &'src str,
2828
first: usize,
2929
},
30+
DuplicateDefault {
31+
recipe: &'src str,
32+
},
3033
DuplicateParameter {
3134
recipe: &'src str,
3235
parameter: &'src str,

tests/default.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
use super::*;
2+
3+
#[test]
4+
fn default_attribute_overrides_first_recipe() {
5+
Test::new()
6+
.justfile(
7+
"
8+
foo:
9+
@echo FOO
10+
11+
[default]
12+
bar:
13+
@echo BAR
14+
",
15+
)
16+
.stdout("BAR\n")
17+
.run();
18+
}
19+
20+
#[test]
21+
fn default_attribute_may_only_appear_once_per_justfile() {
22+
Test::new()
23+
.justfile(
24+
"
25+
[default]
26+
foo:
27+
28+
[default]
29+
bar:
30+
",
31+
)
32+
.stderr(
33+
"
34+
error: Recipe `foo` has duplicate `[default]` attribute, which may only appear once per module
35+
——▶ justfile:2:1
36+
37+
2 │ foo:
38+
│ ^^^
39+
"
40+
)
41+
.status(EXIT_FAILURE)
42+
.run();
43+
}

tests/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ mod conditional;
6161
mod confirm;
6262
mod constants;
6363
mod datetime;
64+
mod default;
6465
mod delimiters;
6566
mod dependencies;
6667
mod directories;

0 commit comments

Comments
 (0)