diff --git a/src/assignment_evaluator.rs b/src/assignment_evaluator.rs index 68bb59d..657062d 100644 --- a/src/assignment_evaluator.rs +++ b/src/assignment_evaluator.rs @@ -5,30 +5,30 @@ use brev; pub struct AssignmentEvaluator<'a: 'b, 'b> { pub assignments: &'b Map<&'a str, Expression<'a>>, pub invocation_directory: &'b Result, - pub dotenv: &'b Map, - pub dry_run: bool, - pub evaluated: Map<&'a str, String>, - pub exports: &'b Set<&'a str>, - pub overrides: &'b Map<&'b str, &'b str>, - pub quiet: bool, - pub scope: &'b Map<&'a str, String>, - pub shell: &'b str, + pub dotenv: &'b Map, + pub dry_run: bool, + pub evaluated: Map<&'a str, String>, + pub exports: &'b Set<&'a str>, + pub overrides: &'b Map<&'b str, &'b str>, + pub quiet: bool, + pub scope: &'b Map<&'a str, String>, + pub shell: &'b str, } impl<'a, 'b> AssignmentEvaluator<'a, 'b> { pub fn evaluate_assignments( assignments: &Map<&'a str, Expression<'a>>, invocation_directory: &Result, - dotenv: &'b Map, - overrides: &Map<&str, &str>, - quiet: bool, - shell: &'a str, - dry_run: bool, + dotenv: &'b Map, + overrides: &Map<&str, &str>, + quiet: bool, + shell: &'a str, + dry_run: bool, ) -> RunResult<'a, Map<&'a str, String>> { let mut evaluator = AssignmentEvaluator { evaluated: empty(), - exports: &empty(), - scope: &empty(), + exports: &empty(), + scope: &empty(), assignments, invocation_directory, dotenv, @@ -47,14 +47,14 @@ impl<'a, 'b> AssignmentEvaluator<'a, 'b> { pub fn evaluate_line( &mut self, - line: &[Fragment<'a>], - arguments: &Map<&str, Cow> + line: &[Fragment<'a>], + arguments: &Map<&str, Cow>, ) -> RunResult<'a, String> { let mut evaluated = String::new(); for fragment in line { match *fragment { - Fragment::Text{ref text} => evaluated += text.lexeme, - Fragment::Expression{ref expression} => { + Fragment::Text { ref text } => evaluated += text.lexeme, + Fragment::Expression { ref expression } => { evaluated += &self.evaluate_expression(expression, arguments)?; } } @@ -76,7 +76,7 @@ impl<'a, 'b> AssignmentEvaluator<'a, 'b> { } } else { return Err(RuntimeError::Internal { - message: format!("attempted to evaluated unknown assignment {}", name) + message: format!("attempted to evaluated unknown assignment {}", name), }); } @@ -86,10 +86,10 @@ impl<'a, 'b> AssignmentEvaluator<'a, 'b> { fn evaluate_expression( &mut self, expression: &Expression<'a>, - arguments: &Map<&str, Cow> + arguments: &Map<&str, Cow>, ) -> RunResult<'a, String> { match *expression { - Expression::Variable{name, ..} => { + Expression::Variable { name, .. } => { if self.evaluated.contains_key(name) { Ok(self.evaluated[name].clone()) } else if self.scope.contains_key(name) { @@ -101,50 +101,50 @@ impl<'a, 'b> AssignmentEvaluator<'a, 'b> { Ok(arguments[name].to_string()) } else { Err(RuntimeError::Internal { - message: format!("attempted to evaluate undefined variable `{}`", name) + message: format!("attempted to evaluate undefined variable `{}`", name), }) } } - Expression::Call{name, arguments: ref call_arguments, ref token} => { - let call_arguments = call_arguments.iter().map(|argument| { - self.evaluate_expression(argument, arguments) - }).collect::, RuntimeError>>()?; + Expression::Call { + name, + arguments: ref call_arguments, + ref token, + } => { + let call_arguments = call_arguments + .iter() + .map(|argument| self.evaluate_expression(argument, arguments)) + .collect::, RuntimeError>>()?; let context = FunctionContext { invocation_directory: &self.invocation_directory, dotenv: self.dotenv, }; evaluate_function(token, name, &context, &call_arguments) } - Expression::String{ref cooked_string} => Ok(cooked_string.cooked.clone()), - Expression::Backtick{raw, ref token} => { + Expression::String { ref cooked_string } => Ok(cooked_string.cooked.clone()), + Expression::Backtick { raw, ref token } => { if self.dry_run { Ok(format!("`{}`", raw)) } else { Ok(self.run_backtick(self.dotenv, raw, token)?) } } - Expression::Concatination{ref lhs, ref rhs} => { - Ok( - self.evaluate_expression(lhs, arguments)? - + - &self.evaluate_expression(rhs, arguments)? - ) + Expression::Concatination { ref lhs, ref rhs } => { + Ok(self.evaluate_expression(lhs, arguments)? + &self.evaluate_expression(rhs, arguments)?) } } } fn run_backtick( &self, - dotenv: &Map, - raw: &str, - token: &Token<'a>, + dotenv: &Map, + raw: &str, + token: &Token<'a>, ) -> RunResult<'a, String> { let mut cmd = Command::new(self.shell); cmd.export_environment_variables(self.scope, dotenv, self.exports)?; - cmd.arg("-cu") - .arg(raw); + cmd.arg("-cu").arg(raw); cmd.stderr(if self.quiet { process::Stdio::null() @@ -152,13 +152,15 @@ impl<'a, 'b> AssignmentEvaluator<'a, 'b> { process::Stdio::inherit() }); - InterruptHandler::guard(|| brev::output(cmd) - .map_err(|output_error| RuntimeError::Backtick{token: token.clone(), output_error}) - ) + InterruptHandler::guard(|| { + brev::output(cmd).map_err(|output_error| RuntimeError::Backtick { + token: token.clone(), + output_error, + }) + }) } } - #[cfg(test)] mod test { use super::*; @@ -173,11 +175,16 @@ mod test { #[test] fn backtick_code() { match parse_success("a:\n echo {{`f() { return 100; }; f`}}") - .run(&no_cwd_err(), &["a"], &Default::default()).unwrap_err() { - RuntimeError::Backtick{token, output_error: OutputError::Code(code)} => { + .run(&no_cwd_err(), &["a"], &Default::default()) + .unwrap_err() + { + RuntimeError::Backtick { + token, + output_error: OutputError::Code(code), + } => { assert_eq!(code, 100); assert_eq!(token.lexeme, "`f() { return 100; }; f`"); - }, + } other => panic!("expected a code run error, but got: {}", other), } } @@ -196,10 +203,16 @@ recipe: ..Default::default() }; - match parse_success(text).run(&no_cwd_err(), &["recipe"], &configuration).unwrap_err() { - RuntimeError::Backtick{token, output_error: OutputError::Code(_)} => { + match parse_success(text) + .run(&no_cwd_err(), &["recipe"], &configuration) + .unwrap_err() + { + RuntimeError::Backtick { + token, + output_error: OutputError::Code(_), + } => { assert_eq!(token.lexeme, "`echo $exported_variable`"); - }, + } other => panic!("expected a backtick code errror, but got: {}", other), } } diff --git a/src/assignment_resolver.rs b/src/assignment_resolver.rs index 88c1c5b..27cda0f 100644 --- a/src/assignment_resolver.rs +++ b/src/assignment_resolver.rs @@ -3,22 +3,21 @@ use common::*; use CompilationErrorKind::*; pub struct AssignmentResolver<'a: 'b, 'b> { - assignments: &'b Map<&'a str, Expression<'a>>, + assignments: &'b Map<&'a str, Expression<'a>>, assignment_tokens: &'b Map<&'a str, Token<'a>>, - stack: Vec<&'a str>, - seen: Set<&'a str>, - evaluated: Set<&'a str>, + stack: Vec<&'a str>, + seen: Set<&'a str>, + evaluated: Set<&'a str>, } impl<'a: 'b, 'b> AssignmentResolver<'a, 'b> { pub fn resolve_assignments( - assignments: &Map<&'a str, Expression<'a>>, + assignments: &Map<&'a str, Expression<'a>>, assignment_tokens: &Map<&'a str, Token<'a>>, ) -> CompilationResult<'a, ()> { - let mut resolver = AssignmentResolver { - stack: empty(), - seen: empty(), + stack: empty(), + seen: empty(), evaluated: empty(), assignments, assignment_tokens, @@ -45,21 +44,20 @@ impl<'a: 'b, 'b> AssignmentResolver<'a, 'b> { } else { let message = format!("attempted to resolve unknown assignment `{}`", name); return Err(CompilationError { - text: "", - index: 0, - line: 0, + text: "", + index: 0, + line: 0, column: 0, - width: None, - kind: Internal{message} + width: None, + kind: Internal { message }, }); } Ok(()) } - fn resolve_expression( - &mut self, expression: &Expression<'a>) -> CompilationResult<'a, ()> { + fn resolve_expression(&mut self, expression: &Expression<'a>) -> CompilationResult<'a, ()> { match *expression { - Expression::Variable{name, ref token} => { + Expression::Variable { name, ref token } => { if self.evaluated.contains(name) { return Ok(()); } else if self.seen.contains(name) { @@ -67,22 +65,24 @@ impl<'a: 'b, 'b> AssignmentResolver<'a, 'b> { self.stack.push(name); return Err(token.error(CircularVariableDependency { variable: name, - circle: self.stack.clone(), + circle: self.stack.clone(), })); } else if self.assignments.contains_key(name) { self.resolve_assignment(name)?; } else { - return Err(token.error(UndefinedVariable{variable: name})); + return Err(token.error(UndefinedVariable { variable: name })); } } - Expression::Call{ref token, ref arguments, ..} => { - resolve_function(token, arguments.len())? - } - Expression::Concatination{ref lhs, ref rhs} => { + Expression::Call { + ref token, + ref arguments, + .. + } => resolve_function(token, arguments.len())?, + Expression::Concatination { ref lhs, ref rhs } => { self.resolve_expression(lhs)?; self.resolve_expression(rhs)?; } - Expression::String{..} | Expression::Backtick{..} => {} + Expression::String { .. } | Expression::Backtick { .. } => {} } Ok(()) } diff --git a/src/color.rs b/src/color.rs index 3cd039e..affb080 100644 --- a/src/color.rs +++ b/src/color.rs @@ -3,8 +3,8 @@ extern crate atty; use common::*; -use self::ansi_term::{Style, Prefix, Suffix, ANSIGenericString}; use self::ansi_term::Color::*; +use self::ansi_term::{ANSIGenericString, Prefix, Style, Suffix}; use self::atty::is as is_atty; use self::atty::Stream; @@ -18,26 +18,23 @@ pub enum UseColor { #[derive(Copy, Clone)] pub struct Color { use_color: UseColor, - atty: bool, - style: Style, + atty: bool, + style: Style, } impl Default for Color { fn default() -> Color { Color { use_color: UseColor::Never, - atty: false, - style: Style::new(), + atty: false, + style: Style::new(), } } } impl Color { fn restyle(self, style: Style) -> Color { - Color { - style, - ..self - } + Color { style, ..self } } fn redirect(self, stream: Stream) -> Color { @@ -127,8 +124,8 @@ impl Color { pub fn active(&self) -> bool { match self.use_color { UseColor::Always => true, - UseColor::Never => false, - UseColor::Auto => self.atty, + UseColor::Never => false, + UseColor::Auto => self.atty, } } diff --git a/src/command_ext.rs b/src/command_ext.rs index ea02080..4feeaa5 100644 --- a/src/command_ext.rs +++ b/src/command_ext.rs @@ -3,18 +3,18 @@ use common::*; pub trait CommandExt { fn export_environment_variables<'a>( &mut self, - scope: &Map<&'a str, String>, - dotenv: &Map, - exports: &Set<&'a str> + scope: &Map<&'a str, String>, + dotenv: &Map, + exports: &Set<&'a str>, ) -> RunResult<'a, ()>; } impl CommandExt for Command { fn export_environment_variables<'a>( &mut self, - scope: &Map<&'a str, String>, - dotenv: &Map, - exports: &Set<&'a str> + scope: &Map<&'a str, String>, + dotenv: &Map, + exports: &Set<&'a str>, ) -> RunResult<'a, ()> { for (name, value) in dotenv { self.env(name, value); @@ -24,7 +24,7 @@ impl CommandExt for Command { self.env(name, value); } else { return Err(RuntimeError::Internal { - message: format!("scope does not contain exported variable `{}`", name), + message: format!("scope does not contain exported variable `{}`", name), }); } } diff --git a/src/common.rs b/src/common.rs index 3986863..d0db758 100644 --- a/src/common.rs +++ b/src/common.rs @@ -6,7 +6,7 @@ pub use std::ops::Range; pub use std::path::{Path, PathBuf}; pub use std::process::Command; pub use std::sync::{Mutex, MutexGuard}; -pub use std::{cmp, env, fs, fmt, io, iter, process, vec, usize}; +pub use std::{cmp, env, fmt, fs, io, iter, process, usize, vec}; pub use color::Color; pub use libc::{EXIT_FAILURE, EXIT_SUCCESS}; @@ -32,7 +32,7 @@ pub use parser::Parser; pub use range_ext::RangeExt; pub use recipe::{Recipe, RecipeContext}; pub use recipe_resolver::RecipeResolver; -pub use runtime_error::{RuntimeError, RunResult}; +pub use runtime_error::{RunResult, RuntimeError}; pub use shebang::Shebang; pub use token::{Token, TokenKind}; pub use verbosity::Verbosity; diff --git a/src/compilation_error.rs b/src/compilation_error.rs index 0fc9fc5..712ee34 100644 --- a/src/compilation_error.rs +++ b/src/compilation_error.rs @@ -1,42 +1,91 @@ use common::*; -use misc::{Or, write_error_context, show_whitespace, maybe_s}; +use misc::{maybe_s, show_whitespace, write_error_context, Or}; pub type CompilationResult<'a, T> = Result>; #[derive(Debug, PartialEq)] pub struct CompilationError<'a> { - pub text: &'a str, - pub index: usize, - pub line: usize, + pub text: &'a str, + pub index: usize, + pub line: usize, pub column: usize, - pub width: Option, - pub kind: CompilationErrorKind<'a>, + pub width: Option, + pub kind: CompilationErrorKind<'a>, } #[derive(Debug, PartialEq)] pub enum CompilationErrorKind<'a> { - CircularRecipeDependency{recipe: &'a str, circle: Vec<&'a str>}, - CircularVariableDependency{variable: &'a str, circle: Vec<&'a str>}, - DependencyHasParameters{recipe: &'a str, dependency: &'a str}, - DuplicateDependency{recipe: &'a str, dependency: &'a str}, - DuplicateParameter{recipe: &'a str, parameter: &'a str}, - DuplicateRecipe{recipe: &'a str, first: usize}, - DuplicateVariable{variable: &'a str}, + CircularRecipeDependency { + recipe: &'a str, + circle: Vec<&'a str>, + }, + CircularVariableDependency { + variable: &'a str, + circle: Vec<&'a str>, + }, + DependencyHasParameters { + recipe: &'a str, + dependency: &'a str, + }, + DuplicateDependency { + recipe: &'a str, + dependency: &'a str, + }, + DuplicateParameter { + recipe: &'a str, + parameter: &'a str, + }, + DuplicateRecipe { + recipe: &'a str, + first: usize, + }, + DuplicateVariable { + variable: &'a str, + }, ExtraLeadingWhitespace, - FunctionArgumentCountMismatch{function: &'a str, found: usize, expected: usize}, - InconsistentLeadingWhitespace{expected: &'a str, found: &'a str}, - Internal{message: String}, - InvalidEscapeSequence{character: char}, - MixedLeadingWhitespace{whitespace: &'a str}, + FunctionArgumentCountMismatch { + function: &'a str, + found: usize, + expected: usize, + }, + InconsistentLeadingWhitespace { + expected: &'a str, + found: &'a str, + }, + Internal { + message: String, + }, + InvalidEscapeSequence { + character: char, + }, + MixedLeadingWhitespace { + whitespace: &'a str, + }, OuterShebang, - ParameterFollowsVariadicParameter{parameter: &'a str}, - ParameterShadowsVariable{parameter: &'a str}, - RequiredParameterFollowsDefaultParameter{parameter: &'a str}, - UndefinedVariable{variable: &'a str}, - UnexpectedToken{expected: Vec, found: TokenKind}, - UnknownDependency{recipe: &'a str, unknown: &'a str}, - UnknownFunction{function: &'a str}, + ParameterFollowsVariadicParameter { + parameter: &'a str, + }, + ParameterShadowsVariable { + parameter: &'a str, + }, + RequiredParameterFollowsDefaultParameter { + parameter: &'a str, + }, + UndefinedVariable { + variable: &'a str, + }, + UnexpectedToken { + expected: Vec, + found: TokenKind, + }, + UnknownDependency { + recipe: &'a str, + unknown: &'a str, + }, + UnknownFunction { + function: &'a str, + }, UnknownStartOfToken, UnterminatedInterpolation, UnterminatedString, @@ -45,32 +94,43 @@ pub enum CompilationErrorKind<'a> { impl<'a> Display for CompilationError<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { use CompilationErrorKind::*; - let error = Color::fmt(f).error(); + let error = Color::fmt(f).error(); let message = Color::fmt(f).message(); write!(f, "{} {}", error.paint("error:"), message.prefix())?; match self.kind { - CircularRecipeDependency{recipe, ref circle} => { + CircularRecipeDependency { recipe, ref circle } => { if circle.len() == 2 { writeln!(f, "Recipe `{}` depends on itself", recipe)?; } else { - writeln!(f, "Recipe `{}` has circular dependency `{}`", - recipe, circle.join(" -> "))?; + writeln!( + f, + "Recipe `{}` has circular dependency `{}`", + recipe, + circle.join(" -> ") + )?; } } - CircularVariableDependency{variable, ref circle} => { + CircularVariableDependency { + variable, + ref circle, + } => { if circle.len() == 2 { writeln!(f, "Variable `{}` is defined in terms of itself", variable)?; } else { - writeln!(f, "Variable `{}` depends on its own value: `{}`", - variable, circle.join(" -> "))?; + writeln!( + f, + "Variable `{}` depends on its own value: `{}`", + variable, + circle.join(" -> ") + )?; } } - InvalidEscapeSequence{character} => { + InvalidEscapeSequence { character } => { let representation = match character { - '`' => r"\`".to_string(), + '`' => r"\`".to_string(), '\\' => r"\".to_string(), '\'' => r"'".to_string(), '"' => r#"""#.to_string(), @@ -78,69 +138,111 @@ impl<'a> Display for CompilationError<'a> { }; writeln!(f, "`\\{}` is not a valid escape sequence", representation)?; } - DuplicateParameter{recipe, parameter} => { - writeln!(f, "Recipe `{}` has duplicate parameter `{}`", recipe, parameter)?; + DuplicateParameter { recipe, parameter } => { + writeln!( + f, + "Recipe `{}` has duplicate parameter `{}`", + recipe, parameter + )?; } - DuplicateVariable{variable} => { + DuplicateVariable { variable } => { writeln!(f, "Variable `{}` has multiple definitions", variable)?; } - UnexpectedToken{ref expected, found} => { + UnexpectedToken { + ref expected, + found, + } => { writeln!(f, "Expected {}, but found {}", Or(expected), found)?; } - DuplicateDependency{recipe, dependency} => { - writeln!(f, "Recipe `{}` has duplicate dependency `{}`", recipe, dependency)?; + DuplicateDependency { recipe, dependency } => { + writeln!( + f, + "Recipe `{}` has duplicate dependency `{}`", + recipe, dependency + )?; } - DuplicateRecipe{recipe, first} => { - writeln!(f, "Recipe `{}` first defined on line {} is redefined on line {}", - recipe, first + 1, self.line + 1)?; + DuplicateRecipe { recipe, first } => { + writeln!( + f, + "Recipe `{}` first defined on line {} is redefined on line {}", + recipe, + first + 1, + self.line + 1 + )?; } - DependencyHasParameters{recipe, dependency} => { - writeln!(f, "Recipe `{}` depends on `{}` which requires arguments. \ - Dependencies may not require arguments", recipe, dependency)?; + DependencyHasParameters { recipe, dependency } => { + writeln!( + f, + "Recipe `{}` depends on `{}` which requires arguments. \ + Dependencies may not require arguments", + recipe, dependency + )?; } - ParameterShadowsVariable{parameter} => { - writeln!(f, "Parameter `{}` shadows variable of the same name", parameter)?; + ParameterShadowsVariable { parameter } => { + writeln!( + f, + "Parameter `{}` shadows variable of the same name", + parameter + )?; } - RequiredParameterFollowsDefaultParameter{parameter} => { - writeln!(f, "Non-default parameter `{}` follows default parameter", parameter)?; + RequiredParameterFollowsDefaultParameter { parameter } => { + writeln!( + f, + "Non-default parameter `{}` follows default parameter", + parameter + )?; } - ParameterFollowsVariadicParameter{parameter} => { + ParameterFollowsVariadicParameter { parameter } => { writeln!(f, "Parameter `{}` follows variadic parameter", parameter)?; } - MixedLeadingWhitespace{whitespace} => { - writeln!(f, + MixedLeadingWhitespace { whitespace } => { + writeln!( + f, "Found a mix of tabs and spaces in leading whitespace: `{}`\n\ - Leading whitespace may consist of tabs or spaces, but not both", + Leading whitespace may consist of tabs or spaces, but not both", show_whitespace(whitespace) )?; } ExtraLeadingWhitespace => { writeln!(f, "Recipe line has extra leading whitespace")?; } - FunctionArgumentCountMismatch{function, found, expected} => { + FunctionArgumentCountMismatch { + function, + found, + expected, + } => { writeln!( f, "Function `{}` called with {} argument{} but takes {}", - function, found, maybe_s(found), expected + function, + found, + maybe_s(found), + expected )?; } - InconsistentLeadingWhitespace{expected, found} => { - writeln!(f, + InconsistentLeadingWhitespace { expected, found } => { + writeln!( + f, "Recipe line has inconsistent leading whitespace. \ Recipe started with `{}` but found line with `{}`", - show_whitespace(expected), show_whitespace(found) + show_whitespace(expected), + show_whitespace(found) )?; } OuterShebang => { writeln!(f, "`#!` is reserved syntax outside of recipes")?; } - UnknownDependency{recipe, unknown} => { - writeln!(f, "Recipe `{}` has unknown dependency `{}`", recipe, unknown)?; + UnknownDependency { recipe, unknown } => { + writeln!( + f, + "Recipe `{}` has unknown dependency `{}`", + recipe, unknown + )?; } - UndefinedVariable{variable} => { + UndefinedVariable { variable } => { writeln!(f, "Variable `{}` not defined", variable)?; } - UnknownFunction{function} => { + UnknownFunction { function } => { writeln!(f, "Call to unknown function `{}`", function)?; } UnknownStartOfToken => { @@ -152,10 +254,13 @@ impl<'a> Display for CompilationError<'a> { UnterminatedString => { writeln!(f, "Unterminated string")?; } - Internal{ref message} => { - writeln!(f, "Internal error, this may indicate a bug in just: {}\n\ - consider filing an issue: https://github.com/casey/just/issues/new", - message)?; + Internal { ref message } => { + writeln!( + f, + "Internal error, this may indicate a bug in just: {}\n\ + consider filing an issue: https://github.com/casey/just/issues/new", + message + )?; } } diff --git a/src/configuration.rs b/src/configuration.rs index 77d662e..63dae66 100644 --- a/src/configuration.rs +++ b/src/configuration.rs @@ -3,26 +3,26 @@ use common::*; pub const DEFAULT_SHELL: &str = "sh"; pub struct Configuration<'a> { - pub dry_run: bool, - pub evaluate: bool, + pub dry_run: bool, + pub evaluate: bool, pub highlight: bool, pub overrides: Map<&'a str, &'a str>, - pub quiet: bool, - pub shell: &'a str, - pub color: Color, + pub quiet: bool, + pub shell: &'a str, + pub color: Color, pub verbosity: Verbosity, } impl<'a> Default for Configuration<'a> { fn default() -> Configuration<'static> { Configuration { - dry_run: false, - evaluate: false, + dry_run: false, + evaluate: false, highlight: false, overrides: empty(), - quiet: false, - shell: DEFAULT_SHELL, - color: default(), + quiet: false, + shell: DEFAULT_SHELL, + color: default(), verbosity: Verbosity::from_flag_occurrences(0), } } diff --git a/src/cooked_string.rs b/src/cooked_string.rs index a886fa1..52a5cff 100644 --- a/src/cooked_string.rs +++ b/src/cooked_string.rs @@ -2,30 +2,35 @@ use common::*; #[derive(PartialEq, Debug)] pub struct CookedString<'a> { - pub raw: &'a str, + pub raw: &'a str, pub cooked: String, } impl<'a> CookedString<'a> { pub fn new(token: &Token<'a>) -> CompilationResult<'a, CookedString<'a>> { - let raw = &token.lexeme[1..token.lexeme.len()-1]; + let raw = &token.lexeme[1..token.lexeme.len() - 1]; if let TokenKind::RawString = token.kind { - Ok(CookedString{cooked: raw.to_string(), raw}) + Ok(CookedString { + cooked: raw.to_string(), + raw, + }) } else if let TokenKind::StringToken = token.kind { let mut cooked = String::new(); let mut escape = false; for c in raw.chars() { if escape { match c { - 'n' => cooked.push('\n'), - 'r' => cooked.push('\r'), - 't' => cooked.push('\t'), - '\\' => cooked.push('\\'), - '"' => cooked.push('"'), - other => return Err(token.error(CompilationErrorKind::InvalidEscapeSequence { - character: other, - })), + 'n' => cooked.push('\n'), + 'r' => cooked.push('\r'), + 't' => cooked.push('\t'), + '\\' => cooked.push('\\'), + '"' => cooked.push('"'), + other => { + return Err( + token.error(CompilationErrorKind::InvalidEscapeSequence { character: other }), + ) + } } escape = false; continue; @@ -36,10 +41,10 @@ impl<'a> CookedString<'a> { } cooked.push(c); } - Ok(CookedString{raw, cooked}) + Ok(CookedString { raw, cooked }) } else { Err(token.error(CompilationErrorKind::Internal { - message: "cook_string() called on non-string token".to_string() + message: "cook_string() called on non-string token".to_string(), })) } } diff --git a/src/expression.rs b/src/expression.rs index 5c370eb..f2a26b1 100644 --- a/src/expression.rs +++ b/src/expression.rs @@ -2,35 +2,50 @@ use common::*; #[derive(PartialEq, Debug)] pub enum Expression<'a> { - Backtick{raw: &'a str, token: Token<'a>}, - Call{name: &'a str, token: Token<'a>, arguments: Vec>}, - Concatination{lhs: Box>, rhs: Box>}, - String{cooked_string: CookedString<'a>}, - Variable{name: &'a str, token: Token<'a>}, + Backtick { + raw: &'a str, + token: Token<'a>, + }, + Call { + name: &'a str, + token: Token<'a>, + arguments: Vec>, + }, + Concatination { + lhs: Box>, + rhs: Box>, + }, + String { + cooked_string: CookedString<'a>, + }, + Variable { + name: &'a str, + token: Token<'a>, + }, } impl<'a> Expression<'a> { pub fn variables(&'a self) -> Variables<'a> { - Variables { - stack: vec![self], - } + Variables { stack: vec![self] } } pub fn functions(&'a self) -> Functions<'a> { - Functions { - stack: vec![self], - } + Functions { stack: vec![self] } } } impl<'a> Display for Expression<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { match *self { - Expression::Backtick {raw, .. } => write!(f, "`{}`", raw)?, - Expression::Concatination{ref lhs, ref rhs } => write!(f, "{} + {}", lhs, rhs)?, - Expression::String {ref cooked_string } => write!(f, "\"{}\"", cooked_string.raw)?, - Expression::Variable {name, .. } => write!(f, "{}", name)?, - Expression::Call {name, ref arguments, ..} => { + Expression::Backtick { raw, .. } => write!(f, "`{}`", raw)?, + Expression::Concatination { ref lhs, ref rhs } => write!(f, "{} + {}", lhs, rhs)?, + Expression::String { ref cooked_string } => write!(f, "\"{}\"", cooked_string.raw)?, + Expression::Variable { name, .. } => write!(f, "{}", name)?, + Expression::Call { + name, + ref arguments, + .. + } => { write!(f, "{}(", name)?; for (i, argument) in arguments.iter().enumerate() { if i > 0 { @@ -56,11 +71,11 @@ impl<'a> Iterator for Variables<'a> { fn next(&mut self) -> Option<&'a Token<'a>> { match self.stack.pop() { None - | Some(&Expression::String{..}) - | Some(&Expression::Backtick{..}) - | Some(&Expression::Call{..}) => None, - Some(&Expression::Variable{ref token,..}) => Some(token), - Some(&Expression::Concatination{ref lhs, ref rhs}) => { + | Some(&Expression::String { .. }) + | Some(&Expression::Backtick { .. }) + | Some(&Expression::Call { .. }) => None, + Some(&Expression::Variable { ref token, .. }) => Some(token), + Some(&Expression::Concatination { ref lhs, ref rhs }) => { self.stack.push(lhs); self.stack.push(rhs); self.next() @@ -79,11 +94,15 @@ impl<'a> Iterator for Functions<'a> { fn next(&mut self) -> Option { match self.stack.pop() { None - | Some(&Expression::String{..}) - | Some(&Expression::Backtick{..}) - | Some(&Expression::Variable{..}) => None, - Some(&Expression::Call{ref token, ref arguments, ..}) => Some((token, arguments.len())), - Some(&Expression::Concatination{ref lhs, ref rhs}) => { + | Some(&Expression::String { .. }) + | Some(&Expression::Backtick { .. }) + | Some(&Expression::Variable { .. }) => None, + Some(&Expression::Call { + ref token, + ref arguments, + .. + }) => Some((token, arguments.len())), + Some(&Expression::Concatination { ref lhs, ref rhs }) => { self.stack.push(lhs); self.stack.push(rhs); self.next() diff --git a/src/fragment.rs b/src/fragment.rs index d603cf0..1cb3d59 100644 --- a/src/fragment.rs +++ b/src/fragment.rs @@ -2,14 +2,14 @@ use common::*; #[derive(PartialEq, Debug)] pub enum Fragment<'a> { - Text{text: Token<'a>}, - Expression{expression: Expression<'a>}, + Text { text: Token<'a> }, + Expression { expression: Expression<'a> }, } impl<'a> Fragment<'a> { pub fn continuation(&self) -> bool { match *self { - Fragment::Text{ref text} => text.lexeme.ends_with('\\'), + Fragment::Text { ref text } => text.lexeme.ends_with('\\'), _ => false, } } diff --git a/src/function.rs b/src/function.rs index 49f3c37..590826e 100644 --- a/src/function.rs +++ b/src/function.rs @@ -6,19 +6,24 @@ use platform::{Platform, PlatformInterface}; lazy_static! { static ref FUNCTIONS: Map<&'static str, Function> = vec![ - ("arch", Function::Nullary(arch )), - ("os", Function::Nullary(os )), - ("os_family", Function::Nullary(os_family )), - ("env_var", Function::Unary (env_var )), - ("env_var_or_default", Function::Binary (env_var_or_default)), - ("invocation_directory", Function::Nullary(invocation_directory)), - ].into_iter().collect(); + ("arch", Function::Nullary(arch)), + ("os", Function::Nullary(os)), + ("os_family", Function::Nullary(os_family)), + ("env_var", Function::Unary(env_var)), + ("env_var_or_default", Function::Binary(env_var_or_default)), + ( + "invocation_directory", + Function::Nullary(invocation_directory) + ), + ] + .into_iter() + .collect(); } enum Function { - Nullary(fn(&FunctionContext, ) -> Result), - Unary (fn(&FunctionContext, &str ) -> Result), - Binary (fn(&FunctionContext, &str, &str) -> Result), + Nullary(fn(&FunctionContext) -> Result), + Unary(fn(&FunctionContext, &str) -> Result), + Binary(fn(&FunctionContext, &str, &str) -> Result), } impl Function { @@ -26,8 +31,8 @@ impl Function { use self::Function::*; match *self { Nullary(_) => 0, - Unary(_) => 1, - Binary(_) => 2, + Unary(_) => 1, + Binary(_) => 2, } } } @@ -42,45 +47,56 @@ pub fn resolve_function<'a>(token: &Token<'a>, argc: usize) -> CompilationResult if let Some(function) = FUNCTIONS.get(&name) { use self::Function::*; match (function, argc) { - (&Nullary(_), 0) | - (&Unary(_), 1) | - (&Binary(_), 2) => Ok(()), - _ => { - Err(token.error(CompilationErrorKind::FunctionArgumentCountMismatch{ - function: name, found: argc, expected: function.argc(), - })) - } + (&Nullary(_), 0) | (&Unary(_), 1) | (&Binary(_), 2) => Ok(()), + _ => Err( + token.error(CompilationErrorKind::FunctionArgumentCountMismatch { + function: name, + found: argc, + expected: function.argc(), + }), + ), } } else { - Err(token.error(CompilationErrorKind::UnknownFunction{function: token.lexeme})) + Err(token.error(CompilationErrorKind::UnknownFunction { + function: token.lexeme, + })) } } pub fn evaluate_function<'a>( - token: &Token<'a>, - name: &'a str, - context: &FunctionContext, - arguments: &[String] + token: &Token<'a>, + name: &'a str, + context: &FunctionContext, + arguments: &[String], ) -> RunResult<'a, String> { if let Some(function) = FUNCTIONS.get(name) { use self::Function::*; let argc = arguments.len(); match (function, argc) { - (&Nullary(f), 0) => f(context) - .map_err(|message| RuntimeError::FunctionCall{token: token.clone(), message}), - (&Unary(f), 1) => f(context, &arguments[0]) - .map_err(|message| RuntimeError::FunctionCall{token: token.clone(), message}), - (&Binary(f), 2) => f(context, &arguments[0], &arguments[1]) - .map_err(|message| RuntimeError::FunctionCall{token: token.clone(), message}), - _ => { - Err(RuntimeError::Internal { - message: format!("attempted to evaluate function `{}` with {} arguments", name, argc) + (&Nullary(f), 0) => f(context).map_err(|message| RuntimeError::FunctionCall { + token: token.clone(), + message, + }), + (&Unary(f), 1) => f(context, &arguments[0]).map_err(|message| RuntimeError::FunctionCall { + token: token.clone(), + message, + }), + (&Binary(f), 2) => { + f(context, &arguments[0], &arguments[1]).map_err(|message| RuntimeError::FunctionCall { + token: token.clone(), + message, }) } + _ => Err(RuntimeError::Internal { + message: format!( + "attempted to evaluate function `{}` with {} arguments", + name, argc + ), + }), } } else { Err(RuntimeError::Internal { - message: format!("attempted to evaluate unknown function: `{}`", name) + message: format!("attempted to evaluate unknown function: `{}`", name), }) } } @@ -98,9 +114,9 @@ pub fn os_family(_context: &FunctionContext) -> Result { } pub fn invocation_directory(context: &FunctionContext) -> Result { - context.invocation_directory.clone() - .and_then(|s| Platform::to_shell_path(&s) - .map_err(|e| format!("Error getting shell path: {}", e))) + context.invocation_directory.clone().and_then(|s| { + Platform::to_shell_path(&s).map_err(|e| format!("Error getting shell path: {}", e)) + }) } pub fn env_var(context: &FunctionContext, key: &str) -> Result { @@ -112,16 +128,18 @@ pub fn env_var(context: &FunctionContext, key: &str) -> Result { match env::var(key) { Err(NotPresent) => Err(format!("environment variable `{}` not present", key)), - Err(NotUnicode(os_string)) => - Err(format!("environment variable `{}` not unicode: {:?}", key, os_string)), + Err(NotUnicode(os_string)) => Err(format!( + "environment variable `{}` not unicode: {:?}", + key, os_string + )), Ok(value) => Ok(value), } } pub fn env_var_or_default( context: &FunctionContext, - key: &str, - default: &str, + key: &str, + default: &str, ) -> Result { if let Some(value) = context.dotenv.get(key) { return Ok(value.clone()); @@ -130,8 +148,10 @@ pub fn env_var_or_default( use std::env::VarError::*; match env::var(key) { Err(NotPresent) => Ok(default.to_string()), - Err(NotUnicode(os_string)) => - Err(format!("environment variable `{}` not unicode: {:?}", key, os_string)), + Err(NotUnicode(os_string)) => Err(format!( + "environment variable `{}` not unicode: {:?}", + key, os_string + )), Ok(value) => Ok(value), } } diff --git a/src/fuzzing.rs b/src/fuzzing.rs index 2bd32f8..91524d3 100644 --- a/src/fuzzing.rs +++ b/src/fuzzing.rs @@ -2,7 +2,7 @@ use common::*; pub fn compile(text: &str) { if let Err(error) = Parser::parse(text) { - if let CompilationErrorKind::Internal{..} = error.kind { + if let CompilationErrorKind::Internal { .. } = error.kind { panic!("{}", error) } } diff --git a/src/justfile.rs b/src/justfile.rs index cbedf02..893eb4c 100644 --- a/src/justfile.rs +++ b/src/justfile.rs @@ -126,17 +126,15 @@ impl<'a> Justfile<'a> where { }); } - let context = RecipeContext{invocation_directory, configuration, scope}; + let context = RecipeContext { + invocation_directory, + configuration, + scope, + }; let mut ran = empty(); for (recipe, arguments) in grouped { - self.run_recipe( - &context, - recipe, - arguments, - &dotenv, - &mut ran, - )? + self.run_recipe(&context, recipe, arguments, &dotenv, &mut ran)? } Ok(()) @@ -152,21 +150,10 @@ impl<'a> Justfile<'a> where { ) -> RunResult<()> { for dependency_name in &recipe.dependencies { if !ran.contains(dependency_name) { - self.run_recipe( - context, - &self.recipes[dependency_name], - &[], - dotenv, - ran, - )?; + self.run_recipe(context, &self.recipes[dependency_name], &[], dotenv, ran)?; } } - recipe.run( - context, - arguments, - dotenv, - &self.exports, - )?; + recipe.run(context, arguments, dotenv, &self.exports)?; ran.insert(recipe.name); Ok(()) } @@ -313,10 +300,7 @@ a return code: min, max, } => { - let param_names = parameters - .iter() - .map(|p| p.name) - .collect::>(); + let param_names = parameters.iter().map(|p| p.name).collect::>(); assert_eq!(recipe, "a"); assert_eq!(param_names, ["b", "c", "d"]); assert_eq!(found, 2); @@ -340,10 +324,7 @@ a return code: min, max, } => { - let param_names = parameters - .iter() - .map(|p| p.name) - .collect::>(); + let param_names = parameters.iter().map(|p| p.name).collect::>(); assert_eq!(recipe, "a"); assert_eq!(param_names, ["b", "c", "d"]); assert_eq!(found, 2); @@ -367,10 +348,7 @@ a return code: min, max, } => { - let param_names = parameters - .iter() - .map(|p| p.name) - .collect::>(); + let param_names = parameters.iter().map(|p| p.name).collect::>(); assert_eq!(recipe, "a"); assert_eq!(param_names, ["b", "c", "d"]); assert_eq!(found, 0); @@ -394,10 +372,7 @@ a return code: min, max, } => { - let param_names = parameters - .iter() - .map(|p| p.name) - .collect::>(); + let param_names = parameters.iter().map(|p| p.name).collect::>(); assert_eq!(recipe, "a"); assert_eq!(param_names, ["b", "c", "d"]); assert_eq!(found, 1); @@ -421,10 +396,7 @@ a return code: min, max, } => { - let param_names = parameters - .iter() - .map(|p| p.name) - .collect::>(); + let param_names = parameters.iter().map(|p| p.name).collect::>(); assert_eq!(recipe, "a"); assert_eq!(param_names, ["b", "c", "d"]); assert_eq!(found, 0); diff --git a/src/lexer.rs b/src/lexer.rs index 83905ff..018cb74 100644 --- a/src/lexer.rs +++ b/src/lexer.rs @@ -1,7 +1,7 @@ use common::*; -use TokenKind::*; use CompilationErrorKind::*; +use TokenKind::*; fn re(pattern: &str) -> Regex { Regex::new(pattern).unwrap() @@ -21,12 +21,12 @@ fn mixed_whitespace(text: &str) -> bool { pub struct Lexer<'a> { tokens: Vec>, - text: &'a str, - rest: &'a str, - index: usize, + text: &'a str, + rest: &'a str, + index: usize, column: usize, - line: usize, - state: Vec>, + line: usize, + state: Vec>, } #[derive(PartialEq)] @@ -39,13 +39,13 @@ enum State<'a> { impl<'a> Lexer<'a> { pub fn lex(text: &'a str) -> CompilationResult>> { - let lexer = Lexer{ + let lexer = Lexer { tokens: vec![], - rest: text, - index: 0, - line: 0, + rest: text, + index: 0, + line: 0, column: 0, - state: vec![State::Start], + state: vec![State::Start], text, }; @@ -54,21 +54,21 @@ impl<'a> Lexer<'a> { fn error(&self, kind: CompilationErrorKind<'a>) -> CompilationError<'a> { CompilationError { - text: self.text, - index: self.index, - line: self.line, + text: self.text, + index: self.index, + line: self.line, column: self.column, - width: None, + width: None, kind, } } fn token(&self, prefix: &'a str, lexeme: &'a str, kind: TokenKind) -> Token<'a> { Token { - index: self.index, - line: self.line, + index: self.index, + line: self.line, column: self.column, - text: self.text, + text: self.text, prefix, lexeme, kind, @@ -80,19 +80,21 @@ impl<'a> Lexer<'a> { static ref INDENT: Regex = re(r"^([ \t]*)[^ \t\n\r]"); } - let indentation = INDENT.captures(self.rest).map(|captures| captures.get(1).unwrap().as_str()); + let indentation = INDENT + .captures(self.rest) + .map(|captures| captures.get(1).unwrap().as_str()); if self.column == 0 { if let Some(kind) = match (self.state.last().unwrap(), indentation) { // ignore: was no indentation and there still isn't // or current line is blank - (&State::Start, Some("")) | (_, None) => { - None - } + (&State::Start, Some("")) | (_, None) => None, // indent: was no indentation, now there is (&State::Start, Some(current)) => { if mixed_whitespace(current) { - return Err(self.error(MixedLeadingWhitespace{whitespace: current})); + return Err(self.error(MixedLeadingWhitespace { + whitespace: current, + })); } //indent = Some(current); self.state.push(State::Indent(current)); @@ -107,9 +109,9 @@ impl<'a> Lexer<'a> { // was indentation and still is, check if the new indentation matches (&State::Indent(previous), Some(current)) => { if !current.starts_with(previous) { - return Err(self.error(InconsistentLeadingWhitespace{ + return Err(self.error(InconsistentLeadingWhitespace { expected: previous, - found: current + found: current, })); } None @@ -117,7 +119,7 @@ impl<'a> Lexer<'a> { // at column 0 in some other state: this should never happen (&State::Text, _) | (&State::Interpolation, _) => { return Err(self.error(Internal { - message: "unexpected state at column 0".to_string() + message: "unexpected state at column 0".to_string(), })); } } { @@ -129,27 +131,27 @@ impl<'a> Lexer<'a> { pub fn inner(mut self) -> CompilationResult<'a, Vec>> { lazy_static! { - static ref AT: Regex = token(r"@" ); - static ref BACKTICK: Regex = token(r"`[^`\n\r]*`" ); - static ref COLON: Regex = token(r":" ); - static ref COMMA: Regex = token(r"," ); - static ref COMMENT: Regex = token(r"#([^!\n\r][^\n\r]*)?\r?$" ); - static ref EOF: Regex = token(r"\z" ); - 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 PAREN_L: Regex = token(r"[(]" ); - static ref PAREN_R: Regex = token(r"[)]" ); - static ref PLUS: Regex = token(r"[+]" ); - static ref RAW_STRING: Regex = token(r#"'[^']*'"# ); - static ref STRING: Regex = token(r#"["]"# ); - static ref UNTERMINATED_RAW_STRING: Regex = token(r#"'[^']*"# ); - static ref INTERPOLATION_START: Regex = re(r"^[{][{]" ); - static ref LEADING_TEXT: Regex = re(r"^(?m)(.+?)[{][{]" ); - static ref LINE: Regex = re(r"^(?m)[ \t]+[^ \t\n\r].*$"); - static ref TEXT: Regex = re(r"^(?m)(.+)" ); + static ref AT: Regex = token(r"@"); + static ref BACKTICK: Regex = token(r"`[^`\n\r]*`"); + static ref COLON: Regex = token(r":"); + static ref COMMA: Regex = token(r","); + static ref COMMENT: Regex = token(r"#([^!\n\r][^\n\r]*)?\r?$"); + static ref EOF: Regex = token(r"\z"); + 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 PAREN_L: Regex = token(r"[(]"); + static ref PAREN_R: Regex = token(r"[)]"); + static ref PLUS: Regex = token(r"[+]"); + static ref RAW_STRING: Regex = token(r#"'[^']*'"#); + static ref STRING: Regex = token(r#"["]"#); + static ref UNTERMINATED_RAW_STRING: Regex = token(r#"'[^']*"#); + static ref INTERPOLATION_START: Regex = re(r"^[{][{]"); + static ref LEADING_TEXT: Regex = re(r"^(?m)(.+?)[{][{]"); + static ref LINE: Regex = re(r"^(?m)[ \t]+[^ \t\n\r].*$"); + static ref TEXT: Regex = re(r"^(?m)(.+)"); } loop { @@ -163,17 +165,25 @@ impl<'a> Lexer<'a> { self.tokens.push(token); } - let (prefix, lexeme, kind) = - if let (0, &State::Indent(indent), Some(captures)) = - (self.column, self.state.last().unwrap(), LINE.captures(self.rest)) { + let (prefix, lexeme, kind) = if let (0, &State::Indent(indent), Some(captures)) = ( + self.column, + self.state.last().unwrap(), + LINE.captures(self.rest), + ) { let line = captures.get(0).unwrap().as_str(); if !line.starts_with(indent) { - return Err(self.error(Internal{message: "unexpected indent".to_string()})); + return Err(self.error(Internal { + message: "unexpected indent".to_string(), + })); } self.state.push(State::Text); (&line[0..indent.len()], "", Line) } else if let Some(captures) = EOF.captures(self.rest) { - (captures.get(1).unwrap().as_str(), captures.get(2).unwrap().as_str(), Eof) + ( + captures.get(1).unwrap().as_str(), + captures.get(2).unwrap().as_str(), + Eof, + ) } else if let State::Text = *self.state.last().unwrap() { if let Some(captures) = INTERPOLATION_START.captures(self.rest) { self.state.push(State::Interpolation); @@ -184,51 +194,111 @@ impl<'a> Lexer<'a> { ("", captures.get(1).unwrap().as_str(), Text) } else if let Some(captures) = EOL.captures(self.rest) { self.state.pop(); - (captures.get(1).unwrap().as_str(), captures.get(2).unwrap().as_str(), Eol) + ( + captures.get(1).unwrap().as_str(), + captures.get(2).unwrap().as_str(), + Eol, + ) } else { return Err(self.error(Internal { - message: format!("Could not match token in text state: \"{}\"", self.rest) + message: format!("Could not match token in text state: \"{}\"", self.rest), })); } } else if let Some(captures) = INTERPOLATION_START_TOKEN.captures(self.rest) { - (captures.get(1).unwrap().as_str(), captures.get(2).unwrap().as_str(), InterpolationStart) + ( + captures.get(1).unwrap().as_str(), + captures.get(2).unwrap().as_str(), + InterpolationStart, + ) } else if let Some(captures) = INTERPOLATION_END.captures(self.rest) { if self.state.last().unwrap() == &State::Interpolation { self.state.pop(); } - (captures.get(1).unwrap().as_str(), captures.get(2).unwrap().as_str(), InterpolationEnd) + ( + captures.get(1).unwrap().as_str(), + captures.get(2).unwrap().as_str(), + InterpolationEnd, + ) } else if let Some(captures) = NAME.captures(self.rest) { - (captures.get(1).unwrap().as_str(), captures.get(2).unwrap().as_str(), Name) + ( + captures.get(1).unwrap().as_str(), + captures.get(2).unwrap().as_str(), + Name, + ) } else if let Some(captures) = EOL.captures(self.rest) { if self.state.last().unwrap() == &State::Interpolation { return Err(self.error(UnterminatedInterpolation)); } - (captures.get(1).unwrap().as_str(), captures.get(2).unwrap().as_str(), Eol) + ( + captures.get(1).unwrap().as_str(), + captures.get(2).unwrap().as_str(), + Eol, + ) } else if let Some(captures) = BACKTICK.captures(self.rest) { - (captures.get(1).unwrap().as_str(), captures.get(2).unwrap().as_str(), Backtick) + ( + captures.get(1).unwrap().as_str(), + captures.get(2).unwrap().as_str(), + Backtick, + ) } else if let Some(captures) = COLON.captures(self.rest) { - (captures.get(1).unwrap().as_str(), captures.get(2).unwrap().as_str(), Colon) + ( + captures.get(1).unwrap().as_str(), + captures.get(2).unwrap().as_str(), + Colon, + ) } else if let Some(captures) = AT.captures(self.rest) { - (captures.get(1).unwrap().as_str(), captures.get(2).unwrap().as_str(), At) + ( + captures.get(1).unwrap().as_str(), + captures.get(2).unwrap().as_str(), + At, + ) } else if let Some(captures) = COMMA.captures(self.rest) { - (captures.get(1).unwrap().as_str(), captures.get(2).unwrap().as_str(), Comma) + ( + captures.get(1).unwrap().as_str(), + captures.get(2).unwrap().as_str(), + Comma, + ) } else if let Some(captures) = PAREN_L.captures(self.rest) { - (captures.get(1).unwrap().as_str(), captures.get(2).unwrap().as_str(), ParenL) + ( + captures.get(1).unwrap().as_str(), + captures.get(2).unwrap().as_str(), + ParenL, + ) } else if let Some(captures) = PAREN_R.captures(self.rest) { - (captures.get(1).unwrap().as_str(), captures.get(2).unwrap().as_str(), ParenR) + ( + captures.get(1).unwrap().as_str(), + captures.get(2).unwrap().as_str(), + ParenR, + ) } else if let Some(captures) = PLUS.captures(self.rest) { - (captures.get(1).unwrap().as_str(), captures.get(2).unwrap().as_str(), Plus) + ( + captures.get(1).unwrap().as_str(), + captures.get(2).unwrap().as_str(), + Plus, + ) } else if let Some(captures) = EQUALS.captures(self.rest) { - (captures.get(1).unwrap().as_str(), captures.get(2).unwrap().as_str(), Equals) + ( + captures.get(1).unwrap().as_str(), + captures.get(2).unwrap().as_str(), + Equals, + ) } else if let Some(captures) = COMMENT.captures(self.rest) { - (captures.get(1).unwrap().as_str(), captures.get(2).unwrap().as_str(), Comment) + ( + captures.get(1).unwrap().as_str(), + captures.get(2).unwrap().as_str(), + Comment, + ) } else if let Some(captures) = RAW_STRING.captures(self.rest) { - (captures.get(1).unwrap().as_str(), captures.get(2).unwrap().as_str(), RawString) + ( + captures.get(1).unwrap().as_str(), + captures.get(2).unwrap().as_str(), + RawString, + ) } else if UNTERMINATED_RAW_STRING.is_match(self.rest) { return Err(self.error(UnterminatedString)); } else if let Some(captures) = STRING.captures(self.rest) { let prefix = captures.get(1).unwrap().as_str(); - let contents = &self.rest[prefix.len()+1..]; + let contents = &self.rest[prefix.len() + 1..]; if contents.is_empty() { return Err(self.error(UnterminatedString)); } @@ -266,10 +336,12 @@ impl<'a> Lexer<'a> { if len == 0 { let last = self.tokens.last().unwrap(); match last.kind { - Eof => {}, - _ => return Err(last.error(Internal { - message: format!("zero length token: {:?}", last) - })), + Eof => {} + _ => { + return Err(last.error(Internal { + message: format!("zero length token: {:?}", last), + })) + } } } @@ -314,46 +386,55 @@ mod test { let input = $input; let expected = $expected; let tokens = ::Lexer::lex(input).unwrap(); - let roundtrip = tokens.iter().map(|t| { - let mut s = String::new(); - s += t.prefix; - s += t.lexeme; - s - }).collect::>().join(""); + let roundtrip = tokens + .iter() + .map(|t| { + let mut s = String::new(); + s += t.prefix; + s += t.lexeme; + s + }) + .collect::>() + .join(""); let actual = token_summary(&tokens); if actual != expected { - panic!("token summary mismatch:\nexpected: {}\ngot: {}\n", expected, actual); + panic!( + "token summary mismatch:\nexpected: {}\ngot: {}\n", + expected, actual + ); } assert_eq!(input, roundtrip); } - } + }; } fn token_summary(tokens: &[Token]) -> String { - tokens.iter().map(|t| { - match t.kind { - At => "@", - Backtick => "`", - Colon => ":", - Comma => ",", - Comment{..} => "#", - Dedent => "<", - Eof => ".", - Eol => "$", - Equals => "=", - Indent{..} => ">", - InterpolationEnd => "}", + tokens + .iter() + .map(|t| match t.kind { + At => "@", + Backtick => "`", + Colon => ":", + Comma => ",", + Comment { .. } => "#", + Dedent => "<", + Eof => ".", + Eol => "$", + Equals => "=", + Indent { .. } => ">", + InterpolationEnd => "}", InterpolationStart => "{", - Line{..} => "^", - Name => "N", - ParenL => "(", - ParenR => ")", - Plus => "+", - RawString => "'", - StringToken => "\"", - Text => "_", - } - }).collect::>().join("") + Line { .. } => "^", + Name => "N", + ParenL => "(", + ParenR => ")", + Plus => "+", + RawString => "'", + StringToken => "\"", + Text => "_", + }) + .collect::>() + .join("") } macro_rules! error_test { @@ -371,26 +452,26 @@ mod test { let input = $input; let expected = CompilationError { - text: input, - index: $index, - line: $line, + text: input, + index: $index, + line: $line, column: $column, - width: $width, - kind: $kind, + width: $width, + kind: $kind, }; if let Err(error) = Lexer::lex(input) { - assert_eq!(error.text, expected.text); - assert_eq!(error.index, expected.index); - assert_eq!(error.line, expected.line); + assert_eq!(error.text, expected.text); + assert_eq!(error.index, expected.index); + assert_eq!(error.line, expected.line); assert_eq!(error.column, expected.column); - assert_eq!(error.kind, expected.kind); - assert_eq!(error, expected); + assert_eq!(error.kind, expected.kind); + assert_eq!(error, expected); } else { panic!("tokenize succeeded but expected: {}\n{}", expected, input); } } - } + }; } summary_test! { diff --git a/src/load_dotenv.rs b/src/load_dotenv.rs index e20c47d..de55799 100644 --- a/src/load_dotenv.rs +++ b/src/load_dotenv.rs @@ -6,12 +6,14 @@ pub fn load_dotenv() -> RunResult<'static, Map> { match dotenv::dotenv_iter() { Ok(iter) => { let result: dotenv::Result> = iter.collect(); - result.map_err(|dotenv_error| RuntimeError::Dotenv{dotenv_error}) + result.map_err(|dotenv_error| RuntimeError::Dotenv { dotenv_error }) } - Err(dotenv_error) => if dotenv_error.not_found() { - Ok(Map::new()) - } else { - Err(RuntimeError::Dotenv{dotenv_error}) + Err(dotenv_error) => { + if dotenv_error.not_found() { + Ok(Map::new()) + } else { + Err(RuntimeError::Dotenv { dotenv_error }) + } } } } diff --git a/src/misc.rs b/src/misc.rs index 52e957a..86fb724 100644 --- a/src/misc.rs +++ b/src/misc.rs @@ -3,7 +3,14 @@ use common::*; use unicode_width::UnicodeWidthChar; pub fn show_whitespace(text: &str) -> String { - text.chars().map(|c| match c { '\t' => '␉', ' ' => '␠', _ => c }).collect() + text + .chars() + .map(|c| match c { + '\t' => '␉', + ' ' => '␠', + _ => c, + }) + .collect() } pub fn default() -> T { @@ -27,15 +34,16 @@ pub fn maybe_s(n: usize) -> &'static str { } pub fn conjoin( - f: &mut fmt::Formatter, - values: &[T], + f: &mut fmt::Formatter, + values: &[T], conjunction: &str, ) -> Result<(), fmt::Error> { - match values.len() { - 0 => {}, - 1 => write!(f, "{}", values[0])?, - 2 => write!(f, "{} {} {}", values[0], conjunction, values[1])?, - _ => for (i, item) in values.iter().enumerate() { + match values.len() { + 0 => {} + 1 => write!(f, "{}", values[0])?, + 2 => write!(f, "{} {} {}", values[0], conjunction, values[1])?, + _ => { + for (i, item) in values.iter().enumerate() { write!(f, "{}", item)?; if i == values.len() - 1 { } else if i == values.len() - 2 { @@ -43,18 +51,19 @@ pub fn conjoin( } else { write!(f, ", ")? } - }, + } } - Ok(()) + } + Ok(()) } pub fn write_error_context( - f: &mut fmt::Formatter, - text: &str, - index: usize, - line: usize, + f: &mut fmt::Formatter, + text: &str, + index: usize, + line: usize, column: usize, - width: Option, + width: Option, ) -> Result<(), fmt::Error> { let line_number = line + 1; let red = Color::fmt(f).error(); @@ -62,8 +71,8 @@ pub fn write_error_context( Some(line) => { let mut i = 0; let mut space_column = 0; - let mut space_line = String::new(); - let mut space_width = 0; + let mut space_line = String::new(); + let mut space_width = 0; for c in line.chars() { if c == '\t' { space_line.push_str(" "); @@ -76,7 +85,6 @@ pub fn write_error_context( } else { if i < column { space_column += UnicodeWidthChar::width(c).unwrap_or(0); - } if i >= column && i < column + width.unwrap_or(1) { space_width += UnicodeWidthChar::width(c).unwrap_or(0); @@ -90,15 +98,36 @@ pub fn write_error_context( writeln!(f, "{} | {}", line_number, space_line)?; write!(f, "{0:1$} |", "", line_number_width)?; if width == None { - write!(f, " {0:1$}{2}^{3}", "", space_column, red.prefix(), red.suffix())?; + write!( + f, + " {0:1$}{2}^{3}", + "", + space_column, + red.prefix(), + red.suffix() + )?; } else { - write!(f, " {0:1$}{2}{3:^<4$}{5}", "", space_column, - red.prefix(), "", space_width, red.suffix())?; + write!( + f, + " {0:1$}{2}{3:^<4$}{5}", + "", + space_column, + red.prefix(), + "", + space_width, + red.suffix() + )?; } - }, - None => if index != text.len() { - write!(f, "internal error: Error has invalid line number: {}", line_number)? - }, + } + None => { + if index != text.len() { + write!( + f, + "internal error: Error has invalid line number: {}", + line_number + )? + } + } } Ok(()) } @@ -111,7 +140,7 @@ impl<'a, T: Display> Display for And<'a, T> { } } -pub struct Or <'a, T: 'a + Display>(pub &'a [T]); +pub struct Or<'a, T: 'a + Display>(pub &'a [T]); impl<'a, T: Display> Display for Or<'a, T> { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { @@ -133,17 +162,17 @@ mod test { #[test] fn conjoin_or() { - assert_eq!("1", Or(&[1 ]).to_string()); - assert_eq!("1 or 2", Or(&[1,2 ]).to_string()); - assert_eq!("1, 2, or 3", Or(&[1,2,3 ]).to_string()); - assert_eq!("1, 2, 3, or 4", Or(&[1,2,3,4]).to_string()); + assert_eq!("1", Or(&[1]).to_string()); + assert_eq!("1 or 2", Or(&[1, 2]).to_string()); + assert_eq!("1, 2, or 3", Or(&[1, 2, 3]).to_string()); + assert_eq!("1, 2, 3, or 4", Or(&[1, 2, 3, 4]).to_string()); } #[test] fn conjoin_and() { - assert_eq!("1", And(&[1 ]).to_string()); - assert_eq!("1 and 2", And(&[1,2 ]).to_string()); - assert_eq!("1, 2, and 3", And(&[1,2,3 ]).to_string()); - assert_eq!("1, 2, 3, and 4", And(&[1,2,3,4]).to_string()); + assert_eq!("1", And(&[1]).to_string()); + assert_eq!("1 and 2", And(&[1, 2]).to_string()); + assert_eq!("1, 2, and 3", And(&[1, 2, 3]).to_string()); + assert_eq!("1, 2, 3, and 4", And(&[1, 2, 3, 4]).to_string()); } } diff --git a/src/parameter.rs b/src/parameter.rs index 54cdd30..5945fae 100644 --- a/src/parameter.rs +++ b/src/parameter.rs @@ -2,9 +2,9 @@ use common::*; #[derive(PartialEq, Debug)] pub struct Parameter<'a> { - pub default: Option, - pub name: &'a str, - pub token: Token<'a>, + pub default: Option, + pub name: &'a str, + pub token: Token<'a>, pub variadic: bool, } @@ -16,7 +16,10 @@ impl<'a> Display for Parameter<'a> { } write!(f, "{}", color.parameter().paint(self.name))?; if let Some(ref default) = self.default { - let escaped = default.chars().flat_map(char::escape_default).collect::();; + let escaped = default + .chars() + .flat_map(char::escape_default) + .collect::();; write!(f, r#"='{}'"#, color.string().paint(&escaped))?; } Ok(()) diff --git a/src/parser.rs b/src/parser.rs index 31e24b0..e6ec475 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,16 +1,16 @@ use common::*; use itertools; -use TokenKind::*; use CompilationErrorKind::*; +use TokenKind::*; pub struct Parser<'a> { - text: &'a str, - tokens: itertools::PutBackN>>, - recipes: Map<&'a str, Recipe<'a>>, - assignments: Map<&'a str, Expression<'a>>, + text: &'a str, + tokens: itertools::PutBackN>>, + recipes: Map<&'a str, Recipe<'a>>, + assignments: Map<&'a str, Expression<'a>>, assignment_tokens: Map<&'a str, Token<'a>>, - exports: Set<&'a str>, + exports: Set<&'a str>, } impl<'a> Parser<'a> { @@ -22,11 +22,11 @@ impl<'a> Parser<'a> { pub fn new(text: &'a str, tokens: Vec>) -> Parser<'a> { Parser { - tokens: itertools::put_back_n(tokens), - recipes: empty(), - assignments: empty(), + tokens: itertools::put_back_n(tokens), + recipes: empty(), + assignments: empty(), assignment_tokens: empty(), - exports: empty(), + exports: empty(), text, } } @@ -83,20 +83,20 @@ impl<'a> Parser<'a> { fn unexpected_token(&self, found: &Token<'a>, expected: &[TokenKind]) -> CompilationError<'a> { found.error(UnexpectedToken { expected: expected.to_vec(), - found: found.kind, + found: found.kind, }) } fn recipe( &mut self, - name: &Token<'a>, - doc: Option>, + name: &Token<'a>, + doc: Option>, quiet: bool, ) -> CompilationResult<'a, ()> { if let Some(recipe) = self.recipes.get(name.lexeme) { return Err(name.error(DuplicateRecipe { recipe: recipe.name, - first: recipe.line_number + first: recipe.line_number, })); } @@ -108,11 +108,13 @@ impl<'a> Parser<'a> { let parameter = match self.accept(Name) { Some(parameter) => parameter, - None => if let Some(plus) = plus { - return Err(self.unexpected_token(&plus, &[Name])); - } else { - break - }, + None => { + if let Some(plus) = plus { + return Err(self.unexpected_token(&plus, &[Name])); + } else { + break; + } + } }; let variadic = plus.is_some(); @@ -125,7 +127,8 @@ impl<'a> Parser<'a> { if parameters.iter().any(|p| p.name == parameter.lexeme) { return Err(parameter.error(DuplicateParameter { - recipe: name.lexeme, parameter: parameter.lexeme + recipe: name.lexeme, + parameter: parameter.lexeme, })); } @@ -142,7 +145,7 @@ impl<'a> Parser<'a> { } if parsed_parameter_with_default && default.is_none() { - return Err(parameter.error(RequiredParameterFollowsDefaultParameter{ + return Err(parameter.error(RequiredParameterFollowsDefaultParameter { parameter: parameter.lexeme, })); } @@ -151,8 +154,8 @@ impl<'a> Parser<'a> { parsed_variadic_parameter = variadic; parameters.push(Parameter { - name: parameter.lexeme, - token: parameter, + name: parameter.lexeme, + token: parameter, default, variadic, }); @@ -173,8 +176,8 @@ impl<'a> Parser<'a> { while let Some(dependency) = self.accept(Name) { if dependencies.contains(&dependency.lexeme) { return Err(dependency.error(DuplicateDependency { - recipe: name.lexeme, - dependency: dependency.lexeme + recipe: name.lexeme, + dependency: dependency.lexeme, })); } dependencies.push(dependency.lexeme); @@ -195,9 +198,9 @@ impl<'a> Parser<'a> { continue; } if let Some(token) = self.expect(Line) { - return Err(token.error(Internal{ - message: format!("Expected a line but got {}", token.kind) - })) + return Err(token.error(Internal { + message: format!("Expected a line but got {}", token.kind), + })); } let mut fragments = vec![]; @@ -209,18 +212,22 @@ impl<'a> Parser<'a> { shebang = true; } } else if !shebang - && !lines.last().and_then(|line| line.last()) - .map(Fragment::continuation).unwrap_or(false) - && (token.lexeme.starts_with(' ') || 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(ExtraLeadingWhitespace)); } } - fragments.push(Fragment::Text{text: token}); + fragments.push(Fragment::Text { text: token }); } else if let Some(token) = self.expect(InterpolationStart) { return Err(self.unexpected_token(&token, &[Text, InterpolationStart, Eol])); } else { - fragments.push(Fragment::Expression{ - expression: self.expression()? + fragments.push(Fragment::Expression { + expression: self.expression()?, }); if let Some(token) = self.expect(InterpolationEnd) { @@ -233,18 +240,21 @@ impl<'a> Parser<'a> { } } - self.recipes.insert(name.lexeme, Recipe { - line_number: name.line, - name: name.lexeme, - doc: doc.map(|t| t.lexeme[1..].trim()), - private: &name.lexeme[0..1] == "_", - dependencies, - dependency_tokens, - lines, - parameters, - quiet, - shebang, - }); + self.recipes.insert( + name.lexeme, + Recipe { + line_number: name.line, + name: name.lexeme, + doc: doc.map(|t| t.lexeme[1..].trim()), + private: &name.lexeme[0..1] == "_", + dependencies, + dependency_tokens, + lines, + parameters, + quiet, + shebang, + }, + ); Ok(()) } @@ -261,24 +271,34 @@ impl<'a> Parser<'a> { if let Some(token) = self.expect(ParenR) { return Err(self.unexpected_token(&token, &[Name, StringToken, ParenR])); } - Expression::Call {name: first.lexeme, token: first, arguments} + Expression::Call { + name: first.lexeme, + token: first, + arguments, + } } else { - Expression::Variable {name: first.lexeme, token: first} + Expression::Variable { + name: first.lexeme, + token: first, + } } } Backtick => Expression::Backtick { - raw: &first.lexeme[1..first.lexeme.len()-1], - token: first + raw: &first.lexeme[1..first.lexeme.len() - 1], + token: first, + }, + RawString | StringToken => Expression::String { + cooked_string: CookedString::new(&first)?, }, - RawString | StringToken => { - Expression::String{cooked_string: CookedString::new(&first)?} - } _ => return Err(self.unexpected_token(&first, &[Name, StringToken])), }; if self.accepted(Plus) { let rhs = self.expression()?; - Ok(Expression::Concatination{lhs: Box::new(lhs), rhs: Box::new(rhs)}) + Ok(Expression::Concatination { + lhs: Box::new(lhs), + rhs: Box::new(rhs), + }) } else { Ok(lhs) } @@ -304,7 +324,9 @@ impl<'a> Parser<'a> { fn assignment(&mut self, name: Token<'a>, export: bool) -> CompilationResult<'a, ()> { if self.assignments.contains_key(name.lexeme) { - return Err(name.error(DuplicateVariable {variable: name.lexeme})); + return Err(name.error(DuplicateVariable { + variable: name.lexeme, + })); } if export { self.exports.insert(name.lexeme); @@ -338,49 +360,58 @@ impl<'a> Parser<'a> { } doc = Some(token); } - At => if let Some(name) = self.accept(Name) { - self.recipe(&name, doc, true)?; - doc = None; - } else { - let unexpected = &self.tokens.next().unwrap(); - return Err(self.unexpected_token(unexpected, &[Name])); - }, - Name => if token.lexeme == "export" { - let next = self.tokens.next().unwrap(); - if next.kind == Name && self.accepted(Equals) { - self.assignment(next, true)?; + At => { + if let Some(name) = self.accept(Name) { + self.recipe(&name, doc, true)?; + doc = None; + } else { + let unexpected = &self.tokens.next().unwrap(); + return Err(self.unexpected_token(unexpected, &[Name])); + } + } + Name => { + if token.lexeme == "export" { + let next = self.tokens.next().unwrap(); + if next.kind == Name && self.accepted(Equals) { + self.assignment(next, true)?; + doc = None; + } else { + self.tokens.put_back(next); + self.recipe(&token, doc, false)?; + doc = None; + } + } else if self.accepted(Equals) { + self.assignment(token, false)?; doc = None; } else { - self.tokens.put_back(next); self.recipe(&token, doc, false)?; doc = None; } - } else if self.accepted(Equals) { - self.assignment(token, false)?; - doc = None; - } else { - self.recipe(&token, doc, false)?; - doc = None; - }, + } _ => return Err(self.unexpected_token(&token, &[Name, At])), }, - None => return Err(CompilationError { - text: self.text, - index: 0, - line: 0, - column: 0, - width: None, - kind: Internal { - message: "unexpected end of token stream".to_string() - } - }), + None => { + return Err(CompilationError { + text: self.text, + index: 0, + line: 0, + column: 0, + width: None, + kind: Internal { + message: "unexpected end of token stream".to_string(), + }, + }) + } } } if let Some(token) = self.tokens.next() { return Err(token.error(Internal { - message: format!("unexpected token remaining after parsing completed: {:?}", token.kind) - })) + message: format!( + "unexpected token remaining after parsing completed: {:?}", + token.kind + ), + })); } RecipeResolver::resolve_recipes(&self.recipes, &self.assignments, self.text)?; @@ -389,7 +420,7 @@ impl<'a> Parser<'a> { for parameter in &recipe.parameters { if self.assignments.contains_key(parameter.token.lexeme) { return Err(parameter.token.error(ParameterShadowsVariable { - parameter: parameter.token.lexeme + parameter: parameter.token.lexeme, })); } } @@ -407,9 +438,9 @@ impl<'a> Parser<'a> { AssignmentResolver::resolve_assignments(&self.assignments, &self.assignment_tokens)?; Ok(Justfile { - recipes: self.recipes, + recipes: self.recipes, assignments: self.assignments, - exports: self.exports, + exports: self.exports, }) } } @@ -434,7 +465,7 @@ mod test { assert_eq!(actual, expected); } } - } + }; } summary_test! { @@ -502,7 +533,7 @@ export a = "hello" } summary_test! { - parse_complex, + parse_complex, " x: y: @@ -540,7 +571,7 @@ z:" } summary_test! { - parse_shebang, + parse_shebang, " practicum = 'hello' install: @@ -565,7 +596,7 @@ install: } summary_test! { - parse_assignments, + parse_assignments, r#"a = "0" c = a + b + a + b b = "1" @@ -578,7 +609,7 @@ c = a + b + a + b"#, } summary_test! { - parse_assignment_backticks, + parse_assignment_backticks, "a = `echo hello` c = a + b + a + b b = `echo goodbye`", @@ -590,7 +621,7 @@ c = a + b + a + b", } summary_test! { - parse_interpolation_backticks, + parse_interpolation_backticks, r#"a: echo {{ `echo hello` + "blarg" }} {{ `echo bob` }}"#, r#"a: @@ -616,7 +647,7 @@ c = a + b + a + b", } summary_test! { - parameters, + parameters, "a b c: {{b}} {{c}}", "a b c: @@ -624,7 +655,7 @@ c = a + b + a + b", } summary_test! { - unary_functions, + unary_functions, " x = arch() @@ -637,7 +668,7 @@ a: } summary_test! { - env_functions, + env_functions, r#" x = env_var('foo',) diff --git a/src/platform.rs b/src/platform.rs index 5e6de39..7e8c528 100644 --- a/src/platform.rs +++ b/src/platform.rs @@ -7,8 +7,11 @@ pub struct Platform; pub trait PlatformInterface { /// Construct a command equivelant to running the script at `path` with the /// shebang line `shebang` - fn make_shebang_command(path: &Path, command: &str, argument: Option<&str>) - -> Result; + fn make_shebang_command( + path: &Path, + command: &str, + argument: Option<&str>, + ) -> Result; /// Set the execute permission on the file pointed to by `path` fn set_execute_permission(path: &Path) -> Result<(), io::Error>; @@ -20,12 +23,13 @@ pub trait PlatformInterface { fn to_shell_path(path: &Path) -> Result; } - #[cfg(unix)] impl PlatformInterface for Platform { - fn make_shebang_command(path: &Path, _command: &str, _argument: Option<&str>) - -> Result - { + fn make_shebang_command( + path: &Path, + _command: &str, + _argument: Option<&str>, + ) -> Result { // shebang scripts can be executed directly on unix Ok(Command::new(path)) } @@ -50,17 +54,20 @@ impl PlatformInterface for Platform { } fn to_shell_path(path: &Path) -> Result { - path.to_str().map(str::to_string) - .ok_or_else(|| String::from( - "Error getting current directory: unicode decode error")) + path + .to_str() + .map(str::to_string) + .ok_or_else(|| String::from("Error getting current directory: unicode decode error")) } } #[cfg(windows)] impl PlatformInterface for Platform { - fn make_shebang_command(path: &Path, command: &str, argument: Option<&str>) - -> Result - { + fn make_shebang_command( + path: &Path, + command: &str, + argument: Option<&str>, + ) -> Result { // Translate path to the interpreter from unix style to windows style let mut cygpath = Command::new("cygpath"); cygpath.arg("--windows"); diff --git a/src/range_ext.rs b/src/range_ext.rs index db70cec..e5fa976 100644 --- a/src/range_ext.rs +++ b/src/range_ext.rs @@ -4,8 +4,11 @@ pub trait RangeExt { fn range_contains(&self, i: T) -> bool; } -impl RangeExt for Range where T: PartialOrd + Copy { - fn range_contains(&self, i: T) -> bool { +impl RangeExt for Range +where + T: PartialOrd + Copy, +{ + fn range_contains(&self, i: T) -> bool { i >= self.start && i < self.end } } @@ -16,10 +19,10 @@ mod test { #[test] fn range() { - assert!( ( 0.. 1).range_contains( 0)); - assert!( (10..20).range_contains(15)); - assert!(!( 0.. 0).range_contains( 0)); - assert!(!( 1..10).range_contains( 0)); - assert!(!( 1..10).range_contains(10)); + assert!((0..1).range_contains(0)); + assert!((10..20).range_contains(15)); + assert!(!(0..0).range_contains(0)); + assert!(!(1..10).range_contains(0)); + assert!(!(1..10).range_contains(10)); } } diff --git a/src/recipe.rs b/src/recipe.rs index 90cea69..07bff18 100644 --- a/src/recipe.rs +++ b/src/recipe.rs @@ -1,40 +1,47 @@ use common::*; -use std::process::{ExitStatus, Command, Stdio}; +use std::process::{Command, ExitStatus, Stdio}; use platform::{Platform, PlatformInterface}; /// Return a `RuntimeError::Signal` if the process was terminated by a signal, /// otherwise return an `RuntimeError::UnknownFailure` fn error_from_signal( - recipe: &str, + recipe: &str, line_number: Option, - exit_status: ExitStatus + exit_status: ExitStatus, ) -> RuntimeError { match Platform::signal_from_exit_status(exit_status) { - Some(signal) => RuntimeError::Signal{recipe, line_number, signal}, - None => RuntimeError::Unknown{recipe, line_number}, + Some(signal) => RuntimeError::Signal { + recipe, + line_number, + signal, + }, + None => RuntimeError::Unknown { + recipe, + line_number, + }, } } #[derive(PartialEq, Debug)] pub struct Recipe<'a> { - pub dependencies: Vec<&'a str>, + pub dependencies: Vec<&'a str>, pub dependency_tokens: Vec>, - pub doc: Option<&'a str>, - pub line_number: usize, - pub lines: Vec>>, - pub name: &'a str, - pub parameters: Vec>, - pub private: bool, - pub quiet: bool, - pub shebang: bool, + pub doc: Option<&'a str>, + pub line_number: usize, + pub lines: Vec>>, + pub name: &'a str, + pub parameters: Vec>, + pub private: bool, + pub quiet: bool, + pub shebang: bool, } pub struct RecipeContext<'a> { pub invocation_directory: &'a Result, - pub configuration: &'a Configuration<'a>, - pub scope: Map<&'a str, String>, + pub configuration: &'a Configuration<'a>, + pub scope: Map<&'a str, String>, } impl<'a> Recipe<'a> { @@ -43,7 +50,11 @@ impl<'a> Recipe<'a> { } pub fn min_arguments(&self) -> usize { - self.parameters.iter().filter(|p| p.default.is_none()).count() + self + .parameters + .iter() + .filter(|p| p.default.is_none()) + .count() } pub fn max_arguments(&self) -> usize { @@ -56,16 +67,21 @@ impl<'a> Recipe<'a> { pub fn run( &self, - context: &RecipeContext<'a>, - arguments: &[&'a str], - dotenv: &Map, - exports: &Set<&'a str>, + context: &RecipeContext<'a>, + arguments: &[&'a str], + dotenv: &Map, + exports: &Set<&'a str>, ) -> RunResult<'a, ()> { let configuration = &context.configuration; if configuration.verbosity.loquacious() { let color = configuration.color.stderr().banner(); - eprintln!("{}===> Running recipe `{}`...{}", color.prefix(), self.name, color.suffix()); + eprintln!( + "{}===> Running recipe `{}`...{}", + color.prefix(), + self.name, + color.suffix() + ); } let mut argument_map = Map::new(); @@ -75,9 +91,11 @@ impl<'a> Recipe<'a> { let value = if rest.is_empty() { match parameter.default { Some(ref default) => Cow::Borrowed(default.as_str()), - None => return Err(RuntimeError::Internal{ - message: "missing parameter without default".to_string() - }), + None => { + return Err(RuntimeError::Internal { + message: "missing parameter without default".to_string(), + }) + } } } else if parameter.variadic { let value = Cow::Owned(rest.to_vec().join(" ")); @@ -92,14 +110,14 @@ impl<'a> Recipe<'a> { } let mut evaluator = AssignmentEvaluator { - assignments: &empty(), - dry_run: configuration.dry_run, - evaluated: empty(), + assignments: &empty(), + dry_run: configuration.dry_run, + evaluated: empty(), invocation_directory: context.invocation_directory, - overrides: &empty(), - quiet: configuration.quiet, - scope: &context.scope, - shell: configuration.shell, + overrides: &empty(), + quiet: configuration.quiet, + scope: &context.scope, + shell: configuration.shell, dotenv, exports, }; @@ -120,13 +138,17 @@ impl<'a> Recipe<'a> { return Ok(()); } - let tmp = TempDir::new("just") - .map_err(|error| RuntimeError::TmpdirIoError{recipe: self.name, io_error: error})?; + let tmp = TempDir::new("just").map_err(|error| RuntimeError::TmpdirIoError { + recipe: self.name, + io_error: error, + })?; let mut path = tmp.path().to_path_buf(); path.push(self.name); { - let mut f = fs::File::create(&path) - .map_err(|error| RuntimeError::TmpdirIoError{recipe: self.name, io_error: error})?; + let mut f = fs::File::create(&path).map_err(|error| RuntimeError::TmpdirIoError { + recipe: self.name, + io_error: error, + })?; let mut text = String::new(); // add the shebang text += &evaluated_lines[0]; @@ -147,44 +169,65 @@ impl<'a> Recipe<'a> { } f.write_all(text.as_bytes()) - .map_err(|error| RuntimeError::TmpdirIoError{recipe: self.name, io_error: error})?; + .map_err(|error| RuntimeError::TmpdirIoError { + recipe: self.name, + io_error: error, + })?; } // make the script executable - Platform::set_execute_permission(&path) - .map_err(|error| RuntimeError::TmpdirIoError{recipe: self.name, io_error: error})?; + Platform::set_execute_permission(&path).map_err(|error| RuntimeError::TmpdirIoError { + recipe: self.name, + io_error: error, + })?; - let shebang_line = evaluated_lines.first() + let shebang_line = evaluated_lines + .first() .ok_or_else(|| RuntimeError::Internal { - message: "evaluated_lines was empty".to_string() + message: "evaluated_lines was empty".to_string(), })?; - let Shebang{interpreter, argument} = Shebang::new(shebang_line) - .ok_or_else(|| RuntimeError::Internal { - message: format!("bad shebang line: {}", shebang_line) - })?; + let Shebang { + interpreter, + argument, + } = Shebang::new(shebang_line).ok_or_else(|| RuntimeError::Internal { + message: format!("bad shebang line: {}", shebang_line), + })?; // create a command to run the script - let mut command = Platform::make_shebang_command(&path, interpreter, argument) - .map_err(|output_error| RuntimeError::Cygpath{recipe: self.name, output_error})?; + let mut command = + Platform::make_shebang_command(&path, interpreter, argument).map_err(|output_error| { + RuntimeError::Cygpath { + recipe: self.name, + output_error, + } + })?; command.export_environment_variables(&context.scope, dotenv, exports)?; // run it! match InterruptHandler::guard(|| command.status()) { - Ok(exit_status) => if let Some(code) = exit_status.code() { - if code != 0 { - return Err(RuntimeError::Code{recipe: self.name, line_number: None, code}) + Ok(exit_status) => { + if let Some(code) = exit_status.code() { + if code != 0 { + return Err(RuntimeError::Code { + recipe: self.name, + line_number: None, + code, + }); + } + } else { + return Err(error_from_signal(self.name, None, exit_status)); } - } else { - return Err(error_from_signal(self.name, None, exit_status)) - }, - Err(io_error) => return Err(RuntimeError::Shebang { - recipe: self.name, - command: interpreter.to_string(), - argument: argument.map(String::from), - io_error, - }) + } + Err(io_error) => { + return Err(RuntimeError::Shebang { + recipe: self.name, + command: interpreter.to_string(), + argument: argument.map(String::from), + io_error, + }) + } }; } else { let mut lines = self.lines.iter().peekable(); @@ -219,8 +262,7 @@ impl<'a> Recipe<'a> { if configuration.dry_run || configuration.verbosity.loquacious() - || !((quiet_command ^ self.quiet) - || configuration.quiet) + || !((quiet_command ^ self.quiet) || configuration.quiet) { let color = if configuration.highlight { configuration.color.command() @@ -246,19 +288,25 @@ impl<'a> Recipe<'a> { cmd.export_environment_variables(&context.scope, dotenv, exports)?; match InterruptHandler::guard(|| cmd.status()) { - Ok(exit_status) => if let Some(code) = exit_status.code() { - if code != 0 { - return Err(RuntimeError::Code{ - recipe: self.name, line_number: Some(line_number), code, - }); + Ok(exit_status) => { + if let Some(code) = exit_status.code() { + if code != 0 { + return Err(RuntimeError::Code { + recipe: self.name, + line_number: Some(line_number), + code, + }); + } + } else { + return Err(error_from_signal(self.name, Some(line_number), exit_status)); } - } else { - return Err(error_from_signal(self.name, Some(line_number), exit_status)); - }, - Err(io_error) => return Err(RuntimeError::IoError{ - recipe: self.name, - io_error, - }), + } + Err(io_error) => { + return Err(RuntimeError::IoError { + recipe: self.name, + io_error, + }) + } }; } } @@ -289,9 +337,8 @@ impl<'a> Display for Recipe<'a> { write!(f, " ")?; } match *piece { - Fragment::Text{ref text} => write!(f, "{}", text.lexeme)?, - Fragment::Expression{ref expression, ..} => - write!(f, "{{{{{}}}}}", expression)?, + Fragment::Text { ref text } => write!(f, "{}", text.lexeme)?, + Fragment::Expression { ref expression, .. } => write!(f, "{{{{{}}}}}", expression)?, } } if i + 1 < self.lines.len() { diff --git a/src/recipe_resolver.rs b/src/recipe_resolver.rs index 1291780..b624d3e 100644 --- a/src/recipe_resolver.rs +++ b/src/recipe_resolver.rs @@ -3,21 +3,21 @@ use common::*; use CompilationErrorKind::*; pub struct RecipeResolver<'a: 'b, 'b> { - stack: Vec<&'a str>, - seen: Set<&'a str>, + stack: Vec<&'a str>, + seen: Set<&'a str>, resolved: Set<&'a str>, - recipes: &'b Map<&'a str, Recipe<'a>>, + recipes: &'b Map<&'a str, Recipe<'a>>, } impl<'a, 'b> RecipeResolver<'a, 'b> { pub fn resolve_recipes( - recipes: &Map<&'a str, Recipe<'a>>, + recipes: &Map<&'a str, Recipe<'a>>, assignments: &Map<&'a str, Expression<'a>>, - text: &'a str, + text: &'a str, ) -> CompilationResult<'a, ()> { let mut resolver = RecipeResolver { - seen: empty(), - stack: empty(), + seen: empty(), + stack: empty(), resolved: empty(), recipes, }; @@ -40,15 +40,15 @@ impl<'a, 'b> RecipeResolver<'a, 'b> { for recipe in recipes.values() { for line in &recipe.lines { for fragment in line { - if let Fragment::Expression{ref expression, ..} = *fragment { + if let Fragment::Expression { ref expression, .. } = *fragment { for (function, argc) in expression.functions() { if let Err(error) = resolve_function(function, argc) { return Err(CompilationError { - index: error.index, - line: error.line, + index: error.index, + line: error.line, column: error.column, - width: error.width, - kind: UnknownFunction { + width: error.width, + kind: UnknownFunction { function: &text[error.index..error.index + error.width.unwrap()], }, text, @@ -60,13 +60,13 @@ impl<'a, 'b> RecipeResolver<'a, 'b> { let undefined = !assignments.contains_key(name) && !recipe.parameters.iter().any(|p| p.name == name); if undefined { - let error = variable.error(UndefinedVariable{variable: name}); + let error = variable.error(UndefinedVariable { variable: name }); return Err(CompilationError { - index: error.index, - line: error.line, + index: error.index, + line: error.line, column: error.column, - width: error.width, - kind: UndefinedVariable { + width: error.width, + kind: UndefinedVariable { variable: &text[error.index..error.index + error.width.unwrap()], }, text, @@ -83,29 +83,38 @@ impl<'a, 'b> RecipeResolver<'a, 'b> { fn resolve_recipe(&mut self, recipe: &Recipe<'a>) -> CompilationResult<'a, ()> { if self.resolved.contains(recipe.name) { - return Ok(()) + return Ok(()); } self.stack.push(recipe.name); self.seen.insert(recipe.name); for dependency_token in &recipe.dependency_tokens { match self.recipes.get(dependency_token.lexeme) { - Some(dependency) => if !self.resolved.contains(dependency.name) { - if self.seen.contains(dependency.name) { - let first = self.stack[0]; - self.stack.push(first); - return Err(dependency_token.error(CircularRecipeDependency { - recipe: recipe.name, - circle: self.stack.iter() - .skip_while(|name| **name != dependency.name) - .cloned().collect() - })); + Some(dependency) => { + if !self.resolved.contains(dependency.name) { + if self.seen.contains(dependency.name) { + let first = self.stack[0]; + self.stack.push(first); + return Err( + dependency_token.error(CircularRecipeDependency { + recipe: recipe.name, + circle: self + .stack + .iter() + .skip_while(|name| **name != dependency.name) + .cloned() + .collect(), + }), + ); + } + self.resolve_recipe(dependency)?; } - self.resolve_recipe(dependency)?; - }, - None => return Err(dependency_token.error(UnknownDependency { - recipe: recipe.name, - unknown: dependency_token.lexeme - })), + } + None => { + return Err(dependency_token.error(UnknownDependency { + recipe: recipe.name, + unknown: dependency_token.lexeme, + })) + } } } self.resolved.insert(recipe.name); diff --git a/src/run.rs b/src/run.rs index baaa91b..e628410 100644 --- a/src/run.rs +++ b/src/run.rs @@ -1,23 +1,21 @@ use common::*; -use std::{convert, ffi}; -use clap::{App, Arg, ArgGroup, AppSettings}; +use clap::{App, AppSettings, Arg, ArgGroup}; use configuration::DEFAULT_SHELL; -use misc::maybe_s; -use unicode_width::UnicodeWidthStr; use env_logger; use interrupt_handler::InterruptHandler; +use misc::maybe_s; +use std::{convert, ffi}; +use unicode_width::UnicodeWidthStr; #[cfg(windows)] use ansi_term::enable_ansi_support; fn edit>(path: P) -> ! { - let editor = env::var_os("EDITOR") - .unwrap_or_else(|| die!("Error getting EDITOR environment variable")); + let editor = + env::var_os("EDITOR").unwrap_or_else(|| die!("Error getting EDITOR environment variable")); - let error = Command::new(editor) - .arg(path) - .status(); + let error = Command::new(editor).arg(path).status(); match error { Ok(status) => process::exit(status.code().unwrap_or(EXIT_FAILURE)), @@ -42,102 +40,151 @@ pub fn run() { enable_ansi_support().ok(); env_logger::Builder::from_env( - env_logger::Env::new().filter("JUST_LOG").write_style("JUST_LOG_STYLE") - ).init(); + env_logger::Env::new() + .filter("JUST_LOG") + .write_style("JUST_LOG_STYLE"), + ) + .init(); - let invocation_directory = env::current_dir() - .map_err(|e| format!("Error getting current directory: {}", e)); + let invocation_directory = + env::current_dir().map_err(|e| format!("Error getting current directory: {}", e)); let matches = App::new(env!("CARGO_PKG_NAME")) .version(concat!("v", env!("CARGO_PKG_VERSION"))) .author(env!("CARGO_PKG_AUTHORS")) - .about(concat!(env!("CARGO_PKG_DESCRIPTION"), " - ", env!("CARGO_PKG_HOMEPAGE"))) + .about(concat!( + env!("CARGO_PKG_DESCRIPTION"), + " - ", + env!("CARGO_PKG_HOMEPAGE") + )) .help_message("Print help information") .version_message("Print version information") .setting(AppSettings::ColoredHelp) .setting(AppSettings::TrailingVarArg) - .arg(Arg::with_name("ARGUMENTS") - .multiple(true) - .help("The recipe(s) to run, defaults to the first recipe in the justfile")) - .arg(Arg::with_name("COLOR") - .long("color") - .takes_value(true) - .possible_values(&["auto", "always", "never"]) - .default_value("auto") - .help("Print colorful output")) - .arg(Arg::with_name("DRY-RUN") - .long("dry-run") - .help("Print what just would do without doing it") - .conflicts_with("QUIET")) - .arg(Arg::with_name("DUMP") - .long("dump") - .help("Print entire justfile")) - .arg(Arg::with_name("EDIT") - .short("e") - .long("edit") - .help("Open justfile with $EDITOR")) - .arg(Arg::with_name("EVALUATE") - .long("evaluate") - .help("Print evaluated variables")) - .arg(Arg::with_name("HIGHLIGHT") - .long("highlight") - .help("Highlight echoed recipe lines in bold")) - .arg(Arg::with_name("JUSTFILE") - .short("f") - .long("justfile") - .takes_value(true) - .help("Use as justfile. --working-directory must also be set") - .requires("WORKING-DIRECTORY")) - .arg(Arg::with_name("LIST") - .short("l") - .long("list") - .help("List available recipes and their arguments")) - .arg(Arg::with_name("QUIET") - .short("q") - .long("quiet") - .help("Suppress all output") - .conflicts_with("DRY-RUN")) - .arg(Arg::with_name("SET") - .long("set") - .takes_value(true) - .number_of_values(2) - .value_names(&["VARIABLE", "VALUE"]) - .multiple(true) - .help("Set to ")) - .arg(Arg::with_name("SHELL") - .long("shell") - .takes_value(true) - .default_value(DEFAULT_SHELL) - .help("Invoke to run recipes")) - .arg(Arg::with_name("SHOW") - .short("s") - .long("show") - .takes_value(true) - .value_name("RECIPE") - .help("Show information about ")) - .arg(Arg::with_name("SUMMARY") - .long("summary") - .help("List names of available recipes")) - .arg(Arg::with_name("VERBOSE") - .short("v") - .long("verbose") - .multiple(true) - .help("Use verbose output")) - .arg(Arg::with_name("WORKING-DIRECTORY") + .arg( + Arg::with_name("ARGUMENTS") + .multiple(true) + .help("The recipe(s) to run, defaults to the first recipe in the justfile"), + ) + .arg( + Arg::with_name("COLOR") + .long("color") + .takes_value(true) + .possible_values(&["auto", "always", "never"]) + .default_value("auto") + .help("Print colorful output"), + ) + .arg( + Arg::with_name("DRY-RUN") + .long("dry-run") + .help("Print what just would do without doing it") + .conflicts_with("QUIET"), + ) + .arg( + Arg::with_name("DUMP") + .long("dump") + .help("Print entire justfile"), + ) + .arg( + Arg::with_name("EDIT") + .short("e") + .long("edit") + .help("Open justfile with $EDITOR"), + ) + .arg( + Arg::with_name("EVALUATE") + .long("evaluate") + .help("Print evaluated variables"), + ) + .arg( + Arg::with_name("HIGHLIGHT") + .long("highlight") + .help("Highlight echoed recipe lines in bold"), + ) + .arg( + Arg::with_name("JUSTFILE") + .short("f") + .long("justfile") + .takes_value(true) + .help("Use as justfile. --working-directory must also be set") + .requires("WORKING-DIRECTORY"), + ) + .arg( + Arg::with_name("LIST") + .short("l") + .long("list") + .help("List available recipes and their arguments"), + ) + .arg( + Arg::with_name("QUIET") + .short("q") + .long("quiet") + .help("Suppress all output") + .conflicts_with("DRY-RUN"), + ) + .arg( + Arg::with_name("SET") + .long("set") + .takes_value(true) + .number_of_values(2) + .value_names(&["VARIABLE", "VALUE"]) + .multiple(true) + .help("Set to "), + ) + .arg( + Arg::with_name("SHELL") + .long("shell") + .takes_value(true) + .default_value(DEFAULT_SHELL) + .help("Invoke to run recipes"), + ) + .arg( + Arg::with_name("SHOW") + .short("s") + .long("show") + .takes_value(true) + .value_name("RECIPE") + .help("Show information about "), + ) + .arg( + Arg::with_name("SUMMARY") + .long("summary") + .help("List names of available recipes"), + ) + .arg( + Arg::with_name("VERBOSE") + .short("v") + .long("verbose") + .multiple(true) + .help("Use verbose output"), + ) + .arg( + Arg::with_name("WORKING-DIRECTORY") .short("d") .long("working-directory") .takes_value(true) .help("Use as working directory. --justfile must also be set") - .requires("JUSTFILE")) - .group(ArgGroup::with_name("EARLY-EXIT") - .args(&["DUMP", "EDIT", "LIST", "SHOW", "SUMMARY", "ARGUMENTS", "EVALUATE"])) + .requires("JUSTFILE"), + ) + .group(ArgGroup::with_name("EARLY-EXIT").args(&[ + "DUMP", + "EDIT", + "LIST", + "SHOW", + "SUMMARY", + "ARGUMENTS", + "EVALUATE", + ])) .get_matches(); let color = match matches.value_of("COLOR").expect("`--color` had no value") { - "auto" => Color::auto(), + "auto" => Color::auto(), "always" => Color::always(), - "never" => Color::never(), - other => die!("Invalid argument `{}` to --color. This is a bug in just.", other), + "never" => Color::never(), + other => die!( + "Invalid argument `{}` to --color. This is a bug in just.", + other + ), }; let set_count = matches.occurrences_of("SET"); @@ -151,15 +198,25 @@ pub fn run() { let override_re = Regex::new("^([^=]+)=(.*)$").unwrap(); - let raw_arguments = matches.values_of("ARGUMENTS").map(|values| values.collect::>()) + let raw_arguments = matches + .values_of("ARGUMENTS") + .map(|values| values.collect::>()) .unwrap_or_default(); - for argument in raw_arguments.iter().take_while(|arg| override_re.is_match(arg)) { + for argument in raw_arguments + .iter() + .take_while(|arg| override_re.is_match(arg)) + { let captures = override_re.captures(argument).unwrap(); - overrides.insert(captures.get(1).unwrap().as_str(), captures.get(2).unwrap().as_str()); + overrides.insert( + captures.get(1).unwrap().as_str(), + captures.get(2).unwrap().as_str(), + ); } - let rest = raw_arguments.iter().skip_while(|arg| override_re.is_match(arg)) + let rest = raw_arguments + .iter() + .skip_while(|arg| override_re.is_match(arg)) .enumerate() .flat_map(|(i, argument)| { if i == 0 { @@ -208,10 +265,12 @@ pub fn run() { 'outer: loop { for candidate in &["justfile", "Justfile"] { match fs::metadata(candidate) { - Ok(metadata) => if metadata.is_file() { - name = *candidate; - break 'outer; - }, + Ok(metadata) => { + if metadata.is_file() { + name = *candidate; + break 'outer; + } + } Err(error) => { if error.kind() != io::ErrorKind::NotFound { die!("Error fetching justfile metadata: {}", error) @@ -221,7 +280,11 @@ pub fn run() { } match env::current_dir() { - Ok(pathbuf) => if pathbuf.as_os_str() == "/" { die!("No justfile found."); }, + Ok(pathbuf) => { + if pathbuf.as_os_str() == "/" { + die!("No justfile found."); + } + } Err(error) => die!("Error getting current dir: {}", error), } @@ -240,19 +303,21 @@ pub fn run() { .unwrap_or_else(|error| die!("Error reading justfile: {}", error)); } - let justfile = Parser::parse(&text).unwrap_or_else(|error| + let justfile = Parser::parse(&text).unwrap_or_else(|error| { if color.stderr().active() { die!("{:#}", error); } else { die!("{}", error); } - ); + }); if matches.is_present("SUMMARY") { if justfile.count() == 0 { eprintln!("Justfile contains no recipes."); } else { - let summary = justfile.recipes.iter() + let summary = justfile + .recipes + .iter() .filter(|&(_, recipe)| !recipe.private) .map(|(name, _)| name) .cloned() @@ -305,10 +370,12 @@ pub fn run() { } if let Some(doc) = recipe.doc { print!( - " {:padding$}{} {}", "", doc_color.paint("#"), doc_color.paint(doc), - padding = max_line_width.saturating_sub( - line_widths.get(name).cloned().unwrap_or(max_line_width) - ) + " {:padding$}{} {}", + "", + doc_color.paint("#"), + doc_color.paint(doc), + padding = + max_line_width.saturating_sub(line_widths.get(name).cloned().unwrap_or(max_line_width)) ); } println!(); @@ -337,8 +404,12 @@ pub fn run() { } else if let Some(recipe) = justfile.first() { let min_arguments = recipe.min_arguments(); if min_arguments > 0 { - die!("Recipe `{}` cannot be used as default recipe since it requires at least {} argument{}.", - recipe.name, min_arguments, maybe_s(min_arguments)); + die!( + "Recipe `{}` cannot be used as default recipe since it requires at least {} argument{}.", + recipe.name, + min_arguments, + maybe_s(min_arguments) + ); } vec![recipe.name] } else { @@ -348,11 +419,11 @@ pub fn run() { let verbosity = Verbosity::from_flag_occurrences(matches.occurrences_of("VERBOSE")); let configuration = Configuration { - dry_run: matches.is_present("DRY-RUN"), - evaluate: matches.is_present("EVALUATE"), + dry_run: matches.is_present("DRY-RUN"), + evaluate: matches.is_present("EVALUATE"), highlight: matches.is_present("HIGHLIGHT"), - quiet: matches.is_present("QUIET"), - shell: matches.value_of("SHELL").unwrap(), + quiet: matches.is_present("QUIET"), + shell: matches.value_of("SHELL").unwrap(), verbosity, color, overrides, @@ -362,11 +433,7 @@ pub fn run() { warn!("Failed to set CTRL-C handler: {}", error) } - if let Err(run_error) = justfile.run( - &invocation_directory, - &arguments, - &configuration) - { + if let Err(run_error) = justfile.run(&invocation_directory, &arguments, &configuration) { if !configuration.quiet { if color.stderr().active() { eprintln!("{:#}", run_error); diff --git a/src/runtime_error.rs b/src/runtime_error.rs index ac982a2..26060aa 100644 --- a/src/runtime_error.rs +++ b/src/runtime_error.rs @@ -4,7 +4,7 @@ use dotenv; use brev::OutputError; -use misc::{And, Or, maybe_s, Tick, ticks, write_error_context}; +use misc::{maybe_s, ticks, write_error_context, And, Or, Tick}; use self::RuntimeError::*; @@ -17,38 +17,82 @@ fn write_token_error_context(f: &mut fmt::Formatter, token: &Token) -> Result<() token.index, token.line, token.column + token.prefix.len(), - Some(token.lexeme.len()) + Some(token.lexeme.len()), ) } #[derive(Debug)] pub enum RuntimeError<'a> { - ArgumentCountMismatch{ + ArgumentCountMismatch { recipe: &'a str, parameters: Vec<&'a Parameter<'a>>, found: usize, min: usize, max: usize, }, - Backtick{token: Token<'a>, output_error: OutputError}, - Code{recipe: &'a str, line_number: Option, code: i32}, - Cygpath{recipe: &'a str, output_error: OutputError}, - Dotenv{dotenv_error: dotenv::Error}, - FunctionCall{token: Token<'a>, message: String}, - Internal{message: String}, - IoError{recipe: &'a str, io_error: io::Error}, - Shebang{recipe: &'a str, command: String, argument: Option, io_error: io::Error}, - Signal{recipe: &'a str, line_number: Option, signal: i32}, - TmpdirIoError{recipe: &'a str, io_error: io::Error}, - UnknownOverrides{overrides: Vec<&'a str>}, - UnknownRecipes{recipes: Vec<&'a str>, suggestion: Option<&'a str>}, - Unknown{recipe: &'a str, line_number: Option}, + Backtick { + token: Token<'a>, + output_error: OutputError, + }, + Code { + recipe: &'a str, + line_number: Option, + code: i32, + }, + Cygpath { + recipe: &'a str, + output_error: OutputError, + }, + Dotenv { + dotenv_error: dotenv::Error, + }, + FunctionCall { + token: Token<'a>, + message: String, + }, + Internal { + message: String, + }, + IoError { + recipe: &'a str, + io_error: io::Error, + }, + Shebang { + recipe: &'a str, + command: String, + argument: Option, + io_error: io::Error, + }, + Signal { + recipe: &'a str, + line_number: Option, + signal: i32, + }, + TmpdirIoError { + recipe: &'a str, + io_error: io::Error, + }, + UnknownOverrides { + overrides: Vec<&'a str>, + }, + UnknownRecipes { + recipes: Vec<&'a str>, + suggestion: Option<&'a str>, + }, + Unknown { + recipe: &'a str, + line_number: Option, + }, } impl<'a> RuntimeError<'a> { pub fn code(&self) -> Option { match *self { - Code{code, ..} | Backtick{output_error: OutputError::Code(code), ..} => Some(code), + Code { code, .. } + | Backtick { + output_error: OutputError::Code(code), + .. + } => Some(code), _ => None, } } @@ -57,7 +101,11 @@ impl<'a> RuntimeError<'a> { impl<'a> Display for RuntimeError<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { use RuntimeError::*; - let color = if f.alternate() { Color::always() } else { Color::never() }; + let color = if f.alternate() { + Color::always() + } else { + Color::never() + }; let error = color.error(); let message = color.message(); write!(f, "{} {}", error.paint("error:"), message.prefix())?; @@ -65,30 +113,64 @@ impl<'a> Display for RuntimeError<'a> { let mut error_token = None; match *self { - UnknownRecipes{ref recipes, ref suggestion} => { - write!(f, "Justfile does not contain recipe{} {}.", - maybe_s(recipes.len()), Or(&ticks(recipes)))?; + UnknownRecipes { + ref recipes, + ref suggestion, + } => { + write!( + f, + "Justfile does not contain recipe{} {}.", + maybe_s(recipes.len()), + Or(&ticks(recipes)) + )?; if let Some(suggestion) = *suggestion { write!(f, "\nDid you mean `{}`?", suggestion)?; } - }, - UnknownOverrides{ref overrides} => { - write!(f, "Variable{} {} overridden on the command line but not present in justfile", - maybe_s(overrides.len()), - And(&overrides.iter().map(Tick).collect::>()))?; - }, - ArgumentCountMismatch{recipe, ref parameters, found, min, max} => { + } + UnknownOverrides { ref overrides } => { + write!( + f, + "Variable{} {} overridden on the command line but not present in justfile", + maybe_s(overrides.len()), + And(&overrides.iter().map(Tick).collect::>()) + )?; + } + ArgumentCountMismatch { + recipe, + ref parameters, + found, + min, + max, + } => { if min == max { let expected = min; - write!(f, "Recipe `{}` got {} argument{} but {}takes {}", - recipe, found, maybe_s(found), - if expected < found { "only " } else { "" }, expected)?; + write!( + f, + "Recipe `{}` got {} argument{} but {}takes {}", + recipe, + found, + maybe_s(found), + if expected < found { "only " } else { "" }, + expected + )?; } else if found < min { - write!(f, "Recipe `{}` got {} argument{} but takes at least {}", - recipe, found, maybe_s(found), min)?; + write!( + f, + "Recipe `{}` got {} argument{} but takes at least {}", + recipe, + found, + maybe_s(found), + min + )?; } else if found > max { - write!(f, "Recipe `{}` got {} argument{} but takes at most {}", - recipe, found, maybe_s(found), max)?; + write!( + f, + "Recipe `{}` got {} argument{} but takes at most {}", + recipe, + found, + maybe_s(found), + max + )?; } write!(f, "\nusage:\n just {}", recipe)?; for param in parameters { @@ -98,89 +180,170 @@ impl<'a> Display for RuntimeError<'a> { write!(f, " {}", param)?; } } - }, - Code{recipe, line_number, code} => { + } + Code { + recipe, + line_number, + code, + } => { if let Some(n) = line_number { - write!(f, "Recipe `{}` failed on line {} with exit code {}", recipe, n, code)?; + write!( + f, + "Recipe `{}` failed on line {} with exit code {}", + recipe, n, code + )?; } else { write!(f, "Recipe `{}` failed with exit code {}", recipe, code)?; } - }, - Cygpath{recipe, ref output_error} => match *output_error { + } + Cygpath { + recipe, + ref output_error, + } => match *output_error { OutputError::Code(code) => { - write!(f, "Cygpath failed with exit code {} while translating recipe `{}` \ - shebang interpreter path", code, recipe)?; + write!( + f, + "Cygpath failed with exit code {} while translating recipe `{}` \ + shebang interpreter path", + code, recipe + )?; } OutputError::Signal(signal) => { - write!(f, "Cygpath terminated by signal {} while translating recipe `{}` \ - shebang interpreter path", signal, recipe)?; + write!( + f, + "Cygpath terminated by signal {} while translating recipe `{}` \ + shebang interpreter path", + signal, recipe + )?; } OutputError::Unknown => { - write!(f, "Cygpath experienced an unknown failure while translating recipe `{}` \ - shebang interpreter path", recipe)?; + write!( + f, + "Cygpath experienced an unknown failure while translating recipe `{}` \ + shebang interpreter path", + recipe + )?; } OutputError::Io(ref io_error) => { match io_error.kind() { io::ErrorKind::NotFound => write!( - f, "Could not find `cygpath` executable to translate recipe `{}` \ - shebang interpreter path:\n{}", recipe, io_error), + f, + "Could not find `cygpath` executable to translate recipe `{}` \ + shebang interpreter path:\n{}", + recipe, io_error + ), io::ErrorKind::PermissionDenied => write!( - f, "Could not run `cygpath` executable to translate recipe `{}` \ - shebang interpreter path:\n{}", recipe, io_error), + f, + "Could not run `cygpath` executable to translate recipe `{}` \ + shebang interpreter path:\n{}", + recipe, io_error + ), _ => write!(f, "Could not run `cygpath` executable:\n{}", io_error), }?; } OutputError::Utf8(ref utf8_error) => { - write!(f, "Cygpath successfully translated recipe `{}` shebang interpreter path, \ - but output was not utf8: {}", recipe, utf8_error)?; + write!( + f, + "Cygpath successfully translated recipe `{}` shebang interpreter path, \ + but output was not utf8: {}", + recipe, utf8_error + )?; } }, - Dotenv{ref dotenv_error} => { + Dotenv { ref dotenv_error } => { writeln!(f, "Failed to load .env: {}", dotenv_error)?; } - FunctionCall{ref token, ref message} => { + FunctionCall { + ref token, + ref message, + } => { writeln!(f, "Call to function `{}` failed: {}", token.lexeme, message)?; error_token = Some(token); } - Shebang{recipe, ref command, ref argument, ref io_error} => { + Shebang { + recipe, + ref command, + ref argument, + ref io_error, + } => { if let Some(ref argument) = *argument { - write!(f, "Recipe `{}` with shebang `#!{} {}` execution error: {}", - recipe, command, argument, io_error)?; + write!( + f, + "Recipe `{}` with shebang `#!{} {}` execution error: {}", + recipe, command, argument, io_error + )?; } else { - write!(f, "Recipe `{}` with shebang `#!{}` execution error: {}", - recipe, command, io_error)?; + write!( + f, + "Recipe `{}` with shebang `#!{}` execution error: {}", + recipe, command, io_error + )?; } } - Signal{recipe, line_number, signal} => { + Signal { + recipe, + line_number, + signal, + } => { if let Some(n) = line_number { - write!(f, "Recipe `{}` was terminated on line {} by signal {}", recipe, n, signal)?; + write!( + f, + "Recipe `{}` was terminated on line {} by signal {}", + recipe, n, signal + )?; } else { write!(f, "Recipe `{}` was terminated by signal {}", recipe, signal)?; } } - Unknown{recipe, line_number} => { + Unknown { + recipe, + line_number, + } => { if let Some(n) = line_number { - write!(f, "Recipe `{}` failed on line {} for an unknown reason", recipe, n)?; + write!( + f, + "Recipe `{}` failed on line {} for an unknown reason", + recipe, n + )?; } else { } - }, - IoError{recipe, ref io_error} => { + } + IoError { + recipe, + ref io_error, + } => { match io_error.kind() { - io::ErrorKind::NotFound => writeln!(f, + io::ErrorKind::NotFound => writeln!( + f, "Recipe `{}` could not be run because just could not find `sh`:{}", - recipe, io_error), + recipe, io_error + ), io::ErrorKind::PermissionDenied => writeln!( - f, "Recipe `{}` could not be run because just could not run `sh`:{}", - recipe, io_error), - _ => writeln!(f, "Recipe `{}` could not be run because of an IO error while \ - launching `sh`:{}", recipe, io_error), + f, + "Recipe `{}` could not be run because just could not run `sh`:{}", + recipe, io_error + ), + _ => writeln!( + f, + "Recipe `{}` could not be run because of an IO error while \ + launching `sh`:{}", + recipe, io_error + ), }?; - }, - TmpdirIoError{recipe, ref io_error} => - writeln!(f, "Recipe `{}` could not be run because of an IO error while trying \ - to create a temporary directory or write a file to that directory`:{}", - recipe, io_error)?, - Backtick{ref token, ref output_error} => match *output_error { + } + TmpdirIoError { + recipe, + ref io_error, + } => writeln!( + f, + "Recipe `{}` could not be run because of an IO error while trying \ + to create a temporary directory or write a file to that directory`:{}", + recipe, io_error + )?, + Backtick { + ref token, + ref output_error, + } => match *output_error { OutputError::Code(code) => { writeln!(f, "Backtick failed with exit code {}", code)?; error_token = Some(token); @@ -196,24 +359,40 @@ impl<'a> Display for RuntimeError<'a> { OutputError::Io(ref io_error) => { match io_error.kind() { io::ErrorKind::NotFound => write!( - f, "Backtick could not be run because just could not find `sh`:\n{}", - io_error), + f, + "Backtick could not be run because just could not find `sh`:\n{}", + io_error + ), io::ErrorKind::PermissionDenied => write!( - f, "Backtick could not be run because just could not run `sh`:\n{}", io_error), - _ => write!(f, "Backtick could not be run because of an IO \ - error while launching `sh`:\n{}", io_error), + f, + "Backtick could not be run because just could not run `sh`:\n{}", + io_error + ), + _ => write!( + f, + "Backtick could not be run because of an IO \ + error while launching `sh`:\n{}", + io_error + ), }?; error_token = Some(token); } OutputError::Utf8(ref utf8_error) => { - writeln!(f, "Backtick succeeded but stdout was not utf8: {}", utf8_error)?; + writeln!( + f, + "Backtick succeeded but stdout was not utf8: {}", + utf8_error + )?; error_token = Some(token); } }, - Internal{ref message} => { - write!(f, "Internal error, this may indicate a bug in just: {} \ - consider filing an issue: https://github.com/casey/just/issues/new", - message)?; + Internal { ref message } => { + write!( + f, + "Internal error, this may indicate a bug in just: {} \ + consider filing an issue: https://github.com/casey/just/issues/new", + message + )?; } } diff --git a/src/shebang.rs b/src/shebang.rs index ae58b2c..28a85d6 100644 --- a/src/shebang.rs +++ b/src/shebang.rs @@ -1,6 +1,6 @@ pub struct Shebang<'a> { pub interpreter: &'a str, - pub argument: Option<&'a str>, + pub argument: Option<&'a str>, } impl<'a> Shebang<'a> { @@ -17,13 +17,16 @@ impl<'a> Shebang<'a> { .splitn(2, |c| c == ' ' || c == '\t'); let interpreter = pieces.next().unwrap_or(""); - let argument = pieces.next(); + let argument = pieces.next(); if interpreter == "" { return None; } - Some(Shebang{interpreter, argument}) + Some(Shebang { + interpreter, + argument, + }) } } @@ -35,26 +38,59 @@ mod test { fn split_shebang() { fn check(text: &str, expected_split: Option<(&str, Option<&str>)>) { let shebang = Shebang::new(text); - assert_eq!(shebang.map(|shebang| (shebang.interpreter, shebang.argument)), expected_split); + assert_eq!( + shebang.map(|shebang| (shebang.interpreter, shebang.argument)), + expected_split + ); } - check("#! ", None ); - check("#!", None ); - check("#!/bin/bash", Some(("/bin/bash", None ))); - check("#!/bin/bash ", Some(("/bin/bash", None ))); - check("#!/usr/bin/env python", Some(("/usr/bin/env", Some("python" )))); - check("#!/usr/bin/env python ", Some(("/usr/bin/env", Some("python" )))); - check("#!/usr/bin/env python -x", Some(("/usr/bin/env", Some("python -x" )))); - check("#!/usr/bin/env python -x", Some(("/usr/bin/env", Some("python -x")))); - check("#!/usr/bin/env python \t-x\t", Some(("/usr/bin/env", Some("python \t-x")))); - check("#/usr/bin/env python \t-x\t", None ); - check("#! /bin/bash", Some(("/bin/bash", None ))); - check("#!\t\t/bin/bash ", Some(("/bin/bash", None ))); - check("#! \t\t/usr/bin/env python", Some(("/usr/bin/env", Some("python" )))); - check("#! /usr/bin/env python ", Some(("/usr/bin/env", Some("python" )))); - check("#! /usr/bin/env python -x", Some(("/usr/bin/env", Some("python -x" )))); - check("#! /usr/bin/env python -x", Some(("/usr/bin/env", Some("python -x")))); - check("#! /usr/bin/env python \t-x\t", Some(("/usr/bin/env", Some("python \t-x")))); - check("# /usr/bin/env python \t-x\t", None ); + check("#! ", None); + check("#!", None); + check("#!/bin/bash", Some(("/bin/bash", None))); + check("#!/bin/bash ", Some(("/bin/bash", None))); + check( + "#!/usr/bin/env python", + Some(("/usr/bin/env", Some("python"))), + ); + check( + "#!/usr/bin/env python ", + Some(("/usr/bin/env", Some("python"))), + ); + check( + "#!/usr/bin/env python -x", + Some(("/usr/bin/env", Some("python -x"))), + ); + check( + "#!/usr/bin/env python -x", + Some(("/usr/bin/env", Some("python -x"))), + ); + check( + "#!/usr/bin/env python \t-x\t", + Some(("/usr/bin/env", Some("python \t-x"))), + ); + check("#/usr/bin/env python \t-x\t", None); + check("#! /bin/bash", Some(("/bin/bash", None))); + check("#!\t\t/bin/bash ", Some(("/bin/bash", None))); + check( + "#! \t\t/usr/bin/env python", + Some(("/usr/bin/env", Some("python"))), + ); + check( + "#! /usr/bin/env python ", + Some(("/usr/bin/env", Some("python"))), + ); + check( + "#! /usr/bin/env python -x", + Some(("/usr/bin/env", Some("python -x"))), + ); + check( + "#! /usr/bin/env python -x", + Some(("/usr/bin/env", Some("python -x"))), + ); + check( + "#! /usr/bin/env python \t-x\t", + Some(("/usr/bin/env", Some("python \t-x"))), + ); + check("# /usr/bin/env python \t-x\t", None); } } diff --git a/src/testing.rs b/src/testing.rs index 2fcd9b3..6c84497 100644 --- a/src/testing.rs +++ b/src/testing.rs @@ -22,28 +22,28 @@ macro_rules! compilation_error_test { let input = $input; let expected = ::CompilationError { - text: input, - index: $index, - line: $line, + text: input, + index: $index, + line: $line, column: $column, - width: $width, - kind: $kind, + width: $width, + kind: $kind, }; let tokens = ::Lexer::lex(input).unwrap(); let parser = ::Parser::new(input, tokens); if let Err(error) = parser.justfile() { - assert_eq!(error.text, expected.text); - assert_eq!(error.index, expected.index); - assert_eq!(error.line, expected.line); + assert_eq!(error.text, expected.text); + assert_eq!(error.index, expected.index); + assert_eq!(error.line, expected.line); assert_eq!(error.column, expected.column); - assert_eq!(error.width, expected.width); - assert_eq!(error.kind, expected.kind); - assert_eq!(error, expected); + assert_eq!(error.width, expected.width); + assert_eq!(error.kind, expected.kind); + assert_eq!(error, expected); } else { panic!("parse succeeded but expected: {}\n{}", expected, input); } } - } + }; } diff --git a/src/token.rs b/src/token.rs index 20335f3..6321ec9 100644 --- a/src/token.rs +++ b/src/token.rs @@ -2,23 +2,23 @@ use common::*; #[derive(Debug, PartialEq, Clone)] pub struct Token<'a> { - pub index: usize, - pub line: usize, + pub index: usize, + pub line: usize, pub column: usize, - pub text: &'a str, + pub text: &'a str, pub prefix: &'a str, pub lexeme: &'a str, - pub kind: TokenKind, + pub kind: TokenKind, } impl<'a> Token<'a> { pub fn error(&self, kind: CompilationErrorKind<'a>) -> CompilationError<'a> { CompilationError { column: self.column + self.prefix.len(), - index: self.index + self.prefix.len(), - line: self.line, - text: self.text, - width: Some(self.lexeme.len()), + index: self.index + self.prefix.len(), + line: self.line, + text: self.text, + width: Some(self.lexeme.len()), kind, } } @@ -51,27 +51,31 @@ pub enum TokenKind { impl Display for TokenKind { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { use TokenKind::*; - write!(f, "{}", match *self { - Backtick => "backtick", - Colon => "':'", - Comma => "','", - Comment => "comment", - Dedent => "dedent", - Eof => "end of file", - Eol => "end of line", - Equals => "'='", - Indent => "indent", - InterpolationEnd => "'}}'", - InterpolationStart => "'{{'", - Line => "command", - Name => "name", - Plus => "'+'", - At => "'@'", - ParenL => "'('", - ParenR => "')'", - StringToken => "string", - RawString => "raw string", - Text => "command text", - }) + write!( + f, + "{}", + match *self { + Backtick => "backtick", + Colon => "':'", + Comma => "','", + Comment => "comment", + Dedent => "dedent", + Eof => "end of file", + Eol => "end of line", + Equals => "'='", + Indent => "indent", + InterpolationEnd => "'}}'", + InterpolationStart => "'{{'", + Line => "command", + Name => "name", + Plus => "'+'", + At => "'@'", + ParenL => "'('", + ParenR => "')'", + StringToken => "string", + RawString => "raw string", + Text => "command text", + } + ) } } diff --git a/tests/integration.rs b/tests/integration.rs index a8d85b3..400b40e 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -41,16 +41,19 @@ macro_rules! integration_test { } fn integration_test( - shell: &str, - justfile: &str, - args: &[&str], + shell: &str, + justfile: &str, + args: &[&str], expected_stdout: &str, expected_stderr: &str, expected_status: i32, ) { - let tmp = TempDir::new("just-integration") - .unwrap_or_else( - |err| panic!("integration test: failed to create temporary directory: {}", err)); + let tmp = TempDir::new("just-integration").unwrap_or_else(|err| { + panic!( + "integration test: failed to create temporary directory: {}", + err + ) + }); let mut justfile_path = tmp.path().to_path_buf(); justfile_path.push("justfile"); @@ -77,13 +80,19 @@ fn integration_test( let stdout = str::from_utf8(&output.stdout).unwrap(); if stdout != expected_stdout { - println!("bad stdout:\ngot:\n{}\n\nexpected:\n{}", stdout, expected_stdout); + println!( + "bad stdout:\ngot:\n{}\n\nexpected:\n{}", + stdout, expected_stdout + ); failure = true; } let stderr = str::from_utf8(&output.stderr).unwrap(); if stderr != expected_stderr { - println!("bad stderr:\ngot:\n{}\n\nexpected:\n{}", stderr, expected_stderr); + println!( + "bad stderr:\ngot:\n{}\n\nexpected:\n{}", + stderr, expected_stderr + ); failure = true; } @@ -591,7 +600,6 @@ wut: status: EXIT_FAILURE, } - integration_test! { name: export_shebang, justfile: r#" @@ -929,7 +937,6 @@ integration_test! { status: EXIT_FAILURE, } - integration_test! { name: required_after_default, justfile: "bar:\nhello baz arg='foo' bar:", @@ -969,7 +976,6 @@ hello baz arg="XYZ\t\" ": status: EXIT_SUCCESS, } - integration_test! { name: use_raw_string_default, justfile: r#" @@ -1228,7 +1234,6 @@ foo: status: EXIT_SUCCESS, } - integration_test! { name: env_var_failure, justfile: "a:\n echo {{env_var('ZADDY')}}", @@ -1500,7 +1505,6 @@ a:"#, status: EXIT_FAILURE, } - integration_test! { name: multiline_raw_string, justfile: " diff --git a/tests/interrupts.rs b/tests/interrupts.rs index e3ce15a..c94f23e 100644 --- a/tests/interrupts.rs +++ b/tests/interrupts.rs @@ -4,8 +4,12 @@ extern crate libc; extern crate tempdir; use executable_path::executable_path; +use std::{ + process::Command, + thread, + time::{Duration, Instant}, +}; use tempdir::TempDir; -use std::{process::Command, time::{Duration, Instant}, thread}; #[cfg(unix)] fn kill(process_id: u32) { @@ -16,9 +20,12 @@ fn kill(process_id: u32) { #[cfg(unix)] fn interrupt_test(justfile: &str) { - let tmp = TempDir::new("just-interrupts") - .unwrap_or_else( - |err| panic!("integration test: failed to create temporary directory: {}", err)); + let tmp = TempDir::new("just-interrupts").unwrap_or_else(|err| { + panic!( + "integration test: failed to create temporary directory: {}", + err + ) + }); let mut justfile_path = tmp.path().to_path_buf(); justfile_path.push("justfile"); @@ -53,29 +60,35 @@ fn interrupt_test(justfile: &str) { #[cfg(unix)] #[test] fn interrupt_shebang() { - interrupt_test(" + interrupt_test( + " default: #!/usr/bin/env sh sleep 2 -"); +", + ); } #[cfg(unix)] #[test] fn interrupt_line() { - interrupt_test(" + interrupt_test( + " default: @sleep 2 -"); +", + ); } #[cfg(unix)] #[test] fn interrupt_backtick() { - interrupt_test(" + interrupt_test( + " foo = `sleep 2` default: @echo hello -"); +", + ); } diff --git a/tests/invocation_directory.rs b/tests/invocation_directory.rs index 7a968c6..ad00dbb 100644 --- a/tests/invocation_directory.rs +++ b/tests/invocation_directory.rs @@ -4,16 +4,19 @@ extern crate target; extern crate tempdir; use executable_path::executable_path; +use std::path::Path; use std::process; use std::str; -use std::path::Path; use tempdir::TempDir; #[cfg(unix)] fn to_shell_path(path: &Path) -> String { use std::fs; - fs::canonicalize(path).expect("canonicalize failed") - .to_str().map(str::to_string).expect("unicode decode failed") + fs::canonicalize(path) + .expect("canonicalize failed") + .to_str() + .map(str::to_string) + .expect("unicode decode failed") } #[cfg(windows)] @@ -27,13 +30,19 @@ fn to_shell_path(path: &Path) -> String { #[test] fn test_invocation_directory() { - let tmp = TempDir::new("just-integration") - .unwrap_or_else( - |err| panic!("integration test: failed to create temporary directory: {}", err)); + let tmp = TempDir::new("just-integration").unwrap_or_else(|err| { + panic!( + "integration test: failed to create temporary directory: {}", + err + ) + }); let mut justfile_path = tmp.path().to_path_buf(); justfile_path.push("justfile"); - brev::dump(justfile_path, "default:\n @cd {{invocation_directory()}}\n @echo {{invocation_directory()}}"); + brev::dump( + justfile_path, + "default:\n @cd {{invocation_directory()}}\n @echo {{invocation_directory()}}", + ); let mut subdir = tmp.path().to_path_buf(); subdir.push("subdir"); @@ -48,8 +57,7 @@ fn test_invocation_directory() { let mut failure = false; let expected_status = 0; - let expected_stdout = - to_shell_path(&subdir) + "\n"; + let expected_stdout = to_shell_path(&subdir) + "\n"; let expected_stderr = ""; let status = output.status.code().unwrap(); @@ -60,13 +68,19 @@ fn test_invocation_directory() { let stdout = str::from_utf8(&output.stdout).unwrap(); if stdout != expected_stdout { - println!("bad stdout:\ngot:\n{:?}\n\nexpected:\n{:?}", stdout, expected_stdout); + println!( + "bad stdout:\ngot:\n{:?}\n\nexpected:\n{:?}", + stdout, expected_stdout + ); failure = true; } let stderr = str::from_utf8(&output.stderr).unwrap(); if stderr != expected_stderr { - println!("bad stderr:\ngot:\n{:?}\n\nexpected:\n{:?}", stderr, expected_stderr); + println!( + "bad stderr:\ngot:\n{:?}\n\nexpected:\n{:?}", + stderr, expected_stderr + ); failure = true; } diff --git a/tests/search.rs b/tests/search.rs index 0702132..caf7e30 100644 --- a/tests/search.rs +++ b/tests/search.rs @@ -2,9 +2,9 @@ extern crate brev; extern crate executable_path; extern crate tempdir; -use tempdir::TempDir; -use std::{path, str, fs, process}; use executable_path::executable_path; +use std::{fs, path, process, str}; +use tempdir::TempDir; fn search_test>(path: P, args: &[&str]) { let binary = executable_path("just");