just/src/assignment_evaluator.rs

218 lines
5.9 KiB
Rust
Raw Normal View History

use crate::common::*;
2017-11-16 23:30:08 -08:00
pub struct AssignmentEvaluator<'a: 'b, 'b> {
pub assignments: &'b BTreeMap<&'a str, Expression<'a>>,
pub invocation_directory: &'b Result<PathBuf, String>,
pub dotenv: &'b BTreeMap<String, String>,
pub dry_run: bool,
pub evaluated: BTreeMap<&'a str, String>,
pub exports: &'b BTreeSet<&'a str>,
pub overrides: &'b BTreeMap<&'b str, &'b str>,
pub quiet: bool,
pub scope: &'b BTreeMap<&'a str, String>,
pub shell: &'b str,
2017-11-16 23:30:08 -08:00
}
impl<'a, 'b> AssignmentEvaluator<'a, 'b> {
pub fn evaluate_assignments(
assignments: &BTreeMap<&'a str, Expression<'a>>,
invocation_directory: &Result<PathBuf, String>,
dotenv: &'b BTreeMap<String, String>,
overrides: &BTreeMap<&str, &str>,
quiet: bool,
shell: &'a str,
dry_run: bool,
) -> RunResult<'a, BTreeMap<&'a str, String>> {
let mut evaluator = AssignmentEvaluator {
2018-03-05 13:21:35 -08:00
evaluated: empty(),
exports: &empty(),
scope: &empty(),
2018-03-05 13:21:35 -08:00
assignments,
invocation_directory,
2018-03-05 13:21:35 -08:00
dotenv,
dry_run,
overrides,
quiet,
shell,
};
for name in assignments.keys() {
evaluator.evaluate_assignment(name)?;
}
Ok(evaluator.evaluated)
}
2017-11-16 23:30:08 -08:00
pub fn evaluate_line(
&mut self,
line: &[Fragment<'a>],
arguments: &BTreeMap<&str, Cow<str>>,
2017-11-17 17:28:06 -08:00
) -> RunResult<'a, String> {
2017-11-16 23:30:08 -08:00
let mut evaluated = String::new();
for fragment in line {
match *fragment {
Fragment::Text { ref text } => evaluated += text.lexeme(),
Fragment::Expression { ref expression } => {
2017-11-16 23:30:08 -08:00
evaluated += &self.evaluate_expression(expression, arguments)?;
}
}
}
Ok(evaluated)
}
2017-11-17 17:28:06 -08:00
fn evaluate_assignment(&mut self, name: &'a str) -> RunResult<'a, ()> {
2017-11-16 23:30:08 -08:00
if self.evaluated.contains_key(name) {
return Ok(());
}
if let Some(expression) = self.assignments.get(name) {
if let Some(value) = self.overrides.get(name) {
self.evaluated.insert(name, value.to_string());
} else {
let value = self.evaluate_expression(expression, &empty())?;
self.evaluated.insert(name, value);
}
} else {
return Err(RuntimeError::Internal {
message: format!("attempted to evaluated unknown assignment {}", name),
2017-11-16 23:30:08 -08:00
});
}
Ok(())
}
pub fn evaluate_expression(
2017-11-16 23:30:08 -08:00
&mut self,
expression: &Expression<'a>,
arguments: &BTreeMap<&str, Cow<str>>,
2017-11-17 17:28:06 -08:00
) -> RunResult<'a, String> {
match *expression {
Expression::Variable { name, .. } => {
2017-11-16 23:30:08 -08:00
if self.evaluated.contains_key(name) {
Ok(self.evaluated[name].clone())
2017-11-16 23:30:08 -08:00
} else if self.scope.contains_key(name) {
Ok(self.scope[name].clone())
2017-11-16 23:30:08 -08:00
} else if self.assignments.contains_key(name) {
self.evaluate_assignment(name)?;
Ok(self.evaluated[name].clone())
2017-11-16 23:30:08 -08:00
} else if arguments.contains_key(name) {
Ok(arguments[name].to_string())
2017-11-16 23:30:08 -08:00
} else {
Err(RuntimeError::Internal {
message: format!("attempted to evaluate undefined variable `{}`", name),
})
2017-11-16 23:30:08 -08:00
}
}
Expression::Call {
name,
arguments: ref call_arguments,
ref token,
} => {
let call_arguments = call_arguments
.iter()
.map(|argument| self.evaluate_expression(argument, arguments))
.collect::<Result<Vec<String>, 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.to_string()),
Expression::Backtick { raw, ref token } => {
if self.dry_run {
Ok(format!("`{}`", raw))
} else {
2018-03-05 13:21:35 -08:00
Ok(self.run_backtick(self.dotenv, raw, token)?)
}
2017-11-16 23:30:08 -08:00
}
Expression::Concatination { ref lhs, ref rhs } => {
Ok(self.evaluate_expression(lhs, arguments)? + &self.evaluate_expression(rhs, arguments)?)
2017-11-16 23:30:08 -08:00
}
Expression::Group { ref expression } => self.evaluate_expression(&expression, arguments),
}
2017-11-16 23:30:08 -08:00
}
fn run_backtick(
&self,
dotenv: &BTreeMap<String, String>,
raw: &str,
token: &Token<'a>,
) -> RunResult<'a, String> {
let mut cmd = Command::new(self.shell);
2018-03-05 13:21:35 -08:00
cmd.export_environment_variables(self.scope, dotenv, self.exports)?;
cmd.arg("-cu").arg(raw);
cmd.stderr(if self.quiet {
process::Stdio::null()
} else {
process::Stdio::inherit()
});
InterruptHandler::guard(|| {
brev::output(cmd).map_err(|output_error| RuntimeError::Backtick {
token: token.clone(),
output_error,
})
})
}
2017-11-16 23:30:08 -08:00
}
#[cfg(test)]
mod test {
use super::*;
use crate::testing::parse_success;
2017-11-16 23:30:08 -08:00
use brev::OutputError;
fn no_cwd_err() -> Result<PathBuf, String> {
Err(String::from("no cwd in tests"))
}
2017-11-17 17:28:06 -08:00
#[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),
} => {
2017-11-17 17:28:06 -08:00
assert_eq!(code, 100);
assert_eq!(token.lexeme(), "`f() { return 100; }; f`");
}
2017-11-17 17:28:06 -08:00
other => panic!("expected a code run error, but got: {}", other),
}
2017-11-16 23:30:08 -08:00
}
2017-11-17 17:28:06 -08:00
#[test]
fn export_assignment_backtick() {
let text = r#"
2017-11-16 23:30:08 -08:00
export exported_variable = "A"
b = `echo $exported_variable`
recipe:
echo {{b}}
"#;
let configuration = Configuration {
2017-11-17 17:28:06 -08:00
quiet: true,
..Default::default()
};
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`");
}
2017-11-17 17:28:06 -08:00
other => panic!("expected a backtick code errror, but got: {}", other),
}
2017-11-16 23:30:08 -08:00
}
}