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 passing arguments to dependencies #555

Merged
merged 13 commits into from
Dec 7, 2019
5 changes: 3 additions & 2 deletions GRAMMAR.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,13 @@ string : STRING
sequence : expression ',' sequence
| expression ','?

recipe : '@'? NAME parameter* ('+' parameter)? ':' dependencies? body?
recipe : '@'? NAME parameter* ('+' parameter)? ':' dependency* body?

parameter : NAME
| NAME '=' value

dependencies : NAME+
dependency : NAME
| '(' NAME expression* ')

body : INDENT line+ DEDENT

Expand Down
14 changes: 11 additions & 3 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -547,16 +547,24 @@ build target:
cd {{target}} && make
```

Other recipes may not depend on a recipe with parameters.

To pass arguments, put them after the recipe name:
To pass arguments on the command line, put them after the recipe name:

```sh
$ just build my-awesome-project
Building my-awesome-project...
cd my-awesome-project && make
```

To pass arguments to a dependency, put the dependency in parentheses along with the arguments:

```make
default: (build "main")

build target:
@echo 'Building {{target}}...'
cd {{target}} && make
```

Parameters may have default values:

```make
Expand Down
35 changes: 2 additions & 33 deletions src/analyzer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::common::*;
use CompilationErrorKind::*;

pub(crate) struct Analyzer<'src> {
recipes: Table<'src, Recipe<'src, Name<'src>>>,
recipes: Table<'src, UnresolvedRecipe<'src>>,
assignments: Table<'src, Assignment<'src>>,
aliases: Table<'src, Alias<'src, Name<'src>>>,
sets: Table<'src, Set<'src>>,
Expand Down Expand Up @@ -91,7 +91,7 @@ impl<'src> Analyzer<'src> {
})
}

