extern crate nom; use crate::renderer::CompareContextElement; use parser::Filter; use parser::OwnedLiteral; use renderer::compile_template; use renderer::CompiledTemplate; use renderer::ContextElement; use renderer::DustRenderer; use renderer::Loopable; use renderer::RenderError; use renderer::Renderable; use renderer::WalkError; use renderer::Walkable; use std::cmp::Ordering; 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, WalkError> { match self { serde_json::Value::Null => Err(WalkError::CantWalk), serde_json::Value::Bool(_boolean) => Err(WalkError::CantWalk), serde_json::Value::Number(_num) => Err(WalkError::CantWalk), serde_json::Value::String(_string) => Err(WalkError::CantWalk), serde_json::Value::Array(_arr) => Err(WalkError::CantWalk), serde_json::Value::Object(obj) => obj .get(segment) .map(|val| val as _) .ok_or(WalkError::CantWalk), } } } impl Loopable for serde_json::Value { fn get_loop_elements(&self) -> Vec<&dyn ContextElement> { match self { serde_json::Value::Null => Vec::new(), serde_json::Value::Bool(boolean) => { if *boolean { vec![self] } else { Vec::new() } } serde_json::Value::Number(_num) => vec![self], serde_json::Value::String(string_value) => { if string_value.is_empty() { Vec::new() } else { vec![self] } } serde_json::Value::Array(array_value) => { if array_value.is_empty() { Vec::new() } else { array_value.iter().map(|x| x as _).collect() } } serde_json::Value::Object(_obj) => vec![self], } } } impl CompareContextElement for serde_json::Value { fn equals(&self, other: &dyn ContextElement) -> bool { // println!("equals json {:?} | {:?}", self, other); // Handle other serde_json::Value match other.to_any().downcast_ref::() { None => (), Some(other_json_value) => match (self, other_json_value) { // Non-scalar values not caught in the renderer by the // identical-path shortcut are always not equal. (serde_json::Value::Array(_), _) | (_, serde_json::Value::Array(_)) | (serde_json::Value::Object(_), _) | (_, serde_json::Value::Object(_)) => return false, _ => return self == other_json_value, }, } // Handle literals match other.to_any().downcast_ref::() { None => (), Some(OwnedLiteral::LString(other_string)) => { return self.as_str().map_or(false, |s| s == other_string) } Some(OwnedLiteral::LPositiveInteger(other_num)) => { return self.as_u64().map_or(false, |n| n == *other_num) } } false } fn partial_compare(&self, other: &dyn ContextElement) -> Option { // println!("partial_compare json {:?} | {:?}", self, other); // Handle type coerced objects // When doing a greater than or less than comparison, // javascript coerces objects into "[object Object]". if let serde_json::Value::Object(_) = self { return OwnedLiteral::LString("[object Object]".to_owned()).partial_compare(other); } // Handle other serde_json::Value match other.to_any().downcast_ref::() { None => (), Some(other_json_value) => { return match (self, other_json_value) { ( serde_json::Value::Bool(self_boolean), serde_json::Value::Bool(other_boolean), ) => self_boolean.partial_cmp(other_boolean), ( serde_json::Value::Number(self_number), serde_json::Value::Number(other_number), ) => return compare_json_numbers(self_number, other_number), ( serde_json::Value::String(self_string), serde_json::Value::Number(other_number), ) => return compare_json_numbers(self_string, other_number), ( serde_json::Value::Number(self_number), serde_json::Value::String(other_string), ) => return compare_json_numbers(self_number, other_string), ( serde_json::Value::String(self_string), serde_json::Value::String(other_string), ) => self_string.partial_cmp(other_string), ( serde_json::Value::Array(self_array), serde_json::Value::Array(other_array), ) => { return self .render(&Vec::new()) .unwrap_or("".to_owned()) .partial_cmp( &other_json_value .render(&Vec::new()) .unwrap_or("".to_owned()), ) } _ => None, }; } } // Handle literals match other.to_any().downcast_ref::() { None => (), Some(other_literal) => match (self, other_literal) { (serde_json::Value::String(self_string), OwnedLiteral::LString(other_string)) => { return self_string.partial_cmp(other_string) } ( serde_json::Value::String(self_string), OwnedLiteral::LPositiveInteger(other_num), ) => return compare_json_numbers(self_string, other_literal), (serde_json::Value::Number(self_num), OwnedLiteral::LString(other_string)) => { return compare_json_numbers(self_num, other_string) } ( serde_json::Value::Number(self_num), OwnedLiteral::LPositiveInteger(other_num), ) => return compare_json_numbers(self_num, other_literal), (serde_json::Value::Array(_), _) => { // TODO todo!() } (serde_json::Value::Object(_), _) => { // TODO todo!() } (serde_json::Value::Bool(_), _) => { // TODO todo!() } (serde_json::Value::Null, _) => { // TODO todo!() } }, } None } } /// Create a new vec by of references to the serde_json::Values as /// ContextElement trait objects so we can use its implementation of /// PartialOrd. /// /// You cannot implement a trait you do not define for a type you do /// not define, so I cannot implement PartialOrd for /// serde_json::value. Instead, I just re-use the PartialOrd /// implementation for ContextElement which unfortunately has extra /// overhead of downcasting. This would be a good spot for /// optimization. fn convert_vec_to_context_element(array: &Vec) -> Vec<&dyn ContextElement> { array.iter().map(|v| v as _).collect() } #[derive(Debug)] enum JsonNumber { UnsignedInteger(u64), SignedInteger(i64), Decimal(f64), Failure, } impl From<&String> for JsonNumber { fn from(original: &String) -> Self { match original.parse::() { Ok(num) => return JsonNumber::UnsignedInteger(num), Err(_) => (), }; match original.parse::() { Ok(num) => return JsonNumber::SignedInteger(num), Err(_) => (), }; match original.parse::() { Ok(num) => return JsonNumber::Decimal(num), Err(_) => (), }; JsonNumber::Failure } } impl From<&serde_json::Number> for JsonNumber { fn from(original: &serde_json::Number) -> Self { match original.as_u64() { Some(num) => return JsonNumber::UnsignedInteger(num), None => (), }; match original.as_i64() { Some(num) => return JsonNumber::SignedInteger(num), None => (), }; match original.as_f64() { Some(num) => return JsonNumber::Decimal(num), None => (), }; JsonNumber::Failure } } impl From<&OwnedLiteral> for JsonNumber { fn from(original: &OwnedLiteral) -> Self { match original { OwnedLiteral::LPositiveInteger(num) => JsonNumber::UnsignedInteger(*num), OwnedLiteral::LString(text) => text.into(), } } } /// Compare json numbers /// /// While this function can be called with two strings, it would not /// make sense because javascript does not use numeric comparisons for /// strings fn compare_json_numbers(self_input: S, other_input: O) -> Option where S: Into, O: Into, { let self_number: JsonNumber = self_input.into(); let other_number: JsonNumber = other_input.into(); // println!( // "compare_number_and_string {:?} | {:?}", // self_number, other_number // ); // TODO: Figure out how javascript compares floats and ints match (self_number, other_number) { (JsonNumber::Failure, _) => return None, (_, JsonNumber::Failure) => return None, (JsonNumber::UnsignedInteger(self_num), JsonNumber::UnsignedInteger(other_num)) => { return self_num.partial_cmp(&other_num) } (JsonNumber::UnsignedInteger(self_num), JsonNumber::SignedInteger(other_num)) => { return Some(Ordering::Greater) } (JsonNumber::UnsignedInteger(self_num), JsonNumber::Decimal(other_num)) => return None, (JsonNumber::SignedInteger(self_num), JsonNumber::UnsignedInteger(other_num)) => { return Some(Ordering::Less) } (JsonNumber::SignedInteger(self_num), JsonNumber::SignedInteger(other_num)) => { return self_num.partial_cmp(&other_num) } (JsonNumber::SignedInteger(self_num), JsonNumber::Decimal(other_num)) => return None, (JsonNumber::Decimal(self_num), JsonNumber::UnsignedInteger(other_num)) => return None, (JsonNumber::Decimal(self_num), JsonNumber::SignedInteger(other_num)) => return None, (JsonNumber::Decimal(self_num), JsonNumber::Decimal(other_num)) => { return self_num.partial_cmp(&other_num) } } None } #[cfg(test)] mod tests { use super::*; #[test] fn test_nested_array_render() { let x: serde_json::Value = serde_json::from_str(r#"[3,5,[7,9]]"#).expect("Failed to parse json"); assert_eq!( x.render(&Vec::new()), Ok::<_, RenderError>("3,5,7,9".to_owned()) ); } }