1use std::fmt;
2use std::io::prelude::*;
3
4use termcolor::Color::{Cyan, Green, Red, Yellow};
5use termcolor::{self, Color, ColorSpec, StandardStream, WriteColor};
6
7use crate::util::errors::CargoResult;
8
9#[derive(Debug, Clone, Copy, PartialEq)]
11pub enum Verbosity {
12 Verbose,
13 Normal,
14 Quiet,
15}
16
17pub struct Shell {
20 err: ShellOut,
23 verbosity: Verbosity,
25 needs_clear: bool,
28}
29
30impl fmt::Debug for Shell {
31 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32 match self.err {
33 ShellOut::Write(_) => f
34 .debug_struct("Shell")
35 .field("verbosity", &self.verbosity)
36 .finish(),
37 ShellOut::Stream { color_choice, .. } => f
38 .debug_struct("Shell")
39 .field("verbosity", &self.verbosity)
40 .field("color_choice", &color_choice)
41 .finish(),
42 }
43 }
44}
45
46enum ShellOut {
48 Write(Box<dyn Write>),
50 Stream {
52 stream: StandardStream,
53 tty: bool,
54 color_choice: ColorChoice,
55 },
56}
57
58#[derive(Debug, PartialEq, Clone, Copy)]
60pub enum ColorChoice {
61 Always,
63 Never,
65 CargoAuto,
67}
68
69impl Shell {
70 pub fn new() -> Shell {
73 Shell {
74 err: ShellOut::Stream {
75 stream: StandardStream::stderr(ColorChoice::CargoAuto.to_termcolor_color_choice()),
76 color_choice: ColorChoice::CargoAuto,
77 tty: atty::is(atty::Stream::Stderr),
78 },
79 verbosity: Verbosity::Verbose,
80 needs_clear: false,
81 }
82 }
83
84 pub fn from_write(out: Box<dyn Write>) -> Shell {
86 Shell {
87 err: ShellOut::Write(out),
88 verbosity: Verbosity::Verbose,
89 needs_clear: false,
90 }
91 }
92
93 fn print(
96 &mut self,
97 status: &dyn fmt::Display,
98 message: Option<&dyn fmt::Display>,
99 color: Color,
100 justified: bool,
101 ) -> CargoResult<()> {
102 match self.verbosity {
103 Verbosity::Quiet => Ok(()),
104 _ => {
105 if self.needs_clear {
106 self.err_erase_line();
107 }
108 self.err.print(status, message, color, justified)
109 }
110 }
111 }
112
113 pub fn stdout_println(&mut self, message: impl fmt::Display) {
114 if self.needs_clear {
115 self.err_erase_line();
116 }
117 println!("{}", message);
118 }
119
120 pub fn set_needs_clear(&mut self, needs_clear: bool) {
122 self.needs_clear = needs_clear;
123 }
124
125 pub fn is_cleared(&self) -> bool {
127 !self.needs_clear
128 }
129
130 pub fn err_width(&self) -> Option<usize> {
132 match self.err {
133 ShellOut::Stream { tty: true, .. } => imp::stderr_width(),
134 _ => None,
135 }
136 }
137
138 pub fn is_err_tty(&self) -> bool {
140 match self.err {
141 ShellOut::Stream { tty, .. } => tty,
142 _ => false,
143 }
144 }
145
146 pub fn err(&mut self) -> &mut dyn Write {
148 if self.needs_clear {
149 self.err_erase_line();
150 }
151 self.err.as_write()
152 }
153
154 pub fn err_erase_line(&mut self) {
156 if let ShellOut::Stream { tty: true, .. } = self.err {
157 imp::err_erase_line(self);
158 self.needs_clear = false;
159 }
160 }
161
162 pub fn status<T, U>(&mut self, status: T, message: U) -> CargoResult<()>
164 where
165 T: fmt::Display,
166 U: fmt::Display,
167 {
168 self.print(&status, Some(&message), Green, true)
169 }
170
171 pub fn status_header<T>(&mut self, status: T) -> CargoResult<()>
172 where
173 T: fmt::Display,
174 {
175 self.print(&status, None, Cyan, true)
176 }
177
178 pub fn status_with_color<T, U>(
180 &mut self,
181 status: T,
182 message: U,
183 color: Color,
184 ) -> CargoResult<()>
185 where
186 T: fmt::Display,
187 U: fmt::Display,
188 {
189 self.print(&status, Some(&message), color, true)
190 }
191
192 pub fn verbose<F>(&mut self, mut callback: F) -> CargoResult<()>
194 where
195 F: FnMut(&mut Shell) -> CargoResult<()>,
196 {
197 match self.verbosity {
198 Verbosity::Verbose => callback(self),
199 _ => Ok(()),
200 }
201 }
202
203 pub fn concise<F>(&mut self, mut callback: F) -> CargoResult<()>
205 where
206 F: FnMut(&mut Shell) -> CargoResult<()>,
207 {
208 match self.verbosity {
209 Verbosity::Verbose => Ok(()),
210 _ => callback(self),
211 }
212 }
213
214 pub fn error<T: fmt::Display>(&mut self, message: T) -> CargoResult<()> {
216 if self.needs_clear {
217 self.err_erase_line();
218 }
219 self.err.print(&"error", Some(&message), Red, false)
220 }
221
222 pub fn warn<T: fmt::Display>(&mut self, message: T) -> CargoResult<()> {
224 match self.verbosity {
225 Verbosity::Quiet => Ok(()),
226 _ => self.print(&"warning", Some(&message), Yellow, false),
227 }
228 }
229
230 pub fn note<T: fmt::Display>(&mut self, message: T) -> CargoResult<()> {
232 self.print(&"note", Some(&message), Cyan, false)
233 }
234
235 pub fn set_verbosity(&mut self, verbosity: Verbosity) {
237 self.verbosity = verbosity;
238 }
239
240 pub fn verbosity(&self) -> Verbosity {
242 self.verbosity
243 }
244
245 pub fn set_color_choice(&mut self, color: Option<&str>) -> CargoResult<()> {
247 if let ShellOut::Stream {
248 ref mut stream,
249 ref mut color_choice,
250 ..
251 } = self.err
252 {
253 let cfg = match color {
254 Some("always") => ColorChoice::Always,
255 Some("never") => ColorChoice::Never,
256
257 Some("auto") | None => ColorChoice::CargoAuto,
258
259 Some(arg) => anyhow::bail!(
260 "argument for --color must be auto, always, or \
261 never, but found `{}`",
262 arg
263 ),
264 };
265 *color_choice = cfg;
266 *stream = StandardStream::stderr(cfg.to_termcolor_color_choice());
267 }
268 Ok(())
269 }
270
271 pub fn color_choice(&self) -> ColorChoice {
276 match self.err {
277 ShellOut::Stream { color_choice, .. } => color_choice,
278 ShellOut::Write(_) => ColorChoice::Never,
279 }
280 }
281
282 pub fn supports_color(&self) -> bool {
284 match &self.err {
285 ShellOut::Write(_) => false,
286 ShellOut::Stream { stream, .. } => stream.supports_color(),
287 }
288 }
289
290 pub fn print_ansi(&mut self, message: &[u8]) -> CargoResult<()> {
292 if self.needs_clear {
293 self.err_erase_line();
294 }
295 #[cfg(windows)]
296 {
297 if let ShellOut::Stream { stream, .. } = &mut self.err {
298 ::fwdansi::write_ansi(stream, message)?;
299 return Ok(());
300 }
301 }
302 self.err().write_all(message)?;
303 Ok(())
304 }
305}
306
307impl Default for Shell {
308 fn default() -> Self {
309 Self::new()
310 }
311}
312
313impl ShellOut {
314 fn print(
318 &mut self,
319 status: &dyn fmt::Display,
320 message: Option<&dyn fmt::Display>,
321 color: Color,
322 justified: bool,
323 ) -> CargoResult<()> {
324 match *self {
325 ShellOut::Stream { ref mut stream, .. } => {
326 stream.reset()?;
327 stream.set_color(ColorSpec::new().set_bold(true).set_fg(Some(color)))?;
328 if justified {
329 write!(stream, "{:>12}", status)?;
330 } else {
331 write!(stream, "{}", status)?;
332 stream.set_color(ColorSpec::new().set_bold(true))?;
333 write!(stream, ":")?;
334 }
335 stream.reset()?;
336 match message {
337 Some(message) => writeln!(stream, " {}", message)?,
338 None => write!(stream, " ")?,
339 }
340 }
341 ShellOut::Write(ref mut w) => {
342 if justified {
343 write!(w, "{:>12}", status)?;
344 } else {
345 write!(w, "{}:", status)?;
346 }
347 match message {
348 Some(message) => writeln!(w, " {}", message)?,
349 None => write!(w, " ")?,
350 }
351 }
352 }
353 Ok(())
354 }
355
356 fn as_write(&mut self) -> &mut dyn Write {
358 match *self {
359 ShellOut::Stream { ref mut stream, .. } => stream,
360 ShellOut::Write(ref mut w) => w,
361 }
362 }
363}
364
365impl ColorChoice {
366 fn to_termcolor_color_choice(self) -> termcolor::ColorChoice {
368 match self {
369 ColorChoice::Always => termcolor::ColorChoice::Always,
370 ColorChoice::Never => termcolor::ColorChoice::Never,
371 ColorChoice::CargoAuto => {
372 if atty::is(atty::Stream::Stderr) {
373 termcolor::ColorChoice::Auto
374 } else {
375 termcolor::ColorChoice::Never
376 }
377 }
378 }
379 }
380}
381
382#[cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))]
383mod imp {
384 use super::Shell;
385 use std::mem;
386
387 pub fn stderr_width() -> Option<usize> {
388 unsafe {
389 let mut winsize: libc::winsize = mem::zeroed();
390 if libc::ioctl(libc::STDERR_FILENO, libc::TIOCGWINSZ.into(), &mut winsize) < 0 {
393 return None;
394 }
395 if winsize.ws_col > 0 {
396 Some(winsize.ws_col as usize)
397 } else {
398 None
399 }
400 }
401 }
402
403 pub fn err_erase_line(shell: &mut Shell) {
404 let _ = shell.err.as_write().write_all(b"\x1B[K");
408 }
409}
410
411#[cfg(all(
412 unix,
413 not(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))
414))]
415mod imp {
416 pub(super) use super::default_err_erase_line as err_erase_line;
417
418 pub fn stderr_width() -> Option<usize> {
419 None
420 }
421}
422
423#[cfg(windows)]
424mod imp {
425 use std::{cmp, mem, ptr};
426 use winapi::um::fileapi::*;
427 use winapi::um::handleapi::*;
428 use winapi::um::processenv::*;
429 use winapi::um::winbase::*;
430 use winapi::um::wincon::*;
431 use winapi::um::winnt::*;
432
433 pub(super) use super::default_err_erase_line as err_erase_line;
434
435 pub fn stderr_width() -> Option<usize> {
436 unsafe {
437 let stdout = GetStdHandle(STD_ERROR_HANDLE);
438 let mut csbi: CONSOLE_SCREEN_BUFFER_INFO = mem::zeroed();
439 if GetConsoleScreenBufferInfo(stdout, &mut csbi) != 0 {
440 return Some((csbi.srWindow.Right - csbi.srWindow.Left) as usize);
441 }
442
443 let h = CreateFileA(
447 "CONOUT$\0".as_ptr() as *const CHAR,
448 GENERIC_READ | GENERIC_WRITE,
449 FILE_SHARE_READ | FILE_SHARE_WRITE,
450 ptr::null_mut(),
451 OPEN_EXISTING,
452 0,
453 ptr::null_mut(),
454 );
455 if h == INVALID_HANDLE_VALUE {
456 return None;
457 }
458
459 let mut csbi: CONSOLE_SCREEN_BUFFER_INFO = mem::zeroed();
460 let rc = GetConsoleScreenBufferInfo(h, &mut csbi);
461 CloseHandle(h);
462 if rc != 0 {
463 let width = (csbi.srWindow.Right - csbi.srWindow.Left) as usize;
464 return Some(cmp::min(60, width));
473 }
474 None
475 }
476 }
477}
478
479#[cfg(any(
480 all(
481 unix,
482 not(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))
483 ),
484 windows,
485))]
486fn default_err_erase_line(shell: &mut Shell) {
487 if let Some(max_width) = imp::stderr_width() {
488 let blank = " ".repeat(max_width);
489 drop(write!(shell.err.as_write(), "{}\r", blank));
490 }
491}