calypso_base 0.1.1

Base types and utilities for Calypso that don't require any other Calypso crates (excluding calypso_error)
Documentation
use std::io::prelude::*;
use std::ops::{Deref, DerefMut};

use atty::Stream;
use termcolor::{Buffer, BufferWriter, Color, ColorChoice, ColorSpec, WriteColor};

use calypso_error::CalResult;

pub use atty;
pub use termcolor;

/// Parse a color preference (`always`, `ansi`, `auto`, anything else: auto) to
/// a color preference. Uses [`atty`] internally to test if the output stream
/// is a TTY.
#[must_use]
pub fn parse_color_pref(pref: &str, stream: Stream) -> ColorChoice {
    match pref {
        "always" => ColorChoice::Always,
        "ansi" => ColorChoice::AlwaysAnsi,
        "auto" => {
            if atty::is(stream) {
                ColorChoice::Auto
            } else {
                ColorChoice::Never
            }
        }
        _ => ColorChoice::Never,
    }
}

/// A helper struct containing the emitters for stdout and stderr.
pub struct Emitters {
    /// Emits to stdout
    pub out: Emitter,
    /// Emits to stderr
    pub err: Emitter,
}

impl Emitters {
    /// Create a new instance of this structure with the given preferences for
    /// color output for each stream. These can be parsed from text using
    /// [`parse_color_pref`].
    #[must_use]
    pub fn new(out_colors: ColorChoice, err_colors: ColorChoice) -> Self {
        {
            Self {
                out: Emitter::new(BufferWriter::stdout(out_colors)),
                err: Emitter::new(BufferWriter::stdout(err_colors)),
            }
        }
    }
}

/// A structure handling emitting messages to the terminal. This [`Deref`]s and
/// [`DerefMut`]s to a [`termcolor::Buffer`], so you can use
/// [`termcolor::WriteColor`] and methods on `Buffer` to write custom text.
///
/// Note that text is never automatically flushed, so you must manually use
/// [`Emitter::flush`].
pub struct Emitter {
    writer: BufferWriter,
    buf: Buffer,
}

impl Emitter {
    /// Create a new emitter.
    pub fn new(writer: BufferWriter) -> Self {
        let buf = writer.buffer();
        Self { writer, buf }
    }

    /// Create a new buffer based on the color preferences of this emitter.
    pub fn buffer(&self) -> Buffer {
        self.writer.buffer()
    }

    /// Flush the emitter. This will clear the internal buffer.
    ///
    /// # Errors
    ///
    /// This function will error if the emitter could not print the contents of
    /// the internal buffer.
    pub fn flush(&mut self) -> CalResult<&mut Self> {
        self.writer.print(&self.buf)?;
        self.buf.clear();
        Ok(self)
    }

    /// Add a newline to the internal buffer.
    ///
    /// # Errors
    ///
    /// This function will error if the buffer could not be updated.
    pub fn newline(&mut self) -> CalResult<&mut Self> {
        writeln!(self.buf)?;
        Ok(self)
    }

    /// Add the string provided to the internal buffer verbatim. (Chainable)
    ///
    /// # Errors
    ///
    /// This function will error if the buffer could not be updated.
    pub fn print(&mut self, s: &str) -> CalResult<&mut Self> {
        write!(self.buf, "{}", s)?;
        Ok(self)
    }

    /// Emit an error. Note that this function **will** reset the existing
    /// color of the internal buffer. The emitted text will have a newline.
    ///
    /// # Forms
    ///
    /// Angle brackets (`<>`) indicate a string provided to the function.
    ///
    /// ## Without `code` or `message`
    ///
    /// ```text
    /// error: <short>
    /// ```
    ///
    /// ## With `code` but not `message`
    ///
    /// ```text
    /// error[<code>]: <short>
    /// ```
    ///
    /// ## With `message` but not `code`
    ///
    /// ```text
    /// error: <short>: <message>
    /// ```
    ///
    /// ## With `code` and `message`
    ///
    /// ```text
    /// error[<code>]: <short>: <message>
    /// ```
    ///
    /// # Color
    ///
    /// When color is enabled for the output provided, the segments will be
    /// colored as such:
    ///
    /// - `error[<code>]`: Red; bold, intense
    /// - `<short>`: White; bold, intense
    /// - `<message>`: Default color
    ///
    /// # Errors
    ///
    /// This function will error if at any point text could not be written to
    /// the internal buffer.
    pub fn error(
        &mut self,
        code: Option<&str>,
        short: &str,
        message: Option<&str>,
    ) -> CalResult<&mut Self> {
        self.buf.set_color(
            ColorSpec::new()
                .set_fg(Some(Color::Red))
                .set_bold(true)
                .set_intense(true),
        )?;
        write!(self.buf, "error")?;
        if let Some(code) = code {
            write!(self.buf, "[{}]", code)?;
        }
        write!(self.buf, ": ")?;
        self.buf.set_color(
            ColorSpec::new()
                .set_fg(Some(Color::White))
                .set_bold(true)
                .set_intense(true),
        )?;
        write!(self.buf, "{}", short)?;
        if let Some(message) = message {
            write!(self.buf, ": ")?;
            self.buf.reset()?;
            write!(self.buf, "{}", message)?;
        }
        writeln!(self.buf)?;
        Ok(self)
    }

