zen_template 0.1.0

A simple template engine based on rust.
Documentation
#![allow(dead_code)]

use std::collections::HashMap;
#[cfg(any(feature = "yaml", test))]
use std::ops::Not;

#[cfg(any(feature = "json", test))]
use serde_json::Value as JsonValue;
#[cfg(any(feature = "yaml", test))]
use serde_yaml::Value as YamlValue;

#[cfg(any(feature = "json", test))]
use crate::err::TemplateError;
use crate::err::TmplResult;

#[derive(Debug, Clone)]
pub enum TmplValue {
    Float(f64),
    Number(i64),
    Text(String),
    Bool(bool),
    Array(Vec<TmplValue>),
    Table(HashMap<String, TmplValue>),
    //仅用于参与计算
    None,
}

impl PartialEq for TmplValue {
    fn eq(&self, other: &Self) -> bool {
        match self {
            TmplValue::Float(_) => false,
            TmplValue::Number(e) => {
                if let TmplValue::Number(oe) = other {
                    e.eq(oe)
                } else {
                    false
                }
            }
            TmplValue::Text(e) => {
                if let TmplValue::Text(oe) = other {
                    e.eq(oe)
                } else {
                    false
                }
            }
            TmplValue::Bool(e) => {
                if let TmplValue::Bool(oe) = other {
                    e.eq(oe)
                } else {
                    false
                }
            }
            TmplValue::Array(e) => {
                if let TmplValue::Array(oe) = other {
                    e.eq(oe)
                } else {
                    false
                }
            }
            TmplValue::Table(e) => {
                if let TmplValue::Table(oe) = other {
                    e.eq(oe)
                } else {
                    false
                }
            }
            TmplValue::None => {
                if let TmplValue::None = other {
                    true
                } else {
                    false
                }
            }
        }
    }
}

impl ToString for TmplValue {
    fn to_string(&self) -> String {
        match self {
            TmplValue::Float(f) => f.to_string(),
            TmplValue::Number(n) => n.to_string(),
            TmplValue::Text(t) => t.to_string(),
            TmplValue::Bool(b) => b.to_string(),
            TmplValue::Array(a) => a
                .iter()
                .map(|e| e.to_string())
                .collect::<Vec<String>>()
                .join(",")
                .to_string(),
            TmplValue::Table(t) => t
                .iter()
                .map(|e| format!("{}={}", e.0.to_string(), e.1.to_string()))
                .collect::<Vec<String>>()
                .join(",")
                .to_string(),
            TmplValue::None => "None".to_string(),
        }
    }
}

impl TmplValue {
    pub fn from(src: &str) -> TmplValue {
        src.parse::<bool>()
            .map(|e| TmplValue::Bool(e))
            .or_else(|_| src.parse::<i64>().map(|e| TmplValue::Number(e)))
            .or_else(|_| src.parse::<f64>().map(|e| TmplValue::Float(e)))
            .unwrap_or(TmplValue::Text(src.to_string()))
    }

    #[cfg(any(feature = "json", test))]
    pub fn from_json(src: &str) -> TmplResult<TmplValue> {
        let value: JsonValue =
            serde_json::from_str(src).map_err(|e| TemplateError::GenericError(e.to_string()))?;
        fn covert_value(old: &JsonValue) -> TmplValue {
            match old {
                JsonValue::Null => TmplValue::None,
                JsonValue::Bool(item) => TmplValue::Bool(item.clone()),
                JsonValue::Number(item) => {
                    if item.is_f64() {
                        TmplValue::Float(item.as_f64().unwrap())
                    } else {
                        TmplValue::Number(item.as_i64().unwrap())
                    }
                }
                JsonValue::String(item) => TmplValue::Text(item.to_string()),
                JsonValue::Array(array) => TmplValue::Array(
                    array
                        .iter()
                        .map(|e| covert_value(e))
                        .collect::<Vec<TmplValue>>(),
                ),
                JsonValue::Object(object) => TmplValue::Table(
                    object
                        .iter()
                        .map(|(k, v)| (k.to_string(), covert_value(v)))
                        .collect(),
                ),
            }
        }
        Ok(covert_value(&value))
    }
    #[cfg(any(feature = "yaml", test))]
    pub fn from_yaml(src: &str) -> TmplResult<TmplValue> {
        let value: YamlValue =
            serde_yaml::from_str(src).map_err(|e| TemplateError::GenericError(e.to_string()))?;
        fn covert_value(old: &YamlValue) -> TmplValue {
            match old {
                YamlValue::Null => TmplValue::None,
                YamlValue::Bool(item) => TmplValue::Bool(item.clone()),
                YamlValue::Number(item) => {
                    if item.is_f64().not() {
                        TmplValue::Number(item.as_i64().unwrap())
                    } else {
                        TmplValue::Float(item.as_f64().unwrap())
                    }
                }
                YamlValue::String(item) => TmplValue::Text(item.to_string()),
                YamlValue::Sequence(array) => TmplValue::Array(
                    array
                        .iter()
                        .map(|e| covert_value(e))
                        .collect::<Vec<TmplValue>>(),
                ),
                YamlValue::Mapping(object) => TmplValue::Table(
                    object
                        .iter()
                        .map(|(k, v)| (covert_value(k).to_string(), covert_value(v)))
                        .collect(),
                ),
                YamlValue::Tagged(_) => TmplValue::None,
            }
        }
        Ok(covert_value(&value))
    }
}

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

    use crate::value::TmplValue;

    #[test]
    fn test_from() {
        assert_eq!(
            TmplValue::from("false").to_string(),
            TmplValue::Bool(false).to_string()
        );
        assert_eq!(
            TmplValue::from("12.33").to_string(),
            TmplValue::Float(12.33).to_string()
        );
        assert_eq!(
            TmplValue::from("15").to_string(),
            TmplValue::Number(15).to_string()
        );
        assert_eq!(
            TmplValue::from("text").to_string(),
            TmplValue::Text("text".to_string()).to_string()
        );
    }

    #[test]
    fn test_from_json() {
        assert!(TmplValue::from_json(
            r#"
         {
            "name": "John Doe",
            "age": 43,
            "phones": [
                "+44 1234567",
                "+44 2345678"
            ]
        }
        "#
        )
        .is_ok());
    }

    #[test]
    fn test_from_yaml() {
        let yaml = indoc! {"
        ---
        - &CENTER { x: 1, y: 2 }
        - &LEFT { x: 0, y: 2 }
        - &BIG { r: 10 }
        - &SMALL { r: 1 }
        # All the following maps are equal:
        - # Explicit keys
          x: 1
          y: 2
          r: 10
          label: center/big
        - # Merge one map
          << : *CENTER
          r: 10
          label: center/big
        - # Merge multiple maps
          << : [ *CENTER, *BIG ]
          label: center/big
        - # Override
          << : [ *BIG, *LEFT, *SMALL ]
          x: 1
          label: center/big
        "};
        assert!(TmplValue::from_yaml(yaml).is_ok())
    }
}