From 4e2232f2de0a0076c7338bfbb1ba377b1315451a Mon Sep 17 00:00:00 2001 From: Michael Pfaff Date: Mon, 11 Mar 2024 17:33:11 -0400 Subject: [PATCH] Unify Render* and Template* traits --- README.md | 29 +++--- docs/en/docs/getting-started.md | 6 +- docs/en/docs/index.md | 16 ++-- docs/en/docs/options.md | 26 +++--- docs/en/docs/syntax/overview.md | 21 ++--- docs/en/docs/syntax/tags.md | 41 ++------- examples/actix.rs | 4 +- examples/include.rs | 4 +- examples/simple.rs | 4 +- sailfish-compiler/src/parser.rs | 13 +-- sailfish-compiler/src/procmacro.rs | 29 ++---- sailfish-compiler/src/translator.rs | 13 +-- sailfish-macros/src/lib.rs | 4 +- .../tests/fails/invalid_option_value.rs | 19 ++-- .../tests/fails/invalid_option_value.stderr | 8 +- .../tests/fails/missing_semicolon.rs | 6 +- .../tests/fails/missing_semicolon.stderr | 12 +-- .../integration-tests/tests/fails/no_path.rs | 8 +- .../tests/fails/no_path.stderr | 12 +-- .../tests/fails/repeated_arguments.rs | 19 ++-- .../tests/fails/repeated_arguments.stderr | 8 +- .../tests/fails/template_not_found.rs | 10 +- .../tests/fails/template_not_found.stderr | 2 +- .../tests/fails/unbalanced_brace.rs | 11 ++- .../tests/fails/unbalanced_brace.stderr | 4 +- .../tests/fails/unclosed_delimter.rs | 6 +- .../tests/fails/unclosed_delimter.stderr | 12 +-- .../tests/fails/unexpected_token.rs | 8 +- .../tests/fails/unexpected_token.stderr | 10 +- .../tests/fails/unknown_option.rs | 17 +++- .../tests/fails/unknown_option.stderr | 8 +- .../integration-tests/tests/template_once.rs | 40 ++++---- sailfish/src/lib.rs | 92 +------------------ sailfish/src/runtime/render.rs | 55 ++++++++++- 34 files changed, 262 insertions(+), 315 deletions(-) diff --git a/README.md b/README.md index 8fb3885..c2d897b 100755 --- a/README.md +++ b/README.md @@ -16,14 +16,14 @@ Simple, small, and extremely fast template engine for Rust ## ✨ Features -- Simple and intuitive syntax inspired by [EJS](https://ejs.co/) -- Include another template file inside template -- Built-in filters -- Minimal dependencies (<15 crates in total) -- Extremely fast (See [benchmarks](https://github.com/djc/template-benchmarks-rs)) -- Better error message -- Syntax highlighting support ([vscode](./syntax/vscode), [vim](./syntax/vim)) -- Works on Rust 1.60 or later +- Simple and intuitive syntax inspired by [EJS](https://ejs.co/) +- Include another template file inside template +- Built-in filters +- Minimal dependencies (<15 crates in total) +- Extremely fast (See [benchmarks](https://github.com/djc/template-benchmarks-rs)) +- Better error message +- Syntax highlighting support ([vscode](./syntax/vscode), [vim](./syntax/vim)) +- Works on Rust 1.60 or later ## 🐟 Example @@ -49,9 +49,9 @@ Template file (templates/hello.stpl): Code: ```rust -use sailfish::TemplateOnce; +use sailfish::RenderOnce; -#[derive(TemplateOnce)] +#[derive(RenderOnce)] #[template(path = "hello.stpl")] struct HelloTemplate { messages: Vec @@ -69,14 +69,14 @@ You can find more examples in [examples](./examples) directory. ## 🐾 Roadmap -- `Template` trait ([RFC](https://github.com/rust-sailfish/sailfish/issues/3)) -- Template inheritance (block, partials, etc.) +- `Render` derive macro ([RFC](https://github.com/rust-sailfish/sailfish/issues/3)) +- Template inheritance (block, partials, etc.) ## 👤 Author 🇯🇵 **Ryohei Machida** -* GitHub: [@Kogia-sima](https://github.com/Kogia-sima) +- GitHub: [@Kogia-sima](https://github.com/Kogia-sima) ## 🤝 Contributing @@ -96,5 +96,6 @@ Copyright © 2020 [Ryohei Machida](https://github.com/Kogia-sima). This project is [MIT](https://github.com/rust-sailfish/sailfish/blob/master/LICENSE) licensed. -*** +--- + _This README was generated with ❤️ by [readme-md-generator](https://github.com/kefranabg/readme-md-generator)_ diff --git a/docs/en/docs/getting-started.md b/docs/en/docs/getting-started.md index d6bc09e..c31b567 100644 --- a/docs/en/docs/getting-started.md +++ b/docs/en/docs/getting-started.md @@ -4,7 +4,7 @@ Create a new directory named `templates` in the same directory as `Cargo.toml`. Copy the following contents and paste it to a new file named `templates/hello.stpl`. -``` rhtml +```rhtml <% for msg in &messages { %> @@ -29,13 +29,13 @@ templates/
  1. Import the sailfish crates:
```rust -use sailfish::TemplateOnce; +use sailfish::RenderOnce; ```
  1. Define the template struct to be rendered:
