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}