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 @@ -519,13 +519,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 duplicate recipes (last definition is used). |
lutostag marked this conversation as resolved.
Show resolved Hide resolved
| `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 @@ -539,6 +540,25 @@ Which is equivalent to:
set NAME := true
```

#### Allow Duplicate Recipes

If `allow-duplicate-recipes` is `true`, recipes can be redefined and the last definition of duplicate recipes will be used. Defaults to `false`.
lutostag 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
69 changes: 41 additions & 28 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,41 @@ 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 {
self.recipes.remove(recipe.name.lexeme());
} else {
return Err(recipe.name.token().error(DuplicateRecipe {
recipe: original.name(),
first: original.line_number(),
}));
}
}
lutostag marked this conversation as resolved.
Show resolved Hide resolved
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,12 +121,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(),
}));
}

lutostag marked this conversation as resolved.
Show resolved Hide resolved
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
59 changes: 59 additions & 0 deletions tests/json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ fn alias() {
}
},
"settings": {
"allow_duplicate_recipes": false,
"dotenv_load": null,
"export": false,
"positional_arguments": false,
Expand Down Expand Up @@ -66,6 +67,7 @@ fn assignment() {
"first": null,
"recipes": {},
"settings": {
"allow_duplicate_recipes": false,
"dotenv_load": null,
"export": false,
"positional_arguments": false,
Expand Down Expand Up @@ -106,6 +108,7 @@ fn body() {
}
},
"settings": {
"allow_duplicate_recipes": false,
"dotenv_load": null,
"export": false,
"positional_arguments": false,
Expand Down Expand Up @@ -156,6 +159,7 @@ fn dependencies() {
}
},
"settings": {
"allow_duplicate_recipes": false,
"dotenv_load": null,
"export": false,
"positional_arguments": false,
Expand Down Expand Up @@ -243,6 +247,52 @@ fn dependency_argument() {
}
},
"settings": {
"allow_duplicate_recipes": false,
"dotenv_load": null,
"export": false,
"positional_arguments": false,
"shell": null,
"windows_powershell": false,
},
"warnings": [],
}),
);
}

#[test]
fn duplicate_recipes() {
test(
"
set allow-duplicate-recipes
alias f := foo

foo:
foo:
lutostag marked this conversation as resolved.
Show resolved Hide resolved
",
json!({
"first": "foo",
"aliases": {
"f": {
"name": "f",
"target": "foo",
}
},
"assignments": {},
"recipes": {
"foo": {
"body": [],
"dependencies": [],
"doc": null,
"name": "foo",
"parameters": [],
"priors": 0,
"private": false,
"quiet": false,
"shebang": false,
}
},
"settings": {
"allow_duplicate_recipes": true,
"dotenv_load": null,
"export": false,
"positional_arguments": false,
Expand Down Expand Up @@ -276,6 +326,7 @@ fn doc_comment() {
}
},
"settings": {
"allow_duplicate_recipes": false,
"dotenv_load": null,
"export": false,
"positional_arguments": false,
Expand All @@ -297,6 +348,7 @@ fn empty_justfile() {
"first": null,
"recipes": {},
"settings": {
"allow_duplicate_recipes": false,
"dotenv_load": null,
"export": false,
"positional_arguments": false,
Expand Down Expand Up @@ -427,6 +479,7 @@ fn parameters() {
},
},
"settings": {
"allow_duplicate_recipes": false,
"dotenv_load": null,
"export": false,
"positional_arguments": false,
Expand Down Expand Up @@ -496,6 +549,7 @@ fn priors() {
},
},
"settings": {
"allow_duplicate_recipes": false,
"dotenv_load": null,
"export": false,
"positional_arguments": false,
Expand Down Expand Up @@ -529,6 +583,7 @@ fn private() {
}
},
"settings": {
"allow_duplicate_recipes": false,
"dotenv_load": null,
"export": false,
"positional_arguments": false,
Expand Down Expand Up @@ -562,6 +617,7 @@ fn quiet() {
}
},
"settings": {
"allow_duplicate_recipes": false,
"dotenv_load": null,
"export": false,
"positional_arguments": false,
Expand Down Expand Up @@ -613,6 +669,7 @@ fn settings() {
}
},
"settings": {
"allow_duplicate_recipes": false,
"dotenv_load": true,
"export": true,
"positional_arguments": true,
Expand Down Expand Up @@ -652,6 +709,7 @@ fn shebang() {
}
},
"settings": {
"allow_duplicate_recipes": false,
"dotenv_load": null,
"export": false,
"positional_arguments": false,
Expand Down Expand Up @@ -685,6 +743,7 @@ fn simple() {
}
},
"settings": {
"allow_duplicate_recipes": false,
"dotenv_load": null,
"export": false,
"positional_arguments": false,
Expand Down
Loading