diff --git a/src/main.rs b/src/main.rs index 49e60c1..8e39f8b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ pub mod profile; +pub mod runner; fn main() { println!("Hello, world!"); diff --git a/src/runner.rs b/src/runner.rs new file mode 100644 index 0000000..5894ebe --- /dev/null +++ b/src/runner.rs @@ -0,0 +1,122 @@ +use std::{ + collections::HashMap, + io::{BufRead, BufReader}, + process::{Child, Command, Stdio}, +}; + +pub struct Runner { + environment: HashMap, + command: String, + args: Vec, + stdout: Vec, + stderr: Vec, + process: Option, +} + +#[derive(PartialEq, Eq, Debug)] +pub enum RunnerStatus { + Running, + Stopped, +} + +impl Runner { + pub fn new(environment: HashMap, command: String, args: Vec) -> Runner { + Runner { + environment, + command, + args, + stdout: Vec::new(), + stderr: Vec::new(), + process: None, + } + } + + pub fn start(&mut self) { + self.process = Some( + Command::new(&self.command) + .args(&self.args) + .envs(self.environment.clone()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .expect("Failed to execute runner"), + ); + } + + pub fn terminate(&mut self) { + match self.process.as_mut() { + None => { + println!("Runner already stopped") + } + Some(proc) => { + proc.kill().expect("Failed to kill process"); + proc.wait().expect("Failed to wait for process"); + self.process = None + } + } + } + + pub fn status(&mut self) -> RunnerStatus { + match self.process.as_mut() { + None => RunnerStatus::Stopped, + Some(proc) => match proc.try_wait() { + Ok(_) => RunnerStatus::Stopped, + Err(_) => RunnerStatus::Running, + }, + } + } + + pub fn flush_out(&mut self) { + match self.process.as_mut() { + None => (), + Some(proc) => { + let out = proc.stdout.take().expect("Unable to read stout"); + let mut reader = BufReader::new(out); + let mut bytes_read = 1; + let mut buf = "".to_string(); + while bytes_read != 0 { + bytes_read = match reader.read_line(&mut buf) { + Ok(bytes) => bytes, + Err(_) => 0, + }; + self.stdout.push(buf.clone()); + buf.clear(); + } + + let err = proc.stderr.take().expect("Unable to read stderr"); + let mut err_reader = BufReader::new(err); + bytes_read = 1; + while bytes_read != 0 { + bytes_read = match err_reader.read_line(&mut buf) { + Ok(bytes) => bytes, + Err(_) => 0, + }; + self.stderr.push(buf.clone()); + buf.clear(); + } + } + } + } +} + +#[cfg(test)] +mod tests { + use super::{Runner, RunnerStatus}; + use core::time; + use std::{collections::HashMap, thread::sleep}; + + #[test] + fn can_run_command_and_read_env() { + let mut env = HashMap::new(); + env.insert("REX2TEST".to_string(), "Lorem ipsum dolor".to_string()); + let mut runner = Runner::new(env, "bash".into(), vec!["-c".into(), "echo \"REX2TEST: $REX2TEST\"".into()]); + runner.start(); + while runner.status() == RunnerStatus::Running { + sleep(time::Duration::from_millis(10)); + } + runner.flush_out(); + assert_eq!(runner.status(), RunnerStatus::Stopped); + assert_eq!(runner.stdout.len(), 2); + assert_eq!(runner.stdout.get(0).unwrap(), "REX2TEST: Lorem ipsum dolor\n"); + } +}