Skip to content

Commit

Permalink
Allow duplicate recipes (#1095)
Browse files Browse the repository at this point in the history
  • Loading branch information
lutostag authored Feb 15, 2022
1 parent 0988a42 commit bcdaa95
Show file tree
Hide file tree
Showing 10 changed files with 197 additions and 41 deletions.
34 changes: 27 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -521,13 +521,14 @@ foo:

#### Table of Settings

| Name | Value | Description |
| ---------------------- | ------------------ | -------------------------------------------------------------- |
| `dotenv-load` | boolean | Load a `.env` file, if present. |
| `export` | boolean | Export all variables as environment variables. |
| `positional-arguments` | boolean | Pass positional arguments. |
| `shell` | `[COMMAND, ARGS…]` | Set the command used to invoke recipes and evaluate backticks. |
| `windows-powershell` | boolean | Use PowerShell on Windows as default shell. |
| Name | Value | Description |
| ------------------------- | ------------------ | --------------------------------------------------------------------------------------------- |
| `allow-duplicate-recipes` | boolean | Allow recipes appearing later in a `justfile` to override earlier recipes with the same name. |
| `dotenv-load` | boolean | Load a `.env` file, if present. |
| `export` | boolean | Export all variables as environment variables. |
| `positional-arguments` | boolean | Pass positional arguments. |
| `shell` | `[COMMAND, ARGS…]` | Set the command used to invoke recipes and evaluate backticks. |
| `windows-powershell` | boolean | Use PowerShell on Windows as default shell. |

Boolean settings can be written as:

Expand All @@ -541,6 +542,25 @@ Which is equivalent to:
set NAME := true
```

#### Allow Duplicate Recipes

If `allow-duplicate-recipes` is set to `true`, defining multiple recipes with the same name is not an error and the last definition is used. Defaults to `false`.

```make
set allow-duplicate-recipes

@foo:
echo foo

@foo:
echo bar
```

```sh
$ just foo
bar
```

#### Dotenv Load

If `dotenv-load` is `true`, a `.env` file will be loaded if present. Defaults to `true`.
Expand Down
72 changes: 41 additions & 31 deletions src/analyzer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ impl<'src> Analyzer<'src> {
}

pub(crate) fn justfile(mut self, ast: Ast<'src>) -> CompileResult<'src, Justfile<'src>> {
let mut recipes = Vec::new();

for item in ast.items {
match item {
Item::Alias(alias) => {
Expand All @@ -28,8 +30,8 @@ impl<'src> Analyzer<'src> {
}
Item::Comment(_) => (),
Item::Recipe(recipe) => {
self.analyze_recipe(&recipe)?;
self.recipes.insert(recipe);
Self::analyze_recipe(&recipe)?;
recipes.push(recipe);
}
Item::Set(set) => {
self.analyze_set(&set)?;
Expand All @@ -38,31 +40,13 @@ impl<'src> Analyzer<'src> {
}
}

let assignments = self.assignments;

AssignmentResolver::resolve_assignments(&assignments)?;

let recipes = RecipeResolver::resolve_recipes(self.recipes, &assignments)?;

for recipe in recipes.values() {
for parameter in &recipe.parameters {
if assignments.contains_key(parameter.name.lexeme()) {
return Err(parameter.name.token().error(ParameterShadowsVariable {
parameter: parameter.name.lexeme(),
}));
}
}
}

let mut aliases = Table::new();
while let Some(alias) = self.aliases.pop() {
aliases.insert(Self::resolve_alias(&recipes, alias)?);
}

let mut settings = Settings::new();

for (_, set) in self.sets {
match set.value {
Setting::AllowDuplicateRecipes(allow_duplicate_recipes) => {
settings.allow_duplicate_recipes = allow_duplicate_recipes;
}
Setting::DotenvLoad(dotenv_load) => {
settings.dotenv_load = Some(dotenv_load);
}
Expand All @@ -82,6 +66,39 @@ impl<'src> Analyzer<'src> {
}
}

let assignments = self.assignments;

AssignmentResolver::resolve_assignments(&assignments)?;

for recipe in recipes {
if let Some(original) = self.recipes.get(recipe.name.lexeme()) {
if !settings.allow_duplicate_recipes {
return Err(recipe.name.token().error(DuplicateRecipe {
recipe: original.name(),
first: original.line_number(),
}));
}
}
self.recipes.insert(recipe);
}

let recipes = RecipeResolver::resolve_recipes(self.recipes, &assignments)?;

for recipe in recipes.values() {
for parameter in &recipe.parameters {
if assignments.contains_key(parameter.name.lexeme()) {
return Err(parameter.name.token().error(ParameterShadowsVariable {
parameter: parameter.name.lexeme(),
}));
}
}
}

let mut aliases = Table::new();
while let Some(alias) = self.aliases.pop() {
aliases.insert(Self::resolve_alias(&recipes, alias)?);
}

Ok(Justfile {
warnings: ast.warnings,
first: recipes
Expand All @@ -101,14 +118,7 @@ impl<'src> Analyzer<'src> {
})
}

fn analyze_recipe(&self, recipe: &UnresolvedRecipe<'src>) -> CompileResult<'src, ()> {
if let Some(original) = self.recipes.get(recipe.name.lexeme()) {
return Err(recipe.name.token().error(DuplicateRecipe {
recipe: original.name(),
first: original.line_number(),
}));
}

fn analyze_recipe(recipe: &UnresolvedRecipe<'src>) -> CompileResult<'src, ()> {
let mut parameters = BTreeSet::new();
let mut passed_default = false;

Expand Down
1 change: 1 addition & 0 deletions src/keyword.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::common::*;
#[strum(serialize_all = "kebab_case")]
pub(crate) enum Keyword {
Alias,
AllowDuplicateRecipes,
DotenvLoad,
Else,
Export,
Expand Down
6 changes: 5 additions & 1 deletion src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,11 @@ impl<'src> Node<'src> for Set<'src> {
set.push_mut(self.name.lexeme().replace('-', "_"));

match &self.value {
DotenvLoad(value) | Export(value) | PositionalArguments(value) | WindowsPowerShell(value) => {
AllowDuplicateRecipes(value)
| DotenvLoad(value)
| Export(value)
| PositionalArguments(value)
| WindowsPowerShell(value) => {
set.push_mut(value.to_string());
}
Shell(setting::Shell { command, arguments }) => {
Expand Down
14 changes: 13 additions & 1 deletion src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -729,7 +729,13 @@ impl<'tokens, 'src> Parser<'tokens, 'src> {
let name = Name::from_identifier(self.presume(Identifier)?);
let lexeme = name.lexeme();

if Keyword::DotenvLoad == lexeme {
if Keyword::AllowDuplicateRecipes == lexeme {
let value = self.parse_set_bool()?;
return Ok(Set {
value: Setting::AllowDuplicateRecipes(value),
name,
});
} else if Keyword::DotenvLoad == lexeme {
let value = self.parse_set_bool()?;
return Ok(Set {
value: Setting::DotenvLoad(value),
Expand Down Expand Up @@ -1714,6 +1720,12 @@ mod tests {
tree: (justfile (set dotenv_load true)),
}

test! {
name: set_allow_duplicate_recipes_implicit,
text: "set allow-duplicate-recipes",
tree: (justfile (set allow_duplicate_recipes true)),
}

test! {
name: set_dotenv_load_true,
text: "set dotenv-load := true",
Expand Down
4 changes: 3 additions & 1 deletion src/setting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::common::*;

#[derive(Debug, Clone)]
pub(crate) enum Setting<'src> {
AllowDuplicateRecipes(bool),
DotenvLoad(bool),
Export(bool),
PositionalArguments(bool),
Expand All @@ -18,7 +19,8 @@ pub(crate) struct Shell<'src> {
impl<'src> Display for Setting<'src> {
fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
match self {
Setting::DotenvLoad(value)
Setting::AllowDuplicateRecipes(value)
| Setting::DotenvLoad(value)
| Setting::Export(value)
| Setting::PositionalArguments(value)
| Setting::WindowsPowerShell(value) => write!(f, "{}", value),
Expand Down
2 changes: 2 additions & 0 deletions src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pub(crate) const WINDOWS_POWERSHELL_ARGS: &[&str] = &["-NoLogo", "-Command"];

#[derive(Debug, PartialEq, Serialize)]
pub(crate) struct Settings<'src> {
pub(crate) allow_duplicate_recipes: bool,
pub(crate) dotenv_load: Option<bool>,
pub(crate) export: bool,
pub(crate) positional_arguments: bool,
Expand All @@ -17,6 +18,7 @@ pub(crate) struct Settings<'src> {
impl<'src> Settings<'src> {
pub(crate) fn new() -> Settings<'src> {
Settings {
allow_duplicate_recipes: false,
dotenv_load: None,
export: false,
positional_arguments: false,
Expand Down
38 changes: 38 additions & 0 deletions tests/allow_duplicate_recipes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
use crate::common::*;

#[test]
fn allow_duplicate_recipes() {
Test::new()
.justfile(
"
b:
echo foo
b:
echo bar
set allow-duplicate-recipes
",
)
.stdout("bar\n")
.stderr("echo bar\n")
.run();
}

#[test]
fn allow_duplicate_recipes_with_args() {
Test::new()
.justfile(
"
b a:
echo foo
b c d:
echo bar {{c}} {{d}}
set allow-duplicate-recipes
",
)
.args(&["b", "one", "two"])
.stdout("bar one two\n")
.stderr("echo bar one two\n")
.run();
}
Loading

0 comments on commit bcdaa95

Please sign in to comment.