nixterm 0.1.2

A terminal library for rust
Documentation
use errors::*;
use failure::ResultExt;
use std::io::Write;
use std::str::{Chars, FromStr};
use util;

#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum Color {
    Index(u8),
    Rgb(u8, u8, u8),
}

pub const ALL_OFF: &[u8] = b"\x1b[0m";

pub const IMPACT_BOLD: &[u8] = b"\x1b[1m";
pub const IMPACT_FAINT: &[u8] = b"\x1b[2m";
pub const IMPACT_OFF: &[u8] = b"\x1b[22m";

pub const BLINK: &[u8] = b"\x1b[5m";
pub const BLINK_OFF: &[u8] = b"\x1b[25m";

pub const ITALICS: &[u8] = b"\x1b[3m";
pub const ITALICS_OFF: &[u8] = b"\x1b[23m";

pub const UNDERLINE: &[u8] = b"\x1b[4m";
pub const UNDERLINE_OFF: &[u8] = b"\x1b[24m";

pub const RESET_BACKGROUND: &[u8] = b"\x1b[49m";
pub const RESET_FOREGROUND: &[u8] = b"\x1b[39m";

#[repr(u8)]
pub enum GraphicRendition {
    Reset = 0,
    Bold = 1,
    Faint = 2,
    Italic = 3,
    Underline = 4,
    Blink = 5,
    FastBlink = 6,
    Invert = 7,
    Conceal = 8,
    Strike = 9,
    Fraktur = 20,

    ResetImpact = 22,
    ResetStyle = 23,
    ResetUnderline = 24,
    ResetBlink = 25,
    ResetInvert = 27,
    ResetConceal = 28,
    ResetStrike = 29,

    ResetFrame = 54,
    ResetOverline = 55,
    ResetIdeogram = 65,

    ResetFont = 10,
    ResetBackground = 49,
    ResetForeground = 39,
}

impl From<u8> for Color {
    fn from(v: u8) -> Color {
        Color::Index(v)
    }
}

impl From<(u8, u8, u8)> for Color {
    fn from(v: (u8, u8, u8)) -> Color {
        Color::Rgb(v.0, v.1, v.2)
    }
}

impl From<&'static str> for Color {
    fn from(v: &'static str) -> Color {
        Color::from_str(v).unwrap()
    }
}

impl Color {
    fn from_hex<'a>(iter: Chars<'a>) -> Result<Color> {
        let s = iter.as_str();
        let char_count = s.len();
        match char_count {
            3 => Ok(Color::Rgb(
                s[0..1].parse::<u8>().context(ErrorKind::InvalidNumber)?,
                s[1..2].parse::<u8>().context(ErrorKind::InvalidNumber)?,
                s[2..3].parse::<u8>().context(ErrorKind::InvalidNumber)?,
            )),
            6 => Ok(Color::Rgb(
                s[0..2].parse::<u8>().context(ErrorKind::InvalidNumber)?,
                s[2..4].parse::<u8>().context(ErrorKind::InvalidNumber)?,
                s[4..6].parse::<u8>().context(ErrorKind::InvalidNumber)?,
            )),
            _ => Err(ErrorKind::InvalidNumber.into()),
        }
    }

    fn from_name(s: &str) -> Result<Color> {
        Ok(match s {
            "black" => Color::Index(0),
            "red" => Color::Index(1),
            "green" => Color::Index(2),
            "yellow" => Color::Index(3),
            "blue" => Color::Index(4),
            "magenta" => Color::Index(5),
            "cyan" => Color::Index(6),
            "grey" => Color::Index(7),
            "darkgrey" => Color::Index(8),
            "brightred" => Color::Index(9),
            "brightgreen" => Color::Index(10),
            "brightyellow" => Color::Index(11),
            "brightblue" => Color::Index(12),
            "brightmagenta" => Color::Index(13),
            "brightcyan" => Color::Index(14),
            "white" => Color::Index(15),
            _ => return Err(ErrorKind::UnknownColorName(s.to_owned()).into()),
        })
    }

    fn from_rgb<'a>(srciter: Chars<'a>) -> Result<Color> {
        let s = srciter.clone().as_str();
        let iter = srciter.enumerate();

        let mut rgb: [&str; 3] = ["", "", ""];
        let mut is_floating = false;

        let mut index = 1;
        for x in 0..3 {
            for (i, c) in iter.clone().skip(index) {
                match c {
                    ' ' | '\t' | '\n' => index += 1,
                    '0'...'9' => (),
                    '.' => is_floating = true,
                    'e' | 'E' => is_floating = true,
                    ',' | ')' => {
                        rgb[x] = &s[index..i];
                        index = i + 1;
                        break;
                    }
                    _ => (),
                }
            }
        }
        if !is_floating {
            Ok(Color::Rgb(
                rgb[0].parse::<u8>().context(ErrorKind::InvalidNumber)?,
                rgb[1].parse::<u8>().context(ErrorKind::InvalidNumber)?,
                rgb[2].parse::<u8>().context(ErrorKind::InvalidNumber)?,
            ))
        } else {
            Ok(Color::Rgb(
                (rgb[0]
                    .parse::<f64>()
                    .context(ErrorKind::InvalidNumber)?
                    .fract() * 255.0) as u8,
                (rgb[1]
                    .parse::<f64>()
                    .context(ErrorKind::InvalidNumber)?
                    .fract() * 255.0) as u8,
                (rgb[2]
                    .parse::<f64>()
                    .context(ErrorKind::InvalidNumber)?
                    .fract() * 255.0) as u8,
            ))
        }
    }
}

