heks 0.0.2

A lib/tool exchanges hexadecimal color input
extern crate nom;

use std::num::ParseIntError;

use nom::IResult;
use nom::bytes::complete::{tag, take_while_m_n};
use nom::combinator::map_res;
use nom::sequence::tuple;

use rustyline::Editor;

#[derive(Debug, PartialEq)]
pub struct Color {
    pub r: u8,
    pub g: u8,
    pub b: u8,
}

fn from_hex_to_u8(s: &str) -> Result<u8, ParseIntError> {
    u8::from_str_radix(s, 16)
}

fn is_hex(c: char) -> bool {
    c.is_digit(16)
}

fn hex(s: &str) -> IResult<&str, u8> {
    let mut f = map_res(take_while_m_n(2, 2, is_hex), from_hex_to_u8);
    f(s)
}

#[allow(clippy::many_single_char_names)]
fn parse_hex_color(s: &str) -> IResult<&str, Color> {
    let (i, _) = tag("#")(s)?;
    let (i, (r, g, b)) = tuple((hex, hex, hex))(i)?;

    let c = Color { r, g, b };
    Ok((i, c))
}

fn with_tag(s: &str) -> String {
    if !s.starts_with('#') {
        return format!("#{}", s);
    }
    s.to_string()
}

fn main() {
    let mut rl = Editor::<()>::new();

    loop {
        let readline = rl.readline("> ");
        match readline {
            Ok(line) => {
                let hex = with_tag(&line);
                if let Ok((_, c)) = parse_hex_color(&hex) {
                    println!("r: {} g: {} b: {}", c.r, c.g, c.b);
                } else {
                    println!("invalid value is given");
                }
            }
            _ => break,
        }
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_from_hex_to_u8() {
        let result = from_hex_to_u8("22").unwrap();
        assert_eq!(result, 34);
    }

    #[test]
    fn test_is_hex() {
        assert!(is_hex('0'));
        assert!(is_hex('9'));
        assert!(is_hex('a'));
        assert!(is_hex('f'));

        assert!(!is_hex('g'));
    }

    #[test]
    fn test_parse_hex_color() {
        let color = Color {
            r: 34,
            g: 34,
            b: 34,
        };

        let (_, result) = parse_hex_color("#222222").unwrap();
        assert_eq!(result, color);
    }

    #[test]
    fn test_with_tag() {
        assert_eq!("#222222", with_tag("#222222"));
        assert_eq!("#222222", with_tag("222222"));
    }
}