Merge branch 'master' into stable

This commit is contained in:
Kogia-sima 2020-07-17 04:48:56 +09:00
commit c809027152
90 changed files with 2600 additions and 1547 deletions

1329
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

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

View File

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

74
benches/Cargo.lock generated
View File

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

View File

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

View File

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

View File

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

1532
examples/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -1,4 +0,0 @@
{
"name": "<%= name %>",
"value": <%= value %>
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1 @@
/out/

258
sailfish-tests/fuzzing-tests/Cargo.lock generated Normal file
View File

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

View File

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

View File

@ -0,0 +1 @@
<h1><%= message %></h1>

View File

@ -0,0 +1,9 @@
<table>
<% for i in 1..=9 %>
<tr>
<% for j in 1..=9 %>
<td><%= i * j %></td>
<% } %>
</td>
<% } %>
</table>

View File

@ -0,0 +1 @@
<a href="http://example.com/">

View File

@ -0,0 +1,5 @@
for row in &table {
for col in row {
println!("{}", *col);
}
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,6 @@
template_dir: "../templates"
escape: true
delimiter: "%"
optimization:
rm_whitespace: false

View File

@ -0,0 +1,4 @@
disp: hello
dbg: "hello"
disp escaped: hello
dbg escaped: &quot;hello&quot;

View File

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

View File

@ -0,0 +1 @@
00010203040506070809

View File

@ -0,0 +1 @@
<% for i in 0..10 { %><%= format!("{:02}", i) %><% } %>

View File

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

View File

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

View File

@ -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::*;

View File

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

View File

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

View File

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

View File

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

View File

@ -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(), "&lt;h1&gt;title&lt;/h1&gt;");
}
}

View File

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

View File

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

View File

@ -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&lt;&amp; ");
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");
}
}