cargo-vet 0.3.1

Supply-chain security for Rust
//! Types for abstracting over communicating with the user, with support for
//! mocking in tests.
//!
//! In general, this type should be preferred over directly writing to stdout or
//! stderr.

use crate::editor::Editor;
use console::{Style, Term};
use indicatif::{MultiProgress, ProgressBar, ProgressDrawTarget, ProgressStyle};
use lazy_static::lazy_static;
use std::{borrow::Cow, fmt, fs::File, io, mem, time::Duration};

/// Object-safe extension of `std::io::Write` with extra features for
/// interacting with the terminal. Can be mocked in tests to allow them to test
/// other features.
pub trait Out: Send + Sync + 'static {
    /// Write to the output.
    fn write(&self, buf: &[u8]) -> io::Result<usize>;

    /// Write to the output
    fn write_fmt(&self, args: fmt::Arguments<'_>) {
        struct AsWrite<'a, T: ?Sized>(&'a T);
        impl<'a, T: ?Sized + Out> io::Write for AsWrite<'a, T> {
            fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
                Out::write(self.0, buf)
            }

            fn flush(&mut self) -> io::Result<()> {
                Ok(())
            }
        }

        io::Write::write_fmt(&mut AsWrite(self), args).unwrap();
    }

    /// Check if this output is a real terminal.
    fn is_term(&self) -> bool {
        false
    }

    /// If the user is interacting through a terminal, clear the screen.
    /// Should fail silently if the screen cannot be cleared.
    fn clear_screen(&self) -> io::Result<()> {
        Ok(())
    }

    /// Ask the user a question, and read in a line with the user's response. If
    /// there's no user able to respond, an error will be returned instead.
    fn read_line_with_prompt(&self, _prompt: &str) -> io::Result<String> {
        Err(io::ErrorKind::Unsupported.into())
    }

    /// Get a `Style` object which can be used to style text written to this
    /// user. Defaults to a disabled style object.
    fn style(&self) -> Style {
        Style::new().force_styling(false)
    }

    /// Create an editor to prompt the user with.
    /// Exists primarily to allow tests to mock out this feature, and block
    /// editor usage when using a file output.
    fn editor<'a>(&'a self, _name: &'a str) -> io::Result<Editor<'a>> {
        Err(io::ErrorKind::Unsupported.into())
    }
}

// "Real" user-facing terminal on stdout
impl Out for Term {
    fn write(&self, buf: &[u8]) -> io::Result<usize> {
        // XXX: Consider suspending the MultiProgress when writing if we ever
        // want to write to `Out` while a progress bar is rendering.
        io::Write::write(&mut &*self, buf)
    }

    fn is_term(&self) -> bool {
        self.is_term()
    }

    fn clear_screen(&self) -> io::Result<()> {
        self.clear_screen()
    }

    fn read_line_with_prompt(&self, prompt: &str) -> io::Result<String> {
        self.write_str(prompt)?;
        self.flush()?;
        self.read_line()
    }

    fn style(&self) -> Style {
        self.style()
    }

    fn editor(&self, name: &str) -> io::Result<Editor<'_>> {
        Editor::new(name)
    }
}

// File based output with no special features.
impl Out for File {
    fn write(&self, buf: &[u8]) -> io::Result<usize> {
        io::Write::write(&mut &*self, buf)
    }
}

impl io::Write for &'_ dyn Out {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        Out::write(*self, buf)
    }

    fn flush(&mut self) -> io::Result<()> {
        Ok(())
    }
}

lazy_static! {
    /// The global `MultiProgress` which can be used to write out progress reports.
    ///
    /// By default, this is invisible so that it isn't shown during tests, but
    /// it will be configured to be visible early during main.
    pub static ref MULTIPROGRESS: MultiProgress =
        MultiProgress::with_draw_target(ProgressDrawTarget::hidden());
}

/// Helper for bracketing some region with an indeterminate spinner which shows
/// no meaningful progress.
pub fn indeterminate_spinner(
    prefix: impl Into<Cow<'static, str>>,
    message: impl Into<Cow<'static, str>>,
) -> ProgressBar {
    let progress_bar = MULTIPROGRESS.add(
        ProgressBar::new_spinner()
            .with_style(
                ProgressStyle::with_template("{prefix:>12.cyan.bold} {msg} {spinner}").unwrap(),
            )
            .with_prefix(prefix)
            .with_message(message),
    );
    progress_bar.enable_steady_tick(Duration::from_millis(100));
    progress_bar
}

/// Create a new progress bar with a cargo-inspired style.
pub fn progress_bar(
    prefix: impl Into<Cow<'static, str>>,
    message: impl Into<Cow<'static, str>>,
    len: u64,
) -> ProgressBar {
    let progress_bar = MULTIPROGRESS.add(
        ProgressBar::new(len)
            .with_style(
                ProgressStyle::with_template("{prefix:>12.cyan.bold} {msg} [{bar:57}] {pos}/{len}")
                    .unwrap()
                    .progress_chars("=> "),
            )
            .with_prefix(prefix)
            .with_message(message),
    );
    progress_bar.tick();
    progress_bar
}

/// Helper guard object to increment progress for the given progress bar by the
/// given amount when this object is destroyed.
pub struct IncProgressOnDrop<'a>(pub &'a ProgressBar, pub u64);
impl Drop for IncProgressOnDrop<'_> {
    fn drop(&mut self) {
        self.0.inc(self.1);
    }
}

/// A helper type which implements `io::Write`, and will buffer up input, then
/// suspend the MultiProgress and write it all out when dropped to avoid
/// tearing.
///
/// This is used for tracing logs when no log file is specified.
pub struct StderrLogWriter {
    buffer: Vec<u8>,
}

impl StderrLogWriter {
    pub fn new() -> Self {
        StderrLogWriter { buffer: Vec::new() }
    }
}

impl io::Write for StderrLogWriter {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        io::Write::write(&mut self.buffer, buf)
    }

    fn flush(&mut self) -> io::Result<()> {
        let buffer = mem::take(&mut self.buffer);
        MULTIPROGRESS.suspend(|| io::stderr().write_all(&buffer))
    }
}

impl Drop for StderrLogWriter {
    fn drop(&mut self) {
        let _ = io::Write::flush(self);
    }
}