trimmer 0.3.6

A whitespace- and memory-friendly template engine
Documentation
extern crate trimmer;
extern crate argparse;
#[cfg(feature="json")] extern crate serde_json;

use std::io::{Read, Write, stdout};
use std::fs::File;
use std::path::{Path};
use std::process::exit;

use trimmer::Parser;
#[cfg(feature="json")] use serde_json::Value;


fn main() {
    let mut vars = Vec::<String>::new();
    let mut templates = Vec::<String>::new();
    #[cfg(feature="json")]
    let mut json_vars = Vec::<String>::new();
    let mut output = None::<String>;
    {
        use argparse::*;
        let mut ap = ArgumentParser::new();
        ap.refer(&mut templates)
            .add_argument("template", Collect,
                "Templates to check or render. \
                 If `-o` specified then rendering mode is activated and \
                 only one template is allowed");
        ap.refer(&mut output)
            .add_option(&["-o", "--render-to-file"], ParseOption,
                "Output file to render to. \
                 If specified template will be rendered rather than just \
                 syntax checked. To render to stdout use dash (`-o-`)");
        ap.refer(&mut vars)
            .add_option(&["-D", "--var"], Collect,
                "Define a string variable. Only useful if `-o-` is also
                 specified");
        ap.add_option(&["--version"],
            Print(env!("CARGO_PKG_VERSION").into()),
            "Print version and exit");
        #[cfg(feature="json")]
        {
            ap.refer(&mut json_vars)
                .add_option(&["-J", "--json"], Collect,
                    "Define set of variables using json dict (object).
                     This option is repeatable, where latter objects
                     override former. `-D` args override json variables");
        }
        ap.parse_args_or_exit();
    }
    let parser = Parser::new();
    if let Some(out_file) = output {
        if templates.len() != 1 {
            eprintln!("Exactly one template might be specified when in \
                       render mode (with `-o`/`--render-to_file`)");
            exit(1);
        }
        let path = Path::new(&templates[0]);

        #[cfg(feature="json")]
        let parsed_jsons = {
            let mut v = Vec::new();
            for val in &json_vars {
                match serde_json::from_str::<Value>(val) {
                    Ok(Value::Object(map)) => v.push(map),
                    Ok(val) => {
                        eprintln!("Json must contain object, not {:?}", val);
                        exit(1);
                    }
                    Err(e) => {
                        eprintln!("Can't parse json: {}", e);
                        exit(1);
                    }
                }
            }
            v
        };


        let mut parsed_vars = Vec::new();
        for pair in vars {
            let mut piter = pair.splitn(2, '=');
            match (piter.next(), piter.next()) {
                (Some(""), _) | (None, _) => {
                    eprintln!("Var name must not be empty in {:?}", pair);
                    exit(1);
                }
                (_, None) => {
                    eprintln!("Var {:?} must contain equals sign", pair);
                    exit(1);
                }
                (Some(x), Some(y)) => {
                    parsed_vars.push((x.to_string(), y.to_string()));
                }
            }
        }

        let mut buf = String::with_capacity(1024);
        let read = File::open(path)
            .and_then(|mut f| f.read_to_string(&mut buf));
        match read {
            Ok(_) => {},
            Err(e) => {
                eprintln!("Error reading {:?}: {}", path, e);
                exit(1);
            }
        }
        let template = match parser.parse(&buf) {
            Ok(tpl) => tpl,
            Err(e) => {
                eprintln!("Error parsing {:?}: {}", path, e);
                exit(2);
            }
        };

        let mut context = trimmer::Context::new();
        #[cfg(feature="json")]
        for map in &parsed_jsons {
            for (k, v) in map {
                context.set(k, v);
            }
        }
        for &(ref key, ref value) in &parsed_vars {
            context.set(key, value);
        }

        let buf = match template.render(&context) {
            Ok(value) => value,
            Err(e) => {
                eprintln!("Error rendering {:?}: {}", path, e);
                exit(3);
            }
        };

        let res = if out_file == "-" {
            stdout().write_all(buf.as_bytes())
        } else {
            File::create(&out_file)
                .and_then(|mut f| f.write_all(&buf.as_bytes()))
        };

        match res {
            Ok(()) => {}
            Err(e) => {
                eprintln!("Error writing output {:?}: {}", out_file, e);
                exit(3);
            }
        }

    } else {

        #[cfg(not(feature="json"))]
        let has_vars = vars.len() > 0;
        #[cfg(feature="json")]
        let has_vars = vars.len() > 0 || json_vars.len() > 0;
        if has_vars {
            eprintln!("No vars allowed in syntax check mode. \
                (Use `-o`/`--render-to-file` to render template)");
            exit(1);
        }

        let mut buf = String::with_capacity(4096);
        let mut code = 0;

        for template in templates {
            let path = Path::new(&template);
            if path.is_dir() {
                eprintln!("{:?} is a directory. \
                    Scanning directories is not implemented.", path);
                code = 1;
            }
            buf.truncate(0);
            let read = File::open(path)
                .and_then(|mut f| f.read_to_string(&mut buf));
            match read {
                Ok(_) => {},
                Err(e) => {
                    eprintln!("Error reading {:?}: {}", path, e);
                    code = 1;
                }
            }
            match parser.parse(&buf) {
                Ok(_) => {}
                Err(e) => {
                    eprintln!("Error parsing {:?}: {}", path, e);
                    code = 2;
                }
            }
        }
        exit(code);
    }
}