Unify Render* and Template* traits
This commit is contained in:
parent
4ed1e098bd
commit
133b98bc5d
48 changed files with 354 additions and 981 deletions
|
|
@ -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());
|
||||
> }
|
||||
> ```
|
||||
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ fn main() {
|
|||
};
|
||||
|
||||
// Now render templates with given data
|
||||
println!("{}", ctx.render_once().unwrap());
|
||||
println!("{}", ctx.render_once_to_string().unwrap());
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -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!
|
||||
|
||||
|
|
|
|||
|
|
@ -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 %>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 })
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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") ; }"#
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +0,0 @@
|
|||
disp: <%- message | disp %>
|
||||
dbg: <%- message | dbg %>
|
||||
disp escaped: <%= message | disp %>
|
||||
dbg escaped: <%= message | dbg %>
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
1
|
||||
2
|
||||
INCLUDED: foo
|
||||
3
|
||||
4
|
||||
INCLUDED: foo
|
||||
5
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
1
|
||||
<% include!("includes/include_parent.stpl"); %>
|
||||
4
|
||||
<% include!("./included.stpl"); %>
|
||||
5
|
||||
|
|
@ -1 +0,0 @@
|
|||
<% let a = include!("includes/rust_s.rs"); %><%= a %>
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
{
|
||||
"name": <%- self.name | dbg %>,
|
||||
"name": <%- &self.name | dbg %>,
|
||||
"value": <%= self.value %>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
{
|
||||
"name": "JSON test",
|
||||
"data": <%- self.data | json %>
|
||||
"data": <%- &self.data | json %>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +0,0 @@
|
|||
{
|
||||
"name": "JSON test",
|
||||
"data": <%- data | json %>
|
||||
}
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
raw: <%- raw %>
|
||||
|
|
@ -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>
|
||||
<% } %>
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
<% if matches!(value, Some(_)) { %>
|
||||
Some
|
||||
<% } else { %>
|
||||
None
|
||||
<% } %>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
}
|
||||
}
|
||||
|
|
@ -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};
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue