1#![doc = include_str!("../README.md")]
2use std::cmp::Ordering;
3use std::io::{BufRead, BufReader, Lines};
4use std::process::{ChildStderr, ChildStdout, Command, Stdio};
5use std::thread;
6use std::time::{Duration, Instant};
7
8mod tests;
9
10#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct CmdOutput {
16 lines: Option<Vec<Line>>,
17 status_code: Option<i32>,
18 start_time: Instant,
19 end_time: Instant,
20 duration: Duration,
21}
22
23impl CmdOutput {
24 pub fn stdout(self) -> Option<Vec<Line>> {
28 self.lines.and_then(|lines| {
29 Some(
30 lines
31 .into_iter()
32 .filter(|line| line.printed_to == LineType::Stdout)
33 .collect(),
34 )
35 })
36 }
37
38 pub fn stderr(self) -> Option<Vec<Line>> {
42 self.lines.and_then(|lines| {
43 Some(
44 lines
45 .into_iter()
46 .filter(|line| line.printed_to == LineType::Stderr)
47 .collect(),
48 )
49 })
50 }
51
52 pub fn lines(self) -> Option<Vec<Line>> {
57 return self.lines;
58 }
59
60 pub fn status_code(self) -> Option<i32> {
64 return self.status_code;
65 }
66
67 pub fn duration(self) -> Duration {
69 return self.duration;
70 }
71
72 pub fn start_time(self) -> Instant {
74 return self.start_time;
75 }
76
77 pub fn end_time(self) -> Instant {
79 return self.end_time;
80 }
81}
82
83#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
85pub enum LineType {
86 Stdout,
87 Stderr,
88}
89
90#[derive(Debug, Clone, PartialEq, Eq, Ord)]
92pub struct Line {
93 pub printed_to: LineType,
95 pub time: Instant,
97 pub content: String,
99}
100
101impl Line {
102 pub fn from_stdout<S: AsRef<str>>(content: S) -> Self {
104 return Line {
105 content: content.as_ref().to_string(),
106 printed_to: LineType::Stdout,
107 time: Instant::now(),
108 };
109 }
110
111 pub fn from_stderr<S: AsRef<str>>(content: S) -> Self {
113 return Line {
114 content: content.as_ref().to_string(),
115 printed_to: LineType::Stderr,
116 time: Instant::now(),
117 };
118 }
119}
120
121impl PartialOrd for Line {
122 fn ge(&self, other: &Line) -> bool {
123 if self.time >= other.time {
124 return true;
125 }
126 return false;
127 }
128
129 fn gt(&self, other: &Self) -> bool {
130 if self.time > other.time {
131 return true;
132 }
133 return false;
134 }
135
136 fn le(&self, other: &Self) -> bool {
137 if self.time <= other.time {
138 return true;
139 }
140 return false;
141 }
142
143 fn lt(&self, other: &Self) -> bool {
144 if self.time < other.time {
145 return true;
146 }
147 return false;
148 }
149
150 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
151 if self < other {
152 return Some(Ordering::Less);
153 }
154 if self > other {
155 return Some(Ordering::Greater);
156 }
157 return Some(Ordering::Equal);
158 }
159}
160
161pub fn run(command: &mut Command) -> CmdOutput {
175 let start = Instant::now();
177 let mut child = command
178 .stdout(Stdio::piped())
179 .stderr(Stdio::piped())
180 .spawn()
181 .unwrap();
182
183 let child_stdout = child.stdout.take().unwrap();
184 let child_stderr = child.stderr.take().unwrap();
185
186 let stdout_lines = BufReader::new(child_stdout).lines();
187 let stdout_thread = thread::spawn(move || {
188 let mut lines: Vec<Line> = Vec::new();
189 for line in stdout_lines {
190 lines.push(Line {
191 content: line.unwrap(),
192 printed_to: LineType::Stdout,
193 time: Instant::now(),
194 });
195 }
196 return lines;
197 });
198
199 let stderr_lines = BufReader::new(child_stderr).lines();
200 let stderr_thread = thread::spawn(move || {
201 let mut lines: Vec<Line> = Vec::new();
202 for line in stderr_lines {
203 let time = Instant::now();
204 lines.push(Line {
205 content: line.unwrap(),
206 printed_to: LineType::Stderr,
207 time: time,
208 });
209 }
210 return lines;
211 });
212
213 let status = child.wait().unwrap().code();
214 let end = Instant::now();
215
216 let mut lines = stdout_thread.join().unwrap();
217 lines.append(&mut stderr_thread.join().unwrap());
218 lines.sort();
219
220 return CmdOutput {
221 lines: Some(lines),
222 status_code: status,
223 start_time: start,
224 end_time: end,
225 duration: end.duration_since(start),
226 };
227}
228
229pub fn run_funcs(
253 command: &mut Command,
254 stdout_func: impl FnOnce(Lines<BufReader<ChildStdout>>) -> () + std::marker::Send + 'static,
255 stderr_func: impl FnOnce(Lines<BufReader<ChildStderr>>) -> () + std::marker::Send + 'static,
256) -> CmdOutput {
257 let start = Instant::now();
259 let mut child = command
260 .stdout(Stdio::piped())
261 .stderr(Stdio::piped())
262 .spawn()
263 .unwrap();
264
265 let child_stdout = child.stdout.take().unwrap();
266 let child_stderr = child.stderr.take().unwrap();
267
268 let stdout_lines = BufReader::new(child_stdout).lines();
269 let stdout_thread = thread::spawn(move || stdout_func(stdout_lines));
270
271 let stderr_lines = BufReader::new(child_stderr).lines();
272 let stderr_thread = thread::spawn(move || stderr_func(stderr_lines));
273
274 let status = child.wait().unwrap().code();
275 let end = Instant::now();
276
277 stdout_thread.join().unwrap();
278 stderr_thread.join().unwrap();
279
280 return CmdOutput {
281 lines: None,
282 status_code: status,
283 start_time: start,
284 end_time: end,
285 duration: end.duration_since(start),
286 };
287}
288
289pub fn run_funcs_with_lines(
332 command: &mut Command,
333 stdout_func: impl FnOnce(Lines<BufReader<ChildStdout>>) -> Vec<Line> + std::marker::Send + 'static,
334 stderr_func: impl FnOnce(Lines<BufReader<ChildStderr>>) -> Vec<Line> + std::marker::Send + 'static,
335) -> CmdOutput {
336 let start = Instant::now();
338 let mut child = command
339 .stdout(Stdio::piped())
340 .stderr(Stdio::piped())
341 .spawn()
342 .unwrap();
343
344 let child_stdout = child.stdout.take().unwrap();
345 let child_stderr = child.stderr.take().unwrap();
346
347 let stdout_lines = BufReader::new(child_stdout).lines();
348 let stderr_lines = BufReader::new(child_stderr).lines();
349
350 let stdout_thread = thread::spawn(move || stdout_func(stdout_lines));
351 let stderr_thread = thread::spawn(move || stderr_func(stderr_lines));
352
353 let mut lines = stdout_thread.join().unwrap();
354 let mut lines_printed_to_stderr = stderr_thread.join().unwrap();
355 lines.append(&mut lines_printed_to_stderr);
356 lines.sort();
357
358 let status = child.wait().unwrap().code();
359 let end = Instant::now();
360
361 return CmdOutput {
362 lines: Some(lines),
363 status_code: status,
364 start_time: start,
365 end_time: end,
366 duration: end.duration_since(start),
367 };
368}