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::SubstrateResult;
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_clr = ColorChoice::CargoAuto;
100 Shell {
101 output: ShellOut::Stream {
102 stdout: StandardStream::stdout(
103 auto_clr.to_termcolor_color_choice(atty::Stream::Stdout),
104 ),
105 stderr: StandardStream::stderr(
106 auto_clr.to_termcolor_color_choice(atty::Stream::Stderr),
107 ),
108 color_choice: ColorChoice::CargoAuto,
109 stderr_tty: atty::is(atty::Stream::Stderr),
110 },
111 verbosity: Verbosity::Verbose,
112 needs_clear: false,
113 }
114 }
115
116 pub fn from_write(out: Box<dyn Write>) -> Shell {
118 Shell {
119 output: ShellOut::Write(out),
120 verbosity: Verbosity::Verbose,
121 needs_clear: false,
122 }
123 }
124
125 fn print(
128 &mut self,
129 status: &dyn fmt::Display,
130 message: Option<&dyn fmt::Display>,
131 color: Color,
132 justified: bool,
133 ) -> SubstrateResult<()> {
134 match self.verbosity {
135 Verbosity::Quiet => Ok(()),
136 _ => {
137 if self.needs_clear {
138 self.err_erase_line();
139 }
140 self.output
141 .message_stderr(status, message, color, justified)
142 }
143 }
144 }
145
146 pub fn set_needs_clear(&mut self, needs_clear: bool) {
148 self.needs_clear = needs_clear;
149 }
150
151 pub fn is_cleared(&self) -> bool {
153 !self.needs_clear
154 }
155
156 pub fn err_width(&self) -> TtyWidth {
158 match self.output {
159 ShellOut::Stream {
160 stderr_tty: true, ..
161 } => imp::stderr_width(),
162 _ => TtyWidth::NoTty,
163 }
164 }
165
166 pub fn is_err_tty(&self) -> bool {
168 match self.output {
169 ShellOut::Stream { stderr_tty, .. } => stderr_tty,
170 _ => false,
171 }
172 }
173
174 pub fn out(&mut self) -> &mut dyn Write {
176 if self.needs_clear {
177 self.err_erase_line();
178 }
179 self.output.stdout()
180 }
181
182 pub fn err(&mut self) -> &mut dyn Write {
184 if self.needs_clear {
185 self.err_erase_line();
186 }
187 self.output.stderr()
188 }
189
190 pub fn err_erase_line(&mut self) {
192 if self.err_supports_color() {
193 imp::err_erase_line(self);
194 self.needs_clear = false;
195 }
196 }
197
198 pub fn status<T, U>(&mut self, status: T, message: U) -> SubstrateResult<()>
200 where
201 T: fmt::Display,
202 U: fmt::Display,
203 {
204 self.print(&status, Some(&message), Green, true)
205 }
206
207 pub fn status_header<T>(&mut self, status: T) -> SubstrateResult<()>
208 where
209 T: fmt::Display,
210 {
211 self.print(&status, None, Cyan, true)
212 }
213
214 pub fn status_with_color<T, U>(
216 &mut self,
217 status: T,
218 message: U,
219 color: Color,
220 ) -> SubstrateResult<()>
221 where
222 T: fmt::Display,
223 U: fmt::Display,
224 {
225 self.print(&status, Some(&message), color, true)
226 }
227
228 pub fn verbose<F>(&mut self, mut callback: F) -> SubstrateResult<()>
230 where
231 F: FnMut(&mut Shell) -> SubstrateResult<()>,
232 {
233 match self.verbosity {
234 Verbosity::Verbose => callback(self),
235 _ => Ok(()),
236 }
237 }
238
239 pub fn concise<F>(&mut self, mut callback: F) -> SubstrateResult<()>
241 where
242 F: FnMut(&mut Shell) -> SubstrateResult<()>,
243 {
244 match self.verbosity {
245 Verbosity::Verbose => Ok(()),
246 _ => callback(self),
247 }
248 }
249
250 pub fn error<T: fmt::Display>(&mut self, message: T) -> SubstrateResult<()> {
252 if self.needs_clear {
253 self.err_erase_line();
254 }
255 self.output
256 .message_stderr(&"error", Some(&message), Red, false)
257 }
258
259 pub fn warn<T: fmt::Display>(&mut self, message: T) -> SubstrateResult<()> {
261 match self.verbosity {
262 Verbosity::Quiet => Ok(()),
263 _ => self.print(&"warning", Some(&message), Yellow, false),
264 }
265 }
266
267 pub fn note<T: fmt::Display>(&mut self, message: T) -> SubstrateResult<()> {
269 self.print(&"note", Some(&message), Cyan, false)
270 }
271
272 pub fn set_verbosity(&mut self, verbosity: Verbosity) {
274 self.verbosity = verbosity;
275 }
276
277 pub fn verbosity(&self) -> Verbosity {
279 self.verbosity
280 }
281
282 pub fn set_color_choice(&mut self, color: Option<&str>) -> SubstrateResult<()> {
284 if let ShellOut::Stream {
285 ref mut stdout,
286 ref mut stderr,
287 ref mut color_choice,
288 ..
289 } = self.output
290 {
291 let cfg = match color {
292 Some("always") => ColorChoice::Always,
293 Some("never") => ColorChoice::Never,
294
295 Some("auto") | None => ColorChoice::CargoAuto,
296
297 Some(arg) => anyhow::bail!(
298 "argument for --color must be auto, always, or \
299 never, but found `{}`",
300 arg
301 ),
302 };
303 *color_choice = cfg;
304 *stdout = StandardStream::stdout(cfg.to_termcolor_color_choice(atty::Stream::Stdout));
305 *stderr = StandardStream::stderr(cfg.to_termcolor_color_choice(atty::Stream::Stderr));
306 }
307 Ok(())
308 }
309
310 pub fn color_choice(&self) -> ColorChoice {
315 match self.output {
316 ShellOut::Stream { color_choice, .. } => color_choice,
317 ShellOut::Write(_) => ColorChoice::Never,
318 }
319 }
320
321 pub fn err_supports_color(&self) -> bool {
323 match &self.output {
324 ShellOut::Write(_) => false,
325 ShellOut::Stream { stderr, .. } => stderr.supports_color(),
326 }
327 }
328
329 pub fn out_supports_color(&self) -> bool {
330 match &self.output {
331 ShellOut::Write(_) => false,
332 ShellOut::Stream { stdout, .. } => stdout.supports_color(),
333 }
334 }
335
336 pub fn write_stdout(
340 &mut self,
341 fragment: impl fmt::Display,
342 color: &ColorSpec,
343 ) -> SubstrateResult<()> {
344 self.output.write_stdout(fragment, color)
345 }
346
347 pub fn write_stderr(
351 &mut self,
352 fragment: impl fmt::Display,
353 color: &ColorSpec,
354 ) -> SubstrateResult<()> {
355 self.output.write_stderr(fragment, color)
356 }
357
358 pub fn print_ansi_stderr(&mut self, message: &[u8]) -> SubstrateResult<()> {
360 if self.needs_clear {
361 self.err_erase_line();
362 }
363 #[cfg(windows)]
364 {
365 if let ShellOut::Stream { stderr, .. } = &mut self.output {
366 ::fwdansi::write_ansi(stderr, message)?;
367 return Ok(());
368 }
369 }
370 self.err().write_all(message)?;
371 Ok(())
372 }
373
374 pub fn print_ansi_stdout(&mut self, message: &[u8]) -> SubstrateResult<()> {
376 if self.needs_clear {
377 self.err_erase_line();
378 }
379 #[cfg(windows)]
380 {
381 if let ShellOut::Stream { stdout, .. } = &mut self.output {
382 ::fwdansi::write_ansi(stdout, message)?;
383 return Ok(());
384 }
385 }
386 self.out().write_all(message)?;
387 Ok(())
388 }
389
390 pub fn print_json<T: serde::ser::Serialize>(&mut self, obj: &T) -> SubstrateResult<()> {
391 let encoded = serde_json::to_string(&obj)?;
393 drop(writeln!(self.out(), "{}", encoded));
395 Ok(())
396 }
397}
398
399impl Default for Shell {
400 fn default() -> Self {
401 Self::new()
402 }
403}
404
405impl ShellOut {
406 fn message_stderr(
410 &mut self,
411 status: &dyn fmt::Display,
412 message: Option<&dyn fmt::Display>,
413 color: Color,
414 justified: bool,
415 ) -> SubstrateResult<()> {
416 match *self {
417 ShellOut::Stream { ref mut stderr, .. } => {
418 stderr.reset()?;
419 stderr.set_color(ColorSpec::new().set_bold(true).set_fg(Some(color)))?;
420 if justified {
421 write!(stderr, "{:>12}", status)?;
422 } else {
423 write!(stderr, "{}", status)?;
424 stderr.set_color(ColorSpec::new().set_bold(true))?;
425 write!(stderr, ":")?;
426 }
427 stderr.reset()?;
428 match message {
429 Some(message) => writeln!(stderr, " {}", message)?,
430 None => write!(stderr, " ")?,
431 }
432 }
433 ShellOut::Write(ref mut w) => {
434 if justified {
435 write!(w, "{:>12}", status)?;
436 } else {
437 write!(w, "{}:", status)?;
438 }
439 match message {
440 Some(message) => writeln!(w, " {}", message)?,
441 None => write!(w, " ")?,
442 }
443 }
444 }
445 Ok(())
446 }
447
448 fn write_stdout(
450 &mut self,
451 fragment: impl fmt::Display,
452 color: &ColorSpec,
453 ) -> SubstrateResult<()> {
454 match *self {
455 ShellOut::Stream { ref mut stdout, .. } => {
456 stdout.reset()?;
457 stdout.set_color(&color)?;
458 write!(stdout, "{}", fragment)?;
459 stdout.reset()?;
460 }
461 ShellOut::Write(ref mut w) => {
462 write!(w, "{}", fragment)?;
463 }
464 }
465 Ok(())
466 }
467
468 fn write_stderr(
470 &mut self,
471 fragment: impl fmt::Display,
472 color: &ColorSpec,
473 ) -> SubstrateResult<()> {
474 match *self {
475 ShellOut::Stream { ref mut stderr, .. } => {
476 stderr.reset()?;
477 stderr.set_color(&color)?;
478 write!(stderr, "{}", fragment)?;
479 stderr.reset()?;
480 }
481 ShellOut::Write(ref mut w) => {
482 write!(w, "{}", fragment)?;
483 }
484 }
485 Ok(())
486 }
487
488 fn stdout(&mut self) -> &mut dyn Write {
490 match *self {
491 ShellOut::Stream { ref mut stdout, .. } => stdout,
492 ShellOut::Write(ref mut w) => w,
493 }
494 }
495
496 fn stderr(&mut self) -> &mut dyn Write {
498 match *self {
499 ShellOut::Stream { ref mut stderr, .. } => stderr,
500 ShellOut::Write(ref mut w) => w,
501 }
502 }
503}
504
505impl ColorChoice {
506 fn to_termcolor_color_choice(self, stream: atty::Stream) -> termcolor::ColorChoice {
508 match self {
509 ColorChoice::Always => termcolor::ColorChoice::Always,
510 ColorChoice::Never => termcolor::ColorChoice::Never,
511 ColorChoice::CargoAuto => {
512 if atty::is(stream) {
513 termcolor::ColorChoice::Auto
514 } else {
515 termcolor::ColorChoice::Never
516 }
517 }
518 }
519 }
520}
521
522#[cfg(unix)]
523mod imp {
524 use super::{Shell, TtyWidth};
525 use std::mem;
526
527 pub fn stderr_width() -> TtyWidth {
528 unsafe {
529 let mut winsize: libc::winsize = mem::zeroed();
530 if libc::ioctl(libc::STDERR_FILENO, libc::TIOCGWINSZ.into(), &mut winsize) < 0 {
533 return TtyWidth::NoTty;
534 }
535 if winsize.ws_col > 0 {
536 TtyWidth::Known(winsize.ws_col as usize)
537 } else {
538 TtyWidth::NoTty
539 }
540 }
541 }
542
543 pub fn err_erase_line(shell: &mut Shell) {
544 let _ = shell.output.stderr().write_all(b"\x1B[K");
548 }
549}
550
551#[cfg(windows)]
552mod imp {
553 use std::{cmp, mem, ptr};
554 use winapi::um::fileapi::*;
555 use winapi::um::handleapi::*;
556 use winapi::um::processenv::*;
557 use winapi::um::winbase::*;
558 use winapi::um::wincon::*;
559 use winapi::um::winnt::*;
560
561 pub(super) use super::{default_err_erase_line as err_erase_line, TtyWidth};
562
563 pub fn stderr_width() -> TtyWidth {
564 unsafe {
565 let stdout = GetStdHandle(STD_ERROR_HANDLE);
566 let mut csbi: CONSOLE_SCREEN_BUFFER_INFO = mem::zeroed();
567 if GetConsoleScreenBufferInfo(stdout, &mut csbi) != 0 {
568 return TtyWidth::Known((csbi.srWindow.Right - csbi.srWindow.Left) as usize);
569 }
570
571 let h = CreateFileA(
575 "CONOUT$\0".as_ptr() as *const CHAR,
576 GENERIC_READ | GENERIC_WRITE,
577 FILE_SHARE_READ | FILE_SHARE_WRITE,
578 ptr::null_mut(),
579 OPEN_EXISTING,
580 0,
581 ptr::null_mut(),
582 );
583 if h == INVALID_HANDLE_VALUE {
584 return TtyWidth::NoTty;
585 }
586
587 let mut csbi: CONSOLE_SCREEN_BUFFER_INFO = mem::zeroed();
588 let rc = GetConsoleScreenBufferInfo(h, &mut csbi);
589 CloseHandle(h);
590 if rc != 0 {
591 let width = (csbi.srWindow.Right - csbi.srWindow.Left) as usize;
592 return TtyWidth::Guess(cmp::min(60, width));
601 }
602
603 TtyWidth::NoTty
604 }
605 }
606}
607
608#[cfg(windows)]
609fn default_err_erase_line(shell: &mut Shell) {
610 match imp::stderr_width() {
611 TtyWidth::Known(max_width) | TtyWidth::Guess(max_width) => {
612 let blank = " ".repeat(max_width);
613 drop(write!(shell.output.stderr(), "{}\r", blank));
614 }
615 _ => (),
616 }
617}