diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:02:58 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:02:58 +0000 |
commit | 698f8c2f01ea549d77d7dc3338a12e04c11057b9 (patch) | |
tree | 173a775858bd501c378080a10dca74132f05bc50 /vendor/termcolor/src | |
parent | Initial commit. (diff) | |
download | rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.tar.xz rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.zip |
Adding upstream version 1.64.0+dfsg1.upstream/1.64.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/termcolor/src')
-rw-r--r-- | vendor/termcolor/src/lib.rs | 2246 |
1 files changed, 2246 insertions, 0 deletions
diff --git a/vendor/termcolor/src/lib.rs b/vendor/termcolor/src/lib.rs new file mode 100644 index 000000000..735ce97d2 --- /dev/null +++ b/vendor/termcolor/src/lib.rs @@ -0,0 +1,2246 @@ +/*! +This crate provides a cross platform abstraction for writing colored text to +a terminal. Colors are written using either ANSI escape sequences or by +communicating with a Windows console. Much of this API was motivated by use +inside command line applications, where colors or styles can be configured +by the end user and/or the environment. + +This crate also provides platform independent support for writing colored text +to an in memory buffer. While this is easy to do with ANSI escape sequences +(because they are in the buffer themselves), it is trickier to do with the +Windows console API, which requires synchronous communication. + +# Organization + +The `WriteColor` trait extends the `io::Write` trait with methods for setting +colors or resetting them. + +`StandardStream` and `StandardStreamLock` both satisfy `WriteColor` and are +analogous to `std::io::Stdout` and `std::io::StdoutLock`, or `std::io::Stderr` +and `std::io::StderrLock`. + +`Buffer` is an in memory buffer that supports colored text. In a parallel +program, each thread might write to its own buffer. A buffer can be printed to +using a `BufferWriter`. The advantage of this design is that each thread can +work in parallel on a buffer without having to synchronize access to global +resources such as the Windows console. Moreover, this design also prevents +interleaving of buffer output. + +`Ansi` and `NoColor` both satisfy `WriteColor` for arbitrary implementors of +`io::Write`. These types are useful when you know exactly what you need. An +analogous type for the Windows console is not provided since it cannot exist. + +# Example: using `StandardStream` + +The `StandardStream` type in this crate works similarly to `std::io::Stdout`, +except it is augmented with methods for coloring by the `WriteColor` trait. +For example, to write some green text: + +```rust,no_run +# fn test() -> Result<(), Box<::std::error::Error>> { +use std::io::Write; +use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor}; + +let mut stdout = StandardStream::stdout(ColorChoice::Always); +stdout.set_color(ColorSpec::new().set_fg(Some(Color::Green)))?; +writeln!(&mut stdout, "green text!")?; +# Ok(()) } +``` + +Note that any text written to the terminal now will be colored +green when using ANSI escape sequences, even if it is written via +stderr, and even if stderr had previously been set to `Color::Red`. +Users will need to manage any color changes themselves by calling +[`WriteColor::set_color`](trait.WriteColor.html#tymethod.set_color), and this +may include calling [`WriteColor::reset`](trait.WriteColor.html#tymethod.reset) +before the program exits to a shell. + +# Example: using `BufferWriter` + +A `BufferWriter` can create buffers and write buffers to stdout or stderr. It +does *not* implement `io::Write` or `WriteColor` itself. Instead, `Buffer` +implements `io::Write` and `io::WriteColor`. + +This example shows how to print some green text to stderr. + +```rust,no_run +# fn test() -> Result<(), Box<::std::error::Error>> { +use std::io::Write; +use termcolor::{BufferWriter, Color, ColorChoice, ColorSpec, WriteColor}; + +let mut bufwtr = BufferWriter::stderr(ColorChoice::Always); +let mut buffer = bufwtr.buffer(); +buffer.set_color(ColorSpec::new().set_fg(Some(Color::Green)))?; +writeln!(&mut buffer, "green text!")?; +bufwtr.print(&buffer)?; +# Ok(()) } +``` + +# Detecting presence of a terminal + +In many scenarios when using color, one often wants to enable colors +automatically when writing to a terminal and disable colors automatically when +writing to anything else. The typical way to achieve this in Unix environments +is via libc's +[`isatty`](http://man7.org/linux/man-pages/man3/isatty.3.html) +function. +Unfortunately, this notoriously does not work well in Windows environments. To +work around that, the currently recommended solution is to use the +[`atty`](https://crates.io/crates/atty) +crate, which goes out of its way to get this as right as possible in Windows +environments. + +For example, in a command line application that exposes a `--color` flag, +your logic for how to enable colors might look like this: + +```rust,ignore +use atty; +use termcolor::{ColorChoice, StandardStream}; + +let preference = argv.get_flag("color").unwrap_or("auto"); +let choice = match preference { + "always" => ColorChoice::Always, + "ansi" => ColorChoice::AlwaysAnsi, + "auto" => { + if atty::is(atty::Stream::Stdout) { + ColorChoice::Auto + } else { + ColorChoice::Never + } + } + _ => ColorChoice::Never, +}; +let stdout = StandardStream::stdout(choice); +// ... write to stdout +``` + +Currently, `termcolor` does not provide anything to do this for you. +*/ + +#![deny(missing_docs)] + +// #[cfg(doctest)] +// use doc_comment::doctest; +// #[cfg(doctest)] +// doctest!("../README.md"); + +use std::env; +use std::error; +use std::fmt; +use std::io::{self, Write}; +use std::str::FromStr; +use std::sync::atomic::{AtomicBool, Ordering}; +#[cfg(windows)] +use std::sync::{Mutex, MutexGuard}; + +#[cfg(windows)] +use winapi_util::console as wincon; + +/// This trait describes the behavior of writers that support colored output. +pub trait WriteColor: io::Write { + /// Returns true if and only if the underlying writer supports colors. + fn supports_color(&self) -> bool; + + /// Set the color settings of the writer. + /// + /// Subsequent writes to this writer will use these settings until either + /// `reset` is called or new color settings are set. + /// + /// If there was a problem setting the color settings, then an error is + /// returned. + fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()>; + + /// Reset the current color settings to their original settings. + /// + /// If there was a problem resetting the color settings, then an error is + /// returned. + fn reset(&mut self) -> io::Result<()>; + + /// Returns true if and only if the underlying writer must synchronously + /// interact with an end user's device in order to control colors. By + /// default, this always returns `false`. + /// + /// In practice, this should return `true` if the underlying writer is + /// manipulating colors using the Windows console APIs. + /// + /// This is useful for writing generic code (such as a buffered writer) + /// that can perform certain optimizations when the underlying writer + /// doesn't rely on synchronous APIs. For example, ANSI escape sequences + /// can be passed through to the end user's device as is. + fn is_synchronous(&self) -> bool { + false + } +} + +impl<'a, T: ?Sized + WriteColor> WriteColor for &'a mut T { + fn supports_color(&self) -> bool { + (&**self).supports_color() + } + fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> { + (&mut **self).set_color(spec) + } + fn reset(&mut self) -> io::Result<()> { + (&mut **self).reset() + } + fn is_synchronous(&self) -> bool { + (&**self).is_synchronous() + } +} + +impl<T: ?Sized + WriteColor> WriteColor for Box<T> { + fn supports_color(&self) -> bool { + (&**self).supports_color() + } + fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> { + (&mut **self).set_color(spec) + } + fn reset(&mut self) -> io::Result<()> { + (&mut **self).reset() + } + fn is_synchronous(&self) -> bool { + (&**self).is_synchronous() + } +} + +/// ColorChoice represents the color preferences of an end user. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum ColorChoice { + /// Try very hard to emit colors. This includes emitting ANSI colors + /// on Windows if the console API is unavailable. + Always, + /// AlwaysAnsi is like Always, except it never tries to use anything other + /// than emitting ANSI color codes. + AlwaysAnsi, + /// Try to use colors, but don't force the issue. If the console isn't + /// available on Windows, or if TERM=dumb, or if `NO_COLOR` is defined, for + /// example, then don't use colors. + Auto, + /// Never emit colors. + Never, +} + +impl ColorChoice { + /// Returns true if we should attempt to write colored output. + fn should_attempt_color(&self) -> bool { + match *self { + ColorChoice::Always => true, + ColorChoice::AlwaysAnsi => true, + ColorChoice::Never => false, + ColorChoice::Auto => self.env_allows_color(), + } + } + + #[cfg(not(windows))] + fn env_allows_color(&self) -> bool { + match env::var_os("TERM") { + // If TERM isn't set, then we are in a weird environment that + // probably doesn't support colors. + None => return false, + Some(k) => { + if k == "dumb" { + return false; + } + } + } + // If TERM != dumb, then the only way we don't allow colors at this + // point is if NO_COLOR is set. + if env::var_os("NO_COLOR").is_some() { + return false; + } + true + } + + #[cfg(windows)] + fn env_allows_color(&self) -> bool { + // On Windows, if TERM isn't set, then we shouldn't automatically + // assume that colors aren't allowed. This is unlike Unix environments + // where TERM is more rigorously set. + if let Some(k) = env::var_os("TERM") { + if k == "dumb" { + return false; + } + } + // If TERM != dumb, then the only way we don't allow colors at this + // point is if NO_COLOR is set. + if env::var_os("NO_COLOR").is_some() { + return false; + } + true + } + + /// Returns true if this choice should forcefully use ANSI color codes. + /// + /// It's possible that ANSI is still the correct choice even if this + /// returns false. + #[cfg(windows)] + fn should_ansi(&self) -> bool { + match *self { + ColorChoice::Always => false, + ColorChoice::AlwaysAnsi => true, + ColorChoice::Never => false, + ColorChoice::Auto => { + match env::var("TERM") { + Err(_) => false, + // cygwin doesn't seem to support ANSI escape sequences + // and instead has its own variety. However, the Windows + // console API may be available. + Ok(k) => k != "dumb" && k != "cygwin", + } + } + } + } +} + +/// `std::io` implements `Stdout` and `Stderr` (and their `Lock` variants) as +/// separate types, which makes it difficult to abstract over them. We use +/// some simple internal enum types to work around this. + +enum StandardStreamType { + Stdout, + Stderr, + StdoutBuffered, + StderrBuffered, +} + +enum IoStandardStream { + Stdout(io::Stdout), + Stderr(io::Stderr), + StdoutBuffered(io::BufWriter<io::Stdout>), + StderrBuffered(io::BufWriter<io::Stderr>), +} + +impl IoStandardStream { + fn new(sty: StandardStreamType) -> IoStandardStream { + match sty { + StandardStreamType::Stdout => { + IoStandardStream::Stdout(io::stdout()) + } + StandardStreamType::Stderr => { + IoStandardStream::Stderr(io::stderr()) + } + StandardStreamType::StdoutBuffered => { + let wtr = io::BufWriter::new(io::stdout()); + IoStandardStream::StdoutBuffered(wtr) + } + StandardStreamType::StderrBuffered => { + let wtr = io::BufWriter::new(io::stderr()); + IoStandardStream::StderrBuffered(wtr) + } + } + } + + fn lock(&self) -> IoStandardStreamLock<'_> { + match *self { + IoStandardStream::Stdout(ref s) => { + IoStandardStreamLock::StdoutLock(s.lock()) + } + IoStandardStream::Stderr(ref s) => { + IoStandardStreamLock::StderrLock(s.lock()) + } + IoStandardStream::StdoutBuffered(_) + | IoStandardStream::StderrBuffered(_) => { + // We don't permit this case to ever occur in the public API, + // so it's OK to panic. + panic!("cannot lock a buffered standard stream") + } + } + } +} + +impl io::Write for IoStandardStream { + #[inline(always)] + fn write(&mut self, b: &[u8]) -> io::Result<usize> { + match *self { + IoStandardStream::Stdout(ref mut s) => s.write(b), + IoStandardStream::Stderr(ref mut s) => s.write(b), + IoStandardStream::StdoutBuffered(ref mut s) => s.write(b), + IoStandardStream::StderrBuffered(ref mut s) => s.write(b), + } + } + + #[inline(always)] + fn flush(&mut self) -> io::Result<()> { + match *self { + IoStandardStream::Stdout(ref mut s) => s.flush(), + IoStandardStream::Stderr(ref mut s) => s.flush(), + IoStandardStream::StdoutBuffered(ref mut s) => s.flush(), + IoStandardStream::StderrBuffered(ref mut s) => s.flush(), + } + } +} + +// Same rigmarole for the locked variants of the standard streams. + +enum IoStandardStreamLock<'a> { + StdoutLock(io::StdoutLock<'a>), + StderrLock(io::StderrLock<'a>), +} + +impl<'a> io::Write for IoStandardStreamLock<'a> { + #[inline(always)] + fn write(&mut self, b: &[u8]) -> io::Result<usize> { + match *self { + IoStandardStreamLock::StdoutLock(ref mut s) => s.write(b), + IoStandardStreamLock::StderrLock(ref mut s) => s.write(b), + } + } + + #[inline(always)] + fn flush(&mut self) -> io::Result<()> { + match *self { + IoStandardStreamLock::StdoutLock(ref mut s) => s.flush(), + IoStandardStreamLock::StderrLock(ref mut s) => s.flush(), + } + } +} + +/// Satisfies `io::Write` and `WriteColor`, and supports optional coloring +/// to either of the standard output streams, stdout and stderr. +pub struct StandardStream { + wtr: LossyStandardStream<WriterInner<IoStandardStream>>, +} + +/// `StandardStreamLock` is a locked reference to a `StandardStream`. +/// +/// This implements the `io::Write` and `WriteColor` traits, and is constructed +/// via the `Write::lock` method. +/// +/// The lifetime `'a` refers to the lifetime of the corresponding +/// `StandardStream`. +pub struct StandardStreamLock<'a> { + wtr: LossyStandardStream<WriterInnerLock<'a, IoStandardStreamLock<'a>>>, +} + +/// Like `StandardStream`, but does buffered writing. +pub struct BufferedStandardStream { + wtr: LossyStandardStream<WriterInner<IoStandardStream>>, +} + +/// WriterInner is a (limited) generic representation of a writer. It is +/// limited because W should only ever be stdout/stderr on Windows. +enum WriterInner<W> { + NoColor(NoColor<W>), + Ansi(Ansi<W>), + #[cfg(windows)] + Windows { + wtr: W, + console: Mutex<wincon::Console>, + }, +} + +/// WriterInnerLock is a (limited) generic representation of a writer. It is +/// limited because W should only ever be stdout/stderr on Windows. +enum WriterInnerLock<'a, W> { + NoColor(NoColor<W>), + Ansi(Ansi<W>), + /// What a gross hack. On Windows, we need to specify a lifetime for the + /// console when in a locked state, but obviously don't need to do that + /// on Unix, which makes the `'a` unused. To satisfy the compiler, we need + /// a PhantomData. + #[allow(dead_code)] + Unreachable(::std::marker::PhantomData<&'a ()>), + #[cfg(windows)] + Windows { + wtr: W, + console: MutexGuard<'a, wincon::Console>, + }, +} + +impl StandardStream { + /// Create a new `StandardStream` with the given color preferences that + /// writes to standard output. + /// + /// On Windows, if coloring is desired and a Windows console could not be + /// found, then ANSI escape sequences are used instead. + /// + /// The specific color/style settings can be configured when writing via + /// the `WriteColor` trait. + pub fn stdout(choice: ColorChoice) -> StandardStream { + let wtr = WriterInner::create(StandardStreamType::Stdout, choice); + StandardStream { wtr: LossyStandardStream::new(wtr) } + } + + /// Create a new `StandardStream` with the given color preferences that + /// writes to standard error. + /// + /// On Windows, if coloring is desired and a Windows console could not be + /// found, then ANSI escape sequences are used instead. + /// + /// The specific color/style settings can be configured when writing via + /// the `WriteColor` trait. + pub fn stderr(choice: ColorChoice) -> StandardStream { + let wtr = WriterInner::create(StandardStreamType::Stderr, choice); + StandardStream { wtr: LossyStandardStream::new(wtr) } + } + + /// Lock the underlying writer. + /// + /// The lock guard returned also satisfies `io::Write` and + /// `WriteColor`. + /// + /// This method is **not reentrant**. It may panic if `lock` is called + /// while a `StandardStreamLock` is still alive. + pub fn lock(&self) -> StandardStreamLock<'_> { + StandardStreamLock::from_stream(self) + } +} + +impl<'a> StandardStreamLock<'a> { + #[cfg(not(windows))] + fn from_stream(stream: &StandardStream) -> StandardStreamLock<'_> { + let locked = match *stream.wtr.get_ref() { + WriterInner::NoColor(ref w) => { + WriterInnerLock::NoColor(NoColor(w.0.lock())) + } + WriterInner::Ansi(ref w) => { + WriterInnerLock::Ansi(Ansi(w.0.lock())) + } + }; + StandardStreamLock { wtr: stream.wtr.wrap(locked) } + } + + #[cfg(windows)] + fn from_stream(stream: &StandardStream) -> StandardStreamLock { + let locked = match *stream.wtr.get_ref() { + WriterInner::NoColor(ref w) => { + WriterInnerLock::NoColor(NoColor(w.0.lock())) + } + WriterInner::Ansi(ref w) => { + WriterInnerLock::Ansi(Ansi(w.0.lock())) + } + #[cfg(windows)] + WriterInner::Windows { ref wtr, ref console } => { + WriterInnerLock::Windows { + wtr: wtr.lock(), + console: console.lock().unwrap(), + } + } + }; + StandardStreamLock { wtr: stream.wtr.wrap(locked) } + } +} + +impl BufferedStandardStream { + /// Create a new `BufferedStandardStream` with the given color preferences + /// that writes to standard output via a buffered writer. + /// + /// On Windows, if coloring is desired and a Windows console could not be + /// found, then ANSI escape sequences are used instead. + /// + /// The specific color/style settings can be configured when writing via + /// the `WriteColor` trait. + pub fn stdout(choice: ColorChoice) -> BufferedStandardStream { + let wtr = + WriterInner::create(StandardStreamType::StdoutBuffered, choice); + BufferedStandardStream { wtr: LossyStandardStream::new(wtr) } + } + + /// Create a new `BufferedStandardStream` with the given color preferences + /// that writes to standard error via a buffered writer. + /// + /// On Windows, if coloring is desired and a Windows console could not be + /// found, then ANSI escape sequences are used instead. + /// + /// The specific color/style settings can be configured when writing via + /// the `WriteColor` trait. + pub fn stderr(choice: ColorChoice) -> BufferedStandardStream { + let wtr = + WriterInner::create(StandardStreamType::StderrBuffered, choice); + BufferedStandardStream { wtr: LossyStandardStream::new(wtr) } + } +} + +impl WriterInner<IoStandardStream> { + /// Create a new inner writer for a standard stream with the given color + /// preferences. + #[cfg(not(windows))] + fn create( + sty: StandardStreamType, + choice: ColorChoice, + ) -> WriterInner<IoStandardStream> { + if choice.should_attempt_color() { + WriterInner::Ansi(Ansi(IoStandardStream::new(sty))) + } else { + WriterInner::NoColor(NoColor(IoStandardStream::new(sty))) + } + } + + /// Create a new inner writer for a standard stream with the given color + /// preferences. + /// + /// If coloring is desired and a Windows console could not be found, then + /// ANSI escape sequences are used instead. + #[cfg(windows)] + fn create( + sty: StandardStreamType, + choice: ColorChoice, + ) -> WriterInner<IoStandardStream> { + let mut con = match sty { + StandardStreamType::Stdout => wincon::Console::stdout(), + StandardStreamType::Stderr => wincon::Console::stderr(), + StandardStreamType::StdoutBuffered => wincon::Console::stdout(), + StandardStreamType::StderrBuffered => wincon::Console::stderr(), + }; + let is_console_virtual = con + .as_mut() + .map(|con| con.set_virtual_terminal_processing(true).is_ok()) + .unwrap_or(false); + if choice.should_attempt_color() { + if choice.should_ansi() || is_console_virtual { + WriterInner::Ansi(Ansi(IoStandardStream::new(sty))) + } else if let Ok(console) = con { + WriterInner::Windows { + wtr: IoStandardStream::new(sty), + console: Mutex::new(console), + } + } else { + WriterInner::Ansi(Ansi(IoStandardStream::new(sty))) + } + } else { + WriterInner::NoColor(NoColor(IoStandardStream::new(sty))) + } + } +} + +impl io::Write for StandardStream { + #[inline] + fn write(&mut self, b: &[u8]) -> io::Result<usize> { + self.wtr.write(b) + } + + #[inline] + fn flush(&mut self) -> io::Result<()> { + self.wtr.flush() + } +} + +impl WriteColor for StandardStream { + #[inline] + fn supports_color(&self) -> bool { + self.wtr.supports_color() + } + + #[inline] + fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> { + self.wtr.set_color(spec) + } + + #[inline] + fn reset(&mut self) -> io::Result<()> { + self.wtr.reset() + } + + #[inline] + fn is_synchronous(&self) -> bool { + self.wtr.is_synchronous() + } +} + +impl<'a> io::Write for StandardStreamLock<'a> { + #[inline] + fn write(&mut self, b: &[u8]) -> io::Result<usize> { + self.wtr.write(b) + } + + #[inline] + fn flush(&mut self) -> io::Result<()> { + self.wtr.flush() + } +} + +impl<'a> WriteColor for StandardStreamLock<'a> { + #[inline] + fn supports_color(&self) -> bool { + self.wtr.supports_color() + } + + #[inline] + fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> { + self.wtr.set_color(spec) + } + + #[inline] + fn reset(&mut self) -> io::Result<()> { + self.wtr.reset() + } + + #[inline] + fn is_synchronous(&self) -> bool { + self.wtr.is_synchronous() + } +} + +impl io::Write for BufferedStandardStream { + #[inline] + fn write(&mut self, b: &[u8]) -> io::Result<usize> { + self.wtr.write(b) + } + + #[inline] + fn flush(&mut self) -> io::Result<()> { + self.wtr.flush() + } +} + +impl WriteColor for BufferedStandardStream { + #[inline] + fn supports_color(&self) -> bool { + self.wtr.supports_color() + } + + #[inline] + fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> { + if self.is_synchronous() { + self.wtr.flush()?; + } + self.wtr.set_color(spec) + } + + #[inline] + fn reset(&mut self) -> io::Result<()> { + self.wtr.reset() + } + + #[inline] + fn is_synchronous(&self) -> bool { + self.wtr.is_synchronous() + } +} + +impl<W: io::Write> io::Write for WriterInner<W> { + #[inline(always)] + fn write(&mut self, buf: &[u8]) -> io::Result<usize> { + match *self { + WriterInner::NoColor(ref mut wtr) => wtr.write(buf), + WriterInner::Ansi(ref mut wtr) => wtr.write(buf), + #[cfg(windows)] + WriterInner::Windows { ref mut wtr, .. } => wtr.write(buf), + } + } + + #[inline(always)] + fn flush(&mut self) -> io::Result<()> { + match *self { + WriterInner::NoColor(ref mut wtr) => wtr.flush(), + WriterInner::Ansi(ref mut wtr) => wtr.flush(), + #[cfg(windows)] + WriterInner::Windows { ref mut wtr, .. } => wtr.flush(), + } + } +} + +impl<W: io::Write> WriteColor for WriterInner<W> { + fn supports_color(&self) -> bool { + match *self { + WriterInner::NoColor(_) => false, + WriterInner::Ansi(_) => true, + #[cfg(windows)] + WriterInner::Windows { .. } => true, + } + } + + fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> { + match *self { + WriterInner::NoColor(ref mut wtr) => wtr.set_color(spec), + WriterInner::Ansi(ref mut wtr) => wtr.set_color(spec), + #[cfg(windows)] + WriterInner::Windows { ref mut wtr, ref console } => { + wtr.flush()?; + let mut console = console.lock().unwrap(); + spec.write_console(&mut *console) + } + } + } + + fn reset(&mut self) -> io::Result<()> { + match *self { + WriterInner::NoColor(ref mut wtr) => wtr.reset(), + WriterInner::Ansi(ref mut wtr) => wtr.reset(), + #[cfg(windows)] + WriterInner::Windows { ref mut wtr, ref mut console } => { + wtr.flush()?; + console.lock().unwrap().reset()?; + Ok(()) + } + } + } + + fn is_synchronous(&self) -> bool { + match *self { + WriterInner::NoColor(_) => false, + WriterInner::Ansi(_) => false, + #[cfg(windows)] + WriterInner::Windows { .. } => true, + } + } +} + +impl<'a, W: io::Write> io::Write for WriterInnerLock<'a, W> { + fn write(&mut self, buf: &[u8]) -> io::Result<usize> { + match *self { + WriterInnerLock::Unreachable(_) => unreachable!(), + WriterInnerLock::NoColor(ref mut wtr) => wtr.write(buf), + WriterInnerLock::Ansi(ref mut wtr) => wtr.write(buf), + #[cfg(windows)] + WriterInnerLock::Windows { ref mut wtr, .. } => wtr.write(buf), + } + } + + fn flush(&mut self) -> io::Result<()> { + match *self { + WriterInnerLock::Unreachable(_) => unreachable!(), + WriterInnerLock::NoColor(ref mut wtr) => wtr.flush(), + WriterInnerLock::Ansi(ref mut wtr) => wtr.flush(), + #[cfg(windows)] + WriterInnerLock::Windows { ref mut wtr, .. } => wtr.flush(), + } + } +} + +impl<'a, W: io::Write> WriteColor for WriterInnerLock<'a, W> { + fn supports_color(&self) -> bool { + match *self { + WriterInnerLock::Unreachable(_) => unreachable!(), + WriterInnerLock::NoColor(_) => false, + WriterInnerLock::Ansi(_) => true, + #[cfg(windows)] + WriterInnerLock::Windows { .. } => true, + } + } + + fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> { + match *self { + WriterInnerLock::Unreachable(_) => unreachable!(), + WriterInnerLock::NoColor(ref mut wtr) => wtr.set_color(spec), + WriterInnerLock::Ansi(ref mut wtr) => wtr.set_color(spec), + #[cfg(windows)] + WriterInnerLock::Windows { ref mut wtr, ref mut console } => { + wtr.flush()?; + spec.write_console(console) + } + } + } + + fn reset(&mut self) -> io::Result<()> { + match *self { + WriterInnerLock::Unreachable(_) => unreachable!(), + WriterInnerLock::NoColor(ref mut wtr) => wtr.reset(), + WriterInnerLock::Ansi(ref mut wtr) => wtr.reset(), + #[cfg(windows)] + WriterInnerLock::Windows { ref mut wtr, ref mut console } => { + wtr.flush()?; + console.reset()?; + Ok(()) + } + } + } + + fn is_synchronous(&self) -> bool { + match *self { + WriterInnerLock::Unreachable(_) => unreachable!(), + WriterInnerLock::NoColor(_) => false, + WriterInnerLock::Ansi(_) => false, + #[cfg(windows)] + WriterInnerLock::Windows { .. } => true, + } + } +} + +/// Writes colored buffers to stdout or stderr. +/// +/// Writable buffers can be obtained by calling `buffer` on a `BufferWriter`. +/// +/// This writer works with terminals that support ANSI escape sequences or +/// with a Windows console. +/// +/// It is intended for a `BufferWriter` to be put in an `Arc` and written to +/// from multiple threads simultaneously. +pub struct BufferWriter { + stream: LossyStandardStream<IoStandardStream>, + printed: AtomicBool, + separator: Option<Vec<u8>>, + color_choice: ColorChoice, + #[cfg(windows)] + console: Option<Mutex<wincon::Console>>, +} + +impl BufferWriter { + /// Create a new `BufferWriter` that writes to a standard stream with the + /// given color preferences. + /// + /// The specific color/style settings can be configured when writing to + /// the buffers themselves. + #[cfg(not(windows))] + fn create(sty: StandardStreamType, choice: ColorChoice) -> BufferWriter { + BufferWriter { + stream: LossyStandardStream::new(IoStandardStream::new(sty)), + printed: AtomicBool::new(false), + separator: None, + color_choice: choice, + } + } + + /// Create a new `BufferWriter` that writes to a standard stream with the + /// given color preferences. + /// + /// If coloring is desired and a Windows console could not be found, then + /// ANSI escape sequences are used instead. + /// + /// The specific color/style settings can be configured when writing to + /// the buffers themselves. + #[cfg(windows)] + fn create(sty: StandardStreamType, choice: ColorChoice) -> BufferWriter { + let mut con = match sty { + StandardStreamType::Stdout => wincon::Console::stdout(), + StandardStreamType::Stderr => wincon::Console::stderr(), + StandardStreamType::StdoutBuffered => wincon::Console::stdout(), + StandardStreamType::StderrBuffered => wincon::Console::stderr(), + } + .ok(); + let is_console_virtual = con + .as_mut() + .map(|con| con.set_virtual_terminal_processing(true).is_ok()) + .unwrap_or(false); + // If we can enable ANSI on Windows, then we don't need the console + // anymore. + if is_console_virtual { + con = None; + } + let stream = LossyStandardStream::new(IoStandardStream::new(sty)); + BufferWriter { + stream: stream, + printed: AtomicBool::new(false), + separator: None, + color_choice: choice, + console: con.map(Mutex::new), + } + } + + /// Create a new `BufferWriter` that writes to stdout with the given + /// color preferences. + /// + /// On Windows, if coloring is desired and a Windows console could not be + /// found, then ANSI escape sequences are used instead. + /// + /// The specific color/style settings can be configured when writing to + /// the buffers themselves. + pub fn stdout(choice: ColorChoice) -> BufferWriter { + BufferWriter::create(StandardStreamType::Stdout, choice) + } + + /// Create a new `BufferWriter` that writes to stderr with the given + /// color preferences. + /// + /// On Windows, if coloring is desired and a Windows console could not be + /// found, then ANSI escape sequences are used instead. + /// + /// The specific color/style settings can be configured when writing to + /// the buffers themselves. + pub fn stderr(choice: ColorChoice) -> BufferWriter { + BufferWriter::create(StandardStreamType::Stderr, choice) + } + + /// If set, the separator given is printed between buffers. By default, no + /// separator is printed. + /// + /// The default value is `None`. + pub fn separator(&mut self, sep: Option<Vec<u8>>) { + self.separator = sep; + } + + /// Creates a new `Buffer` with the current color preferences. + /// + /// A `Buffer` satisfies both `io::Write` and `WriteColor`. A `Buffer` can + /// be printed using the `print` method. + #[cfg(not(windows))] + pub fn buffer(&self) -> Buffer { + Buffer::new(self.color_choice) + } + + /// Creates a new `Buffer` with the current color preferences. + /// + /// A `Buffer` satisfies both `io::Write` and `WriteColor`. A `Buffer` can + /// be printed using the `print` method. + #[cfg(windows)] + pub fn buffer(&self) -> Buffer { + Buffer::new(self.color_choice, self.console.is_some()) + } + + /// Prints the contents of the given buffer. + /// + /// It is safe to call this from multiple threads simultaneously. In + /// particular, all buffers are written atomically. No interleaving will + /// occur. + pub fn print(&self, buf: &Buffer) -> io::Result<()> { + if buf.is_empty() { + return Ok(()); + } + let mut stream = self.stream.wrap(self.stream.get_ref().lock()); + if let Some(ref sep) = self.separator { + if self.printed.load(Ordering::SeqCst) { + stream.write_all(sep)?; + stream.write_all(b"\n")?; + } + } + match buf.0 { + BufferInner::NoColor(ref b) => stream.write_all(&b.0)?, + BufferInner::Ansi(ref b) => stream.write_all(&b.0)?, + #[cfg(windows)] + BufferInner::Windows(ref b) => { + // We guarantee by construction that we have a console here. + // Namely, a BufferWriter is the only way to produce a Buffer. + let console_mutex = self + .console + .as_ref() + .expect("got Windows buffer but have no Console"); + let mut console = console_mutex.lock().unwrap(); + b.print(&mut *console, &mut stream)?; + } + } + self.printed.store(true, Ordering::SeqCst); + Ok(()) + } +} + +/// Write colored text to memory. +/// +/// `Buffer` is a platform independent abstraction for printing colored text to +/// an in memory buffer. When the buffer is printed using a `BufferWriter`, the +/// color information will be applied to the output device (a tty on Unix and a +/// console on Windows). +/// +/// A `Buffer` is typically created by calling the `BufferWriter.buffer` +/// method, which will take color preferences and the environment into +/// account. However, buffers can also be manually created using `no_color`, +/// `ansi` or `console` (on Windows). +pub struct Buffer(BufferInner); + +/// BufferInner is an enumeration of different buffer types. +enum BufferInner { + /// No coloring information should be applied. This ignores all coloring + /// directives. + NoColor(NoColor<Vec<u8>>), + /// Apply coloring using ANSI escape sequences embedded into the buffer. + Ansi(Ansi<Vec<u8>>), + /// Apply coloring using the Windows console APIs. This buffer saves + /// color information in memory and only interacts with the console when + /// the buffer is printed. + #[cfg(windows)] + Windows(WindowsBuffer), +} + +impl Buffer { + /// Create a new buffer with the given color settings. + #[cfg(not(windows))] + fn new(choice: ColorChoice) -> Buffer { + if choice.should_attempt_color() { + Buffer::ansi() + } else { + Buffer::no_color() + } + } + + /// Create a new buffer with the given color settings. + /// + /// On Windows, one can elect to create a buffer capable of being written + /// to a console. Only enable it if a console is available. + /// + /// If coloring is desired and `console` is false, then ANSI escape + /// sequences are used instead. + #[cfg(windows)] + fn new(choice: ColorChoice, console: bool) -> Buffer { + if choice.should_attempt_color() { + if !console || choice.should_ansi() { + Buffer::ansi() + } else { + Buffer::console() + } + } else { + Buffer::no_color() + } + } + + /// Create a buffer that drops all color information. + pub fn no_color() -> Buffer { + Buffer(BufferInner::NoColor(NoColor(vec![]))) + } + + /// Create a buffer that uses ANSI escape sequences. + pub fn ansi() -> Buffer { + Buffer(BufferInner::Ansi(Ansi(vec![]))) + } + + /// Create a buffer that can be written to a Windows console. + #[cfg(windows)] + pub fn console() -> Buffer { + Buffer(BufferInner::Windows(WindowsBuffer::new())) + } + + /// Returns true if and only if this buffer is empty. + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Returns the length of this buffer in bytes. + pub fn len(&self) -> usize { + match self.0 { + BufferInner::NoColor(ref b) => b.0.len(), + BufferInner::Ansi(ref b) => b.0.len(), + #[cfg(windows)] + BufferInner::Windows(ref b) => b.buf.len(), + } + } + + /// Clears this buffer. + pub fn clear(&mut self) { + match self.0 { + BufferInner::NoColor(ref mut b) => b.0.clear(), + BufferInner::Ansi(ref mut b) => b.0.clear(), + #[cfg(windows)] + BufferInner::Windows(ref mut b) => b.clear(), + } + } + + /// Consume this buffer and return the underlying raw data. + /// + /// On Windows, this unrecoverably drops all color information associated + /// with the buffer. + pub fn into_inner(self) -> Vec<u8> { + match self.0 { + BufferInner::NoColor(b) => b.0, + BufferInner::Ansi(b) => b.0, + #[cfg(windows)] + BufferInner::Windows(b) => b.buf, + } + } + + /// Return the underlying data of the buffer. + pub fn as_slice(&self) -> &[u8] { + match self.0 { + BufferInner::NoColor(ref b) => &b.0, + BufferInner::Ansi(ref b) => &b.0, + #[cfg(windows)] + BufferInner::Windows(ref b) => &b.buf, + } + } + + /// Return the underlying data of the buffer as a mutable slice. + pub fn as_mut_slice(&mut self) -> &mut [u8] { + match self.0 { + BufferInner::NoColor(ref mut b) => &mut b.0, + BufferInner::Ansi(ref mut b) => &mut b.0, + #[cfg(windows)] + BufferInner::Windows(ref mut b) => &mut b.buf, + } + } +} + +impl io::Write for Buffer { + #[inline] + fn write(&mut self, buf: &[u8]) -> io::Result<usize> { + match self.0 { + BufferInner::NoColor(ref mut w) => w.write(buf), + BufferInner::Ansi(ref mut w) => w.write(buf), + #[cfg(windows)] + BufferInner::Windows(ref mut w) => w.write(buf), + } + } + + #[inline] + fn flush(&mut self) -> io::Result<()> { + match self.0 { + BufferInner::NoColor(ref mut w) => w.flush(), + BufferInner::Ansi(ref mut w) => w.flush(), + #[cfg(windows)] + BufferInner::Windows(ref mut w) => w.flush(), + } + } +} + +impl WriteColor for Buffer { + #[inline] + fn supports_color(&self) -> bool { + match self.0 { + BufferInner::NoColor(_) => false, + BufferInner::Ansi(_) => true, + #[cfg(windows)] + BufferInner::Windows(_) => true, + } + } + + #[inline] + fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> { + match self.0 { + BufferInner::NoColor(ref mut w) => w.set_color(spec), + BufferInner::Ansi(ref mut w) => w.set_color(spec), + #[cfg(windows)] + BufferInner::Windows(ref mut w) => w.set_color(spec), + } + } + + #[inline] + fn reset(&mut self) -> io::Result<()> { + match self.0 { + BufferInner::NoColor(ref mut w) => w.reset(), + BufferInner::Ansi(ref mut w) => w.reset(), + #[cfg(windows)] + BufferInner::Windows(ref mut w) => w.reset(), + } + } + + #[inline] + fn is_synchronous(&self) -> bool { + false + } +} + +/// Satisfies `WriteColor` but ignores all color options. +pub struct NoColor<W>(W); + +impl<W: Write> NoColor<W> { + /// Create a new writer that satisfies `WriteColor` but drops all color + /// information. + pub fn new(wtr: W) -> NoColor<W> { + NoColor(wtr) + } + + /// Consume this `NoColor` value and return the inner writer. + pub fn into_inner(self) -> W { + self.0 + } + + /// Return a reference to the inner writer. + pub fn get_ref(&self) -> &W { + &self.0 + } + + /// Return a mutable reference to the inner writer. + pub fn get_mut(&mut self) -> &mut W { + &mut self.0 + } +} + +impl<W: io::Write> io::Write for NoColor<W> { + #[inline] + fn write(&mut self, buf: &[u8]) -> io::Result<usize> { + self.0.write(buf) + } + + #[inline] + fn flush(&mut self) -> io::Result<()> { + self.0.flush() + } +} + +impl<W: io::Write> WriteColor for NoColor<W> { + #[inline] + fn supports_color(&self) -> bool { + false + } + + #[inline] + fn set_color(&mut self, _: &ColorSpec) -> io::Result<()> { + Ok(()) + } + + #[inline] + fn reset(&mut self) -> io::Result<()> { + Ok(()) + } + + #[inline] + fn is_synchronous(&self) -> bool { + false + } +} + +/// Satisfies `WriteColor` using standard ANSI escape sequences. +pub struct Ansi<W>(W); + +impl<W: Write> Ansi<W> { + /// Create a new writer that satisfies `WriteColor` using standard ANSI + /// escape sequences. + pub fn new(wtr: W) -> Ansi<W> { + Ansi(wtr) + } + + /// Consume this `Ansi` value and return the inner writer. + pub fn into_inner(self) -> W { + self.0 + } + + /// Return a reference to the inner writer. + pub fn get_ref(&self) -> &W { + &self.0 + } + + /// Return a mutable reference to the inner writer. + pub fn get_mut(&mut self) -> &mut W { + &mut self.0 + } +} + +impl<W: io::Write> io::Write for Ansi<W> { + #[inline] + fn write(&mut self, buf: &[u8]) -> io::Result<usize> { + self.0.write(buf) + } + + #[inline] + fn flush(&mut self) -> io::Result<()> { + self.0.flush() + } +} + +impl<W: io::Write> WriteColor for Ansi<W> { + #[inline] + fn supports_color(&self) -> bool { + true + } + + #[inline] + fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> { + if spec.reset { + self.reset()?; + } + if spec.bold { + self.write_str("\x1B[1m")?; + } + if spec.dimmed { + self.write_str("\x1B[2m")?; + } + if spec.italic { + self.write_str("\x1B[3m")?; + } + if spec.underline { + self.write_str("\x1B[4m")?; + } + if let Some(ref c) = spec.fg_color { + self.write_color(true, c, spec.intense)?; + } + if let Some(ref c) = spec.bg_color { + self.write_color(false, c, spec.intense)?; + } + Ok(()) + } + + #[inline] + fn reset(&mut self) -> io::Result<()> { + self.write_str("\x1B[0m") + } + + #[inline] + fn is_synchronous(&self) -> bool { + false + } +} + +impl<W: io::Write> Ansi<W> { + fn write_str(&mut self, s: &str) -> io::Result<()> { + self.write_all(s.as_bytes()) + } + + fn write_color( + &mut self, + fg: bool, + c: &Color, + intense: bool, + ) -> io::Result<()> { + macro_rules! write_intense { + ($clr:expr) => { + if fg { + self.write_str(concat!("\x1B[38;5;", $clr, "m")) + } else { + self.write_str(concat!("\x1B[48;5;", $clr, "m")) + } + }; + } + macro_rules! write_normal { + ($clr:expr) => { + if fg { + self.write_str(concat!("\x1B[3", $clr, "m")) + } else { + self.write_str(concat!("\x1B[4", $clr, "m")) + } + }; + } + macro_rules! write_var_ansi_code { + ($pre:expr, $($code:expr),+) => {{ + // The loop generates at worst a literal of the form + // '255,255,255m' which is 12-bytes. + // The largest `pre` expression we currently use is 7 bytes. + // This gives us the maximum of 19-bytes for our work buffer. + let pre_len = $pre.len(); + assert!(pre_len <= 7); + let mut fmt = [0u8; 19]; + fmt[..pre_len].copy_from_slice($pre); + let mut i = pre_len - 1; + $( + let c1: u8 = ($code / 100) % 10; + let c2: u8 = ($code / 10) % 10; + let c3: u8 = $code % 10; + let mut printed = false; + + if c1 != 0 { + printed = true; + i += 1; + fmt[i] = b'0' + c1; + } + if c2 != 0 || printed { + i += 1; + fmt[i] = b'0' + c2; + } + // If we received a zero value we must still print a value. + i += 1; + fmt[i] = b'0' + c3; + i += 1; + fmt[i] = b';'; + )+ + + fmt[i] = b'm'; + self.write_all(&fmt[0..i+1]) + }} + } + macro_rules! write_custom { + ($ansi256:expr) => { + if fg { + write_var_ansi_code!(b"\x1B[38;5;", $ansi256) + } else { + write_var_ansi_code!(b"\x1B[48;5;", $ansi256) + } + }; + + ($r:expr, $g:expr, $b:expr) => {{ + if fg { + write_var_ansi_code!(b"\x1B[38;2;", $r, $g, $b) + } else { + write_var_ansi_code!(b"\x1B[48;2;", $r, $g, $b) + } + }}; + } + if intense { + match *c { + Color::Black => write_intense!("8"), + Color::Blue => write_intense!("12"), + Color::Green => write_intense!("10"), + Color::Red => write_intense!("9"), + Color::Cyan => write_intense!("14"), + Color::Magenta => write_intense!("13"), + Color::Yellow => write_intense!("11"), + Color::White => write_intense!("15"), + Color::Ansi256(c) => write_custom!(c), + Color::Rgb(r, g, b) => write_custom!(r, g, b), + Color::__Nonexhaustive => unreachable!(), + } + } else { + match *c { + Color::Black => write_normal!("0"), + Color::Blue => write_normal!("4"), + Color::Green => write_normal!("2"), + Color::Red => write_normal!("1"), + Color::Cyan => write_normal!("6"), + Color::Magenta => write_normal!("5"), + Color::Yellow => write_normal!("3"), + Color::White => write_normal!("7"), + Color::Ansi256(c) => write_custom!(c), + Color::Rgb(r, g, b) => write_custom!(r, g, b), + Color::__Nonexhaustive => unreachable!(), + } + } + } +} + +/// An in-memory buffer that provides Windows console coloring. +/// +/// This doesn't actually communicate with the Windows console. Instead, it +/// acts like a normal buffer but also saves the color information associated +/// with positions in the buffer. It is only when the buffer is written to the +/// console that coloring is actually applied. +/// +/// This is roughly isomorphic to the ANSI based approach (i.e., +/// `Ansi<Vec<u8>>`), except with ANSI, the color information is embedded +/// directly into the buffer. +/// +/// Note that there is no way to write something generic like +/// `WindowsConsole<W: io::Write>` since coloring on Windows is tied +/// specifically to the console APIs, and therefore can't work on arbitrary +/// writers. +#[cfg(windows)] +#[derive(Clone, Debug)] +struct WindowsBuffer { + /// The actual content that should be printed. + buf: Vec<u8>, + /// A sequence of position oriented color specifications. Namely, each + /// element is a position and a color spec, where the color spec should + /// be applied at the position inside of `buf`. + /// + /// A missing color spec implies the underlying console should be reset. + colors: Vec<(usize, Option<ColorSpec>)>, +} + +#[cfg(windows)] +impl WindowsBuffer { + /// Create a new empty buffer for Windows console coloring. + fn new() -> WindowsBuffer { + WindowsBuffer { buf: vec![], colors: vec![] } + } + + /// Push the given color specification into this buffer. + /// + /// This has the effect of setting the given color information at the + /// current position in the buffer. + fn push(&mut self, spec: Option<ColorSpec>) { + let pos = self.buf.len(); + self.colors.push((pos, spec)); + } + + /// Print the contents to the given stream handle, and use the console + /// for coloring. + fn print( + &self, + console: &mut wincon::Console, + stream: &mut LossyStandardStream<IoStandardStreamLock>, + ) -> io::Result<()> { + let mut last = 0; + for &(pos, ref spec) in &self.colors { + stream.write_all(&self.buf[last..pos])?; + stream.flush()?; + last = pos; + match *spec { + None => console.reset()?, + Some(ref spec) => spec.write_console(console)?, + } + } + stream.write_all(&self.buf[last..])?; + stream.flush() + } + + /// Clear the buffer. + fn clear(&mut self) { + self.buf.clear(); + self.colors.clear(); + } +} + +#[cfg(windows)] +impl io::Write for WindowsBuffer { + #[inline] + fn write(&mut self, buf: &[u8]) -> io::Result<usize> { + self.buf.extend_from_slice(buf); + Ok(buf.len()) + } + + #[inline] + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +#[cfg(windows)] +impl WriteColor for WindowsBuffer { + #[inline] + fn supports_color(&self) -> bool { + true + } + + #[inline] + fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> { + self.push(Some(spec.clone())); + Ok(()) + } + + #[inline] + fn reset(&mut self) -> io::Result<()> { + self.push(None); + Ok(()) + } + + #[inline] + fn is_synchronous(&self) -> bool { + false + } +} + +/// A color specification. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct ColorSpec { + fg_color: Option<Color>, + bg_color: Option<Color>, + bold: bool, + intense: bool, + underline: bool, + dimmed: bool, + italic: bool, + reset: bool, +} + +impl Default for ColorSpec { + fn default() -> ColorSpec { + ColorSpec { + fg_color: None, + bg_color: None, + bold: false, + intense: false, + underline: false, + dimmed: false, + italic: false, + reset: true, + } + } +} + +impl ColorSpec { + /// Create a new color specification that has no colors or styles. + pub fn new() -> ColorSpec { + ColorSpec::default() + } + + /// Get the foreground color. + pub fn fg(&self) -> Option<&Color> { + self.fg_color.as_ref() + } + + /// Set the foreground color. + pub fn set_fg(&mut self, color: Option<Color>) -> &mut ColorSpec { + self.fg_color = color; + self + } + + /// Get the background color. + pub fn bg(&self) -> Option<&Color> { + self.bg_color.as_ref() + } + + /// Set the background color. + pub fn set_bg(&mut self, color: Option<Color>) -> &mut ColorSpec { + self.bg_color = color; + self + } + + /// Get whether this is bold or not. + /// + /// Note that the bold setting has no effect in a Windows console. + pub fn bold(&self) -> bool { + self.bold + } + + /// Set whether the text is bolded or not. + /// + /// Note that the bold setting has no effect in a Windows console. + pub fn set_bold(&mut self, yes: bool) -> &mut ColorSpec { + self.bold = yes; + self + } + + /// Get whether this is dimmed or not. + /// + /// Note that the dimmed setting has no effect in a Windows console. + pub fn dimmed(&self) -> bool { + self.dimmed + } + + /// Set whether the text is dimmed or not. + /// + /// Note that the dimmed setting has no effect in a Windows console. + pub fn set_dimmed(&mut self, yes: bool) -> &mut ColorSpec { + self.dimmed = yes; + self + } + + /// Get whether this is italic or not. + /// + /// Note that the italic setting has no effect in a Windows console. + pub fn italic(&self) -> bool { + self.italic + } + + /// Set whether the text is italicized or not. + /// + /// Note that the italic setting has no effect in a Windows console. + pub fn set_italic(&mut self, yes: bool) -> &mut ColorSpec { + self.italic = yes; + self + } + + /// Get whether this is underline or not. + /// + /// Note that the underline setting has no effect in a Windows console. + pub fn underline(&self) -> bool { + self.underline + } + + /// Set whether the text is underlined or not. + /// + /// Note that the underline setting has no effect in a Windows console. + pub fn set_underline(&mut self, yes: bool) -> &mut ColorSpec { + self.underline = yes; + self + } + + /// Get whether reset is enabled or not. + /// + /// reset is enabled by default. When disabled and using ANSI escape + /// sequences, a "reset" code will be emitted every time a `ColorSpec`'s + /// settings are applied. + /// + /// Note that the reset setting has no effect in a Windows console. + pub fn reset(&self) -> bool { + self.reset + } + + /// Set whether to reset the terminal whenever color settings are applied. + /// + /// reset is enabled by default. When disabled and using ANSI escape + /// sequences, a "reset" code will be emitted every time a `ColorSpec`'s + /// settings are applied. + /// + /// Typically this is useful if callers have a requirement to more + /// scrupulously manage the exact sequence of escape codes that are emitted + /// when using ANSI for colors. + /// + /// Note that the reset setting has no effect in a Windows console. + pub fn set_reset(&mut self, yes: bool) -> &mut ColorSpec { + self.reset = yes; + self + } + + /// Get whether this is intense or not. + /// + /// On Unix-like systems, this will output the ANSI escape sequence + /// that will print a high-intensity version of the color + /// specified. + /// + /// On Windows systems, this will output the ANSI escape sequence + /// that will print a brighter version of the color specified. + pub fn intense(&self) -> bool { + self.intense + } + + /// Set whether the text is intense or not. + /// + /// On Unix-like systems, this will output the ANSI escape sequence + /// that will print a high-intensity version of the color + /// specified. + /// + /// On Windows systems, this will output the ANSI escape sequence + /// that will print a brighter version of the color specified. + pub fn set_intense(&mut self, yes: bool) -> &mut ColorSpec { + self.intense = yes; + self + } + + /// Returns true if this color specification has no colors or styles. + pub fn is_none(&self) -> bool { + self.fg_color.is_none() + && self.bg_color.is_none() + && !self.bold + && !self.underline + && !self.dimmed + && !self.italic + && !self.intense + } + + /// Clears this color specification so that it has no color/style settings. + pub fn clear(&mut self) { + self.fg_color = None; + self.bg_color = None; + self.bold = false; + self.underline = false; + self.intense = false; + self.dimmed = false; + self.italic = false; + } + + /// Writes this color spec to the given Windows console. + #[cfg(windows)] + fn write_console(&self, console: &mut wincon::Console) -> io::Result<()> { + let fg_color = self.fg_color.and_then(|c| c.to_windows(self.intense)); + if let Some((intense, color)) = fg_color { + console.fg(intense, color)?; + } + let bg_color = self.bg_color.and_then(|c| c.to_windows(self.intense)); + if let Some((intense, color)) = bg_color { + console.bg(intense, color)?; + } + Ok(()) + } +} + +/// The set of available colors for the terminal foreground/background. +/// +/// The `Ansi256` and `Rgb` colors will only output the correct codes when +/// paired with the `Ansi` `WriteColor` implementation. +/// +/// The `Ansi256` and `Rgb` color types are not supported when writing colors +/// on Windows using the console. If they are used on Windows, then they are +/// silently ignored and no colors will be emitted. +/// +/// This set may expand over time. +/// +/// This type has a `FromStr` impl that can parse colors from their human +/// readable form. The format is as follows: +/// +/// 1. Any of the explicitly listed colors in English. They are matched +/// case insensitively. +/// 2. A single 8-bit integer, in either decimal or hexadecimal format. +/// 3. A triple of 8-bit integers separated by a comma, where each integer is +/// in decimal or hexadecimal format. +/// +/// Hexadecimal numbers are written with a `0x` prefix. +#[allow(missing_docs)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum Color { + Black, + Blue, + Green, + Red, + Cyan, + Magenta, + Yellow, + White, + Ansi256(u8), + Rgb(u8, u8, u8), + #[doc(hidden)] + __Nonexhaustive, +} + +impl Color { + /// Translate this color to a wincon::Color. + #[cfg(windows)] + fn to_windows( + self, + intense: bool, + ) -> Option<(wincon::Intense, wincon::Color)> { + use wincon::Intense::{No, Yes}; + + let color = match self { + Color::Black => wincon::Color::Black, + Color::Blue => wincon::Color::Blue, + Color::Green => wincon::Color::Green, + Color::Red => wincon::Color::Red, + Color::Cyan => wincon::Color::Cyan, + Color::Magenta => wincon::Color::Magenta, + Color::Yellow => wincon::Color::Yellow, + Color::White => wincon::Color::White, + Color::Ansi256(0) => return Some((No, wincon::Color::Black)), + Color::Ansi256(1) => return Some((No, wincon::Color::Red)), + Color::Ansi256(2) => return Some((No, wincon::Color::Green)), + Color::Ansi256(3) => return Some((No, wincon::Color::Yellow)), + Color::Ansi256(4) => return Some((No, wincon::Color::Blue)), + Color::Ansi256(5) => return Some((No, wincon::Color::Magenta)), + Color::Ansi256(6) => return Some((No, wincon::Color::Cyan)), + Color::Ansi256(7) => return Some((No, wincon::Color::White)), + Color::Ansi256(8) => return Some((Yes, wincon::Color::Black)), + Color::Ansi256(9) => return Some((Yes, wincon::Color::Red)), + Color::Ansi256(10) => return Some((Yes, wincon::Color::Green)), + Color::Ansi256(11) => return Some((Yes, wincon::Color::Yellow)), + Color::Ansi256(12) => return Some((Yes, wincon::Color::Blue)), + Color::Ansi256(13) => return Some((Yes, wincon::Color::Magenta)), + Color::Ansi256(14) => return Some((Yes, wincon::Color::Cyan)), + Color::Ansi256(15) => return Some((Yes, wincon::Color::White)), + Color::Ansi256(_) => return None, + Color::Rgb(_, _, _) => return None, + Color::__Nonexhaustive => unreachable!(), + }; + let intense = if intense { Yes } else { No }; + Some((intense, color)) + } + + /// Parses a numeric color string, either ANSI or RGB. + fn from_str_numeric(s: &str) -> Result<Color, ParseColorError> { + // The "ansi256" format is a single number (decimal or hex) + // corresponding to one of 256 colors. + // + // The "rgb" format is a triple of numbers (decimal or hex) delimited + // by a comma corresponding to one of 256^3 colors. + + fn parse_number(s: &str) -> Option<u8> { + use std::u8; + + if s.starts_with("0x") { + u8::from_str_radix(&s[2..], 16).ok() + } else { + u8::from_str_radix(s, 10).ok() + } + } + + let codes: Vec<&str> = s.split(',').collect(); + if codes.len() == 1 { + if let Some(n) = parse_number(&codes[0]) { + Ok(Color::Ansi256(n)) + } else { + if s.chars().all(|c| c.is_digit(16)) { + Err(ParseColorError { + kind: ParseColorErrorKind::InvalidAnsi256, + given: s.to_string(), + }) + } else { + Err(ParseColorError { + kind: ParseColorErrorKind::InvalidName, + given: s.to_string(), + }) + } + } + } else if codes.len() == 3 { + let mut v = vec![]; + for code in codes { + let n = parse_number(code).ok_or_else(|| ParseColorError { + kind: ParseColorErrorKind::InvalidRgb, + given: s.to_string(), + })?; + v.push(n); + } + Ok(Color::Rgb(v[0], v[1], v[2])) + } else { + Err(if s.contains(",") { + ParseColorError { + kind: ParseColorErrorKind::InvalidRgb, + given: s.to_string(), + } + } else { + ParseColorError { + kind: ParseColorErrorKind::InvalidName, + given: s.to_string(), + } + }) + } + } +} + +/// An error from parsing an invalid color specification. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct ParseColorError { + kind: ParseColorErrorKind, + given: String, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +enum ParseColorErrorKind { + InvalidName, + InvalidAnsi256, + InvalidRgb, +} + +impl ParseColorError { + /// Return the string that couldn't be parsed as a valid color. + pub fn invalid(&self) -> &str { + &self.given + } +} + +impl error::Error for ParseColorError { + fn description(&self) -> &str { + use self::ParseColorErrorKind::*; + match self.kind { + InvalidName => "unrecognized color name", + InvalidAnsi256 => "invalid ansi256 color number", + InvalidRgb => "invalid RGB color triple", + } + } +} + +impl fmt::Display for ParseColorError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use self::ParseColorErrorKind::*; + match self.kind { + InvalidName => write!( + f, + "unrecognized color name '{}'. Choose from: \ + black, blue, green, red, cyan, magenta, yellow, \ + white", + self.given + ), + InvalidAnsi256 => write!( + f, + "unrecognized ansi256 color number, \ + should be '[0-255]' (or a hex number), but is '{}'", + self.given + ), + InvalidRgb => write!( + f, + "unrecognized RGB color triple, \ + should be '[0-255],[0-255],[0-255]' (or a hex \ + triple), but is '{}'", + self.given + ), + } + } +} + +impl FromStr for Color { + type Err = ParseColorError; + + fn from_str(s: &str) -> Result<Color, ParseColorError> { + match &*s.to_lowercase() { + "black" => Ok(Color::Black), + "blue" => Ok(Color::Blue), + "green" => Ok(Color::Green), + "red" => Ok(Color::Red), + "cyan" => Ok(Color::Cyan), + "magenta" => Ok(Color::Magenta), + "yellow" => Ok(Color::Yellow), + "white" => Ok(Color::White), + _ => Color::from_str_numeric(s), + } + } +} + +struct LossyStandardStream<W> { + wtr: W, + #[cfg(windows)] + is_console: bool, +} + +impl<W: io::Write> LossyStandardStream<W> { + #[cfg(not(windows))] + fn new(wtr: W) -> LossyStandardStream<W> { + LossyStandardStream { wtr: wtr } + } + + #[cfg(windows)] + fn new(wtr: W) -> LossyStandardStream<W> { + let is_console = wincon::Console::stdout().is_ok() + || wincon::Console::stderr().is_ok(); + LossyStandardStream { wtr: wtr, is_console: is_console } + } + + #[cfg(not(windows))] + fn wrap<Q: io::Write>(&self, wtr: Q) -> LossyStandardStream<Q> { + LossyStandardStream::new(wtr) + } + + #[cfg(windows)] + fn wrap<Q: io::Write>(&self, wtr: Q) -> LossyStandardStream<Q> { + LossyStandardStream { wtr: wtr, is_console: self.is_console } + } + + fn get_ref(&self) -> &W { + &self.wtr + } +} + +impl<W: WriteColor> WriteColor for LossyStandardStream<W> { + fn supports_color(&self) -> bool { + self.wtr.supports_color() + } + fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> { + self.wtr.set_color(spec) + } + fn reset(&mut self) -> io::Result<()> { + self.wtr.reset() + } + fn is_synchronous(&self) -> bool { + self.wtr.is_synchronous() + } +} + +impl<W: io::Write> io::Write for LossyStandardStream<W> { + #[cfg(not(windows))] + fn write(&mut self, buf: &[u8]) -> io::Result<usize> { + self.wtr.write(buf) + } + + #[cfg(windows)] + fn write(&mut self, buf: &[u8]) -> io::Result<usize> { + if self.is_console { + write_lossy_utf8(&mut self.wtr, buf) + } else { + self.wtr.write(buf) + } + } + + fn flush(&mut self) -> io::Result<()> { + self.wtr.flush() + } +} + +#[cfg(windows)] +fn write_lossy_utf8<W: io::Write>(mut w: W, buf: &[u8]) -> io::Result<usize> { + match ::std::str::from_utf8(buf) { + Ok(s) => w.write(s.as_bytes()), + Err(ref e) if e.valid_up_to() == 0 => { + w.write(b"\xEF\xBF\xBD")?; + Ok(1) + } + Err(e) => w.write(&buf[..e.valid_up_to()]), + } +} + +#[cfg(test)] +mod tests { + use super::{ + Ansi, Color, ColorSpec, ParseColorError, ParseColorErrorKind, + StandardStream, WriteColor, + }; + + fn assert_is_send<T: Send>() {} + + #[test] + fn standard_stream_is_send() { + assert_is_send::<StandardStream>(); + } + + #[test] + fn test_simple_parse_ok() { + let color = "green".parse::<Color>(); + assert_eq!(color, Ok(Color::Green)); + } + + #[test] + fn test_256_parse_ok() { + let color = "7".parse::<Color>(); + assert_eq!(color, Ok(Color::Ansi256(7))); + + let color = "32".parse::<Color>(); + assert_eq!(color, Ok(Color::Ansi256(32))); + + let color = "0xFF".parse::<Color>(); + assert_eq!(color, Ok(Color::Ansi256(0xFF))); + } + + #[test] + fn test_256_parse_err_out_of_range() { + let color = "256".parse::<Color>(); + assert_eq!( + color, + Err(ParseColorError { + kind: ParseColorErrorKind::InvalidAnsi256, + given: "256".to_string(), + }) + ); + } + + #[test] + fn test_rgb_parse_ok() { + let color = "0,0,0".parse::<Color>(); + assert_eq!(color, Ok(Color::Rgb(0, 0, 0))); + + let color = "0,128,255".parse::<Color>(); + assert_eq!(color, Ok(Color::Rgb(0, 128, 255))); + + let color = "0x0,0x0,0x0".parse::<Color>(); + assert_eq!(color, Ok(Color::Rgb(0, 0, 0))); + + let color = "0x33,0x66,0xFF".parse::<Color>(); + assert_eq!(color, Ok(Color::Rgb(0x33, 0x66, 0xFF))); + } + + #[test] + fn test_rgb_parse_err_out_of_range() { + let color = "0,0,256".parse::<Color>(); + assert_eq!( + color, + Err(ParseColorError { + kind: ParseColorErrorKind::InvalidRgb, + given: "0,0,256".to_string(), + }) + ); + } + + #[test] + fn test_rgb_parse_err_bad_format() { + let color = "0,0".parse::<Color>(); + assert_eq!( + color, + Err(ParseColorError { + kind: ParseColorErrorKind::InvalidRgb, + given: "0,0".to_string(), + }) + ); + + let color = "not_a_color".parse::<Color>(); + assert_eq!( + color, + Err(ParseColorError { + kind: ParseColorErrorKind::InvalidName, + given: "not_a_color".to_string(), + }) + ); + } + + #[test] + fn test_var_ansi_write_rgb() { + let mut buf = Ansi::new(vec![]); + let _ = buf.write_color(true, &Color::Rgb(254, 253, 255), false); + assert_eq!(buf.0, b"\x1B[38;2;254;253;255m"); + } + + #[test] + fn test_reset() { + let spec = ColorSpec::new(); + let mut buf = Ansi::new(vec![]); + buf.set_color(&spec).unwrap(); + assert_eq!(buf.0, b"\x1B[0m"); + } + + #[test] + fn test_no_reset() { + let mut spec = ColorSpec::new(); + spec.set_reset(false); + + let mut buf = Ansi::new(vec![]); + buf.set_color(&spec).unwrap(); + assert_eq!(buf.0, b""); + } + + #[test] + fn test_var_ansi_write_256() { + let mut buf = Ansi::new(vec![]); + let _ = buf.write_color(false, &Color::Ansi256(7), false); + assert_eq!(buf.0, b"\x1B[48;5;7m"); + + let mut buf = Ansi::new(vec![]); + let _ = buf.write_color(false, &Color::Ansi256(208), false); + assert_eq!(buf.0, b"\x1B[48;5;208m"); + } + + fn all_attributes() -> Vec<ColorSpec> { + let mut result = vec![]; + for fg in vec![None, Some(Color::Red)] { + for bg in vec![None, Some(Color::Red)] { + for bold in vec![false, true] { + for underline in vec![false, true] { + for intense in vec![false, true] { + for italic in vec![false, true] { + for dimmed in vec![false, true] { + let mut color = ColorSpec::new(); + color.set_fg(fg); + color.set_bg(bg); + color.set_bold(bold); + color.set_underline(underline); + color.set_intense(intense); + color.set_dimmed(dimmed); + color.set_italic(italic); + result.push(color); + } + } + } + } + } + } + } + result + } + + #[test] + fn test_is_none() { + for (i, color) in all_attributes().iter().enumerate() { + assert_eq!( + i == 0, + color.is_none(), + "{:?} => {}", + color, + color.is_none() + ) + } + } + + #[test] + fn test_clear() { + for color in all_attributes() { + let mut color1 = color.clone(); + color1.clear(); + assert!(color1.is_none(), "{:?} => {:?}", color, color1); + } + } +} |