parent
babe97bf0d
commit
112462ec62
@ -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
|
||||||
|
18
README.md
18
README.md
@ -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
|
||||||
|
@ -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",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
32
src/lib.rs
32
src/lib.rs
@ -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,
|
||||||
|
35
src/unit.rs
35
src/unit.rs
@ -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},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user