From abf251c68d649d02c4ea87d5f6cffe0276badb7c Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sun, 24 May 2020 13:53:01 -0400 Subject: [PATCH 1/9] Add a test for $idx and $len. --- js/test_cases/idx_and_len/README.md | 1 + js/test_cases/idx_and_len/array.json | 7 +++++ js/test_cases/idx_and_len/false.json | 3 ++ js/test_cases/idx_and_len/main.dust | 40 +++++++++++++++++++++++++++ js/test_cases/idx_and_len/number.json | 3 ++ js/test_cases/idx_and_len/string.json | 3 ++ 6 files changed, 57 insertions(+) create mode 100644 js/test_cases/idx_and_len/README.md create mode 100644 js/test_cases/idx_and_len/array.json create mode 100644 js/test_cases/idx_and_len/false.json create mode 100644 js/test_cases/idx_and_len/main.dust create mode 100644 js/test_cases/idx_and_len/number.json create mode 100644 js/test_cases/idx_and_len/string.json diff --git a/js/test_cases/idx_and_len/README.md b/js/test_cases/idx_and_len/README.md new file mode 100644 index 0000000..c4e8bdd --- /dev/null +++ b/js/test_cases/idx_and_len/README.md @@ -0,0 +1 @@ +$idx and $len seem to only be valid inside sections iterating over arrays, but nothing else. diff --git a/js/test_cases/idx_and_len/array.json b/js/test_cases/idx_and_len/array.json new file mode 100644 index 0000000..3f3e0ab --- /dev/null +++ b/js/test_cases/idx_and_len/array.json @@ -0,0 +1,7 @@ +{ + "things": [ + "Alice", + "Bob", + "Chris" + ] +} diff --git a/js/test_cases/idx_and_len/false.json b/js/test_cases/idx_and_len/false.json new file mode 100644 index 0000000..d51e741 --- /dev/null +++ b/js/test_cases/idx_and_len/false.json @@ -0,0 +1,3 @@ +{ + "things": false +} diff --git a/js/test_cases/idx_and_len/main.dust b/js/test_cases/idx_and_len/main.dust new file mode 100644 index 0000000..2e645b4 --- /dev/null +++ b/js/test_cases/idx_and_len/main.dust @@ -0,0 +1,40 @@ +Outside $idx: {$idx}{~n} +Outside $len: {$len}{~n} +Outside {?$idx}$idx is true{:else}$idx is false{/$idx}{~n} +Outside {?$len}$len is true{:else}$len is false{/$len}{~n} + +{#things} + Inside Section $idx: {$idx}{~n} + Inside Section $len: {$len}{~n} + Inside Section {?$idx}$idx is true{:else}$idx is false{/$idx}{~n} + Inside Section {?$len}$len is true{:else}$len is false{/$len}{~n} +{:else} + Else Section $idx: {$idx}{~n} + Else Section $len: {$len}{~n} + Else Section {?$idx}$idx is true{:else}$idx is false{/$idx}{~n} + Else Section {?$len}$len is true{:else}$len is false{/$len}{~n} +{/things} + +{?things} + Inside Exists $idx: {$idx}{~n} + Inside Exists $len: {$len}{~n} + Inside Exists {?$idx}$idx is true{:else}$idx is false{/$idx}{~n} + Inside Exists {?$len}$len is true{:else}$len is false{/$len}{~n} +{:else} + Else Exists $idx: {$idx}{~n} + Else Exists $len: {$len}{~n} + Else Exists {?$idx}$idx is true{:else}$idx is false{/$idx}{~n} + Else Exists {?$len}$len is true{:else}$len is false{/$len}{~n} +{/things} + +{^things} + Inside Not Exists $idx: {$idx}{~n} + Inside Not Exists $len: {$len}{~n} + Inside Not Exists {?$idx}$idx is true{:else}$idx is false{/$idx}{~n} + Inside Not Exists {?$len}$len is true{:else}$len is false{/$len}{~n} +{:else} + Else Not Exists $idx: {$idx}{~n} + Else Not Exists $len: {$len}{~n} + Else Not Exists {?$idx}$idx is true{:else}$idx is false{/$idx}{~n} + Else Not Exists {?$len}$len is true{:else}$len is false{/$len}{~n} +{/things} diff --git a/js/test_cases/idx_and_len/number.json b/js/test_cases/idx_and_len/number.json new file mode 100644 index 0000000..585368a --- /dev/null +++ b/js/test_cases/idx_and_len/number.json @@ -0,0 +1,3 @@ +{ + "things": 7 +} diff --git a/js/test_cases/idx_and_len/string.json b/js/test_cases/idx_and_len/string.json new file mode 100644 index 0000000..90316e2 --- /dev/null +++ b/js/test_cases/idx_and_len/string.json @@ -0,0 +1,3 @@ +{ + "things": "foobar" +} From fff401da7ec0e2d71747e5d0154fd7d74966f406 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sun, 24 May 2020 13:58:20 -0400 Subject: [PATCH 2/9] Add a test to confirm references are getting parsed. --- src/parser/parser.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/parser/parser.rs b/src/parser/parser.rs index fb0bf3d..82e2bbd 100644 --- a/src/parser/parser.rs +++ b/src/parser/parser.rs @@ -711,6 +711,20 @@ mod tests { ); } + #[test] + fn test_reference_to_variable() { + assert_eq!( + super::reference("{$idx}"), + Ok(( + "", + Reference { + path: Path { keys: vec!["$idx"] }, + filters: Vec::new(), + } + )) + ); + } + #[test] fn test_path() { assert_eq!( From 93adaa518d16e8b2fd4a5744e8e93fa6c00234ce Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sun, 24 May 2020 14:06:44 -0400 Subject: [PATCH 3/9] Add a test for $idx and $len inside nested sections. --- js/test_cases/idx_and_len_nested/array.json | 16 ++++++++++++++++ js/test_cases/idx_and_len_nested/main.dust | 17 +++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 js/test_cases/idx_and_len_nested/array.json create mode 100644 js/test_cases/idx_and_len_nested/main.dust diff --git a/js/test_cases/idx_and_len_nested/array.json b/js/test_cases/idx_and_len_nested/array.json new file mode 100644 index 0000000..322270c --- /dev/null +++ b/js/test_cases/idx_and_len_nested/array.json @@ -0,0 +1,16 @@ +{ + "things": [ + [ + "Alice", + "Andy" + ], + [ + "Bob", + "Becky" + ], + [ + "Chris", + "Cathy" + ] + ] +} diff --git a/js/test_cases/idx_and_len_nested/main.dust b/js/test_cases/idx_and_len_nested/main.dust new file mode 100644 index 0000000..498cd8b --- /dev/null +++ b/js/test_cases/idx_and_len_nested/main.dust @@ -0,0 +1,17 @@ +Outside $idx: {$idx}{~n} +Outside $len: {$len}{~n} +Outside {?$idx}$idx is true{:else}$idx is false{/$idx}{~n} +Outside {?$len}$len is true{:else}$len is false{/$len}{~n} + +{#things} + Inside Section $idx: {$idx}{~n} + Inside Section $len: {$len}{~n} + Inside Section {?$idx}$idx is true{:else}$idx is false{/$idx}{~n} + Inside Section {?$len}$len is true{:else}$len is false{/$len}{~n} + {#.} + Inside Nested Section $idx: {$idx}{~n} + Inside Nested Section $len: {$len}{~n} + Inside Nested Section {?$idx}$idx is true{:else}$idx is false{/$idx}{~n} + Inside Nested Section {?$len}$len is true{:else}$len is false{/$len}{~n} + {/.} +{/things} From 9925802ae9d1f1150bf24df9b89a93b734215b92 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sun, 24 May 2020 15:25:17 -0400 Subject: [PATCH 4/9] Add .$idx to nested section test. --- js/test_cases/idx_and_len_nested/main.dust | 2 ++ 1 file changed, 2 insertions(+) diff --git a/js/test_cases/idx_and_len_nested/main.dust b/js/test_cases/idx_and_len_nested/main.dust index 498cd8b..7eaa5e3 100644 --- a/js/test_cases/idx_and_len_nested/main.dust +++ b/js/test_cases/idx_and_len_nested/main.dust @@ -6,11 +6,13 @@ Outside {?$len}$len is true{:else}$len is false{/$len}{~n} {#things} Inside Section $idx: {$idx}{~n} Inside Section $len: {$len}{~n} + Inside Section .$idx: {.$idx}{~n} Inside Section {?$idx}$idx is true{:else}$idx is false{/$idx}{~n} Inside Section {?$len}$len is true{:else}$len is false{/$len}{~n} {#.} Inside Nested Section $idx: {$idx}{~n} Inside Nested Section $len: {$len}{~n} + Inside Nested Section .$idx: {.$idx}{~n} Inside Nested Section {?$idx}$idx is true{:else}$idx is false{/$idx}{~n} Inside Nested Section {?$len}$len is true{:else}$len is false{/$len}{~n} {/.} From e0fe7475c562705c0cb424ddebc2468137ee6d10 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sun, 24 May 2020 15:29:14 -0400 Subject: [PATCH 5/9] Add a single element array test for idx_and_len. This test is mostly because I am currently turning truthy values into single element arrays during render, so I need to make sure idx and len and not being set for non-array iteration. --- js/test_cases/idx_and_len/single_element_array.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 js/test_cases/idx_and_len/single_element_array.json diff --git a/js/test_cases/idx_and_len/single_element_array.json b/js/test_cases/idx_and_len/single_element_array.json new file mode 100644 index 0000000..97cb724 --- /dev/null +++ b/js/test_cases/idx_and_len/single_element_array.json @@ -0,0 +1,5 @@ +{ + "things": [ + "Alice" + ] +} From 055d88984ef1a9bbff1f587aa739cd1fe4848b42 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sun, 24 May 2020 16:01:56 -0400 Subject: [PATCH 6/9] Implement an injected context for iteration values. --- src/renderer/iteration_context.rs | 71 +++++++++++++++++++++++++++++++ src/renderer/mod.rs | 1 + 2 files changed, 72 insertions(+) create mode 100644 src/renderer/iteration_context.rs diff --git a/src/renderer/iteration_context.rs b/src/renderer/iteration_context.rs new file mode 100644 index 0000000..d294817 --- /dev/null +++ b/src/renderer/iteration_context.rs @@ -0,0 +1,71 @@ +use crate::renderer::context_element::CompareContextElement; +use crate::renderer::context_element::ContextElement; +use crate::renderer::Loopable; +use crate::renderer::RenderError; +use crate::renderer::Renderable; +use crate::renderer::WalkError; +use crate::{parser::Filter, parser::OwnedLiteral, renderer::Walkable}; + +use std::cmp::Ordering; + +/// An injected context for $idx and $len +/// +/// Functions the same as the injected parameters contexts for +/// helpers/partials with parameters but this has no need for storing +/// breadcrumbs since its simply storing two integers. +#[derive(Debug, Clone)] +pub struct IterationContext { + idx: OwnedLiteral, + len: OwnedLiteral, +} + +impl IterationContext { + pub fn new(idx: u64, len: u64) -> Self { + IterationContext { + idx: OwnedLiteral::LPositiveInteger(idx), + len: OwnedLiteral::LPositiveInteger(len), + } + } +} + +impl ContextElement for IterationContext {} + +impl Renderable for IterationContext { + fn render(&self, _filters: &Vec) -> Result { + // TODO: Would this even ever be called? Won't matter, but I'd + // like to know. Since it is injected 1 above the current + // context, we wouldn't be able to access it with `{.}`. + Ok("[object Object]".to_owned()) + } +} + +impl Loopable for IterationContext { + 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 + // context, we wouldn't be able to access it with `{.}`. + vec![self] + } +} + +impl Walkable for IterationContext { + fn walk(&self, segment: &str) -> Result<&dyn ContextElement, WalkError> { + match segment { + "$idx" => Ok(&self.idx), + "$len" => Ok(&self.len), + _ => Err(WalkError::CantWalk), + } + } +} + +impl CompareContextElement for IterationContext { + fn equals(&self, other: &dyn ContextElement) -> bool { + // TODO: Does this ever happen? perhaps I should have a panic here. + false + } + + fn partial_compare(&self, other: &dyn ContextElement) -> Option { + // TODO: Does this ever happen? perhaps I should have a panic here. + None + } +} diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 45dbdfd..3878e18 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -3,6 +3,7 @@ mod context_element; mod errors; mod inline_partial_tree; +mod iteration_context; mod parameters_context; mod renderer; mod walking; From 59ee4f508fde967994366f725be568940a0423ee Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sun, 24 May 2020 16:16:43 -0400 Subject: [PATCH 7/9] Add a new trait to ContextElement for Truthiness. Before I was relying on Loopable to both determine truthiness and get a list of elements to loop over. This will no longer work since I need to only set $idx and $len when iterating over actual arrays, as opposed to all truthy values, so I've finally made truthiness explicit. --- src/bin.rs | 14 ++++++++++++++ src/renderer/context_element.rs | 12 +++++++++++- src/renderer/iteration_context.rs | 10 ++++++++++ src/renderer/mod.rs | 1 + src/renderer/parameters_context.rs | 19 +++++++++++++++++++ src/renderer/renderer.rs | 19 +++++++++++++++++++ 6 files changed, 74 insertions(+), 1 deletion(-) diff --git a/src/bin.rs b/src/bin.rs index 2b19174..f913772 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -10,6 +10,7 @@ use renderer::DustRenderer; use renderer::Loopable; use renderer::RenderError; use renderer::Renderable; +use renderer::Truthiness; use renderer::WalkError; use renderer::Walkable; use std::cmp::Ordering; @@ -236,6 +237,19 @@ fn apply_filters( impl ContextElement for serde_json::Value {} +impl Truthiness for serde_json::Value { + fn is_truthy(&self) -> bool { + match self { + serde_json::Value::Null => false, + serde_json::Value::Bool(boolean) => *boolean, + serde_json::Value::Number(_num) => true, + serde_json::Value::String(string_value) => !string_value.is_empty(), + serde_json::Value::Array(array_value) => !array_value.is_empty(), + serde_json::Value::Object(_obj) => true, + } + } +} + impl Renderable for serde_json::Value { fn render(&self, _filters: &Vec) -> Result { let after_apply = if _filters.is_empty() { diff --git a/src/renderer/context_element.rs b/src/renderer/context_element.rs index bb43cf8..bb0369f 100644 --- a/src/renderer/context_element.rs +++ b/src/renderer/context_element.rs @@ -5,10 +5,20 @@ use std::any::Any; use std::{cmp::Ordering, fmt::Debug}; pub trait ContextElement: - Debug + Walkable + Renderable + Loopable + CloneIntoBoxedContextElement + CompareContextElement + Debug + + Truthiness + + Walkable + + Renderable + + Loopable + + CloneIntoBoxedContextElement + + CompareContextElement { } +pub trait Truthiness { + fn is_truthy(&self) -> bool; +} + pub trait Walkable { fn walk(&self, segment: &str) -> Result<&dyn ContextElement, WalkError>; } diff --git a/src/renderer/iteration_context.rs b/src/renderer/iteration_context.rs index d294817..51619ae 100644 --- a/src/renderer/iteration_context.rs +++ b/src/renderer/iteration_context.rs @@ -3,6 +3,7 @@ use crate::renderer::context_element::ContextElement; use crate::renderer::Loopable; use crate::renderer::RenderError; use crate::renderer::Renderable; +use crate::renderer::Truthiness; use crate::renderer::WalkError; use crate::{parser::Filter, parser::OwnedLiteral, renderer::Walkable}; @@ -30,6 +31,15 @@ impl IterationContext { impl ContextElement for IterationContext {} +impl Truthiness for IterationContext { + fn is_truthy(&self) -> bool { + // TODO: Would this even ever be called? Won't matter, but I'd + // like to know. Since it is injected 1 above the current + // context, we wouldn't be able to access it with `{.}`. + true + } +} + impl Renderable for IterationContext { fn render(&self, _filters: &Vec) -> Result { // TODO: Would this even ever be called? Won't matter, but I'd diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 3878e18..ff0729b 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -13,6 +13,7 @@ pub use context_element::CompareContextElement; pub use context_element::ContextElement; pub use context_element::Loopable; pub use context_element::Renderable; +pub use context_element::Truthiness; pub use context_element::Walkable; pub use errors::CompileError; pub use errors::RenderError; diff --git a/src/renderer/parameters_context.rs b/src/renderer/parameters_context.rs index 92ee3b1..31d52b8 100644 --- a/src/renderer/parameters_context.rs +++ b/src/renderer/parameters_context.rs @@ -6,6 +6,7 @@ use crate::renderer::walking::walk_path; use crate::renderer::Loopable; use crate::renderer::RenderError; use crate::renderer::Renderable; +use crate::renderer::Truthiness; use crate::renderer::WalkError; use crate::renderer::Walkable; use std::{cmp::Ordering, collections::HashMap}; @@ -67,6 +68,15 @@ impl ParametersContext { impl ContextElement for ParametersContext {} +impl Truthiness for ParametersContext { + fn is_truthy(&self) -> bool { + // TODO: Would this even ever be called? Won't matter, but I'd + // like to know. Since it is injected 1 above the current + // context, we wouldn't be able to access it with `{.}`. + true + } +} + impl Renderable for ParametersContext { fn render(&self, _filters: &Vec) -> Result { // TODO: Would this even ever be called? Won't matter, but I'd @@ -128,6 +138,15 @@ impl CompareContextElement for ParametersContext { impl ContextElement for OwnedLiteral {} +impl Truthiness for OwnedLiteral { + fn is_truthy(&self) -> bool { + match self { + OwnedLiteral::LString(text) => !text.is_empty(), + OwnedLiteral::LPositiveInteger(num) => true, + } + } +} + impl Renderable for OwnedLiteral { fn render(&self, _filters: &Vec) -> Result { match self { diff --git a/src/renderer/renderer.rs b/src/renderer/renderer.rs index 03ff2f4..dbac9f7 100644 --- a/src/renderer/renderer.rs +++ b/src/renderer/renderer.rs @@ -524,12 +524,19 @@ mod tests { use crate::parser::Filter; use crate::renderer::context_element::Loopable; use crate::renderer::context_element::Renderable; + use crate::renderer::context_element::Truthiness; use crate::renderer::context_element::Walkable; use crate::renderer::CompareContextElement; use std::cmp::Ordering; impl ContextElement for String {} + impl Truthiness for String { + fn is_truthy(&self) -> bool { + !self.is_empty() + } + } + impl Renderable for String { fn render(&self, _filters: &Vec) -> Result { Ok(self.clone()) @@ -569,6 +576,12 @@ mod tests { } impl ContextElement for u64 {} + impl Truthiness for u64 { + fn is_truthy(&self) -> bool { + true + } + } + impl Renderable for u64 { fn render(&self, _filters: &Vec) -> Result { Ok(self.to_string()) @@ -605,6 +618,12 @@ mod tests { impl ContextElement for HashMap {} + impl Truthiness for HashMap { + fn is_truthy(&self) -> bool { + true + } + } + impl Renderable for HashMap { fn render(&self, _filters: &Vec) -> Result { // TODO: handle the filters From 966499db764879cac45260200546ec166828befd Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sun, 24 May 2020 16:27:13 -0400 Subject: [PATCH 8/9] Switch the get_loop_elements implementation to only return populated arrays when its an array-like object. --- src/bin.rs | 26 ++------------------------ src/renderer/context_element.rs | 13 ++++++------- src/renderer/iteration_context.rs | 2 +- src/renderer/parameters_context.rs | 13 ++----------- src/renderer/renderer.rs | 10 +++------- 5 files changed, 14 insertions(+), 50 deletions(-) diff --git a/src/bin.rs b/src/bin.rs index f913772..2ca7e1a 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -294,30 +294,8 @@ impl Walkable for serde_json::Value { impl Loopable for serde_json::Value { fn get_loop_elements(&self) -> Vec<&dyn ContextElement> { match self { - serde_json::Value::Null => Vec::new(), - serde_json::Value::Bool(boolean) => { - if *boolean { - vec![self] - } else { - Vec::new() - } - } - serde_json::Value::Number(_num) => vec![self], - serde_json::Value::String(string_value) => { - if string_value.is_empty() { - Vec::new() - } else { - vec![self] - } - } - serde_json::Value::Array(array_value) => { - if array_value.is_empty() { - Vec::new() - } else { - array_value.iter().map(|x| x as _).collect() - } - } - serde_json::Value::Object(_obj) => vec![self], + serde_json::Value::Array(array_value) => array_value.iter().map(|x| x as _).collect(), + _ => Vec::new(), } } } diff --git a/src/renderer/context_element.rs b/src/renderer/context_element.rs index bb0369f..992fa6b 100644 --- a/src/renderer/context_element.rs +++ b/src/renderer/context_element.rs @@ -30,13 +30,12 @@ pub trait Renderable { pub trait Loopable { /// Return the elements for a Dust section /// - /// Sections in dust are accomplished with the {#path} syntax. A - /// section has a truthiness check performed on it. If that - /// truthiness check fails, then it will render the - /// else-block. Otherwise if its a scalar value it will render - /// once with the context being the element at that path. Finally, - /// if its an array-like value then it will render n-times, once - /// for each element of the array. + /// Sections in dust are accomplished with the {#path} syntax. If + /// its an array-like value then it will render n-times, once for + /// each element of the array. If this is a scalar value, then + /// return an empty array. Sections with scalar values will still + /// be rendered (only once) if their truthiness check comes back + /// true. fn get_loop_elements(&self) -> Vec<&dyn ContextElement>; } diff --git a/src/renderer/iteration_context.rs b/src/renderer/iteration_context.rs index 51619ae..e0b48a0 100644 --- a/src/renderer/iteration_context.rs +++ b/src/renderer/iteration_context.rs @@ -54,7 +54,7 @@ impl Loopable for IterationContext { // TODO: Would this even ever be called? Won't matter, but I'd // like to know. Since it is injected 1 above the current // context, we wouldn't be able to access it with `{.}`. - vec![self] + Vec::new() } } diff --git a/src/renderer/parameters_context.rs b/src/renderer/parameters_context.rs index 31d52b8..7a3e6b1 100644 --- a/src/renderer/parameters_context.rs +++ b/src/renderer/parameters_context.rs @@ -91,7 +91,7 @@ impl Loopable for ParametersContext { // TODO: Would this even ever be called? Won't matter, but I'd // like to know. Since it is injected 1 above the current // context, we wouldn't be able to access it with `{.}`. - vec![self] + Vec::new() } } @@ -158,16 +158,7 @@ impl Renderable for OwnedLiteral { impl Loopable for OwnedLiteral { fn get_loop_elements(&self) -> Vec<&dyn ContextElement> { - match self { - OwnedLiteral::LString(text) => { - if text.is_empty() { - Vec::new() - } else { - vec![self] - } - } - OwnedLiteral::LPositiveInteger(num) => vec![self], - } + Vec::new() } } diff --git a/src/renderer/renderer.rs b/src/renderer/renderer.rs index dbac9f7..deb570d 100644 --- a/src/renderer/renderer.rs +++ b/src/renderer/renderer.rs @@ -545,11 +545,7 @@ mod tests { impl Loopable for String { fn get_loop_elements(&self) -> Vec<&dyn ContextElement> { - if self.is_empty() { - Vec::new() - } else { - vec![self] - } + Vec::new() } } @@ -590,7 +586,7 @@ mod tests { impl Loopable for u64 { fn get_loop_elements(&self) -> Vec<&dyn ContextElement> { - vec![self] + Vec::new() } } @@ -640,7 +636,7 @@ mod tests { impl Loopable for HashMap { fn get_loop_elements(&self) -> Vec<&dyn ContextElement> { - vec![self] + Vec::new() } } From c09393da803d157e0930534e5e41063a7193a03c Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sun, 24 May 2020 16:57:24 -0400 Subject: [PATCH 9/9] Switch the renderer over to using the new is_truthy value and add the injection of $idx and $len into the context tree. --- src/renderer/iteration_context.rs | 8 ++- src/renderer/renderer.rs | 106 ++++++++++++++++++++---------- 2 files changed, 76 insertions(+), 38 deletions(-) diff --git a/src/renderer/iteration_context.rs b/src/renderer/iteration_context.rs index e0b48a0..acdd9a1 100644 --- a/src/renderer/iteration_context.rs +++ b/src/renderer/iteration_context.rs @@ -6,6 +6,7 @@ use crate::renderer::Renderable; use crate::renderer::Truthiness; use crate::renderer::WalkError; use crate::{parser::Filter, parser::OwnedLiteral, renderer::Walkable}; +use std::convert::TryInto; use std::cmp::Ordering; @@ -21,10 +22,11 @@ pub struct IterationContext { } impl IterationContext { - pub fn new(idx: u64, len: u64) -> Self { + pub fn new(idx: usize, len: usize) -> Self { + // TODO: it would be nice to handle usize vs u64 better IterationContext { - idx: OwnedLiteral::LPositiveInteger(idx), - len: OwnedLiteral::LPositiveInteger(len), + idx: OwnedLiteral::LPositiveInteger(idx.try_into().unwrap()), + len: OwnedLiteral::LPositiveInteger(len.try_into().unwrap()), } } } diff --git a/src/renderer/renderer.rs b/src/renderer/renderer.rs index deb570d..6dce3bb 100644 --- a/src/renderer/renderer.rs +++ b/src/renderer/renderer.rs @@ -13,6 +13,7 @@ use crate::renderer::errors::RenderError; use crate::renderer::errors::WalkError; use crate::renderer::inline_partial_tree::extract_inline_partials; use crate::renderer::inline_partial_tree::InlinePartialTreeElement; +use crate::renderer::iteration_context::IterationContext; use crate::renderer::parameters_context::ParametersContext; use crate::renderer::walking::walk_path; use std::collections::HashMap; @@ -150,58 +151,93 @@ impl<'a> DustRenderer<'a> { match val { Err(WalkError::CantWalk) => return Ok("".to_owned()), Ok(final_val) => { - let loop_elements = final_val.get_loop_elements(); - if loop_elements.is_empty() { - return Ok("".to_owned()); + return if final_val.is_truthy() { + final_val.render(&Self::preprocess_filters(&reference.filters)) } else { - return final_val.render(&Self::preprocess_filters(&reference.filters)); - } + Ok("".to_owned()) + }; } } } DustTag::DTSection(container) => { let val = walk_path(breadcrumbs, &container.path.keys); - let loop_elements: Vec<&dyn ContextElement> = Self::get_loop_elements(val); - if loop_elements.is_empty() { - // Oddly enough if the value is falsey (like - // an empty array or null), Dust uses the - // original context before walking the path as - // the context for rendering the else block - return self.render_maybe_body(&container.else_contents, breadcrumbs, blocks); - } else { - match &container.contents { - None => return Ok("".to_owned()), - Some(body) => { - let rendered_results: Result, RenderError> = loop_elements - .into_iter() - .map(|array_elem| { - let mut new_breadcumbs = breadcrumbs.clone(); - new_breadcumbs.push(array_elem); - self.render_body(&body, &new_breadcumbs, blocks) - }) - .collect(); - let rendered_slice: &[String] = &rendered_results?; - return Ok(rendered_slice.join("")); - } + match val { + Err(WalkError::CantWalk) => { + return self.render_maybe_body( + &container.else_contents, + breadcrumbs, + blocks, + ); + } + Ok(final_val) => { + return if final_val.is_truthy() { + match &container.contents { + // If the body is empty, just shortcut + // to an empty string now rather than + // generating intermediate contexts + // and iterating for nothing. + None => Ok("".to_owned()), + Some(body) => { + let loop_elements: Vec<&dyn ContextElement> = + final_val.get_loop_elements(); + if loop_elements.is_empty() { + // Scalar value + let mut new_breadcrumbs = breadcrumbs.clone(); + new_breadcrumbs.push(final_val); + self.render_body(body, &new_breadcrumbs, blocks) + } else { + // Array-like value + let total_length = loop_elements.len(); + let rendered_results: Result, RenderError> = + loop_elements + .into_iter() + .enumerate() + .map(|(i, array_elem)| { + let injected_context = + IterationContext::new(i, total_length); + let mut new_breadcrumbs = breadcrumbs.clone(); + new_breadcrumbs.push(&injected_context); + new_breadcrumbs.push(array_elem); + self.render_body( + &body, + &new_breadcrumbs, + blocks, + ) + }) + .collect(); + let rendered_slice: &[String] = &rendered_results?; + return Ok(rendered_slice.join("")); + } + } + } + } else { + // Oddly enough if the value is falsey (like + // an empty array or null), Dust uses the + // original context before walking the path as + // the context for rendering the else block + return self.render_maybe_body( + &container.else_contents, + breadcrumbs, + blocks, + ); + }; } } } DustTag::DTExists(container) => { let val = walk_path(breadcrumbs, &container.path.keys); - let loop_elements: Vec<&dyn ContextElement> = Self::get_loop_elements(val); - return if loop_elements.is_empty() { - self.render_maybe_body(&container.else_contents, breadcrumbs, blocks) - } else { + return if val.map(|v| v.is_truthy()).unwrap_or(false) { self.render_maybe_body(&container.contents, breadcrumbs, blocks) + } else { + self.render_maybe_body(&container.else_contents, breadcrumbs, blocks) }; } DustTag::DTNotExists(container) => { let val = walk_path(breadcrumbs, &container.path.keys); - let loop_elements: Vec<&dyn ContextElement> = Self::get_loop_elements(val); - return if !loop_elements.is_empty() { - self.render_maybe_body(&container.else_contents, breadcrumbs, blocks) - } else { + return if !val.map(|v| v.is_truthy()).unwrap_or(false) { self.render_maybe_body(&container.contents, breadcrumbs, blocks) + } else { + self.render_maybe_body(&container.else_contents, breadcrumbs, blocks) }; } DustTag::DTPartial(partial) => {