2020-04-11 00:27:27 +00:00
use crate ::parser ::template ;
2020-04-12 03:03:07 +00:00
use crate ::parser ::Body ;
2020-04-11 22:40:36 +00:00
use crate ::parser ::DustTag ;
2020-05-10 18:22:59 +00:00
use crate ::parser ::KVPair ;
2020-05-18 01:11:55 +00:00
use crate ::parser ::PartialNameElement ;
2020-05-25 19:40:42 +00:00
use crate ::parser ::Path ;
2020-05-10 18:22:59 +00:00
use crate ::parser ::RValue ;
2020-05-03 18:52:08 +00:00
use crate ::parser ::Special ;
2020-04-11 00:27:27 +00:00
use crate ::parser ::Template ;
2020-05-23 21:57:19 +00:00
use crate ::parser ::{ Filter , TemplateElement } ;
2020-04-28 23:09:02 +00:00
use crate ::renderer ::context_element ::ContextElement ;
2020-04-11 22:25:48 +00:00
use crate ::renderer ::errors ::CompileError ;
use crate ::renderer ::errors ::RenderError ;
2020-05-09 18:00:19 +00:00
use crate ::renderer ::errors ::WalkError ;
2020-05-10 02:05:43 +00:00
use crate ::renderer ::inline_partial_tree ::extract_inline_partials ;
use crate ::renderer ::inline_partial_tree ::InlinePartialTreeElement ;
2020-05-24 20:57:24 +00:00
use crate ::renderer ::iteration_context ::IterationContext ;
2020-05-11 02:21:18 +00:00
use crate ::renderer ::parameters_context ::ParametersContext ;
2020-05-24 19:21:30 +00:00
use crate ::renderer ::walking ::walk_path ;
2020-05-23 21:57:19 +00:00
use std ::collections ::HashMap ;
2020-04-11 00:27:27 +00:00
#[ derive(Clone, Debug) ]
pub struct CompiledTemplate < ' a > {
template : Template < ' a > ,
2020-04-11 22:25:48 +00:00
pub name : String ,
2020-04-11 00:27:27 +00:00
}
2020-04-11 00:55:44 +00:00
#[ derive(Clone, Debug) ]
2020-04-11 00:58:55 +00:00
pub struct DustRenderer < ' a > {
2020-04-11 22:25:48 +00:00
templates : HashMap < String , & ' a Template < ' a > > ,
2020-04-11 00:55:44 +00:00
}
2020-04-11 22:25:48 +00:00
pub fn compile_template < ' a > (
source : & ' a str ,
name : String ,
) -> Result < CompiledTemplate < ' a > , CompileError > {
// TODO: This could use better error management
2020-04-11 00:27:27 +00:00
let ( _remaining , parsed_template ) = template ( source ) . expect ( " Failed to compile template " ) ;
2020-04-11 22:25:48 +00:00
Ok ( CompiledTemplate {
2020-04-11 00:27:27 +00:00
template : parsed_template ,
name : name ,
2020-04-11 22:25:48 +00:00
} )
2020-04-11 00:27:27 +00:00
}
2020-04-11 00:55:44 +00:00
2020-04-11 00:58:55 +00:00
impl < ' a > DustRenderer < ' a > {
pub fn new ( ) -> DustRenderer < ' a > {
DustRenderer {
2020-04-11 22:25:48 +00:00
templates : HashMap ::new ( ) ,
2020-04-11 00:55:44 +00:00
}
}
pub fn load_source ( & mut self , template : & ' a CompiledTemplate ) {
2020-04-11 22:25:48 +00:00
self . templates
. insert ( template . name . clone ( ) , & template . template ) ;
2020-04-11 00:55:44 +00:00
}
2020-04-11 00:58:55 +00:00
2020-05-06 00:46:31 +00:00
pub fn render (
2020-05-05 23:51:07 +00:00
& ' a self ,
name : & str ,
breadcrumbs : & Vec < & ' a dyn ContextElement > ,
2020-05-10 02:05:43 +00:00
) -> Result < String , RenderError > {
self . render_template ( name , breadcrumbs , None )
}
fn render_template (
& ' a self ,
name : & str ,
breadcrumbs : & Vec < & ' a dyn ContextElement > ,
blocks : Option < & ' a InlinePartialTreeElement < ' a > > ,
2020-05-09 18:27:42 +00:00
) -> Result < String , RenderError > {
2020-05-05 23:51:07 +00:00
let main_template = match self . templates . get ( name ) {
Some ( tmpl ) = > tmpl ,
None = > {
2020-05-09 18:27:42 +00:00
return Err ( RenderError ::TemplateNotFound ( name . to_owned ( ) ) ) ;
2020-05-05 23:51:07 +00:00
}
} ;
2020-05-10 02:05:43 +00:00
let extracted_inline_partials = extract_inline_partials ( main_template ) ;
let new_blocks = InlinePartialTreeElement ::new ( blocks , extracted_inline_partials ) ;
2020-05-25 23:11:14 +00:00
let new_block_context = BlockContext {
breadcrumbs : breadcrumbs ,
blocks : & new_blocks ,
} ;
self . render_body ( & main_template . contents , breadcrumbs , & new_block_context )
2020-05-05 23:51:07 +00:00
}
2020-05-16 21:17:43 +00:00
fn render_maybe_body (
& ' a self ,
body : & ' a Option < Body > ,
breadcrumbs : & Vec < & ' a dyn ContextElement > ,
2020-05-25 23:11:14 +00:00
blocks : & ' a BlockContext < ' a > ,
2020-05-16 21:17:43 +00:00
) -> Result < String , RenderError > {
match body {
None = > Ok ( " " . to_owned ( ) ) ,
Some ( body ) = > Ok ( self . render_body ( body , breadcrumbs , blocks ) ? ) ,
}
}
2020-05-06 00:46:31 +00:00
fn render_body (
2020-05-05 23:51:07 +00:00
& ' a self ,
body : & ' a Body ,
breadcrumbs : & Vec < & ' a dyn ContextElement > ,
2020-05-25 23:11:14 +00:00
blocks : & ' a BlockContext < ' a > ,
2020-05-09 18:27:42 +00:00
) -> Result < String , RenderError > {
2020-05-05 23:51:07 +00:00
let mut output = String ::new ( ) ;
for elem in & body . elements {
match elem {
TemplateElement ::TEIgnoredWhitespace ( _ ) = > { }
TemplateElement ::TESpan ( span ) = > output . push_str ( span . contents ) ,
TemplateElement ::TETag ( dt ) = > {
2020-05-10 02:29:58 +00:00
output . push_str ( & self . render_tag ( dt , breadcrumbs , blocks ) ? ) ;
2020-05-05 23:51:07 +00:00
}
}
}
Ok ( output )
}
2020-05-25 22:08:29 +00:00
/// For rendering a dynamic partial's name
2020-05-18 01:11:55 +00:00
fn render_partial_name (
& ' a self ,
body : & ' a Vec < PartialNameElement > ,
breadcrumbs : & Vec < & ' a dyn ContextElement > ,
2020-05-25 23:11:14 +00:00
blocks : & ' a BlockContext < ' a > ,
2020-05-18 01:11:55 +00:00
) -> Result < String , RenderError > {
let converted_to_template_elements : Vec < TemplateElement < ' a > > =
body . into_iter ( ) . map ( | e | e . into ( ) ) . collect ( ) ;
self . render_body (
& Body {
elements : converted_to_template_elements ,
} ,
breadcrumbs ,
blocks ,
)
}
2020-05-06 00:46:31 +00:00
fn render_tag (
2020-05-05 23:51:07 +00:00
& ' a self ,
tag : & ' a DustTag ,
breadcrumbs : & Vec < & ' a dyn ContextElement > ,
2020-05-25 23:11:14 +00:00
blocks : & ' a BlockContext < ' a > ,
2020-05-09 18:27:42 +00:00
) -> Result < String , RenderError > {
2020-05-05 23:51:07 +00:00
match tag {
DustTag ::DTComment ( _comment ) = > ( ) ,
DustTag ::DTSpecial ( special ) = > {
return Ok ( match special {
Special ::Space = > " " ,
Special ::NewLine = > " \n " ,
Special ::CarriageReturn = > " \r " ,
Special ::LeftCurlyBrace = > " { " ,
Special ::RightCurlyBrace = > " } " ,
}
. to_owned ( ) )
}
2020-05-24 03:41:05 +00:00
DustTag ::DTLiteralStringBlock ( literal ) = > return Ok ( ( * literal ) . to_owned ( ) ) ,
2020-05-05 23:51:07 +00:00
DustTag ::DTReference ( reference ) = > {
2020-05-24 19:21:30 +00:00
let val = walk_path ( breadcrumbs , & reference . path . keys ) ;
2020-05-07 00:30:03 +00:00
match val {
2020-05-09 18:10:38 +00:00
Err ( WalkError ::CantWalk ) = > return Ok ( " " . to_owned ( ) ) ,
2020-05-07 00:30:03 +00:00
Ok ( final_val ) = > {
2020-05-24 20:57:24 +00:00
return if final_val . is_truthy ( ) {
final_val . render ( & Self ::preprocess_filters ( & reference . filters ) )
2020-05-07 00:30:03 +00:00
} else {
2020-05-24 20:57:24 +00:00
Ok ( " " . to_owned ( ) )
} ;
2020-05-07 00:30:03 +00:00
}
2020-05-05 23:51:07 +00:00
}
}
DustTag ::DTSection ( container ) = > {
2020-05-24 19:21:30 +00:00
let val = walk_path ( breadcrumbs , & container . path . keys ) ;
2020-05-24 20:57:24 +00:00
match val {
Err ( WalkError ::CantWalk ) = > {
2020-05-25 19:49:30 +00:00
let new_breadcrumbs = Self ::new_breadcrumbs (
2020-05-25 23:11:14 +00:00
breadcrumbs ,
2020-05-25 19:49:30 +00:00
breadcrumbs ,
None ,
& container . explicit_context ,
None ,
) ;
2020-05-24 20:57:24 +00:00
return self . render_maybe_body (
& container . else_contents ,
2020-05-25 19:49:30 +00:00
new_breadcrumbs . as_ref ( ) . unwrap_or ( breadcrumbs ) ,
2020-05-24 20:57:24 +00:00
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
2020-05-25 19:55:52 +00:00
let new_breadcrumbs = Self ::new_breadcrumbs (
2020-05-25 23:11:14 +00:00
breadcrumbs ,
2020-05-25 19:55:52 +00:00
breadcrumbs ,
None ,
& container . explicit_context ,
Some ( final_val ) ,
) ;
self . render_body (
body ,
new_breadcrumbs . as_ref ( ) . unwrap_or ( breadcrumbs ) ,
blocks ,
)
2020-05-24 20:57:24 +00:00
} 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 ) ;
2020-05-25 19:55:52 +00:00
let new_breadcrumbs = Self ::new_breadcrumbs (
2020-05-25 23:11:14 +00:00
breadcrumbs ,
2020-05-25 19:55:52 +00:00
breadcrumbs ,
Some ( & injected_context ) ,
& container . explicit_context ,
Some ( array_elem ) ,
) ;
2020-05-24 20:57:24 +00:00
self . render_body (
& body ,
2020-05-25 19:55:52 +00:00
new_breadcrumbs
. as_ref ( )
. unwrap_or ( breadcrumbs ) ,
2020-05-24 20:57:24 +00:00
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
2020-05-25 19:55:52 +00:00
let new_breadcrumbs = Self ::new_breadcrumbs (
2020-05-25 23:11:14 +00:00
breadcrumbs ,
2020-05-25 19:55:52 +00:00
breadcrumbs ,
None ,
& container . explicit_context ,
None ,
) ;
2020-05-24 20:57:24 +00:00
return self . render_maybe_body (
& container . else_contents ,
2020-05-25 19:55:52 +00:00
new_breadcrumbs . as_ref ( ) . unwrap_or ( breadcrumbs ) ,
2020-05-24 20:57:24 +00:00
blocks ,
) ;
} ;
2020-05-05 23:51:07 +00:00
}
}
}
2020-05-06 23:10:09 +00:00
DustTag ::DTExists ( container ) = > {
2020-05-25 23:11:14 +00:00
let new_breadcrumbs = Self ::new_breadcrumbs (
breadcrumbs ,
breadcrumbs ,
None ,
& container . explicit_context ,
None ,
) ;
2020-05-24 19:21:30 +00:00
let val = walk_path ( breadcrumbs , & container . path . keys ) ;
2020-05-24 20:57:24 +00:00
return if val . map ( | v | v . is_truthy ( ) ) . unwrap_or ( false ) {
2020-05-25 20:03:00 +00:00
self . render_maybe_body (
& container . contents ,
new_breadcrumbs . as_ref ( ) . unwrap_or ( breadcrumbs ) ,
blocks ,
)
2020-05-24 20:57:24 +00:00
} else {
2020-05-25 20:03:00 +00:00
self . render_maybe_body (
& container . else_contents ,
new_breadcrumbs . as_ref ( ) . unwrap_or ( breadcrumbs ) ,
blocks ,
)
2020-05-16 21:17:43 +00:00
} ;
2020-05-06 23:10:09 +00:00
}
2020-05-07 00:13:33 +00:00
DustTag ::DTNotExists ( container ) = > {
2020-05-25 23:11:14 +00:00
let new_breadcrumbs = Self ::new_breadcrumbs (
breadcrumbs ,
breadcrumbs ,
None ,
& container . explicit_context ,
None ,
) ;
2020-05-24 19:21:30 +00:00
let val = walk_path ( breadcrumbs , & container . path . keys ) ;
2020-05-24 20:57:24 +00:00
return if ! val . map ( | v | v . is_truthy ( ) ) . unwrap_or ( false ) {
2020-05-25 20:03:00 +00:00
self . render_maybe_body (
& container . contents ,
new_breadcrumbs . as_ref ( ) . unwrap_or ( breadcrumbs ) ,
blocks ,
)
2020-05-24 20:57:24 +00:00
} else {
2020-05-25 20:03:00 +00:00
self . render_maybe_body (
& container . else_contents ,
new_breadcrumbs . as_ref ( ) . unwrap_or ( breadcrumbs ) ,
blocks ,
)
2020-05-16 21:17:43 +00:00
} ;
2020-05-07 00:13:33 +00:00
}
2020-05-18 01:17:34 +00:00
DustTag ::DTPartial ( partial ) = > {
2020-05-18 01:11:55 +00:00
let partial_name = self . render_partial_name ( & partial . name , breadcrumbs , blocks ) ? ;
if partial . params . is_empty ( ) {
2020-05-25 23:11:14 +00:00
let new_breadcrumbs = Self ::new_breadcrumbs (
breadcrumbs ,
breadcrumbs ,
None ,
& partial . explicit_context ,
None ,
) ;
2020-05-25 20:03:00 +00:00
let rendered_content = self . render_template (
& partial_name ,
new_breadcrumbs . as_ref ( ) . unwrap_or ( breadcrumbs ) ,
2020-05-25 23:11:14 +00:00
Some ( blocks . blocks ) ,
2020-05-25 20:03:00 +00:00
) ? ;
2020-05-18 01:11:55 +00:00
return Ok ( rendered_content ) ;
} else {
let injected_context = ParametersContext ::new ( breadcrumbs , & partial . params ) ;
2020-05-25 20:30:15 +00:00
let new_breadcrumbs = Self ::new_breadcrumbs (
2020-05-25 23:11:14 +00:00
breadcrumbs ,
2020-05-25 20:30:15 +00:00
breadcrumbs ,
Some ( & injected_context ) ,
& partial . explicit_context ,
None ,
) ;
let rendered_content = self . render_template (
& partial_name ,
new_breadcrumbs . as_ref ( ) . unwrap_or ( breadcrumbs ) ,
2020-05-25 23:11:14 +00:00
Some ( blocks . blocks ) ,
2020-05-25 20:30:15 +00:00
) ? ;
2020-05-18 01:11:55 +00:00
return Ok ( rendered_content ) ;
}
}
2020-05-16 21:17:43 +00:00
DustTag ::DTInlinePartial ( _named_block ) = > {
2020-05-10 01:30:54 +00:00
// Inline partials are blank during rendering (they get injected into blocks)
2020-05-25 21:53:04 +00:00
// TODO: We will need to support explicit contexts for inline partials.
2020-05-10 01:30:54 +00:00
return Ok ( " " . to_owned ( ) ) ;
}
DustTag ::DTBlock ( named_block ) = > {
2020-05-25 23:11:14 +00:00
let new_breadcrumbs = blocks
. blocks
. get_explicit_context ( named_block . name )
. map ( | explicit_context | {
Self ::new_breadcrumbs (
breadcrumbs ,
blocks . breadcrumbs ,
None ,
explicit_context ,
None ,
)
} )
. flatten ( ) ;
return match blocks . blocks . get_block ( named_block . name ) {
None = > self . render_maybe_body (
& named_block . contents ,
new_breadcrumbs . as_ref ( ) . unwrap_or ( breadcrumbs ) ,
blocks ,
) ,
Some ( interior ) = > self . render_maybe_body (
interior ,
new_breadcrumbs . as_ref ( ) . unwrap_or ( breadcrumbs ) ,
blocks ,
) ,
2020-05-10 02:48:30 +00:00
} ;
2020-05-10 01:30:54 +00:00
}
2020-05-10 18:22:59 +00:00
DustTag ::DTHelperEquals ( parameterized_block ) = > {
2020-05-25 22:08:29 +00:00
let new_breadcrumbs = Self ::new_breadcrumbs (
2020-05-25 23:11:14 +00:00
breadcrumbs ,
2020-05-25 22:08:29 +00:00
breadcrumbs ,
None ,
& parameterized_block . explicit_context ,
None ,
) ;
2020-05-16 21:17:43 +00:00
let param_map : HashMap < & str , & RValue < ' a > > =
Self ::get_rval_map ( & parameterized_block . params ) ;
2020-05-16 19:22:48 +00:00
// Special case: when comparing two RVPaths, if the
// path is equal then dust assumes the values are
// equal (otherwise, non-scalar values are
// automatically not equal)
if Self ::are_paths_identical ( & param_map ) {
return match & parameterized_block . contents {
None = > Ok ( " " . to_owned ( ) ) ,
Some ( body ) = > {
2020-05-25 22:08:29 +00:00
let rendered_content = self . render_body (
body ,
new_breadcrumbs . as_ref ( ) . unwrap_or ( breadcrumbs ) ,
blocks ,
) ? ;
2020-05-16 19:22:48 +00:00
Ok ( rendered_content )
}
} ;
}
2020-05-16 21:17:43 +00:00
let left_side : Result < & dyn ContextElement , WalkError > =
match Self ::get_rval ( breadcrumbs , & param_map , " key " ) {
None = > return Ok ( " " . to_owned ( ) ) ,
Some ( res ) = > res ,
2020-05-10 18:22:59 +00:00
} ;
2020-05-16 21:17:43 +00:00
let right_side : Result < & dyn ContextElement , WalkError > =
Self ::get_rval ( breadcrumbs , & param_map , " value " )
. unwrap_or ( Err ( WalkError ::CantWalk ) ) ;
2020-05-25 22:08:29 +00:00
2020-05-11 01:00:52 +00:00
if left_side = = right_side {
2020-05-16 21:17:43 +00:00
return self . render_maybe_body (
& parameterized_block . contents ,
2020-05-25 22:08:29 +00:00
new_breadcrumbs . as_ref ( ) . unwrap_or ( breadcrumbs ) ,
2020-05-16 21:17:43 +00:00
blocks ,
) ;
2020-05-11 01:14:51 +00:00
} else {
2020-05-16 21:17:43 +00:00
return self . render_maybe_body (
& parameterized_block . else_contents ,
2020-05-25 22:08:29 +00:00
new_breadcrumbs . as_ref ( ) . unwrap_or ( breadcrumbs ) ,
2020-05-16 21:17:43 +00:00
blocks ,
) ;
2020-05-16 16:26:28 +00:00
}
}
DustTag ::DTHelperNotEquals ( parameterized_block ) = > {
2020-05-25 22:08:29 +00:00
let new_breadcrumbs = Self ::new_breadcrumbs (
2020-05-25 23:11:14 +00:00
breadcrumbs ,
2020-05-25 22:08:29 +00:00
breadcrumbs ,
None ,
& parameterized_block . explicit_context ,
None ,
) ;
2020-05-16 21:17:43 +00:00
let param_map : HashMap < & str , & RValue < ' a > > =
Self ::get_rval_map ( & parameterized_block . params ) ;
2020-05-16 19:30:17 +00:00
// Special case: when comparing two RVPaths, if the
// path is equal then dust assumes the values are
// equal (otherwise, non-scalar values are
// automatically not equal)
if Self ::are_paths_identical ( & param_map ) {
return match & parameterized_block . else_contents {
None = > Ok ( " " . to_owned ( ) ) ,
Some ( body ) = > {
2020-05-25 22:08:29 +00:00
let rendered_content = self . render_body (
body ,
new_breadcrumbs . as_ref ( ) . unwrap_or ( breadcrumbs ) ,
blocks ,
) ? ;
2020-05-16 19:30:17 +00:00
Ok ( rendered_content )
}
} ;
}
2020-05-16 21:17:43 +00:00
let left_side : Result < & dyn ContextElement , WalkError > =
match Self ::get_rval ( breadcrumbs , & param_map , " key " ) {
None = > return Ok ( " " . to_owned ( ) ) ,
Some ( res ) = > res ,
2020-05-16 16:26:28 +00:00
} ;
2020-05-16 21:17:43 +00:00
let right_side : Result < & dyn ContextElement , WalkError > =
Self ::get_rval ( breadcrumbs , & param_map , " value " )
. unwrap_or ( Err ( WalkError ::CantWalk ) ) ;
2020-05-16 16:26:28 +00:00
if left_side ! = right_side {
2020-05-16 21:17:43 +00:00
return self . render_maybe_body (
& parameterized_block . contents ,
2020-05-25 22:08:29 +00:00
new_breadcrumbs . as_ref ( ) . unwrap_or ( breadcrumbs ) ,
2020-05-16 21:17:43 +00:00
blocks ,
) ;
2020-05-16 16:26:28 +00:00
} else {
2020-05-16 21:17:43 +00:00
return self . render_maybe_body (
& parameterized_block . else_contents ,
2020-05-25 22:08:29 +00:00
new_breadcrumbs . as_ref ( ) . unwrap_or ( breadcrumbs ) ,
2020-05-16 21:17:43 +00:00
blocks ,
) ;
2020-05-10 23:16:55 +00:00
}
2020-05-10 18:22:59 +00:00
}
2020-05-16 19:45:57 +00:00
DustTag ::DTHelperGreaterThan ( parameterized_block ) = > {
2020-05-25 22:08:29 +00:00
let new_breadcrumbs = Self ::new_breadcrumbs (
2020-05-25 23:11:14 +00:00
breadcrumbs ,
2020-05-25 22:08:29 +00:00
breadcrumbs ,
None ,
& parameterized_block . explicit_context ,
None ,
) ;
2020-05-16 21:17:43 +00:00
let param_map : HashMap < & str , & RValue < ' a > > =
Self ::get_rval_map ( & parameterized_block . params ) ;
let left_side : Result < & dyn ContextElement , WalkError > =
match Self ::get_rval ( breadcrumbs , & param_map , " key " ) {
None = > return Ok ( " " . to_owned ( ) ) ,
Some ( res ) = > res ,
2020-05-16 20:24:36 +00:00
} ;
2020-05-16 21:17:43 +00:00
let right_side : Result < & dyn ContextElement , WalkError > =
Self ::get_rval ( breadcrumbs , & param_map , " value " )
. unwrap_or ( Err ( WalkError ::CantWalk ) ) ;
2020-05-16 20:24:36 +00:00
match ( left_side , right_side ) {
2020-05-16 22:28:23 +00:00
( Err ( _ ) , _ ) | ( _ , Err ( _ ) ) = > {
return self . render_maybe_body (
& parameterized_block . else_contents ,
2020-05-25 22:08:29 +00:00
new_breadcrumbs . as_ref ( ) . unwrap_or ( breadcrumbs ) ,
2020-05-16 22:28:23 +00:00
blocks ,
)
}
2020-05-16 20:24:36 +00:00
( Ok ( left_side_unwrapped ) , Ok ( right_side_unwrapped ) ) = > {
if left_side_unwrapped > right_side_unwrapped {
2020-05-16 21:17:43 +00:00
return self . render_maybe_body (
& parameterized_block . contents ,
2020-05-25 22:08:29 +00:00
new_breadcrumbs . as_ref ( ) . unwrap_or ( breadcrumbs ) ,
2020-05-16 21:17:43 +00:00
blocks ,
) ;
2020-05-16 20:24:36 +00:00
} else {
2020-05-16 21:17:43 +00:00
return self . render_maybe_body (
& parameterized_block . else_contents ,
2020-05-25 22:08:29 +00:00
new_breadcrumbs . as_ref ( ) . unwrap_or ( breadcrumbs ) ,
2020-05-16 21:17:43 +00:00
blocks ,
) ;
2020-05-16 20:24:36 +00:00
}
}
}
2020-05-16 19:45:57 +00:00
}
2020-05-16 23:05:03 +00:00
DustTag ::DTHelperGreaterThanOrEquals ( parameterized_block ) = > {
2020-05-25 22:08:29 +00:00
let new_breadcrumbs = Self ::new_breadcrumbs (
2020-05-25 23:11:14 +00:00
breadcrumbs ,
2020-05-25 22:08:29 +00:00
breadcrumbs ,
None ,
& parameterized_block . explicit_context ,
None ,
) ;
2020-05-16 22:28:23 +00:00
let param_map : HashMap < & str , & RValue < ' a > > =
Self ::get_rval_map ( & parameterized_block . params ) ;
let left_side : Result < & dyn ContextElement , WalkError > =
match Self ::get_rval ( breadcrumbs , & param_map , " key " ) {
None = > return Ok ( " " . to_owned ( ) ) ,
Some ( res ) = > res ,
} ;
let right_side : Result < & dyn ContextElement , WalkError > =
Self ::get_rval ( breadcrumbs , & param_map , " value " )
. unwrap_or ( Err ( WalkError ::CantWalk ) ) ;
match ( left_side , right_side ) {
( Err ( _ ) , _ ) | ( _ , Err ( _ ) ) = > {
return self . render_maybe_body (
2020-05-16 22:36:47 +00:00
& parameterized_block . else_contents ,
2020-05-25 22:08:29 +00:00
new_breadcrumbs . as_ref ( ) . unwrap_or ( breadcrumbs ) ,
2020-05-16 22:28:23 +00:00
blocks ,
)
}
( Ok ( left_side_unwrapped ) , Ok ( right_side_unwrapped ) ) = > {
2020-05-17 03:10:04 +00:00
if left_side_unwrapped > = right_side_unwrapped {
return self . render_maybe_body (
& parameterized_block . contents ,
2020-05-25 22:08:29 +00:00
new_breadcrumbs . as_ref ( ) . unwrap_or ( breadcrumbs ) ,
2020-05-17 03:10:04 +00:00
blocks ,
) ;
} else {
return self . render_maybe_body (
2020-05-16 22:28:23 +00:00
& parameterized_block . else_contents ,
2020-05-25 22:08:29 +00:00
new_breadcrumbs . as_ref ( ) . unwrap_or ( breadcrumbs ) ,
2020-05-16 22:28:23 +00:00
blocks ,
2020-05-17 03:10:04 +00:00
) ;
}
2020-05-16 22:28:23 +00:00
}
}
}
2020-05-16 23:05:03 +00:00
DustTag ::DTHelperLessThan ( parameterized_block ) = > {
2020-05-25 22:08:29 +00:00
let new_breadcrumbs = Self ::new_breadcrumbs (
2020-05-25 23:11:14 +00:00
breadcrumbs ,
2020-05-25 22:08:29 +00:00
breadcrumbs ,
None ,
& parameterized_block . explicit_context ,
None ,
) ;
2020-05-16 23:05:03 +00:00
let param_map : HashMap < & str , & RValue < ' a > > =
Self ::get_rval_map ( & parameterized_block . params ) ;
let left_side : Result < & dyn ContextElement , WalkError > =
match Self ::get_rval ( breadcrumbs , & param_map , " key " ) {
None = > return Ok ( " " . to_owned ( ) ) ,
Some ( res ) = > res ,
} ;
let right_side : Result < & dyn ContextElement , WalkError > =
Self ::get_rval ( breadcrumbs , & param_map , " value " )
. unwrap_or ( Err ( WalkError ::CantWalk ) ) ;
match ( left_side , right_side ) {
( Err ( _ ) , _ ) | ( _ , Err ( _ ) ) = > {
return self . render_maybe_body (
& parameterized_block . else_contents ,
2020-05-25 22:08:29 +00:00
new_breadcrumbs . as_ref ( ) . unwrap_or ( breadcrumbs ) ,
2020-05-16 23:05:03 +00:00
blocks ,
)
}
( Ok ( left_side_unwrapped ) , Ok ( right_side_unwrapped ) ) = > {
if left_side_unwrapped < right_side_unwrapped {
return self . render_maybe_body (
& parameterized_block . contents ,
2020-05-25 22:08:29 +00:00
new_breadcrumbs . as_ref ( ) . unwrap_or ( breadcrumbs ) ,
2020-05-16 23:05:03 +00:00
blocks ,
) ;
} else {
return self . render_maybe_body (
& parameterized_block . else_contents ,
2020-05-25 22:08:29 +00:00
new_breadcrumbs . as_ref ( ) . unwrap_or ( breadcrumbs ) ,
2020-05-16 23:05:03 +00:00
blocks ,
) ;
}
}
}
}
2020-05-16 23:06:40 +00:00
DustTag ::DTHelperLessThanOrEquals ( parameterized_block ) = > {
2020-05-25 22:08:29 +00:00
let new_breadcrumbs = Self ::new_breadcrumbs (
2020-05-25 23:11:14 +00:00
breadcrumbs ,
2020-05-25 22:08:29 +00:00
breadcrumbs ,
None ,
& parameterized_block . explicit_context ,
None ,
) ;
2020-05-16 23:06:40 +00:00
let param_map : HashMap < & str , & RValue < ' a > > =
Self ::get_rval_map ( & parameterized_block . params ) ;
let left_side : Result < & dyn ContextElement , WalkError > =
match Self ::get_rval ( breadcrumbs , & param_map , " key " ) {
None = > return Ok ( " " . to_owned ( ) ) ,
Some ( res ) = > res ,
} ;
let right_side : Result < & dyn ContextElement , WalkError > =
Self ::get_rval ( breadcrumbs , & param_map , " value " )
. unwrap_or ( Err ( WalkError ::CantWalk ) ) ;
match ( left_side , right_side ) {
( Err ( _ ) , _ ) | ( _ , Err ( _ ) ) = > {
return self . render_maybe_body (
& parameterized_block . else_contents ,
2020-05-25 22:08:29 +00:00
new_breadcrumbs . as_ref ( ) . unwrap_or ( breadcrumbs ) ,
2020-05-16 23:06:40 +00:00
blocks ,
)
}
( Ok ( left_side_unwrapped ) , Ok ( right_side_unwrapped ) ) = > {
2020-05-17 03:10:04 +00:00
if left_side_unwrapped < = right_side_unwrapped {
return self . render_maybe_body (
& parameterized_block . contents ,
2020-05-25 22:08:29 +00:00
new_breadcrumbs . as_ref ( ) . unwrap_or ( breadcrumbs ) ,
2020-05-17 03:10:04 +00:00
blocks ,
) ;
} else {
return self . render_maybe_body (
2020-05-16 23:06:40 +00:00
& parameterized_block . else_contents ,
2020-05-25 22:08:29 +00:00
new_breadcrumbs . as_ref ( ) . unwrap_or ( breadcrumbs ) ,
2020-05-16 23:06:40 +00:00
blocks ,
2020-05-17 03:10:04 +00:00
) ;
}
2020-05-16 23:06:40 +00:00
}
}
}
2020-05-05 23:51:07 +00:00
}
Ok ( " " . to_owned ( ) )
}
2020-05-03 19:29:02 +00:00
/// Gets the elements to loop over for a section.
///
/// If the value is falsey, and therefore should render the else
/// block, this will return an empty vector.
fn get_loop_elements < ' b > (
2020-05-09 18:14:22 +00:00
walk_result : Result < & ' b dyn ContextElement , WalkError > ,
) -> Vec < & ' b dyn ContextElement > {
match walk_result {
Err ( WalkError ::CantWalk ) = > Vec ::new ( ) ,
Ok ( walk_target ) = > walk_target . get_loop_elements ( ) ,
2020-05-03 19:29:02 +00:00
}
}
2020-05-16 19:22:48 +00:00
fn are_paths_identical < ' b > ( param_map : & HashMap < & str , & RValue < ' b > > ) -> bool {
match ( param_map . get ( " key " ) , param_map . get ( " value " ) ) {
( None , _ ) = > false ,
( _ , None ) = > false ,
( Some ( key_rval ) , Some ( value_rval ) ) = > match ( key_rval , value_rval ) {
( RValue ::RVPath ( key_path ) , RValue ::RVPath ( value_path ) ) = > {
key_path . keys = = value_path . keys
}
_ = > false ,
} ,
}
}
2020-05-16 21:17:43 +00:00
fn get_rval_map < ' b > ( params : & ' b Vec < KVPair < ' b > > ) -> HashMap < & ' b str , & ' b RValue < ' b > > {
params
. iter ( )
. map ( | pair : & KVPair < ' b > | ( pair . key , & pair . value ) )
. collect ( )
}
fn get_rval < ' b > (
2020-05-24 19:01:41 +00:00
breadcrumbs : & ' b Vec < & ' b dyn ContextElement > ,
2020-05-16 21:17:43 +00:00
param_map : & HashMap < & str , & ' b RValue < ' b > > ,
key : & str ,
) -> Option < Result < & ' b dyn ContextElement , WalkError > > {
match param_map . get ( key ) {
None = > None ,
Some ( rval ) = > match rval {
2020-05-17 02:30:04 +00:00
RValue ::RVLiteral ( literal ) = > Some ( Ok ( literal ) ) ,
2020-05-24 19:21:30 +00:00
RValue ::RVPath ( path ) = > Some ( walk_path ( breadcrumbs , & path . keys ) ) ,
2020-05-16 21:17:43 +00:00
} ,
}
}
2020-05-23 21:57:19 +00:00
fn preprocess_filters ( filters : & Vec < Filter > ) -> Vec < Filter > {
let mut final_filters : Vec < Filter > = filters
. into_iter ( )
. filter ( | f | f ! = & & Filter ::DisableHtmlEncode )
. map ( | f | f . clone ( ) )
. collect ( ) ;
// If the user has not specified any escaping filter (|s or
// |h), automatically add an html escape filter
2020-05-23 22:18:59 +00:00
if ! filters . iter ( ) . any ( | f | f = = & Filter ::DisableHtmlEncode ) {
2020-05-23 21:57:19 +00:00
final_filters . push ( Filter ::HtmlEncode ) ;
}
final_filters
}
2020-05-25 19:40:42 +00:00
/// Generate a new breadcrumbs object
///
/// This function generates a new breadcrumbs object based on the
/// new context information provided.
///
2020-05-25 23:11:14 +00:00
/// breadcrumbs are the breadcrumbs that will be used in the final
/// breadcrumbs (unless omitted due to an explicit context)
///
/// explicit_context_breadcrumbs are the breadcrumbs used to
/// evaluate an explicit context path. Most of the time the two
/// breadcrumbs parameters will be identical, but for
/// blocks/inline partials the explicit_context_breadcrumbs will
/// be the breadcrumbs from the start of the partial containing
/// the block.
///
2020-05-25 19:40:42 +00:00
/// explicit_context is for contexts specified with a `:path`
/// inside a dust tag.
///
/// injected_context is for any generated context. This includes
/// both parameters on a tag and also the handling of $idx and
/// $len.
///
/// New context element is the element is an element to append to
/// the end, generally for use in section tags which walk to a new
/// context.
///
/// If explicit_context is not None, then the final breadcrumb stack will be:
///
2020-05-25 22:08:29 +00:00
/// ```text
2020-05-25 19:40:42 +00:00
/// breadcrumbs
/// injected_context
/// new_context_element
/// ```
///
/// However, if explicit_context is not None, then the old
/// breadcrumbs are omitted, leading to the new breadcrumb stack
/// as:
///
2020-05-25 22:08:29 +00:00
/// ```text
2020-05-25 19:40:42 +00:00
/// injected_context
/// explicit_context
/// new_context_element
/// ```
fn new_breadcrumbs < ' b > (
breadcrumbs : & ' b Vec < & ' b dyn ContextElement > ,
2020-05-25 23:11:14 +00:00
explicit_context_breadcrumbs : & ' b Vec < & ' b dyn ContextElement > ,
2020-05-25 19:40:42 +00:00
injected_context : Option < & ' b dyn ContextElement > ,
explicit_context : & Option < Path < ' b > > ,
new_context_element : Option < & ' b dyn ContextElement > ,
2020-05-25 19:49:30 +00:00
) -> Option < Vec < & ' b dyn ContextElement > > {
// If none of the additional contexts are present, return None
// to signal that the original breadcrumbs should be used
// rather than incurring a copy here.
match ( injected_context , explicit_context , new_context_element ) {
( None , None , None ) = > return None ,
_ = > ( ) ,
} ;
2020-05-25 19:40:42 +00:00
let mut new_stack = match explicit_context {
Some ( _ ) = > Vec ::with_capacity ( 3 ) ,
None = > breadcrumbs . clone ( ) ,
} ;
2020-05-25 21:53:04 +00:00
// TODO: Can sections have parameters, and if so, what happens then? Currently when there is an injected context or an explicit context it gets inserted behind the current context, so 1->2->3 becomes 1->2->injected->3 or explicit->3. When there is a new context(4) with injected we're doing 1->2->3->injected->4. When there is an explicit context and a new context we're doing explicit->4. But what happens if there is a section with parameters and an explicit context, hitting all the categories? Would it be parameters->explicit->4? I would definitely have to change the parameters to this function since right now iteration variables and parameters are both sharing injected_context.
2020-05-25 20:30:15 +00:00
injected_context . map ( | ctx | {
// Special case: when there is no explicit context or new
// context element, the injected context gets inserted 1
// spot behind the current context. Otherwise, the
// injected context gets added after the current context
// but before the explicit context and new context
// element.
match ( explicit_context , new_context_element ) {
( None , None ) = > new_stack . insert ( std ::cmp ::max ( new_stack . len ( ) - 1 , 0 ) , ctx ) ,
_ = > new_stack . push ( ctx ) ,
}
} ) ;
2020-05-25 19:40:42 +00:00
explicit_context . as_ref ( ) . map ( | path | {
2020-05-25 23:11:14 +00:00
walk_path ( explicit_context_breadcrumbs , & path . keys ) . map ( | val | {
2020-05-25 19:40:42 +00:00
if val . is_truthy ( ) {
new_stack . push ( val )
}
} ) ;
} ) ;
new_context_element . map ( | ctx | new_stack . push ( ctx ) ) ;
2020-05-25 19:49:30 +00:00
Some ( new_stack )
2020-05-25 19:40:42 +00:00
}
2020-04-11 00:55:44 +00:00
}
2020-04-12 00:31:44 +00:00
2020-05-25 23:11:14 +00:00
struct BlockContext < ' a > {
/// The breadcrumbs at the time of entering the current partial
breadcrumbs : & ' a Vec < & ' a dyn ContextElement > ,
blocks : & ' a InlinePartialTreeElement < ' a > ,
}
2020-04-12 00:31:44 +00:00
#[ cfg(test) ]
mod tests {
use super ::* ;
2020-04-13 01:54:15 +00:00
use crate ::parser ::Filter ;
2020-04-28 23:34:52 +00:00
use crate ::renderer ::context_element ::Loopable ;
2020-04-28 23:09:02 +00:00
use crate ::renderer ::context_element ::Renderable ;
2020-05-24 20:16:43 +00:00
use crate ::renderer ::context_element ::Truthiness ;
2020-04-28 23:09:02 +00:00
use crate ::renderer ::context_element ::Walkable ;
2020-05-11 03:26:15 +00:00
use crate ::renderer ::CompareContextElement ;
2020-05-23 23:34:38 +00:00
use std ::cmp ::Ordering ;
2020-04-12 00:31:44 +00:00
2020-05-17 02:39:29 +00:00
impl ContextElement for String { }
2020-05-24 20:16:43 +00:00
impl Truthiness for String {
fn is_truthy ( & self ) -> bool {
! self . is_empty ( )
}
}
2020-05-17 02:39:29 +00:00
impl Renderable for String {
fn render ( & self , _filters : & Vec < Filter > ) -> Result < String , RenderError > {
Ok ( self . clone ( ) )
}
}
impl Loopable for String {
fn get_loop_elements ( & self ) -> Vec < & dyn ContextElement > {
2020-05-24 20:27:13 +00:00
Vec ::new ( )
2020-05-17 02:39:29 +00:00
}
}
impl Walkable for String {
fn walk ( & self , segment : & str ) -> Result < & dyn ContextElement , WalkError > {
Err ( WalkError ::CantWalk )
}
}
impl CompareContextElement for String {
fn equals ( & self , other : & dyn ContextElement ) -> bool {
match other . to_any ( ) . downcast_ref ::< Self > ( ) {
None = > false ,
Some ( other_string ) = > self = = other_string ,
}
}
fn partial_compare ( & self , other : & dyn ContextElement ) -> Option < Ordering > {
match other . to_any ( ) . downcast_ref ::< Self > ( ) {
None = > None ,
Some ( other_string ) = > self . partial_cmp ( other_string ) ,
}
}
}
impl ContextElement for u64 { }
2020-05-24 20:16:43 +00:00
impl Truthiness for u64 {
fn is_truthy ( & self ) -> bool {
true
}
}
2020-05-17 02:39:29 +00:00
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 > {
2020-05-24 20:27:13 +00:00
Vec ::new ( )
2020-05-17 02:39:29 +00:00
}
}
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 {
match other . to_any ( ) . downcast_ref ::< Self > ( ) {
None = > false ,
Some ( other_num ) = > self = = other_num ,
}
}
fn partial_compare ( & self , other : & dyn ContextElement ) -> Option < Ordering > {
match other . to_any ( ) . downcast_ref ::< Self > ( ) {
None = > None ,
Some ( other_num ) = > self . partial_cmp ( other_num ) ,
}
}
}
2020-05-11 03:26:15 +00:00
impl < I : 'static + ContextElement + Clone > ContextElement for HashMap < String , I > { }
2020-04-12 02:19:54 +00:00
2020-05-24 20:16:43 +00:00
impl < I : ContextElement > Truthiness for HashMap < String , I > {
fn is_truthy ( & self ) -> bool {
true
}
}
2020-05-11 03:26:15 +00:00
impl < I : ContextElement > Renderable for HashMap < String , I > {
2020-05-06 00:22:25 +00:00
fn render ( & self , _filters : & Vec < Filter > ) -> Result < String , RenderError > {
// TODO: handle the filters
2020-05-09 17:46:12 +00:00
Ok ( " [object Object] " . to_owned ( ) )
2020-04-12 00:31:44 +00:00
}
2020-05-06 00:22:25 +00:00
}
2020-04-12 00:31:44 +00:00
2020-05-11 03:26:15 +00:00
impl < I : ContextElement > Walkable for HashMap < String , I > {
2020-05-09 18:22:36 +00:00
fn walk ( & self , segment : & str ) -> Result < & dyn ContextElement , WalkError > {
let child = self . get ( segment ) . ok_or ( WalkError ::CantWalk ) ? ;
2020-05-06 00:22:25 +00:00
Ok ( child )
2020-04-12 01:07:12 +00:00
}
2020-05-06 00:22:25 +00:00
}
2020-04-12 01:07:12 +00:00
2020-05-11 03:26:15 +00:00
impl < I : 'static + ContextElement + Clone > Loopable for HashMap < String , I > {
2020-05-09 18:22:36 +00:00
fn get_loop_elements ( & self ) -> Vec < & dyn ContextElement > {
2020-05-24 20:27:13 +00:00
Vec ::new ( )
2020-04-28 23:34:52 +00:00
}
2020-05-06 00:22:25 +00:00
}
2020-05-11 03:26:15 +00:00
impl < I : 'static + ContextElement + Clone > CompareContextElement for HashMap < String , I > {
fn equals ( & self , other : & dyn ContextElement ) -> bool {
false
2020-05-06 00:22:25 +00:00
}
2020-05-17 01:30:51 +00:00
fn partial_compare ( & self , other : & dyn ContextElement ) -> Option < Ordering > {
// TODO: Implement
None
}
2020-05-06 00:22:25 +00:00
}
#[ test ]
2020-05-06 00:43:53 +00:00
fn test_walk_path ( ) {
2020-05-11 03:26:15 +00:00
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 < String , u64 > = [
( " cat " . to_string ( ) , 1 ) ,
( " dog " . to_string ( ) , 2 ) ,
( " tiger " . to_string ( ) , 3 ) ,
]
. 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 ( ) ,
) ,
2020-05-06 00:22:25 +00:00
]
. iter ( )
. cloned ( )
. collect ( ) ;
assert_eq! (
2020-05-24 19:21:30 +00:00
walk_path ( & vec! [ & context as & dyn ContextElement ] , & vec! [ " cat " ] )
2020-05-06 00:22:25 +00:00
. unwrap ( )
. render ( & Vec ::new ( ) )
. unwrap ( ) ,
" kitty " . to_owned ( )
) ;
2020-05-06 00:38:42 +00:00
assert_eq! (
2020-05-24 19:21:30 +00:00
walk_path (
2020-05-06 00:38:42 +00:00
& vec! [ & number_context as & dyn ContextElement ] ,
& vec! [ " tiger " ]
)
. unwrap ( )
. render ( & Vec ::new ( ) )
. unwrap ( ) ,
" 3 " . to_owned ( )
) ;
assert_eq! (
2020-05-24 19:21:30 +00:00
walk_path (
2020-05-06 00:38:42 +00:00
& vec! [ & deep_context as & dyn ContextElement ] ,
& vec! [ " tiger " , " food " ]
)
. unwrap ( )
. render ( & Vec ::new ( ) )
. unwrap ( ) ,
" people " . to_owned ( )
) ;
2020-05-06 00:22:25 +00:00
}
2020-04-12 00:31:44 +00:00
}