    fn message_general(
        &mut self,
        main: &'static str,
        color: Color,
        short: &str,
        message: Option<&str>,
    ) -> CalResult<&mut Self> {
        self.buf.set_color(
            ColorSpec::new()
                .set_fg(Some(color))
                .set_bold(true)
                .set_intense(true),
        )?;
        write!(self.buf, "{}: ", main)?;
        self.buf.set_color(
            ColorSpec::new()
                .set_fg(Some(Color::White))
                .set_bold(true)
                .set_intense(true),
        )?;
        write!(self.buf, "{}", short)?;
        if let Some(message) = message {
            write!(self.buf, ": ")?;
            self.buf.reset()?;
            write!(self.buf, "{}", message)?;
        }
        writeln!(self.buf)?;
        Ok(self)
    }

    /// Emit an informational message. Note that this function **will** reset
    /// the existing color of the internal buffer. The emitted text will have a
    /// newline.
    ///
    /// # Forms
    ///
    /// Angle brackets (`<>`) indicate a string provided to the function.
    ///
    /// ## Without `message`
    ///
    /// ```text
    /// info: <short>
    /// ```
    ///
    /// ## With `message`
    ///
    /// ```text
    /// info: <short>: <message>
    /// ```
    ///
    /// # Color
    ///
    /// When color is enabled for the output provided, the segments will be
    /// colored as such:
    ///
    /// - `info`: Cyan; bold, intense
    /// - `<short>`: White; bold, intense
    /// - `<message>`: Default color
    ///
    /// # Errors
    ///
    /// This function will error if at any point text could not be written to
    /// the internal buffer.
    pub fn info(&mut self, short: &str, message: Option<&str>) -> CalResult<&mut Self> {
        self.message_general("info", Color::Cyan, short, message)
    }

    /// Emit a note. Note that this function **will** reset the existing color
    /// of the internal buffer. The emitted text will have a newline.
    ///
    /// # Forms
    ///
    /// Angle brackets (`<>`) indicate a string provided to the function.
    ///
    /// ## Without `message`
    ///
    /// ```text
    /// note: <short>
    /// ```
    ///
    /// ## With `message`
    ///
    /// ```text
    /// note: <short>: <message>
    /// ```
    ///
    /// # Color
    ///
    /// When color is enabled for the output provided, the segments will be
    /// colored as such:
    ///
    /// - `note`: Green; bold, intense
    /// - `<short>`: White; bold, intense
    /// - `<message>`: Default color
    ///
    /// # Errors
    ///
    /// This function will error if at any point text could not be written to
    /// the internal buffer.
    pub fn note(&mut self, short: &str, message: Option<&str>) -> CalResult<&mut Self> {
        self.message_general("note", Color::Green, short, message)
    }

    /// Emit a warning. Note that this function **will** reset the existing
    /// color of the internal buffer. The emitted text will have a newline.
    ///
    /// # Forms
    ///
    /// Angle brackets (`<>`) indicate a string provided to the function.
    ///
    /// ## Without `message`
    ///
    /// ```text
    /// warn: <short>
    /// ```
    ///
    /// ## With `message`
    ///
    /// ```text
    /// warn: <short>: <message>
    /// ```
    ///
    /// # Color
    ///
    /// When color is enabled for the output provided, the segments will be
    /// colored as such:
    ///
    /// - `warn`: Yellow; bold, intense
    /// - `<short>`: White; bold, intense
    /// - `<message>`: Default color
    ///
    /// # Errors
    ///
    /// This function will error if at any point text could not be written to
    /// the internal buffer.
    pub fn warn(&mut self, short: &str, message: Option<&str>) -> CalResult<&mut Self> {
        self.message_general("warn", Color::Yellow, short, message)
    }

    /// Emit a `Buffer` now.
    ///
    /// Since we can't merge buffers (see
    /// [termcolor#45](https://github.com/BurntSushi/termcolor/issues/45)),
    /// this function directly prints the buffer instead of extending the
    /// internal buffer with this one.
    ///
    /// # Errors
    ///
    /// This function will error
    pub fn emit(&mut self, buf: &Buffer) -> CalResult<&mut Self> {
        self.writer.print(buf)?;
        Ok(self)
    }
}

impl Deref for Emitter {
    type Target = Buffer;

    fn deref(&self) -> &Self::Target {
        &self.buf
    }
}

impl DerefMut for Emitter {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.buf
    }
}