diff --git a/src/parser/parser.rs b/src/parser/parser.rs index 4a6db06..9422a6d 100644 --- a/src/parser/parser.rs +++ b/src/parser/parser.rs @@ -6,7 +6,7 @@ use nom::bytes::complete::{tag, take_until, take_until_parser_matches}; use nom::character::complete::line_ending; use nom::character::complete::multispace0; use nom::character::complete::one_of; -use nom::character::complete::space1; +use nom::character::complete::{space0, space1}; use nom::combinator::all_consuming; use nom::combinator::map; use nom::combinator::opt; @@ -387,7 +387,11 @@ where let (i, (name, params, inner, maybe_else, _closing_name)) = tuple(( preceded(tag(open_matcher), tag(tag_name)), terminated( - opt(preceded(space1, separated_list1(space1, key_value_pair))), + opt(delimited( + space1, + separated_list1(space1, key_value_pair), + space0, + )), tag("}"), ), opt(body), @@ -420,7 +424,11 @@ where tag(open_matcher), tuple(( tag(tag_name), - opt(preceded(space1, separated_list1(space1, key_value_pair))), + opt(delimited( + space1, + separated_list1(space1, key_value_pair), + space0, + )), )), tag("/}"), )(i)?; @@ -449,7 +457,11 @@ where tag(open_matcher), tuple(( alt((map(key, String::from), quoted_string)), - opt(preceded(space1, separated_list1(space1, key_value_pair))), + opt(delimited( + space1, + separated_list1(space1, key_value_pair), + space0, + )), )), tag("/}"), )(i)?; @@ -1052,4 +1064,55 @@ mod tests { )) ); } + + #[test] + fn test_full_document_parameterized_partial() { + assert_eq!( + super::template( + r#"{#level3.level4}{>partialtwo v1="b" v2="b" v3="b" v4="b" v5="b" /}{/level3.level4}"# + ), + Ok::<_, nom::Err<(&str, ErrorKind)>>(( + "", + Template { + contents: Body { + elements: vec![TemplateElement::TETag(DustTag::DTSection(Container { + path: Path { + keys: vec!["level3", "level4"] + }, + contents: Some(Body { + elements: vec![TemplateElement::TETag(DustTag::DTPartial( + Partial { + name: "partialtwo".to_owned(), + params: vec![ + KVPair { + key: "v1", + value: RValue::RVString("b".to_owned()) + }, + KVPair { + key: "v2", + value: RValue::RVString("b".to_owned()) + }, + KVPair { + key: "v3", + value: RValue::RVString("b".to_owned()) + }, + KVPair { + key: "v4", + value: RValue::RVString("b".to_owned()) + }, + KVPair { + key: "v5", + value: RValue::RVString("b".to_owned()) + } + ] + } + ))] + }), + else_contents: None + }))] + } + } + )) + ); + } } diff --git a/src/renderer/walking.rs b/src/renderer/walking.rs new file mode 100644 index 0000000..feadce3 --- /dev/null +++ b/src/renderer/walking.rs @@ -0,0 +1,53 @@ +use crate::renderer::context_element::ContextElement; +use crate::renderer::WalkError; + +enum WalkResult<'a> { + NoWalk, + PartialWalk, + FullyWalked(&'a dyn ContextElement), +} + +fn walk_path_from_single_level<'a>( + context: &'a dyn ContextElement, + path: &Vec<&str>, +) -> WalkResult<'a> { + if path.is_empty() { + return WalkResult::FullyWalked(context); + } + + let mut walk_failure = WalkResult::NoWalk; + let mut output = context; + for elem in path.iter() { + let new_val = output.walk(elem); + match output.walk(elem) { + Err(WalkError::CantWalk { .. }) => { + return walk_failure; + } + Ok(new_val) => { + walk_failure = WalkResult::PartialWalk; + output = new_val; + } + } + } + + WalkResult::FullyWalked(output) +} + +pub fn walk_path<'a>( + breadcrumbs: &Vec<&'a dyn ContextElement>, + path: &'a Vec<&str>, +) -> Result<&'a dyn ContextElement, WalkError> { + for context in breadcrumbs.iter().rev() { + match walk_path_from_single_level(*context, path) { + // 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) +}