Parsing and tokenizing tests are now mostly passsing, not running
recipes though.
This commit is contained in:
parent
aae665a4e9
commit
d5f81dc0b4
2
notes
2
notes
@ -37,6 +37,8 @@ notes
|
|||||||
or should non-slash recipes still run in this directory?
|
or should non-slash recipes still run in this directory?
|
||||||
will need to change things a great deal
|
will need to change things a great deal
|
||||||
- indentation is line continuation
|
- indentation is line continuation
|
||||||
|
- should i disallow a shebang recipe where the shebang isn't on the first line?
|
||||||
|
- add insane borrow checker issue to issue tracker
|
||||||
- add context to unexpected_token error
|
- add context to unexpected_token error
|
||||||
"while parsing a recipe"
|
"while parsing a recipe"
|
||||||
"while parsing an expression"
|
"while parsing an expression"
|
||||||
|
161
src/lib.rs
161
src/lib.rs
@ -13,7 +13,7 @@ extern crate tempdir;
|
|||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
|
|
||||||
use std::{fs, fmt, process, io};
|
use std::{fs, fmt, process, io};
|
||||||
use std::collections::{BTreeMap, BTreeSet, HashSet};
|
use std::collections::{BTreeMap, HashSet};
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
||||||
@ -54,10 +54,11 @@ fn re(pattern: &str) -> Regex {
|
|||||||
struct Recipe<'a> {
|
struct Recipe<'a> {
|
||||||
line_number: usize,
|
line_number: usize,
|
||||||
name: &'a str,
|
name: &'a str,
|
||||||
lines: Vec<&'a str>,
|
lines: Vec<String>,
|
||||||
fragments: Vec<Vec<Fragment<'a>>>,
|
// fragments: Vec<Vec<Fragment<'a>>>,
|
||||||
variables: BTreeSet<&'a str>,
|
// variables: BTreeSet<&'a str>,
|
||||||
variable_tokens: Vec<Token<'a>>,
|
// variable_tokens: Vec<Token<'a>>,
|
||||||
|
new_lines: Vec<Vec<Fragmant<'a>>>,
|
||||||
dependencies: Vec<&'a str>,
|
dependencies: Vec<&'a str>,
|
||||||
dependency_tokens: Vec<Token<'a>>,
|
dependency_tokens: Vec<Token<'a>>,
|
||||||
arguments: Vec<&'a str>,
|
arguments: Vec<&'a str>,
|
||||||
@ -66,17 +67,47 @@ struct Recipe<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Debug)]
|
#[derive(PartialEq, Debug)]
|
||||||
enum Fragment<'a> {
|
enum Fragmant<'a> {
|
||||||
Text{text: &'a str},
|
Text{text: Token<'a>},
|
||||||
Variable{name: &'a str},
|
Expression{expression: Expression<'a>},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Debug)]
|
||||||
enum Expression<'a> {
|
enum Expression<'a> {
|
||||||
Variable{name: &'a str, token: Token<'a>},
|
Variable{name: &'a str, token: Token<'a>},
|
||||||
String{contents: &'a str},
|
String{contents: &'a str},
|
||||||
Concatination{lhs: Box<Expression<'a>>, rhs: Box<Expression<'a>>},
|
Concatination{lhs: Box<Expression<'a>>, rhs: Box<Expression<'a>>},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> Expression<'a> {
|
||||||
|
fn variables(&'a self) -> Variables<'a> {
|
||||||
|
Variables {
|
||||||
|
stack: vec![self],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Variables<'a> {
|
||||||
|
stack: Vec<&'a Expression<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Iterator for Variables<'a> {
|
||||||
|
type Item = &'a Token<'a>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<&'a Token<'a>> {
|
||||||
|
match self.stack.pop() {
|
||||||
|
None => None,
|
||||||
|
Some(&Expression::Variable{ref token,..}) => Some(token),
|
||||||
|
Some(&Expression::String{..}) => None,
|
||||||
|
Some(&Expression::Concatination{ref lhs, ref rhs}) => {
|
||||||
|
self.stack.push(lhs);
|
||||||
|
self.stack.push(rhs);
|
||||||
|
self.next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> Display for Expression<'a> {
|
impl<'a> Display for Expression<'a> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||||
match *self {
|
match *self {
|
||||||
@ -118,7 +149,7 @@ impl<'a> Recipe<'a> {
|
|||||||
);
|
);
|
||||||
let mut text = String::new();
|
let mut text = String::new();
|
||||||
// add the shebang
|
// add the shebang
|
||||||
text += self.lines[0];
|
text += &self.lines[0];
|
||||||
text += "\n";
|
text += "\n";
|
||||||
// add blank lines so that lines in the generated script
|
// add blank lines so that lines in the generated script
|
||||||
// have the same line number as the corresponding lines
|
// have the same line number as the corresponding lines
|
||||||
@ -127,7 +158,7 @@ impl<'a> Recipe<'a> {
|
|||||||
text += "\n"
|
text += "\n"
|
||||||
}
|
}
|
||||||
for line in &self.lines[1..] {
|
for line in &self.lines[1..] {
|
||||||
text += line;
|
text += &line;
|
||||||
text += "\n";
|
text += "\n";
|
||||||
}
|
}
|
||||||
try!(
|
try!(
|
||||||
@ -163,7 +194,7 @@ impl<'a> Recipe<'a> {
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
for command in &self.lines {
|
for command in &self.lines {
|
||||||
let mut command = *command;
|
let mut command = &command[0..];
|
||||||
if !command.starts_with('@') {
|
if !command.starts_with('@') {
|
||||||
warn!("{}", command);
|
warn!("{}", command);
|
||||||
} else {
|
} else {
|
||||||
@ -202,21 +233,20 @@ impl<'a> Display for Recipe<'a> {
|
|||||||
try!(write!(f, " {}", dependency))
|
try!(write!(f, " {}", dependency))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (i, pieces) in self.new_lines.iter().enumerate() {
|
||||||
for (i, fragments) in self.fragments.iter().enumerate() {
|
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
try!(writeln!(f, ""));
|
try!(writeln!(f, ""));
|
||||||
}
|
}
|
||||||
for (j, fragment) in fragments.iter().enumerate() {
|
for (j, piece) in pieces.iter().enumerate() {
|
||||||
if j == 0 {
|
if j == 0 {
|
||||||
try!(write!(f, " "));
|
try!(write!(f, " "));
|
||||||
}
|
}
|
||||||
match *fragment {
|
match piece {
|
||||||
Fragment::Text{text} => try!(write!(f, "{}", text)),
|
&Fragmant::Text{ref text} => try!(write!(f, "{}", text.lexeme)),
|
||||||
Fragment::Variable{name} => try!(write!(f, "{}{}{}", "{{", name, "}}")),
|
&Fragmant::Expression{ref expression} => try!(write!(f, "{}{}{}", "{{", expression, "}}")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if i + 1 < self.fragments.len() {
|
if i + 1 < self.new_lines.len() {
|
||||||
try!(write!(f, "\n"));
|
try!(write!(f, "\n"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -378,8 +408,6 @@ enum ErrorKind<'a> {
|
|||||||
DuplicateVariable{variable: &'a str},
|
DuplicateVariable{variable: &'a str},
|
||||||
ArgumentShadowsVariable{argument: &'a str},
|
ArgumentShadowsVariable{argument: &'a str},
|
||||||
MixedLeadingWhitespace{whitespace: &'a str},
|
MixedLeadingWhitespace{whitespace: &'a str},
|
||||||
UnclosedInterpolationDelimiter,
|
|
||||||
BadInterpolationVariableName{recipe: &'a str, text: &'a str},
|
|
||||||
ExtraLeadingWhitespace,
|
ExtraLeadingWhitespace,
|
||||||
InconsistentLeadingWhitespace{expected: &'a str, found: &'a str},
|
InconsistentLeadingWhitespace{expected: &'a str, found: &'a str},
|
||||||
OuterShebang,
|
OuterShebang,
|
||||||
@ -478,12 +506,6 @@ impl<'a> Display for Error<'a> {
|
|||||||
ErrorKind::OuterShebang => {
|
ErrorKind::OuterShebang => {
|
||||||
try!(writeln!(f, "a shebang \"#!\" is reserved syntax outside of recipes"))
|
try!(writeln!(f, "a shebang \"#!\" is reserved syntax outside of recipes"))
|
||||||
}
|
}
|
||||||
ErrorKind::UnclosedInterpolationDelimiter => {
|
|
||||||
try!(writeln!(f, "unmatched {}", "{{"))
|
|
||||||
}
|
|
||||||
ErrorKind::BadInterpolationVariableName{recipe, text} => {
|
|
||||||
try!(writeln!(f, "recipe {} contains a bad variable interpolation: {}", recipe, text))
|
|
||||||
}
|
|
||||||
ErrorKind::UnknownDependency{recipe, unknown} => {
|
ErrorKind::UnknownDependency{recipe, unknown} => {
|
||||||
try!(writeln!(f, "recipe {} has unknown dependency {}", recipe, unknown));
|
try!(writeln!(f, "recipe {} has unknown dependency {}", recipe, unknown));
|
||||||
}
|
}
|
||||||
@ -660,22 +682,6 @@ impl<'a> Token<'a> {
|
|||||||
kind: kind,
|
kind: kind,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
fn split(
|
|
||||||
self,
|
|
||||||
leading_prefix_len: usize,
|
|
||||||
lexeme_len: usize,
|
|
||||||
trailing_prefix_len: usize,
|
|
||||||
) -> (Token<'a>, Token<'a>) {
|
|
||||||
let len = self.prefix.len() + self.lexeme.len();
|
|
||||||
|
|
||||||
// let length = self.prefix.len() + self.lexeme.len();
|
|
||||||
// if lexeme_start > lexeme_end || lexeme_end > length {
|
|
||||||
// }
|
|
||||||
// panic!("Tried to split toke
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||||
@ -1071,29 +1077,37 @@ impl<'a> Parser<'a> {
|
|||||||
return Err(self.unexpected_token(&token, &[Name, Eol, Eof]));
|
return Err(self.unexpected_token(&token, &[Name, Eol, Eof]));
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Piece<'a> {
|
|
||||||
Text{text: Token<'a>},
|
|
||||||
Expression{expression: Expression<'a>},
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut new_lines = vec![];
|
let mut new_lines = vec![];
|
||||||
|
let mut shebang = false;
|
||||||
|
|
||||||
if self.accepted(Indent) {
|
if self.accepted(Indent) {
|
||||||
while !self.accepted(Dedent) {
|
while !self.accepted(Dedent) {
|
||||||
|
if self.accepted(Eol) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if let Some(token) = self.expect(Line) {
|
if let Some(token) = self.expect(Line) {
|
||||||
return Err(token.error(ErrorKind::InternalError{
|
return Err(token.error(ErrorKind::InternalError{
|
||||||
message: format!("Expected a dedent but got {}", token.class)
|
message: format!("Expected a line but got {}", token.class)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
let mut pieces = vec![];
|
let mut pieces = vec![];
|
||||||
|
|
||||||
while !self.accepted(Eol) {
|
while !(self.accepted(Eol) || self.peek(Dedent)) {
|
||||||
if let Some(token) = self.accept(Text) {
|
if let Some(token) = self.accept(Text) {
|
||||||
pieces.push(Piece::Text{text: token});
|
if pieces.is_empty() {
|
||||||
|
if new_lines.is_empty() {
|
||||||
|
if token.lexeme.starts_with("#!") {
|
||||||
|
shebang = true;
|
||||||
|
}
|
||||||
|
} else if !shebang && token.lexeme.starts_with(" ") || token.lexeme.starts_with("\t") {
|
||||||
|
return Err(token.error(ErrorKind::ExtraLeadingWhitespace));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pieces.push(Fragmant::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(Piece::Expression{expression: try!(self.expression(true))});
|
pieces.push(Fragmant::Expression{expression: try!(self.expression(true))});
|
||||||
if let Some(token) = self.expect(InterpolationEnd) {
|
if let Some(token) = self.expect(InterpolationEnd) {
|
||||||
return Err(self.unexpected_token(&token, &[InterpolationEnd]));
|
return Err(self.unexpected_token(&token, &[InterpolationEnd]));
|
||||||
}
|
}
|
||||||
@ -1104,8 +1118,7 @@ impl<'a> Parser<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
panic!("done!");
|
/*
|
||||||
|
|
||||||
let mut lines = vec![];
|
let mut lines = vec![];
|
||||||
let mut line_tokens = vec![];
|
let mut line_tokens = vec![];
|
||||||
let mut shebang = false;
|
let mut shebang = false;
|
||||||
@ -1195,6 +1208,7 @@ impl<'a> Parser<'a> {
|
|||||||
}
|
}
|
||||||
fragments.push(line_fragments);
|
fragments.push(line_fragments);
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
Ok(Recipe {
|
Ok(Recipe {
|
||||||
line_number: line_number,
|
line_number: line_number,
|
||||||
@ -1203,10 +1217,12 @@ impl<'a> Parser<'a> {
|
|||||||
dependency_tokens: dependency_tokens,
|
dependency_tokens: dependency_tokens,
|
||||||
arguments: arguments,
|
arguments: arguments,
|
||||||
argument_tokens: argument_tokens,
|
argument_tokens: argument_tokens,
|
||||||
fragments: fragments,
|
// fragments: fragments,
|
||||||
variables: variables,
|
// variables: variables,
|
||||||
variable_tokens: variable_tokens,
|
// variable_tokens: variable_tokens,
|
||||||
lines: lines,
|
lines: vec![],
|
||||||
|
new_lines: new_lines,
|
||||||
|
// lines: lines,
|
||||||
shebang: shebang,
|
shebang: shebang,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -1226,7 +1242,7 @@ impl<'a> Parser<'a> {
|
|||||||
Ok(lhs)
|
Ok(lhs)
|
||||||
} else if let Some(token) = self.expect_eol() {
|
} else if let Some(token) = self.expect_eol() {
|
||||||
if interpolation {
|
if interpolation {
|
||||||
Err(self.unexpected_token(&token, &[Plus, Eol, InterpolationEnd]))
|
return Err(self.unexpected_token(&token, &[Plus, Eol, InterpolationEnd]))
|
||||||
} else {
|
} else {
|
||||||
Err(self.unexpected_token(&token, &[Plus, Eol]))
|
Err(self.unexpected_token(&token, &[Plus, Eol]))
|
||||||
}
|
}
|
||||||
@ -1299,10 +1315,35 @@ impl<'a> Parser<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for variable in &recipe.variable_tokens {
|
for line in &recipe.new_lines {
|
||||||
|
for piece in line {
|
||||||
|
if let &Fragmant::Expression{ref expression} = piece {
|
||||||
|
for variable in expression.variables() {
|
||||||
let name = variable.lexeme;
|
let name = variable.lexeme;
|
||||||
if !(assignments.contains_key(&name) || recipe.arguments.contains(&name)) {
|
if !(assignments.contains_key(&name) || recipe.arguments.contains(&name)) {
|
||||||
return Err(variable.error(ErrorKind::UnknownVariable{variable: name}));
|
// There's a borrow issue here that seems to difficult to solve.
|
||||||
|
// The error derived from the variable token has too short a lifetime,
|
||||||
|
// so we create a new error from its contents, which do live long
|
||||||
|
// enough.
|
||||||
|
//
|
||||||
|
// I suspect the solution here is to give recipes, pieces, and expressions
|
||||||
|
// two lifetime parameters instead of one, with one being the lifetime
|
||||||
|
// of the struct, and the second being the lifetime of the tokens
|
||||||
|
// that it contains
|
||||||
|
let error = variable.error(ErrorKind::UnknownVariable{variable: name});
|
||||||
|
return Err(Error {
|
||||||
|
text: self.text,
|
||||||
|
index: error.index,
|
||||||
|
line: error.line,
|
||||||
|
column: error.column,
|
||||||
|
width: error.width,
|
||||||
|
kind: ErrorKind::UnknownVariable {
|
||||||
|
variable: &self.text[error.index..error.index + error.width.unwrap()],
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
177
src/tests.rs
177
src/tests.rs
@ -53,7 +53,6 @@ fn token_summary(tokens: &[Token]) -> String {
|
|||||||
}).collect::<Vec<_>>().join("")
|
}).collect::<Vec<_>>().join("")
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
fn parse_success(text: &str) -> Justfile {
|
fn parse_success(text: &str) -> Justfile {
|
||||||
match super::parse(text) {
|
match super::parse(text) {
|
||||||
Ok(justfile) => justfile,
|
Ok(justfile) => justfile,
|
||||||
@ -84,7 +83,6 @@ fn parse_error(text: &str, expected: Error) {
|
|||||||
panic!("Expected {:?} but parse succeeded", expected.kind);
|
panic!("Expected {:?} but parse succeeded", expected.kind);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn tokenize_recipe_interpolation_eol() {
|
fn tokenize_recipe_interpolation_eol() {
|
||||||
@ -196,7 +194,7 @@ fn tokenize_tabs_then_tab_space() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn outer_shebang() {
|
fn tokenize_outer_shebang() {
|
||||||
let text = "#!/usr/bin/env bash";
|
let text = "#!/usr/bin/env bash";
|
||||||
tokenize_error(text, Error {
|
tokenize_error(text, Error {
|
||||||
text: text,
|
text: text,
|
||||||
@ -209,7 +207,7 @@ fn outer_shebang() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn unknown_start_of_token() {
|
fn tokenize_unknown() {
|
||||||
let text = "~";
|
let text = "~";
|
||||||
tokenize_error(text, Error {
|
tokenize_error(text, Error {
|
||||||
text: text,
|
text: text,
|
||||||
@ -221,7 +219,6 @@ fn unknown_start_of_token() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_empty() {
|
fn parse_empty() {
|
||||||
parse_summary("
|
parse_summary("
|
||||||
@ -239,20 +236,22 @@ x:
|
|||||||
y:
|
y:
|
||||||
z:
|
z:
|
||||||
foo = \"x\"
|
foo = \"x\"
|
||||||
|
bar = foo
|
||||||
goodbye = \"y\"
|
goodbye = \"y\"
|
||||||
hello a b c : x y z #hello
|
hello a b c : x y z #hello
|
||||||
#! blah
|
#! blah
|
||||||
#blarg
|
#blarg
|
||||||
{{ foo }}abc{{ goodbye\t }}xyz
|
{{ foo + bar}}abc{{ goodbye\t + \"x\" }}xyz
|
||||||
1
|
1
|
||||||
2
|
2
|
||||||
3
|
3
|
||||||
", "foo = \"x\" # \"x\"
|
", "bar = foo # \"x\"
|
||||||
|
foo = \"x\" # \"x\"
|
||||||
goodbye = \"y\" # \"y\"
|
goodbye = \"y\" # \"y\"
|
||||||
hello a b c: x y z
|
hello a b c: x y z
|
||||||
#! blah
|
#! blah
|
||||||
#blarg
|
#blarg
|
||||||
{{foo}}abc{{goodbye}}xyz
|
{{foo + bar}}abc{{goodbye + \"x\"}}xyz
|
||||||
1
|
1
|
||||||
2
|
2
|
||||||
3
|
3
|
||||||
@ -456,54 +455,6 @@ fn write_or() {
|
|||||||
assert_eq!("1, 2, 3, or 4", super::Or(&[1,2,3,4]).to_string());
|
assert_eq!("1, 2, 3, or 4", super::Or(&[1,2,3,4]).to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn run_shebang() {
|
|
||||||
// this test exists to make sure that shebang recipes
|
|
||||||
// run correctly. although this script is still
|
|
||||||
// executed by sh its behavior depends on the value of a
|
|
||||||
// variable and continuing even though a command fails,
|
|
||||||
// whereas in plain recipes variables are not available
|
|
||||||
// in subsequent lines and execution stops when a line
|
|
||||||
// fails
|
|
||||||
let text = "
|
|
||||||
a:
|
|
||||||
#!/usr/bin/env sh
|
|
||||||
code=200
|
|
||||||
function x { return $code; }
|
|
||||||
x
|
|
||||||
x
|
|
||||||
";
|
|
||||||
|
|
||||||
match parse_success(text).run(&["a"]).unwrap_err() {
|
|
||||||
super::RunError::Code{recipe, code} => {
|
|
||||||
assert_eq!(recipe, "a");
|
|
||||||
assert_eq!(code, 200);
|
|
||||||
},
|
|
||||||
other => panic!("expected an code run error, but got: {}", other),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn run_order() {
|
|
||||||
let tmp = tempdir::TempDir::new("run_order").unwrap_or_else(|err| panic!("tmpdir: failed to create temporary directory: {}", err));
|
|
||||||
let path = tmp.path().to_str().unwrap_or_else(|| panic!("tmpdir: path was not valid UTF-8")).to_owned();
|
|
||||||
let text = r"
|
|
||||||
b: a
|
|
||||||
@mv a b
|
|
||||||
|
|
||||||
a:
|
|
||||||
@touch a
|
|
||||||
|
|
||||||
d: c
|
|
||||||
@rm c
|
|
||||||
|
|
||||||
c: b
|
|
||||||
@mv b c
|
|
||||||
";
|
|
||||||
super::std::env::set_current_dir(path).expect("failed to set current directory");
|
|
||||||
parse_success(text).run(&["a", "d"]).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn unknown_recipes() {
|
fn unknown_recipes() {
|
||||||
match parse_success("a:\nb:\nc:").run(&["a", "x", "y", "z"]).unwrap_err() {
|
match parse_success("a:\nb:\nc:").run(&["a", "x", "y", "z"]).unwrap_err() {
|
||||||
@ -512,17 +463,6 @@ fn unknown_recipes() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn code_error() {
|
|
||||||
match parse_success("fail:\n @function x { return 100; }; x").run(&["fail"]).unwrap_err() {
|
|
||||||
super::RunError::Code{recipe, code} => {
|
|
||||||
assert_eq!(recipe, "fail");
|
|
||||||
assert_eq!(code, 100);
|
|
||||||
},
|
|
||||||
other @ _ => panic!("expected a code run error, but got: {}", other),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn extra_whitespace() {
|
fn extra_whitespace() {
|
||||||
// we might want to make extra leading whitespace a line continuation in the future,
|
// we might want to make extra leading whitespace a line continuation in the future,
|
||||||
@ -576,24 +516,24 @@ fn bad_interpolation_variable_name() {
|
|||||||
let text = "a:\n echo {{hello--hello}}";
|
let text = "a:\n echo {{hello--hello}}";
|
||||||
parse_error(text, Error {
|
parse_error(text, Error {
|
||||||
text: text,
|
text: text,
|
||||||
index: 4,
|
index: 11,
|
||||||
line: 1,
|
line: 1,
|
||||||
column: 1,
|
column: 8,
|
||||||
width: Some(21),
|
width: Some(12),
|
||||||
kind: ErrorKind::BadInterpolationVariableName{recipe: "a", text: "hello--hello"}
|
kind: ErrorKind::BadName{name: "hello--hello"}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn unclosed_interpolation_delimiter() {
|
fn unclosed_interpolation_delimiter() {
|
||||||
let text = "a:\n echo {{";
|
let text = "a:\n echo {{ foo";
|
||||||
parse_error(text, Error {
|
parse_error(text, Error {
|
||||||
text: text,
|
text: text,
|
||||||
index: 4,
|
index: 15,
|
||||||
line: 1,
|
line: 1,
|
||||||
column: 1,
|
column: 12,
|
||||||
width: Some(7),
|
width: Some(0),
|
||||||
kind: ErrorKind::UnclosedInterpolationDelimiter,
|
kind: ErrorKind::UnexpectedToken{expected: vec![Plus, Eol, InterpolationEnd], found: Dedent},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -621,15 +561,82 @@ fn unknown_interpolation_variable() {
|
|||||||
width: Some(5),
|
width: Some(5),
|
||||||
kind: ErrorKind::UnknownVariable{variable: "hello"},
|
kind: ErrorKind::UnknownVariable{variable: "hello"},
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// let text = "x:\n echo\n {{ lol }}";
|
#[test]
|
||||||
// parse_error(text, Error {
|
fn unknown_second_interpolation_variable() {
|
||||||
// text: text,
|
let text = "wtf=\"x\"\nx:\n echo\n foo {{wtf}} {{ lol }}";
|
||||||
// index: 11,
|
parse_error(text, Error {
|
||||||
// line: 2,
|
text: text,
|
||||||
// column: 2,
|
index: 33,
|
||||||
// width: Some(3),
|
line: 3,
|
||||||
// kind: ErrorKind::UnknownVariable{variable: "lol"},
|
column: 16,
|
||||||
// });
|
width: Some(3),
|
||||||
|
kind: ErrorKind::UnknownVariable{variable: "lol"},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn run_order() {
|
||||||
|
let tmp = tempdir::TempDir::new("run_order").unwrap_or_else(|err| panic!("tmpdir: failed to create temporary directory: {}", err));
|
||||||
|
let path = tmp.path().to_str().unwrap_or_else(|| panic!("tmpdir: path was not valid UTF-8")).to_owned();
|
||||||
|
let text = r"
|
||||||
|
b: a
|
||||||
|
@mv a b
|
||||||
|
|
||||||
|
a:
|
||||||
|
@touch F
|
||||||
|
@touch a
|
||||||
|
|
||||||
|
d: c
|
||||||
|
@rm c
|
||||||
|
|
||||||
|
c: b
|
||||||
|
@mv b c";
|
||||||
|
tokenize_success(text, "$N:N$>^_$$<N:$>^_$^_$$<N:N$>^_$$<N:N$>^_<.");
|
||||||
|
super::std::env::set_current_dir(path).expect("failed to set current directory");
|
||||||
|
parse_success(text).run(&["a", "d"]).unwrap();
|
||||||
|
if let Err(_) = super::std::fs::metadata("F") {
|
||||||
|
panic!("recipes did not run");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
#[test]
|
||||||
|
fn run_shebang() {
|
||||||
|
// this test exists to make sure that shebang recipes
|
||||||
|
// run correctly. although this script is still
|
||||||
|
// executed by sh its behavior depends on the value of a
|
||||||
|
// variable and continuing even though a command fails,
|
||||||
|
// whereas in plain recipes variables are not available
|
||||||
|
// in subsequent lines and execution stops when a line
|
||||||
|
// fails
|
||||||
|
let text = "
|
||||||
|
a:
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
code=200
|
||||||
|
function x { return $code; }
|
||||||
|
x
|
||||||
|
x
|
||||||
|
";
|
||||||
|
|
||||||
|
match parse_success(text).run(&["a"]).unwrap_err() {
|
||||||
|
super::RunError::Code{recipe, code} => {
|
||||||
|
assert_eq!(recipe, "a");
|
||||||
|
assert_eq!(code, 200);
|
||||||
|
},
|
||||||
|
other => panic!("expected an code run error, but got: {}", other),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn code_error() {
|
||||||
|
match parse_success("fail:\n @function x { return 100; }; x").run(&["fail"]).unwrap_err() {
|
||||||
|
super::RunError::Code{recipe, code} => {
|
||||||
|
assert_eq!(recipe, "fail");
|
||||||
|
assert_eq!(code, 100);
|
||||||
|
},
|
||||||
|
other @ _ => panic!("expected a code run error, but got: {}", other),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
Loading…
Reference in New Issue
Block a user