Merge branch 'master' into stable
This commit is contained in:
commit
c809027152
File diff suppressed because it is too large
Load Diff
|
@ -3,11 +3,12 @@ members = [
|
|||
"sailfish",
|
||||
"sailfish-compiler",
|
||||
"sailfish-macros",
|
||||
"examples",
|
||||
"integration-tests"
|
||||
"sailfish-tests/integration-tests"
|
||||
]
|
||||
exclude = [
|
||||
"benches"
|
||||
"sailfish-tests/fuzzing-tests",
|
||||
"benches",
|
||||
"examples"
|
||||
]
|
||||
|
||||
[profile.dev.package.syn]
|
||||
|
|
|
@ -30,8 +30,8 @@ Dependencies:
|
|||
|
||||
```toml
|
||||
[dependencies]
|
||||
sailfish = "0.1.3"
|
||||
sailfish-macros = "0.1.3"
|
||||
sailfish = "0.2.0"
|
||||
sailfish-macros = "0.2.0"
|
||||
```
|
||||
|
||||
Template file (templates/hello.stpl):
|
||||
|
|
|
@ -188,9 +188,10 @@ checksum = "474a626a67200bd107d44179bb3d4fc61891172d11696609264589be6a0e6a43"
|
|||
|
||||
[[package]]
|
||||
name = "benches"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"askama",
|
||||
"bytes",
|
||||
"criterion",
|
||||
"fomat-macros",
|
||||
"handlebars",
|
||||
|
@ -207,7 +208,7 @@ dependencies = [
|
|||
"serde_json",
|
||||
"serde_yaml",
|
||||
"tera",
|
||||
"v_htmlescape",
|
||||
"v_htmlescape 0.9.1",
|
||||
"yarte",
|
||||
]
|
||||
|
||||
|
@ -271,6 +272,15 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "buf-min"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c4ab58c23b5bf0d27713787d5dcd5f62c2260ca172d5af24b7de706bcc2897f"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.4.0"
|
||||
|
@ -1800,7 +1810,7 @@ checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072"
|
|||
|
||||
[[package]]
|
||||
name = "sailfish"
|
||||
version = "0.1.2"
|
||||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"itoap",
|
||||
"ryu",
|
||||
|
@ -1809,7 +1819,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "sailfish-compiler"
|
||||
version = "0.1.2"
|
||||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"proc-macro2 1.0.18",
|
||||
|
@ -1820,7 +1830,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "sailfish-macros"
|
||||
version = "0.1.2"
|
||||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.18",
|
||||
"sailfish-compiler",
|
||||
|
@ -2286,6 +2296,16 @@ dependencies = [
|
|||
"v_escape_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "v_escape"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b66158ce426982197fd44266d68125fd4000f1d42f5ee33ef02b500b4b6b0024"
|
||||
dependencies = [
|
||||
"buf-min",
|
||||
"v_escape_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "v_escape_derive"
|
||||
version = "0.8.1"
|
||||
|
@ -2315,7 +2335,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "37981da7d2bd82edc21de370f4d7b010360c8590f70c9f76f5df20e780dc49f2"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"v_escape",
|
||||
"v_escape 0.11.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "v_htmlescape"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f5fd25529cb2f78527b5ee507bcfb357b26d057b5e480853c26d49a4ead5c629"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"v_escape 0.12.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2489,18 +2519,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "yarte"
|
||||
version = "0.11.3"
|
||||
source = "git+https://github.com/botika/yarte#cf0bf4504c4d8d8a6346d3584e0331bfdd8f78f5"
|
||||
version = "0.12.0"
|
||||
source = "git+https://github.com/botika/yarte#a39f49d5e2f7826b1be492bca56cd79342369fe3"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"buf-min",
|
||||
"yarte_derive",
|
||||
"yarte_helpers",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yarte_codegen"
|
||||
version = "0.11.3"
|
||||
source = "git+https://github.com/botika/yarte#cf0bf4504c4d8d8a6346d3584e0331bfdd8f78f5"
|
||||
version = "0.12.0"
|
||||
source = "git+https://github.com/botika/yarte#a39f49d5e2f7826b1be492bca56cd79342369fe3"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.18",
|
||||
"quote 1.0.7",
|
||||
|
@ -2511,8 +2541,8 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "yarte_derive"
|
||||
version = "0.11.3"
|
||||
source = "git+https://github.com/botika/yarte#cf0bf4504c4d8d8a6346d3584e0331bfdd8f78f5"
|
||||
version = "0.12.0"
|
||||
source = "git+https://github.com/botika/yarte#a39f49d5e2f7826b1be492bca56cd79342369fe3"
|
||||
dependencies = [
|
||||
"bat",
|
||||
"proc-macro2 1.0.18",
|
||||
|
@ -2528,37 +2558,37 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "yarte_helpers"
|
||||
version = "0.11.3"
|
||||
source = "git+https://github.com/botika/yarte#cf0bf4504c4d8d8a6346d3584e0331bfdd8f78f5"
|
||||
version = "0.12.0"
|
||||
source = "git+https://github.com/botika/yarte#a39f49d5e2f7826b1be492bca56cd79342369fe3"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"buf-min",
|
||||
"dtoa",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
"toml",
|
||||
"v_htmlescape",
|
||||
"v_htmlescape 0.10.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yarte_hir"
|
||||
version = "0.11.3"
|
||||
source = "git+https://github.com/botika/yarte#cf0bf4504c4d8d8a6346d3584e0331bfdd8f78f5"
|
||||
version = "0.12.0"
|
||||
source = "git+https://github.com/botika/yarte#a39f49d5e2f7826b1be492bca56cd79342369fe3"
|
||||
dependencies = [
|
||||
"derive_more",
|
||||
"proc-macro2 1.0.18",
|
||||
"quote 1.0.7",
|
||||
"syn 1.0.33",
|
||||
"v_eval",
|
||||
"v_htmlescape",
|
||||
"v_htmlescape 0.10.0",
|
||||
"yarte_helpers",
|
||||
"yarte_parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yarte_parser"
|
||||
version = "0.11.3"
|
||||
source = "git+https://github.com/botika/yarte#cf0bf4504c4d8d8a6346d3584e0331bfdd8f78f5"
|
||||
version = "0.12.0"
|
||||
source = "git+https://github.com/botika/yarte#a39f49d5e2f7826b1be492bca56cd79342369fe3"
|
||||
dependencies = [
|
||||
"annotate-snippets",
|
||||
"derive_more",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "benches"
|
||||
version = "0.1.3"
|
||||
version = "0.2.0"
|
||||
authors = ["Dirkjan Ochtman <dirkjan@ochtman.nl>", "Ryohei Machida <orcinus4627@gmail.com>"]
|
||||
build = "src/build.rs"
|
||||
edition = "2018"
|
||||
|
@ -25,6 +25,7 @@ serde_yaml = "0.8"
|
|||
tera = { git = "https://github.com/Keats/tera" }
|
||||
v_htmlescape = "0.9.1"
|
||||
yarte = { git = "https://github.com/botika/yarte", features = ["bytes-buf", "fixed"] }
|
||||
bytes = "0.5.5"
|
||||
|
||||
[build-dependencies]
|
||||
ructe = { git = "https://github.com/kaj/ructe" }
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use bytes::BytesMut;
|
||||
use yarte::TemplateBytes;
|
||||
|
||||
pub fn big_table(b: &mut criterion::Bencher<'_>, size: &usize) {
|
||||
|
@ -10,7 +11,7 @@ pub fn big_table(b: &mut criterion::Bencher<'_>, size: &usize) {
|
|||
table.push(inner);
|
||||
}
|
||||
let t = BigTable { table };
|
||||
b.iter(|| t.call(109915));
|
||||
b.iter(|| t.call::<BytesMut>(109915));
|
||||
}
|
||||
|
||||
#[derive(TemplateBytes)]
|
||||
|
@ -42,7 +43,7 @@ pub fn teams(b: &mut criterion::Bencher<'_>) {
|
|||
},
|
||||
],
|
||||
};
|
||||
b.iter(|| t.call(239));
|
||||
b.iter(|| t.call::<BytesMut>(239));
|
||||
}
|
||||
|
||||
#[derive(TemplateBytes)]
|
||||
|
|
|
@ -4,8 +4,8 @@ In order to use sailfish templates, you have add two dependencies in your `Cargo
|
|||
|
||||
```toml
|
||||
[dependencies]
|
||||
sailfish = "0.1.3"
|
||||
sailfish-macros = "0.1.3"
|
||||
sailfish = "0.2.0"
|
||||
sailfish-macros = "0.2.0"
|
||||
```
|
||||
|
||||
`sailfish` crate contains runtime for rendering contents, and `sailfish-macros` serves you derive macros to compile and import the template files.
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "sailfish-examples"
|
||||
version = "0.1.3"
|
||||
version = "0.2.0"
|
||||
authors = ["Ryohei Machida <orcinus4627@gmail.com>"]
|
||||
edition = "2018"
|
||||
publish = false
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
{
|
||||
"name": "<%= name %>",
|
||||
"value": <%= value %>
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "sailfish-compiler"
|
||||
version = "0.1.3"
|
||||
version = "0.2.0"
|
||||
authors = ["Ryohei Machida <orcinus4627@gmail.com>"]
|
||||
description = "Really fast, intuitive template engine for Rust"
|
||||
homepage = "https://github.com/Kogia-sima/sailfish"
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
use quote::ToTokens;
|
||||
use std::fs;
|
||||
use std::io::Write;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use syn::Block;
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::error::*;
|
||||
|
@ -72,14 +74,13 @@ impl Compiler {
|
|||
fs::create_dir_all(parent)
|
||||
.chain_err(|| format!("Failed to save artifacts in {:?}", parent))?;
|
||||
}
|
||||
if output.exists() {
|
||||
fs::remove_file(output)
|
||||
.chain_err(|| format!("Failed to remove artifact {:?}", output))?;
|
||||
}
|
||||
|
||||
let string = tsource.ast.into_token_stream().to_string();
|
||||
fs::write(output, rustfmt_block(&*string).unwrap_or(string))
|
||||
.chain_err(|| format!("Failed to save artifact in {:?}", output))?;
|
||||
|
||||
let mut f = fs::File::create(output)
|
||||
.chain_err(|| format!("Failed to create artifact: {:?}", output))?;
|
||||
writeln!(f, "{}", rustfmt_block(&*string).unwrap_or(string))
|
||||
.chain_err(|| format!("Failed to write artifact into {:?}", output))?;
|
||||
Ok(report)
|
||||
};
|
||||
|
||||
|
@ -91,4 +92,38 @@ impl Compiler {
|
|||
e
|
||||
})
|
||||
}
|
||||
|
||||
pub fn compile_str(&self, input: &str) -> Result<String, Error> {
|
||||
let dummy_path = Path::new(env!("CARGO_MANIFEST_DIR"));
|
||||
|
||||
let include_handler = Arc::new(|_: &Path| -> Result<Block, Error> {
|
||||
Err(make_error!(
|
||||
ErrorKind::AnalyzeError(
|
||||
"include! macro is not allowed in inline template".to_owned()
|
||||
),
|
||||
source = input.to_owned()
|
||||
))
|
||||
});
|
||||
|
||||
let parser = Parser::new().delimiter(self.config.delimiter);
|
||||
let translator = Translator::new().escape(self.config.escape);
|
||||
let resolver = Resolver::new().include_handler(include_handler);
|
||||
let optimizer = Optimizer::new().rm_whitespace(self.config.rm_whitespace);
|
||||
|
||||
let compile = || -> Result<String, Error> {
|
||||
let stream = parser.parse(input);
|
||||
let mut tsource = translator.translate(stream)?;
|
||||
resolver.resolve(dummy_path, &mut tsource.ast)?;
|
||||
|
||||
optimizer.optimize(&mut tsource.ast);
|
||||
Ok(tsource.ast.into_token_stream().to_string())
|
||||
};
|
||||
|
||||
compile()
|
||||
.chain_err(|| "Failed to compile template.")
|
||||
.map_err(|mut e| {
|
||||
e.source = Some(input.to_owned());
|
||||
e
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ impl Parse for RenderTextMacroArgument {
|
|||
fn get_rendertext_value(i: &ExprMacro) -> Option<String> {
|
||||
let mut it = i.mac.path.segments.iter();
|
||||
|
||||
if it.next().map_or(false, |s| s.ident == "sfrt")
|
||||
if it.next().map_or(false, |s| s.ident == "__sf_rt")
|
||||
&& it.next().map_or(false, |s| s.ident == "render_text")
|
||||
&& it.next().is_none()
|
||||
{
|
||||
|
@ -74,14 +74,14 @@ impl VisitMut for OptmizerImpl {
|
|||
|
||||
fl.body.stmts.remove(0);
|
||||
*fl.body.stmts.last_mut().unwrap() = syn::parse2(quote! {
|
||||
sfrt::render_text!(_ctx, #concat);
|
||||
__sf_rt::render_text!(__sf_buf, #concat);
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let new_expr = syn::parse2(quote! {{
|
||||
sfrt::render_text!(_ctx, #sf);
|
||||
__sf_rt::render_text!(__sf_buf, #sf);
|
||||
#fl;
|
||||
unsafe { _ctx.buf.set_len(_ctx.buf.len() - #sf_len); }
|
||||
unsafe { __sf_buf._set_len(__sf_buf.len() - #sf_len); }
|
||||
}})
|
||||
.unwrap();
|
||||
|
||||
|
@ -116,7 +116,7 @@ impl VisitMut for OptmizerImpl {
|
|||
buffer.push_str(line.trim_start());
|
||||
}
|
||||
}
|
||||
i.mac.tokens = quote! { _ctx, #buffer };
|
||||
i.mac.tokens = quote! { __sf_buf, #buffer };
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -289,7 +289,7 @@ fn find_block_end(haystack: &str, delimiter: &str) -> Option<usize> {
|
|||
b'/' => match remain.as_bytes().get(pos + 1).copied() {
|
||||
Some(b'/') => unwrap_or_break!(find_comment_end(&remain[pos..])),
|
||||
Some(b'*') => unwrap_or_break!(find_block_comment_end(&remain[pos..])),
|
||||
_ => pos + 1,
|
||||
_ => 1,
|
||||
},
|
||||
b'\"' => {
|
||||
// check if the literal is a raw string
|
||||
|
@ -311,7 +311,7 @@ fn find_block_end(haystack: &str, delimiter: &str) -> Option<usize> {
|
|||
if remain[pos..].starts_with(delimiter) {
|
||||
return Some(haystack.len() - remain.len() + pos + delimiter.len());
|
||||
} else {
|
||||
pos + 1
|
||||
1
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -200,6 +200,7 @@ fn derive_template_impl(tokens: TokenStream) -> Result<TokenStream, syn::Error>
|
|||
.ancestors()
|
||||
.find(|p| p.join("LICENSE").exists())
|
||||
.unwrap()
|
||||
.join("sailfish-tests")
|
||||
.join("integration-tests")
|
||||
.join("tests")
|
||||
.join("fails")
|
||||
|
@ -269,23 +270,18 @@ fn derive_template_impl(tokens: TokenStream) -> Result<TokenStream, syn::Error>
|
|||
fn render_once_to_string(self, buf: &mut String) -> Result<(), sailfish::runtime::RenderError> {
|
||||
#include_bytes_seq;
|
||||
|
||||
use sailfish::runtime as sfrt;
|
||||
use sfrt::RenderInternal as _;
|
||||
use sailfish::runtime as __sf_rt;
|
||||
|
||||
static SIZE_HINT: sfrt::SizeHint = sfrt::SizeHint::new();
|
||||
static SIZE_HINT: __sf_rt::SizeHint = __sf_rt::SizeHint::new();
|
||||
|
||||
let mut _ctx = sfrt::Context {
|
||||
buf: sfrt::Buffer::from(std::mem::take(buf))
|
||||
};
|
||||
|
||||
let _size_hint = SIZE_HINT.get();
|
||||
_ctx.buf.reserve(_size_hint);
|
||||
let mut __sf_buf = __sf_rt::Buffer::from(std::mem::take(buf));
|
||||
__sf_buf.reserve(SIZE_HINT.get());
|
||||
|
||||
let #name { #field_names } = self;
|
||||
include!(#output_file_string);
|
||||
|
||||
SIZE_HINT.update(_ctx.buf.len());
|
||||
*buf = _ctx.buf.into_string();
|
||||
SIZE_HINT.update(__sf_buf.len());
|
||||
*buf = __sf_buf.into_string();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,88 @@
|
|||
use proc_macro2::Span;
|
||||
use quote::ToTokens;
|
||||
use syn::parse::{Parse, ParseStream as SynParseStream, Result as ParseResult};
|
||||
use syn::spanned::Spanned;
|
||||
use syn::{BinOp, Block, Expr};
|
||||
|
||||
use crate::error::*;
|
||||
use crate::parser::{ParseStream, Token, TokenKind};
|
||||
|
||||
use syn::Block;
|
||||
enum Filter {
|
||||
Ident(syn::Ident),
|
||||
Call(syn::ExprCall),
|
||||
}
|
||||
|
||||
impl Spanned for Filter {
|
||||
fn span(&self) -> Span {
|
||||
match *self {
|
||||
Filter::Ident(ref i) => i.span(),
|
||||
Filter::Call(ref c) => c.span(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct CodeBlock {
|
||||
#[allow(dead_code)]
|
||||
expr: Box<Expr>,
|
||||
filter: Option<Filter>,
|
||||
}
|
||||
|
||||
impl Parse for CodeBlock {
|
||||
fn parse(s: SynParseStream) -> ParseResult<Self> {
|
||||
let main = s.parse::<Expr>()?;
|
||||
|
||||
let code_block = match main {
|
||||
Expr::Binary(b) if matches!(b.op, BinOp::BitOr(_)) => {
|
||||
match *b.right {
|
||||
Expr::Call(c) => {
|
||||
if let Expr::Path(ref p) = *c.func {
|
||||
if p.path.get_ident().is_some() {
|
||||
CodeBlock {
|
||||
expr: b.left,
|
||||
filter: Some(Filter::Call(c)),
|
||||
}
|
||||
} else {
|
||||
return Err(syn::Error::new_spanned(
|
||||
p,
|
||||
"Invalid filter name",
|
||||
));
|
||||
}
|
||||
} else {
|
||||
// if function in right side is not a path, fallback to
|
||||
// normal evaluation block
|
||||
CodeBlock {
|
||||
expr: b.left,
|
||||
filter: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
Expr::Path(p) => {
|
||||
if let Some(i) = p.path.get_ident() {
|
||||
CodeBlock {
|
||||
expr: b.left,
|
||||
filter: Some(Filter::Ident(i.clone())),
|
||||
}
|
||||
} else {
|
||||
return Err(syn::Error::new_spanned(
|
||||
p,
|
||||
"Invalid filter name",
|
||||
));
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err(syn::Error::new_spanned(b, "Expected filter"));
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => CodeBlock {
|
||||
expr: Box::new(main),
|
||||
filter: None,
|
||||
},
|
||||
};
|
||||
|
||||
Ok(code_block)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SourceMapEntry {
|
||||
|
@ -38,48 +117,6 @@ impl SourceMap {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct TranslatedSource {
|
||||
pub ast: Block,
|
||||
pub source_map: SourceMap,
|
||||
}
|
||||
|
||||
// translate tokens into Rust code
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct Translator {
|
||||
escape: bool,
|
||||
}
|
||||
|
||||
impl Translator {
|
||||
#[inline]
|
||||
pub fn new() -> Self {
|
||||
Self { escape: true }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn escape(mut self, new: bool) -> Self {
|
||||
self.escape = new;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn translate<'a>(
|
||||
&self,
|
||||
token_iter: ParseStream<'a>,
|
||||
) -> Result<TranslatedSource, Error> {
|
||||
let original_source = token_iter.original_source;
|
||||
|
||||
let mut source = String::with_capacity(original_source.len());
|
||||
source.push_str("{\n");
|
||||
let mut ps = SourceBuilder {
|
||||
escape: self.escape,
|
||||
source,
|
||||
source_map: SourceMap::default(),
|
||||
};
|
||||
ps.feed_tokens(&*token_iter.into_vec()?);
|
||||
|
||||
Ok(ps.finalize()?)
|
||||
}
|
||||
}
|
||||
|
||||
struct SourceBuilder {
|
||||
escape: bool,
|
||||
source: String,
|
||||
|
@ -87,6 +124,18 @@ struct SourceBuilder {
|
|||
}
|
||||
|
||||
impl SourceBuilder {
|
||||
fn new(escape: bool) -> SourceBuilder {
|
||||
SourceBuilder {
|
||||
escape,
|
||||
source: String::from("{\n"),
|
||||
source_map: SourceMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn reserve(&mut self, additional: usize) {
|
||||
self.source.reserve(additional);
|
||||
}
|
||||
|
||||
fn write_token<'a>(&mut self, token: &Token<'a>) {
|
||||
let entry = SourceMapEntry {
|
||||
original: token.offset(),
|
||||
|
@ -97,45 +146,96 @@ impl SourceBuilder {
|
|||
self.source.push_str(token.as_str());
|
||||
}
|
||||
|
||||
fn write_code<'a>(&mut self, token: &Token<'a>) {
|
||||
fn write_code<'a>(&mut self, token: &Token<'a>) -> Result<(), Error> {
|
||||
// TODO: automatically add missing tokens (e.g. ';', '{')
|
||||
self.write_token(token);
|
||||
self.source.push_str("\n");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_text<'a>(&mut self, token: &Token<'a>) {
|
||||
fn write_text<'a>(&mut self, token: &Token<'a>) -> Result<(), Error> {
|
||||
use std::fmt::Write;
|
||||
|
||||
self.source.push_str("sfrt::render_text!(_ctx, ");
|
||||
|
||||
self.source.push_str("__sf_rt::render_text!(__sf_buf, ");
|
||||
// write text token with Debug::fmt
|
||||
write!(self.source, "{:?}", token.as_str()).unwrap();
|
||||
|
||||
self.source.push_str(");\n");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_buffered_code<'a>(&mut self, token: &Token<'a>, escape: bool) {
|
||||
fn write_buffered_code<'a>(
|
||||
&mut self,
|
||||
token: &Token<'a>,
|
||||
escape: bool,
|
||||
) -> Result<(), Error> {
|
||||
// parse and split off filter
|
||||
let code_block = syn::parse_str::<CodeBlock>(token.as_str()).map_err(|e| {
|
||||
let span = e.span();
|
||||
let mut err = make_error!(ErrorKind::RustSyntaxError(e));
|
||||
err.offset = into_offset(token.as_str(), span).map(|p| token.offset() + p);
|
||||
err
|
||||
})?;
|
||||
let method = if self.escape && escape {
|
||||
"render_escaped"
|
||||
} else {
|
||||
"render"
|
||||
};
|
||||
|
||||
self.source.push_str("sfrt::");
|
||||
self.source.push_str("__sf_rt::");
|
||||
self.source.push_str(method);
|
||||
self.source.push_str("!(_ctx, ");
|
||||
self.write_token(token);
|
||||
self.source.push_str("!(__sf_buf, ");
|
||||
|
||||
if let Some(filter) = code_block.filter {
|
||||
let expr_str = code_block.expr.into_token_stream().to_string();
|
||||
let (name, extra_args) = match filter {
|
||||
Filter::Ident(i) => (i.to_string(), None),
|
||||
Filter::Call(c) => (
|
||||
c.func.into_token_stream().to_string(),
|
||||
Some(c.args.into_token_stream().to_string()),
|
||||
),
|
||||
};
|
||||
|
||||
self.source.push_str("sailfish::runtime::filter::");
|
||||
self.source.push_str(&*name);
|
||||
self.source.push_str("(");
|
||||
|
||||
// arguments to filter function
|
||||
{
|
||||
self.source.push_str("&(");
|
||||
let entry = SourceMapEntry {
|
||||
original: token.offset(),
|
||||
new: self.source.len(),
|
||||
length: expr_str.len(),
|
||||
};
|
||||
self.source_map.entries.push(entry);
|
||||
self.source.push_str(&expr_str);
|
||||
self.source.push_str(")");
|
||||
|
||||
if let Some(extra_args) = extra_args {
|
||||
self.source.push_str(", ");
|
||||
self.source.push_str(&*extra_args);
|
||||
}
|
||||
}
|
||||
|
||||
self.source.push_str(")");
|
||||
} else {
|
||||
self.write_token(token);
|
||||
}
|
||||
|
||||
self.source.push_str(");\n");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn feed_tokens(&mut self, token_iter: &[Token]) {
|
||||
let mut it = token_iter.iter().peekable();
|
||||
pub fn feed_tokens<'a>(&mut self, token_iter: ParseStream<'a>) -> Result<(), Error> {
|
||||
let mut it = token_iter.peekable();
|
||||
while let Some(token) = it.next() {
|
||||
let token = token?;
|
||||
match token.kind() {
|
||||
TokenKind::Code => self.write_code(&token),
|
||||
TokenKind::Code => self.write_code(&token)?,
|
||||
TokenKind::Comment => {}
|
||||
TokenKind::BufferedCode { escape } => {
|
||||
self.write_buffered_code(&token, escape)
|
||||
self.write_buffered_code(&token, escape)?
|
||||
}
|
||||
TokenKind::Text => {
|
||||
// concatenate repeated text token
|
||||
|
@ -143,7 +243,7 @@ impl SourceBuilder {
|
|||
let mut concatenated = String::new();
|
||||
concatenated.push_str(token.as_str());
|
||||
|
||||
while let Some(next_token) = it.peek() {
|
||||
while let Some(&Ok(ref next_token)) = it.peek() {
|
||||
match next_token.kind() {
|
||||
TokenKind::Text => {
|
||||
concatenated.push_str(next_token.as_str());
|
||||
|
@ -157,10 +257,12 @@ impl SourceBuilder {
|
|||
}
|
||||
|
||||
let new_token = Token::new(&*concatenated, offset, TokenKind::Text);
|
||||
self.write_text(&new_token);
|
||||
self.write_text(&new_token)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn finalize(mut self) -> Result<TranslatedSource, Error> {
|
||||
|
@ -202,6 +304,43 @@ fn into_offset(source: &str, span: Span) -> Option<usize> {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct TranslatedSource {
|
||||
pub ast: Block,
|
||||
pub source_map: SourceMap,
|
||||
}
|
||||
|
||||
// translate tokens into Rust code
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct Translator {
|
||||
escape: bool,
|
||||
}
|
||||
|
||||
impl Translator {
|
||||
#[inline]
|
||||
pub fn new() -> Self {
|
||||
Self { escape: true }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn escape(mut self, new: bool) -> Self {
|
||||
self.escape = new;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn translate<'a>(
|
||||
&self,
|
||||
token_iter: ParseStream<'a>,
|
||||
) -> Result<TranslatedSource, Error> {
|
||||
let original_source = token_iter.original_source;
|
||||
|
||||
let mut ps = SourceBuilder::new(self.escape);
|
||||
ps.reserve(original_source.len());
|
||||
ps.feed_tokens(token_iter)?;
|
||||
|
||||
Ok(ps.finalize()?)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
@ -209,7 +348,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn translate() {
|
||||
let src = "<% pub fn sample() { %> <%% <%=//%>\n%><% } %>";
|
||||
let src = "<% pub fn sample() { %> <%% <%=//%>\n1%><% } %>";
|
||||
let lexer = Parser::new();
|
||||
let token_iter = lexer.parse(src);
|
||||
let mut ps = SourceBuilder {
|
||||
|
@ -217,8 +356,7 @@ mod tests {
|
|||
source: String::with_capacity(token_iter.original_source.len()),
|
||||
source_map: SourceMap::default(),
|
||||
};
|
||||
ps.feed_tokens(&token_iter.clone().into_vec().unwrap());
|
||||
eprintln!("{}", ps.source);
|
||||
ps.feed_tokens(token_iter.clone()).unwrap();
|
||||
Translator::new().translate(token_iter).unwrap();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "sailfish-macros"
|
||||
version = "0.1.3"
|
||||
version = "0.2.0"
|
||||
authors = ["Ryohei Machida <orcinus4627@gmail.com>"]
|
||||
description = "Really fast, intuitive template engine for Rust"
|
||||
homepage = "https://github.com/Kogia-sima/sailfish"
|
||||
|
@ -29,6 +29,6 @@ proc-macro2 = "1.0.11"
|
|||
|
||||
[dependencies.sailfish-compiler]
|
||||
path = "../sailfish-compiler"
|
||||
version = "0.1.3"
|
||||
version = "0.2.0"
|
||||
default-features = false
|
||||
features = ["procmacro"]
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
/out/
|
|
@ -0,0 +1,258 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
[[package]]
|
||||
name = "afl"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2797f92fb146a37560af914b5d5328f8330d6a39b6eaf00f5b184ac73c0c81e7"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"clap",
|
||||
"libc",
|
||||
"rustc_version",
|
||||
"xdg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ansi_term"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.58"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9a06fb2e53271d7c279ec1efea6ab691c35a2ae67ec0d91d7acec0caf13b518"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "2.33.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bdfa80d47f954d53a35a64987ca1422f495b8d6483c0fe9f7117b36c2a792129"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
"atty",
|
||||
"bitflags",
|
||||
"strsim",
|
||||
"textwrap",
|
||||
"unicode-width",
|
||||
"vec_map",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fuzzing-tests"
|
||||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"afl",
|
||||
"sailfish",
|
||||
"sailfish-compiler",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3deed196b6e7f9e44a2ae8d94225d80302d81208b1bb673fd21fe634645c85a9"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoap"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e804a5759b475f44377998918a7e3be9da3767056f5e77751ef7803893db0e9"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.72"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9f8082297d534141b30c8d39e9b1773713ab50fdbe4ff30f750d063b3bfd701"
|
||||
|
||||
[[package]]
|
||||
name = "linked-hash-map"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa"
|
||||
dependencies = [
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
|
||||
dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
|
||||
|
||||
[[package]]
|
||||
name = "sailfish"
|
||||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"itoap",
|
||||
"ryu",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sailfish-compiler"
|
||||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"yaml-rust",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
|
||||
dependencies = [
|
||||
"semver-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver-parser"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "936cae2873c940d92e697597c5eee105fb570cd5689c695806f672883653349b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
|
||||
dependencies = [
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
|
||||
|
||||
[[package]]
|
||||
name = "vec_map"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "xdg"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d089681aa106a86fade1b0128fb5daf07d5867a509ab036d99988dec80429a57"
|
||||
|
||||
[[package]]
|
||||
name = "yaml-rust"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39f0c922f1a334134dc2f7a8b67dc5d25f0735263feec974345ff706bcf20b0d"
|
||||
dependencies = [
|
||||
"linked-hash-map",
|
||||
]
|
|
@ -0,0 +1,13 @@
|
|||
[package]
|
||||
name = "fuzzing-tests"
|
||||
version = "0.2.0"
|
||||
authors = ["Ryohei Machida <orcinus4627@gmail.com>"]
|
||||
edition = "2018"
|
||||
publish = false
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
afl = "0.8.0"
|
||||
sailfish = { path = "../../sailfish" }
|
||||
sailfish-compiler = { path = "../../sailfish-compiler" }
|
|
@ -0,0 +1 @@
|
|||
<h1><%= message %></h1>
|
|
@ -0,0 +1,9 @@
|
|||
<table>
|
||||
<% for i in 1..=9 %>
|
||||
<tr>
|
||||
<% for j in 1..=9 %>
|
||||
<td><%= i * j %></td>
|
||||
<% } %>
|
||||
</td>
|
||||
<% } %>
|
||||
</table>
|
|
@ -0,0 +1 @@
|
|||
<a href="http://example.com/">
|
|
@ -0,0 +1,5 @@
|
|||
for row in &table {
|
||||
for col in row {
|
||||
println!("{}", *col);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
#[macro_use]
|
||||
extern crate afl;
|
||||
|
||||
use sailfish_compiler::Compiler;
|
||||
|
||||
fn main() {
|
||||
fuzz!(|data: &[u8]| {
|
||||
// HTML escaping
|
||||
if let Ok(feed) = std::str::from_utf8(data) {
|
||||
let compiler = Compiler::default();
|
||||
let _ = compiler.compile_str(feed);
|
||||
}
|
||||
});
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
#[macro_use]
|
||||
extern crate afl;
|
||||
|
||||
use sailfish::runtime as sf;
|
||||
use sf::Render;
|
||||
|
||||
fn main() {
|
||||
fuzz!(|data: &[u8]| {
|
||||
// HTML escaping
|
||||
if let Ok(feed) = std::str::from_utf8(data) {
|
||||
let mut buf = sf::Buffer::new();
|
||||
let _ = feed.render_escaped(&mut buf);
|
||||
}
|
||||
});
|
||||
}
|
|
@ -1,13 +1,14 @@
|
|||
[package]
|
||||
name = "integration-tests"
|
||||
version = "0.1.3"
|
||||
version = "0.2.0"
|
||||
authors = ["Kogia-sima <orcinus4627@gmail.com>"]
|
||||
edition = "2018"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
sailfish = { path = "../sailfish" }
|
||||
sailfish-macros = { path = "../sailfish-macros" }
|
||||
sailfish = { path = "../../sailfish" }
|
||||
sailfish-macros = { path = "../../sailfish-macros" }
|
||||
sailfish-compiler = { path = "../../sailfish-compiler" }
|
||||
|
||||
[dev-dependencies]
|
||||
trybuild = "1.0.28"
|
|
@ -0,0 +1,6 @@
|
|||
template_dir: "../templates"
|
||||
escape: true
|
||||
delimiter: "%"
|
||||
|
||||
optimization:
|
||||
rm_whitespace: false
|
|
@ -0,0 +1,4 @@
|
|||
disp: hello
|
||||
dbg: "hello"
|
||||
disp escaped: hello
|
||||
dbg escaped: "hello"
|
|
@ -0,0 +1,4 @@
|
|||
disp: <%- message | disp %>
|
||||
dbg: <%- message | dbg %>
|
||||
disp escaped: <%= message | disp %>
|
||||
dbg escaped: <%= message | dbg %>
|
|
@ -0,0 +1 @@
|
|||
00010203040506070809
|
|
@ -0,0 +1 @@
|
|||
<% for i in 0..10 { %><%= format!("{:02}", i) %><% } %>
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"name": <%- name | dbg %>,
|
||||
"value": <%= value %>
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
use sailfish_compiler::Config;
|
||||
use std::path::Path;
|
||||
|
||||
#[test]
|
||||
fn read_config() {
|
||||
let path = Path::new(env!("CARGO_MANIFEST_DIR")).join("config");
|
||||
let config = Config::search_file_and_read(&*path).unwrap();
|
||||
|
||||
assert_eq!(config.delimiter, '%');
|
||||
assert_eq!(config.escape, true);
|
||||
assert_eq!(config.rm_whitespace, false);
|
||||
assert_eq!(config.template_dirs.len(), 1);
|
||||
}
|
|
@ -191,6 +191,26 @@ fn test_rust_macro() {
|
|||
assert_render("rust_macro", RustMacro { value: Some(10) });
|
||||
}
|
||||
|
||||
#[derive(TemplateOnce)]
|
||||
#[template(path = "formatting.stpl", escape = false)]
|
||||
struct Formatting;
|
||||
|
||||
#[test]
|
||||
fn test_formatting() {
|
||||
assert_render("formatting", Formatting);
|
||||
}
|
||||
|
||||
#[derive(TemplateOnce)]
|
||||
#[template(path = "filter.stpl")]
|
||||
struct Filter<'a> {
|
||||
message: &'a str,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filter() {
|
||||
assert_render("filter", Filter { message: "hello" });
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
mod unix {
|
||||
use super::*;
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "sailfish"
|
||||
version = "0.1.3"
|
||||
version = "0.2.0"
|
||||
authors = ["Ryohei Machida <orcinus4627@gmail.com>"]
|
||||
description = "Really fast, intuitive template engine for Rust"
|
||||
homepage = "https://github.com/Kogia-sima/sailfish"
|
||||
|
|
|
@ -68,15 +68,21 @@ impl Buffer {
|
|||
self.capacity
|
||||
}
|
||||
|
||||
/// Force the length of buffer to `new_len`
|
||||
#[inline]
|
||||
#[doc(hidden)]
|
||||
pub unsafe fn _set_len(&mut self, new_len: usize) {
|
||||
self.len = new_len;
|
||||
}
|
||||
|
||||
/// Increase the length of buffer by `additional` bytes
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// - `new_len` must be less than or equal to `capacity()`
|
||||
/// - The elements at `old_len..new_len` must be initialized
|
||||
/// - `additional` must be less than or equal to `capacity() - len()`
|
||||
/// - The elements at `old_len..old_len + additional` must be initialized
|
||||
#[inline]
|
||||
pub unsafe fn set_len(&mut self, new_len: usize) {
|
||||
self.len = new_len;
|
||||
pub unsafe fn advance(&mut self, additional: usize) {
|
||||
self.len += additional;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -86,9 +92,10 @@ impl Buffer {
|
|||
|
||||
#[inline]
|
||||
pub fn reserve(&mut self, size: usize) {
|
||||
if unlikely!(self.len + size > self.capacity) {
|
||||
self.reserve_internal(size);
|
||||
if size <= self.capacity.wrapping_sub(self.len) {
|
||||
return;
|
||||
}
|
||||
self.reserve_internal(size);
|
||||
debug_assert!(self.len + size <= self.capacity);
|
||||
}
|
||||
|
||||
|
@ -110,7 +117,9 @@ impl Buffer {
|
|||
#[inline]
|
||||
pub fn push_str(&mut self, data: &str) {
|
||||
let size = data.len();
|
||||
self.reserve(size);
|
||||
if unlikely!(size > self.capacity.wrapping_sub(self.len)) {
|
||||
self.reserve_internal(size);
|
||||
}
|
||||
unsafe {
|
||||
let p = self.data.add(self.len);
|
||||
std::ptr::copy_nonoverlapping(data.as_ptr(), p, size);
|
||||
|
@ -129,9 +138,9 @@ impl Buffer {
|
|||
#[cold]
|
||||
fn reserve_internal(&mut self, size: usize) {
|
||||
unsafe {
|
||||
let new_capacity = std::cmp::max(self.capacity * 2, self.len + size);
|
||||
let new_capacity = std::cmp::max(self.capacity * 2, self.capacity + size);
|
||||
debug_assert!(new_capacity > self.capacity);
|
||||
self.data = safe_realloc(self.data, self.capacity, new_capacity);
|
||||
self.data = safe_realloc(self.data, self.capacity, new_capacity, size);
|
||||
self.capacity = new_capacity;
|
||||
}
|
||||
debug_assert!(!self.data.is_null());
|
||||
|
@ -140,7 +149,13 @@ impl Buffer {
|
|||
}
|
||||
|
||||
#[cold]
|
||||
unsafe fn safe_realloc(ptr: *mut u8, capacity: usize, new_capacity: usize) -> *mut u8 {
|
||||
unsafe fn safe_realloc(
|
||||
ptr: *mut u8,
|
||||
capacity: usize,
|
||||
new_capacity: usize,
|
||||
size: usize,
|
||||
) -> *mut u8 {
|
||||
assert!(size <= std::usize::MAX / 2, "capacity is too large");
|
||||
assert!(new_capacity <= std::usize::MAX / 2, "capacity is too large");
|
||||
let data = if unlikely!(capacity == 0) {
|
||||
let new_layout = Layout::from_size_align_unchecked(new_capacity, 1);
|
||||
|
@ -303,11 +318,34 @@ mod tests {
|
|||
assert_eq!(buf.as_str(), "");
|
||||
|
||||
// into empty string
|
||||
let buf = Buffer::new();
|
||||
let buf = Buffer::default();
|
||||
let mut s = buf.into_string();
|
||||
assert_eq!(s, "");
|
||||
|
||||
s.push_str("apple");
|
||||
assert_eq!(s, "apple");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clone() {
|
||||
use std::fmt::Write;
|
||||
|
||||
let mut s1 = Buffer::with_capacity(0);
|
||||
let mut s2 = s1.clone();
|
||||
|
||||
s1.push('a');
|
||||
s2.push_str("b");
|
||||
|
||||
assert_eq!(s1.as_str(), "a");
|
||||
assert_eq!(s2.as_str(), "b");
|
||||
|
||||
let mut s1 = Buffer::from("foo");
|
||||
let mut s2 = s1.clone();
|
||||
|
||||
s1 = s1 + "bar";
|
||||
write!(s2, "baz").unwrap();
|
||||
|
||||
assert_eq!(s1.as_str(), "foobar");
|
||||
assert_eq!(s2.as_str(), "foobaz");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,15 +40,12 @@ fn contains_key(x: usize) -> bool {
|
|||
|
||||
#[inline]
|
||||
pub unsafe fn escape(feed: &str, buffer: &mut Buffer) {
|
||||
debug_assert!(feed.len() >= 16);
|
||||
|
||||
let len = feed.len();
|
||||
let mut start_ptr = feed.as_ptr();
|
||||
let end_ptr = start_ptr.add(len);
|
||||
|
||||
if feed.len() < USIZE_BYTES {
|
||||
naive::escape(buffer, start_ptr, start_ptr, end_ptr);
|
||||
return;
|
||||
}
|
||||
|
||||
let mut ptr = start_ptr;
|
||||
let aligned_ptr = ptr.add(USIZE_BYTES - (start_ptr as usize & USIZE_ALIGN));
|
||||
debug_assert_eq!(aligned_ptr as usize % USIZE_BYTES, 0);
|
||||
|
|
|
@ -50,10 +50,6 @@ fn escape(feed: &str, buf: &mut Buffer) {
|
|||
unsafe { fun(feed, buf) };
|
||||
}
|
||||
|
||||
/// Change the default escape function
|
||||
#[doc(hidden)]
|
||||
pub fn register_escape_fn(_fun: fn(&str, &mut Buffer)) {}
|
||||
|
||||
/// write the escaped contents into `Buffer`
|
||||
#[cfg_attr(feature = "perf-inline", inline)]
|
||||
pub fn escape_to_buf(feed: &str, buf: &mut Buffer) {
|
||||
|
@ -61,7 +57,7 @@ pub fn escape_to_buf(feed: &str, buf: &mut Buffer) {
|
|||
if feed.len() < 16 {
|
||||
buf.reserve(feed.len() * 6);
|
||||
let l = naive::escape_small(feed, buf.as_mut_ptr().add(buf.len()));
|
||||
buf.set_len(buf.len() + l);
|
||||
buf.advance(l);
|
||||
} else {
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
{
|
||||
|
@ -119,6 +115,7 @@ mod tests {
|
|||
#[test]
|
||||
fn noescape() {
|
||||
assert_eq!(escape(""), "");
|
||||
assert_eq!(escape("1234567890"), "1234567890");
|
||||
assert_eq!(
|
||||
escape("abcdefghijklmnopqrstrvwxyz"),
|
||||
"abcdefghijklmnopqrstrvwxyz"
|
||||
|
@ -159,12 +156,12 @@ mod tests {
|
|||
const ASCII_CHARS: &'static [u8] = br##"abcdefghijklmnopqrstuvwxyz0123456789-^\@[;:],./\!"#$%&'()~=~|`{+*}<>?_"##;
|
||||
let mut state = 88172645463325252u64;
|
||||
let mut data = Vec::with_capacity(100);
|
||||
let mut buf1 = Buffer::new();
|
||||
let mut buf2 = Buffer::new();
|
||||
let mut buf3 = Buffer::new();
|
||||
|
||||
for len in 0..100 {
|
||||
for _ in 0..10 {
|
||||
let mut buf_naive = Buffer::new();
|
||||
let mut buf = Buffer::new();
|
||||
|
||||
for len in 16..100 {
|
||||
for _ in 0..5 {
|
||||
data.clear();
|
||||
for _ in 0..len {
|
||||
// xorshift
|
||||
|
@ -178,23 +175,33 @@ mod tests {
|
|||
|
||||
let s = unsafe { std::str::from_utf8_unchecked(&*data) };
|
||||
|
||||
buf1.clear();
|
||||
buf2.clear();
|
||||
buf3.clear();
|
||||
|
||||
unsafe {
|
||||
escape_to_buf(s, &mut buf1);
|
||||
fallback::escape(s, &mut buf2);
|
||||
naive::escape(
|
||||
&mut buf3,
|
||||
&mut buf_naive,
|
||||
s.as_ptr(),
|
||||
s.as_ptr(),
|
||||
s.as_ptr().add(s.len()),
|
||||
);
|
||||
|
||||
dbg!(s);
|
||||
fallback::escape(s, &mut buf);
|
||||
assert_eq!(buf.as_str(), buf_naive.as_str());
|
||||
buf.clear();
|
||||
|
||||
if is_x86_feature_detected!("sse2") {
|
||||
sse2::escape(s, &mut buf);
|
||||
assert_eq!(buf.as_str(), buf_naive.as_str());
|
||||
buf.clear();
|
||||
}
|
||||
|
||||
if is_x86_feature_detected!("avx2") {
|
||||
avx2::escape(s, &mut buf);
|
||||
assert_eq!(buf.as_str(), buf_naive.as_str());
|
||||
buf.clear();
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(buf1.as_str(), buf3.as_str());
|
||||
assert_eq!(buf2.as_str(), buf3.as_str());
|
||||
buf_naive.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
// TODO: performance improvement
|
||||
|
||||
use std::fmt;
|
||||
|
||||
use super::{Buffer, Render, RenderError};
|
||||
|
||||
pub struct Display<'a, T>(&'a T);
|
||||
|
||||
impl<'a, T: fmt::Display> Render for Display<'a, T> {
|
||||
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
use fmt::Write;
|
||||
|
||||
write!(b, "{}", self.0).map_err(|e| RenderError::from(e))
|
||||
}
|
||||
}
|
||||
|
||||
/// render using `std::fmt::Display` trait
|
||||
#[inline]
|
||||
pub fn disp<T: fmt::Display>(expr: &T) -> Display<T> {
|
||||
Display(expr)
|
||||
}
|
||||
|
||||
pub struct Debug<'a, T>(&'a T);
|
||||
|
||||
impl<'a, T: fmt::Debug> Render for Debug<'a, T> {
|
||||
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
use fmt::Write;
|
||||
|
||||
write!(b, "{:?}", self.0).map_err(|e| RenderError::from(e))
|
||||
}
|
||||
}
|
||||
|
||||
/// render using `std::fmt::Debug` trait
|
||||
#[inline]
|
||||
pub fn dbg<T: fmt::Debug>(expr: &T) -> Debug<T> {
|
||||
Debug(expr)
|
||||
}
|
||||
|
||||
pub struct Upper<'a, T>(&'a T);
|
||||
|
||||
impl<'a, T: Render> Render for Upper<'a, T> {
|
||||
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
let old_len = b.len();
|
||||
self.0.render(b)?;
|
||||
|
||||
let s = b.as_str()[old_len..].to_uppercase();
|
||||
unsafe { b._set_len(old_len) };
|
||||
b.push_str(&*s);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// convert the rendered contents to uppercase
|
||||
#[inline]
|
||||
pub fn upper<T: Render>(expr: &T) -> Upper<T> {
|
||||
Upper(expr)
|
||||
}
|
||||
|
||||
pub struct Lower<'a, T>(&'a T);
|
||||
|
||||
impl<'a, T: Render> Render for Lower<'a, T> {
|
||||
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
let old_len = b.len();
|
||||
self.0.render(b)?;
|
||||
|
||||
let s = b.as_str()[old_len..].to_lowercase();
|
||||
unsafe { b._set_len(old_len) };
|
||||
b.push_str(&*s);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
let old_len = b.len();
|
||||
self.0.render_escaped(b)?;
|
||||
|
||||
let s = b.as_str()[old_len..].to_lowercase();
|
||||
unsafe { b._set_len(old_len) };
|
||||
b.push_str(&*s);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// convert the rendered contents to lowercase
|
||||
#[inline]
|
||||
pub fn lower<T: Render>(expr: &T) -> Lower<T> {
|
||||
Lower(expr)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn case() {
|
||||
let mut buf = Buffer::new();
|
||||
upper(&"hElLO, WOrLd!").render(&mut buf).unwrap();
|
||||
assert_eq!(buf.as_str(), "HELLO, WORLD!");
|
||||
|
||||
buf.clear();
|
||||
lower(&"hElLO, WOrLd!").render(&mut buf).unwrap();
|
||||
assert_eq!(buf.as_str(), "hello, world!");
|
||||
|
||||
buf.clear();
|
||||
lower(&"<h1>TITLE</h1>").render_escaped(&mut buf).unwrap();
|
||||
assert_eq!(buf.as_str(), "<h1>title</h1>");
|
||||
}
|
||||
}
|
|
@ -1,29 +1,29 @@
|
|||
#[macro_export]
|
||||
#[doc(hidden)]
|
||||
macro_rules! render {
|
||||
($ctx:ident, $value:expr) => {
|
||||
(&($value))._sf_r_internal(&mut $ctx.buf)?
|
||||
($buf:ident, $value:expr) => {
|
||||
$crate::runtime::Render::render(&($value), &mut $buf)?
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
#[doc(hidden)]
|
||||
macro_rules! render_escaped {
|
||||
($ctx:ident, $value:expr) => {
|
||||
(&($value))._sf_re_internal(&mut $ctx.buf)?
|
||||
($buf:ident, $value:expr) => {
|
||||
$crate::runtime::Render::render_escaped(&($value), &mut $buf)?
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
#[doc(hidden)]
|
||||
macro_rules! render_text {
|
||||
($ctx:ident, $value:expr) => {
|
||||
$ctx.buf.push_str($value)
|
||||
($buf:ident, $value:expr) => {
|
||||
$buf.push_str($value)
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
#[doc(hidden)]
|
||||
macro_rules! render_noop {
|
||||
($ctx:ident, $value:expr) => {};
|
||||
($buf:ident, $value:expr) => {};
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ mod utils;
|
|||
|
||||
mod buffer;
|
||||
pub mod escape;
|
||||
pub mod filter;
|
||||
mod macros;
|
||||
mod render;
|
||||
mod size_hint;
|
||||
|
@ -69,19 +70,6 @@ impl From<fmt::Error> for RenderError {
|
|||
|
||||
pub type RenderResult = Result<String, RenderError>;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct Context {
|
||||
#[doc(hidden)]
|
||||
pub buf: Buffer,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
#[inline]
|
||||
pub fn into_result(self) -> RenderResult {
|
||||
Ok(self.buf.into_string())
|
||||
}
|
||||
}
|
||||
|
||||
// #[inline(never)]
|
||||
// pub fn _instantiate(table: Vec<Vec<usize>>) -> String {
|
||||
// let mut buffer = Buffer::with_capacity(130000);
|
||||
|
@ -92,9 +80,25 @@ impl Context {
|
|||
// let _ = (&r2).render(&mut buffer);
|
||||
// buffer.push_str("</td><td>");
|
||||
// }
|
||||
// unsafe { buffer.set_len(buffer.len() - 4) }
|
||||
// unsafe { buffer._set_len(buffer.len() - 4) }
|
||||
// buffer.push_str("</tr>");
|
||||
// }
|
||||
// buffer.push_str("</table>");
|
||||
// buffer.into_string()
|
||||
// }
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::error::Error;
|
||||
|
||||
#[test]
|
||||
fn render_error() {
|
||||
let err = RenderError::new("custom error");
|
||||
assert!(err.source().is_none());
|
||||
assert_eq!(format!("{}", err), "custom error");
|
||||
|
||||
let err = RenderError::from(std::fmt::Error::default());
|
||||
assert!(err.source().is_some());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,12 @@
|
|||
use std::path::Path;
|
||||
use std::borrow::Cow;
|
||||
use std::cell::{Ref, RefMut};
|
||||
use std::num::{
|
||||
NonZeroI128, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, NonZeroIsize,
|
||||
NonZeroU128, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize, Wrapping,
|
||||
};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::rc::Rc;
|
||||
use std::sync::{Arc, MutexGuard, RwLockReadGuard, RwLockWriteGuard};
|
||||
|
||||
use super::buffer::Buffer;
|
||||
use super::{escape, RenderError};
|
||||
|
@ -59,16 +67,30 @@ pub trait Render {
|
|||
// }
|
||||
// }
|
||||
|
||||
impl Render for str {
|
||||
impl Render for String {
|
||||
#[inline]
|
||||
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
b.push_str(self);
|
||||
b.push_str(&**self);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
escape::escape_to_buf(self, b);
|
||||
escape::escape_to_buf(&**self, b);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for &str {
|
||||
#[inline]
|
||||
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
b.push_str(*self);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
escape::escape_to_buf(*self, b);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -94,6 +116,21 @@ impl Render for char {
|
|||
}
|
||||
}
|
||||
|
||||
impl Render for PathBuf {
|
||||
#[inline]
|
||||
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
// TODO: speed up on Windows using OsStrExt
|
||||
b.push_str(&*self.to_string_lossy());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
escape::escape_to_buf(&*self.to_string_lossy(), b);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for Path {
|
||||
#[inline]
|
||||
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
|
@ -160,7 +197,7 @@ macro_rules! render_int {
|
|||
unsafe {
|
||||
let ptr = b.as_mut_ptr().add(b.len());
|
||||
let l = itoap::write_to_ptr(ptr, *self);
|
||||
b.set_len(b.len() + l);
|
||||
b.advance(l);
|
||||
}
|
||||
debug_assert!(b.len() <= b.capacity());
|
||||
Ok(())
|
||||
|
@ -186,7 +223,7 @@ impl Render for f32 {
|
|||
b.reserve(16);
|
||||
let ptr = b.as_mut_ptr().add(b.len());
|
||||
let l = ryu::raw::format32(*self, ptr);
|
||||
b.set_len(b.len() + l);
|
||||
b.advance(l);
|
||||
debug_assert!(b.len() <= b.capacity());
|
||||
}
|
||||
} else if self.is_nan() {
|
||||
|
@ -215,7 +252,7 @@ impl Render for f64 {
|
|||
b.reserve(24);
|
||||
let ptr = b.as_mut_ptr().add(b.len());
|
||||
let l = ryu::raw::format64(*self, ptr);
|
||||
b.set_len(b.len() + l);
|
||||
b.advance(l);
|
||||
debug_assert!(b.len() <= b.capacity());
|
||||
}
|
||||
} else if self.is_nan() {
|
||||
|
@ -236,22 +273,80 @@ impl Render for f64 {
|
|||
}
|
||||
}
|
||||
|
||||
// private trait for avoiding method name collision in render* macros
|
||||
#[doc(hidden)]
|
||||
pub trait RenderInternal {
|
||||
fn _sf_r_internal(&self, b: &mut Buffer) -> Result<(), RenderError>;
|
||||
fn _sf_re_internal(&self, b: &mut Buffer) -> Result<(), RenderError>;
|
||||
macro_rules! render_deref {
|
||||
(
|
||||
$(#[doc = $doc:tt])*
|
||||
[$($bounds:tt)+] $($desc:tt)+
|
||||
) => {
|
||||
$(#[doc = $doc])*
|
||||
impl <$($bounds)+> Render for $($desc)+ {
|
||||
#[inline]
|
||||
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
(**self).render(b)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
(**self).render_escaped(b)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl<T: Render + ?Sized> RenderInternal for T {
|
||||
render_deref!(['a, T: Render + ?Sized] &'a T);
|
||||
render_deref!(['a, T: Render + ?Sized] &'a mut 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>);
|
||||
render_deref!(['a, T: Render + ?Sized] Ref<'a, T>);
|
||||
render_deref!(['a, T: Render + ?Sized] RefMut<'a, T>);
|
||||
render_deref!(['a, T: Render + ?Sized] MutexGuard<'a, T>);
|
||||
render_deref!(['a, T: Render + ?Sized] RwLockReadGuard<'a, T>);
|
||||
render_deref!(['a, T: Render + ?Sized] RwLockWriteGuard<'a, T>);
|
||||
|
||||
macro_rules! render_nonzero {
|
||||
($($type:ty,)*) => {
|
||||
$(
|
||||
impl Render for $type {
|
||||
#[inline]
|
||||
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
self.get().render(b)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
self.get().render_escaped(b)
|
||||
}
|
||||
}
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
render_nonzero!(
|
||||
NonZeroI8,
|
||||
NonZeroI16,
|
||||
NonZeroI32,
|
||||
NonZeroI64,
|
||||
NonZeroI128,
|
||||
NonZeroIsize,
|
||||
NonZeroU8,
|
||||
NonZeroU16,
|
||||
NonZeroU32,
|
||||
NonZeroU64,
|
||||
NonZeroU128,
|
||||
NonZeroUsize,
|
||||
);
|
||||
|
||||
impl<T: Render> Render for Wrapping<T> {
|
||||
#[inline]
|
||||
fn _sf_r_internal(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
self.render(b)
|
||||
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
self.0.render(b)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn _sf_re_internal(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
self.render_escaped(b)
|
||||
fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
self.0.render_escaped(b)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -262,34 +357,33 @@ mod tests {
|
|||
#[test]
|
||||
fn receiver_coercion() {
|
||||
let mut b = Buffer::new();
|
||||
(&1)._sf_r_internal(&mut b).unwrap();
|
||||
(&&1)._sf_r_internal(&mut b).unwrap();
|
||||
(&&&1)._sf_r_internal(&mut b).unwrap();
|
||||
(&&&&1)._sf_r_internal(&mut b).unwrap();
|
||||
Render::render(&1, &mut b).unwrap();
|
||||
Render::render(&&1, &mut b).unwrap();
|
||||
Render::render(&&&1, &mut b).unwrap();
|
||||
Render::render(&&&&1, &mut b).unwrap();
|
||||
assert_eq!(b.as_str(), "1111");
|
||||
b.clear();
|
||||
|
||||
let v = 2.0;
|
||||
(&v)._sf_r_internal(&mut b).unwrap();
|
||||
(&&v)._sf_r_internal(&mut b).unwrap();
|
||||
(&&&v)._sf_r_internal(&mut b).unwrap();
|
||||
(&&&&v)._sf_r_internal(&mut b).unwrap();
|
||||
assert_eq!(b.as_str(), "2.02.02.02.0");
|
||||
Render::render(&true, &mut b).unwrap();
|
||||
Render::render(&&false, &mut b).unwrap();
|
||||
Render::render(&&&true, &mut b).unwrap();
|
||||
Render::render(&&&&false, &mut b).unwrap();
|
||||
assert_eq!(b.as_str(), "truefalsetruefalse");
|
||||
b.clear();
|
||||
|
||||
let s = "apple";
|
||||
(&*s)._sf_re_internal(&mut b).unwrap();
|
||||
(&s)._sf_re_internal(&mut b).unwrap();
|
||||
(&&s)._sf_re_internal(&mut b).unwrap();
|
||||
(&&&s)._sf_re_internal(&mut b).unwrap();
|
||||
(&&&&s)._sf_re_internal(&mut b).unwrap();
|
||||
Render::render_escaped(&s, &mut b).unwrap();
|
||||
Render::render_escaped(&s, &mut b).unwrap();
|
||||
Render::render_escaped(&&s, &mut b).unwrap();
|
||||
Render::render_escaped(&&&s, &mut b).unwrap();
|
||||
Render::render_escaped(&&&&s, &mut b).unwrap();
|
||||
assert_eq!(b.as_str(), "appleappleappleappleapple");
|
||||
b.clear();
|
||||
|
||||
(&'c')._sf_re_internal(&mut b).unwrap();
|
||||
(&&'<')._sf_re_internal(&mut b).unwrap();
|
||||
(&&&'&')._sf_re_internal(&mut b).unwrap();
|
||||
(&&&&' ')._sf_re_internal(&mut b).unwrap();
|
||||
Render::render_escaped(&'c', &mut b).unwrap();
|
||||
Render::render_escaped(&&'<', &mut b).unwrap();
|
||||
Render::render_escaped(&&&'&', &mut b).unwrap();
|
||||
Render::render_escaped(&&&&' ', &mut b).unwrap();
|
||||
assert_eq!(b.as_str(), "c<& ");
|
||||
b.clear();
|
||||
}
|
||||
|
@ -300,11 +394,29 @@ mod tests {
|
|||
use std::rc::Rc;
|
||||
|
||||
let mut b = Buffer::new();
|
||||
(&String::from("a"))._sf_r_internal(&mut b).unwrap();
|
||||
(&&PathBuf::from("b"))._sf_r_internal(&mut b).unwrap();
|
||||
(&Rc::new(4u32))._sf_re_internal(&mut b).unwrap();
|
||||
(&Rc::new(2.3f32))._sf_re_internal(&mut b).unwrap();
|
||||
Render::render(&String::from("a"), &mut b).unwrap();
|
||||
Render::render(&&PathBuf::from("b"), &mut b).unwrap();
|
||||
Render::render_escaped(&Rc::new(4u32), &mut b).unwrap();
|
||||
Render::render_escaped(&Rc::new(2.3f32), &mut b).unwrap();
|
||||
|
||||
assert_eq!(b.as_str(), "ab42.3");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn float() {
|
||||
let mut b = Buffer::new();
|
||||
|
||||
Render::render_escaped(&0.0f64, &mut b).unwrap();
|
||||
Render::render_escaped(&std::f64::INFINITY, &mut b).unwrap();
|
||||
Render::render_escaped(&std::f64::NEG_INFINITY, &mut b).unwrap();
|
||||
Render::render_escaped(&std::f64::NAN, &mut b).unwrap();
|
||||
assert_eq!(b.as_str(), "0.0inf-infNaN");
|
||||
b.clear();
|
||||
|
||||
Render::render_escaped(&0.0f32, &mut b).unwrap();
|
||||
Render::render_escaped(&std::f32::INFINITY, &mut b).unwrap();
|
||||
Render::render_escaped(&std::f32::NEG_INFINITY, &mut b).unwrap();
|
||||
Render::render_escaped(&std::f32::NAN, &mut b).unwrap();
|
||||
assert_eq!(b.as_str(), "0.0inf-infNaN");
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue