Handle line interpolation parsing
This commit is contained in:
parent
f01ef06bf0
commit
9aed7ca129
80
src/lib.rs
80
src/lib.rs
@ -13,7 +13,7 @@ extern crate tempdir;
|
||||
use std::io::prelude::*;
|
||||
|
||||
use std::{fs, fmt, process, io};
|
||||
use std::collections::{BTreeMap, HashSet};
|
||||
use std::collections::{BTreeMap, BTreeSet, HashSet};
|
||||
use std::fmt::Display;
|
||||
use regex::Regex;
|
||||
|
||||
@ -55,8 +55,8 @@ struct Recipe<'a> {
|
||||
line_number: usize,
|
||||
name: &'a str,
|
||||
lines: Vec<&'a str>,
|
||||
// fragments: Vec<Vec<Fragment<'a>>>,
|
||||
// variables: BTreeSet<&'a str>,
|
||||
fragments: Vec<Vec<Fragment<'a>>>,
|
||||
variables: BTreeSet<&'a str>,
|
||||
dependencies: Vec<&'a str>,
|
||||
dependency_tokens: Vec<Token<'a>>,
|
||||
arguments: Vec<&'a str>,
|
||||
@ -64,12 +64,11 @@ struct Recipe<'a> {
|
||||
shebang: bool,
|
||||
}
|
||||
|
||||
/*
|
||||
#[derive(PartialEq, Debug)]
|
||||
enum Fragment<'a> {
|
||||
Text{text: &'a str},
|
||||
Variable{name: &'a str},
|
||||
}
|
||||
*/
|
||||
|
||||
#[cfg(unix)]
|
||||
fn error_from_signal(recipe: &str, exit_status: process::ExitStatus) -> RunError {
|
||||
@ -184,13 +183,23 @@ impl<'a> Display for Recipe<'a> {
|
||||
for dependency in &self.dependencies {
|
||||
try!(write!(f, " {}", dependency))
|
||||
}
|
||||
for (i, line) in self.lines.iter().enumerate() {
|
||||
|
||||
|
||||
for (i, fragments) in self.fragments.iter().enumerate() {
|
||||
if i == 0 {
|
||||
try!(writeln!(f, ""));
|
||||
}
|
||||
try!(write!(f, " {}", line));
|
||||
if i + 1 < self.lines.len() {
|
||||
try!(writeln!(f, ""));
|
||||
for (j, fragment) in fragments.iter().enumerate() {
|
||||
if j == 0 {
|
||||
try!(write!(f, " "));
|
||||
}
|
||||
match *fragment {
|
||||
Fragment::Text{text} => try!(write!(f, "{}", text)),
|
||||
Fragment::Variable{name} => try!(write!(f, "{}{}{}", "{{", name, "}}")),
|
||||
}
|
||||
}
|
||||
if i + 1 < self.fragments.len() {
|
||||
try!(write!(f, "\n"));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@ -253,6 +262,8 @@ enum ErrorKind<'a> {
|
||||
DuplicateArgument{recipe: &'a str, argument: &'a str},
|
||||
DuplicateRecipe{recipe: &'a str, first: usize},
|
||||
MixedLeadingWhitespace{whitespace: &'a str},
|
||||
UnmatchedInterpolationDelimiter{recipe: &'a str},
|
||||
BadInterpolationVariableName{recipe: &'a str, text: &'a str},
|
||||
ExtraLeadingWhitespace,
|
||||
InconsistentLeadingWhitespace{expected: &'a str, found: &'a str},
|
||||
OuterShebang,
|
||||
@ -340,6 +351,12 @@ impl<'a> Display for Error<'a> {
|
||||
ErrorKind::OuterShebang => {
|
||||
try!(writeln!(f, "a shebang \"#!\" is reserved syntax outside of recipes"))
|
||||
}
|
||||
ErrorKind::UnmatchedInterpolationDelimiter{recipe} => {
|
||||
try!(writeln!(f, "recipe {} contains an unmatched {}", recipe, "{{"))
|
||||
}
|
||||
ErrorKind::BadInterpolationVariableName{recipe, text} => {
|
||||
try!(writeln!(f, "recipe {} contains a bad variable interpolation: {}", recipe, text))
|
||||
}
|
||||
ErrorKind::UnknownDependency{recipe, unknown} => {
|
||||
try!(writeln!(f, "recipe {} has unknown dependency {}", recipe, unknown));
|
||||
}
|
||||
@ -782,6 +799,7 @@ impl<'a> Parser<'a> {
|
||||
}
|
||||
|
||||
let mut lines = vec![];
|
||||
let mut line_tokens = vec![];
|
||||
let mut shebang = false;
|
||||
|
||||
if self.accepted(Indent) {
|
||||
@ -794,8 +812,8 @@ impl<'a> Parser<'a> {
|
||||
} else if !shebang && (line.lexeme.starts_with(' ') || line.lexeme.starts_with('\t')) {
|
||||
return Err(line.error(ErrorKind::ExtraLeadingWhitespace));
|
||||
}
|
||||
|
||||
lines.push(line.lexeme);
|
||||
line_tokens.push(line);
|
||||
if !self.peek(Dedent) {
|
||||
if let Some(token) = self.expect_eol() {
|
||||
return Err(self.unexpected_token(&token, &[Eol]));
|
||||
@ -813,6 +831,46 @@ impl<'a> Parser<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
let mut fragments = vec![];
|
||||
let mut variables = BTreeSet::new();
|
||||
|
||||
lazy_static! {
|
||||
static ref FRAGMENT: Regex = re(r"^(.*?)\{\{(.*?)\}\}" );
|
||||
static ref UNMATCHED: Regex = re(r"^.*?\{\{" );
|
||||
static ref VARIABLE: Regex = re(r"^[ \t]*([a-z](-?[a-z0-9])*)[ \t]*$");
|
||||
}
|
||||
|
||||
for line in &line_tokens {
|
||||
let mut line_fragments = vec![];
|
||||
let mut rest = line.lexeme;
|
||||
while !rest.is_empty() {
|
||||
if let Some(captures) = FRAGMENT.captures(rest) {
|
||||
let prefix = captures.at(1).unwrap();
|
||||
if !prefix.is_empty() {
|
||||
line_fragments.push(Fragment::Text{text: prefix});
|
||||
}
|
||||
let interior = captures.at(2).unwrap();
|
||||
if let Some(captures) = VARIABLE.captures(interior) {
|
||||
let name = captures.at(1).unwrap();
|
||||
line_fragments.push(Fragment::Variable{name: name});
|
||||
variables.insert(name);
|
||||
} else {
|
||||
return Err(line.error(ErrorKind::BadInterpolationVariableName{
|
||||
recipe: name,
|
||||
text: interior,
|
||||
}));
|
||||
}
|
||||
rest = &rest[captures.at(0).unwrap().len()..];
|
||||
} else if UNMATCHED.is_match(rest) {
|
||||
return Err(line.error(ErrorKind::UnmatchedInterpolationDelimiter{recipe: name}));
|
||||
} else {
|
||||
line_fragments.push(Fragment::Text{text: rest});
|
||||
rest = "";
|
||||
}
|
||||
}
|
||||
fragments.push(line_fragments);
|
||||
}
|
||||
|
||||
Ok(Recipe {
|
||||
line_number: line_number,
|
||||
name: name,
|
||||
@ -820,6 +878,8 @@ impl<'a> Parser<'a> {
|
||||
dependency_tokens: dependency_tokens,
|
||||
arguments: arguments,
|
||||
argument_tokens: argument_tokens,
|
||||
fragments: fragments,
|
||||
variables: variables,
|
||||
lines: lines,
|
||||
shebang: shebang,
|
||||
})
|
||||
|
32
src/tests.rs
32
src/tests.rs
@ -58,6 +58,10 @@ fn parse_summary(input: &str, output: &str) {
|
||||
for recipe in justfile.recipes {
|
||||
s += &format!("{}\n", recipe.1);
|
||||
}
|
||||
if s != output {
|
||||
println!("got:\n\"{}\"\n", s);
|
||||
println!("\texpected:\n\"{}\"", output);
|
||||
}
|
||||
assert_eq!(s, output);
|
||||
}
|
||||
|
||||
@ -174,12 +178,14 @@ z:
|
||||
hello a b c : x y z #hello
|
||||
#! blah
|
||||
#blarg
|
||||
{{ hello }}
|
||||
1
|
||||
2
|
||||
3
|
||||
", "hello a b c: x y z
|
||||
#! blah
|
||||
#blarg
|
||||
{{hello}}
|
||||
1
|
||||
2
|
||||
3
|
||||
@ -434,3 +440,29 @@ fn bad_recipe_names() {
|
||||
bad_name("a: 9a", "9a", 3, 0, 3);
|
||||
bad_name("a:\nZ:", "Z", 3, 1, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bad_interpolation_variable_name() {
|
||||
let text = "a:\n echo {{hello--hello}}";
|
||||
parse_error(text, Error {
|
||||
text: text,
|
||||
index: 4,
|
||||
line: 1,
|
||||
column: 1,
|
||||
width: Some(21),
|
||||
kind: ErrorKind::BadInterpolationVariableName{recipe: "a", text: "hello--hello"}
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unmatched_interpolation_delimiter() {
|
||||
let text = "a:\n echo {{";
|
||||
parse_error(text, Error {
|
||||
text: text,
|
||||
index: 4,
|
||||
line: 1,
|
||||
column: 1,
|
||||
width: Some(7),
|
||||
kind: ErrorKind::UnmatchedInterpolationDelimiter{recipe: "a"}
|
||||
});
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user