Unify Render* and Template* traits

This commit is contained in:
Michael Pfaff 2024-03-11 17:33:11 -04:00
parent e5a471d9ca
commit 4e2232f2de
34 changed files with 262 additions and 315 deletions

View File

@ -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<String>
@ -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)_

View File

@ -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
<html>
<body>
<% for msg in &messages { %>
@ -29,13 +29,13 @@ templates/
<ol><li>Import the sailfish crates:</li></ol>
```rust
use sailfish::TemplateOnce;
use sailfish::RenderOnce;
```
<ol start="2"><li>Define the template struct to be rendered:</li></ol>
```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

View File

@ -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!

View File

@ -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 = "%"

View File

@ -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() { %>
<div>No messages</div>
<% } %>
@ -19,7 +18,7 @@
## loop
``` rhtml
```rhtml
<% for (i, msg) in messages.iter().enumerate() { %>
<div><%= i %>: <%= msg %></div>
<% } %>
@ -27,17 +26,17 @@
## Includes
``` rhtml
```rhtml
<% include!("path/to/template"); %>
```
## Filters
``` rhtml
```rhtml
<%= message | upper %>
```
``` rhtml
```rhtml
{
"id": <%= id %>
"comment": <%- comment | json %>

View File

@ -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
<strong>A <%= val %></strong>
```
=== "Template B"
``` rhtml
B <%+ A { val: "example" } %>
```
=== "Result"
``` text
B <strong>A example</strong>
```

View File

@ -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,

View File

@ -1,6 +1,6 @@
use sailfish::TemplateOnce;
use sailfish::RenderOnce;
#[derive(TemplateOnce)]
#[derive(RenderOnce)]
#[template(path = "include.stpl")]
struct Include {
title: String,

View File

@ -1,6 +1,6 @@
use sailfish::TemplateOnce;
use sailfish::RenderOnce;
#[derive(TemplateOnce)]
#[derive(RenderOnce)]
#[template(path = "simple.stpl")]
struct Simple {
messages: Vec<String>,

View File

@ -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,
},
]

View File

@ -342,30 +342,23 @@ fn derive_template_impl(tokens: TokenStream) -> Result<TokenStream, syn::Error>
_ => {
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<TokenStream, syn::Error>
Ok(())
}
}
impl #impl_generics sailfish::private::Sealed for #name #ty_generics #where_clause {}
};
Ok(tokens)

View File

@ -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") ; }"#
);
}
}

View File

@ -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);

View File

@ -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()
);
}

View File

@ -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

View File

@ -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 {}

View File

@ -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

View File

@ -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() {

View File

@ -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

View File

@ -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()
);
}

View File

@ -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

View File

@ -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() {

View File

@ -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`

View File

@ -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<Player>,
@ -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()

View File

@ -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)

View File

@ -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,

View File

@ -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

View File

@ -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() {

View File

@ -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

View File

@ -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()
);
}

View File

@ -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

View File

@ -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<T: TemplateOnce>(name: &str, template: T) {
assert_render_result(name, template.render_once());
fn assert_render<T: RenderOnce>(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<Fortune>,
@ -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<i32>,
@ -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,

View File

@ -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<String>
@ -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<String>,
/// # }
/// #
/// # impl TemplateOnce for HelloTemplate {
/// # fn render_once(self) -> Result<String, sailfish::RenderError> {
/// # 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<T> 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};

View File

@ -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<String>,
/// # }
/// #
/// # 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<T> 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<T>);
render_deref!([T: Render + ?Sized] [] Box<T>);
render_deref!([T: Render + ?Sized] [] Rc<T>);
render_deref!([T: Render + ?Sized] [] Arc<T>);
render_deref!(['a, T: Render + ToOwned + ?Sized] [] Cow<'a, T>);
@ -457,7 +508,7 @@ impl From<fmt::Error> for RenderError {
}
}
/// Result type returned from `TemplateOnce::render_once` method
/// Result type returned from [`RenderOnce::render_once_to_string`] method
pub type RenderResult = Result<String, RenderError>;
#[cfg(test)]