Merge branch 'idx_and_len' into render
This commit is contained in:
commit
d813a878ca
1
js/test_cases/idx_and_len/README.md
Normal file
1
js/test_cases/idx_and_len/README.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
$idx and $len seem to only be valid inside sections iterating over arrays, but nothing else.
|
7
js/test_cases/idx_and_len/array.json
Normal file
7
js/test_cases/idx_and_len/array.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"things": [
|
||||||
|
"Alice",
|
||||||
|
"Bob",
|
||||||
|
"Chris"
|
||||||
|
]
|
||||||
|
}
|
3
js/test_cases/idx_and_len/false.json
Normal file
3
js/test_cases/idx_and_len/false.json
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"things": false
|
||||||
|
}
|
40
js/test_cases/idx_and_len/main.dust
Normal file
40
js/test_cases/idx_and_len/main.dust
Normal file
@ -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}
|
3
js/test_cases/idx_and_len/number.json
Normal file
3
js/test_cases/idx_and_len/number.json
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"things": 7
|
||||||
|
}
|
5
js/test_cases/idx_and_len/single_element_array.json
Normal file
5
js/test_cases/idx_and_len/single_element_array.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"things": [
|
||||||
|
"Alice"
|
||||||
|
]
|
||||||
|
}
|
3
js/test_cases/idx_and_len/string.json
Normal file
3
js/test_cases/idx_and_len/string.json
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"things": "foobar"
|
||||||
|
}
|
16
js/test_cases/idx_and_len_nested/array.json
Normal file
16
js/test_cases/idx_and_len_nested/array.json
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"things": [
|
||||||
|
[
|
||||||
|
"Alice",
|
||||||
|
"Andy"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Bob",
|
||||||
|
"Becky"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Chris",
|
||||||
|
"Cathy"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
19
js/test_cases/idx_and_len_nested/main.dust
Normal file
19
js/test_cases/idx_and_len_nested/main.dust
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
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}{~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}
|
||||||
|
{/.}
|
||||||
|
{/things}
|
40
src/bin.rs
40
src/bin.rs
@ -10,6 +10,7 @@ use renderer::DustRenderer;
|
|||||||
use renderer::Loopable;
|
use renderer::Loopable;
|
||||||
use renderer::RenderError;
|
use renderer::RenderError;
|
||||||
use renderer::Renderable;
|
use renderer::Renderable;
|
||||||
|
use renderer::Truthiness;
|
||||||
use renderer::WalkError;
|
use renderer::WalkError;
|
||||||
use renderer::Walkable;
|
use renderer::Walkable;
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
@ -236,6 +237,19 @@ fn apply_filters(
|
|||||||
|
|
||||||
impl ContextElement for serde_json::Value {}
|
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 {
|
impl Renderable for serde_json::Value {
|
||||||
fn render(&self, _filters: &Vec<Filter>) -> Result<String, RenderError> {
|
fn render(&self, _filters: &Vec<Filter>) -> Result<String, RenderError> {
|
||||||
let after_apply = if _filters.is_empty() {
|
let after_apply = if _filters.is_empty() {
|
||||||
@ -280,30 +294,8 @@ impl Walkable for serde_json::Value {
|
|||||||
impl Loopable for serde_json::Value {
|
impl Loopable for serde_json::Value {
|
||||||
fn get_loop_elements(&self) -> Vec<&dyn ContextElement> {
|
fn get_loop_elements(&self) -> Vec<&dyn ContextElement> {
|
||||||
match self {
|
match self {
|
||||||
serde_json::Value::Null => Vec::new(),
|
serde_json::Value::Array(array_value) => array_value.iter().map(|x| x as _).collect(),
|
||||||
serde_json::Value::Bool(boolean) => {
|
_ => Vec::new(),
|
||||||
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],
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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]
|
#[test]
|
||||||
fn test_path() {
|
fn test_path() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -5,10 +5,20 @@ use std::any::Any;
|
|||||||
use std::{cmp::Ordering, fmt::Debug};
|
use std::{cmp::Ordering, fmt::Debug};
|
||||||
|
|
||||||
pub trait ContextElement:
|
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 {
|
pub trait Walkable {
|
||||||
fn walk(&self, segment: &str) -> Result<&dyn ContextElement, WalkError>;
|
fn walk(&self, segment: &str) -> Result<&dyn ContextElement, WalkError>;
|
||||||
}
|
}
|
||||||
@ -20,13 +30,12 @@ pub trait Renderable {
|
|||||||
pub trait Loopable {
|
pub trait Loopable {
|
||||||
/// Return the elements for a Dust section
|
/// Return the elements for a Dust section
|
||||||
///
|
///
|
||||||
/// Sections in dust are accomplished with the {#path} syntax. A
|
/// Sections in dust are accomplished with the {#path} syntax. If
|
||||||
/// section has a truthiness check performed on it. If that
|
/// its an array-like value then it will render n-times, once for
|
||||||
/// truthiness check fails, then it will render the
|
/// each element of the array. If this is a scalar value, then
|
||||||
/// else-block. Otherwise if its a scalar value it will render
|
/// return an empty array. Sections with scalar values will still
|
||||||
/// once with the context being the element at that path. Finally,
|
/// be rendered (only once) if their truthiness check comes back
|
||||||
/// if its an array-like value then it will render n-times, once
|
/// true.
|
||||||
/// for each element of the array.
|
|
||||||
fn get_loop_elements(&self) -> Vec<&dyn ContextElement>;
|
fn get_loop_elements(&self) -> Vec<&dyn ContextElement>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
83
src/renderer/iteration_context.rs
Normal file
83
src/renderer/iteration_context.rs
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
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::Truthiness;
|
||||||
|
use crate::renderer::WalkError;
|
||||||
|
use crate::{parser::Filter, parser::OwnedLiteral, renderer::Walkable};
|
||||||
|
use std::convert::TryInto;
|
||||||
|
|
||||||
|
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: usize, len: usize) -> Self {
|
||||||
|
// TODO: it would be nice to handle usize vs u64 better
|
||||||
|
IterationContext {
|
||||||
|
idx: OwnedLiteral::LPositiveInteger(idx.try_into().unwrap()),
|
||||||
|
len: OwnedLiteral::LPositiveInteger(len.try_into().unwrap()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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<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
|
||||||
|
// 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::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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<Ordering> {
|
||||||
|
// TODO: Does this ever happen? perhaps I should have a panic here.
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,7 @@
|
|||||||
mod context_element;
|
mod context_element;
|
||||||
mod errors;
|
mod errors;
|
||||||
mod inline_partial_tree;
|
mod inline_partial_tree;
|
||||||
|
mod iteration_context;
|
||||||
mod parameters_context;
|
mod parameters_context;
|
||||||
mod renderer;
|
mod renderer;
|
||||||
mod walking;
|
mod walking;
|
||||||
@ -12,6 +13,7 @@ pub use context_element::CompareContextElement;
|
|||||||
pub use context_element::ContextElement;
|
pub use context_element::ContextElement;
|
||||||
pub use context_element::Loopable;
|
pub use context_element::Loopable;
|
||||||
pub use context_element::Renderable;
|
pub use context_element::Renderable;
|
||||||
|
pub use context_element::Truthiness;
|
||||||
pub use context_element::Walkable;
|
pub use context_element::Walkable;
|
||||||
pub use errors::CompileError;
|
pub use errors::CompileError;
|
||||||
pub use errors::RenderError;
|
pub use errors::RenderError;
|
||||||
|
@ -6,6 +6,7 @@ use crate::renderer::walking::walk_path;
|
|||||||
use crate::renderer::Loopable;
|
use crate::renderer::Loopable;
|
||||||
use crate::renderer::RenderError;
|
use crate::renderer::RenderError;
|
||||||
use crate::renderer::Renderable;
|
use crate::renderer::Renderable;
|
||||||
|
use crate::renderer::Truthiness;
|
||||||
use crate::renderer::WalkError;
|
use crate::renderer::WalkError;
|
||||||
use crate::renderer::Walkable;
|
use crate::renderer::Walkable;
|
||||||
use std::{cmp::Ordering, collections::HashMap};
|
use std::{cmp::Ordering, collections::HashMap};
|
||||||
@ -67,6 +68,15 @@ impl ParametersContext {
|
|||||||
|
|
||||||
impl ContextElement for 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 {
|
impl Renderable for ParametersContext {
|
||||||
fn render(&self, _filters: &Vec<Filter>) -> Result<String, RenderError> {
|
fn render(&self, _filters: &Vec<Filter>) -> Result<String, RenderError> {
|
||||||
// TODO: Would this even ever be called? Won't matter, but I'd
|
// TODO: Would this even ever be called? Won't matter, but I'd
|
||||||
@ -81,7 +91,7 @@ impl Loopable for ParametersContext {
|
|||||||
// TODO: Would this even ever be called? Won't matter, but I'd
|
// TODO: Would this even ever be called? Won't matter, but I'd
|
||||||
// like to know. Since it is injected 1 above the current
|
// like to know. Since it is injected 1 above the current
|
||||||
// context, we wouldn't be able to access it with `{.}`.
|
// context, we wouldn't be able to access it with `{.}`.
|
||||||
vec![self]
|
Vec::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,6 +138,15 @@ impl CompareContextElement for ParametersContext {
|
|||||||
|
|
||||||
impl ContextElement for OwnedLiteral {}
|
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 {
|
impl Renderable for OwnedLiteral {
|
||||||
fn render(&self, _filters: &Vec<Filter>) -> Result<String, RenderError> {
|
fn render(&self, _filters: &Vec<Filter>) -> Result<String, RenderError> {
|
||||||
match self {
|
match self {
|
||||||
@ -139,16 +158,7 @@ impl Renderable for OwnedLiteral {
|
|||||||
|
|
||||||
impl Loopable for OwnedLiteral {
|
impl Loopable for OwnedLiteral {
|
||||||
fn get_loop_elements(&self) -> Vec<&dyn ContextElement> {
|
fn get_loop_elements(&self) -> Vec<&dyn ContextElement> {
|
||||||
match self {
|
Vec::new()
|
||||||
OwnedLiteral::LString(text) => {
|
|
||||||
if text.is_empty() {
|
|
||||||
Vec::new()
|
|
||||||
} else {
|
|
||||||
vec![self]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
OwnedLiteral::LPositiveInteger(num) => vec![self],
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ use crate::renderer::errors::RenderError;
|
|||||||
use crate::renderer::errors::WalkError;
|
use crate::renderer::errors::WalkError;
|
||||||
use crate::renderer::inline_partial_tree::extract_inline_partials;
|
use crate::renderer::inline_partial_tree::extract_inline_partials;
|
||||||
use crate::renderer::inline_partial_tree::InlinePartialTreeElement;
|
use crate::renderer::inline_partial_tree::InlinePartialTreeElement;
|
||||||
|
use crate::renderer::iteration_context::IterationContext;
|
||||||
use crate::renderer::parameters_context::ParametersContext;
|
use crate::renderer::parameters_context::ParametersContext;
|
||||||
use crate::renderer::walking::walk_path;
|
use crate::renderer::walking::walk_path;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
@ -150,58 +151,93 @@ impl<'a> DustRenderer<'a> {
|
|||||||
match val {
|
match val {
|
||||||
Err(WalkError::CantWalk) => return Ok("".to_owned()),
|
Err(WalkError::CantWalk) => return Ok("".to_owned()),
|
||||||
Ok(final_val) => {
|
Ok(final_val) => {
|
||||||
let loop_elements = final_val.get_loop_elements();
|
return if final_val.is_truthy() {
|
||||||
if loop_elements.is_empty() {
|
final_val.render(&Self::preprocess_filters(&reference.filters))
|
||||||
return Ok("".to_owned());
|
|
||||||
} else {
|
} else {
|
||||||
return final_val.render(&Self::preprocess_filters(&reference.filters));
|
Ok("".to_owned())
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DustTag::DTSection(container) => {
|
DustTag::DTSection(container) => {
|
||||||
let val = walk_path(breadcrumbs, &container.path.keys);
|
let val = walk_path(breadcrumbs, &container.path.keys);
|
||||||
let loop_elements: Vec<&dyn ContextElement> = Self::get_loop_elements(val);
|
match val {
|
||||||
if loop_elements.is_empty() {
|
Err(WalkError::CantWalk) => {
|
||||||
// Oddly enough if the value is falsey (like
|
return self.render_maybe_body(
|
||||||
// an empty array or null), Dust uses the
|
&container.else_contents,
|
||||||
// original context before walking the path as
|
breadcrumbs,
|
||||||
// the context for rendering the else block
|
blocks,
|
||||||
return self.render_maybe_body(&container.else_contents, breadcrumbs, blocks);
|
);
|
||||||
} else {
|
}
|
||||||
match &container.contents {
|
Ok(final_val) => {
|
||||||
None => return Ok("".to_owned()),
|
return if final_val.is_truthy() {
|
||||||
Some(body) => {
|
match &container.contents {
|
||||||
let rendered_results: Result<Vec<String>, RenderError> = loop_elements
|
// If the body is empty, just shortcut
|
||||||
.into_iter()
|
// to an empty string now rather than
|
||||||
.map(|array_elem| {
|
// generating intermediate contexts
|
||||||
let mut new_breadcumbs = breadcrumbs.clone();
|
// and iterating for nothing.
|
||||||
new_breadcumbs.push(array_elem);
|
None => Ok("".to_owned()),
|
||||||
self.render_body(&body, &new_breadcumbs, blocks)
|
Some(body) => {
|
||||||
})
|
let loop_elements: Vec<&dyn ContextElement> =
|
||||||
.collect();
|
final_val.get_loop_elements();
|
||||||
let rendered_slice: &[String] = &rendered_results?;
|
if loop_elements.is_empty() {
|
||||||
return Ok(rendered_slice.join(""));
|
// 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<Vec<String>, 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) => {
|
DustTag::DTExists(container) => {
|
||||||
let val = walk_path(breadcrumbs, &container.path.keys);
|
let val = walk_path(breadcrumbs, &container.path.keys);
|
||||||
let loop_elements: Vec<&dyn ContextElement> = Self::get_loop_elements(val);
|
return if val.map(|v| v.is_truthy()).unwrap_or(false) {
|
||||||
return if loop_elements.is_empty() {
|
|
||||||
self.render_maybe_body(&container.else_contents, breadcrumbs, blocks)
|
|
||||||
} else {
|
|
||||||
self.render_maybe_body(&container.contents, breadcrumbs, blocks)
|
self.render_maybe_body(&container.contents, breadcrumbs, blocks)
|
||||||
|
} else {
|
||||||
|
self.render_maybe_body(&container.else_contents, breadcrumbs, blocks)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
DustTag::DTNotExists(container) => {
|
DustTag::DTNotExists(container) => {
|
||||||
let val = walk_path(breadcrumbs, &container.path.keys);
|
let val = walk_path(breadcrumbs, &container.path.keys);
|
||||||
let loop_elements: Vec<&dyn ContextElement> = Self::get_loop_elements(val);
|
return if !val.map(|v| v.is_truthy()).unwrap_or(false) {
|
||||||
return if !loop_elements.is_empty() {
|
|
||||||
self.render_maybe_body(&container.else_contents, breadcrumbs, blocks)
|
|
||||||
} else {
|
|
||||||
self.render_maybe_body(&container.contents, breadcrumbs, blocks)
|
self.render_maybe_body(&container.contents, breadcrumbs, blocks)
|
||||||
|
} else {
|
||||||
|
self.render_maybe_body(&container.else_contents, breadcrumbs, blocks)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
DustTag::DTPartial(partial) => {
|
DustTag::DTPartial(partial) => {
|
||||||
@ -524,12 +560,19 @@ mod tests {
|
|||||||
use crate::parser::Filter;
|
use crate::parser::Filter;
|
||||||
use crate::renderer::context_element::Loopable;
|
use crate::renderer::context_element::Loopable;
|
||||||
use crate::renderer::context_element::Renderable;
|
use crate::renderer::context_element::Renderable;
|
||||||
|
use crate::renderer::context_element::Truthiness;
|
||||||
use crate::renderer::context_element::Walkable;
|
use crate::renderer::context_element::Walkable;
|
||||||
use crate::renderer::CompareContextElement;
|
use crate::renderer::CompareContextElement;
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
impl ContextElement for String {}
|
impl ContextElement for String {}
|
||||||
|
|
||||||
|
impl Truthiness for String {
|
||||||
|
fn is_truthy(&self) -> bool {
|
||||||
|
!self.is_empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Renderable for String {
|
impl Renderable for String {
|
||||||
fn render(&self, _filters: &Vec<Filter>) -> Result<String, RenderError> {
|
fn render(&self, _filters: &Vec<Filter>) -> Result<String, RenderError> {
|
||||||
Ok(self.clone())
|
Ok(self.clone())
|
||||||
@ -538,11 +581,7 @@ mod tests {
|
|||||||
|
|
||||||
impl Loopable for String {
|
impl Loopable for String {
|
||||||
fn get_loop_elements(&self) -> Vec<&dyn ContextElement> {
|
fn get_loop_elements(&self) -> Vec<&dyn ContextElement> {
|
||||||
if self.is_empty() {
|
Vec::new()
|
||||||
Vec::new()
|
|
||||||
} else {
|
|
||||||
vec![self]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -569,6 +608,12 @@ mod tests {
|
|||||||
}
|
}
|
||||||
impl ContextElement for u64 {}
|
impl ContextElement for u64 {}
|
||||||
|
|
||||||
|
impl Truthiness for u64 {
|
||||||
|
fn is_truthy(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Renderable for u64 {
|
impl Renderable for u64 {
|
||||||
fn render(&self, _filters: &Vec<Filter>) -> Result<String, RenderError> {
|
fn render(&self, _filters: &Vec<Filter>) -> Result<String, RenderError> {
|
||||||
Ok(self.to_string())
|
Ok(self.to_string())
|
||||||
@ -577,7 +622,7 @@ mod tests {
|
|||||||
|
|
||||||
impl Loopable for u64 {
|
impl Loopable for u64 {
|
||||||
fn get_loop_elements(&self) -> Vec<&dyn ContextElement> {
|
fn get_loop_elements(&self) -> Vec<&dyn ContextElement> {
|
||||||
vec![self]
|
Vec::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -605,6 +650,12 @@ mod tests {
|
|||||||
|
|
||||||
impl<I: 'static + ContextElement + Clone> ContextElement for HashMap<String, I> {}
|
impl<I: 'static + ContextElement + Clone> ContextElement for HashMap<String, I> {}
|
||||||
|
|
||||||
|
impl<I: ContextElement> Truthiness for HashMap<String, I> {
|
||||||
|
fn is_truthy(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<I: ContextElement> Renderable for HashMap<String, I> {
|
impl<I: ContextElement> Renderable for HashMap<String, I> {
|
||||||
fn render(&self, _filters: &Vec<Filter>) -> Result<String, RenderError> {
|
fn render(&self, _filters: &Vec<Filter>) -> Result<String, RenderError> {
|
||||||
// TODO: handle the filters
|
// TODO: handle the filters
|
||||||
@ -621,7 +672,7 @@ mod tests {
|
|||||||
|
|
||||||
impl<I: 'static + ContextElement + Clone> Loopable for HashMap<String, I> {
|
impl<I: 'static + ContextElement + Clone> Loopable for HashMap<String, I> {
|
||||||
fn get_loop_elements(&self) -> Vec<&dyn ContextElement> {
|
fn get_loop_elements(&self) -> Vec<&dyn ContextElement> {
|
||||||
vec![self]
|
Vec::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user