From 205093c7331be43f0d2fe6be9e3704fc5969c8f2 Mon Sep 17 00:00:00 2001 From: Yannik_Sc Date: Thu, 17 Apr 2025 00:58:31 +0200 Subject: [PATCH] Implement block-scoped inline partials --- src/block.rs | 11 +++++++++++ src/decorators/inline.rs | 7 +++++++ src/partial.rs | 22 ++++++++++++++-------- src/render.rs | 7 +++++++ tests/scoped_inline.rs | 25 +++++++++++++++++++++++++ 5 files changed, 64 insertions(+), 8 deletions(-) create mode 100644 tests/scoped_inline.rs diff --git a/src/block.rs b/src/block.rs index 07701bc18..9bc12a665 100644 --- a/src/block.rs +++ b/src/block.rs @@ -4,6 +4,7 @@ use serde_json::value::Value as Json; use crate::error::RenderError; use crate::local_vars::LocalVars; +use crate::Template; #[derive(Clone, Debug)] pub enum BlockParamHolder { @@ -64,6 +65,8 @@ pub struct BlockContext<'rc> { base_value: Option, /// current block context variables block_params: BlockParams<'rc>, + /// the partials available in this block + block_partials: BTreeMap, /// local variables in current context local_variables: LocalVars, } @@ -110,6 +113,14 @@ impl<'rc> BlockContext<'rc> { self.base_value = Some(value); } + pub fn get_local_partial(&self, name: &str) -> Option<&'rc Template> { + self.block_partials.get(name).map(|v| &**v) + } + + pub fn set_local_partial(&mut self, name: String, template: &'rc Template) { + self.block_partials.insert(name, template); + } + /// Get a block parameter from this block. /// Block parameters needed to be supported by the block helper. /// The typical syntax for block parameter is: diff --git a/src/decorators/inline.rs b/src/decorators/inline.rs index 55d8cb4ba..8ca3ae3b0 100644 --- a/src/decorators/inline.rs +++ b/src/decorators/inline.rs @@ -33,7 +33,14 @@ impl DecoratorDef for InlineDecorator { .template() .ok_or(RenderErrorReason::BlockContentRequired)?; + if let Some(block) = rc.block_mut() { + block.set_local_partial(name, template); + + return Ok(()); + } + rc.set_partial(name, template); + Ok(()) } } diff --git a/src/partial.rs b/src/partial.rs index 95fa5cbcc..9e55531cf 100644 --- a/src/partial.rs +++ b/src/partial.rs @@ -1,4 +1,4 @@ -use std::collections::{HashMap, VecDeque}; +use std::collections::HashMap; use serde_json::Value as Json; @@ -45,11 +45,6 @@ pub fn expand_partial<'reg: 'rc, 'rc>( rc: &mut RenderContext<'reg, 'rc>, out: &mut dyn Output, ) -> Result<(), RenderError> { - // try eval inline partials first - if let Some(t) = d.template() { - t.eval(r, ctx, rc)?; - } - let tname = d.name(); let current_template_before = rc.get_current_template_name(); @@ -85,6 +80,12 @@ pub fn expand_partial<'reg: 'rc, 'rc>( .collect::>(); let mut partial_include_block = BlockContext::new(); + + for (name, value) in &hash_ctx { + partial_include_block + .set_block_param(name, crate::BlockParamHolder::Value((*value).clone())); + } + // evaluate context for partial let merged_context = if let Some(p) = d.param(0) { if let Some(relative_path) = p.relative_path() { @@ -98,12 +99,17 @@ pub fn expand_partial<'reg: 'rc, 'rc>( // use current path merge_json(rc.evaluate2(ctx, &Path::current())?.as_json(), &hash_ctx) }; + partial_include_block.set_base_value(merged_context); // replace and hold blocks from current render context - let current_blocks = rc.replace_blocks(VecDeque::with_capacity(1)); rc.push_block(partial_include_block); + // try eval inline partials + if let Some(t) = d.template() { + t.eval(r, ctx, rc)?; + } + // @partial-block if let Some(pb) = d.template() { rc.push_partial_block(pb); @@ -121,7 +127,7 @@ pub fn expand_partial<'reg: 'rc, 'rc>( rc.pop_partial_block(); } - let _ = rc.replace_blocks(current_blocks); + rc.pop_block(); rc.set_trailing_newline(trailing_newline); rc.set_current_template_name(current_template_before); rc.set_indent_string(indent_before); diff --git a/src/render.rs b/src/render.rs index bf0c293fa..24bb323c9 100644 --- a/src/render.rs +++ b/src/render.rs @@ -173,6 +173,13 @@ impl<'reg: 'rc, 'rc> RenderContext<'reg, 'rc> { .get(self.partial_block_depth as usize) .copied(); } + + for block in &self.blocks { + if let Some(partial) = block.get_local_partial(name) { + return Some(partial); + } + } + self.partials.get(name).copied() } diff --git a/tests/scoped_inline.rs b/tests/scoped_inline.rs new file mode 100644 index 000000000..4813b75c9 --- /dev/null +++ b/tests/scoped_inline.rs @@ -0,0 +1,25 @@ +use handlebars::Handlebars; + +#[test] +fn test_inline_scope() { + let mut hbs = Handlebars::new(); + hbs.register_partial( + "test_partial", + r#"{{#>nested_partial}}Inner Content{{/nested_partial}}"#, + ) + .unwrap(); + let output = hbs + .render_template( + r#"{{>test_partial}} + +{{#>test_partial}} +{{#*inline "nested_partial"}}Overwrite{{/inline}} +{{/test_partial}} + +{{>test_partial}}"#, + &(), + ) + .unwrap(); + + assert_eq!("Inner Content\nOverwrite\nInner Content", output); +}