rune/cli/
out.rs

1use std::fmt;
2use std::io::{self, Write};
3
4use crate::termcolor::{self, ColorSpec, StandardStream, WriteColor};
5
6pub(super) enum Stream {
7    Stdout,
8    Stderr,
9}
10
11impl Stream {
12    fn find<'a>(
13        &self,
14        stdout: &'a mut StandardStream,
15        stderr: &'a mut StandardStream,
16    ) -> &'a mut StandardStream {
17        match self {
18            Stream::Stdout => stdout,
19            Stream::Stderr => stderr,
20        }
21    }
22}
23
24pub(super) enum Color {
25    Error,
26    Passed,
27    Ignore,
28    Highlight,
29    Important,
30    Progress,
31}
32
33impl Color {
34    fn find<'a>(&self, colors: &'a Colors) -> &'a ColorSpec {
35        match self {
36            Color::Error => &colors.error,
37            Color::Passed => &colors.passed,
38            Color::Ignore => &colors.ignored,
39            Color::Highlight => &colors.highlight,
40            Color::Important => &colors.important,
41            Color::Progress => &colors.progress,
42        }
43    }
44}
45
46pub(super) struct Io<'io> {
47    pub(super) stdout: &'io mut StandardStream,
48    pub(super) stderr: &'io mut StandardStream,
49    colors: Option<Colors>,
50}
51
52impl<'io> Io<'io> {
53    pub(super) fn new(stdout: &'io mut StandardStream, stderr: &'io mut StandardStream) -> Self {
54        Self {
55            stdout,
56            stderr,
57            colors: None,
58        }
59    }
60
61    pub(super) fn with_color(
62        &mut self,
63        stream: Stream,
64        color: Color,
65    ) -> io::Result<&mut ColorStream> {
66        let stream = stream.find(self.stdout, self.stderr);
67        let colors = self.colors.get_or_insert_with(Colors::new);
68        stream.set_color(color.find(colors))?;
69        Ok(ColorStream::new(stream))
70    }
71
72    pub(super) fn section(
73        &mut self,
74        title: impl fmt::Display,
75        stream: Stream,
76        color: Color,
77    ) -> io::Result<Section<'_>> {
78        let io = stream.find(self.stdout, self.stderr);
79        let colors = self.colors.get_or_insert_with(Colors::new);
80
81        io.set_color(color.find(colors))?;
82        write!(io, "{title:>12}")?;
83        io.reset()?;
84
85        Ok(Section { io, colors })
86    }
87
88    pub(super) fn write(
89        &mut self,
90        title: impl fmt::Display,
91        stream: Stream,
92        color: Color,
93    ) -> io::Result<()> {
94        let stream = stream.find(self.stdout, self.stderr);
95        let colors = self.colors.get_or_insert_with(Colors::new);
96
97        stream.set_color(color.find(colors))?;
98        write!(stream, "{title}")?;
99        stream.reset()?;
100        Ok(())
101    }
102}
103
104#[derive(Default)]
105struct Colors {
106    error: ColorSpec,
107    passed: ColorSpec,
108    highlight: ColorSpec,
109    important: ColorSpec,
110    progress: ColorSpec,
111    ignored: ColorSpec,
112}
113
114impl Colors {
115    fn new() -> Self {
116        let mut this = Self::default();
117        this.error.set_fg(Some(termcolor::Color::Red));
118        this.passed.set_fg(Some(termcolor::Color::Green));
119        this.highlight.set_fg(Some(termcolor::Color::Green));
120        this.highlight.set_bold(true);
121        this.important.set_fg(Some(termcolor::Color::White));
122        this.important.set_bold(true);
123        this.progress.set_fg(Some(termcolor::Color::Cyan));
124        this.progress.set_bold(true);
125        this.ignored.set_fg(Some(termcolor::Color::Yellow));
126        this.ignored.set_bold(true);
127        this
128    }
129}
130
131pub(super) struct Section<'a> {
132    pub(super) io: &'a mut StandardStream,
133    colors: &'a Colors,
134}
135
136impl Section<'_> {
137    pub(super) fn append(&mut self, text: impl fmt::Display) -> io::Result<&mut Self> {
138        write!(self.io, "{text}")?;
139        Ok(self)
140    }
141
142    /// Flush the current section.
143    pub(super) fn flush(&mut self) -> io::Result<&mut Self> {
144        self.io.flush()?;
145        Ok(self)
146    }
147
148    pub(super) fn append_with(
149        &mut self,
150        text: impl fmt::Display,
151        color: Color,
152    ) -> io::Result<&mut Self> {
153        self.io.set_color(color.find(self.colors))?;
154        write!(self.io, "{text}")?;
155        self.io.reset()?;
156        Ok(self)
157    }
158
159    pub(super) fn error(&mut self, text: impl fmt::Display) -> io::Result<&mut Self> {
160        self.append_with(text, Color::Error)?;
161        Ok(self)
162    }
163
164    pub(super) fn passed(&mut self, text: impl fmt::Display) -> io::Result<&mut Self> {
165        self.append_with(text, Color::Passed)?;
166        Ok(self)
167    }
168
169    pub(super) fn close(&mut self) -> io::Result<()> {
170        writeln!(self.io)?;
171        Ok(())
172    }
173}
174
175#[repr(transparent)]
176pub(super) struct ColorStream(StandardStream);
177
178impl ColorStream {
179    fn new(io: &mut StandardStream) -> &mut Self {
180        unsafe { &mut *(io as *mut StandardStream as *mut Self) }
181    }
182
183    pub(super) fn close(&mut self) -> io::Result<()> {
184        self.0.reset()
185    }
186}
187
188impl io::Write for ColorStream {
189    #[inline]
190    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
191        self.0.write(buf)
192    }
193
194    #[inline]
195    fn flush(&mut self) -> io::Result<()> {
196        self.0.flush()
197    }
198}