tectonic/status/
termcolor.rs1use std::fmt::Arguments;
9use std::io::Write;
10use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
11
12use tectonic_errors::Error;
13
14use super::{ChatterLevel, MessageKind, StatusBackend};
15
16pub struct TermcolorStatusBackend {
18 chatter: ChatterLevel,
19 always_stderr: bool,
20 stdout: StandardStream,
21 stderr: StandardStream,
22 note_spec: ColorSpec,
23 highlight_spec: ColorSpec,
24 warning_spec: ColorSpec,
25 error_spec: ColorSpec,
26}
27
28impl TermcolorStatusBackend {
29 pub fn new(chatter: ChatterLevel) -> TermcolorStatusBackend {
31 let mut note_spec = ColorSpec::new();
32 note_spec.set_fg(Some(Color::Green)).set_bold(true);
33
34 let mut highlight_spec = ColorSpec::new();
35 highlight_spec.set_bold(true);
36
37 let mut warning_spec = ColorSpec::new();
38 warning_spec.set_fg(Some(Color::Yellow)).set_bold(true);
39
40 let mut error_spec = ColorSpec::new();
41 error_spec.set_fg(Some(Color::Red)).set_bold(true);
42
43 TermcolorStatusBackend {
44 chatter,
45 always_stderr: false,
46 stdout: StandardStream::stdout(ColorChoice::Auto),
47 stderr: StandardStream::stderr(ColorChoice::Auto),
48 note_spec,
49 highlight_spec,
50 warning_spec,
51 error_spec,
52 }
53 }
54
55 pub fn always_stderr(&mut self, setting: bool) -> &mut Self {
57 self.always_stderr = setting;
58 self
59 }
60
61 fn styled<F>(&mut self, kind: MessageKind, f: F)
62 where
63 F: FnOnce(&mut StandardStream),
64 {
65 if kind == MessageKind::Note && self.chatter <= ChatterLevel::Minimal {
66 return;
67 }
68
69 let (spec, stream) = match kind {
70 MessageKind::Note => {
71 if self.always_stderr {
72 (&self.note_spec, &mut self.stderr)
73 } else {
74 (&self.note_spec, &mut self.stdout)
75 }
76 }
77 MessageKind::Warning => (&self.warning_spec, &mut self.stderr),
78 MessageKind::Error => (&self.error_spec, &mut self.stderr),
79 };
80
81 stream.set_color(spec).expect("failed to set color");
82 f(stream);
83 stream.reset().expect("failed to clear color");
84 }
85
86 fn with_stream<F>(&mut self, kind: MessageKind, f: F)
87 where
88 F: FnOnce(&mut StandardStream),
89 {
90 if kind == MessageKind::Note && self.chatter <= ChatterLevel::Minimal {
91 return;
92 }
93
94 let stream = match kind {
95 MessageKind::Note => {
96 if self.always_stderr {
97 &mut self.stderr
98 } else {
99 &mut self.stdout
100 }
101 }
102 MessageKind::Warning => &mut self.stderr,
103 MessageKind::Error => &mut self.stderr,
104 };
105
106 f(stream);
107 }
108
109 fn generic_message(&mut self, kind: MessageKind, prefix: Option<&str>, args: Arguments) {
110 let text = match prefix {
111 Some(s) => s,
112 None => match kind {
113 MessageKind::Note => "note:",
114 MessageKind::Warning => "warning:",
115 MessageKind::Error => "error:",
116 },
117 };
118
119 self.styled(kind, |s| {
120 write!(s, "{text}").expect("failed to write to standard stream");
121 });
122 self.with_stream(kind, |s| {
123 writeln!(s, " {args}").expect("failed to write to standard stream");
124 });
125 }
126
127 pub fn note_styled(&mut self, args: Arguments) {
133 if self.chatter > ChatterLevel::Minimal {
134 if self.always_stderr {
135 writeln!(self.stderr, "{args}").expect("write to stderr failed");
136 } else {
137 writeln!(self.stdout, "{args}").expect("write to stdout failed");
138 }
139 }
140 }
141
142 pub fn error_styled(&mut self, args: Arguments) {
144 self.styled(MessageKind::Error, |s| {
145 writeln!(s, "{args}").expect("write to stderr failed");
146 });
147 }
148
149 pub fn bare_error(&mut self, err: &Error) {
151 let mut prefix = "error:";
152
153 for item in err.chain() {
154 self.generic_message(MessageKind::Error, Some(prefix), format_args!("{item}"));
155 prefix = "caused by:";
156 }
157 }
158}
159
160#[macro_export]
165macro_rules! tt_error_styled {
166 ($dest:expr, $( $fmt_args:expr ),*) => {
167 $dest.error_styled(format_args!($( $fmt_args ),*))
168 };
169}
170
171impl StatusBackend for TermcolorStatusBackend {
172 fn report(&mut self, kind: MessageKind, args: Arguments, err: Option<&Error>) {
173 self.generic_message(kind, None, args);
174
175 if let Some(e) = err {
176 for item in e.chain() {
177 self.generic_message(kind, Some("caused by:"), format_args!("{item}"));
178 }
179 }
180 }
181
182 fn report_error(&mut self, err: &Error) {
183 let mut first = true;
184 let kind = MessageKind::Error;
185
186 for item in err.chain() {
187 if first {
188 self.generic_message(kind, None, format_args!("{item}"));
189 first = false;
190 } else {
191 self.generic_message(kind, Some("caused by:"), format_args!("{item}"));
192 }
193 }
194 }
195
196 fn note_highlighted(&mut self, before: &str, highlighted: &str, after: &str) {
197 if self.chatter > ChatterLevel::Minimal {
198 let stream = if self.always_stderr {
199 &mut self.stderr
200 } else {
201 &mut self.stdout
202 };
203
204 write!(stream, "{before}").expect("write failed");
205 stream
206 .set_color(&self.highlight_spec)
207 .expect("write failed");
208 write!(stream, "{highlighted}").expect("write failed");
209 stream.reset().expect("write failed");
210 writeln!(stream, "{after}").expect("write failed");
211 }
212 }
213
214 fn dump_error_logs(&mut self, output: &[u8]) {
215 tt_error_styled!(
216 self,
217 "==============================================================================="
218 );
219
220 self.stderr
221 .write_all(output)
222 .expect("write to stderr failed");
223
224 tt_error_styled!(
225 self,
226 "==============================================================================="
227 );
228 }
229}