| 1 | use once_cell::sync::OnceCell; |
| 2 | use std::io::{Result, Write}; |
| 3 | use std::sync::{Mutex, MutexGuard, PoisonError}; |
| 4 | use termcolor::{Color, ColorChoice, ColorSpec, StandardStream as Stream, WriteColor}; |
| 5 | |
| 6 | static TERM: OnceCell<Mutex<Term>> = OnceCell::new(); |
| 7 | |
| 8 | pub fn lock() -> MutexGuard<'static, Term> { |
| 9 | TERM.get_or_init(|| Mutex::new(Term::new())) |
| 10 | .lock() |
| 11 | .unwrap_or_else(PoisonError::into_inner) |
| 12 | } |
| 13 | |
| 14 | pub fn bold() { |
| 15 | lock().set_color(ColorSpec::new().set_bold(true)); |
| 16 | } |
| 17 | |
| 18 | pub fn color(color: Color) { |
| 19 | lock().set_color(ColorSpec::new().set_fg(Some(color))); |
| 20 | } |
| 21 | |
| 22 | pub fn bold_color(color: Color) { |
| 23 | lock().set_color(ColorSpec::new().set_bold(true).set_fg(Some(color))); |
| 24 | } |
| 25 | |
| 26 | pub fn reset() { |
| 27 | lock().reset(); |
| 28 | } |
| 29 | |
| 30 | #[deny (unused_macros)] |
| 31 | macro_rules! print { |
| 32 | ($($args:tt)*) => {{ |
| 33 | use std::io::Write; |
| 34 | let _ = std::write!($crate::term::lock(), $($args)*); |
| 35 | }}; |
| 36 | } |
| 37 | |
| 38 | #[deny (unused_macros)] |
| 39 | macro_rules! println { |
| 40 | ($($args:tt)*) => {{ |
| 41 | use std::io::Write; |
| 42 | let _ = std::writeln!($crate::term::lock(), $($args)*); |
| 43 | }}; |
| 44 | } |
| 45 | |
| 46 | pub struct Term { |
| 47 | spec: ColorSpec, |
| 48 | stream: Stream, |
| 49 | start_of_line: bool, |
| 50 | } |
| 51 | |
| 52 | impl Term { |
| 53 | fn new() -> Self { |
| 54 | Term { |
| 55 | spec: ColorSpec::new(), |
| 56 | stream: Stream::stderr(ColorChoice::Auto), |
| 57 | start_of_line: true, |
| 58 | } |
| 59 | } |
| 60 | |
| 61 | fn set_color(&mut self, spec: &ColorSpec) { |
| 62 | if self.spec != *spec { |
| 63 | self.spec = spec.clone(); |
| 64 | self.start_of_line = true; |
| 65 | } |
| 66 | } |
| 67 | |
| 68 | fn reset(&mut self) { |
| 69 | self.spec = ColorSpec::new(); |
| 70 | let _ = self.stream.reset(); |
| 71 | } |
| 72 | } |
| 73 | |
| 74 | impl Write for Term { |
| 75 | // Color one line at a time because Travis does not preserve color setting |
| 76 | // across output lines. |
| 77 | fn write(&mut self, mut buf: &[u8]) -> Result<usize> { |
| 78 | if self.spec.is_none() { |
| 79 | return self.stream.write(buf); |
| 80 | } |
| 81 | |
| 82 | let len = buf.len(); |
| 83 | while !buf.is_empty() { |
| 84 | if self.start_of_line { |
| 85 | let _ = self.stream.set_color(&self.spec); |
| 86 | } |
| 87 | match buf.iter().position(|byte| *byte == b' \n' ) { |
| 88 | Some(line_len) => { |
| 89 | self.stream.write_all(&buf[..line_len + 1])?; |
| 90 | self.start_of_line = true; |
| 91 | buf = &buf[line_len + 1..]; |
| 92 | } |
| 93 | None => { |
| 94 | self.stream.write_all(buf)?; |
| 95 | self.start_of_line = false; |
| 96 | break; |
| 97 | } |
| 98 | } |
| 99 | } |
| 100 | Ok(len) |
| 101 | } |
| 102 | |
| 103 | fn flush(&mut self) -> Result<()> { |
| 104 | self.stream.flush() |
| 105 | } |
| 106 | } |
| 107 | |