1use bstr::ByteSlice;
4use std::error::Error;
5use std::fmt;
6use std::process;
7
8pub trait OutputOkExt
26where
27 Self: ::std::marker::Sized,
28{
29 fn ok(self) -> OutputResult;
46
47 fn unwrap(self) -> process::Output {
63 match self.ok() {
64 Ok(output) => output,
65 Err(err) => panic!("{}", err),
66 }
67 }
68
69 fn unwrap_err(self) -> OutputError {
85 match self.ok() {
86 Ok(output) => panic!(
87 "Command completed successfully\nstdout=```{}```",
88 DebugBytes::new(&output.stdout)
89 ),
90 Err(err) => err,
91 }
92 }
93}
94
95impl OutputOkExt for process::Output {
96 fn ok(self) -> OutputResult {
97 if self.status.success() {
98 Ok(self)
99 } else {
100 let error = OutputError::new(self);
101 Err(error)
102 }
103 }
104}
105
106impl OutputOkExt for &mut process::Command {
107 fn ok(self) -> OutputResult {
108 let output = self.output().map_err(OutputError::with_cause)?;
109 if output.status.success() {
110 Ok(output)
111 } else {
112 let error = OutputError::new(output).set_cmd(format!("{self:?}"));
113 Err(error)
114 }
115 }
116
117 fn unwrap_err(self) -> OutputError {
118 match self.ok() {
119 Ok(output) => panic!(
120 "Completed successfully:\ncommand=`{:?}`\nstdout=```{}```",
121 self,
122 DebugBytes::new(&output.stdout)
123 ),
124 Err(err) => err,
125 }
126 }
127}
128
129pub type OutputResult = Result<process::Output, OutputError>;
149
150#[derive(Debug)]
168pub struct OutputError {
169 cmd: Option<String>,
170 stdin: Option<bstr::BString>,
171 cause: OutputCause,
172}
173
174impl OutputError {
175 pub fn new(output: process::Output) -> Self {
180 Self {
181 cmd: None,
182 stdin: None,
183 cause: OutputCause::Expected(Output { output }),
184 }
185 }
186
187 pub fn with_cause<E>(cause: E) -> Self
191 where
192 E: Error + Send + Sync + 'static,
193 {
194 Self {
195 cmd: None,
196 stdin: None,
197 cause: OutputCause::Unexpected(Box::new(cause)),
198 }
199 }
200
201 pub fn set_cmd(mut self, cmd: String) -> Self {
203 self.cmd = Some(cmd);
204 self
205 }
206
207 pub fn set_stdin(mut self, stdin: Vec<u8>) -> Self {
209 self.stdin = Some(bstr::BString::from(stdin));
210 self
211 }
212
213 pub fn as_output(&self) -> Option<&process::Output> {
233 match self.cause {
234 OutputCause::Expected(ref e) => Some(&e.output),
235 OutputCause::Unexpected(_) => None,
236 }
237 }
238}
239
240impl Error for OutputError {}
241
242impl fmt::Display for OutputError {
243 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
244 let palette = crate::Palette::color();
245 if let Some(ref cmd) = self.cmd {
246 writeln!(f, "{:#}={:#}", palette.key("command"), palette.value(cmd))?;
247 }
248 if let Some(ref stdin) = self.stdin {
249 writeln!(
250 f,
251 "{:#}={:#}",
252 palette.key("stdin"),
253 palette.value(DebugBytes::new(stdin))
254 )?;
255 }
256 write!(f, "{:#}", self.cause)
257 }
258}
259
260#[derive(Debug)]
261enum OutputCause {
262 Expected(Output),
263 Unexpected(Box<dyn Error + Send + Sync + 'static>),
264}
265
266impl fmt::Display for OutputCause {
267 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
268 match *self {
269 OutputCause::Expected(ref e) => write!(f, "{e:#}"),
270 OutputCause::Unexpected(ref e) => write!(f, "{e:#}"),
271 }
272 }
273}
274
275#[derive(Debug)]
276struct Output {
277 output: process::Output,
278}
279
280impl fmt::Display for Output {
281 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
282 output_fmt(&self.output, f)
283 }
284}
285
286pub(crate) fn output_fmt(output: &process::Output, f: &mut fmt::Formatter<'_>) -> fmt::Result {
287 let palette = crate::Palette::color();
288 if let Some(code) = output.status.code() {
289 writeln!(f, "{:#}={:#}", palette.key("code"), palette.value(code))?;
290 } else {
291 writeln!(
292 f,
293 "{:#}={:#}",
294 palette.key("code"),
295 palette.value("<interrupted>")
296 )?;
297 }
298
299 write!(
300 f,
301 "{:#}={:#}\n{:#}={:#}\n",
302 palette.key("stdout"),
303 palette.value(DebugBytes::new(&output.stdout)),
304 palette.key("stderr"),
305 palette.value(DebugBytes::new(&output.stderr)),
306 )?;
307 Ok(())
308}
309
310#[derive(Debug)]
311pub(crate) struct DebugBytes<'a> {
312 bytes: &'a [u8],
313}
314
315impl<'a> DebugBytes<'a> {
316 pub(crate) fn new(bytes: &'a [u8]) -> Self {
317 DebugBytes { bytes }
318 }
319}
320
321impl fmt::Display for DebugBytes<'_> {
322 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
323 format_bytes(self.bytes, f)
324 }
325}
326
327#[derive(Debug)]
328pub(crate) struct DebugBuffer {
329 buffer: bstr::BString,
330}
331
332impl DebugBuffer {
333 pub(crate) fn new(buffer: Vec<u8>) -> Self {
334 DebugBuffer {
335 buffer: buffer.into(),
336 }
337 }
338}
339
340impl fmt::Display for DebugBuffer {
341 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
342 format_bytes(&self.buffer, f)
343 }
344}
345
346fn format_bytes(data: &[u8], f: &mut impl fmt::Write) -> fmt::Result {
347 #![allow(clippy::assertions_on_constants)]
348
349 const LINES_MIN_OVERFLOW: usize = 80;
350 const LINES_MAX_START: usize = 20;
351 const LINES_MAX_END: usize = 40;
352 const LINES_MAX_PRINTED: usize = LINES_MAX_START + LINES_MAX_END;
353
354 const BYTES_MIN_OVERFLOW: usize = 8192;
355 const BYTES_MAX_START: usize = 2048;
356 const BYTES_MAX_END: usize = 2048;
357 const BYTES_MAX_PRINTED: usize = BYTES_MAX_START + BYTES_MAX_END;
358
359 assert!(LINES_MAX_PRINTED < LINES_MIN_OVERFLOW);
360 assert!(BYTES_MAX_PRINTED < BYTES_MIN_OVERFLOW);
361
362 let lines_total = data.as_bstr().lines_with_terminator().count();
363 let multiline = 1 < lines_total;
364
365 if LINES_MIN_OVERFLOW <= lines_total {
366 let lines_omitted = lines_total - LINES_MAX_PRINTED;
367 let start_lines = data.as_bstr().lines_with_terminator().take(LINES_MAX_START);
368 let end_lines = data
369 .as_bstr()
370 .lines_with_terminator()
371 .skip(LINES_MAX_START + lines_omitted);
372 writeln!(f, "<{lines_total} lines total>")?;
373 write_debug_bstrs(f, true, start_lines)?;
374 writeln!(f, "<{lines_omitted} lines omitted>")?;
375 write_debug_bstrs(f, true, end_lines)
376 } else if BYTES_MIN_OVERFLOW <= data.len() {
377 write!(
378 f,
379 "<{} bytes total>{}",
380 data.len(),
381 if multiline { "\n" } else { "" }
382 )?;
383 write_debug_bstrs(
384 f,
385 multiline,
386 data[..BYTES_MAX_START].lines_with_terminator(),
387 )?;
388 write!(
389 f,
390 "<{} bytes omitted>{}",
391 data.len() - BYTES_MAX_PRINTED,
392 if multiline { "\n" } else { "" }
393 )?;
394 write_debug_bstrs(
395 f,
396 multiline,
397 data[data.len() - BYTES_MAX_END..].lines_with_terminator(),
398 )
399 } else {
400 write_debug_bstrs(f, multiline, data.lines_with_terminator())
401 }
402}
403
404fn write_debug_bstrs<'a>(
405 f: &mut impl fmt::Write,
406 multiline: bool,
407 mut lines: impl Iterator<Item = &'a [u8]>,
408) -> fmt::Result {
409 if multiline {
410 writeln!(f, "```")?;
411 for mut line in lines {
412 let mut newline = false;
413 if line.last() == Some(&b'\n') {
414 line = &line[..line.len() - 1];
415 newline = true;
416 }
417 let s = format!("{:?}", line.as_bstr());
418 write!(
419 f,
420 "{}{}",
421 &s[1..s.len() - 1],
422 if newline { "\n" } else { "" }
423 )?;
424 }
425 writeln!(f, "```")
426 } else {
427 write!(f, "{:?}", lines.next().unwrap_or(&[]).as_bstr())
428 }
429}
430
431#[cfg(test)]
432mod test {
433 #[test]
434 fn format_bytes() {
435 let mut s = String::new();
436 for i in 0..80 {
437 s.push_str(&format!("{i}\n"));
438 }
439
440 let mut buf = String::new();
441 super::format_bytes(s.as_bytes(), &mut buf).unwrap();
442
443 assert_eq!(
444 "<80 lines total>
445```
4460
4471
4482
4493
4504
4515
4526
4537
4548
4559
45610
45711
45812
45913
46014
46115
46216
46317
46418
46519
466```
467<20 lines omitted>
468```
46940
47041
47142
47243
47344
47445
47546
47647
47748
47849
47950
48051
48152
48253
48354
48455
48556
48657
48758
48859
48960
49061
49162
49263
49364
49465
49566
49667
49768
49869
49970
50071
50172
50273
50374
50475
50576
50677
50778
50879
509```
510",
511 buf
512 );
513 }
514
515 #[test]
516 fn no_trailing_newline() {
517 let s = "no\ntrailing\nnewline";
518
519 let mut buf = String::new();
520 super::format_bytes(s.as_bytes(), &mut buf).unwrap();
521
522 assert_eq!(
523 "```
524no
525trailing
526newline```
527",
528 buf
529 );
530 }
531}