Skip to content

Commit

Permalink
Merge pull request #505 from sunng87/fix/partial-indent-multiline
Browse files Browse the repository at this point in the history
Partial indent support
  • Loading branch information
sunng87 authored May 16, 2022
2 parents a98eb53 + 8709f11 commit 2260e5f
Show file tree
Hide file tree
Showing 5 changed files with 217 additions and 67 deletions.
32 changes: 0 additions & 32 deletions src/grammar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,38 +4,6 @@
#[grammar = "grammar.pest"]
pub struct HandlebarsParser;

#[inline]
pub(crate) fn whitespace_matcher(c: char) -> bool {
c == ' ' || c == '\t'
}

#[inline]
pub(crate) fn newline_matcher(c: char) -> bool {
c == '\n' || c == '\r'
}

#[inline]
pub(crate) fn strip_first_newline(s: &str) -> &str {
if let Some(s) = s.strip_prefix("\r\n") {
s
} else if let Some(s) = s.strip_prefix('\n') {
s
} else {
s
}
}

pub(crate) fn ends_with_empty_line(text: &str) -> bool {
let s = text.trim_end_matches(whitespace_matcher);
// also matches when text is just whitespaces
s.ends_with(newline_matcher) || s.is_empty()
}

pub(crate) fn starts_with_empty_line(text: &str) -> bool {
text.trim_start_matches(whitespace_matcher)
.starts_with(newline_matcher)
}

#[cfg(test)]
mod test {
use super::{HandlebarsParser, Rule};
Expand Down
86 changes: 81 additions & 5 deletions src/partial.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,9 @@ pub fn expand_partial<'reg: 'rc, 'rc>(
local_rc.push_partial_block(pb);
}

// indent
local_rc.set_indent_string(d.indent());

let result = t.render(r, ctx, &mut local_rc, out);

// cleanup
Expand Down Expand Up @@ -454,13 +457,13 @@ foofoofoo"#,

assert_eq!(
result,
r#"name: inner_solo
r#" name: inner_solo
name: hello
name: there
name: hello
name: there
name: hello
name: there
name: hello
name: there
"#
);
}
Expand Down Expand Up @@ -542,4 +545,77 @@ Template:test
hb.render("t1", &data).unwrap()
);
}

#[test]
fn test_multiline_partial_indent() {
let mut hb = Registry::new();

hb.register_template_string(
"t1",
r#"{{#*inline "thepartial"}}
inner first line
inner second line
{{/inline}}
{{> thepartial}}
outer third line"#,
)
.unwrap();
assert_eq!(
r#" inner first line
inner second line
outer third line"#,
hb.render("t1", &()).unwrap()
);

hb.register_template_string(
"t2",
r#"{{#*inline "thepartial"}}inner first line
inner second line
{{/inline}}
{{> thepartial}}
outer third line"#,
)
.unwrap();
assert_eq!(
r#" inner first line
inner second line
outer third line"#,
hb.render("t2", &()).unwrap()
);

hb.register_template_string(
"t3",
r#"{{#*inline "thepartial"}}{{a}}{{/inline}}
{{> thepartial}}
outer third line"#,
)
.unwrap();
assert_eq!(
r#"
inner first line
inner second lineouter third line"#,
hb.render("t3", &json!({"a": "inner first line\ninner second line"}))
.unwrap()
);

let mut hb2 = Registry::new();
hb2.set_prevent_indent(true);

hb2.register_template_string(
"t1",
r#"{{#*inline "thepartial"}}
inner first line
inner second line
{{/inline}}
{{> thepartial}}
outer third line"#,
)
.unwrap();
assert_eq!(
r#" inner first line
inner second line
outer third line"#,
hb2.render("t1", &()).unwrap()
)
}
}
45 changes: 36 additions & 9 deletions src/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use crate::json::value::{JsonRender, PathAndJson, ScopedJson};
use crate::output::{Output, StringOutput};
use crate::partial;
use crate::registry::Registry;
use crate::support;
use crate::template::TemplateElement::*;
use crate::template::{
BlockParam, DecoratorTemplate, HelperTemplate, Parameter, Template, TemplateElement,
Expand Down Expand Up @@ -47,10 +48,11 @@ pub struct RenderContextInner<'reg: 'rc, 'rc> {
/// root template name
root_template: Option<&'reg String>,
disable_escape: bool,
indent_string: Option<&'reg String>,
}

impl<'reg: 'rc, 'rc> RenderContext<'reg, 'rc> {
/// Create a render context from a `Write`
/// Create a render context
pub fn new(root_template: Option<&'reg String>) -> RenderContext<'reg, 'rc> {
let inner = Rc::new(RenderContextInner {
partials: BTreeMap::new(),
Expand All @@ -60,6 +62,7 @@ impl<'reg: 'rc, 'rc> RenderContext<'reg, 'rc> {
current_template: None,
root_template,
disable_escape: false,
indent_string: None,
});

let mut blocks = VecDeque::with_capacity(5);
Expand All @@ -73,7 +76,6 @@ impl<'reg: 'rc, 'rc> RenderContext<'reg, 'rc> {
}
}

