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::error::{Error, Result};
8
9pub enum TtyWidth {
10 NoTty,
11 Known(usize),
12 Guess(usize),
13}
14
15impl TtyWidth {
16 pub fn diagnostic_terminal_width(&self) -> Option<usize> {
19 match *self {
20 TtyWidth::NoTty | TtyWidth::Guess(_) => None,
21 TtyWidth::Known(width) => Some(width),
22 }
23 }
24
25 pub fn progress_max_width(&self) -> Option<usize> {
27 match *self {
28 TtyWidth::NoTty => None,
29 TtyWidth::Known(width) | TtyWidth::Guess(width) => Some(width),
30 }
31 }
32}
33
34#[derive(Debug, Clone, Copy, PartialEq)]
36pub enum Verbosity {
37 Verbose,
38 Normal,
39 Quiet,
40}
41
42pub struct Shell {
45 output: ShellOut,
48 verbosity: Verbosity,
50 needs_clear: bool,
53}
54
55impl fmt::Debug for Shell {
56 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57 match self.output {
58 ShellOut::Write(_) => f
59 .debug_struct("Shell")
60 .field("verbosity", &self.verbosity)
61 .finish(),
62 ShellOut::Stream { color_choice, .. } => f
63 .debug_struct("Shell")
64 .field("verbosity", &self.verbosity)
65 .field("color_choice", &color_choice)
66 .finish(),
67 }
68 }
69}
70
71enum ShellOut {
73 Write(Box<dyn Write>),
75 Stream {
77 stdout: StandardStream,
78 stderr: StandardStream,
79 stderr_tty: bool,
80 color_choice: ColorChoice,
81 },
82}
83
84#[derive(Debug, PartialEq, Clone, Copy)]
86pub enum ColorChoice {
87 Always,
89 Never,
91 CargoAuto,
93}
94
95impl Shell {
96 pub fn new() -> Shell {
99 let auto = ColorChoice::CargoAuto.to_termcolor_color_choice();
100 Shell {
101 output: ShellOut::Stream {
102 stdout: StandardStream::stdout(auto),
103 stderr: StandardStream::stderr(auto),
104 color_choice: ColorChoice::CargoAuto,
105 stderr_tty: atty::is(atty::Stream::Stderr),
106 },
107 verbosity: Verbosity::Verbose,
108 needs_clear: false,
109 }
110 }
111
112 pub fn from_write(out: Box<dyn Write>) -> Shell {
114 Shell {
115 output: ShellOut::Write(out),
116 verbosity: Verbosity::Verbose,
117 needs_clear: false,
118 }
119 }
120
121 fn print(
124 &mut self,
125 status: &dyn fmt::Display,
126 message: Option<&dyn fmt::Display>,
127 color: Color,
128 justified: bool,
129 ) -> Result<()> {
130 match self.verbosity {
131 Verbosity::Quiet => Ok(()),
132 _ => {
133 if self.needs_clear {
134 self.err_erase_line();
135 }
136 self.output
137 .message_stderr(status, message, color, justified)
138 }
139 }
140 }
141
142 pub fn set_needs_clear(&mut self, needs_clear: bool) {
144 self.needs_clear = needs_clear;
145 }
146
147 pub fn is_cleared(&self) -> bool {
149 !self.needs_clear
150 }
151
152 pub fn err_width(&self) -> TtyWidth {
154 match self.output {
155 ShellOut::Stream {
156 stderr_tty: true, ..
157 } => imp::stderr_width(),
158 _ => TtyWidth::NoTty,
159 }
160 }
161
162 pub fn is_err_tty(&self) -> bool {
164 match self.output {
165 ShellOut::Stream { stderr_tty, .. } => stderr_tty,
166 _ => false,
167 }
168 }
169
170 pub fn out(&mut self) -> &mut dyn Write {
172 if self.needs_clear {
173 self.err_erase_line();
174 }
175 self.output.stdout()
176 }
177
178 pub fn err(&mut self) -> &mut dyn Write {
180 if self.needs_clear {
181 self.err_erase_line();
182 }
183 self.output.stderr()
184 }
185
186 pub fn err_erase_line(&mut self) {
188 if let ShellOut::Stream {
189 stderr_tty: true, ..
190 } = self.output
191 {
192 imp::err_erase_line(self);
193 self.needs_clear = false;
194 }
195 }
196
197 pub fn status<T>(&mut self, status: T) -> Result<()>
199 where
200 T: fmt::Display,
201 {
202 self.print(&status, None, Green, false)
203 }
204
205 pub fn status_message<T, U>(&mut self, status: T, message: U) -> Result<()>
207 where
208 T: fmt::Display,
209 U: fmt::Display,
210 {
211 self.print(&status, Some(&message), Green, false)
212 }
213
214 pub fn status_header<T>(&mut self, status: T) -> Result<()>
215 where
216 T: fmt::Display,
217 {
218 self.print(&status, None, Cyan, false)
219 }
220
221 pub fn status_with_color<T, U>(&mut self, status: T, message: U, color: Color) -> Result<()>
223 where
224 T: fmt::Display,
225 U: fmt::Display,
226 {
227 self.print(&status, Some(&message), color, false)
228 }
229
230 pub fn verbose<F>(&mut self, mut callback: F) -> Result<()>
232 where
233 F: FnMut(&mut Shell) -> Result<()>,
234 {
235 match self.verbosity {
236 Verbosity::Verbose => callback(self),
237 _ => Ok(()),
238 }
239 }
240
241 pub fn concise<F>(&mut self, mut callback: F) -> Result<()>
243 where
244 F: FnMut(&mut Shell) -> Result<()>,
245 {
246 match self.verbosity {
247 Verbosity::Verbose => Ok(()),
248 _ => callback(self),
249 }
250 }
251
252 pub fn error<T: fmt::Display>(&mut self, message: T) -> Result<()> {
254 if self.needs_clear {
255 self.err_erase_line();
256 }
257 self.output
258 .message_stderr(&"error", Some(&message), Red, false)
259 }
260
261 pub fn warn<T: fmt::Display>(&mut self, message: T) -> Result<()> {
263 match self.verbosity {
264 Verbosity::Quiet => Ok(()),
265 _ => self.print(&"warning", Some(&message), Yellow, false),
266 }
267 }
268
269 pub fn note<T: fmt::Display>(&mut self, message: T) -> Result<()> {
271 self.print(&"note", Some(&message), Cyan, false)
272 }
273
274 pub fn set_verbosity(&mut self, verbosity: Verbosity) {
276 self.verbosity = verbosity;
277 }
278
279 pub fn verbosity(&self) -> Verbosity {
281 self.verbosity
282 }
283
284 pub fn set_color_choice(&mut self, color: Option<&str>) -> Result<()> {
286 if let ShellOut::Stream {
287 ref mut stdout,
288 ref mut stderr,
289 ref mut color_choice,
290 ..
291 } = self.output
292 {
293 let cfg = match color {
294 Some("always") => ColorChoice::Always,
295 Some("never") => ColorChoice::Never,
296
297 Some("auto") | None => ColorChoice::CargoAuto,
298
299 Some(arg) => return Err(Error::FailedToChooseShellStringColor(arg.to_owned())),
300 };
301 *color_choice = cfg;
302 let choice = cfg.to_termcolor_color_choice();
303 *stdout = StandardStream::stdout(choice);
304 *stderr = StandardStream::stderr(choice);
305 }
306 Ok(())
307 }
308
309 pub fn color_choice(&self) -> ColorChoice {
314 match self.output {
315 ShellOut::Stream { color_choice, .. } => color_choice,
316 ShellOut::Write(_) => ColorChoice::Never,
317 }
318 }
319
320 pub fn err_supports_color(&self) -> bool {
322 match &self.output {
323 ShellOut::Write(_) => false,
324 ShellOut::Stream { stderr, .. } => stderr.supports_color(),
325 }
326 }
327
328 pub fn print_ansi(&mut self, message: &[u8]) -> Result<()> {
330 if self.needs_clear {
331 self.err_erase_line();
332 }
333 #[cfg(windows)]
334 {
335 if let ShellOut::Stream { stderr, .. } = &mut self.output {
336 fwdansi::write_ansi(stderr, message)?;
337 return Ok(());
338 }
339 }
340 self.err().write_all(message)?;
341 Ok(())
342 }
343
344 pub fn print_json<T: serde::ser::Serialize>(&mut self, obj: &T) {
345 let encoded = serde_json::to_string(&obj).unwrap();
346 drop(writeln!(self.out(), "{}", encoded));
347 }
348}
349
350impl Default for Shell {
351 fn default() -> Self {
352 Self::new()
353 }
354}
355
356impl ShellOut {
357 fn message_stderr(
361 &mut self,
362 status: &dyn fmt::Display,
363 message: Option<&dyn fmt::Display>,
364 color: Color,
365 justified: bool,
366 ) -> Result<()> {
367 match *self {
368 ShellOut::Stream { ref mut stderr, .. } => {
369 stderr.reset()?;
370 stderr.set_color(ColorSpec::new().set_bold(true).set_fg(Some(color)))?;
371 if justified {
372 write!(stderr, "{:>12}", status)?;
373 } else {
374 write!(stderr, "{}", status)?;
375 stderr.set_color(ColorSpec::new().set_bold(true))?;
376 if message.is_some() {
377 write!(stderr, ":")?;
378 }
379 }
380 stderr.reset()?;
381 match message {
382 Some(message) => writeln!(stderr, " {}", message)?,
383 None => writeln!(stderr, " ")?,
384 }
385 }
386 ShellOut::Write(ref mut w) => {
387 if justified {
388 write!(w, "{:>12}", status)?;
389 } else {
390 write!(w, "{}:", status)?;
391 }
392 match message {
393 Some(message) => writeln!(w, " {}", message)?,
394 None => write!(w, " ")?,
395 }
396 }
397 }
398 Ok(())
399 }
400
401 fn stdout(&mut self) -> &mut dyn Write {
403 match *self {
404 ShellOut::Stream { ref mut stdout, .. } => stdout,
405 ShellOut::Write(ref mut w) => w,
406 }
407 }
408
409 fn stderr(&mut self) -> &mut dyn Write {
411 match *self {
412 ShellOut::Stream { ref mut stderr, .. } => stderr,
413 ShellOut::Write(ref mut w) => w,
414 }
415 }
416}
417
418impl ColorChoice {
419 fn to_termcolor_color_choice(self) -> termcolor::ColorChoice {
421 match self {
422 ColorChoice::Always => termcolor::ColorChoice::Always,
423 ColorChoice::Never => termcolor::ColorChoice::Never,
424 ColorChoice::CargoAuto => {
425 if atty::is(atty::Stream::Stderr) {
426 termcolor::ColorChoice::Auto
427 } else {
428 termcolor::ColorChoice::Never
429 }
430 }
431 }
432 }
433}
434
435#[cfg(unix)]
436mod imp {
437 use super::{Shell, TtyWidth};
438 use std::mem;
439
440 pub fn stderr_width() -> TtyWidth {
441 unsafe {
442 let mut winsize: libc::winsize = mem::zeroed();
443 if libc::ioctl(libc::STDERR_FILENO, libc::TIOCGWINSZ, &mut winsize) < 0 {
446 return TtyWidth::NoTty;
447 }
448 if winsize.ws_col > 0 {
449 TtyWidth::Known(winsize.ws_col as usize)
450 } else {
451 TtyWidth::NoTty
452 }
453 }
454 }
455
456 pub fn err_erase_line(shell: &mut Shell) {
457 let _ = shell.output.stderr().write_all(b"\x1B[K");
461 }
462}
463
464#[cfg(windows)]
465mod imp {
466 use std::{cmp, mem, ptr};
467 use winapi::um::fileapi::*;
468 use winapi::um::handleapi::*;
469 use winapi::um::processenv::*;
470 use winapi::um::winbase::*;
471 use winapi::um::wincon::*;
472 use winapi::um::winnt::*;
473
474 pub(super) use super::{default_err_erase_line as err_erase_line, TtyWidth};
475
476 pub fn stderr_width() -> TtyWidth {
477 unsafe {
478 let stdout = GetStdHandle(STD_ERROR_HANDLE);
479 let mut csbi: CONSOLE_SCREEN_BUFFER_INFO = mem::zeroed();
480 if GetConsoleScreenBufferInfo(stdout, &mut csbi) != 0 {
481 return TtyWidth::Known((csbi.srWindow.Right - csbi.srWindow.Left) as usize);
482 }
483
484 let h = CreateFileA(
488 "CONOUT$\0".as_ptr() as *const CHAR,
489 GENERIC_READ | GENERIC_WRITE,
490 FILE_SHARE_READ | FILE_SHARE_WRITE,
491 ptr::null_mut(),
492 OPEN_EXISTING,
493 0,
494 ptr::null_mut(),
495 );
496 if h == INVALID_HANDLE_VALUE {
497 return TtyWidth::NoTty;
498 }
499
500 let mut csbi: CONSOLE_SCREEN_BUFFER_INFO = mem::zeroed();
501 let rc = GetConsoleScreenBufferInfo(h, &mut csbi);
502 CloseHandle(h);
503 if rc != 0 {
504 let width = (csbi.srWindow.Right - csbi.srWindow.Left) as usize;
505 return TtyWidth::Guess(cmp::min(60, width));
514 }
515
516 TtyWidth::NoTty
517 }
518 }
519}
520
521#[cfg(windows)]
522fn default_err_erase_line(shell: &mut Shell) {
523 match imp::stderr_width() {
524 TtyWidth::Known(max_width) | TtyWidth::Guess(max_width) => {
525 let blank = " ".repeat(max_width);
526 drop(write!(shell.output.stderr(), "{}\r", blank));
527 }
528 _ => (),
529 }
530}