duster/src/bin.rs

259 lines
10 KiB
Rust
Raw Normal View History

extern crate nom;
2020-05-10 23:16:55 +00:00
use crate::renderer::CompareContextElement;
use parser::Filter;
use renderer::compile_template;
use renderer::CompiledTemplate;
use renderer::ContextElement;
2020-04-11 00:58:55 +00:00
use renderer::DustRenderer;
use renderer::Loopable;
use renderer::RenderError;
use renderer::Renderable;
2020-05-09 18:18:45 +00:00
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<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 22:25:48 +00:00
let mut dust_renderer = DustRenderer::new();
compiled_templates.iter().for_each(|template| {
2020-04-11 22:25:48 +00:00
dust_renderer.load_source(template);
});
2020-04-11 22:25:48 +00:00
let main_template_name = &compiled_templates
.first()
.expect("There should be more than 1 template")
.name;
2020-05-05 23:51:07 +00:00
let breadcrumbs = vec![&context as &dyn ContextElement];
2020-04-11 22:25:48 +00:00
println!(
"{}",
dust_renderer
.render(main_template_name, &breadcrumbs)
2020-04-11 22:25:48 +00:00
.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())
2020-04-11 22:25:48 +00:00
.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 {
2020-04-13 01:57:42 +00:00
fn render(&self, _filters: &Vec<Filter>) -> Result<String, RenderError> {
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<Vec<String>, 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 {
2020-05-09 18:18:45 +00:00
fn walk(&self, segment: &str) -> Result<&dyn ContextElement, WalkError> {
match self {
2020-05-09 18:18:45 +00: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),
}
}
}
impl Loopable for serde_json::Value {
2020-05-09 18:14:22 +00:00
fn get_loop_elements(&self) -> Vec<&dyn ContextElement> {
match self {
2020-05-09 18:18:45 +00:00
serde_json::Value::Null => Vec::new(),
serde_json::Value::Bool(boolean) => {
if *boolean {
2020-05-09 18:18:45 +00:00
vec![self]
} else {
2020-05-09 18:18:45 +00:00
Vec::new()
}
}
2020-05-09 18:18:45 +00:00
serde_json::Value::Number(_num) => vec![self],
serde_json::Value::String(string_value) => {
if string_value.is_empty() {
2020-05-09 18:18:45 +00:00
Vec::new()
} else {
2020-05-09 18:18:45 +00:00
vec![self]
}
}
serde_json::Value::Array(array_value) => {
if array_value.is_empty() {
2020-05-09 18:18:45 +00:00
Vec::new()
} else {
2020-05-09 18:18:45 +00:00
array_value.iter().map(|x| x as _).collect()
}
}
2020-05-09 18:18:45 +00:00
serde_json::Value::Object(_obj) => vec![self],
}
}
}
2020-05-11 02:04:41 +00:00
impl CompareContextElement for serde_json::Value {
fn equals(&self, other: &dyn ContextElement) -> bool {
// Handle other serde_json::Value
match other.to_any().downcast_ref::<Self>() {
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 string literals
match other.to_any().downcast_ref::<String>() {
None => (),
Some(other_string) => return self.as_str().map_or(false, |s| s == other_string),
2020-05-11 02:14:07 +00:00
}
// Handle numeric literals
match other.to_any().downcast_ref::<u64>() {
None => (),
Some(other_num) => return self.as_u64().map_or(false, |n| n == *other_num),
}
false
2020-05-11 02:04:41 +00:00
}
fn partial_compare(&self, other: &dyn ContextElement) -> Option<Ordering> {
// Handle other serde_json::Value
match other.to_any().downcast_ref::<Self>() {
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),
) => 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),
(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)),
_ => None,
};
}
}
// Handle string literals
match other.to_any().downcast_ref::<String>() {
None => (),
Some(other_string) => {
return self
.as_str()
.map_or(None, |s| s.partial_cmp(other_string.as_str()))
}
}
// Handle numeric literals
match other.to_any().downcast_ref::<u64>() {
None => (),
Some(other_num) => return self.as_u64().map_or(None, |n| n.partial_cmp(other_num)),
}
None
}
2020-05-11 02:04:41 +00: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()
}