// TODO: better name
pub(crate) fn new_for_block(&self) -> RenderContext<'reg, 'rc> {
let inner = self.inner.clone();

Expand Down Expand Up @@ -200,6 +202,15 @@ impl<'reg: 'rc, 'rc> RenderContext<'reg, 'rc> {
}
}

pub(crate) fn set_indent_string(&mut self, indent: Option<&'reg String>) {
self.inner_mut().indent_string = indent;
}

#[inline]
pub(crate) fn get_indent_string(&self) -> Option<&'reg String> {
self.inner.indent_string
}

/// Remove a registered partial
pub fn remove_partial(&mut self, name: &str) {
self.inner_mut().partials.remove(name);
Expand Down Expand Up @@ -443,6 +454,7 @@ pub struct Decorator<'reg, 'rc> {
params: Vec<PathAndJson<'reg, 'rc>>,
hash: BTreeMap<&'reg str, PathAndJson<'reg, 'rc>>,
template: Option<&'reg Template>,
indent: Option<&'reg String>,
}

impl<'reg: 'rc, 'rc> Decorator<'reg, 'rc> {
Expand Down Expand Up @@ -471,6 +483,7 @@ impl<'reg: 'rc, 'rc> Decorator<'reg, 'rc> {
params: pv,
hash: hm,
template: dt.template.as_ref(),
indent: dt.indent.as_ref(),
})
}

Expand Down Expand Up @@ -503,6 +516,10 @@ impl<'reg: 'rc, 'rc> Decorator<'reg, 'rc> {
pub fn template(&self) -> Option<&'reg Template> {
self.template
}

pub fn indent(&self) -> Option<&'reg String> {
self.indent
}
}

/// Render trait
Expand Down Expand Up @@ -755,6 +772,20 @@ pub(crate) fn do_escape(r: &Registry<'_>, rc: &RenderContext<'_, '_>, content: S
}
}

#[inline]
fn indent_aware_write(
v: &str,
rc: &mut RenderContext<'_, '_>,
out: &mut dyn Output,
) -> Result<(), RenderError> {
if let Some(indent) = rc.get_indent_string() {
out.write(support::str::with_indent(v.as_ref(), indent).as_ref())?;
} else {
out.write(v.as_ref())?;
}
Ok(())
}

impl Renderable for TemplateElement {
fn render<'reg: 'rc, 'rc>(
&'reg self,
Expand All @@ -763,11 +794,8 @@ impl Renderable for TemplateElement {
rc: &mut RenderContext<'reg, 'rc>,
out: &mut dyn Output,
) -> Result<(), RenderError> {
match *self {
RawString(ref v) => {
out.write(v.as_ref())?;
Ok(())
}
match self {
RawString(ref v) => indent_aware_write(v.as_ref(), rc, out),
Expression(ref ht) | HtmlExpression(ref ht) => {
let is_html_expression = matches!(self, HtmlExpression(_));
if is_html_expression {
Expand Down Expand Up @@ -797,8 +825,7 @@ impl Renderable for TemplateElement {
} else {
let rendered = context_json.value().render();
let output = do_escape(registry, rc, rendered);
out.write(output.as_ref())?;
Ok(())
indent_aware_write(output.as_ref(), rc, out)
}
}
} else {
Expand Down
58 changes: 58 additions & 0 deletions src/support.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,64 @@ pub mod str {
output
}

/// add indent for lines but last
pub fn with_indent<'a>(s: &'a str, indent: &String) -> String {
let mut output = String::new();

let mut it = s.chars().peekable();
while let Some(c) = it.next() {
output.push(c);
// check if c is not the last character, we don't append
// indent for last line break
if c == '\n' && it.peek().is_some() {
output.push_str(indent);
}
}

output
}

#[inline]
pub(crate) fn whitespace_matcher(c: char) -> bool {
c == ' ' || c == '\t'
}

#[inline]
pub(crate) fn newline_matcher(c: char) -> bool {
c == '\n' || c == '\r'
}

#[inline]
pub(crate) fn strip_first_newline(s: &str) -> &str {
if let Some(s) = s.strip_prefix("\r\n") {
s
} else if let Some(s) = s.strip_prefix('\n') {
s
} else {
s
}
}

pub(crate) fn find_trailing_whitespace_chars(s: &str) -> Option<&str> {
let trimmed = s.trim_end_matches(whitespace_matcher);
if trimmed.len() == s.len() {
None
} else {
Some(&s[trimmed.len()..])
}
}

pub(crate) fn ends_with_empty_line(text: &str) -> bool {
let s = text.trim_end_matches(whitespace_matcher);
// also matches when text is just whitespaces
s.ends_with(newline_matcher) || s.is_empty()
}

pub(crate) fn starts_with_empty_line(text: &str) -> bool {
text.trim_start_matches(whitespace_matcher)
.starts_with(newline_matcher)
}

#[cfg(test)]
mod test {
use crate::support::str::StringWriter;
Expand Down
Loading

0 comments on commit 2260e5f

Please sign in to comment.