```rust -#[derive(TemplateOnce)] // automatically implement `TemplateOnce` trait +#[derive(RenderOnce)] // automatically implement `TemplateOnce` trait #[template(path = "hello.stpl")] // specify the path to template struct HelloTemplate { // data to be passed to the template diff --git a/docs/en/docs/index.md b/docs/en/docs/index.md index ff9e752..7560460 100644 --- a/docs/en/docs/index.md +++ b/docs/en/docs/index.md @@ -8,19 +8,19 @@ This documentation mainly focuses on concepts of the library, general usage, and There are many libraries for template rendering in Rust. Among those libraries, sailfish aims at **rapid development** and **rapid rendering**. Sailfish has many features that other libraries might not support. -- Write a Rust code directly inside templates, supporting many Rust syntax (struct definition, closure, macro invocation, etc.) -- [Built-in filters](https://docs.rs/sailfish/latest/sailfish/runtime/filter/index.html) -- Minimal dependencies (<15 crates in total) -- Extremely fast (See [benchmarks](https://github.com/djc/template-benchmarks-rs)) -- Template rendering is always type-safe because templates are statically compiled. -- Syntax highlighting ([vscode](http://github.com/rust-sailfish/sailfish/blob/master/syntax/vscode), [vim](http://github.com/rust-sailfish/sailfish/blob/master/syntax/vim)) +- Write a Rust code directly inside templates, supporting many Rust syntax (struct definition, closure, macro invocation, etc.) +- [Built-in filters](https://docs.rs/sailfish/latest/sailfish/runtime/filter/index.html) +- Minimal dependencies (<15 crates in total) +- Extremely fast (See [benchmarks](https://github.com/djc/template-benchmarks-rs)) +- Template rendering is always type-safe because templates are statically compiled. +- Syntax highlighting ([vscode](http://github.com/rust-sailfish/sailfish/blob/master/syntax/vscode), [vim](http://github.com/rust-sailfish/sailfish/blob/master/syntax/vim)) ## Upcoming features Since sailfish is on early stage of development, there are many upcoming features that is not supported yet. You can find many [RFC](https://github.com/rust-sailfish/sailfish/issues?q=is%3Aissue+is%3Aopen+label%3A%22Status%3A+RFC%22)s in my repository. These RFC include: -- `Template` trait (which does not consume itself) -- Template inheritance (block, partials, etc.) +- `Render` derive macro (which does not consume itself) +- Template inheritance (block, partials, etc.) If you have any idea about them or want to implement that feature, please send a comment on the issue! diff --git a/docs/en/docs/options.md b/docs/en/docs/options.md index 02b0e9a..f1e0211 100644 --- a/docs/en/docs/options.md +++ b/docs/en/docs/options.md @@ -4,8 +4,8 @@ You can control the rendering behaviour via `template` attribute. -``` rust -#[derive(TemplateOnce)] +```rust +#[derive(RenderOnce)] #[template(path = "template.stpl", escape = false)] struct TemplateStruct { ... @@ -14,15 +14,15 @@ struct TemplateStruct { `template` attribute accepts the following options. -- `path`: path to template file. This options is always required. -- `escape`: Enable HTML escaping (default: `true`) -- `delimiter`: Replace the '%' character used for the tag delimiter (default: '%') -- `rm_whitespace`: try to strip whitespaces as much as possible without collapsing HTML structure (default: `false`). This option might not work correctly if your templates have inline `script` tag. +- `path`: path to template file. This options is always required. +- `escape`: Enable HTML escaping (default: `true`) +- `delimiter`: Replace the '%' character used for the tag delimiter (default: '%') +- `rm_whitespace`: try to strip whitespaces as much as possible without collapsing HTML structure (default: `false`). This option might not work correctly if your templates have inline `script` tag. You can split the options into multiple `template` attributes. -``` rust -#[derive(TemplateOnce)] +```rust +#[derive(RenderOnce)] #[template(path = "template.stpl")] #[template(delimiter = '?')] #[template(rm_whitespace = true)] @@ -36,10 +36,10 @@ struct TemplateStruct { Sailfish allows global and local configuration in a file named `sailfish.toml`. Sailfish looks for this file in same directory as `Cargo.toml` and all parent directories. If, for example, `Cargo.toml` exists in `/foo/bar/baz` directory, then the following configuration files would be scanned in this order. -- `/foo/bar/baz/sailfish.toml` -- `/foo/bar/sailfish.toml` -- `/foo/sailfish.toml` -- `/sailfish.toml` +- `/foo/bar/baz/sailfish.toml` +- `/foo/bar/sailfish.toml` +- `/foo/sailfish.toml` +- `/sailfish.toml` If a key is specified in multiple configuration files, the value in the deeper directory takes precedence over ancestor directories. @@ -49,7 +49,7 @@ If a key is specified in both configuration file and derive options, then the va Configuration files are written in the TOML 0.5 format. Here is the default configuration: -``` toml +```toml template_dirs = ["templates"] escape = true delimiter = "%" diff --git a/docs/en/docs/syntax/overview.md b/docs/en/docs/syntax/overview.md index 95b9c69..eb30ea7 100644 --- a/docs/en/docs/syntax/overview.md +++ b/docs/en/docs/syntax/overview.md @@ -2,16 +2,15 @@ ## Tags -- `<% %>`: 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 '<%' +- `<% %>`: 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 +- `<%# %>`: Comment tag +- `<%%`: Outputs a literal '<%' ## Condition -``` rhtml +```rhtml <% if messages.is_empty() { %>
No messages
<% } %> @@ -19,7 +18,7 @@ ## loop -``` rhtml +```rhtml <% for (i, msg) in messages.iter().enumerate() { %>
<%= i %>: <%= msg %>
<% } %> @@ -27,17 +26,17 @@ ## Includes -``` rhtml +```rhtml <% include!("path/to/template"); %> ``` ## Filters -``` rhtml +```rhtml <%= message | upper %> ``` -``` rhtml +```rhtml { "id": <%= id %> "comment": <%- comment | json %> diff --git a/docs/en/docs/syntax/tags.md b/docs/en/docs/syntax/tags.md index 830c84d..ed835b2 100644 --- a/docs/en/docs/syntax/tags.md +++ b/docs/en/docs/syntax/tags.md @@ -26,7 +26,7 @@ You can write Rust statement inside `<% %>` tag. ``` !!! Note - Make sure that you cannot omit braces, parenthesis, and semicolons. +Make sure that you cannot omit braces, parenthesis, and semicolons. Sailfish is smart enough to figure out where the code block ends, so you can even include `%>` inside Rust comments or string literals. @@ -57,11 +57,11 @@ If you need to simply render `<%` character, you can escape it, or use evaluatio Although almost all Rust statement is supported, the following statements inside templates may cause a strange compilation error. -- Function/Macro definition that render some contents -- `impl` item -- Macro call which defines some local variable. -- Macro call which behaviour depends on the path to source file -- Generator expression (yield) +- Function/Macro definition that render some contents +- `impl` item +- Macro call which defines some local variable. +- Macro call which behaviour depends on the path to source file +- Generator expression (yield) ## Evaluation block @@ -100,35 +100,8 @@ If you want to render the results without escaping, you can use `<%- %>` tag or ``` !!! Note - Evaluation block does not return any value, so you cannot use the block to pass the render result to another code block. The following code is invalid. +Evaluation block does not return any value, so you cannot use the block to pass the render result to another code block. The following code is invalid. ``` 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 - ``` diff --git a/examples/actix.rs b/examples/actix.rs index b5153fb..7cc0578 100644 --- a/examples/actix.rs +++ b/examples/actix.rs @@ -1,9 +1,9 @@ use actix_web::error::InternalError; use actix_web::http::StatusCode; use actix_web::{web, App, HttpRequest, HttpResponse, HttpServer}; -use sailfish::TemplateOnce; +use sailfish::RenderOnce; -#[derive(TemplateOnce)] +#[derive(RenderOnce)] #[template(path = "actix.stpl")] struct Greet<'a> { name: &'a str, diff --git a/examples/include.rs b/examples/include.rs index 34db74e..e3a33f4 100644 --- a/examples/include.rs +++ b/examples/include.rs @@ -1,6 +1,6 @@ -use sailfish::TemplateOnce; +use sailfish::RenderOnce; -#[derive(TemplateOnce)] +#[derive(RenderOnce)] #[template(path = "include.stpl")] struct Include { title: String, diff --git a/examples/simple.rs b/examples/simple.rs index ed6b10b..342bf13 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -1,6 +1,6 @@ -use sailfish::TemplateOnce; +use sailfish::RenderOnce; -#[derive(TemplateOnce)] +#[derive(RenderOnce)] #[template(path = "simple.stpl")] struct Simple { messages: Vec, diff --git a/sailfish-compiler/src/parser.rs b/sailfish-compiler/src/parser.rs index 0bf84ec..ac95609 100644 --- a/sailfish-compiler/src/parser.rs +++ b/sailfish-compiler/src/parser.rs @@ -56,7 +56,6 @@ impl Default for Parser { #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum TokenKind { - NestedTemplateOnce, BufferedCode { escape: bool }, Code, Comment, @@ -162,10 +161,6 @@ impl<'a> ParseStream<'a> { token_kind = TokenKind::BufferedCode { escape: false }; start += 1; } - Some(b'+') => { - token_kind = TokenKind::NestedTemplateOnce; - start += 1; - } _ => {} } @@ -409,7 +404,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!( @@ -421,13 +416,13 @@ mod tests { kind: TokenKind::Text, }, Token { - content: "inner|upper", + content: "inner | upper", offset: 10, - kind: TokenKind::NestedTemplateOnce, + kind: TokenKind::BufferedCode { escape: false }, }, Token { content: " outer", - offset: 24, + offset: 26, kind: TokenKind::Text, }, ] diff --git a/sailfish-compiler/src/procmacro.rs b/sailfish-compiler/src/procmacro.rs index fc864b3..1ebd126 100644 --- a/sailfish-compiler/src/procmacro.rs +++ b/sailfish-compiler/src/procmacro.rs @@ -342,30 +342,23 @@ fn derive_template_impl(tokens: TokenStream) -> Result _ => { return Err(syn::Error::new( Span::call_site(), - "You cannot derive `Template` or `TemplateOnce` for tuple struct", + "You cannot derive `Render` or `RenderOnce` for tuple struct", )); } }; let (impl_generics, ty_generics, where_clause) = strct.generics.split_for_impl(); - // render_once method always results in the same code. - // This method can be implemented in `sailfish` crate, but I found that performance - // drops when the implementation is written in `sailfish` crate. + let inline = if cfg!(feature = "perf-inline") { + Some(quote!(#[inline])) + } else { + None + }; + let tokens = quote! { - impl #impl_generics sailfish::TemplateOnce for #name #ty_generics #where_clause { - fn render_once(self) -> sailfish::RenderResult { - use sailfish::runtime::{Buffer, SizeHint}; - static SIZE_HINT: SizeHint = SizeHint::new(); - - let mut buf = Buffer::with_capacity(SIZE_HINT.get()); - self.render_once_to(&mut buf)?; - SIZE_HINT.update(buf.len()); - - Ok(buf.into_string()) - } - - fn render_once_to(self, __sf_buf: &mut sailfish::runtime::Buffer) -> std::result::Result<(), sailfish::runtime::RenderError> { + impl #impl_generics sailfish::RenderOnce for #name #ty_generics #where_clause { + #inline + fn render_once(self, __sf_buf: &mut sailfish::runtime::Buffer) -> std::result::Result<(), sailfish::runtime::RenderError> { // This line is required for cargo to track child templates #include_bytes_seq; use sailfish::runtime as __sf_rt; @@ -375,8 +368,6 @@ fn derive_template_impl(tokens: TokenStream) -> Result Ok(()) } } - - impl #impl_generics sailfish::private::Sealed for #name #ty_generics #where_clause {} }; Ok(tokens) diff --git a/sailfish-compiler/src/translator.rs b/sailfish-compiler/src/translator.rs index 50aa549..6e5f796 100644 --- a/sailfish-compiler/src/translator.rs +++ b/sailfish-compiler/src/translator.rs @@ -214,11 +214,6 @@ 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(); @@ -384,7 +379,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 { @@ -400,13 +395,13 @@ mod tests { .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") ; }"# + r#"{ __sf_rt :: render_text (__sf_buf , "outer ") ; __sf_rt :: render (__sf_buf , inner) ? ; __sf_rt :: render_text (__sf_buf , " outer") ; }"# ); } #[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 { @@ -422,7 +417,7 @@ mod tests { .ast .into_token_stream() .to_string(), - r#"{ __sf_rt :: render_text (__sf_buf , "outer ") ; __sf_rt :: render (__sf_buf , sailfish :: runtime :: filter :: upper (inner)) ? ; __sf_rt :: render_text (__sf_buf , " outer") ; }"# + r#"{ __sf_rt :: render_text (__sf_buf , "outer ") ; __sf_rt :: render (__sf_buf , sailfish :: runtime :: filter :: upper ((inner))) ? ; __sf_rt :: render_text (__sf_buf , " outer") ; }"# ); } } diff --git a/sailfish-macros/src/lib.rs b/sailfish-macros/src/lib.rs index a0e7236..b2c8451 100644 --- a/sailfish-macros/src/lib.rs +++ b/sailfish-macros/src/lib.rs @@ -4,7 +4,7 @@ extern crate proc_macro; use proc_macro::TokenStream; -#[proc_macro_derive(TemplateOnce, attributes(template))] +#[proc_macro_derive(RenderOnce, attributes(template))] pub fn derive_template_once(tokens: TokenStream) -> TokenStream { let input = proc_macro2::TokenStream::from(tokens); let output = sailfish_compiler::procmacro::derive_template(input); @@ -12,7 +12,7 @@ pub fn derive_template_once(tokens: TokenStream) -> TokenStream { } /// WIP -#[proc_macro_derive(Template, attributes(template))] +#[proc_macro_derive(Render, attributes(template))] pub fn derive_template(tokens: TokenStream) -> TokenStream { let input = proc_macro2::TokenStream::from(tokens); let output = sailfish_compiler::procmacro::derive_template(input); diff --git a/sailfish-tests/integration-tests/tests/fails/invalid_option_value.rs b/sailfish-tests/integration-tests/tests/fails/invalid_option_value.rs index 634c70d..f50dcdb 100644 --- a/sailfish-tests/integration-tests/tests/fails/invalid_option_value.rs +++ b/sailfish-tests/integration-tests/tests/fails/invalid_option_value.rs @@ -1,12 +1,19 @@ -use sailfish::TemplateOnce; -use sailfish_macros::TemplateOnce; +use sailfish::RenderOnce; +use sailfish_macros::RenderOnce; -#[derive(TemplateOnce)] -#[template(path = "foo.stpl", escape=1)] +#[derive(RenderOnce)] +#[template(path = "foo.stpl", escape = 1)] struct InvalidOptionValue { - name: String + name: String, } fn main() { - println!("{}", InvalidOptionValue { name: "Hanako".to_owned() }.render_once().unwrap()); + println!( + "{}", + InvalidOptionValue { + name: "Hanako".to_owned() + } + .render_once() + .unwrap() + ); } diff --git a/sailfish-tests/integration-tests/tests/fails/invalid_option_value.stderr b/sailfish-tests/integration-tests/tests/fails/invalid_option_value.stderr index d7ec5bd..8851c98 100644 --- a/sailfish-tests/integration-tests/tests/fails/invalid_option_value.stderr +++ b/sailfish-tests/integration-tests/tests/fails/invalid_option_value.stderr @@ -15,12 +15,12 @@ error[E0599]: no method named `render_once` found for struct `InvalidOptionValue | = help: items from traits can only be used if the trait is implemented and in scope = note: the following trait defines an item `render_once`, perhaps you need to implement it: - candidate #1: `TemplateOnce` + candidate #1: `RenderOnce` -warning: unused import: `sailfish::TemplateOnce` +warning: unused import: `sailfish::RenderOnce` --> $DIR/invalid_option_value.rs:1:5 | -1 | use sailfish::TemplateOnce; - | ^^^^^^^^^^^^^^^^^^^^^^ +1 | use sailfish::RenderOnce; + | ^^^^^^^^^^^^^^^^^^^^ | = note: `#[warn(unused_imports)]` on by default diff --git a/sailfish-tests/integration-tests/tests/fails/missing_semicolon.rs b/sailfish-tests/integration-tests/tests/fails/missing_semicolon.rs index 8d68d31..510351c 100644 --- a/sailfish-tests/integration-tests/tests/fails/missing_semicolon.rs +++ b/sailfish-tests/integration-tests/tests/fails/missing_semicolon.rs @@ -1,7 +1,7 @@ -use sailfish::TemplateOnce; -use sailfish_macros::TemplateOnce; +use sailfish::RenderOnce; +use sailfish_macros::RenderOnce; -#[derive(TemplateOnce)] +#[derive(RenderOnce)] #[template(path = "missing_semicolon.stpl")] struct MissingSemicolon {} diff --git a/sailfish-tests/integration-tests/tests/fails/missing_semicolon.stderr b/sailfish-tests/integration-tests/tests/fails/missing_semicolon.stderr index 0f9bc79..ff77df3 100644 --- a/sailfish-tests/integration-tests/tests/fails/missing_semicolon.stderr +++ b/sailfish-tests/integration-tests/tests/fails/missing_semicolon.stderr @@ -10,8 +10,8 @@ position: line 1, column 17 --> $DIR/missing_semicolon.rs:4:10 | -4 | #[derive(TemplateOnce)] - | ^^^^^^^^^^^^ +4 | #[derive(RenderOnce)] + | ^^^^^^^^^^ | = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) @@ -26,12 +26,12 @@ error[E0599]: no method named `render_once` found for struct `MissingSemicolon` | = help: items from traits can only be used if the trait is implemented and in scope = note: the following trait defines an item `render_once`, perhaps you need to implement it: - candidate #1: `TemplateOnce` + candidate #1: `RenderOnce` -warning: unused import: `sailfish::TemplateOnce` +warning: unused import: `sailfish::RenderOnce` --> $DIR/missing_semicolon.rs:1:5 | -1 | use sailfish::TemplateOnce; - | ^^^^^^^^^^^^^^^^^^^^^^ +1 | use sailfish::RenderOnce; + | ^^^^^^^^^^^^^^^^^^^^ | = note: `#[warn(unused_imports)]` on by default diff --git a/sailfish-tests/integration-tests/tests/fails/no_path.rs b/sailfish-tests/integration-tests/tests/fails/no_path.rs index 23a3213..44ecc3e 100644 --- a/sailfish-tests/integration-tests/tests/fails/no_path.rs +++ b/sailfish-tests/integration-tests/tests/fails/no_path.rs @@ -1,9 +1,9 @@ -use sailfish::TemplateOnce; -use sailfish_macros::TemplateOnce; +use sailfish::RenderOnce; +use sailfish_macros::RenderOnce; -#[derive(TemplateOnce)] +#[derive(RenderOnce)] struct NoTemplate { - var: usize + var: usize, } fn main() { diff --git a/sailfish-tests/integration-tests/tests/fails/no_path.stderr b/sailfish-tests/integration-tests/tests/fails/no_path.stderr index 94dacd5..23f670f 100644 --- a/sailfish-tests/integration-tests/tests/fails/no_path.stderr +++ b/sailfish-tests/integration-tests/tests/fails/no_path.stderr @@ -1,8 +1,8 @@ error: `path` option must be specified. --> $DIR/no_path.rs:4:10 | -4 | #[derive(TemplateOnce)] - | ^^^^^^^^^^^^ +4 | #[derive(RenderOnce)] + | ^^^^^^^^^^ | = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) @@ -17,12 +17,12 @@ error[E0599]: no method named `render_once` found for struct `NoTemplate` in the | = help: items from traits can only be used if the trait is implemented and in scope = note: the following trait defines an item `render_once`, perhaps you need to implement it: - candidate #1: `TemplateOnce` + candidate #1: `RenderOnce` -warning: unused import: `sailfish::TemplateOnce` +warning: unused import: `sailfish::RenderOnce` --> $DIR/no_path.rs:1:5 | -1 | use sailfish::TemplateOnce; - | ^^^^^^^^^^^^^^^^^^^^^^ +1 | use sailfish::RenderOnce; + | ^^^^^^^^^^^^^^^^^^^^ | = note: `#[warn(unused_imports)]` on by default diff --git a/sailfish-tests/integration-tests/tests/fails/repeated_arguments.rs b/sailfish-tests/integration-tests/tests/fails/repeated_arguments.rs index 1b94cfb..6d9c8bd 100644 --- a/sailfish-tests/integration-tests/tests/fails/repeated_arguments.rs +++ b/sailfish-tests/integration-tests/tests/fails/repeated_arguments.rs @@ -1,13 +1,20 @@ -use sailfish::TemplateOnce; -use sailfish_macros::TemplateOnce; +use sailfish::RenderOnce; +use sailfish_macros::RenderOnce; -#[derive(TemplateOnce)] -#[template(path = "foo.stpl", escape=true)] +#[derive(RenderOnce)] +#[template(path = "foo.stpl", escape = true)] #[template(escape = false)] struct InvalidOptionValue { - name: String + name: String, } fn main() { - println!("{}", InvalidOptionValue { name: "Hanako".to_owned() }.render_once().unwrap()); + println!( + "{}", + InvalidOptionValue { + name: "Hanako".to_owned() + } + .render_once() + .unwrap() + ); } diff --git a/sailfish-tests/integration-tests/tests/fails/repeated_arguments.stderr b/sailfish-tests/integration-tests/tests/fails/repeated_arguments.stderr index 4af9142..8781f92 100644 --- a/sailfish-tests/integration-tests/tests/fails/repeated_arguments.stderr +++ b/sailfish-tests/integration-tests/tests/fails/repeated_arguments.stderr @@ -15,12 +15,12 @@ error[E0599]: no method named `render_once` found for struct `InvalidOptionValue | = help: items from traits can only be used if the trait is implemented and in scope = note: the following trait defines an item `render_once`, perhaps you need to implement it: - candidate #1: `TemplateOnce` + candidate #1: `RenderOnce` -warning: unused import: `sailfish::TemplateOnce` +warning: unused import: `sailfish::RenderOnce` --> $DIR/repeated_arguments.rs:1:5 | -1 | use sailfish::TemplateOnce; - | ^^^^^^^^^^^^^^^^^^^^^^ +1 | use sailfish::RenderOnce; + | ^^^^^^^^^^^^^^^^^^^^ | = note: `#[warn(unused_imports)]` on by default diff --git a/sailfish-tests/integration-tests/tests/fails/template_not_found.rs b/sailfish-tests/integration-tests/tests/fails/template_not_found.rs index 4081c62..d251641 100644 --- a/sailfish-tests/integration-tests/tests/fails/template_not_found.rs +++ b/sailfish-tests/integration-tests/tests/fails/template_not_found.rs @@ -1,14 +1,14 @@ -use sailfish::TemplateOnce; -use sailfish_macros::TemplateOnce; +use sailfish::RenderOnce; +use sailfish_macros::RenderOnce; -#[derive(TemplateOnce)] +#[derive(RenderOnce)] #[template(path = "empty.stpl")] struct ExistTemplate; -#[derive(TemplateOnce)] +#[derive(RenderOnce)] #[template(path = "not_exist.stpl")] struct NotExistTemplate { - var: usize + var: usize, } fn main() { diff --git a/sailfish-tests/integration-tests/tests/fails/template_not_found.stderr b/sailfish-tests/integration-tests/tests/fails/template_not_found.stderr index 83f9690..fe96001 100644 --- a/sailfish-tests/integration-tests/tests/fails/template_not_found.stderr +++ b/sailfish-tests/integration-tests/tests/fails/template_not_found.stderr @@ -15,4 +15,4 @@ error[E0599]: no method named `render_once` found for struct `NotExistTemplate` | = help: items from traits can only be used if the trait is implemented and in scope = note: the following trait defines an item `render_once`, perhaps you need to implement it: - candidate #1: `TemplateOnce` + candidate #1: `RenderOnce` diff --git a/sailfish-tests/integration-tests/tests/fails/unbalanced_brace.rs b/sailfish-tests/integration-tests/tests/fails/unbalanced_brace.rs index fc481d0..e6d95e4 100644 --- a/sailfish-tests/integration-tests/tests/fails/unbalanced_brace.rs +++ b/sailfish-tests/integration-tests/tests/fails/unbalanced_brace.rs @@ -1,12 +1,12 @@ -use sailfish::TemplateOnce; -use sailfish_macros::TemplateOnce; +use sailfish::RenderOnce; +use sailfish_macros::RenderOnce; struct Player<'a> { name: &'a str, score: u32, } -#[derive(TemplateOnce)] +#[derive(RenderOnce)] #[template(path = "unbalanced_brace.stpl")] struct UnbalancedBrace { players: Vec, @@ -16,7 +16,10 @@ fn main() { println!( "{}", UnclosedDelimiter { - players: vec![Player { name: "Hanako", score: 97 }] + players: vec![Player { + name: "Hanako", + score: 97 + }] } .render_once() .unwrap() diff --git a/sailfish-tests/integration-tests/tests/fails/unbalanced_brace.stderr b/sailfish-tests/integration-tests/tests/fails/unbalanced_brace.stderr index ca181b7..edc4c61 100644 --- a/sailfish-tests/integration-tests/tests/fails/unbalanced_brace.stderr +++ b/sailfish-tests/integration-tests/tests/fails/unbalanced_brace.stderr @@ -5,8 +5,8 @@ file: unbalanced_brace.stpl --> $DIR/unbalanced_brace.rs:9:10 | -9 | #[derive(TemplateOnce)] - | ^^^^^^^^^^^^ +9 | #[derive(RenderOnce)] + | ^^^^^^^^^^ | = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/sailfish-tests/integration-tests/tests/fails/unclosed_delimter.rs b/sailfish-tests/integration-tests/tests/fails/unclosed_delimter.rs index 396f9fc..a73f562 100644 --- a/sailfish-tests/integration-tests/tests/fails/unclosed_delimter.rs +++ b/sailfish-tests/integration-tests/tests/fails/unclosed_delimter.rs @@ -1,7 +1,7 @@ -use sailfish::TemplateOnce; -use sailfish_macros::TemplateOnce; +use sailfish::RenderOnce; +use sailfish_macros::RenderOnce; -#[derive(TemplateOnce)] +#[derive(RenderOnce)] #[template(path = "unclosed_delimiter.stpl")] struct UnclosedDelimiter { content: String, diff --git a/sailfish-tests/integration-tests/tests/fails/unclosed_delimter.stderr b/sailfish-tests/integration-tests/tests/fails/unclosed_delimter.stderr index 656d263..99d2091 100644 --- a/sailfish-tests/integration-tests/tests/fails/unclosed_delimter.stderr +++ b/sailfish-tests/integration-tests/tests/fails/unclosed_delimter.stderr @@ -10,8 +10,8 @@ position: line 3, column 5 --> $DIR/unclosed_delimter.rs:4:10 | -4 | #[derive(TemplateOnce)] - | ^^^^^^^^^^^^ +4 | #[derive(RenderOnce)] + | ^^^^^^^^^^ | = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) @@ -26,12 +26,12 @@ error[E0599]: no method named `render_once` found for struct `UnclosedDelimiter` | = help: items from traits can only be used if the trait is implemented and in scope = note: the following trait defines an item `render_once`, perhaps you need to implement it: - candidate #1: `TemplateOnce` + candidate #1: `RenderOnce` -warning: unused import: `sailfish::TemplateOnce` +warning: unused import: `sailfish::RenderOnce` --> $DIR/unclosed_delimter.rs:1:5 | -1 | use sailfish::TemplateOnce; - | ^^^^^^^^^^^^^^^^^^^^^^ +1 | use sailfish::RenderOnce; + | ^^^^^^^^^^^^^^^^^^^^ | = note: `#[warn(unused_imports)]` on by default diff --git a/sailfish-tests/integration-tests/tests/fails/unexpected_token.rs b/sailfish-tests/integration-tests/tests/fails/unexpected_token.rs index 41c80cc..ef98d40 100644 --- a/sailfish-tests/integration-tests/tests/fails/unexpected_token.rs +++ b/sailfish-tests/integration-tests/tests/fails/unexpected_token.rs @@ -1,5 +1,5 @@ -use sailfish::TemplateOnce; -use sailfish_macros::TemplateOnce; +use sailfish::RenderOnce; +use sailfish_macros::RenderOnce; struct Content<'a> { id: u32, @@ -7,12 +7,12 @@ struct Content<'a> { phone_number: &'a str, } -#[derive(TemplateOnce)] +#[derive(RenderOnce)] #[template(path = "unexpected_token.stpl")] #[template(escape = false)] struct UnexpectedToken<'a> { name: &'a str, - content: Content<'a> + content: Content<'a>, } fn main() { diff --git a/sailfish-tests/integration-tests/tests/fails/unexpected_token.stderr b/sailfish-tests/integration-tests/tests/fails/unexpected_token.stderr index 9370624..a4a6005 100644 --- a/sailfish-tests/integration-tests/tests/fails/unexpected_token.stderr +++ b/sailfish-tests/integration-tests/tests/fails/unexpected_token.stderr @@ -10,8 +10,8 @@ position: line 3, column 17 --> $DIR/unexpected_token.rs:10:10 | -10 | #[derive(TemplateOnce)] - | ^^^^^^^^^^^^ +10 | #[derive(RenderOnce)] + | ^^^^^^^^^^ | = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) @@ -21,10 +21,10 @@ error[E0422]: cannot find struct, variant or union type `UnclosedToken` in this 21 | UnclosedToken { | ^^^^^^^^^^^^^ not found in this scope -warning: unused import: `sailfish::TemplateOnce` +warning: unused import: `sailfish::RenderOnce` --> $DIR/unexpected_token.rs:1:5 | -1 | use sailfish::TemplateOnce; - | ^^^^^^^^^^^^^^^^^^^^^^ +1 | use sailfish::RenderOnce; + | ^^^^^^^^^^^^^^^^^^^^ | = note: `#[warn(unused_imports)]` on by default diff --git a/sailfish-tests/integration-tests/tests/fails/unknown_option.rs b/sailfish-tests/integration-tests/tests/fails/unknown_option.rs index 1010c9c..1d24cd3 100644 --- a/sailfish-tests/integration-tests/tests/fails/unknown_option.rs +++ b/sailfish-tests/integration-tests/tests/fails/unknown_option.rs @@ -1,12 +1,19 @@ -use sailfish::TemplateOnce; -use sailfish_macros::TemplateOnce; +use sailfish::RenderOnce; +use sailfish_macros::RenderOnce; -#[derive(TemplateOnce)] +#[derive(RenderOnce)] #[template(patth = "foo.stpl")] struct UnknownOption { - name: String + name: String, } fn main() { - println!("{}", UnknownOption { name: "Hanako".to_owned() }.render_once().unwrap()); + println!( + "{}", + UnknownOption { + name: "Hanako".to_owned() + } + .render_once() + .unwrap() + ); } diff --git a/sailfish-tests/integration-tests/tests/fails/unknown_option.stderr b/sailfish-tests/integration-tests/tests/fails/unknown_option.stderr index 5c0284f..a1bf7ab 100644 --- a/sailfish-tests/integration-tests/tests/fails/unknown_option.stderr +++ b/sailfish-tests/integration-tests/tests/fails/unknown_option.stderr @@ -15,12 +15,12 @@ error[E0599]: no method named `render_once` found for struct `UnknownOption` in | = help: items from traits can only be used if the trait is implemented and in scope = note: the following trait defines an item `render_once`, perhaps you need to implement it: - candidate #1: `TemplateOnce` + candidate #1: `RenderOnce` -warning: unused import: `sailfish::TemplateOnce` +warning: unused import: `sailfish::RenderOnce` --> $DIR/unknown_option.rs:1:5 | -1 | use sailfish::TemplateOnce; - | ^^^^^^^^^^^^^^^^^^^^^^ +1 | use sailfish::RenderOnce; + | ^^^^^^^^^^^^^^^^^^^^ | = note: `#[warn(unused_imports)]` on by default diff --git a/sailfish-tests/integration-tests/tests/template_once.rs b/sailfish-tests/integration-tests/tests/template_once.rs index de22439..7844fbd 100644 --- a/sailfish-tests/integration-tests/tests/template_once.rs +++ b/sailfish-tests/integration-tests/tests/template_once.rs @@ -3,7 +3,7 @@ extern crate sailfish_macros; use integration_tests::assert_string_eq; use sailfish::runtime::RenderResult; -use sailfish::TemplateOnce; +use sailfish::RenderOnce; use std::path::PathBuf; fn assert_render_result(name: &str, result: RenderResult) { @@ -23,8 +23,8 @@ fn assert_render_result(name: &str, result: RenderResult) { } #[inline] -fn assert_render(name: &str, template: T) { - assert_render_result(name, template.render_once()); +fn assert_render(name: &str, template: T) { + assert_render_result(name, template.render_once_to_string()); } trait ConflictWithSailFishRender { @@ -34,7 +34,7 @@ trait ConflictWithSailFishRender { impl ConflictWithSailFishRender for u8 {} impl ConflictWithSailFishRender for u16 {} -#[derive(TemplateOnce)] +#[derive(RenderOnce)] #[template(path = "empty.stpl")] struct Empty {} @@ -43,7 +43,7 @@ fn empty() { assert_render("empty", Empty {}); } -#[derive(TemplateOnce)] +#[derive(RenderOnce)] #[template(path = "noescape.stpl")] struct Noescape<'a> { raw: &'a str, @@ -59,7 +59,7 @@ fn noescape() { ); } -#[derive(TemplateOnce)] +#[derive(RenderOnce)] #[template(path = "json.stpl")] struct Json { name: String, @@ -77,7 +77,7 @@ fn json() { ); } -#[derive(TemplateOnce)] +#[derive(RenderOnce)] #[template(path = "custom_delimiter.stpl")] #[template(delimiter = '🍣')] struct CustomDelimiter; @@ -87,7 +87,7 @@ fn custom_delimiter() { assert_render("custom_delimiter", CustomDelimiter); } -#[derive(TemplateOnce)] +#[derive(RenderOnce)] #[template(path = "include.stpl")] struct Include<'a> { strs: &'a [&'a str], @@ -103,7 +103,7 @@ fn test_include() { ); } -#[derive(TemplateOnce)] +#[derive(RenderOnce)] #[template(path = "continue-break.stpl", rm_whitespace = true)] struct ContinueBreak; @@ -112,7 +112,7 @@ fn continue_break() { assert_render("continue-break", ContinueBreak); } -#[derive(TemplateOnce)] +#[derive(RenderOnce)] #[template(path = "techempower.stpl", rm_whitespace = true)] struct Techempower { items: Vec, @@ -182,7 +182,7 @@ fn test_techempower() { assert_render("techempower", Techempower { items }); } -#[derive(TemplateOnce)] +#[derive(RenderOnce)] #[template(path = "rm_whitespace.stpl")] #[template(rm_whitespace = true)] struct RmWhitespace<'a, 'b> { @@ -199,7 +199,7 @@ fn test_rm_whitespace() { ); } -#[derive(TemplateOnce)] +#[derive(RenderOnce)] #[template(path = "comment.stpl")] struct Comment {} @@ -208,7 +208,7 @@ fn test_comment() { assert_render("comment", Comment {}) } -#[derive(TemplateOnce)] +#[derive(RenderOnce)] #[template(path = "rust_macro.stpl", rm_whitespace = true)] struct RustMacro { value: Option, @@ -219,7 +219,7 @@ fn test_rust_macro() { assert_render("rust_macro", RustMacro { value: Some(10) }); } -#[derive(TemplateOnce)] +#[derive(RenderOnce)] #[template(path = "formatting.stpl", escape = false)] struct Formatting; @@ -228,7 +228,7 @@ fn test_formatting() { assert_render("formatting", Formatting); } -#[derive(TemplateOnce)] +#[derive(RenderOnce)] #[template(path = "filter.stpl")] struct Filter<'a> { message: &'a str, @@ -239,7 +239,7 @@ fn test_filter() { assert_render("filter", Filter { message: "hello" }); } -#[derive(TemplateOnce)] +#[derive(RenderOnce)] #[template(path = "filter2.stpl")] struct Filter2; @@ -248,7 +248,7 @@ fn test_filter2() { assert_render("filter2", Filter2); } -#[derive(TemplateOnce)] +#[derive(RenderOnce)] #[template(path = "truncate-filter.stpl")] struct TruncateFilter; @@ -256,7 +256,7 @@ struct TruncateFilter; fn test_truncate_filter() { assert_render("truncate-filter", TruncateFilter); } -#[derive(TemplateOnce)] +#[derive(RenderOnce)] #[template(path = "json-filter.stpl")] struct JsonFilter { data: serde_json::Value, @@ -280,7 +280,7 @@ fn test_json_filter() { mod unix { use super::*; - #[derive(TemplateOnce)] + #[derive(RenderOnce)] #[template(path = "include-nest.stpl")] struct IncludeNest<'a> { s: &'a str, @@ -291,7 +291,7 @@ mod unix { assert_render("include-nest", IncludeNest { s: "foo" }); } - #[derive(TemplateOnce)] + #[derive(RenderOnce)] #[template(path = "include_rust.stpl")] struct IncludeRust { value: usize, diff --git a/sailfish/src/lib.rs b/sailfish/src/lib.rs index 8289052..4726a3a 100644 --- a/sailfish/src/lib.rs +++ b/sailfish/src/lib.rs @@ -4,16 +4,16 @@ //! //! This crate contains utilities for rendering sailfish template. //! If you want to use sailfish templates, import `sailfish-macros` crate and use -//! derive macro `#[derive(TemplateOnce)]` or `#[derive(Template)]`. +//! derive macro `#[derive(RenderOnce)]` or `#[derive(Render)]`. //! //! In most cases you don't need to care about the `runtime` module in this crate, but //! if you want to render custom data inside templates, you must implement //! `runtime::Render` trait for that type. //! //! ```ignore -//! use sailfish::TemplateOnce; +//! use sailfish::RenderOnce; //! -//! #[derive(TemplateOnce)] +//! #[derive(RenderOnce)] //! #[template(path = "hello.stpl")] //! struct HelloTemplate { //! messages: Vec @@ -35,92 +35,10 @@ #![cfg_attr(docsrs, feature(doc_cfg))] #![allow(clippy::redundant_closure)] #![deny(missing_docs)] -#![feature(specialization)] pub mod runtime; -use runtime::Buffer; -pub use runtime::{RenderError, RenderResult}; +pub use runtime::{Buffer, Render, RenderError, RenderOnce, RenderResult}; #[cfg(feature = "derive")] #[cfg_attr(docsrs, doc(cfg(feature = "derive")))] -pub use sailfish_macros::TemplateOnce; - -/// Template that can be rendered with consuming itself. -pub trait TemplateOnce: Sized { - /// Render the template and return the rendering result as `RenderResult` - /// - /// This method never returns `Err`, unless you explicitly return RenderError - /// inside templates - /// - /// When you use `render_once` method, total rendered size will be cached, and at - /// the next time, buffer will be pre-allocated based on the cached length. - /// - /// If you don't want this behaviour, you can use `render_once_to` method instead. - fn render_once(self) -> runtime::RenderResult; - - /// Render the template and append the result to `buf`. - /// - /// This method never returns `Err`, unless you explicitly return RenderError - /// inside templates - /// - /// ``` - /// use sailfish::TemplateOnce; - /// use sailfish::runtime::Buffer; - /// - /// # pub struct HelloTemplate { - /// # messages: Vec, - /// # } - /// # - /// # impl TemplateOnce for HelloTemplate { - /// # fn render_once(self) -> Result { - /// # Ok(String::new()) - /// # } - /// # - /// # fn render_once_to(self, buf: &mut Buffer) - /// # -> Result<(), sailfish::RenderError> { - /// # Ok(()) - /// # } - /// # } - /// # - /// # impl sailfish::private::Sealed for HelloTemplate {} - /// # - /// let tpl = HelloTemplate { - /// messages: vec!["foo".to_string()] - /// }; - /// - /// // custom pre-allocation - /// let mut buffer = Buffer::with_capacity(100); - /// tpl.render_once_to(&mut buffer).unwrap(); - /// ``` - fn render_once_to(self, buf: &mut Buffer) -> Result<(), RenderError>; -} - -/// Work in Progress -pub trait Template -where - for<'a> &'a Self: TemplateOnce, -{ - /// Work in progress - fn render(&self) -> runtime::RenderResult; - - /// Work in progress - fn render_to(&self, buf: &mut Buffer) -> Result<(), RenderError>; -} - -impl Template for T -where - for<'a> &'a T: TemplateOnce, -{ - fn render(&self) -> runtime::RenderResult { - TemplateOnce::render_once(self) - } - - fn render_to(&self, buf: &mut Buffer) -> Result<(), RenderError> { - TemplateOnce::render_once_to(self, buf) - } -} - -#[doc(hidden)] -pub mod private { - pub trait Sealed {} -} +pub use sailfish_macros::{Render, RenderOnce}; diff --git a/sailfish/src/runtime/render.rs b/sailfish/src/runtime/render.rs index 5a590cb..8c727b0 100644 --- a/sailfish/src/runtime/render.rs +++ b/sailfish/src/runtime/render.rs @@ -9,6 +9,8 @@ use std::path::{Path, PathBuf}; use std::rc::Rc; use std::sync::{Arc, MutexGuard, RwLockReadGuard, RwLockWriteGuard}; +use crate::runtime::SizeHint; + use super::buffer::Buffer; use super::escape; @@ -57,6 +59,33 @@ pub trait Render { /// See [`Render`] for more information. pub trait RenderOnce: Sized { /// render to `Buffer` without escaping + /// + /// Render the template and append the result to `buf`. + /// + /// This method never returns `Err`, unless you explicitly return RenderError + /// inside templates + /// + /// ```should_fail + /// use sailfish::{Buffer, RenderOnce}; + /// + /// # pub struct HelloTemplate { + /// # messages: Vec, + /// # } + /// # + /// # impl RenderOnce for HelloTemplate { + /// # fn render_once(self, buf: &mut Buffer) -> Result<(), sailfish::RenderError> { + /// # unimplemented!() + /// # } + /// # } + /// # + /// let tpl = HelloTemplate { + /// messages: vec!["foo".to_string()] + /// }; + /// + /// // custom pre-allocation + /// let mut buffer = Buffer::with_capacity(100); + /// tpl.render_once(&mut buffer).unwrap(); + /// ``` fn render_once(self, b: &mut Buffer) -> Result<(), RenderError>; /// render to `Buffer` with HTML escaping @@ -67,6 +96,26 @@ pub trait RenderOnce: Sized { escape::escape_to_buf(tmp.as_str(), b); Ok(()) } + + /// Render the template and return the rendering result as `RenderResult` + /// + /// This method never returns `Err`, unless you explicitly return RenderError + /// inside templates + /// + /// When you use `render_once_to_string` method, total rendered size will be cached, + /// and at the next time, buffer will be pre-allocated based on the cached length. + /// + /// If you don't want this behaviour, you can use `render_once` method instead. + #[inline] + fn render_once_to_string(self) -> RenderResult { + static SIZE_HINT: SizeHint = SizeHint::new(); + + let mut buf = Buffer::with_capacity(SIZE_HINT.get()); + self.render_once(&mut buf)?; + SIZE_HINT.update(buf.len()); + + Ok(buf.into_string()) + } } // impl<'a, T: ?Sized> Render for &'a T @@ -86,10 +135,12 @@ impl RenderOnce for T where T: Render, { + #[inline] fn render_once(self, b: &mut Buffer) -> Result<(), RenderError> { self.render(b) } + #[inline] fn render_once_escaped(self, b: &mut Buffer) -> Result<(), RenderError> { self.render_escaped(b) } @@ -353,7 +404,7 @@ macro_rules! render_deref { render_deref!(['a, T: Render + ?Sized] [] &'a T); render_deref!(['a, T: Render + ?Sized] [] &'a mut T); -render_deref!(default[default] [T: Render + ?Sized] [] Box); +render_deref!([T: Render + ?Sized] [] Box); render_deref!([T: Render + ?Sized] [] Rc); render_deref!([T: Render + ?Sized] [] Arc); render_deref!(['a, T: Render + ToOwned + ?Sized] [] Cow<'a, T>); @@ -457,7 +508,7 @@ impl From for RenderError { } } -/// Result type returned from `TemplateOnce::render_once` method +/// Result type returned from [`RenderOnce::render_once_to_string`] method pub type RenderResult = Result; #[cfg(test)]