mage 0.2.0

An intuitive and powerful template engine.
Documentation
//! Mage is an intuitive and powerful template engine.
//!
//! Supported keywords: `for` `if` `include` `set`
//!
//! Mage support expression evaluate with crate [`eval`](https://crates.io/crates/eval).
//! Built-in functions and operators documentation: [`eval`](https://docs.rs/eval).
//!
//! # Examples
//! Render template from string:
//!
//! ```
//! use mage::Template;
//!
//! let source = "{{if true}}Hello world!{{if}}";
//! let output = Template::new().template(source).render().unwrap();
//! assert_eq!(output, "Hello world!");
//! ```
//!
//! Render template from file:
//!
//! ```
//! use mage::Template;
//!
//! let output = Template::new()
//!     .root("examples")
//!     .extension("html")
//!     .open("main")
//!     .unwrap()
//!     .render()
//!     .unwrap();
//! ```
//!
//!
//! # Keywords
//!
//! ## For
//! Loop a value.
//!
//! ```html
//! {{for value in object}}
//!     {{value}}
//! {{for}}
//!
//! {{for value, key in object}}
//!     {{key}}
//!     {{value}}
//! {{for}}
//! ```
//!
//! ## If
//! Conditionally render a block.
//!
//! ```html
//! {{if boolean}}
//!     Hello world!
//! {{elseif boolean}}
//!     Hello world!
//! {{elseif boolean}}
//!     Hello world!
//! {{else}}
//!     Hello world!
//! {{if}}
//! ```
//!
//! ## Include
//! Include a sub template.
//!
//! ```html
//! {{include views/header}}
//! ```
//!
//! ## Set
//! Set a value to current context.
//!
//! ```html
//! {{set boolean=true}}
//! {{set number=123}}
//! {{set string='Hello world!'}}
//! ```
//!
//!
#![cfg_attr(all(feature = "unstable", test), feature(test))]
#![deny(missing_docs)]

#[macro_use]
extern crate quick_error;
extern crate serde;
extern crate serde_json;
extern crate eval;
extern crate glob;

mod template;
mod render;
mod utils;
mod error;
mod node;
mod tree;
mod keyword;
mod cache;

pub use cache::Cache;
pub use template::RenderOptions;
pub use template::Template;
pub use error::Error;
pub use eval::Error as ExpressionError;
pub use eval::{Context, Contexts, Value, Function, Functions, to_value};

type Compiled = Box<Fn(Contexts, &Functions) -> Result<String, Error>>;



#[cfg(test)]
mod tests {
    use Template;
    use std::collections::HashMap;

    #[test]
    fn test_if() {
        let source = "{{if true}}Hello world!{{if}}";
        assert_eq!(Template::new()
                       .template(source)
                       .render()
                       .unwrap(),
                   "Hello world!");
    }

    #[test]
    fn test_if_value() {
        let source = "{{if boolean}}Hello world!{{if}}";
        assert_eq!(Template::new()
                       .template(source)
                       .value("boolean", true)
                       .render()
                       .unwrap(),
                   "Hello world!");
        assert!(Template::new()
            .template(source)
            .value("boolean", false)
            .render()
            .unwrap()
            .is_empty());
    }

    #[test]
    fn test_if_expression() {
        let source = "{{if 1 < 2}}Hello world!{{if}}";
        assert_eq!(Template::new()
                       .template(source)
                       .render()
                       .unwrap(),
                   "Hello world!");
    }

    #[test]
    fn test_if_complex_expression() {
        let source = "{{if (2 + 3) * 5 == 25}}Hello world!{{if}}";
        assert_eq!(Template::new()
                       .template(source)
                       .render()
                       .unwrap(),
                   "Hello world!");
    }

    #[test]
    fn test_if_is_empty() {
        let source = "{{if is_empty('')}}Hello world!{{if}}";
        assert_eq!(Template::new()
                       .template(source)
                       .render()
                       .unwrap(),
                   "Hello world!");
    }

    #[test]
    fn test_if_is_not_empty() {
        let source = "{{if !is_empty('Not Empty')}}Hello world!{{if}}";
        assert_eq!(Template::new()
                       .template(source)
                       .render()
                       .unwrap(),
                   "Hello world!");
    }

    #[test]
    fn test_else_if() {
        let source = "{{if false}}{{elseif true}}Hello world!{{if}}";
        assert_eq!(Template::new()
                       .template(source)
                       .render()
                       .unwrap(),
                   "Hello world!");
    }

