Merge branch 'remove_lifetime_from_context_elements' into dust_helpers
This commit is contained in:
commit
3c85717952
22
src/bin.rs
22
src/bin.rs
@ -1,5 +1,6 @@
|
||||
extern crate nom;
|
||||
|
||||
use crate::renderer::CompareContextElement;
|
||||
use parser::Filter;
|
||||
use renderer::compile_template;
|
||||
use renderer::CompiledTemplate;
|
||||
@ -137,3 +138,24 @@ impl Loopable for serde_json::Value {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) => 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),
|
||||
}
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
@ -2,11 +2,11 @@ use nom::branch::alt;
|
||||
use nom::bytes::complete::escaped_transform;
|
||||
use nom::bytes::complete::is_a;
|
||||
use nom::bytes::complete::is_not;
|
||||
use nom::bytes::complete::{tag, take_until, take_until_parser_matches};
|
||||
use nom::bytes::complete::{tag, take_until, take_until_parser_matches, take_while};
|
||||
use nom::character::complete::line_ending;
|
||||
use nom::character::complete::multispace0;
|
||||
use nom::character::complete::one_of;
|
||||
use nom::character::complete::{space0, space1};
|
||||
use nom::character::complete::{digit1, space0, space1};
|
||||
use nom::combinator::all_consuming;
|
||||
use nom::combinator::map;
|
||||
use nom::combinator::opt;
|
||||
@ -122,6 +122,7 @@ pub struct Partial<'a> {
|
||||
pub enum RValue<'a> {
|
||||
RVPath(Path<'a>),
|
||||
RVString(String),
|
||||
RVPositiveInteger(u64),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
@ -210,11 +211,23 @@ fn path(i: &str) -> IResult<&str, Path> {
|
||||
))(i)
|
||||
}
|
||||
|
||||
/// Just digits, no signs or decimals
|
||||
fn postitive_integer_literal(i: &str) -> IResult<&str, u64> {
|
||||
map(
|
||||
verify(
|
||||
map(digit1, |number_string: &str| number_string.parse::<u64>()),
|
||||
|parse_result| parse_result.is_ok(),
|
||||
),
|
||||
|parsed_number| parsed_number.unwrap(),
|
||||
)(i)
|
||||
}
|
||||
|
||||
/// Either a literal or a path to a value
|
||||
fn rvalue(i: &str) -> IResult<&str, RValue> {
|
||||
alt((
|
||||
map(path, RValue::RVPath),
|
||||
map(quoted_string, RValue::RVString),
|
||||
map(postitive_integer_literal, RValue::RVPositiveInteger),
|
||||
))(i)
|
||||
}
|
||||
|
||||
@ -929,6 +942,29 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_literals() {
|
||||
assert_eq!(
|
||||
dust_tag(r#"{>foo a="foo" b=179/}"#),
|
||||
Ok((
|
||||
"",
|
||||
DustTag::DTPartial(Partial {
|
||||
name: "foo".to_owned(),
|
||||
params: vec![
|
||||
KVPair {
|
||||
key: "a",
|
||||
value: RValue::RVString("foo".to_owned())
|
||||
},
|
||||
KVPair {
|
||||
key: "b",
|
||||
value: RValue::RVPositiveInteger(179)
|
||||
}
|
||||
]
|
||||
})
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_helper() {
|
||||
assert_eq!(
|
||||
|
@ -1,9 +1,13 @@
|
||||
use crate::parser::Filter;
|
||||
use crate::renderer::errors::RenderError;
|
||||
use crate::renderer::errors::WalkError;
|
||||
use std::any::Any;
|
||||
use std::fmt::Debug;
|
||||
|
||||
pub trait ContextElement: Debug + Walkable + Renderable + Loopable {}
|
||||
pub trait ContextElement:
|
||||
Debug + Walkable + Renderable + Loopable + CloneIntoBoxedContextElement + CompareContextElement
|
||||
{
|
||||
}
|
||||
|
||||
pub trait Walkable {
|
||||
fn walk(&self, segment: &str) -> Result<&dyn ContextElement, WalkError>;
|
||||
@ -26,8 +30,32 @@ pub trait Loopable {
|
||||
fn get_loop_elements(&self) -> Vec<&dyn ContextElement>;
|
||||
}
|
||||
|
||||
// impl PartialEq<dyn ContextElement> for dyn ContextElement {
|
||||
// fn eq(&self, other: &dyn ContextElement) -> bool {
|
||||
// todo!()
|
||||
// }
|
||||
// }
|
||||
pub trait CastToAny {
|
||||
fn to_any(&self) -> &dyn Any;
|
||||
}
|
||||
|
||||
pub trait CompareContextElement: CastToAny {
|
||||
fn equals(&self, other: &dyn ContextElement) -> bool;
|
||||
}
|
||||
|
||||
pub trait CloneIntoBoxedContextElement {
|
||||
fn clone_to_box(&self) -> Box<dyn ContextElement>;
|
||||
}
|
||||
|
||||
impl<C: 'static + ContextElement + Clone> CloneIntoBoxedContextElement for C {
|
||||
fn clone_to_box(&self) -> Box<dyn ContextElement> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: 'static + ContextElement> CastToAny for C {
|
||||
fn to_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> PartialEq<&'b dyn ContextElement> for &'a dyn ContextElement {
|
||||
fn eq(&self, other: &&'b dyn ContextElement) -> bool {
|
||||
self.equals(*other)
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,8 @@ mod parameters_context;
|
||||
mod renderer;
|
||||
mod walking;
|
||||
|
||||
pub use context_element::CloneIntoBoxedContextElement;
|
||||
pub use context_element::CompareContextElement;
|
||||
pub use context_element::ContextElement;
|
||||
pub use context_element::Loopable;
|
||||
pub use context_element::Renderable;
|
||||
|
@ -1,7 +1,8 @@
|
||||
use crate::parser::KVPair;
|
||||
use crate::parser::{Filter, RValue};
|
||||
use crate::renderer::context_element::CompareContextElement;
|
||||
use crate::renderer::context_element::ContextElement;
|
||||
use crate::renderer::walking::walk_path;
|
||||
use crate::renderer::walking::owned_walk_path;
|
||||
use crate::renderer::Loopable;
|
||||
use crate::renderer::RenderError;
|
||||
use crate::renderer::Renderable;
|
||||
@ -9,31 +10,66 @@ use crate::renderer::WalkError;
|
||||
use crate::renderer::Walkable;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ParametersContext<'a> {
|
||||
params: HashMap<&'a str, &'a RValue<'a>>,
|
||||
breadcrumbs: &'a Vec<&'a dyn ContextElement>,
|
||||
/// Copy the data from an RValue to an Owned struct
|
||||
///
|
||||
/// In order to get comparisons to work for our `ContextElement` trait
|
||||
/// objects, we need to be able to use `std::any::Any`. Unfortunately,
|
||||
/// `Any` requires that the structs do not have a lifetime (so they
|
||||
/// will have a `'static` lifetime. This means that we cannot have a
|
||||
/// `<'a>` appended to the struct type, so the struct cannot contain
|
||||
/// any borrows. Rather than impose the copy cost in the parser, we
|
||||
/// are imposing the cost of copying the data in the renderer because
|
||||
/// the parser has no reason to not be able to reference data from the
|
||||
/// input string.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum OwnedRValue {
|
||||
RVPath(OwnedPath),
|
||||
RVString(String),
|
||||
RVPositiveInteger(u64),
|
||||
}
|
||||
|
||||
impl<'a> ParametersContext<'a> {
|
||||
pub fn new(
|
||||
breadcrumbs: &'a Vec<&'a dyn ContextElement>,
|
||||
params: &'a Vec<KVPair<'a>>,
|
||||
) -> ParametersContext<'a> {
|
||||
let param_map = params
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct OwnedPath {
|
||||
pub keys: Vec<String>,
|
||||
}
|
||||
|
||||
impl From<&RValue<'_>> for OwnedRValue {
|
||||
fn from(original: &RValue<'_>) -> Self {
|
||||
match original {
|
||||
RValue::RVString(text) => OwnedRValue::RVString(text.to_owned()),
|
||||
RValue::RVPath(path) => OwnedRValue::RVPath(OwnedPath {
|
||||
keys: path.keys.iter().map(|k| k.to_string()).collect(),
|
||||
}),
|
||||
RValue::RVPositiveInteger(num) => OwnedRValue::RVPositiveInteger(*num),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ParametersContext {
|
||||
params: HashMap<String, OwnedRValue>,
|
||||
breadcrumbs: Vec<Box<dyn ContextElement>>,
|
||||
}
|
||||
|
||||
impl ParametersContext {
|
||||
pub fn new(breadcrumbs: &Vec<&dyn ContextElement>, params: &Vec<KVPair>) -> ParametersContext {
|
||||
let owned_params: HashMap<String, OwnedRValue> = params
|
||||
.iter()
|
||||
.map(|pair: &KVPair<'a>| (pair.key, &pair.value))
|
||||
.map(|kvpair| (kvpair.key.to_string(), OwnedRValue::from(&kvpair.value)))
|
||||
.collect();
|
||||
let owned_breadcrumbs: Vec<Box<dyn ContextElement>> =
|
||||
breadcrumbs.iter().map(|ce| ce.clone_to_box()).collect();
|
||||
|
||||
ParametersContext {
|
||||
params: param_map,
|
||||
breadcrumbs: breadcrumbs,
|
||||
params: owned_params,
|
||||
breadcrumbs: owned_breadcrumbs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ContextElement for ParametersContext<'a> {}
|
||||
impl ContextElement for ParametersContext {}
|
||||
|
||||
impl<'a> Renderable for ParametersContext<'a> {
|
||||
impl Renderable for ParametersContext {
|
||||
fn render(&self, _filters: &Vec<Filter>) -> Result<String, RenderError> {
|
||||
// TODO: Would this even ever be called? Won't matter, but I'd
|
||||
// like to know. Since it is injected 1 above the current
|
||||
@ -42,7 +78,7 @@ impl<'a> Renderable for ParametersContext<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Loopable for ParametersContext<'a> {
|
||||
impl Loopable for ParametersContext {
|
||||
fn get_loop_elements(&self) -> Vec<&dyn ContextElement> {
|
||||
// TODO: Would this even ever be called? Won't matter, but I'd
|
||||
// like to know. Since it is injected 1 above the current
|
||||
@ -51,16 +87,43 @@ impl<'a> Loopable for ParametersContext<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Walkable for ParametersContext<'a> {
|
||||
impl Walkable for ParametersContext {
|
||||
fn walk(&self, segment: &str) -> Result<&dyn ContextElement, WalkError> {
|
||||
let rval = self.params.get(segment).ok_or(WalkError::CantWalk)?;
|
||||
match rval {
|
||||
RValue::RVPath(path) => walk_path(self.breadcrumbs, &path.keys),
|
||||
RValue::RVString(text) => Ok(text),
|
||||
OwnedRValue::RVPath(path) => owned_walk_path(&self.breadcrumbs, &path.keys),
|
||||
OwnedRValue::RVString(text) => Ok(text),
|
||||
OwnedRValue::RVPositiveInteger(num) => Ok(num),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for ParametersContext {
|
||||
fn clone(&self) -> Self {
|
||||
let new_params: HashMap<String, OwnedRValue> = self
|
||||
.params
|
||||
.iter()
|
||||
.map(|(k, v)| (k.clone(), v.clone()))
|
||||
.collect();
|
||||
let new_breadcrumbs: Vec<Box<dyn ContextElement>> = self
|
||||
.breadcrumbs
|
||||
.iter()
|
||||
.map(|bread| bread.clone_to_box())
|
||||
.collect();
|
||||
ParametersContext {
|
||||
params: new_params,
|
||||
breadcrumbs: new_breadcrumbs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CompareContextElement for ParametersContext {
|
||||
fn equals(&self, other: &dyn ContextElement) -> bool {
|
||||
// TODO: Does this ever happen? perhaps I should have a panic here.
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl ContextElement for String {}
|
||||
|
||||
impl Renderable for String {
|
||||
@ -84,3 +147,47 @@ impl Walkable for String {
|
||||
Err(WalkError::CantWalk)
|
||||
}
|
||||
}
|
||||
|
||||
impl CompareContextElement for String {
|
||||
fn equals(&self, other: &dyn ContextElement) -> bool {
|
||||
// If its a String then compare them directly, otherwise defer
|
||||
// to the other type's implementation of CompareContextElement
|
||||
// since the end user could add any type.
|
||||
match other.to_any().downcast_ref::<Self>() {
|
||||
None => other.equals(self),
|
||||
Some(other_string) => self == other_string,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ContextElement for u64 {}
|
||||
|
||||
impl Renderable for u64 {
|
||||
fn render(&self, _filters: &Vec<Filter>) -> Result<String, RenderError> {
|
||||
Ok(self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl Loopable for u64 {
|
||||
fn get_loop_elements(&self) -> Vec<&dyn ContextElement> {
|
||||
vec![self]
|
||||
}
|
||||
}
|
||||
|
||||
impl Walkable for u64 {
|
||||
fn walk(&self, segment: &str) -> Result<&dyn ContextElement, WalkError> {
|
||||
Err(WalkError::CantWalk)
|
||||
}
|
||||
}
|
||||
|
||||
impl CompareContextElement for u64 {
|
||||
fn equals(&self, other: &dyn ContextElement) -> bool {
|
||||
// If its a u64 then compare them directly, otherwise defer
|
||||
// to the other type's implementation of CompareContextElement
|
||||
// since the end user could add any type.
|
||||
match other.to_any().downcast_ref::<Self>() {
|
||||
None => other.equals(self),
|
||||
Some(other_num) => self == other_num,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -235,6 +235,7 @@ impl<'a> DustRenderer<'a> {
|
||||
Some(rval) => match rval {
|
||||
RValue::RVString(text) => Ok(text),
|
||||
RValue::RVPath(path) => walk_path(breadcrumbs, &path.keys),
|
||||
RValue::RVPositiveInteger(num) => Ok(num),
|
||||
},
|
||||
};
|
||||
let right_side: Result<&dyn ContextElement, WalkError> =
|
||||
@ -243,16 +244,26 @@ impl<'a> DustRenderer<'a> {
|
||||
Some(rval) => match rval {
|
||||
RValue::RVString(text) => Ok(text),
|
||||
RValue::RVPath(path) => walk_path(breadcrumbs, &path.keys),
|
||||
RValue::RVPositiveInteger(num) => Ok(num),
|
||||
},
|
||||
};
|
||||
// let x = WalkError::CantWalk;
|
||||
// let y = WalkError::CantWalk;
|
||||
// if x == y {
|
||||
// panic!("placeholder");
|
||||
// }
|
||||
// if left_side.unwrap() == right_side.unwrap() {
|
||||
// panic!("placeholder");
|
||||
// }
|
||||
if left_side == right_side {
|
||||
match ¶meterized_block.contents {
|
||||
None => return Ok("".to_owned()),
|
||||
Some(body) => {
|
||||
let rendered_content = self.render_body(body, breadcrumbs, blocks)?;
|
||||
return Ok(rendered_content);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match ¶meterized_block.else_contents {
|
||||
None => return Ok("".to_owned()),
|
||||
Some(body) => {
|
||||
let rendered_content = self.render_body(body, breadcrumbs, blocks)?;
|
||||
return Ok(rendered_content);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (), // TODO: Implement the rest
|
||||
}
|
||||
@ -281,88 +292,76 @@ mod tests {
|
||||
use crate::renderer::context_element::Loopable;
|
||||
use crate::renderer::context_element::Renderable;
|
||||
use crate::renderer::context_element::Walkable;
|
||||
use crate::renderer::CompareContextElement;
|
||||
|
||||
impl ContextElement for u32 {}
|
||||
impl ContextElement for &str {}
|
||||
impl<I: ContextElement> ContextElement for HashMap<&str, I> {}
|
||||
impl<I: 'static + ContextElement + Clone> ContextElement for HashMap<String, I> {}
|
||||
|
||||
impl Renderable for u32 {
|
||||
fn render(&self, _filters: &Vec<Filter>) -> Result<String, RenderError> {
|
||||
// TODO: handle the filters
|
||||
Ok(self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl Renderable for &str {
|
||||
fn render(&self, _filters: &Vec<Filter>) -> Result<String, RenderError> {
|
||||
// TODO: handle the filters
|
||||
Ok(self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: ContextElement> Renderable for HashMap<&str, I> {
|
||||
impl<I: ContextElement> Renderable for HashMap<String, I> {
|
||||
fn render(&self, _filters: &Vec<Filter>) -> Result<String, RenderError> {
|
||||
// TODO: handle the filters
|
||||
Ok("[object Object]".to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: ContextElement> Walkable for HashMap<&str, I> {
|
||||
impl<I: ContextElement> Walkable for HashMap<String, I> {
|
||||
fn walk(&self, segment: &str) -> Result<&dyn ContextElement, WalkError> {
|
||||
let child = self.get(segment).ok_or(WalkError::CantWalk)?;
|
||||
Ok(child)
|
||||
}
|
||||
}
|
||||
|
||||
impl Walkable for &str {
|
||||
fn walk(&self, segment: &str) -> Result<&dyn ContextElement, WalkError> {
|
||||
Err(WalkError::CantWalk)
|
||||
}
|
||||
}
|
||||
|
||||
impl Walkable for u32 {
|
||||
fn walk(&self, segment: &str) -> Result<&dyn ContextElement, WalkError> {
|
||||
Err(WalkError::CantWalk)
|
||||
}
|
||||
}
|
||||
|
||||
impl Loopable for &str {
|
||||
fn get_loop_elements(&self) -> Vec<&dyn ContextElement> {
|
||||
if self.is_empty() {
|
||||
Vec::new()
|
||||
} else {
|
||||
vec![self]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Loopable for u32 {
|
||||
impl<I: 'static + ContextElement + Clone> Loopable for HashMap<String, I> {
|
||||
fn get_loop_elements(&self) -> Vec<&dyn ContextElement> {
|
||||
vec![self]
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: ContextElement> Loopable for HashMap<&str, I> {
|
||||
fn get_loop_elements(&self) -> Vec<&dyn ContextElement> {
|
||||
vec![self]
|
||||
impl<I: 'static + ContextElement + Clone> CompareContextElement for HashMap<String, I> {
|
||||
fn equals(&self, other: &dyn ContextElement) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_walk_path() {
|
||||
let context: HashMap<&str, &str> =
|
||||
[("cat", "kitty"), ("dog", "doggy"), ("tiger", "murderkitty")]
|
||||
let context: HashMap<String, String> = [
|
||||
("cat".to_string(), "kitty".to_string()),
|
||||
("dog".to_string(), "doggy".to_string()),
|
||||
("tiger".to_string(), "murderkitty".to_string()),
|
||||
]
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect();
|
||||
let number_context: HashMap<&str, u32> = [("cat", 1), ("dog", 2), ("tiger", 3)]
|
||||
let number_context: HashMap<String, u64> = [
|
||||
("cat".to_string(), 1),
|
||||
("dog".to_string(), 2),
|
||||
("tiger".to_string(), 3),
|
||||
]
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect();
|
||||
let deep_context: HashMap<&str, HashMap<&str, &str>> = [
|
||||
("cat", [("food", "meat")].iter().cloned().collect()),
|
||||
("dog", [("food", "meat")].iter().cloned().collect()),
|
||||
("tiger", [("food", "people")].iter().cloned().collect()),
|
||||
let deep_context: HashMap<String, HashMap<String, String>> = [
|
||||
(
|
||||
"cat".to_string(),
|
||||
[("food".to_string(), "meat".to_string())]
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect(),
|
||||
),
|
||||
(
|
||||
"dog".to_string(),
|
||||
[("food".to_string(), "meat".to_string())]
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect(),
|
||||
),
|
||||
(
|
||||
"tiger".to_string(),
|
||||
[("food".to_string(), "people".to_string())]
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect(),
|
||||
),
|
||||
]
|
||||
.iter()
|
||||
.cloned()
|
||||
|
@ -51,3 +51,23 @@ pub fn walk_path<'a>(
|
||||
}
|
||||
Err(WalkError::CantWalk)
|
||||
}
|
||||
|
||||
pub fn owned_walk_path<'a>(
|
||||
breadcrumbs: &'a Vec<Box<dyn ContextElement>>,
|
||||
path: &Vec<String>,
|
||||
) -> Result<&'a dyn ContextElement, WalkError> {
|
||||
let path_reference: Vec<&str> = path.iter().map(|p| &p[..]).collect();
|
||||
for context in breadcrumbs.iter().rev() {
|
||||
match walk_path_from_single_level(context.as_ref(), &path_reference) {
|
||||
// If no walking was done at all, keep looping
|
||||
WalkResult::NoWalk => {}
|
||||
// If we partially walked then stop trying to find
|
||||
// anything
|
||||
WalkResult::PartialWalk => {
|
||||
return Err(WalkError::CantWalk);
|
||||
}
|
||||
WalkResult::FullyWalked(new_context) => return Ok(new_context),
|
||||
}
|
||||
}
|
||||
Err(WalkError::CantWalk)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user