Allow line continuations in plain recipes with '\' (#99)
Some ugly code, but not as bad as I thought. Elected not to go with indentation based line continuations. Too many weird edge cases and feels like a gratuitious incompatibility with make. Fixes #9
This commit is contained in:
parent
5b2d671c1d
commit
babe97bf0d
@ -1157,3 +1157,51 @@ a Z="\t z":
|
|||||||
"error: Justfile does not contain recipe `hell`.\nDid you mean `hello`?\n",
|
"error: Justfile does not contain recipe `hell`.\nDid you mean `hello`?\n",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn line_continuation_with_space() {
|
||||||
|
integration_test(
|
||||||
|
&[],
|
||||||
|
r#"
|
||||||
|
foo:
|
||||||
|
echo a\
|
||||||
|
b \
|
||||||
|
c
|
||||||
|
"#,
|
||||||
|
0,
|
||||||
|
"a b c\n",
|
||||||
|
"echo a b c\n",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn line_continuation_with_quoted_space() {
|
||||||
|
integration_test(
|
||||||
|
&[],
|
||||||
|
r#"
|
||||||
|
foo:
|
||||||
|
echo 'a\
|
||||||
|
b \
|
||||||
|
c'
|
||||||
|
"#,
|
||||||
|
0,
|
||||||
|
"a b c\n",
|
||||||
|
"echo 'a b c'\n",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn line_continuation_no_space() {
|
||||||
|
integration_test(
|
||||||
|
&[],
|
||||||
|
r#"
|
||||||
|
foo:
|
||||||
|
echo a\
|
||||||
|
b\
|
||||||
|
c
|
||||||
|
"#,
|
||||||
|
0,
|
||||||
|
"abc\n",
|
||||||
|
"echo abc\n",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
47
src/lib.rs
47
src/lib.rs
@ -98,6 +98,15 @@ enum Fragment<'a> {
|
|||||||
Expression{expression: Expression<'a>},
|
Expression{expression: Expression<'a>},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> Fragment<'a> {
|
||||||
|
fn continuation(&self) -> bool {
|
||||||
|
match *self {
|
||||||
|
Fragment::Text{ref text} => text.lexeme.ends_with('\\'),
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Debug)]
|
#[derive(PartialEq, Debug)]
|
||||||
enum Expression<'a> {
|
enum Expression<'a> {
|
||||||
Variable{name: &'a str, token: Token<'a>},
|
Variable{name: &'a str, token: Token<'a>},
|
||||||
@ -345,8 +354,24 @@ impl<'a> Recipe<'a> {
|
|||||||
recipe: self.name, io_error: io_error})
|
recipe: self.name, io_error: io_error})
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
for line in &self.lines {
|
let mut lines = self.lines.iter().peekable();
|
||||||
let evaluated = &evaluator.evaluate_line(line, &argument_map)?;
|
loop {
|
||||||
|
if lines.peek().is_none() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let mut evaluated = String::new();
|
||||||
|
loop {
|
||||||
|
if lines.peek().is_none() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let line = lines.next().unwrap();
|
||||||
|
evaluated += &evaluator.evaluate_line(line, &argument_map)?;
|
||||||
|
if line.last().map(Fragment::continuation).unwrap_or(false) {
|
||||||
|
evaluated.pop();
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
let mut command = evaluated.as_str();
|
let mut command = evaluated.as_str();
|
||||||
let quiet_command = command.starts_with('@');
|
let quiet_command = command.starts_with('@');
|
||||||
if quiet_command {
|
if quiet_command {
|
||||||
@ -1792,7 +1817,7 @@ impl<'a> Parser<'a> {
|
|||||||
return Err(self.unexpected_token(&token, &[Name, Eol, Eof]));
|
return Err(self.unexpected_token(&token, &[Name, Eol, Eof]));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut lines = vec![];
|
let mut lines: Vec<Vec<Fragment>> = vec![];
|
||||||
let mut shebang = false;
|
let mut shebang = false;
|
||||||
|
|
||||||
if self.accepted(Indent) {
|
if self.accepted(Indent) {
|
||||||
@ -1805,25 +1830,27 @@ impl<'a> Parser<'a> {
|
|||||||
message: format!("Expected a line but got {}", token.kind)
|
message: format!("Expected a line but got {}", token.kind)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
let mut pieces = vec![];
|
let mut fragments = vec![];
|
||||||
|
|
||||||
while !(self.accepted(Eol) || self.peek(Dedent)) {
|
while !(self.accepted(Eol) || self.peek(Dedent)) {
|
||||||
if let Some(token) = self.accept(Text) {
|
if let Some(token) = self.accept(Text) {
|
||||||
if pieces.is_empty() {
|
if fragments.is_empty() {
|
||||||
if lines.is_empty() {
|
if lines.is_empty() {
|
||||||
if token.lexeme.starts_with("#!") {
|
if token.lexeme.starts_with("#!") {
|
||||||
shebang = true;
|
shebang = true;
|
||||||
}
|
}
|
||||||
} else if !shebang && (token.lexeme.starts_with(' ') ||
|
} else if !shebang
|
||||||
token.lexeme.starts_with('\t')) {
|
&& !lines.last().and_then(|line| line.last())
|
||||||
|
.map(Fragment::continuation).unwrap_or(false)
|
||||||
|
&& (token.lexeme.starts_with(' ') || token.lexeme.starts_with('\t')) {
|
||||||
return Err(token.error(ErrorKind::ExtraLeadingWhitespace));
|
return Err(token.error(ErrorKind::ExtraLeadingWhitespace));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pieces.push(Fragment::Text{text: token});
|
fragments.push(Fragment::Text{text: token});
|
||||||
} else if let Some(token) = self.expect(InterpolationStart) {
|
} else if let Some(token) = self.expect(InterpolationStart) {
|
||||||
return Err(self.unexpected_token(&token, &[Text, InterpolationStart, Eol]));
|
return Err(self.unexpected_token(&token, &[Text, InterpolationStart, Eol]));
|
||||||
} else {
|
} else {
|
||||||
pieces.push(Fragment::Expression{
|
fragments.push(Fragment::Expression{
|
||||||
expression: self.expression(true)?
|
expression: self.expression(true)?
|
||||||
});
|
});
|
||||||
if let Some(token) = self.expect(InterpolationEnd) {
|
if let Some(token) = self.expect(InterpolationEnd) {
|
||||||
@ -1832,7 +1859,7 @@ impl<'a> Parser<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lines.push(pieces);
|
lines.push(fragments);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -721,8 +721,6 @@ fn unknown_recipes() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn extra_whitespace() {
|
fn extra_whitespace() {
|
||||||
// we might want to make extra leading whitespace a line continuation in the future,
|
|
||||||
// so make it a error for now
|
|
||||||
let text = "a:\n blah\n blarg";
|
let text = "a:\n blah\n blarg";
|
||||||
parse_error(text, CompileError {
|
parse_error(text, CompileError {
|
||||||
text: text,
|
text: text,
|
||||||
|
Loading…
Reference in New Issue
Block a user