    #[test]
    fn test_else() {
        let source = "{{if false}}{{else}}Hello world!{{if}}";
        assert_eq!(Template::new()
                       .template(source)
                       .render()
                       .unwrap(),
                   "Hello world!");
    }

    #[test]
    fn test_set_boolean() {
        let source = "{{set boolean=true}}{{if boolean}}Hello world!{{if}}";
        assert_eq!(Template::new()
                       .template(source)
                       .render()
                       .unwrap(),
                   "Hello world!");
    }

    #[test]
    fn test_set_string() {
        let source = "{{set value='Hello world!'}}{{value}}";
        assert_eq!(Template::new()
                       .template(source)
                       .render()
                       .unwrap(),
                   "Hello world!");
    }

    #[test]
    fn test_set_number() {
        let source = "{{set number=123}}{{number == 123}}";
        assert_eq!(Template::new()
                       .template(source)
                       .render()
                       .unwrap(),
                   "true");
    }

    #[test]
    fn test_for() {
        let source = "{{for value, index in array}}[{{index}}]{{value}}{{for}}";
        assert_eq!(Template::new()
                       .template(source)
                       .value("array", vec!["Hello", " ", "world", "!"])
                       .render()
                       .unwrap(),
                   "[0]Hello[1] [2]world[3]!");
    }

    #[test]
    fn test_example() {
        Template::new()
            .root("examples")
            .extension("html")
            .open("main")
            .unwrap()
            .render()
            .unwrap();
    }

    #[test]
    fn test_string() {
        let source = "Hello world!";
        assert_eq!(Template::new()
                       .template(source)
                       .render()
                       .unwrap(),
                   "Hello world!");
    }

    #[test]
    fn test_expression() {
        let source = "{{  3 + 5 * 4 + 3 - 5 }}";
        assert_eq!(Template::new()
                       .template(source)
                       .render()
                       .unwrap(),
                   "21");
    }

    #[test]
    fn test_object_access() {
        let mut object = HashMap::new();
        object.insert("foo", "Foo, hello world!");
        object.insert("bar", "Bar, hello world!");
        let source = "{{if object.foo == 'Foo, hello world!'}}{{object.bar}}{{if}}";
        assert_eq!(Template::new()
                       .template(source)
                       .value("object", object)
                       .render()
                       .unwrap(),
                   "Bar, hello world!");
    }

    #[test]
    fn test_array_access() {
        let array = vec!["hello", "world", "!"];
        let source = "{{if array[0] == 'hello'}}{{array[1]}}{{array[2]}}{{if}}";
        assert_eq!(Template::new()
                       .template(source)
                       .value("array", array)
                       .render()
                       .unwrap(),
                   "world!");
    }
}

#[cfg(all(feature = "unstable", test))]
mod benches {
    extern crate test;
    use {Value, Template, to_value};
    use std::collections::HashMap;

    static SOURCE: &'static str = r#"<html>
<head>
    <title>{{year}}</title>
</head>
<body>
    <h1>{{year}}</h1>
    <ul>
        {{for item, index in items}}
        <li class="{{if index == 0}}active{{if}}"> <b>{{item.name}}</b>
            : {{item.score}}
        </li>
        {{for}}
    </ul>
</body>
</html>"#;

    fn create_items() -> Vec<HashMap<String, Value>> {
        let mut items = Vec::new();

        for tuple in &vec![("Abc", 100), ("Def", 105), ("Ghi", 150), ("Jkl", 510)] {
            let (name, score) = *tuple;
            let mut item = HashMap::new();
            item.insert("name".to_owned(), to_value(name));
            item.insert("score".to_owned(), to_value(score));
            items.push(item);
        }

        items
    }

    #[bench]
    fn bench_compile(b: &mut test::Bencher) {
        b.iter(|| {
            Template::new()
                .template(SOURCE)
                .compile()
                .unwrap();
        })
    }

    #[bench]
    fn bench_render(b: &mut test::Bencher) {
        let template = Template::new()
            .template(SOURCE)
            .value("year", 2016)
            .value("items", create_items())
            .compile()
            .unwrap();

        b.iter(|| template.render().unwrap())
    }

    #[bench]
    fn bench_render_without_compile(b: &mut test::Bencher) {
        let items = create_items();

        b.iter(|| {
            Template::new()
                .template(SOURCE)
                .value("year", 2016)
                .value("items", items.clone())
                .render()
                .unwrap();
        })
    }
}