Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow duplicate recipes #1095

Merged
merged 14 commits into from
Feb 15, 2022
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 `true`, defining multiple recipes with the same name is not an error, and the last definition is used. Defaults to `false`.
casey marked this conversation as resolved.
Show resolved Hide resolved

```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
68 changes: 39 additions & 29 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 @@ -29,7 +31,7 @@ impl<'src> Analyzer<'src> {
Item::Comment(_) => (),
Item::Recipe(recipe) => {
self.analyze_recipe(&recipe)?;
self.recipes.insert(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 @@ -102,13 +119,6 @@ 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(),
}));
}

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
2 changes: 1 addition & 1 deletion src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ 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