1use std::{io, fs::File};
2use io::{BufWriter, Write, BufReader, BufRead};
3use std::process::{Command, Stdio, Output};
4
5#[allow(clippy::manual_flatten)]
14pub fn command_pipe_base<T> (
15 command: &mut Command, piped: &mut Command, piped_stdout: T
16) -> Result<Output, io::Error> where T: Into<Stdio>{
17 let process = command.stdout(Stdio::piped()).spawn();
18 match process {
19 Ok(child) => {
20 if let Some(stdout) = child.stdout {
21 let piped_process = piped
22 .stdin(Stdio::piped())
23 .stdout(piped_stdout)
24 .spawn();
25 match piped_process {
26 Ok(mut piped_child) => {
27 if let Some(mut stdin) = piped_child.stdin.take() {
28 let mut writer = BufWriter::new(&mut stdin);
29 for line in BufReader::new(stdout).lines() {
30 if let Ok(l) = line {
31 writer.write_all(l.as_bytes()).unwrap();
32 writer.write_all(&[b'\n']).unwrap();
33 }
34 }
35 } else {
36 return Err(io::Error::new(
37 io::ErrorKind::BrokenPipe,
38 "Could not pipe command, stdin not found"
39 ))
40 }
41 if let Ok(out) = piped_child.wait_with_output() {
42 return Ok(out)
43 } else {
44 return Err(io::Error::new(
45 io::ErrorKind::BrokenPipe,
46 "Could not wait for pipe"
47 ))
48 }
49 },
50 Err(e) => return Err(e),
51 }
52 } else {
53 return Err(io::Error::new(
54 io::ErrorKind::BrokenPipe,
55 "Could not pipe command, stdout not found"
56 ))
57 }
58 },
59 Err(e) => return Err(e),
60 }
61}
62
63pub fn command_pipe (
71 command: &mut Command, piped: &mut Command
72) -> Result<Output, io::Error> {
73 return command_pipe_base(command, piped, Stdio::piped());
74}
75
76pub fn command_pipe_to_file (
85 command: &mut Command, piped: &mut Command, file: File
86) -> Result<(), io::Error> {
87 return match command_pipe_base(command, piped, file) {
88 Ok(_) => Ok(()),
89 Err(e) => Err(e),
90 };
91}
92
93pub trait CmdPipe {
94 fn pipe (&mut self, piped_command: &mut Command) -> Result<Output, io::Error>;
102
103 fn pipe_to_file (&mut self, piped_command: &mut Command, file: File) -> Result<(), io::Error>;
111}
112
113impl CmdPipe for Command {
114 fn pipe(&mut self, piped_command: &mut Command) -> Result<Output, io::Error> {
115 command_pipe(self, piped_command)
116 }
117
118 fn pipe_to_file (&mut self, piped_command: &mut Command, file: File) -> Result<(), io::Error> {
119 command_pipe_to_file(self, piped_command, file)
120 }
121}
122
123#[cfg(test)]
129mod test {
130 use std::fs;
131 use std::io::Read;
132 use std::process::Command;
133 use std::str::from_utf8;
134
135 use super::CmdPipe;
136
137 use super::command_pipe;
138
139 #[test]
140 fn test_command_to_pipe () {
141 let mut echo = Command::new("echo");
142 let mut wc = Command::new("wc");
143 let output = command_pipe(
144 &mut echo.args(["-n", "test"]),
145 &mut wc.arg("-c")
146 ).unwrap();
147 let res = match from_utf8(&output.stdout) {
148 Ok(s) => s,
149 Err(_) => panic!("unexpected stdout"),
150 };
151 assert_eq!(str::trim_start(res), "5\n");
153 }
154
155 #[test]
156 fn test_command_pipe_trait () {
157 let mut echo = Command::new("echo");
158 let mut wc = Command::new("wc");
159 let output = echo.args(["-n", "test"])
160 .pipe(&mut wc.arg("-c"))
161 .unwrap();
162 let res = match from_utf8(&output.stdout) {
163 Ok(s) => s,
164 Err(_) => panic!("unexpected stdout"),
165 };
166 assert_eq!(str::trim_start(res), "5\n");
168 }
169
170 #[test]
171 fn test_command_pipe_to_file () {
172 const FILE_NAME: &str = "tmp/__command_pipe_to_file";
173 let mut echo = Command::new("echo");
174 let mut wc = Command::new("wc");
175 if let Err(e) = fs::create_dir("tmp") {
176 match e.kind() {
177 std::io::ErrorKind::AlreadyExists => (),
178 _ => panic!("{}", e),
179 }
180 }
181 let file = fs::File::create(FILE_NAME).unwrap();
182 echo.args(["-n", "test"])
183 .pipe_to_file(&mut wc.arg("-c"), file)
184 .unwrap();
185
186 let mut read_file = fs::File::open(FILE_NAME).unwrap();
188 let mut res = String::new();
189 match read_file.read_to_string(&mut res) {
190 Err(e) => panic!("{}", e),
191 _ => (),
192 };
193
194 assert_eq!(str::trim_start(&res), "5\n");
195
196 fs::remove_file(FILE_NAME).unwrap();
198 }
199}