diff --git a/src/lib.rs b/src/lib.rs index b0104ff..c674692 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,9 +16,9 @@ extern crate tempdir; use std::io::prelude::*; use std::{fs, fmt, process, io}; -use std::collections::{BTreeMap, HashSet}; use std::fmt::Display; use regex::Regex; +use std::collections::{BTreeMap as Map, BTreeSet as Set}; use std::os::unix::fs::PermissionsExt; @@ -188,17 +188,17 @@ impl<'a> Recipe<'a> { fn run( &self, arguments: &[&'a str], - scope: &BTreeMap<&'a str, String>, + scope: &Map<&'a str, String>, dry_run: bool, ) -> Result<(), RunError<'a>> { let argument_map = arguments .iter().enumerate() .map(|(i, argument)| (self.arguments[i], *argument)).collect(); let mut evaluator = Evaluator { - evaluated: BTreeMap::new(), + evaluated: Map::new(), scope: scope, - assignments: &BTreeMap::new(), - overrides: &BTreeMap::new(), + assignments: &Map::new(), + overrides: &Map::new(), }; if self.shebang { @@ -339,14 +339,14 @@ impl<'a> Display for Recipe<'a> { } fn resolve_recipes<'a>( - recipes: &BTreeMap<&'a str, Recipe<'a>>, - assignments: &BTreeMap<&'a str, Expression<'a>>, + recipes: &Map<&'a str, Recipe<'a>>, + assignments: &Map<&'a str, Expression<'a>>, text: &'a str, ) -> Result<(), Error<'a>> { let mut resolver = Resolver { - seen: HashSet::new(), + seen: Set::new(), stack: vec![], - resolved: HashSet::new(), + resolved: Set::new(), recipes: recipes, }; @@ -393,9 +393,9 @@ fn resolve_recipes<'a>( struct Resolver<'a: 'b, 'b> { stack: Vec<&'a str>, - seen: HashSet<&'a str>, - resolved: HashSet<&'a str>, - recipes: &'b BTreeMap<&'a str, Recipe<'a>>, + seen: Set<&'a str>, + resolved: Set<&'a str>, + recipes: &'b Map<&'a str, Recipe<'a>>, } impl<'a, 'b> Resolver<'a, 'b> { @@ -433,16 +433,16 @@ impl<'a, 'b> Resolver<'a, 'b> { } fn resolve_assignments<'a>( - assignments: &BTreeMap<&'a str, Expression<'a>>, - assignment_tokens: &BTreeMap<&'a str, Token<'a>>, + assignments: &Map<&'a str, Expression<'a>>, + assignment_tokens: &Map<&'a str, Token<'a>>, ) -> Result<(), Error<'a>> { let mut resolver = AssignmentResolver { assignments: assignments, assignment_tokens: assignment_tokens, stack: vec![], - seen: HashSet::new(), - evaluated: HashSet::new(), + seen: Set::new(), + evaluated: Set::new(), }; for name in assignments.keys() { @@ -453,11 +453,11 @@ fn resolve_assignments<'a>( } struct AssignmentResolver<'a: 'b, 'b> { - assignments: &'b BTreeMap<&'a str, Expression<'a>>, - assignment_tokens: &'b BTreeMap<&'a str, Token<'a>>, + assignments: &'b Map<&'a str, Expression<'a>>, + assignment_tokens: &'b Map<&'a str, Token<'a>>, stack: Vec<&'a str>, - seen: HashSet<&'a str>, - evaluated: HashSet<&'a str>, + seen: Set<&'a str>, + evaluated: Set<&'a str>, } impl<'a: 'b, 'b> AssignmentResolver<'a, 'b> { @@ -507,12 +507,12 @@ impl<'a: 'b, 'b> AssignmentResolver<'a, 'b> { } fn evaluate_assignments<'a>( - assignments: &BTreeMap<&'a str, Expression<'a>>, - overrides: &BTreeMap<&str, &str>, -) -> Result, RunError<'a>> { + assignments: &Map<&'a str, Expression<'a>>, + overrides: &Map<&str, &str>, +) -> Result, RunError<'a>> { let mut evaluator = Evaluator { - evaluated: BTreeMap::new(), - scope: &BTreeMap::new(), + evaluated: Map::new(), + scope: &Map::new(), assignments: assignments, overrides: overrides, }; @@ -525,17 +525,17 @@ fn evaluate_assignments<'a>( } struct Evaluator<'a: 'b, 'b> { - evaluated: BTreeMap<&'a str, String>, - scope: &'b BTreeMap<&'a str, String>, - assignments: &'b BTreeMap<&'a str, Expression<'a>>, - overrides: &'b BTreeMap<&'b str, &'b str>, + evaluated: Map<&'a str, String>, + scope: &'b Map<&'a str, String>, + assignments: &'b Map<&'a str, Expression<'a>>, + overrides: &'b Map<&'b str, &'b str>, } impl<'a, 'b> Evaluator<'a, 'b> { fn evaluate_line( &mut self, line: &[Fragment<'a>], - arguments: &BTreeMap<&str, &str> + arguments: &Map<&str, &str> ) -> Result> { let mut evaluated = String::new(); for fragment in line { @@ -558,7 +558,7 @@ impl<'a, 'b> Evaluator<'a, 'b> { if let Some(value) = self.overrides.get(name) { self.evaluated.insert(name, value.to_string()); } else { - let value = try!(self.evaluate_expression(expression, &BTreeMap::new())); + let value = try!(self.evaluate_expression(expression, &Map::new())); self.evaluated.insert(name, value); } } else { @@ -573,7 +573,7 @@ impl<'a, 'b> Evaluator<'a, 'b> { fn evaluate_expression( &mut self, expression: &Expression<'a>, - arguments: &BTreeMap<&str, &str> + arguments: &Map<&str, &str> ) -> Result> { Ok(match *expression { Expression::Variable{name, ..} => { @@ -616,7 +616,6 @@ struct Error<'a> { #[derive(Debug, PartialEq)] enum ErrorKind<'a> { ArgumentShadowsVariable{argument: &'a str}, - BadName{name: &'a str}, CircularRecipeDependency{recipe: &'a str, circle: Vec<&'a str>}, CircularVariableDependency{variable: &'a str, circle: Vec<&'a str>}, DependencyHasArguments{recipe: &'a str, dependency: &'a str}, @@ -698,9 +697,6 @@ impl<'a> Display for Error<'a> { try!(write!(f, "error: ")); match self.kind { - ErrorKind::BadName{name} => { - try!(writeln!(f, "name `{}` did not match /[a-z](-?[a-z0-9])*/", name)); - } ErrorKind::CircularRecipeDependency{recipe, ref circle} => { if circle.len() == 2 { try!(write!(f, "recipe `{}` depends on itself", recipe)); @@ -792,8 +788,8 @@ impl<'a> Display for Error<'a> { } struct Justfile<'a> { - recipes: BTreeMap<&'a str, Recipe<'a>>, - assignments: BTreeMap<&'a str, Expression<'a>>, + recipes: Map<&'a str, Recipe<'a>>, + assignments: Map<&'a str, Expression<'a>>, } impl<'a, 'b> Justfile<'a> where 'a: 'b { @@ -821,7 +817,7 @@ impl<'a, 'b> Justfile<'a> where 'a: 'b { fn run( &'a self, - overrides: &BTreeMap<&'a str, &'a str>, + overrides: &Map<&'a str, &'a str>, arguments: &[&'a str], dry_run: bool, evaluate: bool, @@ -842,7 +838,7 @@ impl<'a, 'b> Justfile<'a> where 'a: 'b { return Ok(()); } - let mut ran = HashSet::new(); + let mut ran = Set::new(); for (i, argument) in arguments.iter().enumerate() { if let Some(recipe) = self.recipes.get(argument) { @@ -885,8 +881,8 @@ impl<'a, 'b> Justfile<'a> where 'a: 'b { &'c self, recipe: &Recipe<'a>, arguments: &[&'a str], - scope: &BTreeMap<&'c str, String>, - ran: &mut HashSet<&'a str>, + scope: &Map<&'c str, String>, + ran: &mut Set<&'a str>, dry_run: bool, ) -> Result<(), RunError> { for dependency_name in &recipe.dependencies { @@ -1103,17 +1099,17 @@ fn token(pattern: &str) -> Regex { fn tokenize(text: &str) -> Result, Error> { lazy_static! { - static ref BACKTICK: Regex = token(r"`[^`\n\r]*`" ); - static ref COLON: 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" ); - static ref EQUALS: Regex = token(r"=" ); - static ref INTERPOLATION_END: Regex = token(r"[}][}]" ); - static ref INTERPOLATION_START_TOKEN: Regex = token(r"[{][{]" ); - static ref NAME: Regex = token(r"([a-zA-Z0-9_-]+)" ); - static ref PLUS: Regex = token(r"[+]" ); - static ref STRING: Regex = token("\"" ); + static ref BACKTICK: Regex = token(r"`[^`\n\r]*`" ); + static ref COLON: 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" ); + static ref EQUALS: Regex = token(r"=" ); + static ref INTERPOLATION_END: Regex = token(r"[}][}]" ); + static ref INTERPOLATION_START_TOKEN: Regex = token(r"[{][{]" ); + static ref NAME: Regex = token(r"([a-zA-Z_-][a-zA-Z0-9_-]*)"); + static ref PLUS: Regex = token(r"[+]" ); + static ref STRING: Regex = token("\"" ); static ref INDENT: Regex = re(r"^([ \t]*)[^ \t\n\r]" ); static ref INTERPOLATION_START: Regex = re(r"^[{][{]" ); static ref LEADING_TEXT: Regex = re(r"^(?m)(.+?)[{][{]" ); @@ -1347,15 +1343,6 @@ fn tokenize(text: &str) -> Result, Error> { fn parse(text: &str) -> Result { let tokens = try!(tokenize(text)); let filtered: Vec<_> = tokens.into_iter().filter(|token| token.kind != Comment).collect(); - if let Some(token) = filtered.iter().find(|token| { - lazy_static! { - static ref GOOD_NAME: Regex = re("^[a-z](-?[a-z0-9])*$"); - } - token.kind == Name && !GOOD_NAME.is_match(token.lexeme) - }) { - return Err(token.error(ErrorKind::BadName{name: token.lexeme})); - } - let parser = Parser{ text: text, tokens: filtered.into_iter().peekable() @@ -1563,9 +1550,10 @@ impl<'a> Parser<'a> { } fn file(mut self) -> Result, Error<'a>> { - let mut recipes = BTreeMap::<&str, Recipe>::new(); - let mut assignments = BTreeMap::<&str, Expression>::new(); - let mut assignment_tokens = BTreeMap::<&str, Token<'a>>::new(); + let mut recipes = Map::<&str, Recipe>::new(); + let mut assignments = Map::<&str, Expression>::new(); + let mut assignment_tokens = Map::<&str, Token<'a>>::new(); + let mut exports = Set::<&str>::new(); loop { match self.tokens.next() { diff --git a/src/unit.rs b/src/unit.rs index 17e2e30..2e83288 100644 --- a/src/unit.rs +++ b/src/unit.rs @@ -2,7 +2,7 @@ extern crate tempdir; use super::{Token, Error, ErrorKind, Justfile, RunError}; use super::TokenKind::*; -use std::collections::BTreeMap; +use std::collections::BTreeMap as Map; fn tokenize_success(text: &str, expected_summary: &str) { let tokens = super::tokenize(text).unwrap(); @@ -579,7 +579,7 @@ fn conjoin_and() { #[test] fn unknown_recipes() { - match parse_success("a:\nb:\nc:").run(&BTreeMap::new(), &["a", "x", "y", "z"], false, false).unwrap_err() { + match parse_success("a:\nb:\nc:").run(&Map::new(), &["a", "x", "y", "z"], false, false).unwrap_err() { RunError::UnknownRecipes{recipes} => assert_eq!(recipes, &["x", "y", "z"]), other => panic!("expected an unknown recipe error, but got: {}", other), } @@ -603,49 +603,6 @@ fn extra_whitespace() { parse_success("a:\n #!\n print(1)"); } -#[test] -fn bad_recipe_names() { - // We are extra strict with names. Although the tokenizer - // will tokenize anything that matches /[a-zA-Z0-9_-]+/ - // as a name, we throw an error if names do not match - // / [a-z](-?[a-z])* /. This is to support future expansion - // of justfile and command line syntax. - fn bad_name(text: &str, name: &str, index: usize, line: usize, column: usize) { - parse_error(text, Error { - text: text, - index: index, - line: line, - column: column, - width: Some(name.len()), - kind: ErrorKind::BadName{name: name} - }); - } - - bad_name("-a", "-a", 0, 0, 0); - bad_name("_a", "_a", 0, 0, 0); - bad_name("a-", "a-", 0, 0, 0); - bad_name("a_", "a_", 0, 0, 0); - bad_name("a__a", "a__a", 0, 0, 0); - bad_name("a--a", "a--a", 0, 0, 0); - bad_name("a: a--", "a--", 3, 0, 3); - bad_name("a: 9a", "9a", 3, 0, 3); - 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: 11, - line: 1, - column: 8, - width: Some(12), - kind: ErrorKind::BadName{name: "hello--hello"} - }); -} - #[test] fn interpolation_outside_of_recipe() { let text = "{{"; @@ -747,7 +704,7 @@ a: x "; - match parse_success(text).run(&BTreeMap::new(), &["a"], false, false).unwrap_err() { + match parse_success(text).run(&Map::new(), &["a"], false, false).unwrap_err() { RunError::Code{recipe, code} => { assert_eq!(recipe, "a"); assert_eq!(code, 200); @@ -758,7 +715,7 @@ a: #[test] fn code_error() { - match parse_success("fail:\n @function x { return 100; }; x").run(&BTreeMap::new(), &["fail"], false, false).unwrap_err() { + match parse_success("fail:\n @function x { return 100; }; x").run(&Map::new(), &["fail"], false, false).unwrap_err() { RunError::Code{recipe, code} => { assert_eq!(recipe, "fail"); assert_eq!(code, 100); @@ -773,7 +730,7 @@ fn run_args() { a return code: @function x { {{return}} {{code + "0"}}; }; x"#; - match parse_success(text).run(&BTreeMap::new(), &["a", "return", "15"], false, false).unwrap_err() { + match parse_success(text).run(&Map::new(), &["a", "return", "15"], false, false).unwrap_err() { RunError::Code{recipe, code} => { assert_eq!(recipe, "a"); assert_eq!(code, 150); @@ -784,7 +741,7 @@ a return code: #[test] fn missing_args() { - match parse_success("a b c d:").run(&BTreeMap::new(), &["a", "b", "c"], false, false).unwrap_err() { + match parse_success("a b c d:").run(&Map::new(), &["a", "b", "c"], false, false).unwrap_err() { RunError::ArgumentCountMismatch{recipe, found, expected} => { assert_eq!(recipe, "a"); assert_eq!(found, 2); @@ -796,7 +753,7 @@ fn missing_args() { #[test] fn missing_default() { - match parse_success("a b c d:\n echo {{b}}{{c}}{{d}}").run(&BTreeMap::new(), &["a"], false, false).unwrap_err() { + match parse_success("a b c d:\n echo {{b}}{{c}}{{d}}").run(&Map::new(), &["a"], false, false).unwrap_err() { RunError::ArgumentCountMismatch{recipe, found, expected} => { assert_eq!(recipe, "a"); assert_eq!(found, 0); @@ -808,7 +765,7 @@ fn missing_default() { #[test] fn backtick_code() { - match parse_success("a:\n echo {{`function f { return 100; }; f`}}").run(&BTreeMap::new(), &["a"], false, false).unwrap_err() { + match parse_success("a:\n echo {{`function f { return 100; }; f`}}").run(&Map::new(), &["a"], false, false).unwrap_err() { RunError::BacktickCode{code, token} => { assert_eq!(code, 100); assert_eq!(token.lexeme, "`function f { return 100; }; f`"); @@ -819,7 +776,7 @@ fn backtick_code() { #[test] fn unknown_overrides() { - let mut overrides = BTreeMap::new(); + let mut overrides = Map::new(); overrides.insert("foo", "bar"); overrides.insert("baz", "bob"); match parse_success("a:\n echo {{`function f { return 100; }; f`}}")