go-template 0.0.3

The Golang Templating Language for Rust
Documentation
use gtmpl_value::Value;
use std::char;

pub fn unquote_char(s: &str, quote: char) -> Option<char> {
    if s.len() < 2 || !s.starts_with(quote) || !s.ends_with(quote) {
        return None;
    }
    let raw = &s[1..s.len() - 1];
    match unqote(raw) {
        Some((c, l)) => {
            if l == raw.len() {
                c.chars().next()
            } else {
                None
            }
        }
        _ => None,
    }
}

pub fn unquote_str(s: &str) -> Option<String> {
    if s.len() < 2 {
        return None;
    }
    let quote = &s[0..1];
    if !s.ends_with(quote) {
        return None;
    }
    let mut r = String::new();
    let raw = &s[1..s.len() - 1];
    let mut i = 0;
    while i < raw.len() {
        match unqote(&raw[i..]) {
            Some((c, len)) => {
                r += &c;
                i += len;
            }
            None => return None,
        }
    }
    Some(r)
}

fn unqote(raw: &str) -> Option<(String, usize)> {
    if raw.starts_with('\\') {
        match &raw[..2] {
            r"\x" => extract_bytes_x(raw),
            r"\U" => extract_bytes_u32(raw),
            r"\u" => extract_bytes_u16(raw),
            r"\b" => Some(('\u{0008}'.to_string(), 2)),
            r"\f" => Some(('\u{000C}'.to_string(), 2)),
            r"\n" => Some(('\n'.to_string(), 2)),
            r"\r" => Some(('\r'.to_string(), 2)),
            r"\t" => Some(('\t'.to_string(), 2)),
            r"\'" => Some(('\''.to_string(), 2)),
            r#"\""# => Some(('\"'.to_string(), 2)),
            r#"\\"# => Some(('\\'.to_string(), 2)),
            _ => None,
        }
    } else {
        get_char(raw)
    }
}

fn get_char(s: &str) -> Option<(String, usize)> {
    s.char_indices()
        .next()
        .map(|(i, c)| (c.to_string(), i + c.len_utf8()))
}

fn extract_bytes_u32(s: &str) -> Option<(String, usize)> {
    if s.len() != 10 {
        return None;
    }
    u32::from_str_radix(&s[2..10], 16)
        .ok()
        .and_then(char::from_u32)
        .map(|c| (c.to_string(), 10))
}

fn extract_bytes_u16(s: &str) -> Option<(String, usize)> {
    let mut bytes = vec![];
    let mut i = 0;
    while s.len() > i && s.starts_with(r"\u") && s[i..].len() >= 6 {
        match u16::from_str_radix(&s[(i + 2)..(i + 6)], 16) {
            Ok(x) => bytes.push(x),
            _ => {
                return None;
            }
        };
        i += 6;
    }
    String::from_utf16(&bytes).ok().map(|s| (s, i))
}

fn extract_bytes_x(s: &str) -> Option<(String, usize)> {
    let mut bytes = vec![];
    let mut i = 0;
    while s.len() > i && s.starts_with(r"\x") && s[i..].len() >= 4 {
        match u8::from_str_radix(&s[(i + 2)..(i + 4)], 16) {
            Ok(x) => bytes.push(x),
            _ => {
                return None;
            }
        };
        i += 4;
    }
    String::from_utf8(bytes).ok().map(|s| (s, i))
}

/// Returns
pub fn is_true(val: &Value) -> bool {
    match *val {
        Value::Bool(ref b) => *b,
        Value::String(ref s) => !s.is_empty(),
        Value::Array(ref a) => !a.is_empty(),
        Value::Object(ref o) => !o.is_empty(),
        Value::Map(ref m) => !m.is_empty(),
        Value::Function(_) => true,
        Value::NoValue | Value::Nil => false,
        Value::Number(ref n) => n.as_u64().map(|u| u != 0).unwrap_or_else(|| true),
    }
}

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

    #[test]
    fn test_unquote_char() {
        let s = "'→'";
        let c = unquote_char(s, '\'');
        assert_eq!(c, Some(''));
        let s = "'→←'";
        let c = unquote_char(s, '\'');
        assert_eq!(c, None);
        let s = r"'\xf0\x9f\x92\xa9'";
        let c = unquote_char(s, '\'');
        assert_eq!(c, Some('💩'));
        let s = r"'\xf0\x9f\x92\xa'";
        let c = unquote_char(s, '\'');
        assert_eq!(c, None);
        let s = r"'\xf0\x9f\x92\xa99'";
        let c = unquote_char(s, '\'');
        assert_eq!(c, None);
        let s = r"'\u263a'";
        let c = unquote_char(s, '\'');
        assert_eq!(c, Some(''));
        let s = r"'\uD83D\uDCA9'";
        let c = unquote_char(s, '\'');
        assert_eq!(c, Some('💩'));
        let s = r"'\uD83\uDCA9'";
        let c = unquote_char(s, '\'');
        assert_eq!(c, None);
        let s = r"'\uD83D\uDCA9B'";
        let c = unquote_char(s, '\'');
        assert_eq!(c, None);
        let s = r"'\U0001F4A9'";
        let c = unquote_char(s, '\'');
        assert_eq!(c, Some('💩'));
        let s = r"'\U0001F4A'";
        let c = unquote_char(s, '\'');
        assert_eq!(c, None);
        let s = r"'\U0001F4A99'";
        let c = unquote_char(s, '\'');
        assert_eq!(c, None);
    }

    #[test]
    fn test_unquote_str() {
        let s = r#""Fran & Freddie's Diner""#;
        let u = unquote_str(s);
        assert_eq!(u, Some("Fran & Freddie's Diner".to_owned()));
        let s = r#""Fran & Freddie's Diner\t\u263a""#;
        let u = unquote_str(s);
        assert_eq!(u, Some("Fran & Freddie's Diner\t".to_owned()));
    }

    #[test]
    fn test_is_true() {
        let t = Value::from(1i8);
        assert!(is_true(&t));
        let t = Value::from(0u32);
        assert!(!is_true(&t));
    }
}