extern crate nom; use parser::Filter; use renderer::compile_template; use renderer::CompiledTemplate; use renderer::ContextElement; use renderer::DustRenderer; use renderer::Loopable; use renderer::RenderError; use renderer::Renderable; use renderer::Walkable; use std::env; use std::fs; use std::io::{self, Read}; use std::path::Path; mod parser; mod renderer; fn main() { let context = read_context_from_stdin(); let argv: Vec = env::args().collect(); if argv.len() < 2 { panic!("Need to pass templates"); } let template_paths = &argv[1..]; let template_contents: Vec<(String, String)> = template_paths .iter() .map(|p| { let template_content = fs::read_to_string(&p).unwrap(); (p.to_string(), template_content) }) .collect(); let compiled_templates: Vec = template_contents .iter() .map(|(p, contents)| template_from_file(p, contents)) .collect(); let mut dust_renderer = DustRenderer::new(); compiled_templates.iter().for_each(|template| { dust_renderer.load_source(template); }); let main_template_name = &compiled_templates .first() .expect("There should be more than 1 template") .name; let breadcrumbs = vec![&context as &dyn ContextElement]; println!( "{}", dust_renderer .render(main_template_name, &breadcrumbs) .expect("Failed to render") ); } fn template_from_file<'a>(file_path: &str, file_contents: &'a str) -> CompiledTemplate<'a> { let path: &Path = Path::new(file_path); let name = path.file_stem().unwrap(); compile_template(file_contents, name.to_string_lossy().to_string()) .expect("Failed to compile template") } fn read_context_from_stdin() -> serde_json::Value { let mut buffer = String::new(); io::stdin() .read_to_string(&mut buffer) .expect("Failed to read stdin"); serde_json::from_str(&buffer).expect("Failed to parse json") } impl ContextElement for serde_json::Value {} impl Renderable for serde_json::Value { fn render(&self, _filters: &Vec) -> Result { match self { serde_json::Value::Null => Ok("".to_owned()), serde_json::Value::Bool(boolean) => Ok(boolean.to_string()), serde_json::Value::Number(num) => Ok(num.to_string()), serde_json::Value::String(string) => Ok(string.to_string()), serde_json::Value::Array(arr) => { // TODO: Handle the filters instead of passing a Vec::new() let rendered: Result, RenderError> = arr.iter().map(|val| val.render(&Vec::new())).collect(); let rendered_slice: &[String] = &rendered?; Ok(rendered_slice.join(",")) } serde_json::Value::Object(_obj) => Ok("[object Object]".to_owned()), } } } impl Walkable for serde_json::Value { fn walk(&self, segment: &str) -> Result<&dyn ContextElement, RenderError> { match self { serde_json::Value::Null => Err(RenderError::CantWalk { segment: segment.to_string(), elem: self, }), serde_json::Value::Bool(_boolean) => Err(RenderError::CantWalk { segment: segment.to_string(), elem: self, }), serde_json::Value::Number(_num) => Err(RenderError::CantWalk { segment: segment.to_string(), elem: self, }), serde_json::Value::String(_string) => Err(RenderError::CantWalk { segment: segment.to_string(), elem: self, }), serde_json::Value::Array(_arr) => Err(RenderError::CantWalk { segment: segment.to_string(), elem: self, }), serde_json::Value::Object(obj) => { obj.get(segment) .map(|val| val as _) .ok_or(RenderError::CantWalk { segment: segment.to_string(), elem: self, }) } } } } impl Loopable for serde_json::Value { fn get_loop_elements(&self) -> Vec<&dyn ContextElement> { match self { serde_json::Value::Null => Ok(Vec::new()), serde_json::Value::Bool(boolean) => { if *boolean { Ok(vec![self]) } else { Ok(Vec::new()) } } serde_json::Value::Number(_num) => Ok(vec![self]), serde_json::Value::String(string_value) => { if string_value.is_empty() { Ok(Vec::new()) } else { Ok(vec![self]) } } serde_json::Value::Array(array_value) => { if array_value.is_empty() { Ok(Vec::new()) } else { Ok(array_value.iter().map(|x| x as _).collect()) } } serde_json::Value::Object(_obj) => Ok(vec![self]), } } }