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
| expression '+' expression
recipe : NAME argument* ':' dependencies? body?
recipe : '@'? NAME argument* ':' dependencies? body?
argument : NAME
| 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.
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:
```make

View File

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

View File

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