From c282daf9074a39d2fca43eb71f0f30fd501bd4b7 Mon Sep 17 00:00:00 2001 From: Daniel Arbuckle Date: Thu, 19 Jan 2023 03:07:43 -0800 Subject: [PATCH 1/3] Shorthand syntax for rendering a TemplateOnce within another template --- sailfish-compiler/src/parser.rs | 32 +++++++++++++++ sailfish-compiler/src/translator.rs | 61 ++++++++++++++++++++++++++++- 2 files changed, 92 insertions(+), 1 deletion(-) diff --git a/sailfish-compiler/src/parser.rs b/sailfish-compiler/src/parser.rs index 63a0c4b..debeec3 100644 --- a/sailfish-compiler/src/parser.rs +++ b/sailfish-compiler/src/parser.rs @@ -56,6 +56,7 @@ impl Default for Parser { #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum TokenKind { + NestedTemplateOnce, BufferedCode { escape: bool }, Code, Comment, @@ -161,6 +162,10 @@ impl<'a> ParseStream<'a> { token_kind = TokenKind::BufferedCode { escape: false }; start += 1; } + Some(b'^') => { + token_kind = TokenKind::NestedTemplateOnce; + start += 1; + } _ => {} } @@ -402,6 +407,33 @@ mod tests { use super::*; use pretty_assertions::assert_eq; + #[test] + fn nested_render_once() { + let src = r#"outer <%^ inner|upper %> outer"#; + let parser = Parser::default(); + let tokens = parser.parse(src).into_vec().unwrap(); + assert_eq!( + &tokens, + &[ + Token { + content: "outer ", + offset: 0, + kind: TokenKind::Text, + }, + Token { + content: "inner|upper", + offset: 10, + kind: TokenKind::NestedTemplateOnce, + }, + Token { + content: " outer", + offset: 24, + kind: TokenKind::Text, + }, + ] + ); + } + #[test] fn non_ascii_delimiter() { let src = r##"foo <🍣# This is a comment 🍣> bar <🍣= r"🍣>" 🍣> baz <🍣🍣"##; diff --git a/sailfish-compiler/src/translator.rs b/sailfish-compiler/src/translator.rs index 527a1da..60ae027 100644 --- a/sailfish-compiler/src/translator.rs +++ b/sailfish-compiler/src/translator.rs @@ -135,6 +135,15 @@ impl SourceBuilder { &mut self, token: &Token<'a>, escape: bool, + ) -> Result<(), Error> { + self.write_buffered_code_with_suffix(token, escape, "") + } + + fn write_buffered_code_with_suffix<'a>( + &mut self, + token: &Token<'a>, + escape: bool, + suffix: &str, ) -> Result<(), Error> { // parse and split off filter let code_block = syn::parse_str::(token.as_str()).map_err(|e| { @@ -154,7 +163,7 @@ impl SourceBuilder { self.source.push_str("!(__sf_buf, "); if let Some(filter) = code_block.filter { - let expr_str = code_block.expr.into_token_stream().to_string(); + let expr_str = format!("{}{}", code_block.expr.into_token_stream(), suffix); let (name, extra_args) = match filter { Filter::Ident(i) => (i.to_string(), None), Filter::Call(c) => ( @@ -188,6 +197,7 @@ impl SourceBuilder { self.source.push(')'); } else { self.write_token(token); + self.source.push_str(suffix); } self.source.push_str(");\n"); @@ -205,6 +215,11 @@ impl SourceBuilder { TokenKind::BufferedCode { escape } => { self.write_buffered_code(&token, escape)? } + TokenKind::NestedTemplateOnce => self.write_buffered_code_with_suffix( + &token, + false, + ".render_once()?", + )?, TokenKind::Text => { // concatenate repeated text token let offset = token.offset(); @@ -367,4 +382,48 @@ mod tests { ps.feed_tokens(token_iter.clone()).unwrap(); Translator::new().translate(token_iter).unwrap(); } + + #[test] + fn translate_nested_render_once() { + let src = r#"outer <%^ inner %> outer"#; + let lexer = Parser::new(); + let token_iter = lexer.parse(src); + let mut ps = SourceBuilder { + escape: true, + source: String::with_capacity(token_iter.original_source.len()), + source_map: SourceMap::default(), + }; + ps.feed_tokens(token_iter.clone()).unwrap(); + assert_eq!( + &Translator::new() + .translate(token_iter) + .unwrap() + .ast + .into_token_stream() + .to_string(), + r#"{ __sf_rt :: render_text ! (__sf_buf , "outer ") ; __sf_rt :: render ! (__sf_buf , inner . render_once () ?) ; __sf_rt :: render_text ! (__sf_buf , " outer") ; }"# + ); + } + + #[test] + fn translate_nested_render_once_with_filter() { + let src = r#"outer <%^ inner|upper %> outer"#; + let lexer = Parser::new(); + let token_iter = lexer.parse(src); + let mut ps = SourceBuilder { + escape: true, + source: String::with_capacity(token_iter.original_source.len()), + source_map: SourceMap::default(), + }; + ps.feed_tokens(token_iter.clone()).unwrap(); + assert_eq!( + &Translator::new() + .translate(token_iter) + .unwrap() + .ast + .into_token_stream() + .to_string(), + r#"{ __sf_rt :: render_text ! (__sf_buf , "outer ") ; __sf_rt :: render ! (__sf_buf , sailfish :: runtime :: filter :: upper (& (inner . render_once () ?))) ; __sf_rt :: render_text ! (__sf_buf , " outer") ; }"# + ); + } } From 4c009c6b49331856b7242ad4b36ca26c98086f2e Mon Sep 17 00:00:00 2001 From: Daniel Arbuckle Date: Thu, 19 Jan 2023 10:09:10 -0800 Subject: [PATCH 2/3] documentation --- docs/en/docs/syntax/overview.md | 1 + docs/en/docs/syntax/tags.md | 27 +++++++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/docs/en/docs/syntax/overview.md b/docs/en/docs/syntax/overview.md index 1bfb01f..9f350ae 100644 --- a/docs/en/docs/syntax/overview.md +++ b/docs/en/docs/syntax/overview.md @@ -5,6 +5,7 @@ - `<% %>`: Inline tag, you can write Rust code inside this tag - `<%= %>`: Evaluate the Rust expression and outputs the value into the template (HTML escaped) - `<%- %>`: Evaluate the Rust expression and outputs the unescaped value into the template +- `<%^ %>`: Evaluate the Rust expression producing a `TemplateOnce` value, and render that value into the template - `<%# %>`: Comment tag - `<%%`: Outputs a literal '<%' diff --git a/docs/en/docs/syntax/tags.md b/docs/en/docs/syntax/tags.md index c298228..15e0151 100644 --- a/docs/en/docs/syntax/tags.md +++ b/docs/en/docs/syntax/tags.md @@ -105,3 +105,30 @@ If you want to render the results without escaping, you can use `<%- %>` tag or ``` rhtml <% let result = %><%= 1 %><% ; %> ``` + +## Component block + +Rust expression inside `<%^ %>` tag is evaluated and then rendered by +calling its `render_once()` method. If the value does not have an +appropriate method, a compile-time error will be reported. + +This makes it easy to use types which are `TemplateOnce` as components +which can be embedded into other templates. + +=== "Template A" + + ``` rhtml + A <%= val %> + ``` + +=== "Template B" + + ``` rhtml + B <%^ A { val: "example" } %> + ``` + +=== "Result" + + ``` text + B A example + ``` From 96d4ad3e01962385ccb886a13c3d07c1689b7e98 Mon Sep 17 00:00:00 2001 From: Daniel Arbuckle Date: Tue, 24 Jan 2023 07:58:40 -0800 Subject: [PATCH 3/3] switched introductory symbol to + --- sailfish-compiler/src/parser.rs | 4 ++-- sailfish-compiler/src/translator.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/sailfish-compiler/src/parser.rs b/sailfish-compiler/src/parser.rs index debeec3..0bf84ec 100644 --- a/sailfish-compiler/src/parser.rs +++ b/sailfish-compiler/src/parser.rs @@ -162,7 +162,7 @@ impl<'a> ParseStream<'a> { token_kind = TokenKind::BufferedCode { escape: false }; start += 1; } - Some(b'^') => { + Some(b'+') => { token_kind = TokenKind::NestedTemplateOnce; start += 1; } @@ -409,7 +409,7 @@ mod tests { #[test] fn nested_render_once() { - let src = r#"outer <%^ inner|upper %> outer"#; + let src = r#"outer <%+ inner|upper %> outer"#; let parser = Parser::default(); let tokens = parser.parse(src).into_vec().unwrap(); assert_eq!( diff --git a/sailfish-compiler/src/translator.rs b/sailfish-compiler/src/translator.rs index 60ae027..f33d893 100644 --- a/sailfish-compiler/src/translator.rs +++ b/sailfish-compiler/src/translator.rs @@ -385,7 +385,7 @@ mod tests { #[test] fn translate_nested_render_once() { - let src = r#"outer <%^ inner %> outer"#; + let src = r#"outer <%+ inner %> outer"#; let lexer = Parser::new(); let token_iter = lexer.parse(src); let mut ps = SourceBuilder { @@ -407,7 +407,7 @@ mod tests { #[test] fn translate_nested_render_once_with_filter() { - let src = r#"outer <%^ inner|upper %> outer"#; + let src = r#"outer <%+ inner|upper %> outer"#; let lexer = Parser::new(); let token_iter = lexer.parse(src); let mut ps = SourceBuilder {