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}