1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
use crate::error;
use crate::io::Response;
use crate::io::TaskRunner;
use crate::Result;
use std::ffi::OsStr;
use std::io::BufRead;
use std::io::BufReader;
use std::process;
use std::process::Command;
use std::str;
use std::thread;

pub struct BlockingCommand;

impl TaskRunner for BlockingCommand {
    type Response = Response;

    fn run<T>(&self, cmd: T) -> Result<Self::Response>
    where
        T: IntoIterator,
        T::Item: AsRef<OsStr>,
    {
        run_args(cmd)
    }
}

fn run_args<T>(args: T) -> Result<Response>
where
    T: IntoIterator,
    T::Item: AsRef<OsStr>,
{
    let args: Vec<_> = args.into_iter().collect();
    let mut process = process::Command::new(&args[0]);
    process.args(&args[1..]);
    let mut response_builder = Response::builder();
    match process.output() {
        Ok(output) => {
            let status_code = output.status.code().unwrap_or(0);
            if output.status.success() {
                let output_str = str::from_utf8(&output.stdout)?;
                if let Some(output_stripped) = output_str.strip_suffix('\n') {
                    return Ok(response_builder
                        .status(status_code)
                        .body(output_stripped.to_string())
                        .build()?);
                };
                return Ok(response_builder
                    .status(status_code)
                    .body(output_str.to_string())
                    .build()?);
            }
            let err_msg = str::from_utf8(&output.stderr)?;
            Err(error::gen(err_msg))
        }
        Err(val) => Err(error::gen(val.to_string())),
    }
}

pub struct StreamingCommand;

impl TaskRunner for StreamingCommand {
    type Response = Response;

    fn run<T>(&self, cmd: T) -> Result<Self::Response>
    where
        T: IntoIterator,
        T::Item: AsRef<std::ffi::OsStr>,
    {
        let args: Vec<_> = cmd.into_iter().collect();
        let cmd_path = &args[0];
        let args = args[1..]
            .iter()
            .map(|s| s.as_ref().to_str().unwrap())
            .collect::<Vec<&str>>()
            .join(" ");
        let mut child = Command::new(cmd_path)
            .arg(&args)
            .stdout(std::process::Stdio::piped())
            .stderr(std::process::Stdio::piped())
            .spawn()?;

        let stdout = BufReader::new(child.stdout.take().unwrap());
        let stderr = BufReader::new(child.stderr.take().unwrap());

        let stdout_handle = thread::spawn(move || {
            for line in stdout.lines() {
                println!("{}", line.unwrap());
            }
        });
        let stderr_handle = thread::spawn(move || {
            for line in stderr.lines() {
                eprintln!("{}", line.unwrap());
            }
        });
        stdout_handle.join().unwrap();
        stderr_handle.join().unwrap();
        let _ = child.wait()?;
        Ok(Response::builder().status(0).body("".to_string()).build()?)
    }
}

#[cfg(test)]
mod tests {

    use super::*;

    #[test]
    fn test_run() {
        let runner = StreamingCommand;
        let cmd = vec!["echo", "Hello, world!"];
        let response = runner.run(cmd).unwrap();
        assert_eq!(response.body, "");
    }

    #[test]
    #[should_panic(expected = "No such file or directory (os error 2)")]
    fn test_run_invalid_command() {
        let runner = StreamingCommand;
        let cmd = vec!["invalid_command"];
        let _ = runner.run(cmd).unwrap();
    }
}