Refactor to support custom escaping schemes
This commit is contained in:
parent
96107eeb09
commit
2f8f1010f8
62 changed files with 745 additions and 459 deletions
23
Cargo.lock
generated
23
Cargo.lock
generated
|
|
@ -1,6 +1,6 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "basic-toml"
|
||||
|
|
@ -29,6 +29,17 @@ version = "0.1.13"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
|
||||
|
||||
[[package]]
|
||||
name = "displaydoc"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.1"
|
||||
|
|
@ -172,6 +183,7 @@ dependencies = [
|
|||
"sailfish-macros",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tinystr",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
|
|
@ -258,6 +270,15 @@ dependencies = [
|
|||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinystr"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.2"
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ Create a new directory named `templates` in the same directory as `Cargo.toml`.
|
|||
<html>
|
||||
<body>
|
||||
<% for msg in &messages { %>
|
||||
<div><%= msg %></div>
|
||||
<div><%\html msg %></div>
|
||||
<% } %>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@
|
|||
|
||||
You can control the rendering behaviour via `template` attribute.
|
||||
|
||||
``` rust
|
||||
#[derive(TemplateSimple)]
|
||||
#[template(path = "template.stpl", escape = false)]
|
||||
```rust
|
||||
#[derive(Render)]
|
||||
#[template(path = "template.stpl", rm_whitespace = true)]
|
||||
struct TemplateStruct {
|
||||
...
|
||||
}
|
||||
|
|
@ -15,7 +15,6 @@ struct TemplateStruct {
|
|||
`template` attribute accepts the following options.
|
||||
|
||||
- `path`: path to template file. This options is always required.
|
||||
- `escape`: Enable HTML escaping (default: `true`)
|
||||
- `delimiter`: Replace the '%' character used for the tag delimiter (default: '%')
|
||||
- `rm_whitespace`: try to strip whitespaces as much as possible without collapsing HTML structure (default: `false`). This option might not work correctly if your templates have inline `script` tag.
|
||||
|
||||
|
|
@ -51,7 +50,6 @@ Configuration files are written in the TOML 0.5 format. Here is the default conf
|
|||
|
||||
``` toml
|
||||
template_dirs = ["templates"]
|
||||
escape = true
|
||||
delimiter = "%"
|
||||
|
||||
[optimizations]
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ Example:
|
|||
=== "Template"
|
||||
|
||||
``` rhtml
|
||||
message: <%= "foo\nbar" | dbg %>
|
||||
message: <%\html "foo\nbar" | dbg %>
|
||||
```
|
||||
|
||||
=== "Result"
|
||||
|
|
@ -17,20 +17,19 @@ Example:
|
|||
```
|
||||
|
||||
!!! Note
|
||||
Since `dbg` filter accepts `<T: std::fmt::Debug>` types, that type isn't required to implement [`Render`](https://docs.rs/sailfish/latest/sailfish/runtime/trait.Render.html) trait. That means you can pass the type which doesn't implement `Render` trait.
|
||||
|
||||
Since `dbg` filter accepts `<T: std::fmt::Debug>` types, that type isn't required to implement [`Render`](https://docs.rs/sailfish/latest/sailfish/runtime/trait.Render.html) trait. That means you can pass the type which doesn't implement `Render` trait.
|
||||
|
||||
## Syntax
|
||||
|
||||
- Apply filter and HTML escaping
|
||||
- Apply filter and HTML escaping
|
||||
|
||||
``` rhtml
|
||||
<%= expression | filter %>
|
||||
```rhtml
|
||||
<%\html expression | filter %>
|
||||
```
|
||||
|
||||
- Apply filter only
|
||||
- Apply filter only
|
||||
|
||||
``` rhtml
|
||||
```rhtml
|
||||
<%- expression | filter %>
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
## Tags
|
||||
|
||||
- `<% %>`: Inline tag, you can write Rust code inside this tag
|
||||
- `<%= %>`: Evaluate the Rust expression and outputs the value into the template (HTML escaped)
|
||||
- `<%\mode %>`: Evaluate the Rust expression and outputs the value into the template, escaped according to `mode`
|
||||
- `<%- %>`: Evaluate the Rust expression and outputs the unescaped value into the template
|
||||
- `<%# %>`: Comment tag
|
||||
- `<%%`: Outputs a literal '<%'
|
||||
|
|
@ -20,7 +20,7 @@
|
|||
|
||||
```rhtml
|
||||
<% for (i, msg) in messages.iter().enumerate() { %>
|
||||
<div><%= i %>: <%= msg %></div>
|
||||
<div><%\html i %>: <%\html msg %></div>
|
||||
<% } %>
|
||||
```
|
||||
|
||||
|
|
@ -33,12 +33,12 @@
|
|||
## Filters
|
||||
|
||||
```rhtml
|
||||
<%= message | upper %>
|
||||
<%- message | upper %>
|
||||
```
|
||||
|
||||
```rhtml
|
||||
```json
|
||||
{
|
||||
"id": <%= id %>
|
||||
"comment": <%- comment | json %>
|
||||
"id": <%- id %>,
|
||||
"comment": "<%\json comment %>"
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ You can write Rust statement inside `<% %>` tag.
|
|||
}
|
||||
}
|
||||
%>
|
||||
<div>total = <%= total %></div>
|
||||
<div>total = <%\html total %></div>
|
||||
```
|
||||
|
||||
=== "Result"
|
||||
|
|
@ -65,12 +65,12 @@ Although almost all Rust statement is supported, the following statements inside
|
|||
|
||||
## Evaluation block
|
||||
|
||||
Rust expression inside `<%= %>` tag is evaluated and the result will be rendered.
|
||||
Rust expression inside `<%\mode %>` tag is evaluated and the result will be rendered.
|
||||
|
||||
=== "Template"
|
||||
|
||||
``` rhtml
|
||||
<% let a = 1; %><%= a + 2 %>
|
||||
<% let a = 1; %><%\html a + 2 %>
|
||||
```
|
||||
|
||||
=== "Result"
|
||||
|
|
@ -81,7 +81,7 @@ Rust expression inside `<%= %>` tag is evaluated and the result will be rendered
|
|||
|
||||
If the result contains `&"'<>` characters, sailfish replaces these characters with the equivalent html.
|
||||
|
||||
If you want to render the results without escaping, you can use `<%- %>` tag or [configure sailfish to not escape by default](../options.md).
|
||||
If you want to render the results without escaping, you can use `<%- %>` tag.
|
||||
|
||||
=== "Template"
|
||||
|
||||
|
|
@ -103,5 +103,5 @@ If you want to render the results without escaping, you can use `<%- %>` tag or
|
|||
Evaluation block does not return any value, so you cannot use the block to pass the render result to another code block. The following code is invalid.
|
||||
|
||||
``` rhtml
|
||||
<% let result = %><%= 1 %><% ; %>
|
||||
<% let result = %><%\html 1 %><% ; %>
|
||||
```
|
||||
|
|
|
|||
|
|
@ -75,10 +75,10 @@ However, since it is a corner case, It may be better if we provide `no_std=false
|
|||
|
||||
We must ensure that all of the data passed to templates should satisfy the following restrictions.
|
||||
|
||||
- completely immutable
|
||||
- does not allocate/deallocate memory
|
||||
- can be serialized to/deserialized from byte array (All data is serialized to byte array, and then decoded inside templates)
|
||||
- can be defined inside `#![no_std]` crate
|
||||
- completely immutable
|
||||
- does not allocate/deallocate memory
|
||||
- can be serialized to/deserialized from byte array (All data is serialized to byte array, and then decoded inside templates)
|
||||
- can be defined inside `#![no_std]` crate
|
||||
|
||||
Sailfish provide `TemplateData` trait which satisfies the above restrictions.
|
||||
|
||||
|
|
@ -94,12 +94,12 @@ pub unsafe trait TemplateData {
|
|||
|
||||
This trait can be implemented to the following types
|
||||
|
||||
- String,
|
||||
- Primitive integers (bool, char, u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, isize, usize)
|
||||
- [T; N] where T: TemplateData
|
||||
- (T1, T2, T3, ...) where T1, T2, T3, ... : TemplateData
|
||||
- Option\<T\> where T: TemplateData
|
||||
- Vec\<T\> where T: TemplateData
|
||||
- String,
|
||||
- Primitive integers (bool, char, u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, isize, usize)
|
||||
- [T; N] where T: TemplateData
|
||||
- (T1, T2, T3, ...) where T1, T2, T3, ... : TemplateData
|
||||
- Option\<T\> where T: TemplateData
|
||||
- Vec\<T\> where T: TemplateData
|
||||
|
||||
### `#[derive(TemplateData)]` attribute
|
||||
|
||||
|
|
@ -116,7 +116,7 @@ Template file contents is transformed into Rust code when `sailfish::dynamic::co
|
|||
For example, if we have a template
|
||||
|
||||
```html
|
||||
<h1><%= msg %></h1>
|
||||
<h1><%\html msg %></h1>
|
||||
```
|
||||
|
||||
and Rust code
|
||||
|
|
@ -154,7 +154,7 @@ pub extern fn sf_message(version: u64, data: *const [u8], data_len: usize, vtabl
|
|||
let Message { msg } = deserialize(&mut data);
|
||||
|
||||
let mut buf = VBuffer::from_vtable(vtable);
|
||||
|
||||
|
||||
static SIZE_HINT = SizeHint::new();
|
||||
let size_hint = SIZE_HINT.get();
|
||||
buf.reserve(size_hint);
|
||||
|
|
@ -178,11 +178,11 @@ pub extern fn sf_message(version: u64, data: *const [u8], data_len: usize, vtabl
|
|||
Template:
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html>
|
||||
<body>
|
||||
<b><%= name %></b>: <%= score %>
|
||||
</body>
|
||||
<body>
|
||||
<b><%\html name %></b>: <%\html score %>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,9 @@ impl<T: Template> Display for T {
|
|||
If you derive this trait, you cannot move out the struct fields. For example, the following template
|
||||
|
||||
```html
|
||||
<% for msg in messages { %><div><%= msg %></div><% } %>
|
||||
<% for msg in messages { %>
|
||||
<div><%\html msg %></div>
|
||||
<% } %>
|
||||
```
|
||||
|
||||
will be transformed into the Rust code like
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<html>
|
||||
<body>
|
||||
Hello <%= name %>!
|
||||
Hello <%\html name %>!
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -2,4 +2,4 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
<title><%= title %></title>
|
||||
<title><%\html &title %></title>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
<% include!("header.stpl"); %>
|
||||
</head>
|
||||
<body>
|
||||
<h1><%= title %></h1>
|
||||
Hello, <%= name %>!
|
||||
<h1><%\html title %></h1>
|
||||
Hello, <%\html name %>!
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
<% if i == 0 { %>
|
||||
<h1>Hello, world!</h1>
|
||||
<% } %>
|
||||
<div><%= *msg %></div>
|
||||
<div><%\html msg %></div>
|
||||
<% } %>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ impl Compiler {
|
|||
|
||||
fn translate_file_contents(&self, input: &Path) -> Result<TranslatedSource, Error> {
|
||||
let parser = Parser::new().delimiter(self.config.delimiter);
|
||||
let translator = Translator::new().escape(self.config.escape);
|
||||
let translator = Translator::new();
|
||||
let content = read_to_string(input)
|
||||
.chain_err(|| format!("Failed to open template file: {:?}", input))?;
|
||||
|
||||
|
|
@ -114,7 +114,7 @@ impl Compiler {
|
|||
});
|
||||
|
||||
let parser = Parser::new().delimiter(self.config.delimiter);
|
||||
let translator = Translator::new().escape(self.config.escape);
|
||||
let translator = Translator::new();
|
||||
let resolver = Resolver::new().include_handler(include_handler);
|
||||
let optimizer = Optimizer::new().rm_whitespace(self.config.rm_whitespace);
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ use std::path::{Path, PathBuf};
|
|||
#[derive(Clone, Debug, Hash)]
|
||||
pub struct Config {
|
||||
pub delimiter: char,
|
||||
pub escape: bool,
|
||||
pub rm_whitespace: bool,
|
||||
pub template_dirs: Vec<PathBuf>,
|
||||
#[doc(hidden)]
|
||||
|
|
@ -17,7 +16,6 @@ impl Default for Config {
|
|||
Self {
|
||||
template_dirs: Vec::new(),
|
||||
delimiter: '%',
|
||||
escape: true,
|
||||
cache_dir: Path::new(env!("OUT_DIR")).join("cache"),
|
||||
rm_whitespace: false,
|
||||
_non_exhaustive: (),
|
||||
|
|
@ -74,10 +72,6 @@ mod imp {
|
|||
config.delimiter = delimiter;
|
||||
}
|
||||
|
||||
if let Some(escape) = config_file.escape {
|
||||
config.escape = escape;
|
||||
}
|
||||
|
||||
if let Some(optimizations) = config_file.optimizations {
|
||||
if let Some(rm_whitespace) = optimizations.rm_whitespace {
|
||||
config.rm_whitespace = rm_whitespace;
|
||||
|
|
@ -103,7 +97,6 @@ mod imp {
|
|||
struct ConfigFile {
|
||||
template_dirs: Option<Vec<String>>,
|
||||
delimiter: Option<char>,
|
||||
escape: Option<bool>,
|
||||
optimizations: Option<Optimizations>,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ impl fmt::Display for Error {
|
|||
|
||||
if let Some(ref source_file) = self.source_file {
|
||||
let source_file =
|
||||
if env::var("SAILFISH_INTEGRATION_TESTS").map_or(false, |s| s == "1") {
|
||||
if env::var("SAILFISH_INTEGRATION_TESTS").is_ok_and(|s| s == "1") {
|
||||
match source_file.file_name() {
|
||||
Some(f) => Path::new(f),
|
||||
None => Path::new(""),
|
||||
|
|
@ -115,24 +115,32 @@ impl fmt::Display for Error {
|
|||
writeln!(f, "file: {}", source_file.display())?;
|
||||
}
|
||||
|
||||
if let (Some(ref source), Some(offset)) = (source, self.offset) {
|
||||
let (lineno, colno) = into_line_column(source, offset);
|
||||
writeln!(f, "position: line {}, column {}\n", lineno, colno)?;
|
||||
if let Some(ref source) = source {
|
||||
if let Some(offset) = self.offset {
|
||||
let (lineno, colno) = into_line_column(source, offset);
|
||||
writeln!(f, "position: line {}, column {}\n", lineno, colno)?;
|
||||
|
||||
// TODO: display adjacent lines
|
||||
let line = source.lines().nth(lineno - 1).unwrap();
|
||||
let lpad = count_digits(lineno);
|
||||
// TODO: display adjacent lines
|
||||
let line = source.lines().nth(lineno - 1).unwrap();
|
||||
let lpad = count_digits(lineno);
|
||||
|
||||
writeln!(f, "{:<lpad$} |", "", lpad = lpad)?;
|
||||
writeln!(f, "{} | {}", lineno, line)?;
|
||||
writeln!(
|
||||
f,
|
||||
"{:<lpad$} | {:<rpad$}^",
|
||||
"",
|
||||
"",
|
||||
lpad = lpad,
|
||||
rpad = colno - 1
|
||||
)?;
|
||||
writeln!(f, "{:<lpad$} |", "", lpad = lpad)?;
|
||||
writeln!(f, "{} | {}", lineno, line)?;
|
||||
writeln!(
|
||||
f,
|
||||
"{:<lpad$} | {:<rpad$}^",
|
||||
"",
|
||||
"",
|
||||
lpad = lpad,
|
||||
rpad = colno - 1
|
||||
)?;
|
||||
} else {
|
||||
let line_count = source.lines().count();
|
||||
let lpad = count_digits(line_count);
|
||||
for (i, line) in source.lines().enumerate() {
|
||||
writeln!(f, "{:>lpad$} | {line}", i + 1, lpad = lpad)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ impl VisitMut for OptmizerImpl {
|
|||
|
||||
fn visit_expr_call_mut(&mut self, i: &mut ExprCall) {
|
||||
if self.rm_whitespace {
|
||||
if let Some(v) = get_rendertext_value(&i) {
|
||||
if let Some(v) = get_rendertext_value(i) {
|
||||
let ts = match remove_whitespace(v) {
|
||||
Some(value) => value,
|
||||
None => return,
|
||||
|
|
|
|||
|
|
@ -55,8 +55,8 @@ impl Default for Parser {
|
|||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum TokenKind {
|
||||
BufferedCode { escape: bool },
|
||||
pub enum TokenKind<'a> {
|
||||
BufferedCode { escape: Option<&'a str> },
|
||||
Code,
|
||||
Comment,
|
||||
Text,
|
||||
|
|
@ -66,12 +66,12 @@ pub enum TokenKind {
|
|||
pub struct Token<'a> {
|
||||
content: &'a str,
|
||||
offset: usize,
|
||||
kind: TokenKind,
|
||||
kind: TokenKind<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Token<'a> {
|
||||
#[inline]
|
||||
pub fn new(content: &'a str, offset: usize, kind: TokenKind) -> Token<'a> {
|
||||
pub fn new(content: &'a str, offset: usize, kind: TokenKind<'a>) -> Token<'a> {
|
||||
Token {
|
||||
content,
|
||||
offset,
|
||||
|
|
@ -153,15 +153,21 @@ impl<'a> ParseStream<'a> {
|
|||
token_kind = TokenKind::Comment;
|
||||
start += 1;
|
||||
}
|
||||
Some(b'=') => {
|
||||
token_kind = TokenKind::BufferedCode { escape: true };
|
||||
Some(b'\\') => {
|
||||
start += 1;
|
||||
let (mode, _) = self.source[start..]
|
||||
.split_once(' ')
|
||||
.ok_or_else(|| self.error("Invalid syntax for escaped render"))?;
|
||||
start += mode.len();
|
||||
token_kind = TokenKind::BufferedCode { escape: Some(mode) };
|
||||
}
|
||||
Some(b'-') => {
|
||||
token_kind = TokenKind::BufferedCode { escape: false };
|
||||
token_kind = TokenKind::BufferedCode { escape: None };
|
||||
start += 1;
|
||||
}
|
||||
_ => {}
|
||||
Some(b' ') => {}
|
||||
Some(b'%') if self.source[start..] == self.block_delimiter.1 => {}
|
||||
_ => return Err(self.error("Invalid block syntax")),
|
||||
}
|
||||
|
||||
// skip whitespaces
|
||||
|
|
@ -176,8 +182,7 @@ impl<'a> ParseStream<'a> {
|
|||
|
||||
if token_kind == TokenKind::Comment {
|
||||
let block_delim_end = self.block_delimiter.1.as_bytes();
|
||||
let pos = self.source[start..]
|
||||
.as_bytes()
|
||||
let pos = self.source.as_bytes()[start..]
|
||||
.windows(1 + block_delim_end.len())
|
||||
.enumerate()
|
||||
.position(|(_, window)| {
|
||||
|
|
@ -201,9 +206,8 @@ impl<'a> ParseStream<'a> {
|
|||
{
|
||||
// closing bracket was found
|
||||
self.take_n(start);
|
||||
let s = &self.source[..pos - self.block_delimiter.1.len()].trim_end_matches(
|
||||
|c| matches!(c, ' ' | '\t' | '\r' | '\u{000B}' | '\u{000C}'),
|
||||
);
|
||||
let s = &self.source[..pos - self.block_delimiter.1.len()]
|
||||
.trim_end_matches([' ', '\t', '\r', '\u{000B}', '\u{000C}']);
|
||||
let token = Token {
|
||||
content: s,
|
||||
offset: self.offset(),
|
||||
|
|
@ -299,7 +303,7 @@ fn find_block_end(haystack: &str, delimiter: &str) -> Option<usize> {
|
|||
},
|
||||
b'\"' => {
|
||||
// check if the literal is a raw string
|
||||
for (i, byte) in remain[..pos].as_bytes().iter().enumerate().rev() {
|
||||
for (i, byte) in remain.as_bytes()[..pos].iter().enumerate().rev() {
|
||||
match byte {
|
||||
b'#' => {}
|
||||
b'r' => {
|
||||
|
|
@ -424,7 +428,7 @@ mod tests {
|
|||
Token {
|
||||
content: "inner | upper",
|
||||
offset: 10,
|
||||
kind: TokenKind::BufferedCode { escape: false },
|
||||
kind: TokenKind::BufferedCode { escape: None },
|
||||
},
|
||||
Token {
|
||||
content: " outer",
|
||||
|
|
@ -437,7 +441,8 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn non_ascii_delimiter() {
|
||||
let src = r##"foo <🍣# This is a comment #🍣> bar <🍣= r"🍣>" 🍣> baz <🍣🍣"##;
|
||||
let src =
|
||||
r##"foo <🍣# This is a comment #🍣> bar <🍣\html r"🍣>" 🍣> baz <🍣🍣"##;
|
||||
let parser = Parser::new().delimiter('🍣');
|
||||
let tokens = parser.parse(src).into_vec().unwrap();
|
||||
assert_eq!(
|
||||
|
|
@ -460,17 +465,19 @@ mod tests {
|
|||
},
|
||||
Token {
|
||||
content: "r\"🍣>\"",
|
||||
offset: 47,
|
||||
kind: TokenKind::BufferedCode { escape: true }
|
||||
offset: 51,
|
||||
kind: TokenKind::BufferedCode {
|
||||
escape: Some("html")
|
||||
}
|
||||
},
|
||||
Token {
|
||||
content: " baz ",
|
||||
offset: 61,
|
||||
offset: 65,
|
||||
kind: TokenKind::Text
|
||||
},
|
||||
Token {
|
||||
content: "<🍣",
|
||||
offset: 66,
|
||||
offset: 70,
|
||||
kind: TokenKind::Text
|
||||
},
|
||||
]
|
||||
|
|
@ -479,7 +486,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn comment_inside_block() {
|
||||
let src = "<% // %>\n %><%= /* %%>*/ 1 %>";
|
||||
let src = "<% // %>\n %><%\\html /* %%>*/ 1 %>";
|
||||
let parser = Parser::new();
|
||||
let tokens = parser.parse(src).into_vec().unwrap();
|
||||
assert_eq!(
|
||||
|
|
@ -492,8 +499,10 @@ mod tests {
|
|||
},
|
||||
Token {
|
||||
content: "/* %%>*/ 1",
|
||||
offset: 16,
|
||||
kind: TokenKind::BufferedCode { escape: true }
|
||||
offset: 20,
|
||||
kind: TokenKind::BufferedCode {
|
||||
escape: Some("html")
|
||||
}
|
||||
},
|
||||
]
|
||||
);
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ struct DeriveTemplateOptions {
|
|||
found_keys: Vec<Ident>,
|
||||
path: Option<LitStr>,
|
||||
delimiter: Option<LitChar>,
|
||||
escape: Option<LitBool>,
|
||||
rm_whitespace: Option<LitBool>,
|
||||
}
|
||||
|
||||
|
|
@ -33,7 +32,7 @@ impl DeriveTemplateOptions {
|
|||
s.parse::<Token![=]>()?;
|
||||
|
||||
// check if argument is repeated
|
||||
if self.found_keys.iter().any(|e| *e == key) {
|
||||
if self.found_keys.contains(&key) {
|
||||
return Err(syn::Error::new(
|
||||
key.span(),
|
||||
format!("Argument `{}` was repeated.", key),
|
||||
|
|
@ -44,8 +43,6 @@ impl DeriveTemplateOptions {
|
|||
self.path = Some(s.parse::<LitStr>()?);
|
||||
} else if key == "delimiter" {
|
||||
self.delimiter = Some(s.parse::<LitChar>()?);
|
||||
} else if key == "escape" {
|
||||
self.escape = Some(s.parse::<LitBool>()?);
|
||||
} else if key == "rm_whitespace" {
|
||||
self.rm_whitespace = Some(s.parse::<LitBool>()?);
|
||||
} else {
|
||||
|
|
@ -74,9 +71,6 @@ fn merge_config_options(config: &mut Config, options: &DeriveTemplateOptions) {
|
|||
if let Some(ref delimiter) = options.delimiter {
|
||||
config.delimiter = delimiter.value();
|
||||
}
|
||||
if let Some(ref escape) = options.escape {
|
||||
config.escape = escape.value;
|
||||
}
|
||||
if let Some(ref rm_whitespace) = options.rm_whitespace {
|
||||
config.rm_whitespace = rm_whitespace.value;
|
||||
}
|
||||
|
|
@ -169,7 +163,7 @@ fn derive_template_common_impl(
|
|||
#[cfg(not(feature = "config"))]
|
||||
let mut config = Config::default();
|
||||
|
||||
if env::var("SAILFISH_INTEGRATION_TESTS").map_or(false, |s| s == "1") {
|
||||
if env::var("SAILFISH_INTEGRATION_TESTS").is_ok_and(|s| s == "1") {
|
||||
let template_dir = env::current_dir()
|
||||
.unwrap()
|
||||
.ancestors()
|
||||
|
|
@ -341,7 +335,7 @@ fn derive_template_once_only_impl(
|
|||
// drops when the implementation is written in `sailfish` crate.
|
||||
quote! {
|
||||
impl #impl_generics sailfish::RenderOnce for #name #ty_generics #where_clause {
|
||||
fn render_once(mut self, __sf_buf: &mut sailfish::runtime::Buffer) -> std::result::Result<(), sailfish::runtime::RenderError> {
|
||||
fn render_once(self, __sf_buf: &mut sailfish::Buffer) -> std::result::Result<(), sailfish::RenderError> {
|
||||
// This line is required for cargo to track child templates
|
||||
#include_bytes_seq;
|
||||
|
||||
|
|
@ -352,7 +346,7 @@ fn derive_template_once_only_impl(
|
|||
}
|
||||
|
||||
fn render_once_to_string(mut self) -> sailfish::RenderResult {
|
||||
use sailfish::runtime::{Buffer, SizeHint};
|
||||
use sailfish::{Buffer, SizeHint};
|
||||
static SIZE_HINT: SizeHint = SizeHint::new();
|
||||
|
||||
let mut buf = Buffer::with_capacity(SIZE_HINT.get());
|
||||
|
|
@ -377,7 +371,7 @@ fn derive_template_mut_only_impl(
|
|||
// drops when the implementation is written in `sailfish` crate.
|
||||
quote! {
|
||||
impl #impl_generics sailfish::RenderMut for #name #ty_generics #where_clause {
|
||||
fn render_mut(&mut self, __sf_buf: &mut sailfish::runtime::Buffer) -> std::result::Result<(), sailfish::runtime::RenderError> {
|
||||
fn render_mut(&mut self, __sf_buf: &mut sailfish::Buffer) -> std::result::Result<(), sailfish::RenderError> {
|
||||
// This line is required for cargo to track child templates
|
||||
#include_bytes_seq;
|
||||
|
||||
|
|
@ -388,7 +382,7 @@ fn derive_template_mut_only_impl(
|
|||
}
|
||||
|
||||
fn render_mut_to_string(&mut self) -> sailfish::RenderResult {
|
||||
use sailfish::runtime::{Buffer, SizeHint};
|
||||
use sailfish::{Buffer, SizeHint};
|
||||
static SIZE_HINT: SizeHint = SizeHint::new();
|
||||
|
||||
let mut buf = Buffer::with_capacity(SIZE_HINT.get());
|
||||
|
|
@ -413,7 +407,7 @@ fn derive_template_only_impl(
|
|||
// drops when the implementation is written in `sailfish` crate.
|
||||
quote! {
|
||||
impl #impl_generics sailfish::Render for #name #ty_generics #where_clause {
|
||||
fn render(&self, __sf_buf: &mut sailfish::runtime::Buffer) -> std::result::Result<(), sailfish::runtime::RenderError> {
|
||||
fn render(&self, __sf_buf: &mut sailfish::Buffer) -> std::result::Result<(), sailfish::RenderError> {
|
||||
// This line is required for cargo to track child templates
|
||||
#include_bytes_seq;
|
||||
|
||||
|
|
@ -424,7 +418,7 @@ fn derive_template_only_impl(
|
|||
}
|
||||
|
||||
fn render_to_string(&self) -> sailfish::RenderResult {
|
||||
use sailfish::runtime::{Buffer, SizeHint};
|
||||
use sailfish::{Buffer, SizeHint};
|
||||
static SIZE_HINT: SizeHint = SizeHint::new();
|
||||
|
||||
let mut buf = Buffer::with_capacity(SIZE_HINT.get());
|
||||
|
|
|
|||
|
|
@ -7,21 +7,20 @@ use crate::error::*;
|
|||
use crate::parser::{ParseStream, Token, TokenKind};
|
||||
|
||||
// translate tokens into Rust code
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct Translator {
|
||||
escape: bool,
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Translator {}
|
||||
|
||||
impl Default for Translator {
|
||||
#[inline]
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Translator {
|
||||
#[inline]
|
||||
pub fn new() -> Self {
|
||||
Self { escape: true }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn escape(mut self, new: bool) -> Self {
|
||||
self.escape = new;
|
||||
self
|
||||
Self {}
|
||||
}
|
||||
|
||||
pub fn translate(
|
||||
|
|
@ -30,7 +29,7 @@ impl Translator {
|
|||
) -> Result<TranslatedSource, Error> {
|
||||
let original_source = token_iter.original_source;
|
||||
|
||||
let mut ps = SourceBuilder::new(self.escape);
|
||||
let mut ps = SourceBuilder::new();
|
||||
ps.reserve(original_source.len());
|
||||
ps.feed_tokens(token_iter)?;
|
||||
|
||||
|
|
@ -77,15 +76,13 @@ impl SourceMap {
|
|||
}
|
||||
|
||||
struct SourceBuilder {
|
||||
escape: bool,
|
||||
source: String,
|
||||
source_map: SourceMap,
|
||||
}
|
||||
|
||||
impl SourceBuilder {
|
||||
fn new(escape: bool) -> SourceBuilder {
|
||||
fn new() -> SourceBuilder {
|
||||
SourceBuilder {
|
||||
escape,
|
||||
source: String::from("{\n"),
|
||||
source_map: SourceMap::default(),
|
||||
}
|
||||
|
|
@ -133,7 +130,7 @@ impl SourceBuilder {
|
|||
fn write_buffered_code(
|
||||
&mut self,
|
||||
token: &Token<'_>,
|
||||
escape: bool,
|
||||
escape: Option<&str>,
|
||||
) -> Result<(), Error> {
|
||||
self.write_buffered_code_with_suffix(token, escape, "")
|
||||
}
|
||||
|
|
@ -141,7 +138,7 @@ impl SourceBuilder {
|
|||
fn write_buffered_code_with_suffix(
|
||||
&mut self,
|
||||
token: &Token<'_>,
|
||||
escape: bool,
|
||||
escape: Option<&str>,
|
||||
suffix: &str,
|
||||
) -> Result<(), Error> {
|
||||
// parse and split off filter
|
||||
|
|
@ -151,7 +148,7 @@ impl SourceBuilder {
|
|||
err.offset = into_offset(token.as_str(), span).map(|p| token.offset() + p);
|
||||
err
|
||||
})?;
|
||||
let method = if self.escape && escape {
|
||||
let method = if escape.is_some() {
|
||||
"render_escaped"
|
||||
} else {
|
||||
"render"
|
||||
|
|
@ -171,13 +168,13 @@ impl SourceBuilder {
|
|||
),
|
||||
};
|
||||
|
||||
self.source.push_str("sailfish::runtime::filter::");
|
||||
self.source.push_str("__sf_rt::filter::");
|
||||
self.source.push_str(&name);
|
||||
self.source.push('(');
|
||||
|
||||
// arguments to filter function
|
||||
{
|
||||
self.source.push_str("(");
|
||||
self.source.push('(');
|
||||
let entry = SourceMapEntry {
|
||||
original: token.offset(),
|
||||
new: self.source.len(),
|
||||
|
|
@ -199,6 +196,12 @@ impl SourceBuilder {
|
|||
self.source.push_str(suffix);
|
||||
}
|
||||
|
||||
if let Some(escape) = escape {
|
||||
self.source.push_str(", &__sf_rt::esc_");
|
||||
self.source.push_str(escape);
|
||||
self.source.push_str("()");
|
||||
}
|
||||
|
||||
self.source.push_str(")?;\n");
|
||||
|
||||
Ok(())
|
||||
|
|
@ -365,11 +368,10 @@ mod tests {
|
|||
#[test]
|
||||
#[ignore]
|
||||
fn translate() {
|
||||
let src = "<% pub fn sample() { %> <%% <%=//%>\n1%><% } %>";
|
||||
let src = "<% pub fn sample() { %> <%% <%\\html //%>\n1%><% } %>";
|
||||
let lexer = Parser::new();
|
||||
let token_iter = lexer.parse(src);
|
||||
let mut ps = SourceBuilder {
|
||||
escape: true,
|
||||
source: String::with_capacity(token_iter.original_source.len()),
|
||||
source_map: SourceMap::default(),
|
||||
};
|
||||
|
|
@ -383,7 +385,6 @@ mod tests {
|
|||
let lexer = Parser::new();
|
||||
let token_iter = lexer.parse(src);
|
||||
let mut ps = SourceBuilder {
|
||||
escape: true,
|
||||
source: String::with_capacity(token_iter.original_source.len()),
|
||||
source_map: SourceMap::default(),
|
||||
};
|
||||
|
|
@ -405,7 +406,6 @@ mod tests {
|
|||
let lexer = Parser::new();
|
||||
let token_iter = lexer.parse(src);
|
||||
let mut ps = SourceBuilder {
|
||||
escape: true,
|
||||
source: String::with_capacity(token_iter.original_source.len()),
|
||||
source_map: SourceMap::default(),
|
||||
};
|
||||
|
|
@ -417,7 +417,7 @@ mod tests {
|
|||
.ast
|
||||
.into_token_stream()
|
||||
.to_string(),
|
||||
r#"{ __sf_rt :: render_text (__sf_buf , "outer ") ; __sf_rt :: render (__sf_buf , sailfish :: runtime :: filter :: upper ((inner))) ? ; __sf_rt :: render_text (__sf_buf , " outer") ; }"#
|
||||
r#"{ __sf_rt :: render_text (__sf_buf , "outer ") ; __sf_rt :: render (__sf_buf , __sf_rt :: filter :: upper ((inner))) ? ; __sf_rt :: render_text (__sf_buf , " outer") ; }"#
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,10 +70,7 @@ pub fn rustfmt_block(source: &str) -> io::Result<String> {
|
|||
s.replace_range(..brace_offset, "");
|
||||
Ok(s)
|
||||
} else {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"rustfmt command failed",
|
||||
))
|
||||
Err(io::Error::other("rustfmt command failed"))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
template_dirs = ["../templates"]
|
||||
escape = true
|
||||
delimiter = "%"
|
||||
|
||||
[optimizations]
|
||||
rm_whitespace = false
|
||||
rm_whitespace = false
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
<%= 1 + /* 10 + %> /* 100 + */ 1000 %> + */ 10000 %>
|
||||
<%\html 1 + /* 10 + %> /* 100 + */ 1000 %> + */ 10000 %>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
<% for i in 0..10 { %>
|
||||
<div>head</div>
|
||||
<% if i < 2 { continue; } %>
|
||||
<div><%= i %></div>
|
||||
<div><%\html i %></div>
|
||||
<% if i > 5 { break; } %>
|
||||
<div>tail</div>
|
||||
<% } %>
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
<🍣 let i = 10; 🍣><div>i: <🍣= i 🍣></div>
|
||||
<🍣 let i = 10; 🍣><div>i: <🍣\html i 🍣></div>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
disp: <%- self.message | disp %>
|
||||
dbg: <%- self.message | dbg %>
|
||||
disp escaped: <%= self.message | disp %>
|
||||
dbg escaped: <%= self.message | dbg %>
|
||||
disp: <%- &self.message | disp %>
|
||||
dbg: <%- &self.message | dbg %>
|
||||
disp escaped: <%\html &self.message | disp %>
|
||||
dbg escaped: <%\html &self.message | dbg %>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
trim: <%= " <html> " | trim %>
|
||||
lower: <%= "aBcAbc" | lower %>
|
||||
upper: <%= "aBcAbc" | upper %>
|
||||
trim: <%\html " <html> " | trim %>
|
||||
lower: <%\html "aBcAbc" | lower %>
|
||||
upper: <%\html "aBcAbc" | upper %>
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
<% for i in 0..10 { %><%= format!("{:02}", i) %><% } %>
|
||||
<% for i in 0..10 { %><%- format_args!("{:02}", i) %><% } %>
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
<% let a = include!("includes/rust.rs"); %><%= a %>
|
||||
<% let a = include!("includes/rust.rs"); %><%\html a %>
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
INCLUDED: <%= s %>
|
||||
INCLUDED: <%\html s %>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
{
|
||||
"name": <%- &self.name | dbg %>,
|
||||
"value": <%= self.value %>
|
||||
"name": <%- &self.name | json %>,
|
||||
"value": <%- self.value %>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
ESCAPED: <%= self.uppercase() %>
|
||||
ESCAPED: <%\html self.uppercase() %>
|
||||
NON-ESCAPED: <%- self.uppercase() %>
|
||||
PASS-VALUE: <%= Self::uppercase_val(self.s) %>
|
||||
PASS-REF: <%= Self::multiply_ref(&self.i) %>
|
||||
MUTABLE: <% self.mutate(); %><%= &self.mutate %>
|
||||
MATCH: <%= match self.uppercase().as_str() {
|
||||
PASS-VALUE: <%\html Self::uppercase_val(self.s) %>
|
||||
PASS-REF: <%\html Self::multiply_ref(&self.i) %>
|
||||
MUTABLE: <% self.mutate(); %><%\html &self.mutate %>
|
||||
MATCH: <%\html match self.uppercase().as_str() {
|
||||
"<TEST>" => "Cool",
|
||||
_ => "Not cool"
|
||||
} %>
|
||||
|
|
@ -5,9 +5,9 @@
|
|||
<span>3</span>
|
||||
</div>
|
||||
<div>
|
||||
trailing spaces
|
||||
trailing spaces
|
||||
This line should be appeared under the previous line
|
||||
</div>
|
||||
<% for msg in self.messages { %>
|
||||
<div><%= msg %></div>
|
||||
<div><%\html msg %></div>
|
||||
<% } %>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
<body>
|
||||
<table>
|
||||
<tr><th>id</th><th>message</th></tr>
|
||||
<% for item in &self.items { %><tr><td><%= item.id %></td><td><%= item.message %></td></tr><% } %>
|
||||
<% for item in &self.items { %><tr><td><%\html item.id %></td><td><%\html item.message %></td></tr><% } %>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ fn read_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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use sailfish::RenderOnce;
|
|||
use sailfish_macros::RenderOnce;
|
||||
|
||||
#[derive(RenderOnce)]
|
||||
#[template(path = "foo.stpl", escape = 1)]
|
||||
#[template(path = "foo.stpl", rm_whitespace = 1)]
|
||||
struct InvalidOptionValue {
|
||||
name: String,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
error: expected boolean literal
|
||||
--> $DIR/invalid_option_value.rs:5:38
|
||||
|
|
||||
5 | #[template(path = "foo.stpl", escape=1)]
|
||||
| ^
|
||||
5 | #[template(path = "foo.stpl", rm_whitespace = 1)]
|
||||
| ^
|
||||
|
||||
error[E0599]: no method named `render_once` found for struct `InvalidOptionValue` in the current scope
|
||||
--> $DIR/invalid_option_value.rs:11:69
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ use sailfish::RenderOnce;
|
|||
use sailfish_macros::RenderOnce;
|
||||
|
||||
#[derive(RenderOnce)]
|
||||
#[template(path = "foo.stpl", escape = true)]
|
||||
#[template(escape = false)]
|
||||
#[template(path = "foo.stpl", rm_whitespace = true)]
|
||||
#[template(rm_whitespace = false)]
|
||||
struct InvalidOptionValue {
|
||||
name: String,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
error: Argument `escape` was repeated.
|
||||
--> $DIR/repeated_arguments.rs:6:12
|
||||
|
|
||||
6 | #[template(escape = false)]
|
||||
| ^^^^^^
|
||||
6 | #[template(rm_whitespace = false)]
|
||||
| ^^^^^^^^^^^^^
|
||||
|
||||
error[E0599]: no method named `render_once` found for struct `InvalidOptionValue` in the current scope
|
||||
--> $DIR/repeated_arguments.rs:12:69
|
||||
|
|
|
|||
|
|
@ -1,3 +1,8 @@
|
|||
<<<<<<< HEAD
|
||||
<% for player in &self.players %>
|
||||
<div><%= player.name %>: <%= player.score %></div>
|
||||
=======
|
||||
<% for player in &self.players %>
|
||||
<div><%\html player.name %>: <%\html player.score %></div>
|
||||
>>>>>>> 6e09ca7 (Refactor to support custom escaping schemes)
|
||||
<% } %>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<html>
|
||||
<body>
|
||||
<%= self.content
|
||||
<%\html self.content
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
{
|
||||
"name": "<%= self.name %>",
|
||||
"content": <% =self.content %>
|
||||
"name": "<%\json self.name %>",
|
||||
"content": <% \jsonself.content %>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ file: unclosed_delimiter.stpl
|
|||
position: line 3, column 5
|
||||
|
||||
|
|
||||
3 | <%= content
|
||||
3 | <%\html content
|
||||
| ^
|
||||
|
||||
--> $DIR/unclosed_delimter.rs:4:10
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ struct Content<'a> {
|
|||
|
||||
#[derive(RenderOnce)]
|
||||
#[template(path = "unexpected_token.stpl")]
|
||||
#[template(escape = false)]
|
||||
struct UnexpectedToken<'a> {
|
||||
name: &'a str,
|
||||
content: Content<'a>,
|
||||
|
|
|
|||
|
|
@ -1,8 +1,5 @@
|
|||
extern crate sailfish_macros;
|
||||
|
||||
use integration_tests::assert_string_eq;
|
||||
use sailfish::runtime::RenderResult;
|
||||
use sailfish::{RenderOnce, RenderMut, Render};
|
||||
use sailfish::{RenderOnce, RenderMut, Render, RenderResult};
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn assert_render_result(name: &str, result: RenderResult) {
|
||||
|
|
@ -231,7 +228,7 @@ fn test_rust_macro() {
|
|||
}
|
||||
|
||||
#[derive(Render)]
|
||||
#[template(path = "formatting.stpl", escape = false)]
|
||||
#[template(path = "formatting.stpl")]
|
||||
struct Formatting;
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ itoap = "1.0.1"
|
|||
ryu = "1.0.13"
|
||||
serde = { version = "1.0.159", optional = true }
|
||||
serde_json = { version = "1.0.95", optional = true }
|
||||
tinystr = { version = "0.8.1", default-features = false }
|
||||
|
||||
[dependencies.sailfish-macros]
|
||||
path = "../sailfish-macros"
|
||||
|
|
@ -41,3 +42,6 @@ version_check = "0.9.4"
|
|||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(sailfish_nightly)'] }
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ impl Buffer {
|
|||
/// overflows `isize::MAX`.
|
||||
#[inline]
|
||||
pub(crate) unsafe fn reserve_small(&mut self, size: usize) {
|
||||
debug_assert!(size <= std::isize::MAX as usize);
|
||||
debug_assert!(size <= isize::MAX as usize);
|
||||
if likely!(self.len + size <= self.capacity) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -163,7 +163,7 @@ impl Buffer {
|
|||
|
||||
#[cfg_attr(feature = "perf-inline", inline)]
|
||||
fn reserve_internal(&mut self, size: usize) {
|
||||
debug_assert!(size <= std::isize::MAX as usize);
|
||||
debug_assert!(size <= isize::MAX as usize);
|
||||
|
||||
let new_capacity = std::cmp::max(self.capacity * 2, self.capacity + size);
|
||||
debug_assert!(new_capacity > self.capacity);
|
||||
|
|
@ -179,7 +179,7 @@ impl Buffer {
|
|||
fn safe_alloc(capacity: usize) -> *mut u8 {
|
||||
assert!(capacity > 0);
|
||||
assert!(
|
||||
capacity <= std::isize::MAX as usize,
|
||||
capacity <= isize::MAX as usize,
|
||||
"capacity is too large"
|
||||
);
|
||||
|
||||
|
|
@ -198,13 +198,13 @@ fn safe_alloc(capacity: usize) -> *mut u8 {
|
|||
/// # Safety
|
||||
///
|
||||
/// - if `capacity > 0`, `capacity` is the same value that was used to allocate the block
|
||||
/// of memory pointed by `ptr`.
|
||||
/// of memory pointed by `ptr`.
|
||||
#[cold]
|
||||
#[inline(never)]
|
||||
unsafe fn safe_realloc(ptr: *mut u8, capacity: usize, new_capacity: usize) -> *mut u8 {
|
||||
assert!(new_capacity > 0);
|
||||
assert!(
|
||||
new_capacity <= std::isize::MAX as usize,
|
||||
new_capacity <= isize::MAX as usize,
|
||||
"capacity is too large"
|
||||
);
|
||||
|
||||
98
sailfish/src/escape.rs
Normal file
98
sailfish/src/escape.rs
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
use tinystr::{tinystr, TinyAsciiStr};
|
||||
|
||||
use super::Buffer;
|
||||
|
||||
/// A scheme for escaping strings.
|
||||
pub trait Escape {
|
||||
/// The type of an escaped character.
|
||||
type Escaped: AsRef<str>;
|
||||
|
||||
/// True if `true` and `false` will never need escaping.
|
||||
const IDENT_BOOLS: bool = false;
|
||||
/// True if unsigned integers will never need escaping.
|
||||
const IDENT_UINTS: bool = false;
|
||||
/// True if signed integers will never need escaping.
|
||||
const IDENT_INTS: bool = false;
|
||||
/// True if floats (using [`ryu`]'s formatting) will never need escaping.
|
||||
const IDENT_FLOATS: bool = false;
|
||||
|
||||
/// If the character needs to be escaped, does so and returns it as a string. Otherwise,
|
||||
/// returns `None`.
|
||||
fn escape(&self, c: char) -> Option<Self::Escaped>;
|
||||
|
||||
/// Writes the `string` to the `buffer`, applying any necessary escaping.
|
||||
#[inline]
|
||||
fn escape_to_buf(&self, buffer: &mut Buffer, string: &str) {
|
||||
buffer.reserve(string.len());
|
||||
let mut i = 0;
|
||||
for (j, c) in string.char_indices() {
|
||||
if let Some(rep) = self.escape(c) {
|
||||
buffer.push_str(&string[i..j]);
|
||||
buffer.push_str(rep.as_ref());
|
||||
i = j + c.len_utf8();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes the `string` to the `buffer`, applying any necessary escaping.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use sailfish::{Escape, EscapeHtml};
|
||||
///
|
||||
/// let mut buf = String::new();
|
||||
/// EscapeHtml.escape_to_string(&mut buf, "<h1>Hello, world!</h1>");
|
||||
/// assert_eq!(buf, "<h1>Hello, world!</h1>");
|
||||
/// ```
|
||||
#[inline]
|
||||
fn escape_to_string(&self, buffer: &mut String, string: &str) {
|
||||
let mut buf = Buffer::from(std::mem::take(buffer));
|
||||
self.escape_to_buf(&mut buf, string);
|
||||
*buffer = buf.into_string();
|
||||
}
|
||||
}
|
||||
|
||||
/// A scheme for escaping strings for safe insertion into JSON strings.
|
||||
pub struct EscapeJsonString;
|
||||
|
||||
impl Escape for EscapeJsonString {
|
||||
type Escaped = TinyAsciiStrWrapper<4>;
|
||||
|
||||
const IDENT_BOOLS: bool = true;
|
||||
const IDENT_UINTS: bool = true;
|
||||
const IDENT_INTS: bool = true;
|
||||
const IDENT_FLOATS: bool = true;
|
||||
|
||||
#[inline]
|
||||
fn escape(&self, c: char) -> Option<Self::Escaped> {
|
||||
match c {
|
||||
'"' => Some(TinyAsciiStrWrapper(tinystr!(4, r#"\""#))),
|
||||
'\\' => Some(TinyAsciiStrWrapper(tinystr!(4, r"\\"))),
|
||||
'\u{0000}'..='\u{001F}' => {
|
||||
let c = c as u8;
|
||||
const HEX_DIGITS: &[u8; 16] = b"0123456789ABCDEF";
|
||||
let s = [
|
||||
b'\\',
|
||||
b'u',
|
||||
HEX_DIGITS[usize::from(c >> 4)],
|
||||
HEX_DIGITS[usize::from(c & 0xF)],
|
||||
];
|
||||
Some(TinyAsciiStrWrapper(unsafe {
|
||||
// SAFETY: we only write valid UTF-8, and never NUL bytes
|
||||
TinyAsciiStr::from_utf8_unchecked(s)
|
||||
}))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TinyAsciiStrWrapper<const N: usize>(TinyAsciiStr<N>);
|
||||
|
||||
impl<const N: usize> AsRef<str> for TinyAsciiStrWrapper<N> {
|
||||
#[inline]
|
||||
fn as_ref(&self) -> &str {
|
||||
self.0.as_str()
|
||||
}
|
||||
}
|
||||
|
|
@ -3,9 +3,7 @@
|
|||
use std::fmt;
|
||||
use std::ptr;
|
||||
|
||||
use super::escape;
|
||||
use super::render::RenderOnce;
|
||||
use super::{Buffer, Render, RenderError};
|
||||
use crate::{Buffer, Escape, Render, RenderError, RenderOnce};
|
||||
|
||||
/// Helper struct for 'display' filter
|
||||
#[derive(Clone, Copy)]
|
||||
|
|
@ -15,7 +13,7 @@ impl<T: fmt::Display> Render for Display<T> {
|
|||
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
use fmt::Write;
|
||||
|
||||
write!(b, "{}", self.0).map_err(|e| RenderError::from(e))
|
||||
write!(b, "{}", self.0).map_err(RenderError::from)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -24,7 +22,7 @@ impl<T: fmt::Display> Render for Display<T> {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```text
|
||||
/// filename: <%= filename.display() | disp %>
|
||||
/// filename: <%\html filename.display() | disp %>
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn disp<T: fmt::Display>(expr: T) -> Display<T> {
|
||||
|
|
@ -39,7 +37,7 @@ impl<T: fmt::Debug> Render for Debug<T> {
|
|||
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
use fmt::Write;
|
||||
|
||||
write!(b, "{:?}", self.0).map_err(|e| RenderError::from(e))
|
||||
write!(b, "{:?}", self.0).map_err(RenderError::from)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -47,14 +45,14 @@ impl<T: fmt::Debug> Render for Debug<T> {
|
|||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// The following examples produce exactly same results, but former is a bit faster
|
||||
/// The following examples produce exactly same results, but former is faster
|
||||
///
|
||||
/// ```text
|
||||
/// table content: <%= table | dbg %>
|
||||
/// table content: <%\html table | dbg %>
|
||||
/// ```
|
||||
///
|
||||
/// ```text
|
||||
/// table content: <%= format!("{:?}", table) %>
|
||||
/// table content: <%\html format!("{:?}", table) %>
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn dbg<T: fmt::Debug>(expr: T) -> Debug<T> {
|
||||
|
|
@ -66,6 +64,7 @@ pub fn dbg<T: fmt::Debug>(expr: T) -> Debug<T> {
|
|||
pub struct Upper<T>(T);
|
||||
|
||||
impl<T: RenderOnce> RenderOnce for Upper<T> {
|
||||
#[inline]
|
||||
fn render_once(self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
let old_len = b.len();
|
||||
self.0.render_once(b)?;
|
||||
|
|
@ -78,15 +77,20 @@ impl<T: RenderOnce> RenderOnce for Upper<T> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn render_once_escaped(self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
#[inline]
|
||||
fn render_once_escaped<E: Escape>(
|
||||
self,
|
||||
b: &mut Buffer,
|
||||
e: &E,
|
||||
) -> Result<(), RenderError> {
|
||||
let old_len = b.len();
|
||||
self.0.render_once(b)?;
|
||||
|
||||
let content = b.as_str().get(old_len..).ok_or(RenderError::BufSize)?;
|
||||
// TODO: don't allocate unless a character expands to multiple characters
|
||||
let s = content.to_uppercase();
|
||||
unsafe { b._set_len(old_len) };
|
||||
|
||||
escape::escape_to_buf(s.as_str(), b);
|
||||
e.escape_to_buf(b, s.as_str());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -96,7 +100,7 @@ impl<T: RenderOnce> RenderOnce for Upper<T> {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```text
|
||||
/// <%= "tschüß" | upper %>
|
||||
/// <%\html "tschüß" | upper %>
|
||||
/// ```
|
||||
///
|
||||
/// result:
|
||||
|
|
@ -114,11 +118,13 @@ pub fn upper<T: RenderOnce>(expr: T) -> Upper<T> {
|
|||
pub struct Lower<T>(T);
|
||||
|
||||
impl<T: RenderOnce> RenderOnce for Lower<T> {
|
||||
#[inline]
|
||||
fn render_once(self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
let old_len = b.len();
|
||||
self.0.render_once(b)?;
|
||||
|
||||
let content = b.as_str().get(old_len..).ok_or(RenderError::BufSize)?;
|
||||
// TODO: don't allocate unless a character expands to multiple characters
|
||||
let s = content.to_lowercase();
|
||||
unsafe { b._set_len(old_len) };
|
||||
|
||||
|
|
@ -126,7 +132,12 @@ impl<T: RenderOnce> RenderOnce for Lower<T> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn render_once_escaped(self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
#[inline]
|
||||
fn render_once_escaped<E: Escape>(
|
||||
self,
|
||||
b: &mut Buffer,
|
||||
e: &E,
|
||||
) -> Result<(), RenderError> {
|
||||
let old_len = b.len();
|
||||
self.0.render_once(b)?;
|
||||
|
||||
|
|
@ -134,7 +145,7 @@ impl<T: RenderOnce> RenderOnce for Lower<T> {
|
|||
let s = content.to_lowercase();
|
||||
unsafe { b._set_len(old_len) };
|
||||
|
||||
escape::escape_to_buf(s.as_str(), b);
|
||||
e.escape_to_buf(b, s.as_str());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -144,7 +155,7 @@ impl<T: RenderOnce> RenderOnce for Lower<T> {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```text
|
||||
/// <%= "ὈΔΥΣΣΕΎΣ" | lower %>
|
||||
/// <%\html "ὈΔΥΣΣΕΎΣ" | lower %>
|
||||
/// ```
|
||||
///
|
||||
/// result:
|
||||
|
|
@ -170,9 +181,13 @@ impl<T: RenderOnce> RenderOnce for Trim<T> {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
fn render_once_escaped(self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
fn render_once_escaped<E: Escape>(
|
||||
self,
|
||||
b: &mut Buffer,
|
||||
e: &E,
|
||||
) -> Result<(), RenderError> {
|
||||
let old_len = b.len();
|
||||
self.0.render_once_escaped(b)?;
|
||||
self.0.render_once_escaped(b, e)?;
|
||||
trim_impl(b, old_len)
|
||||
}
|
||||
}
|
||||
|
|
@ -215,7 +230,7 @@ fn trim_impl(b: &mut Buffer, old_len: usize) -> Result<(), RenderError> {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```text
|
||||
/// <%= " Hello world\n" | trim %>
|
||||
/// <%\html " Hello world\n" | trim %>
|
||||
/// ```
|
||||
///
|
||||
/// result:
|
||||
|
|
@ -241,23 +256,29 @@ impl<T: RenderOnce> RenderOnce for Truncate<T> {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
fn render_once_escaped(self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
fn render_once_escaped<E: Escape>(
|
||||
self,
|
||||
b: &mut Buffer,
|
||||
e: &E,
|
||||
) -> Result<(), RenderError> {
|
||||
let old_len = b.len();
|
||||
self.0.render_once_escaped(b)?;
|
||||
self.0.render_once_escaped(b, e)?;
|
||||
truncate_impl(b, old_len, self.1)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "perf-inline", inline)]
|
||||
fn truncate_impl(
|
||||
b: &mut Buffer,
|
||||
old_len: usize,
|
||||
limit: usize,
|
||||
) -> Result<(), RenderError> {
|
||||
let new_contents = b.as_str().get(old_len..).ok_or(RenderError::BufSize)?;
|
||||
|
||||
if let Some(idx) = new_contents.char_indices().nth(limit).map(|(i, _)| i) {
|
||||
unsafe { b._set_len(old_len + idx) };
|
||||
b.push_str("...");
|
||||
if new_contents.len() > limit {
|
||||
if let Some(idx) = new_contents.char_indices().nth(limit).map(|(i, _)| i) {
|
||||
unsafe { b._set_len(old_len + idx) };
|
||||
b.push_str("...");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
@ -270,7 +291,7 @@ fn truncate_impl(
|
|||
/// The following example renders the first 20 characters of `message`
|
||||
///
|
||||
/// ```test
|
||||
/// <%= "Hello, world!" | truncate(5) %>
|
||||
/// <%\html "Hello, world!" | truncate(5) %>
|
||||
/// ```
|
||||
///
|
||||
/// result:
|
||||
|
|
@ -296,14 +317,16 @@ cfg_json! {
|
|||
impl<'a> std::io::Write for Writer<'a> {
|
||||
#[inline]
|
||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||
let buf = unsafe { std::str::from_utf8_unchecked(buf) };
|
||||
self.0.push_str(buf);
|
||||
self.write_all(buf)?;
|
||||
Ok(buf.len())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> {
|
||||
self.write(buf).map(|_| {})
|
||||
// SAFETY: serde_json only emits valid UTF-8
|
||||
let buf = unsafe { std::str::from_utf8_unchecked(buf) };
|
||||
self.0.push_str(buf);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
|
@ -317,16 +340,18 @@ cfg_json! {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
use super::escape::escape_to_buf;
|
||||
fn render_escaped<E: Escape>(
|
||||
&self,
|
||||
b: &mut Buffer,
|
||||
e: &E,
|
||||
) -> Result<(), RenderError> {
|
||||
struct Writer<'a, E: Escape>(&'a mut Buffer, &'a E);
|
||||
|
||||
struct Writer<'a>(&'a mut Buffer);
|
||||
|
||||
impl<'a> std::io::Write for Writer<'a> {
|
||||
impl<'a, E: Escape> std::io::Write for Writer<'a, E> {
|
||||
#[inline]
|
||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||
let buf = unsafe { std::str::from_utf8_unchecked(buf) };
|
||||
escape_to_buf(buf, self.0);
|
||||
self.1.escape_to_buf(self.0, buf);
|
||||
Ok(buf.len())
|
||||
}
|
||||
|
||||
|
|
@ -341,7 +366,7 @@ cfg_json! {
|
|||
}
|
||||
}
|
||||
|
||||
serde_json::to_writer(Writer(b), &self.0)
|
||||
serde_json::to_writer(Writer(b, e), &self.0)
|
||||
.map_err(|e| RenderError::new(&e.to_string()))
|
||||
}
|
||||
}
|
||||
|
|
@ -364,6 +389,8 @@ cfg_json! {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::EscapeHtml;
|
||||
|
||||
use super::*;
|
||||
|
||||
fn assert_render<T: RenderOnce>(expr: T, expected: &str) {
|
||||
|
|
@ -374,7 +401,7 @@ mod tests {
|
|||
|
||||
fn assert_render_escaped<T: RenderOnce>(expr: T, expected: &str) {
|
||||
let mut buf = Buffer::new();
|
||||
RenderOnce::render_once_escaped(expr, &mut buf).unwrap();
|
||||
RenderOnce::render_once_escaped(expr, &mut buf, &EscapeHtml).unwrap();
|
||||
assert_eq!(buf.as_str(), expected);
|
||||
}
|
||||
|
||||
|
|
@ -6,9 +6,9 @@ use std::arch::x86::*;
|
|||
use std::arch::x86_64::*;
|
||||
use std::slice;
|
||||
|
||||
use super::super::Buffer;
|
||||
use super::naive::push_escaped_str;
|
||||
use super::{ESCAPED, ESCAPED_LEN, ESCAPE_LUT};
|
||||
use crate::Buffer;
|
||||
|
||||
const VECTOR_BYTES: usize = std::mem::size_of::<__m256i>();
|
||||
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
#![allow(clippy::cast_ptr_alignment)]
|
||||
|
||||
use super::super::Buffer;
|
||||
use super::naive;
|
||||
use crate::Buffer;
|
||||
|
||||
#[cfg(target_pointer_width = "16")]
|
||||
const USIZE_BYTES: usize = 2;
|
||||
|
|
@ -34,94 +34,96 @@ static ESCAPE_LUT: [u8; 256] = [
|
|||
const ESCAPED: [&str; 5] = [""", "&", "'", "<", ">"];
|
||||
const ESCAPED_LEN: usize = 5;
|
||||
|
||||
use super::buffer::Buffer;
|
||||
use super::{Buffer, Escape};
|
||||
|
||||
/// write the escaped contents into `Buffer`
|
||||
#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), not(miri)))]
|
||||
#[cfg_attr(feature = "perf-inline", inline)]
|
||||
pub fn escape_to_buf(feed: &str, buf: &mut Buffer) {
|
||||
#[cfg(not(target_feature = "avx2"))]
|
||||
{
|
||||
use std::sync::atomic::{AtomicPtr, Ordering};
|
||||
/// A scheme for escaping strings for safe insertion into HTML.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct EscapeHtml;
|
||||
|
||||
type FnRaw = *mut ();
|
||||
static FN: AtomicPtr<()> = AtomicPtr::new(detect as FnRaw);
|
||||
impl Escape for EscapeHtml {
|
||||
type Escaped = &'static str;
|
||||
|
||||
fn detect(feed: &str, buf: &mut Buffer) {
|
||||
debug_assert!(feed.len() >= 16);
|
||||
let fun = if is_x86_feature_detected!("avx2") {
|
||||
avx2::escape
|
||||
} else if is_x86_feature_detected!("sse2") {
|
||||
sse2::escape
|
||||
} else {
|
||||
fallback::escape
|
||||
};
|
||||
const IDENT_BOOLS: bool = true;
|
||||
const IDENT_UINTS: bool = true;
|
||||
const IDENT_INTS: bool = true;
|
||||
const IDENT_FLOATS: bool = true;
|
||||
|
||||
FN.store(fun as FnRaw, Ordering::Relaxed);
|
||||
unsafe { fun(feed, buf) };
|
||||
#[inline(always)]
|
||||
fn escape(&self, c: char) -> Option<Self::Escaped> {
|
||||
match c {
|
||||
'\"' => Some("""),
|
||||
'&' => Some("&"),
|
||||
'<' => Some("<"),
|
||||
'>' => Some(">"),
|
||||
'\'' => Some("'"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), not(miri)))]
|
||||
#[cfg_attr(feature = "perf-inline", inline)]
|
||||
fn escape_to_buf(&self, buf: &mut Buffer, string: &str) {
|
||||
unsafe {
|
||||
if feed.len() < 16 {
|
||||
buf.reserve_small(feed.len() * 6);
|
||||
let l = naive::escape_small(feed, buf.as_mut_ptr().add(buf.len()));
|
||||
if string.len() < 16 {
|
||||
buf.reserve_small(string.len() * 6);
|
||||
let l = naive::escape_small(string, buf.as_mut_ptr().add(buf.len()));
|
||||
buf.advance(l);
|
||||
} else {
|
||||
let fun = FN.load(Ordering::Relaxed);
|
||||
std::mem::transmute::<FnRaw, fn(&str, &mut Buffer)>(fun)(feed, buf);
|
||||
#[cfg(target_feature = "avx2")]
|
||||
avx2::escape(string, buf);
|
||||
#[cfg(all(not(target_feature = "avx2"), target_feature = "sse2"))]
|
||||
sse2::escape(string, buf);
|
||||
#[cfg(not(any(target_feature = "avx2", target_feature = "sse2")))]
|
||||
{
|
||||
use std::sync::atomic::{AtomicPtr, Ordering};
|
||||
|
||||
type FnRaw = *mut ();
|
||||
static FN: AtomicPtr<()> = AtomicPtr::new(detect as FnRaw);
|
||||
|
||||
fn detect(string: &str, buf: &mut Buffer) {
|
||||
debug_assert!(string.len() >= 16);
|
||||
let fun = if is_x86_feature_detected!("avx2") {
|
||||
avx2::escape
|
||||
} else if is_x86_feature_detected!("sse2") {
|
||||
sse2::escape
|
||||
} else {
|
||||
fallback::escape
|
||||
};
|
||||
|
||||
FN.store(fun as FnRaw, Ordering::Relaxed);
|
||||
unsafe { fun(string, buf) };
|
||||
}
|
||||
|
||||
let fun = FN.load(Ordering::Relaxed);
|
||||
std::mem::transmute::<FnRaw, fn(&str, &mut Buffer)>(fun)(string, buf);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_feature = "avx2")]
|
||||
unsafe {
|
||||
if feed.len() < 16 {
|
||||
buf.reserve_small(feed.len() * 6);
|
||||
let l = naive::escape_small(feed, buf.as_mut_ptr().add(buf.len()));
|
||||
buf.advance(l);
|
||||
} else if cfg!(target_feature = "avx2") {
|
||||
avx2::escape(feed, buf);
|
||||
#[cfg(not(all(any(target_arch = "x86", target_arch = "x86_64"), not(miri))))]
|
||||
#[cfg_attr(feature = "perf-inline", inline)]
|
||||
fn escape_to_buf(&self, buffer: &mut Buffer, string: &str) {
|
||||
unsafe {
|
||||
if cfg!(miri) {
|
||||
let bp = feed.as_ptr();
|
||||
naive::escape(buf, bp, bp, bp.add(feed.len()))
|
||||
} else if feed.len() < 16 {
|
||||
buf.reserve_small(feed.len() * 6);
|
||||
let l = naive::escape_small(feed, buf.as_mut_ptr().add(buf.len()));
|
||||
buf.advance(l);
|
||||
} else {
|
||||
fallback::escape(feed, buf)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// write the escaped contents into `Buffer`
|
||||
#[cfg(not(all(any(target_arch = "x86", target_arch = "x86_64"), not(miri))))]
|
||||
#[cfg_attr(feature = "perf-inline", inline)]
|
||||
pub fn escape_to_buf(feed: &str, buf: &mut Buffer) {
|
||||
unsafe {
|
||||
if cfg!(miri) {
|
||||
let bp = feed.as_ptr();
|
||||
naive::escape(buf, bp, bp, bp.add(feed.len()))
|
||||
} else if feed.len() < 16 {
|
||||
buf.reserve_small(feed.len() * 6);
|
||||
let l = naive::escape_small(feed, buf.as_mut_ptr().add(buf.len()));
|
||||
buf.advance(l);
|
||||
} else {
|
||||
fallback::escape(feed, buf)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// write the escaped contents into `String`
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use sailfish::runtime::escape::escape_to_string;
|
||||
///
|
||||
/// let mut buf = String::new();
|
||||
/// escape_to_string("<h1>Hello, world!</h1>", &mut buf);
|
||||
/// assert_eq!(buf, "<h1>Hello, world!</h1>");
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn escape_to_string(feed: &str, s: &mut String) {
|
||||
let mut s2 = String::new();
|
||||
std::mem::swap(s, &mut s2);
|
||||
let mut buf = Buffer::from(s2);
|
||||
escape_to_buf(feed, &mut buf);
|
||||
let mut s2 = buf.into_string();
|
||||
std::mem::swap(s, &mut s2);
|
||||
#[deprecated = "Use [`EscapeHtml::escape_to_buf`] instead"]
|
||||
#[inline(always)]
|
||||
fn escape_to_buf(feed: &str, buf: &mut Buffer) {
|
||||
EscapeHtml.escape_to_buf(buf, feed)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
@ -130,7 +132,7 @@ mod tests {
|
|||
|
||||
fn escape(feed: &str) -> String {
|
||||
let mut s = String::new();
|
||||
escape_to_string(feed, &mut s);
|
||||
EscapeHtml.escape_to_string(&mut s, feed);
|
||||
s
|
||||
}
|
||||
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
use std::ptr;
|
||||
use std::slice;
|
||||
|
||||
use super::super::utils::memcpy_16;
|
||||
use super::super::Buffer;
|
||||
use super::{ESCAPED, ESCAPED_LEN, ESCAPE_LUT};
|
||||
use crate::utils::memcpy_16;
|
||||
use crate::Buffer;
|
||||
|
||||
#[inline]
|
||||
pub(super) unsafe fn escape(
|
||||
|
|
@ -6,9 +6,9 @@ use std::arch::x86::*;
|
|||
use std::arch::x86_64::*;
|
||||
use std::slice;
|
||||
|
||||
use super::super::Buffer;
|
||||
use super::naive::push_escaped_str;
|
||||
use super::{ESCAPED, ESCAPED_LEN, ESCAPE_LUT};
|
||||
use crate::Buffer;
|
||||
|
||||
const VECTOR_BYTES: usize = std::mem::size_of::<__m128i>();
|
||||
|
||||
|
|
@ -8,7 +8,7 @@
|
|||
//!
|
||||
//! In most cases you don't need to care about the `runtime` module in this crate, but
|
||||
//! if you want to render custom data inside templates, you must implement
|
||||
//! `runtime::Render` trait for that type.
|
||||
//! [`Render`] or [`RenderOnce`] for that type.
|
||||
//!
|
||||
//! ```ignore
|
||||
//! use sailfish::RenderOnce;
|
||||
|
|
@ -31,14 +31,29 @@
|
|||
#![doc(
|
||||
html_logo_url = "https://raw.githubusercontent.com/rust-sailfish/sailfish/master/resources/icon.png"
|
||||
)]
|
||||
#![cfg_attr(sailfish_nightly, feature(core_intrinsics))]
|
||||
#![cfg_attr(sailfish_nightly, feature(likely_unlikely))]
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
#![allow(clippy::redundant_closure)]
|
||||
#![deny(missing_docs)]
|
||||
|
||||
pub mod runtime;
|
||||
#[macro_use]
|
||||
mod utils;
|
||||
|
||||
mod buffer;
|
||||
mod escape;
|
||||
pub mod filter;
|
||||
mod html_escape;
|
||||
mod render;
|
||||
#[doc(hidden)]
|
||||
pub mod runtime;
|
||||
mod size_hint;
|
||||
|
||||
pub use buffer::Buffer;
|
||||
pub use escape::{Escape, EscapeJsonString};
|
||||
pub use html_escape::EscapeHtml;
|
||||
pub use render::{Render, RenderError, RenderMut, RenderOnce, RenderResult};
|
||||
pub use size_hint::SizeHint;
|
||||
|
||||
pub use runtime::{Buffer, Render, RenderError, RenderMut, RenderOnce, RenderResult};
|
||||
#[cfg(feature = "derive")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "derive")))]
|
||||
pub use sailfish_macros::{Render, RenderMut, RenderOnce};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use std::borrow::Cow;
|
||||
use std::cell::{Ref, RefMut};
|
||||
use std::fmt;
|
||||
use std::fmt::{self, Arguments, Write};
|
||||
use std::num::{
|
||||
NonZeroI128, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, NonZeroIsize,
|
||||
NonZeroU128, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize, Wrapping,
|
||||
|
|
@ -9,12 +9,9 @@ use std::path::{Path, PathBuf};
|
|||
use std::rc::Rc;
|
||||
use std::sync::{Arc, MutexGuard, RwLockReadGuard, RwLockWriteGuard};
|
||||
|
||||
use crate::runtime::SizeHint;
|
||||
use super::{Buffer, Escape, SizeHint};
|
||||
|
||||
use super::buffer::Buffer;
|
||||
use super::escape;
|
||||
|
||||
/// types which can be rendered inside buffer block (`<%= %>`) by reference
|
||||
/// types which can be rendered inside buffer block (`<%- %>`) by reference
|
||||
///
|
||||
/// If you want to render the custom data, you must implement this trait and specify
|
||||
/// the behaviour.
|
||||
|
|
@ -24,24 +21,23 @@ use super::escape;
|
|||
/// This trait allows modifying the previously-rendered contents or even decreasing the
|
||||
/// buffer size. However, such an operation easily cause unexpected rendering results.
|
||||
/// In order to avoid this, implementors should ensure that the contents which is already
|
||||
/// rendered won't be changed during `render` or `render_escaped` method is called.
|
||||
/// rendered won't be changed during [`render_once`] or [`render_once_escaped`] method is
|
||||
/// called.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use sailfish::runtime::{Buffer, Render, RenderError};
|
||||
/// use sailfish::{Buffer, RenderOnce, RenderError};
|
||||
///
|
||||
/// struct MyU64(u64);
|
||||
///
|
||||
/// impl Render for MyU64 {
|
||||
/// impl RenderOnce for MyU64 {
|
||||
/// #[inline]
|
||||
/// fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
/// self.0.render(b)
|
||||
/// fn render_once(self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
/// self.0.render_once(b)
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
/// types which can be rendered inside buffer block (`<%- %>`)
|
||||
pub trait RenderOnce: Sized {
|
||||
/// render to `Buffer` without escaping
|
||||
///
|
||||
|
|
@ -75,11 +71,12 @@ pub trait RenderOnce: Sized {
|
|||
|
||||
/// render to `Buffer` with HTML escaping
|
||||
#[inline]
|
||||
fn render_once_escaped(self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
let mut tmp = Buffer::new();
|
||||
self.render_once(&mut tmp)?;
|
||||
escape::escape_to_buf(tmp.as_str(), b);
|
||||
Ok(())
|
||||
fn render_once_escaped<E: Escape>(
|
||||
self,
|
||||
b: &mut Buffer,
|
||||
e: &E,
|
||||
) -> Result<(), RenderError> {
|
||||
default_render_once_escaped(self, b, e)
|
||||
}
|
||||
|
||||
/// Render the template and return the rendering result as `RenderResult`
|
||||
|
|
@ -110,11 +107,12 @@ pub trait RenderMut {
|
|||
|
||||
/// See [`RenderOnce::render_once_escaped`] for more information.
|
||||
#[inline]
|
||||
fn render_mut_escaped(&mut self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
let mut tmp = Buffer::new();
|
||||
self.render_mut(&mut tmp)?;
|
||||
escape::escape_to_buf(tmp.as_str(), b);
|
||||
Ok(())
|
||||
fn render_mut_escaped<E: Escape>(
|
||||
&mut self,
|
||||
b: &mut Buffer,
|
||||
e: &E,
|
||||
) -> Result<(), RenderError> {
|
||||
default_render_mut_escaped(self, b, e)
|
||||
}
|
||||
|
||||
/// See [`RenderOnce::render_once_to_string`] for more information.
|
||||
|
|
@ -137,11 +135,12 @@ pub trait Render {
|
|||
|
||||
/// See [`RenderOnce::render_once_escaped`] for more information.
|
||||
#[inline]
|
||||
fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
let mut tmp = Buffer::new();
|
||||
self.render(&mut tmp)?;
|
||||
escape::escape_to_buf(tmp.as_str(), b);
|
||||
Ok(())
|
||||
fn render_escaped<E: Escape>(
|
||||
&self,
|
||||
b: &mut Buffer,
|
||||
e: &E,
|
||||
) -> Result<(), RenderError> {
|
||||
default_render_escaped(self, b, e)
|
||||
}
|
||||
|
||||
/// See [`RenderOnce::render_once_to_string`] for more information.
|
||||
|
|
@ -161,9 +160,9 @@ impl<T: Render + ?Sized> RenderMut for T {
|
|||
fn render_mut(&mut self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
self.render(b)
|
||||
}
|
||||
|
||||
fn render_mut_escaped(&mut self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
self.render_escaped(b)
|
||||
|
||||
fn render_mut_escaped<E: Escape>(&mut self, b: &mut Buffer, e: &E) -> Result<(), RenderError> {
|
||||
self.render_escaped(b, e)
|
||||
}
|
||||
|
||||
fn render_mut_to_string(&mut self) -> RenderResult {
|
||||
|
|
@ -176,8 +175,8 @@ impl<T: RenderMut> RenderOnce for T {
|
|||
self.render_mut(b)
|
||||
}
|
||||
|
||||
fn render_once_escaped(mut self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
self.render_mut_escaped(b)
|
||||
fn render_once_escaped<E: Escape>(mut self, b: &mut Buffer, e: &E) -> Result<(), RenderError> {
|
||||
self.render_mut_escaped(b, e)
|
||||
}
|
||||
|
||||
fn render_once_to_string(mut self) -> RenderResult {
|
||||
|
|
@ -185,6 +184,42 @@ impl<T: RenderMut> RenderOnce for T {
|
|||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn default_render_once_escaped<T: RenderOnce, E: Escape>(
|
||||
t: T,
|
||||
b: &mut Buffer,
|
||||
e: &E,
|
||||
) -> Result<(), RenderError> {
|
||||
let mut tmp = Buffer::new();
|
||||
t.render_once(&mut tmp)?;
|
||||
e.escape_to_buf(b, tmp.as_str());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn default_render_mut_escaped<T: RenderMut + ?Sized, E: Escape>(
|
||||
t: &mut T,
|
||||
b: &mut Buffer,
|
||||
e: &E,
|
||||
) -> Result<(), RenderError> {
|
||||
let mut tmp = Buffer::new();
|
||||
t.render_mut(&mut tmp)?;
|
||||
e.escape_to_buf(b, tmp.as_str());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn default_render_escaped<T: Render + ?Sized, E: Escape>(
|
||||
t: &T,
|
||||
b: &mut Buffer,
|
||||
e: &E,
|
||||
) -> Result<(), RenderError> {
|
||||
let mut tmp = Buffer::new();
|
||||
t.render(&mut tmp)?;
|
||||
e.escape_to_buf(b, tmp.as_str());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl Render for String {
|
||||
#[inline]
|
||||
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
|
|
@ -193,8 +228,12 @@ impl Render for String {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
escape::escape_to_buf(self, b);
|
||||
fn render_escaped<E: Escape>(
|
||||
&self,
|
||||
b: &mut Buffer,
|
||||
e: &E,
|
||||
) -> Result<(), RenderError> {
|
||||
e.escape_to_buf(b, self);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -207,8 +246,12 @@ impl Render for str {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
escape::escape_to_buf(self, b);
|
||||
fn render_escaped<E: Escape>(
|
||||
&self,
|
||||
b: &mut Buffer,
|
||||
e: &E,
|
||||
) -> Result<(), RenderError> {
|
||||
e.escape_to_buf(b, self);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -221,14 +264,14 @@ impl Render for char {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
match *self {
|
||||
'\"' => b.push_str("""),
|
||||
'&' => b.push_str("&"),
|
||||
'<' => b.push_str("<"),
|
||||
'>' => b.push_str(">"),
|
||||
'\'' => b.push_str("'"),
|
||||
_ => b.push(*self),
|
||||
fn render_escaped<E: Escape>(
|
||||
&self,
|
||||
b: &mut Buffer,
|
||||
e: &E,
|
||||
) -> Result<(), RenderError> {
|
||||
match e.escape(*self) {
|
||||
Some(s) => b.push_str(s.as_ref()),
|
||||
None => b.push(*self),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -243,8 +286,12 @@ impl Render for PathBuf {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
escape::escape_to_buf(self.to_str().ok_or(RenderError::Fmt(fmt::Error))?, b);
|
||||
fn render_escaped<E: Escape>(
|
||||
&self,
|
||||
b: &mut Buffer,
|
||||
e: &E,
|
||||
) -> Result<(), RenderError> {
|
||||
e.escape_to_buf(b, self.to_str().ok_or(RenderError::Fmt(fmt::Error))?);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -258,8 +305,12 @@ impl Render for Path {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
escape::escape_to_buf(self.to_str().ok_or(RenderError::Fmt(fmt::Error))?, b);
|
||||
fn render_escaped<E: Escape>(
|
||||
&self,
|
||||
b: &mut Buffer,
|
||||
e: &E,
|
||||
) -> Result<(), RenderError> {
|
||||
e.escape_to_buf(b, self.to_str().ok_or(RenderError::Fmt(fmt::Error))?);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -297,13 +348,26 @@ impl Render for bool {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
self.render(b)
|
||||
fn render_escaped<E: Escape>(
|
||||
&self,
|
||||
b: &mut Buffer,
|
||||
e: &E,
|
||||
) -> Result<(), RenderError> {
|
||||
if E::IDENT_BOOLS {
|
||||
self.render(b)
|
||||
} else {
|
||||
if *self {
|
||||
e.escape_to_buf(b, "true");
|
||||
} else {
|
||||
e.escape_to_buf(b, "false");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! render_int {
|
||||
($($int:ty),*) => {
|
||||
($ident_tag:ident, $($int:ty),*) => {
|
||||
$(
|
||||
impl Render for $int {
|
||||
#[cfg_attr(feature = "perf-inline", inline)]
|
||||
|
|
@ -326,16 +390,21 @@ macro_rules! render_int {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
// push_str without escape
|
||||
self.render(b)
|
||||
fn render_escaped<E: Escape>(&self, b: &mut Buffer, e: &E) -> Result<(), RenderError> {
|
||||
if E::$ident_tag {
|
||||
// push_str without escape
|
||||
self.render(b)
|
||||
} else {
|
||||
default_render_once_escaped(self, b, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
render_int!(u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, usize, isize);
|
||||
render_int!(IDENT_UINTS, u8, u16, u32, u64, u128, usize);
|
||||
render_int!(IDENT_INTS, i8, i16, i32, i64, i128, isize);
|
||||
|
||||
impl Render for f32 {
|
||||
#[cfg_attr(feature = "perf-inline", inline)]
|
||||
|
|
@ -360,9 +429,16 @@ impl Render for f32 {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
// escape string
|
||||
self.render(b)
|
||||
fn render_escaped<E: Escape>(
|
||||
&self,
|
||||
b: &mut Buffer,
|
||||
e: &E,
|
||||
) -> Result<(), RenderError> {
|
||||
if E::IDENT_FLOATS {
|
||||
self.render(b)
|
||||
} else {
|
||||
default_render_escaped(self, b, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -389,9 +465,42 @@ impl Render for f64 {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
// escape string
|
||||
self.render(b)
|
||||
fn render_escaped<E: Escape>(
|
||||
&self,
|
||||
b: &mut Buffer,
|
||||
e: &E,
|
||||
) -> Result<(), RenderError> {
|
||||
if E::IDENT_FLOATS {
|
||||
self.render(b)
|
||||
} else {
|
||||
default_render_escaped(self, b, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for Arguments<'_> {
|
||||
#[inline]
|
||||
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
if let Some(s) = self.as_str() {
|
||||
b.push_str(s);
|
||||
Ok(())
|
||||
} else {
|
||||
b.write_fmt(*self).map_err(RenderError::Fmt)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn render_escaped<E: Escape>(
|
||||
&self,
|
||||
b: &mut Buffer,
|
||||
e: &E,
|
||||
) -> Result<(), RenderError> {
|
||||
if let Some(s) = self.as_str() {
|
||||
e.escape_to_buf(b, s);
|
||||
Ok(())
|
||||
} else {
|
||||
default_render_escaped(self, b, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -408,8 +517,8 @@ macro_rules! render_deref {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
(**self).render_escaped(b)
|
||||
fn render_escaped<E: Escape>(&self, b: &mut Buffer, e: &E) -> Result<(), RenderError> {
|
||||
(**self).render_escaped(b, e)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -418,17 +527,17 @@ macro_rules! render_deref {
|
|||
// render_ref!(['a, T] [&'a T: Render] T);
|
||||
// render_ref!(['a] [] String);
|
||||
|
||||
render_deref!(['a, T: Render + ?Sized] [] &'a T);
|
||||
render_deref!(['a, T: Render + ?Sized] [] &'a mut T);
|
||||
render_deref!([T: Render + ?Sized] [] &T);
|
||||
render_deref!([T: Render + ?Sized] [] &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>, [*]);
|
||||
render_deref!([T: Render + ToOwned + ?Sized] [] Cow<'_, T>);
|
||||
render_deref!([T: Render + ?Sized] [] Ref<'_, T>, [*]);
|
||||
render_deref!([T: Render + ?Sized] [] RefMut<'_, T>, [*]);
|
||||
render_deref!([T: Render + ?Sized] [] MutexGuard<'_, T>, [*]);
|
||||
render_deref!([T: Render + ?Sized] [] RwLockReadGuard<'_, T>, [*]);
|
||||
render_deref!([T: Render + ?Sized] [] RwLockWriteGuard<'_, T>, [*]);
|
||||
|
||||
macro_rules! render_nonzero {
|
||||
($($type:ty,)*) => {
|
||||
|
|
@ -440,8 +549,8 @@ macro_rules! render_nonzero {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
self.get().render_escaped(b)
|
||||
fn render_escaped<E: Escape>(&self, b: &mut Buffer, e: &E) -> Result<(), RenderError> {
|
||||
self.get().render_escaped(b, e)
|
||||
}
|
||||
}
|
||||
)*
|
||||
|
|
@ -470,8 +579,12 @@ impl<T: Render> Render for Wrapping<T> {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
self.0.render_escaped(b)
|
||||
fn render_escaped<E: Escape>(
|
||||
&self,
|
||||
b: &mut Buffer,
|
||||
e: &E,
|
||||
) -> Result<(), RenderError> {
|
||||
self.0.render_escaped(b, e)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -495,10 +608,10 @@ macro_rules! render_tuple {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
fn render_escaped<Esc: Escape>(&self, b: &mut Buffer, e: &Esc) -> Result<(), RenderError> {
|
||||
#[allow(non_snake_case)]
|
||||
let ($($T,)+) = self;
|
||||
$($T.render_escaped(b)?;)+
|
||||
$($T.render_escaped(b, e)?;)+
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -524,10 +637,10 @@ macro_rules! render_once_tuple {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
fn render_once_escaped(self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
fn render_once_escaped<Esc: Escape>(self, b: &mut Buffer, e: &Esc) -> Result<(), RenderError> {
|
||||
#[allow(non_snake_case)]
|
||||
let ($($T,)+) = self;
|
||||
$($T.render_once_escaped(b)?;)+
|
||||
$($T.render_once_escaped(b, e)?;)+
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -593,9 +706,12 @@ pub type RenderResult = Result<String, RenderError>;
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::error::Error;
|
||||
|
||||
use crate::EscapeHtml;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn receiver_coercion() {
|
||||
let mut b = Buffer::new();
|
||||
|
|
@ -608,24 +724,24 @@ mod tests {
|
|||
|
||||
RenderOnce::render_once(&true, &mut b).unwrap();
|
||||
RenderOnce::render_once(&&false, &mut b).unwrap();
|
||||
RenderOnce::render_once_escaped(&&&true, &mut b).unwrap();
|
||||
RenderOnce::render_once_escaped(&&&&false, &mut b).unwrap();
|
||||
RenderOnce::render_once_escaped(&&&true, &mut b, &EscapeHtml).unwrap();
|
||||
RenderOnce::render_once_escaped(&&&&false, &mut b, &EscapeHtml).unwrap();
|
||||
assert_eq!(b.as_str(), "truefalsetruefalse");
|
||||
b.clear();
|
||||
|
||||
let s = "apple";
|
||||
RenderOnce::render_once_escaped(&s, &mut b).unwrap();
|
||||
RenderOnce::render_once_escaped(&s, &mut b).unwrap();
|
||||
RenderOnce::render_once_escaped(&&s, &mut b).unwrap();
|
||||
RenderOnce::render_once_escaped(&&&s, &mut b).unwrap();
|
||||
RenderOnce::render_once_escaped(&&&&s, &mut b).unwrap();
|
||||
RenderOnce::render_once_escaped(&s, &mut b, &EscapeHtml).unwrap();
|
||||
RenderOnce::render_once_escaped(&s, &mut b, &EscapeHtml).unwrap();
|
||||
RenderOnce::render_once_escaped(&&s, &mut b, &EscapeHtml).unwrap();
|
||||
RenderOnce::render_once_escaped(&&&s, &mut b, &EscapeHtml).unwrap();
|
||||
RenderOnce::render_once_escaped(&&&&s, &mut b, &EscapeHtml).unwrap();
|
||||
assert_eq!(b.as_str(), "appleappleappleappleapple");
|
||||
b.clear();
|
||||
|
||||
RenderOnce::render_once_escaped(&'c', &mut b).unwrap();
|
||||
RenderOnce::render_once_escaped(&&'<', &mut b).unwrap();
|
||||
RenderOnce::render_once_escaped(&&&'&', &mut b).unwrap();
|
||||
RenderOnce::render_once_escaped(&&&&' ', &mut b).unwrap();
|
||||
RenderOnce::render_once_escaped(&'c', &mut b, &EscapeHtml).unwrap();
|
||||
RenderOnce::render_once_escaped(&&'<', &mut b, &EscapeHtml).unwrap();
|
||||
RenderOnce::render_once_escaped(&&&'&', &mut b, &EscapeHtml).unwrap();
|
||||
RenderOnce::render_once_escaped(&&&&' ', &mut b, &EscapeHtml).unwrap();
|
||||
assert_eq!(b.as_str(), "c<& ");
|
||||
b.clear();
|
||||
}
|
||||
|
|
@ -638,10 +754,10 @@ mod tests {
|
|||
let mut b = Buffer::new();
|
||||
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();
|
||||
Render::render_escaped(Path::new("<"), &mut b).unwrap();
|
||||
Render::render_escaped(&Path::new("d"), &mut b).unwrap();
|
||||
Render::render_escaped(&Rc::new(4u32), &mut b, &EscapeHtml).unwrap();
|
||||
Render::render_escaped(&Rc::new(2.3f32), &mut b, &EscapeHtml).unwrap();
|
||||
Render::render_escaped(Path::new("<"), &mut b, &EscapeHtml).unwrap();
|
||||
Render::render_escaped(&Path::new("d"), &mut b, &EscapeHtml).unwrap();
|
||||
|
||||
assert_eq!(b.as_str(), "ab42.3<d");
|
||||
}
|
||||
|
|
@ -650,17 +766,19 @@ mod tests {
|
|||
fn float() {
|
||||
let mut b = Buffer::new();
|
||||
|
||||
RenderOnce::render_once_escaped(0.0f64, &mut b).unwrap();
|
||||
RenderOnce::render_once_escaped(std::f64::INFINITY, &mut b).unwrap();
|
||||
RenderOnce::render_once_escaped(std::f64::NEG_INFINITY, &mut b).unwrap();
|
||||
RenderOnce::render_once_escaped(std::f64::NAN, &mut b).unwrap();
|
||||
RenderOnce::render_once_escaped(0.0f64, &mut b, &EscapeHtml).unwrap();
|
||||
RenderOnce::render_once_escaped(std::f64::INFINITY, &mut b, &EscapeHtml).unwrap();
|
||||
RenderOnce::render_once_escaped(std::f64::NEG_INFINITY, &mut b, &EscapeHtml)
|
||||
.unwrap();
|
||||
RenderOnce::render_once_escaped(std::f64::NAN, &mut b, &EscapeHtml).unwrap();
|
||||
assert_eq!(b.as_str(), "0.0inf-infNaN");
|
||||
b.clear();
|
||||
|
||||
RenderOnce::render_once_escaped(0.0f32, &mut b).unwrap();
|
||||
RenderOnce::render_once_escaped(std::f32::INFINITY, &mut b).unwrap();
|
||||
RenderOnce::render_once_escaped(std::f32::NEG_INFINITY, &mut b).unwrap();
|
||||
RenderOnce::render_once_escaped(std::f32::NAN, &mut b).unwrap();
|
||||
RenderOnce::render_once_escaped(0.0f32, &mut b, &EscapeHtml).unwrap();
|
||||
RenderOnce::render_once_escaped(std::f32::INFINITY, &mut b, &EscapeHtml).unwrap();
|
||||
RenderOnce::render_once_escaped(std::f32::NEG_INFINITY, &mut b, &EscapeHtml)
|
||||
.unwrap();
|
||||
RenderOnce::render_once_escaped(std::f32::NAN, &mut b, &EscapeHtml).unwrap();
|
||||
assert_eq!(b.as_str(), "0.0inf-infNaN");
|
||||
}
|
||||
|
||||
|
|
@ -669,7 +787,9 @@ mod tests {
|
|||
let mut b = Buffer::new();
|
||||
|
||||
let funcs: Vec<fn(char, &mut Buffer) -> Result<(), RenderError>> =
|
||||
vec![RenderOnce::render_once, RenderOnce::render_once_escaped];
|
||||
vec![RenderOnce::render_once, |c, b| {
|
||||
RenderOnce::render_once_escaped(c, b, &EscapeHtml)
|
||||
}];
|
||||
|
||||
for func in funcs {
|
||||
func('a', &mut b).unwrap();
|
||||
|
|
@ -694,7 +814,12 @@ mod tests {
|
|||
fn test_nonzero() {
|
||||
let mut b = Buffer::with_capacity(2);
|
||||
RenderOnce::render_once(NonZeroU8::new(10).unwrap(), &mut b).unwrap();
|
||||
RenderOnce::render_once_escaped(NonZeroI16::new(-20).unwrap(), &mut b).unwrap();
|
||||
RenderOnce::render_once_escaped(
|
||||
NonZeroI16::new(-20).unwrap(),
|
||||
&mut b,
|
||||
&EscapeHtml,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(b.as_str(), "10-20");
|
||||
}
|
||||
|
||||
|
|
@ -713,6 +838,6 @@ mod tests {
|
|||
|
||||
let err = RenderError::BufSize;
|
||||
assert!(err.source().is_none());
|
||||
format!("{}", err);
|
||||
_ = format!("{}", err);
|
||||
}
|
||||
}
|
||||
34
sailfish/src/runtime.rs
Normal file
34
sailfish/src/runtime.rs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
use crate::RenderError;
|
||||
|
||||
use crate::{Buffer, Escape, EscapeHtml, EscapeJsonString, RenderOnce};
|
||||
|
||||
pub use crate::filter;
|
||||
|
||||
#[inline(always)]
|
||||
pub fn esc_html() -> EscapeHtml {
|
||||
EscapeHtml
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn esc_json() -> EscapeJsonString {
|
||||
EscapeJsonString
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn render<T: RenderOnce>(buf: &mut Buffer, value: T) -> Result<(), RenderError> {
|
||||
value.render_once(buf)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn render_escaped<T: RenderOnce, E: Escape>(
|
||||
buf: &mut Buffer,
|
||||
value: T,
|
||||
escape: &E,
|
||||
) -> Result<(), RenderError> {
|
||||
value.render_once_escaped(buf, escape)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn render_text(buf: &mut Buffer, value: &str) {
|
||||
buf.push_str(value)
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
use crate::RenderError;
|
||||
|
||||
use super::{Buffer, RenderOnce};
|
||||
|
||||
#[doc(hidden)]
|
||||
#[inline(always)]
|
||||
pub fn render<T: RenderOnce>(buf: &mut Buffer, value: T) -> Result<(), RenderError> {
|
||||
value.render_once(buf)
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[inline(always)]
|
||||
pub fn render_escaped<T: RenderOnce>(
|
||||
buf: &mut Buffer,
|
||||
value: T,
|
||||
) -> Result<(), RenderError> {
|
||||
value.render_once_escaped(buf)
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[inline(always)]
|
||||
pub fn render_text(buf: &mut Buffer, value: &str) {
|
||||
buf.push_str(value)
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
//! Sailfish runtime
|
||||
|
||||
#[macro_use]
|
||||
mod utils;
|
||||
|
||||
mod alias_funcs;
|
||||
mod buffer;
|
||||
pub mod escape;
|
||||
pub mod filter;
|
||||
mod render;
|
||||
mod size_hint;
|
||||
|
||||
pub use buffer::Buffer;
|
||||
pub use render::{Render, RenderError, RenderMut, RenderOnce, RenderResult};
|
||||
pub use size_hint::SizeHint;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub use alias_funcs::{render, render_escaped, render_text};
|
||||
|
|
@ -13,7 +13,7 @@ macro_rules! cfg_json {
|
|||
#[cfg(sailfish_nightly)]
|
||||
macro_rules! likely {
|
||||
($val:expr) => {
|
||||
std::intrinsics::likely($val)
|
||||
std::hint::likely($val)
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -27,7 +27,7 @@ macro_rules! likely {
|
|||
#[cfg(sailfish_nightly)]
|
||||
macro_rules! unlikely {
|
||||
($val:expr) => {
|
||||
std::intrinsics::unlikely($val)
|
||||
std::hint::unlikely($val)
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -9,7 +9,7 @@ unlet b:current_syntax
|
|||
syn include @rustSyntax syntax/rust.vim
|
||||
|
||||
syn region sailfishCodeBlock matchgroup=sailfishTag start=/<%/ keepend end=/%>/ contains=@rustSyntax
|
||||
syn region sailfishBufferBlock matchgroup=sailfishTag start=/<%=/ keepend end=/%>/ contains=@rustSyntax
|
||||
syn region sailfishBufferBlock matchgroup=sailfishTag start=/<%-/ keepend end=/%>/ contains=@rustSyntax
|
||||
syn region sailfishCommentBlock start=/<%#/ end=/%>/
|
||||
|
||||
" Redefine htmlTag so that it can contain jspExpr
|
||||
|
|
|
|||
|
|
@ -385,10 +385,13 @@
|
|||
"patterns": [
|
||||
{
|
||||
"name": "source.rust.embedded.html",
|
||||
"begin": "<(%|\\?)(=|-)?",
|
||||
"begin": "<(%|\\?)(\\(\\w+)|-)?",
|
||||
"beginCaptures": {
|
||||
"0": {
|
||||
"name": "punctuation.definition.tag.begin.html"
|
||||
},
|
||||
"2": {
|
||||
"name": "entity.name.function.sailfish"
|
||||
}
|
||||
},
|
||||
"end": "(%|\\?)>",
|
||||
|
|
@ -407,4 +410,4 @@
|
|||
}
|
||||
},
|
||||
"scopeName": "source.sailfish"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue