use std::io::Write;
use std::process::{Command, Stdio};
use std::str;
use anyhow::Context;
use log::{debug, error, info, warn};
use crate::interpreter::context::ContextRef;
use crate::interpreter::interpolateable::Interpolateable;
use crate::interpreter::stack::StackRef;
use crate::interpreter::variables::Variables;
use crate::parse::ast::{Executeable, ExecuteableType};
use super::{Executor, ExecutorError, Stack};
pub struct CommandExecutor {
variables: Variables,
cmd: String,
interpolateable_cmd: Option<Interpolateable>,
stdin_variable: Option<String>,
trim_stdout: bool,
trim_stderr: bool,
stack: Option<StackRef>,
}
impl CommandExecutor {
pub fn new(input: Executeable) -> anyhow::Result<Self> {
if let ExecuteableType::Command { cmd } = input.executeable_type {
let (stdin_variable, trim_stdout, trim_stderr) = match input.options {
Some(bindings) => (
bindings.find("stdin").map(|val| val.into()),
bindings.find("trim_stdout").is_some(),
bindings.find("trim_stderr").is_some(),
),
None => (None, false, false),
};
let mut exe = CommandExecutor {
variables: Variables::new(input.output_variables),
cmd,
interpolateable_cmd: None,
stdin_variable,
trim_stdout,
trim_stderr,
stack: None,
};
exe.interpolateable_cmd = Interpolateable::new(&exe.cmd);
Ok(exe)
} else {
Err(ExecutorError::WrongExecutorType(input.executeable_type).into())
}
}
pub fn interpolate(&self, stack: &StackRef) -> anyhow::Result<String> {
match &self.interpolateable_cmd {
None => Ok(self.cmd.clone()),
Some(inter) => {
let mut target = String::new();
inter
.interpolate(stack, &mut target)
.with_context(|| self.error_context())?;
Ok(target)
}
}
}
pub fn error_context(&self) -> String {
format!("executing command: '{}'", self.cmd)
}
}
impl Executor for CommandExecutor {
fn init(&mut self, mut stack: StackRef, _ctx: ContextRef) -> anyhow::Result<()> {
if let Some(interpolateable) = &self.interpolateable_cmd {
interpolateable
.assert_variables_allocated(&stack)
.with_context(|| self.error_context())?;
}
if let Some(stdin_variable) = &self.stdin_variable {
stack.borrow().assert_allocated(stdin_variable)?;
}
let mut child_stack: StackRef = Stack::inherit_new(&stack).into();
{
let mut child_stack_ref = child_stack.borrow_mut();
child_stack_ref.allocate("stdout".into());
child_stack_ref.allocate("stderr".into());
child_stack_ref.allocate("status".into());
}
self.variables
.allocate_and_check_all(&mut stack, &mut child_stack)?;
self.stack = Some(child_stack);
Ok(())
}
fn execute(&mut self, mut parent_stack: StackRef, _ctx: ContextRef) -> anyhow::Result<()> {
if let Some(mut child_stack) = self.stack.clone() {
let interpolated = self.interpolate(&parent_stack)?;
let mut cmd_iter = interpolated.split(' ');
let program = cmd_iter.next().unwrap();
let args: Vec<&str> = cmd_iter.collect();
let mut cmd = Command::new(program);
cmd.args(args);
cmd.stdin(Stdio::piped());
cmd.stdout(Stdio::piped());
cmd.stderr(Stdio::piped());
debug!("$ {}", &interpolated);
let mut process = cmd.spawn().with_context(|| self.error_context())?;
if let Some(stdin_variable) = &self.stdin_variable {
let stdin = parent_stack.borrow().get(stdin_variable)?;
debug!("< {}", &stdin);
write!(process.stdin.as_mut().unwrap(), "{}", &stdin)?;
}
let output = process
.wait_with_output()
.with_context(|| self.error_context())?;
let mut stdout: String = str::from_utf8(&output.stdout).unwrap().into();
if self.trim_stdout {
stdout = stdout.trim().into();
}
let mut stderr: String = str::from_utf8(&output.stderr).unwrap().into();
if self.trim_stderr {
stderr = stderr.trim().into();
}
let status: String = output.status.code().unwrap().to_string();
if !output.status.success() {
error!("$? {}", status);
}
if !stdout.is_empty() {
info!("1> {}", &stdout);
}
if !stderr.is_empty() {
warn!("2> {}", &stderr);
}
{
let mut child_stack_ref = child_stack.borrow_mut();
child_stack_ref
.set("stdout".into(), stdout)
.with_context(|| self.error_context())?;
child_stack_ref
.set("stderr".into(), stderr)
.with_context(|| self.error_context())?;
child_stack_ref
.set("status".into(), status)
.with_context(|| self.error_context())?;
}
self.variables
.carry_over(&mut parent_stack, &mut child_stack)?;
Ok(())
} else {
Err(ExecutorError::NotInitialized.into())
}
}
}