Toggle meaning of '@' on recipes prefixed with '@' (#100)

Fixes #65
This commit is contained in:
Casey Rodarmor 2016-11-12 16:12:00 -08:00 committed by GitHub
parent babe97bf0d
commit 112462ec62
5 changed files with 92 additions and 26 deletions

View File

@ -48,7 +48,7 @@ expression : STRING
| BACKTICK | BACKTICK
| expression '+' expression | expression '+' expression
recipe : NAME argument* ':' dependencies? body? recipe : '@'? NAME argument* ':' dependencies? body?
argument : NAME argument : NAME
| NAME '=' STRING | NAME '=' STRING

View File

@ -73,6 +73,24 @@ Another recipe.
`just` prints each command to standard error before running it, which is why `echo 'This is a recipe!'` was printed. Lines starting with `@` will not be printed which is why `echo 'Another recipe.'` was not printed. `just` prints each command to standard error before running it, which is why `echo 'This is a recipe!'` was printed. Lines starting with `@` will not be printed which is why `echo 'Another recipe.'` was not printed.
A recipe name may be prefixed with '@' to invert the meaning of '@' before each line:
```make
@quiet:
echo hello
echo goodbye
@# all done!
```
Now only the lines starting with '@' will be echoed:
```sh
$ j quiet
hello
goodbye
# all done!
```
Recipes stop running if a command fails. Here `cargo publish` will only run if `cargo test` succeeds: Recipes stop running if a command fails. Here `cargo publish` will only run if `cargo test` succeeds:
```make ```make

View File

@ -1205,3 +1205,34 @@ foo:
"echo abc\n", "echo abc\n",
); );
} }
#[test]
fn quiet_recipe() {
integration_test(
&[],
r#"
@quiet:
# a
# b
@echo c
"#,
0,
"c\n",
"echo c\n",
);
}
#[test]
fn quiet_shebang_recipe() {
integration_test(
&[],
r#"
@quiet:
#!/bin/sh
echo hello
"#,
0,
"hello\n",
"#!/bin/sh\necho hello\n",
);
}

View File

