extern crate nom; use crate::renderer::CompareContextElement; use parser::Filter; use parser::OwnedLiteral; use parser::Template; use renderer::compile_template; use renderer::CompileError; use renderer::ContextElement; use renderer::DustRenderer; use renderer::IntoContextElement; use renderer::Loopable; use renderer::RenderError; use renderer::Renderable; use renderer::Truthiness; 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 IntoContextElement]; // println!( // "{}", // dust_renderer // .render(main_template_name, &breadcrumbs) // .expect("Failed to render") // ); } fn template_from_file<'a>( file_path: &str, file_contents: &'a str, ) -> Result<(String, Template<'a>), CompileError> { let path: &Path = Path::new(file_path); let name = path.file_stem().ok_or(CompileError { message: format!("Failed to get file stem on {}", file_path), })?; Ok(( name.to_string_lossy().to_string(), compile_template(file_contents)?, )) } 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") } fn html_escape(inp: &str) -> String { // Adding 10% space from the original to avoid re-allocations by // leaving room for escaped sequences. let mut output = String::with_capacity(((inp.len() as f64) * 1.1) as usize); inp.chars().for_each(|c| match c { '<' => output.push_str("<"), '>' => output.push_str(">"), '"' => output.push_str("""), '\'' => output.push_str("'"), '&' => output.push_str("&"), _ => output.push(c), }); output } fn javascript_escape(inp: &str) -> String { // Adding 10% space from the original to avoid re-allocations by // leaving room for escaped sequences. let mut output = String::with_capacity(((inp.len() as f64) * 1.1) as usize); inp.chars().for_each(|c| match c { '"' => output.push_str(r#"\""#), '\'' => output.push_str(r#"\'"#), '\t' => output.push_str(r#"\t"#), '\x0C' => output.push_str(r#"\f"#), '\n' => output.push_str(r#"\n"#), '\r' => output.push_str(r#"\r"#), '\\' => output.push_str(r#"\\"#), '/' => output.push_str(r#"\/"#), _ => output.push(c), }); output } fn get_utf8_hex(inp: char) -> String { let num_bytes = inp.len_utf8(); let mut byte_buffer = [0; 4]; // UTF-8 supports up to 4 bytes per codepoint let mut output = String::with_capacity(num_bytes * 2); inp.encode_utf8(&mut byte_buffer); for b in &byte_buffer[..num_bytes] { output.push_str(&format!("{:02X}", b)); } output } fn encode_uri(inp: &str) -> String { // Adding 10% space from the original to avoid re-allocations by // leaving room for escaped sequences. let mut output = String::with_capacity(((inp.len() as f64) * 1.1) as usize); inp.chars().for_each(|c| match c { '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j' | 'k' | 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w' | 'x' | 'y' | 'z' | 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' | 'I' | 'J' | 'K' | 'L' | 'M' | 'N' | 'O' | 'P' | 'Q' | 'R' | 'S' | 'T' | 'U' | 'V' | 'W' | 'X' | 'Y' | 'Z' | ';' | ',' | '/' | '?' | ':' | '@' | '&' | '=' | '+' | '$' | '-' | '_' | '.' | '!' | '~' | '*' | '\'' | '(' | ')' | '#' => output.push(c), _ => { output.push('%'); output.push_str(&get_utf8_hex(c)); } }); output } fn encode_uri_component(inp: &str) -> String { // Adding 10% space from the original to avoid re-allocations by // leaving room for escaped sequences. let mut output = String::with_capacity(((inp.len() as f64) * 1.1) as usize); inp.chars().for_each(|c| match c { '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j' | 'k' | 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w' | 'x' | 'y' | 'z' | 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' | 'I' | 'J' | 'K' | 'L' | 'M' | 'N' | 'O' | 'P' | 'Q' | 'R' | 'S' | 'T' | 'U' | 'V' | 'W' | 'X' | 'Y' | 'Z' | '-' | '_' | '.' | '!' | '~' | '*' | '\'' | '(' | ')' => { output.push(c) } _ => { output.push('%'); output.push_str(&get_utf8_hex(c)); } }); output } // fn apply_filter( // json_value: &serde_json::Value, // filter: &Filter, // ) -> Result { // match (json_value, filter) { // // Html escape filter // (serde_json::Value::String(string), Filter::HtmlEncode) => { // Ok(serde_json::Value::String(html_escape(string))) // } // (_, Filter::HtmlEncode) => Ok(serde_json::Value::String(html_escape( // &json_value.render(&Vec::new())?, // ))), // // Disable html escape filter // (_, Filter::DisableHtmlEncode) => panic!("The |s filter is automatically removed by the renderer since it is a no-op during rendering."), // // Parse JSON filter // (serde_json::Value::String(string), Filter::JsonParse) => { // serde_json::from_str(&string).or(Err(RenderError::InvalidJson(string.to_owned()))) // } // (_, Filter::JsonParse) => { // let rendered_value = json_value.render(&Vec::new())?; // serde_json::from_str(&rendered_value).or(Err(RenderError::InvalidJson(rendered_value))) // } // // Json Stringify filter // (_, Filter::JsonStringify) => { // Ok(serde_json::Value::String(json_value.to_string())) // } // // Javascript escape filter // (serde_json::Value::String(string), Filter::JavascriptStringEncode) => { // Ok(serde_json::Value::String(javascript_escape(string))) // } // (serde_json::Value::Bool(boolean), Filter::JavascriptStringEncode) => { // Ok(serde_json::Value::Bool(*boolean)) // } // (serde_json::Value::Number(number), Filter::JavascriptStringEncode) => { // Ok(serde_json::Value::Number(number.clone())) // } // (serde_json::Value::Array(arr), Filter::JavascriptStringEncode) => { // Ok(serde_json::Value::Array(arr.clone())) // } // (serde_json::Value::Object(obj), Filter::JavascriptStringEncode) => { // Ok(serde_json::Value::Object(obj.clone())) // } // (_, Filter::JavascriptStringEncode) => Ok(serde_json::Value::String(javascript_escape( // &json_value.render(&Vec::new())?, // ))), // // EncodeURI filter // (serde_json::Value::String(string), Filter::EncodeUri) => { // Ok(serde_json::Value::String(encode_uri(string))) // } // (_, Filter::EncodeUri) => Ok(serde_json::Value::String(encode_uri( // &json_value.render(&Vec::new())?, // ))), // // EncodeURIComponent filter // (serde_json::Value::String(string), Filter::EncodeUriComponent) => { // Ok(serde_json::Value::String(encode_uri_component(string))) // } // (_, Filter::EncodeUriComponent) => Ok(serde_json::Value::String(encode_uri_component( // &json_value.render(&Vec::new())?, // ))), // } // } // fn apply_filters( // json_value: &serde_json::Value, // filters: &[Filter], // ) -> Result { // let mut final_value: serde_json::Value = apply_filter(json_value, &filters[0])?; // for filter in &filters[1..] { // final_value = apply_filter(&final_value, filter)?; // } // Ok(final_value) // } // impl ContextElement for serde_json::Value {} // impl Truthiness for serde_json::Value { // fn is_truthy(&self) -> bool { // match self { // serde_json::Value::Null => false, // serde_json::Value::Bool(boolean) => *boolean, // serde_json::Value::Number(_num) => true, // serde_json::Value::String(string_value) => !string_value.is_empty(), // serde_json::Value::Array(array_value) => !array_value.is_empty(), // serde_json::Value::Object(_obj) => true, // } // } // } // impl Renderable for serde_json::Value { // fn render(&self, _filters: &Vec) -> Result { // let after_apply = if _filters.is_empty() { // None // } else { // Some(apply_filters(self, _filters)?) // }; // match after_apply.as_ref().unwrap_or(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 IntoContextElement, 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::Array(array_value) => array_value.iter().map(|x| x as _).collect(), // _ => Vec::new(), // } // } // } // 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(self.render(&Vec::new()).unwrap_or("".to_owned())) // .partial_compare(other); // } // // When doing a greater than or less than comparison // // javascript turns arrays into strings. // if let serde_json::Value::Array(_) = self { // return OwnedLiteral::LString(self.render(&Vec::new()).unwrap_or("".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()) ); } #[test] fn test_html_escape() { assert_eq!(html_escape("<>&\"'"), "<>&"'".to_owned()) } }