cargo_deb/
listener.rs

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    /// Notified when finished writing .deb file (possibly before install)
23    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}