Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -723,9 +723,12 @@ Features

### The Default Recipe

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

This recipe might be the most frequently run command in the project, like
running the tests:

```just
test:
Expand Down Expand Up @@ -2107,6 +2110,7 @@ change their behavior.
|------|------|-------------|
| `[confirm]`<sup>1.17.0</sup> | recipe | Require confirmation prior to executing recipe. |
| `[confirm(PROMPT)]`<sup>1.23.0</sup> | recipe | Require confirmation prior to executing recipe with a custom prompt. |
| `[default]`<sup>master</sup> | recipe | Use recipe as module's default recipe. |
| `[doc(DOC)]`<sup>1.27.0</sup> | module, recipe | Set recipe or module's [documentation comment](#documentation-comments) to `DOC`. |
| `[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. |
| `[group(NAME)]`<sup>1.27.0</sup> | module, recipe | Put recipe or module in in [group](#groups) `NAME`. |
Expand Down
37 changes: 27 additions & 10 deletions src/analyzer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ impl<'run, 'src> Analyzer<'run, 'src> {
}
Item::Unexport { name } => {
if !self.unexports.insert(name.lexeme().to_string()) {
return Err(name.token.error(DuplicateUnexport {
return Err(name.error(DuplicateUnexport {
variable: name.lexeme(),
}));
}
Expand All @@ -112,7 +112,7 @@ impl<'run, 'src> Analyzer<'run, 'src> {
let variable = assignment.name.lexeme();

if !settings.allow_duplicate_variables && assignments.contains_key(variable) {
return Err(assignment.name.token.error(DuplicateVariable { variable }));
return Err(assignment.name.error(DuplicateVariable { variable }));
}

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

if self.unexports.contains(variable) {
return Err(assignment.name.token.error(ExportUnexported { variable }));
return Err(assignment.name.error(ExportUnexported { variable }));
}
}

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

Ok(Justfile {
aliases,
assignments,
default: recipes
let mut default = None;
for recipe in recipes.values() {
if recipe.attributes.contains(AttributeDiscriminant::Default) {
if default.is_some() {
return Err(recipe.name.error(CompileErrorKind::DuplicateDefault {
recipe: recipe.name.lexeme(),
}));
}

default = Some(Arc::clone(recipe));
}
}

let default = default.or_else(|| {
recipes
.values()
.filter(|recipe| recipe.name.path == root)
.fold(None, |accumulator, next| match accumulator {
Expand All @@ -185,7 +196,13 @@ impl<'run, 'src> Analyzer<'run, 'src> {
} else {
Arc::clone(next)
}),
}),
})
});

Ok(Justfile {
aliases,
assignments,
default,
doc: doc.filter(|doc| !doc.is_empty()),
groups: groups.into(),
loaded: loaded.into(),
Expand Down Expand Up @@ -236,7 +253,7 @@ impl<'run, 'src> Analyzer<'run, 'src> {

for parameter in &recipe.parameters {
if parameters.contains(parameter.name.lexeme()) {
return Err(parameter.name.token.error(DuplicateParameter {
return Err(parameter.name.error(DuplicateParameter {
recipe: recipe.name.lexeme(),
parameter: parameter.name.lexeme(),
}));
Expand Down Expand Up @@ -304,7 +321,7 @@ impl<'run, 'src> Analyzer<'run, 'src> {
) -> CompileResult<'src, Alias<'src>> {
match Self::resolve_recipe(&alias.target, modules, recipes) {
Some(target) => Ok(alias.resolve(target)),
None => Err(alias.name.token.error(UnknownAliasTarget {
None => Err(alias.name.error(UnknownAliasTarget {
alias: alias.name.lexeme(),
target: alias.target,
})),
Expand Down
6 changes: 5 additions & 1 deletion src/attribute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use super::*;
#[strum_discriminants(strum(serialize_all = "kebab-case"))]
pub(crate) enum Attribute<'src> {
Confirm(Option<StringLiteral<'src>>),
Default,
Doc(Option<StringLiteral<'src>>),
ExitMessage,
Extension(StringLiteral<'src>),
Expand All @@ -34,7 +35,8 @@ impl AttributeDiscriminant {
fn argument_range(self) -> RangeInclusive<usize> {
match self {
Self::Confirm | Self::Doc => 0..=1,
Self::ExitMessage
Self::Default
| Self::ExitMessage
| Self::Linux
| Self::Macos
| Self::NoCd
Expand Down Expand Up @@ -83,6 +85,7 @@ impl<'src> Attribute<'src> {

Ok(match discriminant {
AttributeDiscriminant::Confirm => Self::Confirm(arguments.into_iter().next()),
AttributeDiscriminant::Default => Self::Default,
AttributeDiscriminant::Doc => Self::Doc(arguments.into_iter().next()),
AttributeDiscriminant::ExitMessage => Self::ExitMessage,
AttributeDiscriminant::Extension => Self::Extension(arguments.into_iter().next().unwrap()),
Expand Down Expand Up @@ -131,6 +134,7 @@ impl Display for Attribute<'_> {

match self {
Self::Confirm(None)
| Self::Default
| Self::Doc(None)
| Self::ExitMessage
| Self::Linux
Expand Down
4 changes: 4 additions & 0 deletions src/compile_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@ impl Display for CompileError<'_> {
first.ordinal(),
self.token.line.ordinal(),
),
DuplicateDefault { recipe } => write!(
f,
"Recipe `{recipe}` has duplicate `[default]` attribute, which may only appear once per module",
),
DuplicateParameter { recipe, parameter } => {
write!(f, "Recipe `{recipe}` has duplicate parameter `{parameter}`")
}
Expand Down
3 changes: 3 additions & 0 deletions src/compile_error_kind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ pub(crate) enum CompileErrorKind<'src> {
attribute: &'src str,
first: usize,
},
DuplicateDefault {
recipe: &'src str,
},
DuplicateParameter {
recipe: &'src str,
parameter: &'src str,
Expand Down
43 changes: 43 additions & 0 deletions tests/default.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use super::*;

#[test]
fn default_attribute_overrides_first_recipe() {
Test::new()
.justfile(
"
foo:
@echo FOO

[default]
bar:
@echo BAR
",
)
.stdout("BAR\n")
.run();
}

#[test]
fn default_attribute_may_only_appear_once_per_justfile() {
Test::new()
.justfile(
"
[default]
foo:

[default]
bar:
",
)
.stderr(
"
error: Recipe `foo` has duplicate `[default]` attribute, which may only appear once per module
——▶ justfile:2:1
2 │ foo:
│ ^^^
"
)
.status(EXIT_FAILURE)
.run();
}
1 change: 1 addition & 0 deletions tests/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ mod conditional;
mod confirm;
mod constants;
mod datetime;
mod default;
mod delimiters;
mod dependencies;
mod directories;
Expand Down
Loading