Skip to main content

cargo_equip/
shell.rs

1use std::{
2    fmt,
3    io::{self, Sink, Write},
4};
5use termcolor::{Color, ColorSpec, NoColor, StandardStream, WriteColor};
6
7pub struct Shell {
8    output: ShellOut,
9}
10
11impl Shell {
12    pub fn new() -> Self {
13        Self {
14            output: ShellOut::stream(),
15        }
16    }
17
18    pub fn from_stdout(stdout: Box<dyn Write>) -> Shell {
19        Self {
20            output: ShellOut::write(stdout),
21        }
22    }
23
24    pub(crate) fn out(&mut self) -> &mut dyn Write {
25        match &mut self.output {
26            ShellOut::Stream { stdout, .. } => stdout,
27            ShellOut::Write { stdout, .. } => stdout,
28        }
29    }
30
31    pub fn err(&mut self) -> &mut dyn Write {
32        match &mut self.output {
33            ShellOut::Stream { stderr, .. } => stderr,
34            ShellOut::Write { stderr, .. } => stderr,
35        }
36    }
37
38    pub(crate) fn status(
39        &mut self,
40        status: impl fmt::Display,
41        message: impl fmt::Display,
42    ) -> io::Result<()> {
43        self.print(status, message, Color::Green, true)
44    }
45
46    pub(crate) fn warn(&mut self, message: impl fmt::Display) -> io::Result<()> {
47        self.print("warning", message, Color::Yellow, false)
48    }
49
50    pub fn error(&mut self, message: impl fmt::Display) -> io::Result<()> {
51        self.print("error", message, Color::Red, false)
52    }
53
54    fn print(
55        &mut self,
56        status: impl fmt::Display,
57        message: impl fmt::Display,
58        color: Color,
59        justified: bool,
60    ) -> io::Result<()> {
61        return match &mut self.output {
62            ShellOut::Stream { stderr, .. } => print(stderr, status, message, color, justified),
63            ShellOut::Write { .. } => {
64                print(NoColor::new(io::sink()), status, message, color, justified)
65            }
66        };
67
68        fn print(
69            mut stderr: impl WriteColor,
70            status: impl fmt::Display,
71            message: impl fmt::Display,
72            color: Color,
73            justified: bool,
74        ) -> io::Result<()> {
75            stderr.set_color(ColorSpec::new().set_bold(true).set_fg(Some(color)))?;
76            if justified {
77                write!(stderr, "{:>12}", status)?;
78            } else {
79                write!(stderr, "{}", status)?;
80                stderr.set_color(ColorSpec::new().set_bold(true))?;
81                write!(stderr, ":")?;
82            }
83            stderr.reset()?;
84            writeln!(stderr, " {}", message)
85        }
86    }
87}
88
89impl Default for Shell {
90    fn default() -> Self {
91        Self::new()
92    }
93}
94
95enum ShellOut {
96    Stream {
97        stdout: StandardStream,
98        stderr: StandardStream,
99    },
100    Write {
101        stdout: Box<dyn Write>,
102        stderr: Sink,
103    },
104}
105
106impl ShellOut {
107    fn stream() -> Self {
108        Self::Stream {
109            stdout: StandardStream::stdout(if atty::is(atty::Stream::Stdout) {
110                termcolor::ColorChoice::Auto
111            } else {
112                termcolor::ColorChoice::Never
113            }),
114            stderr: StandardStream::stderr(if atty::is(atty::Stream::Stderr) {
115                termcolor::ColorChoice::Auto
116            } else {
117                termcolor::ColorChoice::Never
118            }),
119        }
120    }
121
122    fn write(stdout: Box<dyn Write>) -> Self {
123        Self::Write {
124            stdout,
125            stderr: io::sink(),
126        }
127    }
128}