@ -72,6 +72,7 @@ struct Recipe<'a> {
dependency_tokens: Vec<Token<'a>>, dependency_tokens: Vec<Token<'a>>,
parameters: Vec<Parameter<'a>>, parameters: Vec<Parameter<'a>>,
shebang: bool, shebang: bool,
quiet: bool,
} }
#[derive(PartialEq, Debug)] #[derive(PartialEq, Debug)]
@ -294,10 +295,13 @@ impl<'a> Recipe<'a> {
evaluated_lines.push(evaluator.evaluate_line(line, &argument_map)?); evaluated_lines.push(evaluator.evaluate_line(line, &argument_map)?);
} }
if options.dry_run { if options.dry_run || self.quiet {
for line in evaluated_lines { for line in &evaluated_lines {
warn!("{}", line); warn!("{}", line);
} }
}
if options.dry_run {
return Ok(()); return Ok(());
} }
@ -377,7 +381,7 @@ impl<'a> Recipe<'a> {
if quiet_command { if quiet_command {
command = &command[1..]; command = &command[1..];
} }
if options.dry_run || !(quiet_command || options.quiet) { if options.dry_run || !((quiet_command ^ self.quiet) || options.quiet) {
warn!("{}", command); warn!("{}", command);
} }
if options.dry_run { if options.dry_run {
@ -1369,6 +1373,7 @@ impl<'a> Token<'a> {
#[derive(Debug, PartialEq, Clone, Copy)] #[derive(Debug, PartialEq, Clone, Copy)]
enum TokenKind { enum TokenKind {
At,
Backtick, Backtick,
Colon, Colon,
Comment, Comment,
@ -1382,8 +1387,8 @@ enum TokenKind {
Line, Line,
Name, Name,
Plus, Plus,
StringToken,
RawString, RawString,
StringToken,
Text, Text,
} }
@ -1403,6 +1408,7 @@ impl Display for TokenKind {
Line => "command", Line => "command",
Name => "name", Name => "name",
Plus => "\"+\"", Plus => "\"+\"",
At => "\"@\"",
StringToken => "string", StringToken => "string",
RawString => "raw string", RawString => "raw string",
Text => "command text", Text => "command text",
@ -1424,6 +1430,7 @@ fn tokenize(text: &str) -> Result<Vec<Token>, CompileError> {
lazy_static! { lazy_static! {
static ref BACKTICK: Regex = token(r"`[^`\n\r]*`" ); static ref BACKTICK: Regex = token(r"`[^`\n\r]*`" );
static ref COLON: Regex = token(r":" ); static ref COLON: Regex = token(r":" );
static ref AT: Regex = token(r"@" );
static ref COMMENT: Regex = token(r"#([^!].*)?$" ); static ref COMMENT: Regex = token(r"#([^!].*)?$" );
static ref EOF: Regex = token(r"(?-m)$" ); static ref EOF: Regex = token(r"(?-m)$" );
static ref EOL: Regex = token(r"\n|\r\n" ); static ref EOL: Regex = token(r"\n|\r\n" );
@ -1585,6 +1592,8 @@ fn tokenize(text: &str) -> Result<Vec<Token>, CompileError> {
(captures.at(1).unwrap(), captures.at(2).unwrap(), Backtick) (captures.at(1).unwrap(), captures.at(2).unwrap(), Backtick)
} else if let Some(captures) = COLON.captures(rest) { } else if let Some(captures) = COLON.captures(rest) {
(captures.at(1).unwrap(), captures.at(2).unwrap(), Colon) (captures.at(1).unwrap(), captures.at(2).unwrap(), Colon)
} else if let Some(captures) = AT.captures(rest) {
(captures.at(1).unwrap(), captures.at(2).unwrap(), At)
} else if let Some(captures) = PLUS.captures(rest) { } else if let Some(captures) = PLUS.captures(rest) {
(captures.at(1).unwrap(), captures.at(2).unwrap(), Plus) (captures.at(1).unwrap(), captures.at(2).unwrap(), Plus)
} else if let Some(captures) = EQUALS.captures(rest) { } else if let Some(captures) = EQUALS.captures(rest) {
@ -1746,7 +1755,7 @@ impl<'a> Parser<'a> {
}) })
} }
fn recipe(&mut self, name: Token<'a>) -> Result<(), CompileError<'a>> { fn recipe(&mut self, name: Token<'a>, quiet: bool) -> Result<(), CompileError<'a>> {
if let Some(recipe) = self.recipes.get(name.lexeme) { if let Some(recipe) = self.recipes.get(name.lexeme) {
return Err(name.error(ErrorKind::DuplicateRecipe { return Err(name.error(ErrorKind::DuplicateRecipe {
recipe: recipe.name, recipe: recipe.name,
@ -1871,6 +1880,7 @@ impl<'a> Parser<'a> {
parameters: parameters, parameters: parameters,
lines: lines, lines: lines,
shebang: shebang, shebang: shebang,
quiet: quiet,
}); });
Ok(()) Ok(())
@ -1925,23 +1935,29 @@ impl<'a> Parser<'a> {
Some(token) => match token.kind { Some(token) => match token.kind {
Eof => break, Eof => break,
Eol => continue, Eol => continue,
At => if let Some(name) = self.accept(Name) {
self.recipe(name, true)?;
} else {
let unexpected = &self.tokens.next().unwrap();
return Err(self.unexpected_token(unexpected, &[Name]));
},
Name => if token.lexeme == "export" { Name => if token.lexeme == "export" {
let next = self.tokens.next().unwrap(); let next = self.tokens.next().unwrap();
if next.kind == Name && self.accepted(Equals) { if next.kind == Name && self.accepted(Equals) {
self.assignment(next, true)?; self.assignment(next, true)?;
} else { } else {
self.tokens.put_back(next); self.tokens.put_back(next);
self.recipe(token)?; self.recipe(token, false)?;
} }
} else if self.accepted(Equals) { } else if self.accepted(Equals) {
self.assignment(token, false)?; self.assignment(token, false)?;
} else { } else {
self.recipe(token)?; self.recipe(token, false)?;
}, },
Comment => return Err(token.error(ErrorKind::InternalError { Comment => return Err(token.error(ErrorKind::InternalError {
message: "found comment in token stream".to_string() message: "found comment in token stream".to_string()
})), })),
_ => return return Err(self.unexpected_token(&token, &[Name])), _ => return return Err(self.unexpected_token(&token, &[Name, At])),
}, },
None => return Err(CompileError { None => return Err(CompileError {
text: self.text, text: self.text,

View File

@ -35,22 +35,23 @@ fn tokenize_error(text: &str, expected: CompileError) {
fn token_summary(tokens: &[Token]) -> String { fn token_summary(tokens: &[Token]) -> String {
tokens.iter().map(|t| { tokens.iter().map(|t| {
match t.kind { match t.kind {
super::TokenKind::Backtick => "`", At => "@",
super::TokenKind::Colon => ":", Backtick => "`",
super::TokenKind::Comment{..} => "#", Colon => ":",
super::TokenKind::Dedent => "<", Comment{..} => "#",
super::TokenKind::Eof => ".", Dedent => "<",
super::TokenKind::Eol => "$", Eof => ".",
super::TokenKind::Equals => "=", Eol => "$",
super::TokenKind::Indent{..} => ">", Equals => "=",
super::TokenKind::InterpolationEnd => "}", Indent{..} => ">",
super::TokenKind::InterpolationStart => "{", InterpolationEnd => "}",
super::TokenKind::Line{..} => "^", InterpolationStart => "{",
super::TokenKind::Name => "N", Line{..} => "^",
super::TokenKind::Plus => "+", Name => "N",
super::TokenKind::StringToken => "\"", Plus => "+",
super::TokenKind::RawString => "'", RawString => "'",
super::TokenKind::Text => "_", StringToken => "\"",
Text => "_",
} }
}).collect::<Vec<_>>().join("") }).collect::<Vec<_>>().join("")
} }
@ -744,7 +745,7 @@ fn interpolation_outside_of_recipe() {
line: 0, line: 0,
column: 0, column: 0,
width: Some(2), width: Some(2),
kind: ErrorKind::UnexpectedToken{expected: vec![Name], found: InterpolationStart}, kind: ErrorKind::UnexpectedToken{expected: vec![Name, At], found: InterpolationStart},
}); });
} }