Skip to content

Commit

Permalink
Add recipe opts
Browse files Browse the repository at this point in the history
  • Loading branch information
casey committed Nov 26, 2023
1 parent 92bae08 commit da32c6e
Show file tree
Hide file tree
Showing 15 changed files with 138 additions and 36 deletions.
4 changes: 2 additions & 2 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -182,8 +182,8 @@ build-book:
mdbook build book/en
mdbook build book/zh

convert-integration-test test:
cargo expand --test integration {{test}} | \
convert-integration-test TEST:
cargo expand --test integration {{ TEST }} | \
sed \
-E \
-e 's/#\[cfg\(test\)\]/#\[test\]/' \
Expand Down
5 changes: 5 additions & 0 deletions src/evaluator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ impl<'src, 'run> Evaluator<'src, 'run> {
config: &'run Config,
dotenv: &'run BTreeMap<String, String>,
parameters: &[Parameter<'src>],
opts: BTreeMap<Name<'src>, &str>,
arguments: &[&str],
scope: &'run Scope<'src, 'run>,
settings: &'run Settings,
Expand All @@ -271,6 +272,10 @@ impl<'src, 'run> Evaluator<'src, 'run> {

let mut scope = scope.child();

for (name, value) in opts {
scope.bind(false, name, value.into());
}

let mut positional = Vec::new();

let mut rest = arguments;
Expand Down
29 changes: 25 additions & 4 deletions src/justfile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,8 +215,25 @@ impl<'src> Justfile<'src> {

while let Some((argument, mut tail)) = rest.split_first() {
if let Some(recipe) = self.get_recipe(argument) {
let mut opts = BTreeMap::new();

for opt in &recipe.opts {
if let Some(arg) = tail.first() {
if opt.accepts(arg) {
if let Some(value) = tail.get(1) {
opts.insert(opt.variable, *value);
} else {
panic!("opt with no value");
}
tail = &tail[2..];
continue;
}
}
panic!("opt not found");
}

if recipe.parameters.is_empty() {
grouped.push((recipe, &[][..]));
grouped.push((recipe, opts, &[][..]));
} else {
let argument_range = recipe.argument_range();
let argument_count = cmp::min(tail.len(), recipe.max_arguments());
Expand All @@ -229,7 +246,7 @@ impl<'src> Justfile<'src> {
max: recipe.max_arguments(),
});
}
grouped.push((recipe, &tail[0..argument_count]));
grouped.push((recipe, opts, &tail[0..argument_count]));
tail = &tail[argument_count..];
}
} else {
Expand Down Expand Up @@ -258,8 +275,8 @@ impl<'src> Justfile<'src> {
};

let mut ran = BTreeSet::new();
for (recipe, arguments) in grouped {
Self::run_recipe(&context, recipe, arguments, &dotenv, search, &mut ran)?;
for (recipe, opts, arguments) in grouped {
Self::run_recipe(&context, recipe, opts, arguments, &dotenv, search, &mut ran)?;
}

Ok(())
Expand All @@ -280,6 +297,7 @@ impl<'src> Justfile<'src> {
fn run_recipe(
context: &RecipeContext<'src, '_>,
recipe: &Recipe<'src>,
opts: BTreeMap<Name<'src>, &str>,
arguments: &[&str],
dotenv: &BTreeMap<String, String>,
search: &Search,
Expand All @@ -304,6 +322,7 @@ impl<'src> Justfile<'src> {
context.config,
dotenv,
&recipe.parameters,
opts,
arguments,
&context.scope,
context.settings,
Expand All @@ -324,6 +343,7 @@ impl<'src> Justfile<'src> {
Self::run_recipe(
context,
recipe,
BTreeMap::new(),
&arguments.iter().map(String::as_ref).collect::<Vec<&str>>(),
dotenv,
search,
Expand All @@ -346,6 +366,7 @@ impl<'src> Justfile<'src> {
Self::run_recipe(
context,
recipe,
BTreeMap::new(),
&evaluated.iter().map(String::as_ref).collect::<Vec<&str>>(),
dotenv,
search,
Expand Down
12 changes: 5 additions & 7 deletions src/lexer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -282,11 +282,7 @@ impl<'src> Lexer<'src> {

/// True if `c` can be a continuation character of an identifier
fn is_identifier_continue(c: char) -> bool {
if Self::is_identifier_start(c) {
return true;
}

matches!(c, '0'..='9' | '-')
Self::is_identifier_start(c) | matches!(c, '0'..='9' | '-')
}

/// Consume the text and produce a series of tokens
Expand Down Expand Up @@ -490,6 +486,7 @@ impl<'src> Lexer<'src> {
'*' => self.lex_single(Asterisk),
'+' => self.lex_single(Plus),
',' => self.lex_single(Comma),
'-' => self.lex_digraph('-', '-', DashDash),
'/' => self.lex_single(Slash),
':' => self.lex_colon(),
'\\' => self.lex_escape(),
Expand Down Expand Up @@ -990,6 +987,7 @@ mod tests {
Colon => ":",
ColonEquals => ":=",
Comma => ",",
DashDash => "--",
Dollar => "$",
Eol => "\n",
Equals => "=",
Expand Down Expand Up @@ -2205,8 +2203,8 @@ mod tests {
}

error! {
name: invalid_name_start_dash,
input: "-foo",
name: invalid_name_start_caret,
input: "^foo",
offset: 0,
line: 0,
column: 0,
Expand Down
7 changes: 4 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ pub(crate) use {
fragment::Fragment, function::Function, function_context::FunctionContext,
interrupt_guard::InterruptGuard, interrupt_handler::InterruptHandler, item::Item,
justfile::Justfile, keyed::Keyed, keyword::Keyword, lexer::Lexer, line::Line, list::List,
load_dotenv::load_dotenv, loader::Loader, name::Name, ordinal::Ordinal, output::output,
output_error::OutputError, parameter::Parameter, parameter_kind::ParameterKind, parser::Parser,
platform::Platform, platform_interface::PlatformInterface, position::Position,
load_dotenv::load_dotenv, loader::Loader, name::Name, opt::Opt, ordinal::Ordinal,
output::output, output_error::OutputError, parameter::Parameter, parameter_kind::ParameterKind,
parser::Parser, platform::Platform, platform_interface::PlatformInterface, position::Position,
positional::Positional, range_ext::RangeExt, recipe::Recipe, recipe_context::RecipeContext,
recipe_resolver::RecipeResolver, scope::Scope, search::Search, search_config::SearchConfig,
search_error::SearchError, set::Set, setting::Setting, settings::Settings, shebang::Shebang,
Expand Down Expand Up @@ -145,6 +145,7 @@ mod list;
mod load_dotenv;
mod loader;
mod name;
mod opt;
mod ordinal;
mod output;
mod output_error;
Expand Down
34 changes: 34 additions & 0 deletions src/opt.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use super::*;

#[derive(PartialEq, Debug, Clone, Serialize)]
pub(crate) struct Opt<'src> {
pub(crate) default: Option<Expression<'src>>,
pub(crate) key: Name<'src>,
pub(crate) variable: Name<'src>,
}

impl<'src> Opt<'src> {
pub(crate) fn accepts(&self, arg: &str) -> bool {
arg
.strip_prefix("--")
.map(|key| key == self.key.lexeme())
.unwrap_or_default()
}
}

impl<'src> ColorDisplay for Opt<'src> {
fn fmt(&self, f: &mut Formatter, color: Color) -> Result<(), fmt::Error> {
write!(
f,
"--{} {}",
color.annotation().paint(self.key.lexeme()),
color.parameter().paint(self.variable.lexeme())
)?;

if let Some(ref default) = self.default {
write!(f, "={}", color.string().paint(&default.to_string()))?;
}

Ok(())
}
}
39 changes: 34 additions & 5 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -627,9 +627,16 @@ impl<'tokens, 'src> Parser<'tokens, 'src> {
let name = self.parse_name()?;

let mut positional = Vec::new();
let mut opts = Vec::new();

while self.next_is(Identifier) || self.next_is(Dollar) {
positional.push(self.parse_parameter(ParameterKind::Singular)?);
loop {
if self.next_is(Identifier) || self.next_is(Dollar) {
positional.push(self.parse_parameter(ParameterKind::Singular)?);
} else if self.next_is(DashDash) {
opts.push(self.parse_opt()?);
} else {
break;
}
}

let kind = if self.accepted(Plus)? {
Expand Down Expand Up @@ -687,11 +694,12 @@ impl<'tokens, 'src> Parser<'tokens, 'src> {
private: name.lexeme().starts_with('_'),
shebang: body.first().map_or(false, Line::is_shebang),
attributes,
priors,
body,
dependencies,
doc,
name,
opts,
priors,
quiet,
})
}
Expand All @@ -716,6 +724,27 @@ impl<'tokens, 'src> Parser<'tokens, 'src> {
})
}

/// Parse a recipe option --foo foo
fn parse_opt(&mut self) -> CompileResult<'src, Opt<'src>> {
self.presume(DashDash)?;

let key = self.parse_name()?;

let variable = self.parse_name()?;

let default = if self.accepted(Equals)? {
Some(self.parse_value()?)
} else {
None
};

Ok(Opt {
default,
key,
variable,
})
}

/// Parse the body of a recipe
fn parse_body(&mut self) -> CompileResult<'src, Vec<Line<'src>>> {
let mut lines = Vec::new();
Expand Down Expand Up @@ -2014,7 +2043,7 @@ mod tests {
column: 5,
width: 1,
kind: UnexpectedToken{
expected: vec![Asterisk, Colon, Dollar, Equals, Identifier, Plus],
expected: vec![Asterisk, Colon, DashDash, Dollar, Equals, Identifier, Plus],
found: Eol
},
}
Expand Down Expand Up @@ -2149,7 +2178,7 @@ mod tests {
column: 8,
width: 0,
kind: UnexpectedToken {
expected: vec![Asterisk, Colon, Dollar, Equals, Identifier, Plus],
expected: vec![Asterisk, Colon, DashDash, Dollar, Equals, Identifier, Plus],
found: Eof
},
}
Expand Down
6 changes: 6 additions & 0 deletions src/recipe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ pub(crate) struct Recipe<'src, D = Dependency<'src>> {
pub(crate) private: bool,
pub(crate) quiet: bool,
pub(crate) shebang: bool,
#[serde(skip)]
pub(crate) opts: Vec<Opt<'src>>,
}

impl<'src, D> Recipe<'src, D> {
Expand Down Expand Up @@ -406,6 +408,10 @@ impl<'src, D: Display> ColorDisplay for Recipe<'src, D> {
write!(f, "{}", self.name)?;
}

for opt in &self.opts {
write!(f, " {}", opt.color_display(color))?;
}

for parameter in &self.parameters {
write!(f, " {}", parameter.color_display(color))?;
}
Expand Down
14 changes: 9 additions & 5 deletions src/recipe_resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,15 @@ impl<'src: 'run, 'run> RecipeResolver<'src, 'run> {
for parameter in &recipe.parameters {
if let Some(expression) = &parameter.default {
for variable in expression.variables() {
resolver.resolve_variable(&variable, &[])?;
resolver.resolve_variable(&variable, &Vec::new(), &[])?;
}
}
}

for dependency in &recipe.dependencies {
for argument in &dependency.arguments {
for variable in argument.variables() {
resolver.resolve_variable(&variable, &recipe.parameters)?;
resolver.resolve_variable(&variable, &recipe.opts, &recipe.parameters)?;
}
}
}
Expand All @@ -42,7 +42,7 @@ impl<'src: 'run, 'run> RecipeResolver<'src, 'run> {
for fragment in &line.fragments {
if let Fragment::Interpolation { expression, .. } = fragment {
for variable in expression.variables() {
resolver.resolve_variable(&variable, &recipe.parameters)?;
resolver.resolve_variable(&variable, &recipe.opts, &recipe.parameters)?;
}
}
}
Expand All @@ -55,11 +55,15 @@ impl<'src: 'run, 'run> RecipeResolver<'src, 'run> {
fn resolve_variable(
&self,
variable: &Token<'src>,
opts: &Vec<Opt<'src>>,
parameters: &[Parameter],
) -> CompileResult<'src, ()> {
let name = variable.lexeme();
let undefined =
!self.assignments.contains_key(name) && !parameters.iter().any(|p| p.name.lexeme() == name);
let undefined = !self.assignments.contains_key(name)
&& !opts.iter().any(|opt| opt.variable.lexeme() == name)
&& !parameters
.iter()
.any(|parameter| parameter.name.lexeme() == name);

if undefined {
return Err(variable.error(UndefinedVariable { variable: name }));
Expand Down
2 changes: 2 additions & 0 deletions src/token_kind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub(crate) enum TokenKind {
ColonEquals,
Comma,
Comment,
DashDash,
Dedent,
Dollar,
Eof,
Expand Down Expand Up @@ -60,6 +61,7 @@ impl Display for TokenKind {
ColonEquals => "':='",
Comma => "','",
Comment => "comment",
DashDash => "'--'",
Dedent => "dedent",
Dollar => "'$'",
Eof => "end of file",
Expand Down
7 changes: 4 additions & 3 deletions src/unresolved_recipe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,17 @@ impl<'src> UnresolvedRecipe<'src> {
.collect();

Ok(Recipe {
attributes: self.attributes,
body: self.body,
dependencies,
doc: self.doc,
name: self.name,
opts: self.opts,
parameters: self.parameters,
priors: self.priors,
private: self.private,
quiet: self.quiet,
shebang: self.shebang,
priors: self.priors,
attributes: self.attributes,
dependencies,
})
}
}
Loading

0 comments on commit da32c6e

Please sign in to comment.