snowchains 0.7.0

Tools for online programming contests
use indicatif::ProgressDrawTarget;
use snowchains_core::{color_spec, web::StatusCodeColor};
use std::{
    env, fmt,
    io::{self, BufRead, Stdin, StdinLock, Write},
    process::Stdio,
};
use termcolor::{BufferedStandardStream, Color, WriteColor};

pub struct Shell<R, W1, W2> {
    pub stdin: TtyOrPiped<R>,
    pub stdout: W1,
    pub stderr: W2,
    pub stderr_tty: bool,
    pub stdin_process_redirection: fn() -> Stdio,
    pub stdout_process_redirection: fn() -> Stdio,
    pub stderr_process_redirection: fn() -> Stdio,
}

impl<'a> Shell<StdinLock<'a>, BufferedStandardStream, BufferedStandardStream> {
    pub fn new(stdin: &'a Stdin, color: crate::ColorChoice) -> Self {
        let convert_with_atty_fitler = |stream| match (color, atty::is(stream)) {
            (crate::ColorChoice::Auto, true) => termcolor::ColorChoice::Auto,
            (crate::ColorChoice::Always, _) => termcolor::ColorChoice::Always,
            _ => termcolor::ColorChoice::Never,
        };

        Self {
            stdin: TtyOrPiped::auto(stdin),
            stdout: BufferedStandardStream::stdout(convert_with_atty_fitler(atty::Stream::Stdout)),
            stderr: BufferedStandardStream::stderr(convert_with_atty_fitler(atty::Stream::Stderr)),
            stderr_tty: atty::is(atty::Stream::Stderr),
            stdin_process_redirection: Stdio::inherit,
            stdout_process_redirection: Stdio::inherit,
            stderr_process_redirection: Stdio::inherit,
        }
    }
}

impl<R, W1, W2: WriteColor> Shell<R, W1, W2> {
    pub(crate) fn warn(&mut self, message: impl fmt::Display) -> io::Result<()> {
        self.stderr
            .set_color(color_spec!(Bold, Fg(Color::Yellow)))?;
        write!(self.stderr, "warning:")?;
        self.stderr.reset()?;
        writeln!(self.stderr, " {}", message)?;
        self.stderr.flush()
    }
}

impl<R: BufRead, W1, W2: Write> Shell<R, W1, W2> {
    pub(crate) fn read_reply(&mut self, prompt: &'static str) -> io::Result<String> {
        write!(self.stderr, "{}", prompt)?;
        self.stderr.flush()?;
        self.stdin.read_reply()
    }

    pub(crate) fn read_password(&mut self, prompt: &'static str) -> io::Result<String> {
        write!(self.stderr, "{}", prompt)?;
        self.stderr.flush()?;
        self.stdin.read_password()
    }
}

impl<R, W1, W2> Shell<R, W1, W2> {
    pub(crate) fn progress_draw_target(&self) -> ProgressDrawTarget {
        if self.stderr_tty {
            ProgressDrawTarget::stderr()
        } else {
            ProgressDrawTarget::hidden()
        }
    }
}

impl<R, W1, W2: WriteColor> snowchains_core::web::Shell for Shell<R, W1, W2> {
    fn progress_draw_target(&self) -> ProgressDrawTarget {
        self.progress_draw_target()
    }

    fn print_ansi(&mut self, message: &[u8]) -> io::Result<()> {
        fwdansi::write_ansi(&mut self.stderr, message)
    }

    fn warn<T: fmt::Display>(&mut self, message: T) -> io::Result<()> {
        self.warn(message)
    }

    fn on_request(&mut self, req: &reqwest::blocking::Request) -> io::Result<()> {
        self.stderr.set_color(color_spec!(Bold))?;
        write!(self.stderr, "{}", req.method())?;
        self.stderr.reset()?;

        write!(self.stderr, " ")?;

        self.stderr.set_color(color_spec!(Fg(Color::Cyan)))?;
        write!(self.stderr, "{}", req.url())?;
        self.stderr.reset()?;

        write!(self.stderr, " ... ")?;

        self.stderr.flush()
    }

    fn on_response(
        &mut self,
        res: &reqwest::blocking::Response,
        status_code_color: StatusCodeColor,
    ) -> io::Result<()> {
        let fg = match status_code_color {
            StatusCodeColor::Ok => Some(Color::Green),
            StatusCodeColor::Warn => Some(Color::Yellow),
            StatusCodeColor::Error => Some(Color::Red),
            StatusCodeColor::Unknown => None,
        };

        self.stderr.set_color(color_spec!(Bold).set_fg(fg))?;
        write!(self.stderr, "{}", res.status())?;
        self.stderr.reset()?;
        writeln!(self.stderr)?;
        self.stderr.flush()
    }
}

#[derive(Debug)]
pub enum TtyOrPiped<R> {
    Tty,
    Piped(R),
}

impl<'a> TtyOrPiped<StdinLock<'a>> {
    fn auto(stdin: &'a Stdin) -> Self {
        if atty::is(atty::Stream::Stdin) && !(cfg!(windows) && env::var_os("MSYSTEM").is_some()) {
            TtyOrPiped::Tty
        } else {
            TtyOrPiped::Piped(stdin.lock())
        }
    }
}

impl<R: BufRead> TtyOrPiped<R> {
    fn read_reply(&mut self) -> io::Result<String> {
        match self {
            Self::Tty => rprompt::read_reply(),
            Self::Piped(r) => rpassword::read_password_with_reader(Some(r)),
        }
    }

    fn read_password(&mut self) -> io::Result<String> {
        match self {
            Self::Tty => rpassword::read_password_from_tty(None),
            Self::Piped(r) => rpassword::read_password_with_reader(Some(r)),
        }
    }
}