1use anstream::{AutoStream, ColorChoice};
2use anstyle::{AnsiColor, Style};
3use std::error::Error;
4use std::io::{StderrLock, Write};
5use std::path::Path;
6
7#[cfg_attr(test, mockall::automock)]
8pub trait Listener: Send + Sync {
9 fn warning(&self, s: String);
10 fn info(&self, s: String);
11
12 fn progress(&self, operation: &str, detail: String) {
13 self.info(format!("{operation}: {detail}"));
14 }
15
16 #[allow(unused_parens)]
17 fn error(&self, error: &(dyn Error + 'static)) {
18 let mut out = std::io::stderr().lock();
19 let _ = writeln!(out, "cargo-deb: {error}");
20 }
21
22 fn generated_archive(&self, path: &Path) {
24 println!("{}", path.display());
25 }
26}
27
28pub struct NoOpListener;
29impl Listener for NoOpListener {
30 fn info(&self, _s: String) {}
31 fn warning(&self, _s: String) {}
32 fn progress(&self, _op: &str, _s: String) {}
33 fn generated_archive(&self, _: &Path) {}
34}
35
36pub struct StdErrListener {
37 pub verbose: bool,
38 pub quiet: bool,
39 pub color: ColorChoice,
40}
41
42impl StdErrListener {
43 fn label(&self, label: &str, style: Style, text: &str) {
44 let mut out = AutoStream::new(std::io::stderr(), self.color).lock();
45 Self::label_locked(&mut out, label, 0, style, text);
46 }
47
48 fn label_locked(out: &mut AutoStream<StderrLock<'static>>, label: &str, indent: u8, style: Style, text: &str) {
49 let text = text.strip_prefix(label).and_then(|t| t.strip_prefix(": ")).unwrap_or(text).trim_end();
50 let mut lines = text.lines();
51 if let Some(line) = lines.next() {
52 let _ = writeln!(*out, "{:width$}{style}{label}{style:#}: {line}", "", width = indent as usize);
53 }
54 for line in lines {
55 let _ = writeln!(*out, "{:width$}{line}", "", width = indent as usize + label.len() + 2);
56 }
57 }
58
59 fn error_with_notes(&self, out: &mut AutoStream<StderrLock<'static>>, err: &(dyn Error + 'static), primary_error: bool) {
60 let err_msg = err.to_string();
61 let mut messages = err_msg.split("\nnote: ");
62 let err_msg = messages.next().unwrap_or_default();
63
64 if primary_error {
65 Self::label_locked(&mut *out, "error", 0, Style::new().bold().fg_color(Some(AnsiColor::Red.into())), err_msg);
66 } else {
67 Self::label_locked(&mut *out, "cause", 2, Style::new().fg_color(Some(AnsiColor::Red.into())), err_msg);
68 }
69
70 for note in messages.map(|n| n.trim_start()).filter(|n| !n.is_empty()) {
71 Self::label_locked(out, "note", if primary_error { 0 } else { 3 }, Style::new().fg_color(Some(AnsiColor::Cyan.into())), note);
72 }
73 }
74}
75
76impl Listener for StdErrListener {
77 fn warning(&self, s: String) {
78 if !self.quiet {
79 self.label("warning", Style::new().bold().fg_color(Some(AnsiColor::Yellow.into())), &s);
80 }
81 }
82
83 fn info(&self, s: String) {
84 if self.verbose {
85 self.label("info", Style::new().bold().fg_color(Some(AnsiColor::Cyan.into())), &s);
86 }
87 }
88
89 fn error(&self, err: &(dyn Error + 'static)) {
90 let mut out = AutoStream::new(std::io::stderr(), self.color).lock();
91 self.error_with_notes(&mut out, err, true);
92
93 let mut cause = err.source();
94 let mut max_causes = 5;
95 while let Some(err) = cause {
96 max_causes -= 1;
97 if max_causes == 0 {
98 break;
99 }
100 self.error_with_notes(&mut out, err, false);
101 cause = err.source();
102 }
103 }
104
105 fn progress(&self, operation: &str, detail: String) {
106 if self.verbose {
107 let mut out = AutoStream::new(std::io::stderr(), self.color).lock();
108 let style = Style::new().bold().fg_color(Some(AnsiColor::Green.into()));
109 let _ = writeln!(out, "{style}{operation:>12}{style:#} {detail}");
110 }
111 }
112}
113
114pub(crate) struct PrefixedListener<'l>(pub &'l str, pub &'l dyn Listener);
115impl Listener for PrefixedListener<'_> {
116 fn warning(&self, mut s: String) {
117 s.insert_str(0, self.0);
118 self.1.warning(s);
119 }
120
121 fn error(&self, err: &(dyn Error + 'static)) {
122 self.1.error(err);
123 }
124
125 fn info(&self, mut s: String) {
126 s.insert_str(0, self.0);
127 self.1.info(s);
128 }
129
130 fn progress(&self, operation: &str, mut s: String) {
131 s.insert_str(0, self.0);
132 self.1.progress(operation, s);
133 }
134}