imgtiger 1.0.3

An image viewer for iTerm2
Documentation
//! Types used to encode image size

use std::str::FromStr;
use std::num::ParseIntError;
use regex::Regex;

/// A type to encode image dimensions
/// 
/// There are four variants of `Dimension` that describe each unit.
/// 
/// - The `Cells` variant is used if you want an image to be 5 lines tall or 5
/// characters wide.
/// - The `Pixels` variant is used to describe the image size in pixels.
/// - The `Percent` variant uses the percent of the size of the viewport in that
/// direction. 
/// - The `Auto` variant is used for automatic sizing. Auto uses the image's real
/// size or tries to maintain the aspect ratio if the other dimension is already
/// set. 
/// 
/// A `Dimension` can be specified in a string as a number followed by a unit or
/// with the keyword `auto`.
/// - `N`: N Cells
/// - `Npx`: N Pixels
/// - `N%`: N Percent
/// - `auto`: Auto
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Dimension {
    Cells(u32),
    Pixels(u32),
    Percent(u32),
    Auto
}

/// An error returned which can be returned when parsing a `Dimension`
#[derive(Debug, Fail, PartialEq)]
pub enum DimensionParseError {
    #[fail(display = "Invalid dimension format")]
    InvalidFormat,
    #[fail(display = "Invalid integer")]
    ParseInt(ParseIntError),
    #[fail(display = "Invalid unit")]
    InvalidUnit,
}

impl From<ParseIntError> for DimensionParseError {
    fn from(error: ParseIntError) -> DimensionParseError {
        DimensionParseError::ParseInt(error)
    }
}

impl FromStr for Dimension {
    type Err = DimensionParseError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        lazy_static! {
            static ref RE: Regex = Regex::new(r"^(\d+)(.+)?$").unwrap();
        }
        
        if s == "auto" {
            Ok(Dimension::Auto)
        } else {
            let caps = RE.captures(s).ok_or(DimensionParseError::InvalidFormat)?;
            
            let n = caps[1].parse::<u32>()?;
            
            match caps.get(2).map(|m| m.as_str()) {
                None => Ok(Dimension::Cells(n)),
                Some("px") => Ok(Dimension::Pixels(n)),
                Some("%") => Ok(Dimension::Percent(n)),
                Some(_) => Err(DimensionParseError::InvalidUnit),
            }
        }
    }
}

/// Convert a Dimension into a String
impl Into<String> for Dimension {
    fn into(self) -> String {
        match self {
            Dimension::Cells(cells) => format!("{}", cells),
            Dimension::Pixels(pixels) => format!("{}px", pixels),
            Dimension::Percent(percent) => format!("{}%", percent),
            Dimension::Auto => "auto".to_string()
        }
    } 
}

mod tests {
    #![allow(dead_code)]
    #![allow(unused_imports)]

    use super::*;
    
    fn test_to_string(d: Dimension, s: &str){
        assert_eq!(<Dimension as Into<String>>::into(d), s);
    }
    
    fn test_parse(s: &str, r: Result<Dimension, DimensionParseError>){
        assert_eq!(s.parse::<Dimension>(), r);
    }
    
    mod parse_dimension {
        use super::{test_parse, super::{Dimension, DimensionParseError}};
    
        #[test]
        fn cells_valid() {
            test_parse("1", Ok(Dimension::Cells(1)));
            test_parse("42", Ok(Dimension::Cells(42)));
        }
        
        #[test]
        fn pixels_valid() {
            test_parse("1px", Ok(Dimension::Pixels(1)));
            test_parse("600px", Ok(Dimension::Pixels(600)));
        }
        
        #[test]
        fn percent_valid() {
            test_parse("1%", Ok(Dimension::Percent(1)));
            test_parse("99%", Ok(Dimension::Percent(99)));
        }
        
        #[test]
        fn auto_valid() {
            test_parse("auto", Ok(Dimension::Auto));
        }
        
        #[test]
        fn bad_unit_invalid_unit() {
            test_parse("20invalids", Err(DimensionParseError::InvalidUnit));
            test_parse("42towels", Err(DimensionParseError::InvalidUnit));
            test_parse("300 px", Err(DimensionParseError::InvalidUnit));
        }
        
        #[test]
        fn bad_format_invalid_format() {
            test_parse("This is bad", Err(DimensionParseError::InvalidFormat));
            test_parse("px300", Err(DimensionParseError::InvalidFormat));
        }
    }

    mod dimension_to_string {
        use super::{test_to_string, super::Dimension};
        
        #[test]
        fn auto_valid(){
            test_to_string(Dimension::Auto, "auto");
        }
        
        #[test]
        fn cells_valid(){
            test_to_string(Dimension::Cells(1), "1");
            test_to_string(Dimension::Cells(42), "42");
        }
        
        #[test]
        fn pixels_valid(){
            test_to_string(Dimension::Pixels(2), "2px");
            test_to_string(Dimension::Pixels(75), "75px");
        }
        
        #[test]
        fn percent_valid(){
            test_to_string(Dimension::Percent(1), "1%");
            test_to_string(Dimension::Percent(98), "98%");
        }
    }
    
    // TODO: Possibly add "round trip tests"
}