2020-04-05 19:12:48 -04:00
|
|
|
extern crate nom;
|
2020-04-10 19:07:02 -04:00
|
|
|
|
2020-05-10 19:16:55 -04:00
|
|
|
use crate::renderer::CompareContextElement;
|
2020-04-12 21:54:15 -04:00
|
|
|
use parser::Filter;
|
2020-05-16 22:30:04 -04:00
|
|
|
use parser::OwnedLiteral;
|
2020-04-10 20:27:27 -04:00
|
|
|
use renderer::compile_template;
|
|
|
|
use renderer::CompiledTemplate;
|
2020-04-11 22:47:31 -04:00
|
|
|
use renderer::ContextElement;
|
2020-04-10 20:58:55 -04:00
|
|
|
use renderer::DustRenderer;
|
2020-04-28 19:34:52 -04:00
|
|
|
use renderer::Loopable;
|
2020-04-12 18:29:40 -04:00
|
|
|
use renderer::RenderError;
|
2020-04-11 19:11:14 -04:00
|
|
|
use renderer::Renderable;
|
2020-05-09 14:18:45 -04:00
|
|
|
use renderer::WalkError;
|
2020-04-11 22:47:31 -04:00
|
|
|
use renderer::Walkable;
|
2020-05-16 13:31:52 -04:00
|
|
|
use std::cmp::Ordering;
|
2020-04-10 20:27:27 -04:00
|
|
|
use std::env;
|
|
|
|
use std::fs;
|
2020-04-10 19:07:02 -04:00
|
|
|
use std::io::{self, Read};
|
2020-04-10 20:27:27 -04:00
|
|
|
use std::path::Path;
|
2020-04-05 19:12:48 -04:00
|
|
|
|
|
|
|
mod parser;
|
2020-04-10 20:27:27 -04:00
|
|
|
mod renderer;
|
2020-04-05 19:12:48 -04:00
|
|
|
|
|
|
|
fn main() {
|
2020-04-10 19:07:02 -04:00
|
|
|
let context = read_context_from_stdin();
|
2020-04-10 20:27:27 -04:00
|
|
|
|
|
|
|
let argv: Vec<String> = 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<CompiledTemplate> = template_contents
|
|
|
|
.iter()
|
|
|
|
.map(|(p, contents)| template_from_file(p, contents))
|
|
|
|
.collect();
|
2020-04-11 18:25:48 -04:00
|
|
|
let mut dust_renderer = DustRenderer::new();
|
2020-04-10 20:55:44 -04:00
|
|
|
compiled_templates.iter().for_each(|template| {
|
2020-04-11 18:25:48 -04:00
|
|
|
dust_renderer.load_source(template);
|
2020-04-10 20:55:44 -04:00
|
|
|
});
|
2020-04-11 18:25:48 -04:00
|
|
|
let main_template_name = &compiled_templates
|
|
|
|
.first()
|
|
|
|
.expect("There should be more than 1 template")
|
|
|
|
.name;
|
2020-05-05 19:51:07 -04:00
|
|
|
let breadcrumbs = vec![&context as &dyn ContextElement];
|
2020-04-11 18:25:48 -04:00
|
|
|
println!(
|
|
|
|
"{}",
|
|
|
|
dust_renderer
|
2020-05-05 20:46:31 -04:00
|
|
|
.render(main_template_name, &breadcrumbs)
|
2020-04-11 18:25:48 -04:00
|
|
|
.expect("Failed to render")
|
|
|
|
);
|
2020-04-10 20:27:27 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
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())
|
2020-04-11 18:25:48 -04:00
|
|
|
.expect("Failed to compile template")
|
2020-04-10 19:07:02 -04:00
|
|
|
}
|
|
|
|
|
2020-04-11 22:52:20 -04:00
|
|
|
fn read_context_from_stdin() -> serde_json::Value {
|
2020-04-10 19:07:02 -04:00
|
|
|
let mut buffer = String::new();
|
|
|
|
io::stdin()
|
|
|
|
.read_to_string(&mut buffer)
|
|
|
|
.expect("Failed to read stdin");
|
|
|
|
|
2020-05-03 15:29:02 -04:00
|
|
|
serde_json::from_str(&buffer).expect("Failed to parse json")
|
2020-04-05 19:12:48 -04:00
|
|
|
}
|
2020-04-11 19:11:14 -04:00
|
|
|
|
2020-04-11 22:47:31 -04:00
|
|
|
impl ContextElement for serde_json::Value {}
|
|
|
|
|
2020-04-11 19:11:14 -04:00
|
|
|
impl Renderable for serde_json::Value {
|
2020-04-12 21:57:42 -04:00
|
|
|
fn render(&self, _filters: &Vec<Filter>) -> Result<String, RenderError> {
|
2020-04-11 19:19:40 -04:00
|
|
|
match self {
|
2020-04-12 21:03:55 -04:00
|
|
|
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()),
|
2020-04-12 21:40:34 -04:00
|
|
|
serde_json::Value::Array(arr) => {
|
2020-04-12 21:54:15 -04:00
|
|
|
// TODO: Handle the filters instead of passing a Vec::new()
|
2020-04-12 21:40:34 -04:00
|
|
|
let rendered: Result<Vec<String>, RenderError> =
|
2020-04-12 21:54:15 -04:00
|
|
|
arr.iter().map(|val| val.render(&Vec::new())).collect();
|
2020-04-12 21:40:34 -04:00
|
|
|
let rendered_slice: &[String] = &rendered?;
|
|
|
|
Ok(rendered_slice.join(","))
|
|
|
|
}
|
|
|
|
serde_json::Value::Object(_obj) => Ok("[object Object]".to_owned()),
|
2020-04-11 19:19:40 -04:00
|
|
|
}
|
2020-04-11 19:11:14 -04:00
|
|
|
}
|
|
|
|
}
|
2020-04-11 22:47:31 -04:00
|
|
|
|
|
|
|
impl Walkable for serde_json::Value {
|
2020-05-09 14:18:45 -04:00
|
|
|
fn walk(&self, segment: &str) -> Result<&dyn ContextElement, WalkError> {
|
2020-04-11 22:47:31 -04:00
|
|
|
match self {
|
2020-05-09 14:18:45 -04:00
|
|
|
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),
|
2020-04-11 22:47:31 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-04-28 19:34:52 -04:00
|
|
|
|
|
|
|
impl Loopable for serde_json::Value {
|
2020-05-09 14:14:22 -04:00
|
|
|
fn get_loop_elements(&self) -> Vec<&dyn ContextElement> {
|
2020-04-28 19:34:52 -04:00
|
|
|
match self {
|
2020-05-09 14:18:45 -04:00
|
|
|
serde_json::Value::Null => Vec::new(),
|
2020-04-28 19:34:52 -04:00
|
|
|
serde_json::Value::Bool(boolean) => {
|
|
|
|
if *boolean {
|
2020-05-09 14:18:45 -04:00
|
|
|
vec![self]
|
2020-04-28 19:34:52 -04:00
|
|
|
} else {
|
2020-05-09 14:18:45 -04:00
|
|
|
Vec::new()
|
2020-04-28 19:34:52 -04:00
|
|
|
}
|
|
|
|
}
|
2020-05-09 14:18:45 -04:00
|
|
|
serde_json::Value::Number(_num) => vec![self],
|
2020-04-28 19:34:52 -04:00
|
|
|
serde_json::Value::String(string_value) => {
|
|
|
|
if string_value.is_empty() {
|
2020-05-09 14:18:45 -04:00
|
|
|
Vec::new()
|
2020-04-28 19:34:52 -04:00
|
|
|
} else {
|
2020-05-09 14:18:45 -04:00
|
|
|
vec![self]
|
2020-04-28 19:34:52 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
serde_json::Value::Array(array_value) => {
|
|
|
|
if array_value.is_empty() {
|
2020-05-09 14:18:45 -04:00
|
|
|
Vec::new()
|
2020-04-28 19:34:52 -04:00
|
|
|
} else {
|
2020-05-09 14:18:45 -04:00
|
|
|
array_value.iter().map(|x| x as _).collect()
|
2020-04-28 19:34:52 -04:00
|
|
|
}
|
|
|
|
}
|
2020-05-09 14:18:45 -04:00
|
|
|
serde_json::Value::Object(_obj) => vec![self],
|
2020-04-28 19:34:52 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-05-10 22:04:41 -04:00
|
|
|
|
|
|
|
impl CompareContextElement for serde_json::Value {
|
|
|
|
fn equals(&self, other: &dyn ContextElement) -> bool {
|
2020-05-16 23:19:02 -04:00
|
|
|
// println!("equals json {:?} | {:?}", self, other);
|
2020-05-10 22:26:47 -04:00
|
|
|
// Handle other serde_json::Value
|
|
|
|
match other.to_any().downcast_ref::<Self>() {
|
|
|
|
None => (),
|
2020-05-16 15:30:17 -04:00
|
|
|
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,
|
|
|
|
},
|
2020-05-10 22:26:47 -04:00
|
|
|
}
|
2020-05-16 23:19:02 -04:00
|
|
|
// Handle literals
|
|
|
|
match other.to_any().downcast_ref::<OwnedLiteral>() {
|
2020-05-10 22:26:47 -04:00
|
|
|
None => (),
|
2020-05-16 23:19:02 -04:00
|
|
|
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)
|
|
|
|
}
|
2020-05-10 23:11:16 -04:00
|
|
|
}
|
2020-05-10 22:26:47 -04:00
|
|
|
false
|
2020-05-10 22:04:41 -04:00
|
|
|
}
|
2020-05-16 13:31:52 -04:00
|
|
|
|
|
|
|
fn partial_compare(&self, other: &dyn ContextElement) -> Option<Ordering> {
|
2020-05-16 22:30:04 -04:00
|
|
|
// println!("partial_compare json {:?} | {:?}", self, other);
|
2020-05-16 21:30:51 -04:00
|
|
|
// 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 {
|
2020-05-16 22:30:04 -04:00
|
|
|
return OwnedLiteral::LString("[object Object]".to_owned()).partial_compare(other);
|
2020-05-16 21:30:51 -04:00
|
|
|
}
|
|
|
|
|
2020-05-16 13:40:56 -04:00
|
|
|
// Handle other serde_json::Value
|
|
|
|
match other.to_any().downcast_ref::<Self>() {
|
|
|
|
None => (),
|
2020-05-16 13:44:34 -04:00
|
|
|
Some(other_json_value) => {
|
|
|
|
return match (self, other_json_value) {
|
2020-05-16 14:08:59 -04:00
|
|
|
(
|
|
|
|
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),
|
|
|
|
) => match (
|
|
|
|
self_number.as_f64(),
|
|
|
|
other_number.as_f64(),
|
|
|
|
self_number.as_u64(),
|
|
|
|
other_number.as_u64(),
|
|
|
|
self_number.as_i64(),
|
|
|
|
other_number.as_i64(),
|
|
|
|
) {
|
|
|
|
(_, _, _, _, Some(self_int), Some(other_int)) => {
|
|
|
|
self_int.partial_cmp(&other_int)
|
|
|
|
}
|
|
|
|
(_, _, Some(self_uint), Some(other_uint), _, _) => {
|
|
|
|
self_uint.partial_cmp(&other_uint)
|
|
|
|
}
|
|
|
|
(_, _, Some(_self_uint), _, _, Some(_other_int)) => {
|
|
|
|
// If the previous matches did not catch
|
|
|
|
// it, then other must be negative and
|
|
|
|
// self must be larger than can be
|
|
|
|
// represented with an i64, therefore self
|
|
|
|
// is greater than other.
|
|
|
|
Some(Ordering::Greater)
|
|
|
|
}
|
|
|
|
(_, _, _, Some(_other_uint), Some(_self_int), _) => {
|
|
|
|
// If the previous matches did not catch
|
|
|
|
// it, then self must be negative and
|
|
|
|
// other must be larger than can be
|
|
|
|
// represented with an i64, therefore
|
|
|
|
// other is greater than self.
|
|
|
|
Some(Ordering::Less)
|
|
|
|
}
|
|
|
|
(Some(self_float), Some(other_float), _, _, _, _) => {
|
|
|
|
self_float.partial_cmp(&other_float)
|
|
|
|
}
|
|
|
|
_ => panic!("This should be impossible since u64 and i64 can both be converted to floats"),
|
|
|
|
},
|
|
|
|
(
|
|
|
|
serde_json::Value::String(self_string),
|
|
|
|
serde_json::Value::String(other_string),
|
|
|
|
) => self_string.partial_cmp(other_string),
|
2020-05-16 18:15:03 -04:00
|
|
|
(serde_json::Value::Array(self_array), serde_json::Value::Array(other_array)) => convert_vec_to_context_element(self_array).partial_cmp(&convert_vec_to_context_element(other_array)),
|
2020-05-16 13:44:34 -04:00
|
|
|
_ => None,
|
|
|
|
};
|
|
|
|
}
|
2020-05-16 13:40:56 -04:00
|
|
|
}
|
2020-05-16 23:07:05 -04:00
|
|
|
// Handle literals
|
|
|
|
match other.to_any().downcast_ref::<OwnedLiteral>() {
|
2020-05-16 13:40:56 -04:00
|
|
|
None => (),
|
2020-05-16 23:07:05 -04:00
|
|
|
Some(other_literal) => match (self, other_literal) {
|
|
|
|
(serde_json::Value::String(self_string), OwnedLiteral::LString(other_string)) => {
|
2020-05-16 21:30:51 -04:00
|
|
|
return self_string.partial_cmp(other_string)
|
|
|
|
}
|
2020-05-16 23:07:05 -04:00
|
|
|
(
|
|
|
|
serde_json::Value::String(self_string),
|
|
|
|
OwnedLiteral::LPositiveInteger(other_num),
|
|
|
|
) => return self_string.partial_cmp(&other_num.to_string()),
|
|
|
|
(
|
|
|
|
serde_json::Value::Number(self_num),
|
|
|
|
OwnedLiteral::LString(other_string),
|
2020-05-16 23:26:33 -04:00
|
|
|
) => return None,
|
2020-05-16 23:07:05 -04:00
|
|
|
(
|
|
|
|
serde_json::Value::Number(self_num),
|
|
|
|
OwnedLiteral::LPositiveInteger(other_num),
|
|
|
|
) => match (
|
|
|
|
self_num.as_u64(),
|
|
|
|
self_num.as_i64(),
|
|
|
|
self_num.as_f64(),
|
|
|
|
) {
|
|
|
|
(Some(self_uint), _, _) => {
|
|
|
|
return self_uint.partial_cmp(other_num)
|
|
|
|
}
|
|
|
|
(_, Some(self_int), _) => {
|
|
|
|
return if other_num <= &(i64::max_value() as u64) {
|
|
|
|
self_int.partial_cmp(&(*other_num as i64))
|
|
|
|
} else {
|
|
|
|
Some(Ordering::Less)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
(_, _, Some(self_float)) => {
|
|
|
|
// TODO: How does javascript compare ints and floats? I'm just going to assume a cast to a string for now.
|
|
|
|
return self_float.to_string().partial_cmp(&other_num.to_string())
|
|
|
|
}
|
|
|
|
(None, None, None) => panic!("This should be impossible since u64 and i64 can both be converted to floats")
|
|
|
|
}
|
|
|
|
(serde_json::Value::Array(_), _) => {
|
|
|
|
// TODO
|
|
|
|
todo!()
|
|
|
|
}
|
|
|
|
(serde_json::Value::Object(_), _) => {
|
|
|
|
// TODO
|
|
|
|
todo!()
|
|
|
|
}
|
|
|
|
(serde_json::Value::Bool(_), _) => {
|
|
|
|
// TODO
|
|
|
|
todo!()
|
|
|
|
}
|
|
|
|
(serde_json::Value::Null, _) => {
|
|
|
|
// TODO
|
|
|
|
todo!()
|
2020-05-16 21:30:51 -04:00
|
|
|
}
|
|
|
|
},
|
2020-05-16 13:40:56 -04:00
|
|
|
}
|
2020-05-16 13:31:52 -04:00
|
|
|
None
|
|
|
|
}
|
2020-05-10 22:04:41 -04:00
|
|
|
}
|
2020-05-16 18:15:03 -04:00
|
|
|
|
|
|
|
/// 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<serde_json::Value>) -> Vec<&dyn ContextElement> {
|
|
|
|
array.iter().map(|v| v as _).collect()
|
|
|
|
}
|
2020-05-16 21:30:51 -04:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_type_coercion_object_int() {
|
|
|
|
let x: serde_json::Value =
|
|
|
|
serde_json::from_str(r#"{"name": "cat"}"#).expect("Failed to parse json");
|
|
|
|
let y: u64 = 7;
|
2020-05-16 21:34:45 -04:00
|
|
|
// assert_eq!(x.partial_compare(&y), Some(Ordering::Greater));
|
2020-05-16 21:30:51 -04:00
|
|
|
}
|
|
|
|
}
|