Unify Render* and Template* traits

This commit is contained in:
Michael Pfaff 2024-03-11 17:33:11 -04:00
commit 133b98bc5d
48 changed files with 354 additions and 981 deletions

View file

@ -59,7 +59,7 @@ You can choose to use `TemplateSimple` to access fields directly:
> let ctx = HelloTemplate {
> messages: vec![String::from("foo"), String::from("bar")],
> };
> println!("{}", ctx.render_once().unwrap());
> println!("{}", ctx.render_once_to_string().unwrap());
> }
> ```

View file

@ -52,7 +52,7 @@ fn main() {
};
// Now render templates with given data
println!("{}", ctx.render_once().unwrap());
println!("{}", ctx.render_once_to_string().unwrap());
}
```

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

@ -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::TemplateSimple;
use sailfish::RenderOnce;
#[derive(TemplateSimple)]
#[derive(RenderOnce)]
#[template(path = "actix.stpl")]
struct Greet<'a> {
name: &'a str,
@ -12,7 +12,7 @@ struct Greet<'a> {
async fn greet(req: HttpRequest) -> actix_web::Result<HttpResponse> {
let name = req.match_info().get("name").unwrap_or("World");
let body = Greet { name }
.render_once()
.render_once_to_string()
.map_err(|e| InternalError::new(e, StatusCode::INTERNAL_SERVER_ERROR))?;
Ok(HttpResponse::Ok()

View file

@ -1,6 +1,6 @@
use sailfish::TemplateSimple;
use sailfish::RenderOnce;
#[derive(TemplateSimple)]
#[derive(RenderOnce)]
#[template(path = "include.stpl")]
struct Include {
title: String,
@ -12,5 +12,5 @@ fn main() {
title: "Website".to_owned(),
name: "Hanako".to_owned(),
};
println!("{}", ctx.render_once().unwrap());
println!("{}", ctx.render_once_to_string().unwrap());
}

View file

@ -1,6 +1,6 @@
use sailfish::TemplateSimple;
use sailfish::RenderOnce;
#[derive(TemplateSimple)]
#[derive(RenderOnce)]
#[template(path = "simple.stpl")]
struct Simple {
messages: Vec<String>,
@ -8,5 +8,5 @@ struct Simple {
fn main() {
let messages = vec![String::from("Message 1"), String::from("<Message 2>")];
println!("{}", Simple { messages }.render_once().unwrap());
println!("{}", Simple { messages }.render_once_to_string().unwrap());
}

View file

@ -1,12 +1,11 @@
use proc_macro2::Span;
use quote::{quote, ToTokens};
use syn::parse::{Parse, ParseStream, Result as ParseResult};
use syn::punctuated::Punctuated;
use syn::token::Comma;
use syn::visit_mut::VisitMut;
use syn::{
Block, Expr, ExprBreak, ExprCall, ExprContinue, ExprLit, ExprPath, Ident, Lit,
LitStr, Stmt, Token,
LitStr, Stmt,
};
pub struct Optimizer {
@ -179,15 +178,12 @@ fn get_rendertext_value(call: &ExprCall) -> Option<String> {
pub fn parse(expr: &'a Punctuated<Expr, Comma>) -> Option<Self> {
if expr.len() != 2 {
panic!("bad arguments: {:?}", expr.to_token_stream());
return None;
}
let Expr::Path(ExprPath { path: buf, .. }) = &expr[0] else {
panic!("bad arguments: {:?}", expr.to_token_stream());
return None;
};
let Some(buf) = buf.get_ident() else {
panic!("bad arguments: {:?}", expr.to_token_stream());
return None;
};
let Expr::Lit(ExprLit {
lit: Lit::Str(text),
@ -195,7 +191,6 @@ fn get_rendertext_value(call: &ExprCall) -> Option<String> {
}) = &expr[1]
else {
panic!("bad arguments: {:?}", expr.to_token_stream());
return None;
};
Some(Self { buf, text })
}

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

@ -8,8 +8,7 @@ use std::path::{Path, PathBuf};
use std::time::Duration;
use std::{env, thread};
use syn::parse::{ParseStream, Parser, Result as ParseResult};
use syn::punctuated::Punctuated;
use syn::{Fields, Ident, ItemStruct, LitBool, LitChar, LitStr, Token};
use syn::{Ident, ItemStruct, LitBool, LitChar, LitStr, Token};
use crate::compiler::Compiler;
use crate::config::Config;
@ -341,19 +340,8 @@ fn derive_template_once_only_impl(
// This method can be implemented in `sailfish` crate, but I found that performance
// drops when the implementation is written in `sailfish` crate.
quote! {
impl #impl_generics sailfish::TemplateOnce for #name #ty_generics #where_clause {
fn render_once(mut 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(mut self, __sf_buf: &mut sailfish::runtime::Buffer) -> std::result::Result<(), sailfish::runtime::RenderError> {
impl #impl_generics sailfish::RenderOnce for #name #ty_generics #where_clause {
fn render_once(mut 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;
@ -362,6 +350,17 @@ fn derive_template_once_only_impl(
Ok(())
}
fn render_once_to_string(mut 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(&mut buf)?;
SIZE_HINT.update(buf.len());
Ok(buf.into_string())
}
}
}
}
@ -377,19 +376,8 @@ fn derive_template_mut_only_impl(
// This method can be implemented in `sailfish` crate, but I found that performance
// drops when the implementation is written in `sailfish` crate.
quote! {
impl #impl_generics sailfish::TemplateMut for #name #ty_generics #where_clause {
fn render_mut(&mut 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_mut_to(&mut buf)?;
SIZE_HINT.update(buf.len());
Ok(buf.into_string())
}
fn render_mut_to(&mut self, __sf_buf: &mut sailfish::runtime::Buffer) -> std::result::Result<(), sailfish::runtime::RenderError> {
impl #impl_generics sailfish::RenderMut for #name #ty_generics #where_clause {
fn render_mut(&mut 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;
@ -398,6 +386,17 @@ fn derive_template_mut_only_impl(
Ok(())
}
fn render_mut_to_string(&mut 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_mut(&mut buf)?;
SIZE_HINT.update(buf.len());
Ok(buf.into_string())
}
}
}
}
@ -413,19 +412,8 @@ fn derive_template_only_impl(
// This method can be implemented in `sailfish` crate, but I found that performance
// drops when the implementation is written in `sailfish` crate.
quote! {
impl #impl_generics sailfish::Template for #name #ty_generics #where_clause {
fn render(&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_to(&mut buf)?;
SIZE_HINT.update(buf.len());
Ok(buf.into_string())
}
fn render_to(&self, __sf_buf: &mut sailfish::runtime::Buffer) -> std::result::Result<(), sailfish::runtime::RenderError> {
impl #impl_generics sailfish::Render for #name #ty_generics #where_clause {
fn render(&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;
@ -434,6 +422,17 @@ fn derive_template_only_impl(
Ok(())
}
fn render_to_string(&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(&mut buf)?;
SIZE_HINT.update(buf.len());
Ok(buf.into_string())
}
}
}
}
@ -459,12 +458,6 @@ fn derive_template_mut_impl(tokens: TokenStream) -> Result<TokenStream, syn::Err
let mut output = TokenStream::new();
output.append_all(derive_template_once_only_impl(
&strct,
&include_bytes_seq,
&output_file_string,
));
output.append_all(derive_template_mut_only_impl(
&strct,
&include_bytes_seq,
@ -480,18 +473,6 @@ fn derive_template_impl(tokens: TokenStream) -> Result<TokenStream, syn::Error>
let mut output = TokenStream::new();
output.append_all(derive_template_once_only_impl(
&strct,
&include_bytes_seq,
&output_file_string,
));
output.append_all(derive_template_mut_only_impl(
&strct,
&include_bytes_seq,
&output_file_string,
));
output.append_all(derive_template_only_impl(
&strct,
&include_bytes_seq,
@ -501,62 +482,6 @@ fn derive_template_impl(tokens: TokenStream) -> Result<TokenStream, syn::Error>
Ok(output)
}
fn derive_template_simple_impl(tokens: TokenStream) -> Result<TokenStream, syn::Error> {
let (strct, include_bytes_seq, output_file_string) =
derive_template_common_impl(tokens)?;
let name = &strct.ident;
let field_names: Punctuated<Ident, Token![,]> = match strct.fields {
Fields::Named(fields) => fields
.named
.into_iter()
.map(|f| {
f.ident.expect(
"Internal error: Failed to get field name (error code: 73621)",
)
})
.collect(),
Fields::Unit => Punctuated::new(),
_ => {
return Err(syn::Error::new(
Span::call_site(),
"You cannot derive `TemplateSimple` 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.
Ok(quote! {
impl #impl_generics sailfish::TemplateSimple 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> {
// This line is required for cargo to track child templates
#include_bytes_seq;
use sailfish::runtime as __sf_rt;
let #name { #field_names } = self;
include!(#output_file_string);
Ok(())
}
}
})
}
pub fn derive_template_once(tokens: TokenStream) -> TokenStream {
derive_template_once_impl(tokens).unwrap_or_else(|e| e.to_compile_error())
}
@ -568,7 +493,3 @@ pub fn derive_template_mut(tokens: TokenStream) -> TokenStream {
pub fn derive_template(tokens: TokenStream) -> TokenStream {
derive_template_impl(tokens).unwrap_or_else(|e| e.to_compile_error())
}
pub fn derive_template_simple(tokens: TokenStream) -> TokenStream {
derive_template_simple_impl(tokens).unwrap_or_else(|e| e.to_compile_error())
}

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,30 +4,23 @@ 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_once(input);
TokenStream::from(output)
}
#[proc_macro_derive(TemplateMut, attributes(template))]
#[proc_macro_derive(RenderMut, attributes(template))]
pub fn derive_template_mut(tokens: TokenStream) -> TokenStream {
let input = proc_macro2::TokenStream::from(tokens);
let output = sailfish_compiler::procmacro::derive_template_mut(input);
TokenStream::from(output)
}
#[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);
TokenStream::from(output)
}
#[proc_macro_derive(TemplateSimple, attributes(template))]
pub fn derive_template_simple(tokens: TokenStream) -> TokenStream {
let input = proc_macro2::TokenStream::from(tokens);
let output = sailfish_compiler::procmacro::derive_template_simple(input);
TokenStream::from(output)
}

View file

@ -1,4 +0,0 @@
disp: <%- message | disp %>
dbg: <%- message | dbg %>
disp escaped: <%= message | disp %>
dbg escaped: <%= message | dbg %>

View file

@ -1,7 +0,0 @@
1
2
INCLUDED: foo
3
4
INCLUDED: foo
5

View file

@ -1,5 +0,0 @@
1
<% include!("includes/include_parent.stpl"); %>
4
<% include!("./included.stpl"); %>
5

View file

@ -1 +0,0 @@
<% let a = include!("includes/rust_s.rs"); %><%= a %>

View file

@ -1,4 +1,4 @@
{
"name": <%- self.name | dbg %>,
"name": <%- &self.name | dbg %>,
"value": <%= self.value %>
}

View file

@ -1,4 +1,4 @@
{
"name": "JSON test",
"data": <%- self.data | json %>
"data": <%- &self.data | json %>
}

View file

@ -1,4 +0,0 @@
{
"name": "JSON test",
"data": <%- data | json %>
}

View file

@ -2,7 +2,7 @@ ESCAPED: <%= self.uppercase() %>
NON-ESCAPED: <%- self.uppercase() %>
PASS-VALUE: <%= Self::uppercase_val(self.s) %>
PASS-REF: <%= Self::multiply_ref(&self.i) %>
MUTABLE: <% self.mutate(); %><%= self.mutate %>
MUTABLE: <% self.mutate(); %><%= &self.mutate %>
MATCH: <%= match self.uppercase().as_str() {
"<TEST>" => "Cool",
_ => "Not cool"

View file

@ -1 +0,0 @@
raw: <%- raw %>

View file

@ -1,13 +0,0 @@
<div>
<span>1</span>
<span>2</span>
<span>3</span>
</div>
<div>
trailing spaces
This line should be appeared under the previous line
</div>
<% for msg in messages { %>
<div><%= msg %></div>
<% } %>

View file

@ -1,5 +0,0 @@
<% if matches!(value, Some(_)) { %>
Some
<% } else { %>
None
<% } %>

View file

@ -1,10 +0,0 @@
<!DOCTYPE html>
<html>
<head><title>Fortunes</title></head>
<body>
<table>
<tr><th>id</th><th>message</th></tr>
<% for item in items { %><tr><td><%= item.id %></td><td><%= item.message %></td></tr><% } %>
</table>
</body>
</html>

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_to_string()
.unwrap()
);
}

View file

@ -10,17 +10,17 @@ error[E0599]: no method named `render_once` found for struct `InvalidOptionValue
6 | struct InvalidOptionValue {
| ------------------------- method `render_once` not found for this
...
11 | println!("{}", InvalidOptionValue { name: "Hanako".to_owned() }.render_once().unwrap());
| ^^^^^^^^^^^ method not found in `InvalidOptionValue`
11 | println!("{}", InvalidOptionValue { name: "Hanako".to_owned() }.render_once_to_string().unwrap());
| ^^^^^^^^^^^^^^^^^^^^^ method not found in `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`
= note: the following trait defines an item `render_once_to_string`, perhaps you need to implement it:
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

@ -2,7 +2,7 @@ extern crate sailfish_macros;
use integration_tests::assert_string_eq;
use sailfish::runtime::RenderResult;
use sailfish::{Template, TemplateMut, TemplateOnce, TemplateSimple};
use sailfish::{RenderOnce, RenderMut, Render};
use std::path::PathBuf;
fn assert_render_result(name: &str, result: RenderResult) {
@ -22,21 +22,20 @@ fn assert_render_result(name: &str, result: RenderResult) {
}
#[inline]
fn assert_render_once<T: TemplateOnce>(name: &str, template: T) {
assert_render_result(name, template.render_once());
fn assert_render_once<T: RenderOnce>(name: &str, template: T) {
assert_render_result(name, template.render_once_to_string());
}
#[inline]
fn assert_render_mut<T: TemplateMut>(name: &str, mut template: T) {
assert_render_result(name, template.render_mut());
assert_render_result(name, template.render_once());
fn assert_render_mut<T: RenderMut>(name: &str, mut template: T) {
assert_render_result(name, template.render_mut_to_string());
assert_render_once(name, template);
}
#[inline]
fn assert_render<T: Template>(name: &str, mut template: T) {
assert_render_result(name, template.render());
assert_render_result(name, template.render_mut());
assert_render_result(name, template.render_once());
fn assert_render<T: Render>(name: &str, template: T) {
assert_render_result(name, template.render_to_string());
assert_render_mut(name, template);
}
trait ConflictWithSailFishRender {
@ -46,16 +45,7 @@ trait ConflictWithSailFishRender {
impl ConflictWithSailFishRender for u8 {}
impl ConflictWithSailFishRender for u16 {}
#[derive(TemplateSimple)]
#[template(path = "empty.stpl")]
struct EmptySimple {}
#[test]
fn empty_simple() {
assert_render_once("empty", Empty {});
}
#[derive(Template)]
#[derive(Render)]
#[template(path = "empty.stpl")]
struct Empty {}
@ -64,7 +54,7 @@ fn empty() {
assert_render("empty", Empty {});
}
#[derive(Template)]
#[derive(Render)]
#[template(path = "noescape.stpl")]
struct Noescape<'a> {
raw: &'a str,
@ -80,7 +70,7 @@ fn noescape() {
);
}
#[derive(Template)]
#[derive(Render)]
#[template(path = "json.stpl")]
struct Json {
name: String,
@ -98,7 +88,7 @@ fn json() {
);
}
#[derive(Template)]
#[derive(Render)]
#[template(path = "custom_delimiter.stpl")]
#[template(delimiter = '🍣')]
struct CustomDelimiter;
@ -108,7 +98,7 @@ fn custom_delimiter() {
assert_render("custom_delimiter", CustomDelimiter);
}
#[derive(Template)]
#[derive(Render)]
#[template(path = "include.stpl")]
struct Include<'a> {
strs: &'a [&'a str],
@ -124,7 +114,7 @@ fn test_include() {
);
}
#[derive(Template)]
#[derive(Render)]
#[template(path = "continue_break.stpl", rm_whitespace = true)]
struct ContinueBreak;
@ -133,7 +123,7 @@ fn continue_break() {
assert_render("continue_break", ContinueBreak);
}
#[derive(Template)]
#[derive(Render)]
#[template(path = "techempower.stpl", rm_whitespace = true)]
struct Techempower {
items: Vec<Fortune>,
@ -203,7 +193,7 @@ fn test_techempower() {
assert_render("techempower", Techempower { items });
}
#[derive(Template)]
#[derive(Render)]
#[template(path = "rm_whitespace.stpl")]
#[template(rm_whitespace = true)]
struct RmWhitespace<'a, 'b> {
@ -220,7 +210,7 @@ fn test_rm_whitespace() {
);
}
#[derive(Template)]
#[derive(Render)]
#[template(path = "comment.stpl")]
struct Comment {}
@ -229,7 +219,7 @@ fn test_comment() {
assert_render("comment", Comment {})
}
#[derive(Template)]
#[derive(Render)]
#[template(path = "rust_macro.stpl", rm_whitespace = true)]
struct RustMacro {
value: Option<i32>,
@ -240,7 +230,7 @@ fn test_rust_macro() {
assert_render("rust_macro", RustMacro { value: Some(10) });
}
#[derive(Template)]
#[derive(Render)]
#[template(path = "formatting.stpl", escape = false)]
struct Formatting;
@ -249,7 +239,7 @@ fn test_formatting() {
assert_render("formatting", Formatting);
}
#[derive(Template)]
#[derive(Render)]
#[template(path = "filter.stpl")]
struct Filter<'a> {
message: &'a str,
@ -260,7 +250,7 @@ fn test_filter() {
assert_render("filter", Filter { message: "hello" });
}
#[derive(Template)]
#[derive(Render)]
#[template(path = "filter2.stpl")]
struct Filter2;
@ -269,7 +259,7 @@ fn test_filter2() {
assert_render("filter2", Filter2);
}
#[derive(Template)]
#[derive(Render)]
#[template(path = "truncate_filter.stpl")]
struct TruncateFilter;
@ -277,7 +267,7 @@ struct TruncateFilter;
fn test_truncate_filter() {
assert_render("truncate_filter", TruncateFilter);
}
#[derive(Template)]
#[derive(Render)]
#[template(path = "json_filter.stpl")]
struct JsonFilter {
data: serde_json::Value,
@ -301,7 +291,7 @@ fn test_json_filter() {
mod unix {
use super::*;
#[derive(Template)]
#[derive(Render)]
#[template(path = "include_nest.stpl")]
struct IncludeNest<'a> {
s: &'a str,
@ -312,7 +302,7 @@ mod unix {
assert_render("include_nest", IncludeNest { s: "foo" });
}
#[derive(Template)]
#[derive(Render)]
#[template(path = "include_rust.stpl")]
struct IncludeRust {
value: usize,
@ -324,7 +314,7 @@ mod unix {
}
}
#[derive(TemplateMut)]
#[derive(RenderMut)]
#[template(path = "method.stpl")]
struct Method {
s: &'static str,

View file

@ -1,302 +0,0 @@
extern crate sailfish_macros;
use integration_tests::assert_string_eq;
use sailfish::runtime::RenderResult;
use sailfish::TemplateSimple;
use std::path::PathBuf;
fn assert_render_result(name: &str, result: RenderResult) {
let mut output_file = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
output_file.push("templates");
output_file.push(name);
output_file.set_extension("out");
let mut expected = std::fs::read_to_string(output_file).unwrap();
if expected.ends_with('\n') {
expected.truncate(expected.len() - 1);
if expected.ends_with('\r') {
expected.truncate(expected.len() - 1);
}
}
assert_string_eq!(&*result.unwrap(), &*expected);
}
#[inline]
fn assert_render_simple<T: TemplateSimple>(name: &str, template: T) {
assert_render_result(name, template.render_once());
}
trait ConflictWithSailFishRender {
fn render() {}
}
impl ConflictWithSailFishRender for u8 {}
impl ConflictWithSailFishRender for u16 {}
#[derive(TemplateSimple)]
#[template(path = "empty.stpl")]
struct Empty {}
#[test]
fn empty() {
assert_render_simple("empty", Empty {});
}
#[derive(TemplateSimple)]
#[template(path = "noescape_s.stpl")]
struct Noescape<'a> {
raw: &'a str,
}
#[test]
fn noescape() {
assert_render_simple(
"noescape",
Noescape {
raw: "<h1>Hello, World!</h1>",
},
);
}
#[derive(TemplateSimple)]
#[template(path = "json_s.stpl")]
struct Json {
name: String,
value: u16,
}
#[test]
fn json() {
assert_render_simple(
"json",
Json {
name: String::from("Taro"),
value: 16,
},
);
}
#[derive(TemplateSimple)]
#[template(path = "custom_delimiter.stpl")]
#[template(delimiter = '🍣')]
struct CustomDelimiter;
#[test]
fn custom_delimiter() {
assert_render_simple("custom_delimiter", CustomDelimiter);
}
#[derive(TemplateSimple)]
#[template(path = "include_s.stpl")]
struct Include<'a> {
strs: &'a [&'a str],
}
#[test]
fn test_include() {
assert_render_simple(
"include",
Include {
strs: &["foo", "bar"],
},
);
}
#[derive(TemplateSimple)]
#[template(path = "continue_break.stpl", rm_whitespace = true)]
struct ContinueBreak;
#[test]
fn continue_break() {
assert_render_simple("continue_break", ContinueBreak);
}
#[derive(TemplateSimple)]
#[template(path = "techempower_s.stpl", rm_whitespace = true)]
struct Techempower {
items: Vec<Fortune>,
}
struct Fortune {
id: i32,
message: &'static str,
}
#[test]
fn test_techempower() {
let items = vec![
Fortune {
id: 0,
message: "Additional fortune added at request time.",
},
Fortune {
id: 1,
message: "fortune: No such file or directory",
},
Fortune {
id: 2,
message: "A computer scientist is someone who fixes things that aren't broken.",
},
Fortune {
id: 3,
message: "After enough decimal places, nobody gives a damn.",
},
Fortune {
id: 4,
message: "A bad random number generator: 1, 1, 1, 1, 1, 4.33e+67, 1, 1, 1",
},
Fortune {
id: 5,
message: "A computer program does what you tell it to do, not what you want it to do.",
},
Fortune {
id: 6,
message: "Emacs is a nice operating system, but I prefer UNIX. — Tom Christaensen",
},
Fortune {
id: 7,
message: "Any program that runs right is obsolete.",
},
Fortune {
id: 8,
message: "A list is only as strong as its weakest link. — Donald Knuth",
},
Fortune {
id: 9,
message: "Feature: A bug with seniority.",
},
Fortune {
id: 10,
message: "Computers make very fast, very accurate mistakes.",
},
Fortune {
id: 11,
message: "<script>alert(\"This should not be displayed in a browser alert box.\");</script>",
},
Fortune {
id: 12,
message: "フレームワークのベンチマーク",
},
];
assert_render_simple("techempower", Techempower { items });
}
#[derive(TemplateSimple)]
#[template(path = "rm_whitespace_s.stpl")]
#[template(rm_whitespace = true)]
struct RmWhitespace<'a, 'b> {
messages: &'a [&'b str],
}
#[test]
fn test_rm_whitespace() {
assert_render_simple(
"rm_whitespace",
RmWhitespace {
messages: &["foo", "bar"],
},
);
}
#[derive(TemplateSimple)]
#[template(path = "comment.stpl")]
struct Comment {}
#[test]
fn test_comment() {
assert_render_simple("comment", Comment {})
}
#[derive(TemplateSimple)]
#[template(path = "rust_macro_s.stpl", rm_whitespace = true)]
struct RustMacro {
value: Option<i32>,
}
#[test]
fn test_rust_macro() {
assert_render_simple("rust_macro", RustMacro { value: Some(10) });
}
#[derive(TemplateSimple)]
#[template(path = "formatting.stpl", escape = false)]
struct Formatting;
#[test]
fn test_formatting() {
assert_render_simple("formatting", Formatting);
}
#[derive(TemplateSimple)]
#[template(path = "filter_s.stpl")]
struct Filter<'a> {
message: &'a str,
}
#[test]
fn test_filter() {
assert_render_simple("filter", Filter { message: "hello" });
}
#[derive(TemplateSimple)]
#[template(path = "filter2.stpl")]
struct Filter2;
#[test]
fn test_filter2() {
assert_render_simple("filter2", Filter2);
}
#[derive(TemplateSimple)]
#[template(path = "truncate_filter.stpl")]
struct TruncateFilter;
#[test]
fn test_truncate_filter() {
assert_render_simple("truncate_filter", TruncateFilter);
}
#[derive(TemplateSimple)]
#[template(path = "json_filter_s.stpl")]
struct JsonFilter {
data: serde_json::Value,
}
#[test]
fn test_json_filter() {
let data = serde_json::json!({
"name": "John Doe",
"age": 43,
"phones": [
"+44 1234567",
"+44 2345678"
]
});
assert_render_simple("json_filter", JsonFilter { data });
}
#[cfg(unix)]
mod unix {
use super::*;
#[derive(TemplateSimple)]
#[template(path = "include_nest_s.stpl")]
struct IncludeNest<'a> {
s: &'a str,
}
#[test]
fn test_include_nest() {
assert_render_simple("include_nest_s", IncludeNest { s: "foo" });
}
#[derive(TemplateSimple)]
#[template(path = "include_rust_s.stpl")]
struct IncludeRust {
value: usize,
}
#[test]
fn test_include_rust() {
assert_render_simple("include_rust", IncludeRust { value: 58 });
}
}

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)]`, `#[derive(TemplateMut)]` or `#[derive(Template)]`.
//! derive macro `#[derive(RenderOnce)]`, `#[derive(RenderMut)]` 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::Template;
//! use sailfish::RenderOnce;
//!
//! #[derive(Template)]
//! #[derive(RenderOnce)]
//! #[template(path = "hello.stpl")]
//! struct HelloTemplate {
//! messages: Vec<String>
@ -35,237 +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, RenderMut, RenderOnce, RenderResult};
#[cfg(feature = "derive")]
#[cfg_attr(docsrs, doc(cfg(feature = "derive")))]
pub use sailfish_macros::{Template, TemplateMut, TemplateOnce, TemplateSimple};
/// Template which can be accessed without using `self`.
pub trait TemplateSimple: 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::TemplateSimple;
/// use sailfish::runtime::Buffer;
///
/// # pub struct HelloTemplate {
/// # messages: Vec<String>,
/// # }
/// #
/// # impl TemplateSimple 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(())
/// # }
/// # }
/// #
/// 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>;
}
/// 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(())
/// # }
/// # }
/// #
/// 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>;
}
/// Template that is mutable and can be rendered any number of times.
pub trait TemplateMut: TemplateOnce {
/// 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` 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_to` method instead.
fn render_mut(&mut 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, TemplateMut};
/// 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 TemplateMut for HelloTemplate {
/// # fn render_mut(&mut self) -> Result<String, sailfish::RenderError> {
/// # Ok(String::new())
/// # }
/// #
/// # fn render_mut_to(&mut self, buf: &mut Buffer)
/// # -> Result<(), sailfish::RenderError> {
/// # Ok(())
/// # }
/// # }
/// #
/// let mut tpl = HelloTemplate {
/// messages: vec!["foo".to_string()]
/// };
///
/// // custom pre-allocation
/// let mut buffer = Buffer::with_capacity(100);
/// tpl.render_mut_to(&mut buffer).unwrap();
/// ```
fn render_mut_to(&mut self, buf: &mut Buffer) -> Result<(), RenderError>;
}
/// Template that can be rendered any number of times.
pub trait Template: TemplateMut {
/// 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` 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_to` method instead.
fn render(&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, TemplateMut, Template};
/// 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 TemplateMut for HelloTemplate {
/// # fn render_mut(&mut self) -> Result<String, sailfish::RenderError> {
/// # Ok(String::new())
/// # }
/// #
/// # fn render_mut_to(&mut self, buf: &mut Buffer)
/// # -> Result<(), sailfish::RenderError> {
/// # Ok(())
/// # }
/// # }
/// #
/// # impl Template for HelloTemplate {
/// # fn render(&self) -> Result<String, sailfish::RenderError> {
/// # Ok(String::new())
/// # }
/// #
/// # fn render_to(&self, buf: &mut Buffer)
/// # -> Result<(), sailfish::RenderError> {
/// # Ok(())
/// # }
/// # }
/// #
/// let tpl = HelloTemplate {
/// messages: vec!["foo".to_string()]
/// };
///
/// // custom pre-allocation
/// let mut buffer = Buffer::with_capacity(100);
/// tpl.render_to(&mut buffer).unwrap();
/// ```
fn render_to(&self, buf: &mut Buffer) -> Result<(), RenderError>;
}
pub use sailfish_macros::{Render, RenderMut, RenderOnce};

View file

@ -11,7 +11,7 @@ mod render;
mod size_hint;
pub use buffer::Buffer;
pub use render::{Render, RenderError, RenderOnce, RenderResult};
pub use render::{Render, RenderError, RenderMut, RenderOnce, RenderResult};
pub use size_hint::SizeHint;
#[doc(hidden)]

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;
@ -38,25 +40,37 @@ use super::escape;
/// }
/// }
/// ```
pub trait Render {
/// render to `Buffer` without escaping
fn render(&self, b: &mut Buffer) -> Result<(), RenderError>;
/// render to `Buffer` with HTML escaping
#[inline]
fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
let mut tmp = Buffer::new();
self.render(&mut tmp)?;
escape::escape_to_buf(tmp.as_str(), b);
Ok(())
}
}
/// types which can be rendered inside buffer block (`<%= %>`)
///
/// See [`Render`] for more information.
/// types which can be rendered inside buffer block (`<%- %>`)
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,56 +81,109 @@ 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
// where
// T: Render,
// {
// fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
// T::render(self, b)
// }
/// See [`RenderOnce`] for more information.
pub trait RenderMut {
/// See [`RenderOnce::render_once`] for more information.
fn render_mut(&mut self, b: &mut Buffer) -> Result<(), RenderError>;
// fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
// T::render_escaped(self, b)
// }
// }
/// See [`RenderOnce::render_once_escaped`] for more information.
#[inline]
fn render_mut_escaped(&mut self, b: &mut Buffer) -> Result<(), RenderError> {
let mut tmp = Buffer::new();
self.render_mut(&mut tmp)?;
escape::escape_to_buf(tmp.as_str(), b);
Ok(())
}
impl<T> RenderOnce for T
where
T: Render,
{
fn render_once(self, b: &mut Buffer) -> Result<(), RenderError> {
/// See [`RenderOnce::render_once_to_string`] for more information.
#[inline]
fn render_mut_to_string(&mut self) -> RenderResult {
static SIZE_HINT: SizeHint = SizeHint::new();
let mut buf = Buffer::with_capacity(SIZE_HINT.get());
self.render_mut(&mut buf)?;
SIZE_HINT.update(buf.len());
Ok(buf.into_string())
}
}
/// See [`RenderOnce`] for more information.
pub trait Render {
/// See [`RenderOnce::render_once`] for more information.
fn render(&self, b: &mut Buffer) -> Result<(), RenderError>;
/// See [`RenderOnce::render_once_escaped`] for more information.
#[inline]
fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
let mut tmp = Buffer::new();
self.render(&mut tmp)?;
escape::escape_to_buf(tmp.as_str(), b);
Ok(())
}
/// See [`RenderOnce::render_once_to_string`] for more information.
#[inline]
fn render_to_string(&self) -> RenderResult {
static SIZE_HINT: SizeHint = SizeHint::new();
let mut buf = Buffer::with_capacity(SIZE_HINT.get());
self.render(&mut buf)?;
SIZE_HINT.update(buf.len());
Ok(buf.into_string())
}
}
impl<T: Render + ?Sized> RenderMut for T {
fn render_mut(&mut self, b: &mut Buffer) -> Result<(), RenderError> {
self.render(b)
}
fn render_once_escaped(self, b: &mut Buffer) -> Result<(), RenderError> {
fn render_mut_escaped(&mut self, b: &mut Buffer) -> Result<(), RenderError> {
self.render_escaped(b)
}
fn render_mut_to_string(&mut self) -> RenderResult {
self.render_to_string()
}
}
// /// Autoref-based stable specialization
// ///
// /// Explanation can be found [here](https://github.com/dtolnay/case-studies/blob/master/autoref-specialization/README.md)
// impl<T: Display> Render for &T {
// fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
// fmt::write(b, format_args!("{}", self))
// }
//
// fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
// struct Wrapper<'a>(&'a mut Buffer);
//
// impl<'a> fmt::Write for Wrapper<'a> {
// #[inline]
// fn push_str(&mut self, s: &str) -> Result<(), RenderError> {
// escape::escape_to_buf(s, self.0);
// Ok(())
// }
// }
//
// fmt::write(&mut Wrapper(b), format_args!("{}", self))
// }
// }
impl<T: RenderMut> RenderOnce for T {
fn render_once(mut self, b: &mut Buffer) -> Result<(), RenderError> {
self.render_mut(b)
}
fn render_once_escaped(mut self, b: &mut Buffer) -> Result<(), RenderError> {
self.render_mut_escaped(b)
}
fn render_once_to_string(mut self) -> RenderResult {
self.render_mut_to_string()
}
}
impl Render for String {
#[inline]
@ -353,7 +420,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 +524,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)]