2 use std
::io
::prelude
::*;
5 use termcolor
::Color
::{Cyan, Green, Red, Yellow}
;
6 use termcolor
::{self, Color, ColorSpec, StandardStream, WriteColor}
;
8 use crate::util
::errors
::CargoResult
;
10 /// The requested verbosity of output.
11 #[derive(Debug, Clone, Copy, PartialEq)]
18 /// An abstraction around a `Write`able object that remembers preferences for output verbosity and
21 /// the `Write`able object, either with or without color support (represented by different enum
24 /// How verbose messages should be
26 /// Flag that indicates the current line needs to be cleared before
27 /// printing. Used when a progress bar is currently displayed.
31 impl fmt
::Debug
for Shell
{
32 fn fmt(&self, f
: &mut fmt
::Formatter
<'_
>) -> fmt
::Result
{
34 ShellOut
::Write(_
) => f
35 .debug_struct("Shell")
36 .field("verbosity", &self.verbosity
)
38 ShellOut
::Stream { color_choice, .. }
=> f
39 .debug_struct("Shell")
40 .field("verbosity", &self.verbosity
)
41 .field("color_choice", &color_choice
)
47 /// A `Write`able object, either with or without color support
49 /// A plain write object without color support
50 Write(Box
<dyn Write
>),
51 /// Color-enabled stdio, with information on whether color should be used
53 stream
: StandardStream
,
55 color_choice
: ColorChoice
,
59 /// Whether messages should use color output
60 #[derive(Debug, PartialEq, Clone, Copy)]
61 pub enum ColorChoice
{
62 /// Force color output
64 /// Force disable color output
66 /// Intelligently guess whether to use color output
71 /// Creates a new shell (color choice and verbosity), defaulting to 'auto' color and verbose
73 pub fn new() -> Shell
{
75 err
: ShellOut
::Stream
{
76 stream
: StandardStream
::stderr(ColorChoice
::CargoAuto
.to_termcolor_color_choice()),
77 color_choice
: ColorChoice
::CargoAuto
,
78 tty
: atty
::is(atty
::Stream
::Stderr
),
80 verbosity
: Verbosity
::Verbose
,
85 /// Creates a shell from a plain writable object, with no color, and max verbosity.
86 pub fn from_write(out
: Box
<dyn Write
>) -> Shell
{
88 err
: ShellOut
::Write(out
),
89 verbosity
: Verbosity
::Verbose
,
94 /// Prints a message, where the status will have `color` color, and can be justified. The
95 /// messages follows without color.
98 status
: &dyn fmt
::Display
,
99 message
: Option
<&dyn fmt
::Display
>,
102 ) -> CargoResult
<()> {
103 match self.verbosity
{
104 Verbosity
::Quiet
=> Ok(()),
106 if self.needs_clear
{
107 self.err_erase_line();
109 self.err
.print(status
, message
, color
, justified
)
114 pub fn stdout_println(&mut self, message
: impl fmt
::Display
) {
115 if self.needs_clear
{
116 self.err_erase_line();
118 println
!("{}", message
);
121 /// Sets whether the next print should clear the current line.
122 pub fn set_needs_clear(&mut self, needs_clear
: bool
) {
123 self.needs_clear
= needs_clear
;
126 /// Returns `true` if the `needs_clear` flag is unset.
127 pub fn is_cleared(&self) -> bool
{
131 /// Returns the width of the terminal in spaces, if any.
132 pub fn err_width(&self) -> Option
<usize> {
134 ShellOut
::Stream { tty: true, .. }
=> imp
::stderr_width(),
139 /// Returns `true` if stderr is a tty.
140 pub fn is_err_tty(&self) -> bool
{
142 ShellOut
::Stream { tty, .. }
=> tty
,
147 /// Gets a reference to the underlying writer.
148 pub fn err(&mut self) -> &mut dyn Write
{
149 if self.needs_clear
{
150 self.err_erase_line();
155 /// Erase from cursor to end of line.
156 pub fn err_erase_line(&mut self) {
157 if let ShellOut
::Stream { tty: true, .. }
= self.err
{
158 imp
::err_erase_line(self);
159 self.needs_clear
= false;
163 /// Shortcut to right-align and color green a status message.
164 pub fn status
<T
, U
>(&mut self, status
: T
, message
: U
) -> CargoResult
<()>
169 self.print(&status
, Some(&message
), Green
, true)
172 pub fn status_header
<T
>(&mut self, status
: T
) -> CargoResult
<()>
176 self.print(&status
, None
, Cyan
, true)
179 /// Shortcut to right-align a status message.
180 pub fn status_with_color
<T
, U
>(
190 self.print(&status
, Some(&message
), color
, true)
193 /// Runs the callback only if we are in verbose mode.
194 pub fn verbose
<F
>(&mut self, mut callback
: F
) -> CargoResult
<()>
196 F
: FnMut(&mut Shell
) -> CargoResult
<()>,
198 match self.verbosity
{
199 Verbosity
::Verbose
=> callback(self),
204 /// Runs the callback if we are not in verbose mode.
205 pub fn concise
<F
>(&mut self, mut callback
: F
) -> CargoResult
<()>
207 F
: FnMut(&mut Shell
) -> CargoResult
<()>,
209 match self.verbosity
{
210 Verbosity
::Verbose
=> Ok(()),
215 /// Prints a red 'error' message.
216 pub fn error
<T
: fmt
::Display
>(&mut self, message
: T
) -> CargoResult
<()> {
217 if self.needs_clear
{
218 self.err_erase_line();
220 self.err
.print(&"error", Some(&message
), Red
, false)
223 /// Prints an amber 'warning' message.
224 pub fn warn
<T
: fmt
::Display
>(&mut self, message
: T
) -> CargoResult
<()> {
225 match self.verbosity
{
226 Verbosity
::Quiet
=> Ok(()),
227 _
=> self.print(&"warning", Some(&message
), Yellow
, false),
231 /// Updates the verbosity of the shell.
232 pub fn set_verbosity(&mut self, verbosity
: Verbosity
) {
233 self.verbosity
= verbosity
;
236 /// Gets the verbosity of the shell.
237 pub fn verbosity(&self) -> Verbosity
{
241 /// Updates the color choice (always, never, or auto) from a string..
242 pub fn set_color_choice(&mut self, color
: Option
<&str>) -> CargoResult
<()> {
243 if let ShellOut
::Stream
{
245 ref mut color_choice
,
249 let cfg
= match color
{
250 Some("always") => ColorChoice
::Always
,
251 Some("never") => ColorChoice
::Never
,
253 Some("auto") | None
=> ColorChoice
::CargoAuto
,
255 Some(arg
) => anyhow
::bail
!(
256 "argument for --color must be auto, always, or \
257 never, but found `{}`",
262 *stream
= StandardStream
::stderr(cfg
.to_termcolor_color_choice());
267 /// Gets the current color choice.
269 /// If we are not using a color stream, this will always return `Never`, even if the color
270 /// choice has been set to something else.
271 pub fn color_choice(&self) -> ColorChoice
{
273 ShellOut
::Stream { color_choice, .. }
=> color_choice
,
274 ShellOut
::Write(_
) => ColorChoice
::Never
,
278 /// Whether the shell supports color.
279 pub fn supports_color(&self) -> bool
{
281 ShellOut
::Write(_
) => false,
282 ShellOut
::Stream { stream, .. }
=> stream
.supports_color(),
286 /// Prints a message and translates ANSI escape code into console colors.
287 pub fn print_ansi(&mut self, message
: &[u8]) -> CargoResult
<()> {
288 if self.needs_clear
{
289 self.err_erase_line();
293 if let ShellOut
::Stream { stream, .. }
= &mut self.err
{
294 ::fwdansi
::write_ansi(stream
, message
)?
;
298 self.err().write_all(message
)?
;
303 impl Default
for Shell
{
304 fn default() -> Self {
310 /// Prints out a message with a status. The status comes first, and is bold plus the given
311 /// color. The status can be justified, in which case the max width that will right align is
315 status
: &dyn fmt
::Display
,
316 message
: Option
<&dyn fmt
::Display
>,
319 ) -> CargoResult
<()> {
321 ShellOut
::Stream { ref mut stream, .. }
=> {
323 stream
.set_color(ColorSpec
::new().set_bold(true).set_fg(Some(color
)))?
;
325 write
!(stream
, "{:>12}", status
)?
;
327 write
!(stream
, "{}", status
)?
;
328 stream
.set_color(ColorSpec
::new().set_bold(true))?
;
329 write
!(stream
, ":")?
;
333 Some(message
) => writeln
!(stream
, " {}", message
)?
,
334 None
=> write
!(stream
, " ")?
,
337 ShellOut
::Write(ref mut w
) => {
339 write
!(w
, "{:>12}", status
)?
;
341 write
!(w
, "{}:", status
)?
;
344 Some(message
) => writeln
!(w
, " {}", message
)?
,
345 None
=> write
!(w
, " ")?
,
352 /// Gets this object as a `io::Write`.
353 fn as_write(&mut self) -> &mut dyn Write
{
355 ShellOut
::Stream { ref mut stream, .. }
=> stream
,
356 ShellOut
::Write(ref mut w
) => w
,
362 /// Converts our color choice to termcolor's version.
363 fn to_termcolor_color_choice(self) -> termcolor
::ColorChoice
{
365 ColorChoice
::Always
=> termcolor
::ColorChoice
::Always
,
366 ColorChoice
::Never
=> termcolor
::ColorChoice
::Never
,
367 ColorChoice
::CargoAuto
=> {
368 if atty
::is(atty
::Stream
::Stderr
) {
369 termcolor
::ColorChoice
::Auto
371 termcolor
::ColorChoice
::Never
378 #[cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))]
386 pub fn stderr_width() -> Option
<usize> {
388 let mut winsize
: libc
::winsize
= mem
::zeroed();
389 // The .into() here is needed for FreeBSD which defines TIOCGWINSZ
390 // as c_uint but ioctl wants c_ulong.
391 if libc
::ioctl(libc
::STDERR_FILENO
, libc
::TIOCGWINSZ
.into(), &mut winsize
) < 0 {
394 if winsize
.ws_col
> 0 {
395 Some(winsize
.ws_col
as usize)
402 pub fn err_erase_line(shell
: &mut Shell
) {
403 // This is the "EL - Erase in Line" sequence. It clears from the cursor
404 // to the end of line.
405 // https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_sequences
406 let _
= shell
.err
.as_write().write_all(b
"\x1B[K");
412 not(any(target_os
= "linux", target_os
= "macos", target_os
= "freebsd"))
415 pub(super) use super::default_err_erase_line
as err_erase_line
;
417 pub fn stderr_width() -> Option
<usize> {
424 use std
::{cmp, mem, ptr}
;
425 use winapi
::um
::fileapi
::*;
426 use winapi
::um
::handleapi
::*;
427 use winapi
::um
::processenv
::*;
428 use winapi
::um
::winbase
::*;
429 use winapi
::um
::wincon
::*;
430 use winapi
::um
::winnt
::*;
432 pub(super) use super::default_err_erase_line
as err_erase_line
;
434 pub fn stderr_width() -> Option
<usize> {
436 let stdout
= GetStdHandle(STD_ERROR_HANDLE
);
437 let mut csbi
: CONSOLE_SCREEN_BUFFER_INFO
= mem
::zeroed();
438 if GetConsoleScreenBufferInfo(stdout
, &mut csbi
) != 0 {
439 return Some((csbi
.srWindow
.Right
- csbi
.srWindow
.Left
) as usize);
442 // On mintty/msys/cygwin based terminals, the above fails with
443 // INVALID_HANDLE_VALUE. Use an alternate method which works
444 // in that case as well.
446 "CONOUT$\0".as_ptr() as *const CHAR
,
447 GENERIC_READ
| GENERIC_WRITE
,
448 FILE_SHARE_READ
| FILE_SHARE_WRITE
,
454 if h
== INVALID_HANDLE_VALUE
{
458 let mut csbi
: CONSOLE_SCREEN_BUFFER_INFO
= mem
::zeroed();
459 let rc
= GetConsoleScreenBufferInfo(h
, &mut csbi
);
462 let width
= (csbi
.srWindow
.Right
- csbi
.srWindow
.Left
) as usize;
463 // Unfortunately cygwin/mintty does not set the size of the
464 // backing console to match the actual window size. This
465 // always reports a size of 80 or 120 (not sure what
466 // determines that). Use a conservative max of 60 which should
467 // work in most circumstances. ConEmu does some magic to
468 // resize the console correctly, but there's no reasonable way
469 // to detect which kind of terminal we are running in, or if
470 // GetConsoleScreenBufferInfo returns accurate information.
471 return Some(cmp
::min(60, width
));
481 not(any(target_os
= "linux", target_os
= "macos", target_os
= "freebsd"))
485 fn default_err_erase_line(shell
: &mut Shell
) {
486 if let Some(max_width
) = imp
::stderr_width() {
487 let blank
= " ".repeat(max_width
);
488 drop(write
!(shell
.err
.as_write(), "{}\r", blank
));