Merge branch 'remove_lifetime_from_context_elements' into dust_helpers

This commit is contained in:
Tom Alexander 2020-05-10 23:43:19 -04:00
commit 3c85717952
Signed by: talexander
GPG Key ID: D3A179C9A53C0EDE
7 changed files with 311 additions and 97 deletions

View File

@ -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
}
}

View File

@ -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!(

View File

@ -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)
}
}

View File

@ -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;

View File

@ -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,
}
}
}

View File

@ -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 &parameterized_block.contents {
None => return Ok("".to_owned()),
Some(body) => {
let rendered_content = self.render_body(body, breadcrumbs, blocks)?;
return Ok(rendered_content);
}
}
} else {
match &parameterized_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()

View File

@ -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)
}