tari_log4rs/encode/writer/
console.rs

1//! The console writer
2//!
3//! Requires the `console_writer` feature.
4
5use std::{fmt, io};
6
7use crate::encode::{self, Style};
8
9/// An `encode::Write`r that outputs to a console.
10pub struct ConsoleWriter(imp::Writer);
11
12impl ConsoleWriter {
13    /// Returns a new `ConsoleWriter` that will write to standard out.
14    ///
15    /// Returns `None` if standard out is not a console buffer on Windows, and
16    /// if it is not a TTY on Unix.
17    pub fn stdout() -> Option<ConsoleWriter> {
18        imp::Writer::stdout().map(ConsoleWriter)
19    }
20
21    /// Returns a new `ConsoleWriter` that will write to standard error.
22    ///
23    /// Returns `None` if standard error is not a console buffer on Windows, and
24    /// if it is not a TTY on Unix.
25    pub fn stderr() -> Option<ConsoleWriter> {
26        imp::Writer::stderr().map(ConsoleWriter)
27    }
28
29    /// Locks the console, preventing other threads from writing concurrently.
30    pub fn lock(&self) -> ConsoleWriterLock {
31        ConsoleWriterLock(self.0.lock())
32    }
33}
34
35impl io::Write for ConsoleWriter {
36    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
37        self.0.write(buf)
38    }
39
40    fn flush(&mut self) -> io::Result<()> {
41        self.0.flush()
42    }
43
44    fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
45        self.0.write_all(buf)
46    }
47
48    fn write_fmt(&mut self, fmt: fmt::Arguments) -> io::Result<()> {
49        self.0.write_fmt(fmt)
50    }
51}
52
53impl encode::Write for ConsoleWriter {
54    fn set_style(&mut self, style: &Style) -> io::Result<()> {
55        self.0.set_style(style)
56    }
57}
58
59/// An RAII lock over a console.
60pub struct ConsoleWriterLock<'a>(imp::WriterLock<'a>);
61
62impl<'a> io::Write for ConsoleWriterLock<'a> {
63    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
64        self.0.write(buf)
65    }
66
67    fn flush(&mut self) -> io::Result<()> {
68        self.0.flush()
69    }
70
71    fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
72        self.0.write_all(buf)
73    }
74
75    fn write_fmt(&mut self, fmt: fmt::Arguments) -> io::Result<()> {
76        self.0.write_fmt(fmt)
77    }
78}
79
80impl<'a> encode::Write for ConsoleWriterLock<'a> {
81    fn set_style(&mut self, style: &Style) -> io::Result<()> {
82        self.0.set_style(style)
83    }
84}
85
86#[cfg(unix)]
87mod imp {
88    use std::{fmt, io};
89
90    use crate::{
91        encode::{self, writer::ansi::AnsiWriter, Style},
92        priv_io::{StdWriter, StdWriterLock},
93    };
94
95    pub struct Writer(AnsiWriter<StdWriter>);
96
97    impl Writer {
98        pub fn stdout() -> Option<Writer> {
99            if unsafe { libc::isatty(libc::STDOUT_FILENO) } != 1 {
100                return None;
101            }
102
103            Some(Writer(AnsiWriter(StdWriter::stdout())))
104        }
105
106        pub fn stderr() -> Option<Writer> {
107            if unsafe { libc::isatty(libc::STDERR_FILENO) } != 1 {
108                return None;
109            }
110
111            Some(Writer(AnsiWriter(StdWriter::stderr())))
112        }
113
114        pub fn lock(&self) -> WriterLock {
115            WriterLock(AnsiWriter((self.0).0.lock()))
116        }
117    }
118
119    impl io::Write for Writer {
120        fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
121            self.0.write(buf)
122        }
123
124        fn flush(&mut self) -> io::Result<()> {
125            self.0.flush()
126        }
127
128        fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
129            self.0.write_all(buf)
130        }
131
132        fn write_fmt(&mut self, fmt: fmt::Arguments) -> io::Result<()> {
133            self.0.write_fmt(fmt)
134        }
135    }
136
137    impl encode::Write for Writer {
138        fn set_style(&mut self, style: &Style) -> io::Result<()> {
139            self.0.set_style(style)
140        }
141    }
142
143    pub struct WriterLock<'a>(AnsiWriter<StdWriterLock<'a>>);
144
145    impl<'a> io::Write for WriterLock<'a> {
146        fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
147            self.0.write(buf)
148        }
149
150        fn flush(&mut self) -> io::Result<()> {
151            self.0.flush()
152        }
153
154        fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
155            self.0.write_all(buf)
156        }
157
158        fn write_fmt(&mut self, fmt: fmt::Arguments) -> io::Result<()> {
159            self.0.write_fmt(fmt)
160        }
161    }
162
163    impl<'a> encode::Write for WriterLock<'a> {
164        fn set_style(&mut self, style: &Style) -> io::Result<()> {
165            self.0.set_style(style)
166        }
167    }
168}
169
170#[cfg(windows)]
171mod imp {
172    use std::{
173        fmt,
174        io::{self, Write},
175        mem,
176    };
177    use winapi::{
178        shared::minwindef,
179        um::{handleapi, processenv, winbase, wincon, winnt},
180    };
181
182    use crate::{
183        encode::{self, Color, Style},
184        priv_io::{StdWriter, StdWriterLock},
185    };
186
187    struct RawConsole {
188        handle: winnt::HANDLE,
189        defaults: minwindef::WORD,
190    }
191
192    unsafe impl Sync for RawConsole {}
193    unsafe impl Send for RawConsole {}
194
195    impl RawConsole {
196        fn set_style(&self, style: &Style) -> io::Result<()> {
197            let mut attrs = self.defaults;
198
199            if let Some(text) = style.text {
200                attrs &= !((wincon::FOREGROUND_RED
201                    | wincon::FOREGROUND_GREEN
202                    | wincon::FOREGROUND_BLUE) as minwindef::WORD);
203                attrs |= match text {
204                    Color::Black => 0,
205                    Color::Red => wincon::FOREGROUND_RED,
206                    Color::Green => wincon::FOREGROUND_GREEN,
207                    Color::Yellow => wincon::FOREGROUND_RED | wincon::FOREGROUND_GREEN,
208                    Color::Blue => wincon::FOREGROUND_BLUE,
209                    Color::Magenta => wincon::FOREGROUND_RED | wincon::FOREGROUND_BLUE,
210                    Color::Cyan => wincon::FOREGROUND_GREEN | wincon::FOREGROUND_BLUE,
211                    Color::White => {
212                        wincon::FOREGROUND_RED | wincon::FOREGROUND_GREEN | wincon::FOREGROUND_BLUE
213                    }
214                } as minwindef::WORD;
215            }
216
217            if let Some(background) = style.background {
218                attrs &= !((wincon::BACKGROUND_RED
219                    | wincon::BACKGROUND_GREEN
220                    | wincon::BACKGROUND_BLUE) as minwindef::WORD);
221                attrs |= match background {
222                    Color::Black => 0,
223                    Color::Red => wincon::BACKGROUND_RED,
224                    Color::Green => wincon::BACKGROUND_GREEN,
225                    Color::Yellow => wincon::BACKGROUND_RED | wincon::BACKGROUND_GREEN,
226                    Color::Blue => wincon::BACKGROUND_BLUE,
227                    Color::Magenta => wincon::BACKGROUND_RED | wincon::BACKGROUND_BLUE,
228                    Color::Cyan => wincon::BACKGROUND_GREEN | wincon::BACKGROUND_BLUE,
229                    Color::White => {
230                        wincon::BACKGROUND_RED | wincon::BACKGROUND_GREEN | wincon::BACKGROUND_BLUE
231                    }
232                } as minwindef::WORD;
233            }
234
235            if let Some(intense) = style.intense {
236                if intense {
237                    attrs |= wincon::FOREGROUND_INTENSITY as minwindef::WORD;
238                } else {
239                    attrs &= !(wincon::FOREGROUND_INTENSITY as minwindef::WORD);
240                }
241            }
242
243            if unsafe { wincon::SetConsoleTextAttribute(self.handle, attrs) } == 0 {
244                Err(io::Error::last_os_error())
245            } else {
246                Ok(())
247            }
248        }
249    }
250
251    pub struct Writer {
252        console: RawConsole,
253        inner: StdWriter,
254    }
255
256    impl Writer {
257        pub fn stdout() -> Option<Writer> {
258            unsafe {
259                let handle = processenv::GetStdHandle(winbase::STD_OUTPUT_HANDLE);
260                if handle.is_null() || handle == handleapi::INVALID_HANDLE_VALUE {
261                    return None;
262                }
263
264                let mut info = mem::zeroed();
265                if wincon::GetConsoleScreenBufferInfo(handle, &mut info) == 0 {
266                    return None;
267                }
268
269                Some(Writer {
270                    console: RawConsole {
271                        handle,
272                        defaults: info.wAttributes,
273                    },
274                    inner: StdWriter::stdout(),
275                })
276            }
277        }
278
279        pub fn stderr() -> Option<Writer> {
280            unsafe {
281                let handle = processenv::GetStdHandle(winbase::STD_ERROR_HANDLE);
282                if handle.is_null() || handle == handleapi::INVALID_HANDLE_VALUE {
283                    return None;
284                }
285
286                let mut info = mem::zeroed();
287                if wincon::GetConsoleScreenBufferInfo(handle, &mut info) == 0 {
288                    return None;
289                }
290
291                Some(Writer {
292                    console: RawConsole {
293                        handle,
294                        defaults: info.wAttributes,
295                    },
296                    inner: StdWriter::stderr(),
297                })
298            }
299        }
300
301        pub fn lock<'a>(&'a self) -> WriterLock<'a> {
302            WriterLock {
303                console: &self.console,
304                inner: self.inner.lock(),
305            }
306        }
307    }
308
309    impl io::Write for Writer {
310        fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
311            self.inner.write(buf)
312        }
313
314        fn flush(&mut self) -> io::Result<()> {
315            self.inner.flush()
316        }
317
318        fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
319            self.inner.write_all(buf)
320        }
321
322        fn write_fmt(&mut self, fmt: fmt::Arguments) -> io::Result<()> {
323            self.inner.write_fmt(fmt)
324        }
325    }
326
327    impl encode::Write for Writer {
328        fn set_style(&mut self, style: &Style) -> io::Result<()> {
329            self.inner.flush()?;
330            self.console.set_style(style)
331        }
332    }
333
334    pub struct WriterLock<'a> {
335        console: &'a RawConsole,
336        inner: StdWriterLock<'a>,
337    }
338
339    impl<'a> io::Write for WriterLock<'a> {
340        fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
341            self.inner.write(buf)
342        }
343
344        fn flush(&mut self) -> io::Result<()> {
345            self.inner.flush()
346        }
347
348        fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
349            self.inner.write_all(buf)
350        }
351
352        fn write_fmt(&mut self, fmt: fmt::Arguments) -> io::Result<()> {
353            self.inner.write_fmt(fmt)
354        }
355    }
356
357    impl<'a> encode::Write for WriterLock<'a> {
358        fn set_style(&mut self, style: &Style) -> io::Result<()> {
359            self.inner.flush()?;
360            self.console.set_style(style)
361        }
362    }
363}
364
365#[cfg(test)]
366mod test {
367    use std::io::Write;
368
369    use super::*;
370    use crate::encode::{Color, Style, Write as EncodeWrite};
371
372    #[test]
373    fn basic() {
374        let w = match ConsoleWriter::stdout() {
375            Some(w) => w,
376            None => return,
377        };
378        let mut w = w.lock();
379
380        w.write_all(b"normal ").unwrap();
381        w.set_style(
382            Style::new()
383                .text(Color::Red)
384                .background(Color::Blue)
385                .intense(true),
386        )
387        .unwrap();
388        w.write_all(b"styled").unwrap();
389        w.set_style(Style::new().text(Color::Green)).unwrap();
390        w.write_all(b" styled2").unwrap();
391        w.set_style(&Style::new()).unwrap();
392        w.write_all(b" normal\n").unwrap();
393        w.flush().unwrap();
394    }
395}