From 8537e3e7efeb1d8f25e9fb00fefa80ce555ef986 Mon Sep 17 00:00:00 2001 From: Christian Schwarz Date: Mon, 1 Jul 2024 03:45:40 +0200 Subject: [PATCH 1/2] recursively restore indent directives when faced with empty partials --- src/render.rs | 54 +++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 48 insertions(+), 6 deletions(-) diff --git a/src/render.rs b/src/render.rs index 98bc5ece5..91a2dac0e 100644 --- a/src/render.rs +++ b/src/render.rs @@ -49,10 +49,16 @@ pub struct RenderContextInner<'reg: 'rc, 'rc> { /// root template name root_template: Option<&'reg String>, disable_escape: bool, - // whether the previous text that we rendered ended on a newline - // necessary to make indenting decisions after the end of partials + + // Indicates whether the previous text that we rendered ended on a newline. + // This is necessary to make indenting decisions after the end of partials. trailing_newline: bool, - // whether the previous text that we render should indent itself + + // This should be set to true whenever any output is written. + // We need this to detect empty partials/helpers for indenting decisions. + content_produced: bool, + + // The next text that we render should indent itself. indent_before_write: bool, indent_string: Option>, } @@ -69,6 +75,7 @@ impl<'reg: 'rc, 'rc> RenderContext<'reg, 'rc> { root_template, disable_escape: false, trailing_newline: false, + content_produced: false, indent_before_write: false, indent_string: None, }); @@ -305,6 +312,16 @@ impl<'reg: 'rc, 'rc> RenderContext<'reg, 'rc> { self.inner().trailing_newline } + #[inline] + pub fn set_content_produced(&mut self, content_produced: bool) { + self.inner_mut().content_produced = content_produced; + } + + #[inline] + pub fn get_content_produced(&self) -> bool { + self.inner().content_produced + } + #[inline] pub fn set_indent_before_write(&mut self, indent_before_write: bool) { self.inner_mut().indent_before_write = indent_before_write; @@ -787,9 +804,21 @@ fn render_helper<'reg: 'rc, 'rc>( h.hash() ); let mut call_indent_aware = |helper_def: &dyn HelperDef, rc: &mut RenderContext<'reg, 'rc>| { - rc.set_indent_before_write(ht.indent_before_write && rc.get_trailine_newline()); + let indent_directive_before = rc.get_indent_before_write(); + let content_produced_before = rc.get_content_produced(); + rc.set_content_produced(false); + rc.set_indent_before_write( + indent_directive_before || (ht.indent_before_write && rc.get_trailine_newline()), + ); + helper_def.call(&h, registry, ctx, rc, out)?; - rc.set_indent_before_write(rc.get_trailine_newline()); + + if rc.get_content_produced() { + rc.set_indent_before_write(rc.get_trailine_newline()); + } else { + rc.set_content_produced(content_produced_before); + rc.set_indent_before_write(indent_directive_before); + } Ok(()) }; if let Some(ref d) = rc.get_local_helper(h.name()) { @@ -828,6 +857,7 @@ pub fn indent_aware_write( if v.is_empty() { return Ok(()); } + rc.set_content_produced(true); if !v.starts_with(newline_matcher) && rc.get_indent_before_write() { if let Some(indent) = rc.get_indent_string() { @@ -905,11 +935,23 @@ impl Renderable for TemplateElement { DecoratorExpression(_) | DecoratorBlock(_) => self.eval(registry, ctx, rc), PartialExpression(ref dt) | PartialBlock(ref dt) => { let di = Decorator::try_from_template(dt, registry, ctx, rc)?; + + let indent_directive_before = rc.get_indent_before_write(); + let content_produced_before = rc.get_content_produced(); + rc.set_indent_before_write( dt.indent_before_write && (rc.get_trailine_newline() || dt.indent.is_some()), ); + rc.set_content_produced(false); + partial::expand_partial(&di, registry, ctx, rc, out)?; - rc.set_indent_before_write(rc.get_trailine_newline()); + + if rc.get_content_produced() { + rc.set_indent_before_write(rc.get_trailine_newline()); + } else { + rc.set_content_produced(content_produced_before); + rc.set_indent_before_write(indent_directive_before); + } Ok(()) } _ => Ok(()), From 4c8b504792eb73a6b03b190e7a0c44d6faf4e27f Mon Sep 17 00:00:00 2001 From: Christian Schwarz Date: Mon, 1 Jul 2024 04:04:05 +0200 Subject: [PATCH 2/2] add testcases for nested empty partials --- tests/whitespace.rs | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/whitespace.rs b/tests/whitespace.rs index 61d5af8de..a04703697 100644 --- a/tests/whitespace.rs +++ b/tests/whitespace.rs @@ -270,3 +270,33 @@ foo output ); } + +#[test] +fn test_empty_inline_partials_and_helpers_retain_indent_directive() { + let input = r#" +{{~#*inline "empty_partial"}}{{/inline}} + +{{~#*inline "indented_partial"}} +{{>empty_partial}}{{#if true}}{{>empty_partial}}{{/if}}foo +{{/inline}} + {{>indented_partial}} +"#; + let output = " foo\n"; + let hbs = Handlebars::new(); + + assert_eq!(hbs.render_template(input, &()).unwrap(), output); +} + +#[test] +fn test_indent_directive_propagated_but_not_restored_if_content_was_written() { + let input = r#" +{{~#*inline "indented_partial"}} +{{#if true}}{{/if}}{{#if true}}foo{{/if}}foo +{{/inline}} + {{>indented_partial}} +"#; + let output = " foofoo\n"; + let hbs = Handlebars::new(); + + assert_eq!(hbs.render_template(input, &()).unwrap(), output); +}