use std::fmt;
use std::io::prelude::*;
use atty;
use termcolor::Color::{Cyan, Green, Red, Yellow};
use termcolor::{self, Color, ColorSpec, StandardStream, WriteColor};
use crate::util::errors::CargoResult;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Verbosity {
Verbose,
Normal,
Quiet,
}
pub struct Shell {
err: ShellOut,
verbosity: Verbosity,
needs_clear: bool,
}
impl fmt::Debug for Shell {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.err {
ShellOut::Write(_) => f
.debug_struct("Shell")
.field("verbosity", &self.verbosity)
.finish(),
ShellOut::Stream { color_choice, .. } => f
.debug_struct("Shell")
.field("verbosity", &self.verbosity)
.field("color_choice", &color_choice)
.finish(),
}
}
}
enum ShellOut {
Write(Box<dyn Write>),
Stream {
stream: StandardStream,
tty: bool,
color_choice: ColorChoice,
},
}
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum ColorChoice {
Always,
Never,
CargoAuto,
}
impl Shell {
pub fn new() -> Shell {
Shell {
err: ShellOut::Stream {
stream: StandardStream::stderr(ColorChoice::CargoAuto.to_termcolor_color_choice()),
color_choice: ColorChoice::CargoAuto,
tty: atty::is(atty::Stream::Stderr),
},
verbosity: Verbosity::Verbose,
needs_clear: false,
}
}
pub fn from_write(out: Box<dyn Write>) -> Shell {
Shell {
err: ShellOut::Write(out),
verbosity: Verbosity::Verbose,
needs_clear: false,
}
}
fn print(
&mut self,
status: &dyn fmt::Display,
message: Option<&dyn fmt::Display>,
color: Color,
justified: bool,
) -> CargoResult<()> {
match self.verbosity {
Verbosity::Quiet => Ok(()),
_ => {
if self.needs_clear {
self.err_erase_line();
}
self.err.print(status, message, color, justified)
}
}
}
pub fn stdout_println(&mut self, message: impl fmt::Display) {
if self.needs_clear {
self.err_erase_line();
}
println!("{}", message);
}
pub fn set_needs_clear(&mut self, needs_clear: bool) {
self.needs_clear = needs_clear;
}
pub fn is_cleared(&self) -> bool {
!self.needs_clear
}
pub fn err_width(&self) -> Option<usize> {
match self.err {
ShellOut::Stream { tty: true, .. } => imp::stderr_width(),
_ => None,
}
}
pub fn is_err_tty(&self) -> bool {
match self.err {
ShellOut::Stream { tty, .. } => tty,
_ => false,
}
}
pub fn err(&mut self) -> &mut dyn Write {
if self.needs_clear {
self.err_erase_line();
}
self.err.as_write()
}
pub fn err_erase_line(&mut self) {
if let ShellOut::Stream { tty: true, .. } = self.err {
imp::err_erase_line(self);
self.needs_clear = false;
}
}
pub fn status<T, U>(&mut self, status: T, message: U) -> CargoResult<()>
where
T: fmt::Display,
U: fmt::Display,
{
self.print(&status, Some(&message), Green, true)
}
pub fn status_header<T>(&mut self, status: T) -> CargoResult<()>
where
T: fmt::Display,
{
self.print(&status, None, Cyan, true)
}
pub fn status_with_color<T, U>(
&mut self,
status: T,
message: U,
color: Color,
) -> CargoResult<()>
where
T: fmt::Display,
U: fmt::Display,
{
self.print(&status, Some(&message), color, true)
}
pub fn verbose<F>(&mut self, mut callback: F) -> CargoResult<()>
where
F: FnMut(&mut Shell) -> CargoResult<()>,
{
match self.verbosity {
Verbosity::Verbose => callback(self),
_ => Ok(()),
}
}
pub fn concise<F>(&mut self, mut callback: F) -> CargoResult<()>
where
F: FnMut(&mut Shell) -> CargoResult<()>,
{
match self.verbosity {
Verbosity::Verbose => Ok(()),
_ => callback(self),
}
}
pub fn error<T: fmt::Display>(&mut self, message: T) -> CargoResult<()> {
if self.needs_clear {
self.err_erase_line();
}
self.err.print(&"error", Some(&message), Red, false)
}
pub fn warn<T: fmt::Display>(&mut self, message: T) -> CargoResult<()> {
match self.verbosity {
Verbosity::Quiet => Ok(()),
_ => self.print(&"warning", Some(&message), Yellow, false),
}
}
pub fn set_verbosity(&mut self, verbosity: Verbosity) {
self.verbosity = verbosity;
}
pub fn verbosity(&self) -> Verbosity {
self.verbosity
}
pub fn set_color_choice(&mut self, color: Option<&str>) -> CargoResult<()> {
if let ShellOut::Stream {
ref mut stream,
ref mut color_choice,
..
} = self.err
{
let cfg = match color {
Some("always") => ColorChoice::Always,
Some("never") => ColorChoice::Never,
Some("auto") | None => ColorChoice::CargoAuto,
Some(arg) => failure::bail!(
"argument for --color must be auto, always, or \
never, but found `{}`",
arg
),
};
*color_choice = cfg;
*stream = StandardStream::stderr(cfg.to_termcolor_color_choice());
}
Ok(())
}
pub fn color_choice(&self) -> ColorChoice {
match self.err {
ShellOut::Stream { color_choice, .. } => color_choice,
ShellOut::Write(_) => ColorChoice::Never,
}
}
pub fn supports_color(&self) -> bool {
match &self.err {
ShellOut::Write(_) => false,
ShellOut::Stream { stream, .. } => stream.supports_color(),
}
}
pub fn print_ansi(&mut self, message: &[u8]) -> CargoResult<()> {
if self.needs_clear {
self.err_erase_line();
}
#[cfg(windows)]
{
if let ShellOut::Stream { stream, .. } = &mut self.err {
::fwdansi::write_ansi(stream, message)?;
return Ok(());
}
}
self.err().write_all(message)?;
Ok(())
}
}
impl Default for Shell {
fn default() -> Self {
Self::new()
}
}
impl ShellOut {
fn print(
&mut self,
status: &dyn fmt::Display,
message: Option<&dyn fmt::Display>,
color: Color,
justified: bool,
) -> CargoResult<()> {
match *self {
ShellOut::Stream { ref mut stream, .. } => {
stream.reset()?;
stream.set_color(ColorSpec::new().set_bold(true).set_fg(Some(color)))?;
if justified {
write!(stream, "{:>12}", status)?;
} else {
write!(stream, "{}", status)?;
stream.set_color(ColorSpec::new().set_bold(true))?;
write!(stream, ":")?;
}
stream.reset()?;
match message {
Some(message) => writeln!(stream, " {}", message)?,
None => write!(stream, " ")?,
}
}
ShellOut::Write(ref mut w) => {
if justified {
write!(w, "{:>12}", status)?;
} else {
write!(w, "{}:", status)?;
}
match message {
Some(message) => writeln!(w, " {}", message)?,
None => write!(w, " ")?,
}
}
}
Ok(())
}
fn as_write(&mut self) -> &mut dyn Write {
match *self {
ShellOut::Stream { ref mut stream, .. } => stream,
ShellOut::Write(ref mut w) => w,
}
}
}
impl ColorChoice {
fn to_termcolor_color_choice(self) -> termcolor::ColorChoice {
match self {
ColorChoice::Always => termcolor::ColorChoice::Always,
ColorChoice::Never => termcolor::ColorChoice::Never,
ColorChoice::CargoAuto => {
if atty::is(atty::Stream::Stderr) {
termcolor::ColorChoice::Auto
} else {
termcolor::ColorChoice::Never
}
}
}
}
}
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))]
mod imp {
use std::mem;
use libc;
use super::Shell;
pub fn stderr_width() -> Option<usize> {
unsafe {
let mut winsize: libc::winsize = mem::zeroed();
if libc::ioctl(libc::STDERR_FILENO, libc::TIOCGWINSZ.into(), &mut winsize) < 0 {
return None;
}
if winsize.ws_col > 0 {
Some(winsize.ws_col as usize)
} else {
None
}
}
}
pub fn err_erase_line(shell: &mut Shell) {
let _ = shell.err.as_write().write_all(b"\x1B[K");
}
}
#[cfg(all(
unix,
not(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))
))]
mod imp {
pub(super) use super::default_err_erase_line as err_erase_line;
pub fn stderr_width() -> Option<usize> {
None
}
}
#[cfg(windows)]
mod imp {
use std::{cmp, mem, ptr};
use winapi::um::fileapi::*;
use winapi::um::handleapi::*;
use winapi::um::processenv::*;
use winapi::um::winbase::*;
use winapi::um::wincon::*;
use winapi::um::winnt::*;
pub(super) use super::default_err_erase_line as err_erase_line;
pub fn stderr_width() -> Option<usize> {
unsafe {
let stdout = GetStdHandle(STD_ERROR_HANDLE);
let mut csbi: CONSOLE_SCREEN_BUFFER_INFO = mem::zeroed();
if GetConsoleScreenBufferInfo(stdout, &mut csbi) != 0 {
return Some((csbi.srWindow.Right - csbi.srWindow.Left) as usize);
}
let h = CreateFileA(
"CONOUT$\0".as_ptr() as *const CHAR,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
ptr::null_mut(),
OPEN_EXISTING,
0,
ptr::null_mut(),
);
if h == INVALID_HANDLE_VALUE {
return None;
}
let mut csbi: CONSOLE_SCREEN_BUFFER_INFO = mem::zeroed();
let rc = GetConsoleScreenBufferInfo(h, &mut csbi);
CloseHandle(h);
if rc != 0 {
let width = (csbi.srWindow.Right - csbi.srWindow.Left) as usize;
return Some(cmp::min(60, width));
}
None
}
}
}
#[cfg(any(
all(
unix,
not(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))
),
windows,
))]
fn default_err_erase_line(shell: &mut Shell) {
if let Some(max_width) = imp::stderr_width() {
let blank = " ".repeat(max_width);
drop(write!(shell.err.as_write(), "{}\r", blank));
}
}