1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
use std::{
    fmt,
    io::{self, Write},
};
use termcolor::{Color, ColorSpec, StandardStream, WriteColor as _};

pub struct Shell {
    output: ShellOut,
}

impl Shell {
    pub fn new() -> Self {
        Self {
            output: ShellOut::stream(),
        }
    }

    pub(crate) fn out(&mut self) -> &mut dyn Write {
        let ShellOut::Stream { stdout, .. } = &mut self.output;
        stdout
    }

    pub fn err(&mut self) -> &mut dyn Write {
        let ShellOut::Stream { stderr, .. } = &mut self.output;
        stderr
    }

    pub(crate) fn status(
        &mut self,
        status: impl fmt::Display,
        message: impl fmt::Display,
    ) -> io::Result<()> {
        self.print(status, message, Color::Green, true)
    }

    pub(crate) fn warn(&mut self, message: impl fmt::Display) -> io::Result<()> {
        self.print("warning", message, Color::Yellow, false)
    }

    pub fn error(&mut self, message: impl fmt::Display) -> io::Result<()> {
        self.print("error", message, Color::Red, false)
    }

    fn print(
        &mut self,
        status: impl fmt::Display,
        message: impl fmt::Display,
        color: Color,
        justified: bool,
    ) -> io::Result<()> {
        let ShellOut::Stream { stderr, .. } = &mut self.output;
        stderr.set_color(ColorSpec::new().set_bold(true).set_fg(Some(color)))?;
        if justified {
            write!(stderr, "{:>12}", status)?;
        } else {
            write!(stderr, "{}", status)?;
            stderr.set_color(ColorSpec::new().set_bold(true))?;
            write!(stderr, ":")?;
        }
        stderr.reset()?;
        writeln!(stderr, " {}", message)
    }
}

impl Default for Shell {
    fn default() -> Self {
        Self::new()
    }
}

enum ShellOut {
    Stream {
        stdout: StandardStream,
        stderr: StandardStream,
    },
}

impl ShellOut {
    fn stream() -> Self {
        Self::Stream {
            stdout: StandardStream::stdout(if atty::is(atty::Stream::Stdout) {
                termcolor::ColorChoice::Auto
            } else {
                termcolor::ColorChoice::Never
            }),
            stderr: StandardStream::stderr(if atty::is(atty::Stream::Stderr) {
                termcolor::ColorChoice::Auto
            } else {
                termcolor::ColorChoice::Never
            }),
        }
    }
}