impl FromStr for Color {
    type Err = Error;

    fn from_str(s: &str) -> Result<Color> {
        let lower: String = s
            .to_lowercase()
            .chars()
            .skip_while(|&c| c == ' ' || c == '\t' || c == '\r' || c == '\n')
            .collect();
        let len = lower.len();

        let c0 = lower.chars().nth(0).unwrap();
        match c0 {
            '#' => Ok(Color::from_hex(lower.chars()).context(ErrorKind::InvalidColor)?),
            '0'...'9' => Ok(Color::Index(
                lower.parse::<u8>().context(ErrorKind::InvalidColor)?,
            )),
            'r' => {
                if len > 4
                    && lower.chars().nth(1).unwrap() == 'g'
                    && lower.chars().nth(2).unwrap() == 'b'
                {
                    let offset = lower[2..].chars().take_while(|&c| c != '(').count();
                    Ok(Color::from_rgb(lower[offset + 2..].chars())
                        .context(ErrorKind::InvalidColor)?)
                } else if len > 2
                    && lower.chars().nth(1).unwrap() == 'e'
                    && lower.chars().nth(2).unwrap() == 'd'
                {
                    Ok(Color::Index(1))
                } else {
                    Err(ErrorKind::UnknownColorName(s.to_owned()).into())
                }
            }
            _ => Color::from_name(&lower),
        }
    }
}

pub fn set_foreground<W: Write>(w: &mut W, c: Color) -> Result<()> {
    Ok(match c {
        Color::Index(x @ 0...7) => write!(w, "\x1b[{}m", x + 30),
        Color::Index(x @ 8...15) => write!(w, "\x1b[{}m", x + 82),
        Color::Index(x) => write!(w, "\x1b[38;5;{}m", x),
        Color::Rgb(r, g, b) => write!(w, "\x1b[38;2;{};{};{}m", r, g, b),
    }.context(ErrorKind::CsiFailed)?)
}

pub fn set_background<W: Write>(w: &mut W, c: Color) -> Result<()> {
    Ok(match c {
        Color::Index(x @ 0...7) => write!(w, "\x1b[{}m", x + 40),
        Color::Index(x @ 8...15) => write!(w, "\x1b[{}m", x + 92),
        Color::Index(x) => write!(w, "\x1b[48;5;{}m", x),
        Color::Rgb(r, g, b) => write!(w, "\x1b[98;2;{};{};{}m", r, g, b),
    }.context(ErrorKind::CsiFailed)?)
}

pub fn cursor_shift_vertical<W: Write>(w: &mut W, shift: isize) -> Result<()> {
    if shift < 0 {
        Ok(write!(w, "\x1b[{}B", -shift).context(ErrorKind::CsiFailed)?)
    } else if shift > 0 {
        Ok(write!(w, "\x1b[{}A", shift).context(ErrorKind::CsiFailed)?)
    } else {
        Ok(())
    }
}

pub fn cursor_shift_horizontal<W: Write>(w: &mut W, shift: isize) -> Result<()> {
    if shift < 0 {
        Ok(write!(w, "\x1b[{}D", -shift).context(ErrorKind::CsiFailed)?)
    } else if shift > 0 {
        Ok(write!(w, "\x1b[{}C", shift).context(ErrorKind::CsiFailed)?)
    } else {
        Ok(())
    }
}

pub fn cursor_move<W: Write>(w: &mut W, x: usize, y: usize) -> Result<()> {
    Ok(write!(w, "\x1b[{};{}m", x, y).context(ErrorKind::CsiFailed)?)
}

pub fn cursor_set_column<W: Write>(w: &mut W, x: usize) -> Result<()> {
    Ok(write!(w, "\x1b[{}G", x).context(ErrorKind::CsiFailed)?)
}

#[inline]
pub fn sgr<W: Write>(w: &mut W, gr: GraphicRendition) -> Result<usize> {
    Ok(w.write(b"\x1b[").context(ErrorKind::CsiFailed)?
        + util::write_u8_ansi(w, gr as u8)?
        + w.write(b"m").context(ErrorKind::CsiFailed)?)
}