gr/
shell.rs

1use crate::error;
2use crate::io::ShellResponse;
3use crate::io::TaskRunner;
4use crate::Result;
5use std::ffi::OsStr;
6use std::io::BufRead;
7use std::io::BufReader;
8use std::process;
9use std::process::Command;
10use std::str;
11use std::thread;
12
13pub struct BlockingCommand;
14
15impl TaskRunner for BlockingCommand {
16    type Response = ShellResponse;
17
18    fn run<T>(&self, cmd: T) -> Result<Self::Response>
19    where
20        T: IntoIterator,
21        T::Item: AsRef<OsStr>,
22    {
23        run_args(cmd)
24    }
25}
26
27fn run_args<T>(args: T) -> Result<ShellResponse>
28where
29    T: IntoIterator,
30    T::Item: AsRef<OsStr>,
31{
32    let args: Vec<_> = args.into_iter().collect();
33    let mut process = process::Command::new(&args[0]);
34    process.args(&args[1..]);
35    let mut response_builder = ShellResponse::builder();
36    match process.output() {
37        Ok(output) => {
38            let status_code = output.status.code().unwrap_or(0);
39            if output.status.success() {
40                let output_str = str::from_utf8(&output.stdout)?;
41                if let Some(output_stripped) = output_str.strip_suffix('\n') {
42                    return Ok(response_builder
43                        .status(status_code)
44                        .body(output_stripped.to_string())
45                        .build()?);
46                };
47                return Ok(response_builder
48                    .status(status_code)
49                    .body(output_str.to_string())
50                    .build()?);
51            }
52            let err_msg = str::from_utf8(&output.stderr)?;
53            Err(error::gen(err_msg))
54        }
55        Err(val) => Err(error::gen(val.to_string())),
56    }
57}
58
59pub struct StreamingCommand;
60
61impl TaskRunner for StreamingCommand {
62    type Response = ShellResponse;
63
64    fn run<T>(&self, cmd: T) -> Result<Self::Response>
65    where
66        T: IntoIterator,
67        T::Item: AsRef<std::ffi::OsStr>,
68    {
69        let args: Vec<_> = cmd.into_iter().collect();
70        let cmd_path = &args[0];
71        let args = args[1..]
72            .iter()
73            .map(|s| s.as_ref().to_str().unwrap())
74            .collect::<Vec<&str>>();
75        let mut child = Command::new(cmd_path)
76            .args(&args)
77            .stdout(std::process::Stdio::piped())
78            .stderr(std::process::Stdio::piped())
79            .spawn()?;
80
81        let stdout = BufReader::new(child.stdout.take().unwrap());
82        let stderr = BufReader::new(child.stderr.take().unwrap());
83
84        let stdout_handle = thread::spawn(move || {
85            for line in stdout.lines() {
86                println!("{}", line.unwrap());
87            }
88        });
89        let stderr_handle = thread::spawn(move || {
90            for line in stderr.lines() {
91                eprintln!("{}", line.unwrap());
92            }
93        });
94        stdout_handle.join().unwrap();
95        stderr_handle.join().unwrap();
96        let _ = child.wait()?;
97        Ok(ShellResponse::builder()
98            .status(0)
99            .body("".to_string())
100            .build()?)
101    }
102}
103
104#[cfg(test)]
105mod tests {
106
107    use super::*;
108
109    #[test]
110    fn test_run() {
111        let runner = StreamingCommand;
112        let cmd = vec!["echo", "Hello, world!"];
113        let response = runner.run(cmd).unwrap();
114        assert_eq!(response.body, "");
115    }
116
117    #[test]
118    #[should_panic(expected = "No such file or directory (os error 2)")]
119    fn test_run_invalid_command() {
120        let runner = StreamingCommand;
121        let cmd = vec!["invalid_command"];
122        let _ = runner.run(cmd).unwrap();
123    }
124}