fn analyze_recipe(&self, recipe: &Recipe<'src, Name<'src>>) -> CompilationResult<'src, ()> {
fn analyze_recipe(&self, recipe: &UnresolvedRecipe<'src>) -> CompilationResult<'src, ()> {
if let Some(original) = self.recipes.get(recipe.name.lexeme()) {
return Err(recipe.name.token().error(DuplicateRecipe {
recipe: original.name(),
Expand Down Expand Up @@ -125,17 +125,6 @@ impl<'src> Analyzer<'src> {
}
}

let mut dependencies = BTreeSet::new();
for dependency in &recipe.dependencies {
if dependencies.contains(dependency.lexeme()) {
return Err(dependency.token().error(DuplicateDependency {
recipe: recipe.name.lexeme(),
dependency: dependency.lexeme(),
}));
}
dependencies.insert(dependency.lexeme());
}

let mut continued = false;
for line in &recipe.body {
if !recipe.shebang && !continued {
Expand Down Expand Up @@ -295,26 +284,6 @@ mod tests {
kind: ParameterShadowsVariable{parameter: "foo"},
}

analysis_error! {
name: dependency_has_parameters,
input: "foo arg:\nb: foo",
offset: 12,
line: 1,
column: 3,
width: 3,
kind: DependencyHasParameters{recipe: "b", dependency: "foo"},
}

analysis_error! {
name: duplicate_dependency,
input: "a b c: b c z z",
offset: 13,
line: 0,
column: 13,
width: 1,
kind: DuplicateDependency{recipe: "a", dependency: "z"},
}

analysis_error! {
name: duplicate_recipe,
input: "a:\nb:\na:",
Expand Down
5 changes: 3 additions & 2 deletions src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,9 @@ pub(crate) use crate::{
search_config::SearchConfig, search_error::SearchError, set::Set, setting::Setting,
settings::Settings, shebang::Shebang, show_whitespace::ShowWhitespace, state::State,
string_literal::StringLiteral, subcommand::Subcommand, table::Table, thunk::Thunk, token::Token,
token_kind::TokenKind, use_color::UseColor, variables::Variables, verbosity::Verbosity,
warning::Warning,
token_kind::TokenKind, unresolved_dependency::UnresolvedDependency,
unresolved_recipe::UnresolvedRecipe, use_color::UseColor, variables::Variables,
verbosity::Verbosity, warning::Warning,
};

// type aliases
Expand Down
32 changes: 20 additions & 12 deletions src/compilation_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,13 +89,6 @@ impl Display for CompilationError<'_> {
self.token.line.ordinal(),
)?;
}
DuplicateDependency { recipe, dependency } => {
writeln!(
f,
"Recipe `{}` has duplicate dependency `{}`",
recipe, dependency
)?;
}
DuplicateRecipe { recipe, first } => {
writeln!(
f,
Expand All @@ -114,13 +107,28 @@ impl Display for CompilationError<'_> {
self.token.line.ordinal(),
)?;
}
DependencyHasParameters { recipe, dependency } => {
writeln!(
DependencyArgumentCountMismatch {
dependency,
found,
min,
max,
} => {
write!(
f,
"Recipe `{}` depends on `{}` which requires arguments. \
Dependencies may not require arguments",
recipe, dependency
"Dependency `{}` got {} {} but takes ",
dependency,
found,
Count("argument", found),
)?;

if min == max {
let expected = min;
writeln!(f, "{} {}", expected, Count("argument", expected))?;
} else if found < min {
writeln!(f, "at least {} {}", min, Count("argument", min))?;
} else {
writeln!(f, "at most {} {}", max, Count("argument", max))?;
}
}
ParameterShadowsVariable { parameter } => {
writeln!(
Expand Down
10 changes: 4 additions & 6 deletions src/compilation_error_kind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,16 @@ pub(crate) enum CompilationErrorKind<'src> {
variable: &'src str,
circle: Vec<&'src str>,
},
DependencyHasParameters {
recipe: &'src str,
DependencyArgumentCountMismatch {
dependency: &'src str,
found: usize,
min: usize,
max: usize,
},
DuplicateAlias {
alias: &'src str,
first: usize,
},
DuplicateDependency {
recipe: &'src str,
dependency: &'src str,
},
DuplicateParameter {
recipe: &'src str,
parameter: &'src str,
Expand Down
21 changes: 20 additions & 1 deletion src/dependency.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,23 @@
use crate::common::*;

#[derive(PartialEq, Debug)]
pub(crate) struct Dependency<'src>(pub(crate) Rc<Recipe<'src>>);
pub(crate) struct Dependency<'src> {
pub(crate) recipe: Rc<Recipe<'src>>,
pub(crate) arguments: Vec<Expression<'src>>,
}

impl<'src> Display for Dependency<'src> {
fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
if self.arguments.is_empty() {
write!(f, "{}", self.recipe.name())
} else {
write!(f, "({}", self.recipe.name())?;

for argument in &self.arguments {
write!(f, " {}", argument)?;
}

write!(f, ")")
}
}
}
2 changes: 1 addition & 1 deletion src/evaluator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ impl<'src, 'run> Evaluator<'src, 'run> {
Ok(scope)
}

pub(crate) fn line_evaluator(
pub(crate) fn recipe_evaluator(
config: &'run Config,
dotenv: &'run BTreeMap<String, String>,
scope: &'run Scope<'src, 'run>,
Expand Down
2 changes: 1 addition & 1 deletion src/item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ use crate::common::*;
pub(crate) enum Item<'src> {
Alias(Alias<'src, Name<'src>>),
Assignment(Assignment<'src>),
Recipe(Recipe<'src, Name<'src>>),
Recipe(UnresolvedRecipe<'src>),
Set(Set<'src>),
}
53 changes: 45 additions & 8 deletions src/justfile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ impl<'src> Justfile<'src> {
working_directory,
};

let mut ran = empty();
let mut ran = BTreeSet::new();
for (recipe, arguments) in grouped {
self.run_recipe(&context, recipe, arguments, &dotenv, &mut ran)?
}
Expand All @@ -201,17 +201,54 @@ impl<'src> Justfile<'src> {
&self,
context: &'run RecipeContext<'src, 'run>,
recipe: &Recipe<'src>,
arguments: &[&'src str],
arguments: &[&'run str],
dotenv: &BTreeMap<String, String>,
ran: &mut BTreeSet<&'src str>,
ran: &mut BTreeSet<Vec<String>>,
) -> RunResult<'src, ()> {
for Dependency(dependency) in &recipe.dependencies {
if !ran.contains(dependency.name()) {
self.run_recipe(context, dependency, &[], dotenv, ran)?;
let scope = Evaluator::evaluate_parameters(
context.config,
dotenv,
&recipe.parameters,
arguments,
&context.scope,
context.settings,
context.working_directory,
)?;

let mut evaluator = Evaluator::recipe_evaluator(
context.config,
dotenv,
&scope,
context.settings,
context.working_directory,
);

for Dependency { recipe, arguments } in &recipe.dependencies {
let mut invocation = vec![recipe.name().to_owned()];

for argument in arguments {
invocation.push(evaluator.evaluate_expression(argument)?);
}

if !ran.contains(&invocation) {
let arguments = invocation
.iter()
.skip(1)
.map(String::as_ref)
.collect::<Vec<&str>>();
self.run_recipe(context, recipe, &arguments, dotenv, ran)?;
}
}

recipe.run(context, dotenv, scope)?;

let mut invocation = Vec::new();
invocation.push(recipe.name().to_owned());
for argument in arguments.iter().cloned() {
invocation.push(argument.to_owned());
}
recipe.run(context, arguments, dotenv)?;
ran.insert(recipe.name());

ran.insert(invocation);
Ok(())
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ mod table;
mod thunk;
mod token;
mod token_kind;
mod unresolved_dependency;
mod unresolved_recipe;
mod use_color;
mod variables;
mod verbosity;
Expand Down
23 changes: 14 additions & 9 deletions src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ impl<'src> Node<'src> for Expression<'src> {
}
}

impl<'src> Node<'src> for Recipe<'src, Name<'src>> {
impl<'src> Node<'src> for UnresolvedRecipe<'src> {
fn tree(&self) -> Tree<'src> {
let mut t = Tree::atom("recipe");

Expand Down Expand Up @@ -111,14 +111,19 @@ impl<'src> Node<'src> for Recipe<'src, Name<'src>> {
}

if !self.dependencies.is_empty() {
t = t.push(
Tree::atom("deps").extend(
self
.dependencies
.iter()
.map(|dependency| dependency.lexeme()),
),
);
let mut dependencies = Tree::atom("deps");

for dependency in &self.dependencies {
let mut d = Tree::atom(dependency.recipe.lexeme());

for argument in &dependency.arguments {
d.push_mut(argument.tree());
}

dependencies.push_mut(d);
}

t.push_mut(dependencies);
}

if !self.body.is_empty() {
Expand Down
Loading