Refactor to support custom escaping schemes
This commit is contained in:
parent
d97885392b
commit
6e09ca7464
|
@ -1,432 +0,0 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "basic-toml"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7bfc506e7a2370ec239e1d072507b2a80c833083699d3c6fa176fbb4de8448c6"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "diff"
|
||||
version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "filetime"
|
||||
version = "0.2.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4029edd3e734da6fe05b6cd7bd2960760a616bd2ddd0d59a0124746d6272af0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7dfda62a12f55daeae5015f81b0baea145391cb4520f86c248fc615d72640d12"
|
||||
|
||||
[[package]]
|
||||
name = "home"
|
||||
version = "0.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "integration-tests"
|
||||
version = "0.8.3"
|
||||
dependencies = [
|
||||
"pretty_assertions",
|
||||
"sailfish",
|
||||
"sailfish-compiler",
|
||||
"sailfish-macros",
|
||||
"serde_json",
|
||||
"trybuild",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
|
||||
|
||||
[[package]]
|
||||
name = "itoap"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9028f49264629065d057f340a86acb84867925865f73bbf8d47b4d149a7e88b8"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.149"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
|
||||
|
||||
[[package]]
|
||||
name = "pretty_assertions"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66"
|
||||
dependencies = [
|
||||
"diff",
|
||||
"yansi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
|
||||
|
||||
[[package]]
|
||||
name = "sailfish"
|
||||
version = "0.8.3"
|
||||
dependencies = [
|
||||
"itoap",
|
||||
"ryu",
|
||||
"sailfish-macros",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sailfish-compiler"
|
||||
version = "0.8.3"
|
||||
dependencies = [
|
||||
"filetime",
|
||||
"home",
|
||||
"memchr",
|
||||
"pretty_assertions",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"syn",
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sailfish-macros"
|
||||
version = "0.8.3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"sailfish-compiler",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.189"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e422a44e74ad4001bdc8eede9a4570ab52f71190e9c076d14369f38b9200537"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.189"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e48d1f918009ce3145511378cf68d613e3b3d9137d67272562080d68a2b32d5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.107"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "trybuild"
|
||||
version = "1.0.85"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "196a58260a906cedb9bf6d8034b6379d0c11f552416960452f267402ceeddff1"
|
||||
dependencies = [
|
||||
"basic-toml",
|
||||
"glob",
|
||||
"once_cell",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"termcolor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[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-util"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[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 = "windows-sys"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.5.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3b801d0e0a6726477cc207f60162da452f3a95adb368399bef20a946e06f65c"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yansi"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"
|
|
@ -40,7 +40,7 @@ Template file (templates/hello.stpl):
|
|||
<html>
|
||||
<body>
|
||||
<% for msg in &messages { %>
|
||||
<div><%= msg %></div>
|
||||
<div><%\html msg %></div>
|
||||
<% } %>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -6,7 +6,7 @@ You can control the rendering behaviour via `template` attribute.
|
|||
|
||||
```rust
|
||||
#[derive(RenderOnce)]
|
||||
#[template(path = "template.stpl", escape = false)]
|
||||
#[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"
|
||||
|
@ -19,13 +19,12 @@ 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.
|
||||
|
||||
|
||||
## Syntax
|
||||
|
||||
- Apply filter and HTML escaping
|
||||
|
||||
```rhtml
|
||||
<%= expression | filter %>
|
||||
<%\html expression | filter %>
|
||||
```
|
||||
|
||||
- Apply filter only
|
||||
|
@ -36,4 +35,4 @@ Example:
|
|||
|
||||
## Built-In Filters
|
||||
|
||||
Built-In filters can be found in [`sailfish::runtime::filter`](https://docs.rs/sailfish/latest/sailfish/runtime/filter/index.html) module.
|
||||
Built-In filters can be found in [`sailfish::filter`](https://docs.rs/sailfish/latest/sailfish/runtime/filter/index.html) module.
|
||||
|
|
|
@ -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 %><% ; %>
|
||||
```
|
||||
|
|
|
@ -5905,8 +5905,8 @@ process.umask = function() { return 0; };
|
|||
// following template settings to use alternative delimiters.
|
||||
_.templateSettings = {
|
||||
evaluate : /<%([\s\S]+?)%>/g,
|
||||
interpolate : /<%=([\s\S]+?)%>/g,
|
||||
escape : /<%-([\s\S]+?)%>/g
|
||||
interpolate : /<%-([\s\S]+?)%>/g,
|
||||
escape : /<%\(?:\w+) ([\s\S]+?)%>/g
|
||||
};
|
||||
|
||||
// When customizing `templateSettings`, if you don't want to define an
|
||||
|
|
|
@ -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
|
||||
|
@ -178,10 +178,10 @@ 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 %>
|
||||
<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
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -12,7 +12,7 @@ struct Greet<'a> {
|
|||
async fn greet(req: HttpRequest) -> actix_web::Result<HttpResponse> {
|
||||
let name = req.match_info().get("name").unwrap_or("World");
|
||||
let body = Greet { name }
|
||||
.render_once()
|
||||
.render_once_to_string()
|
||||
.map_err(|e| InternalError::new(e, StatusCode::INTERNAL_SERVER_ERROR))?;
|
||||
|
||||
Ok(HttpResponse::Ok()
|
||||
|
|
|
@ -12,5 +12,5 @@ fn main() {
|
|||
title: "Website".to_owned(),
|
||||
name: "Hanako".to_owned(),
|
||||
};
|
||||
println!("{}", ctx.render_once().unwrap());
|
||||
println!("{}", ctx.render_once_to_string().unwrap());
|
||||
}
|
||||
|
|
|
@ -8,5 +8,5 @@ struct Simple {
|
|||
|
||||
fn main() {
|
||||
let messages = vec![String::from("Message 1"), String::from("<Message 2>")];
|
||||
println!("{}", Simple { messages }.render_once().unwrap());
|
||||
println!("{}", Simple { messages }.render_once_to_string().unwrap());
|
||||
}
|
||||
|
|
|
@ -1,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>,
|
||||
}
|
||||
|
||||
|
|
|
@ -115,7 +115,8 @@ impl fmt::Display for Error {
|
|||
writeln!(f, "file: {}", source_file.display())?;
|
||||
}
|
||||
|
||||
if let (Some(ref source), Some(offset)) = (source, self.offset) {
|
||||
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)?;
|
||||
|
||||
|
@ -133,6 +134,13 @@ impl fmt::Display for Error {
|
|||
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(())
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
use proc_macro2::Span;
|
||||
use quote::{quote, ToTokens};
|
||||
use syn::parse::{Parse, ParseStream, Result as ParseResult};
|
||||
use syn::punctuated::Punctuated;
|
||||
use syn::token::Comma;
|
||||
use syn::visit_mut::VisitMut;
|
||||
use syn::{
|
||||
Block, Expr, ExprBreak, ExprCall, ExprContinue, ExprLit, ExprPath, Ident, Lit,
|
||||
LitStr, Stmt, Token,
|
||||
LitStr, Stmt,
|
||||
};
|
||||
|
||||
pub struct Optimizer {
|
||||
|
|
|
@ -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
|
||||
|
@ -424,7 +430,7 @@ mod tests {
|
|||
Token {
|
||||
content: "inner | upper",
|
||||
offset: 10,
|
||||
kind: TokenKind::BufferedCode { escape: false },
|
||||
kind: TokenKind::BufferedCode { escape: None },
|
||||
},
|
||||
Token {
|
||||
content: " outer",
|
||||
|
@ -437,7 +443,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 +467,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 +488,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 +501,10 @@ mod tests {
|
|||
},
|
||||
Token {
|
||||
content: "/* %%>*/ 1",
|
||||
offset: 16,
|
||||
kind: TokenKind::BufferedCode { escape: true }
|
||||
offset: 20,
|
||||
kind: TokenKind::BufferedCode {
|
||||
escape: Some("html")
|
||||
}
|
||||
},
|
||||
]
|
||||
);
|
||||
|
|
|
@ -22,7 +22,6 @@ struct DeriveTemplateOptions {
|
|||
found_keys: Vec<Ident>,
|
||||
path: Option<LitStr>,
|
||||
delimiter: Option<LitChar>,
|
||||
escape: Option<LitBool>,
|
||||
rm_whitespace: Option<LitBool>,
|
||||
}
|
||||
|
||||
|
@ -45,8 +44,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 {
|
||||
|
@ -75,9 +72,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;
|
||||
}
|
||||
|
@ -358,7 +352,7 @@ fn derive_template_impl(tokens: TokenStream) -> Result<TokenStream, syn::Error>
|
|||
let tokens = quote! {
|
||||
impl #impl_generics sailfish::RenderOnce for #name #ty_generics #where_clause {
|
||||
#inline
|
||||
fn render_once(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;
|
||||
use sailfish::runtime as __sf_rt;
|
||||
|
@ -369,7 +363,7 @@ fn derive_template_impl(tokens: TokenStream) -> Result<TokenStream, syn::Error>
|
|||
}
|
||||
|
||||
#[inline]
|
||||
fn render_once_escaped(self, b: &mut sailfish::runtime::Buffer) -> std::result::Result<(), sailfish::runtime::RenderError> {
|
||||
fn render_once_escaped<E: sailfish::Escape>(self, b: &mut sailfish::Buffer, _: &E) -> std::result::Result<(), sailfish::RenderError> {
|
||||
self.render_once(b)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<'a>(
|
||||
|
@ -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<'a>(
|
||||
&mut self,
|
||||
token: &Token<'a>,
|
||||
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<'a>(
|
||||
&mut self,
|
||||
token: &Token<'a>,
|
||||
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,7 +168,7 @@ impl SourceBuilder {
|
|||
),
|
||||
};
|
||||
|
||||
self.source.push_str("sailfish::runtime::filter::");
|
||||
self.source.push_str("__sf_rt::filter::");
|
||||
self.source.push_str(&*name);
|
||||
self.source.push('(');
|
||||
|
||||
|
@ -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(())
|
||||
|
@ -257,7 +260,7 @@ impl SourceBuilder {
|
|||
let mut err =
|
||||
make_error!(ErrorKind::RustSyntaxError(synerr), source = self.source);
|
||||
|
||||
err.offset = original_offset;
|
||||
// err.offset = original_offset;
|
||||
|
||||
Err(err)
|
||||
}
|
||||
|
@ -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") ; }"#
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
<h1><%= message %></h1>
|
||||
<h1><%\html message %></h1>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<% for i in 1..=9 %>
|
||||
<tr>
|
||||
<% for j in 1..=9 %>
|
||||
<td><%= i * j %></td>
|
||||
<td><%\html i * j %></td>
|
||||
<% } %>
|
||||
</td>
|
||||
<% } %>
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
template_dirs = ["../templates"]
|
||||
escape = true
|
||||
delimiter = "%"
|
||||
|
||||
[optimizations]
|
||||
|
|
|
@ -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: <%- message | disp %>
|
||||
dbg: <%- message | dbg %>
|
||||
disp escaped: <%= message | disp %>
|
||||
dbg escaped: <%= message | dbg %>
|
||||
disp escaped: <%\html message | disp %>
|
||||
dbg escaped: <%\html 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 { %><%\html 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": <%- name | dbg %>,
|
||||
"value": <%= value %>
|
||||
"value": <%- value %>
|
||||
}
|
||||
|
|
|
@ -9,5 +9,5 @@
|
|||
This line should be appeared under the previous line
|
||||
</div>
|
||||
<% for msg in 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 items { %><tr><td><%= item.id %></td><td><%= item.message %></td></tr><% } %>
|
||||
<% for item in 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,7 +1,7 @@
|
|||
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
|
||||
|
|
|
@ -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,3 @@
|
|||
<% for player in &players %>
|
||||
<div><%= player.name %>: <%= player.score %></div>
|
||||
<div><%\html player.name %>: <%\html player.score %></div>
|
||||
<% } %>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<html>
|
||||
<body>
|
||||
<%= content
|
||||
<%\html content
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{
|
||||
"name": "<%= name %>",
|
||||
"content": <% =content %>
|
||||
"name": "<%\json name %>",
|
||||
"content": <% \jsoncontent %>
|
||||
}
|
||||
|
|
|
@ -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,9 +1,5 @@
|
|||
#[macro_use]
|
||||
extern crate sailfish_macros;
|
||||
|
||||
use integration_tests::assert_string_eq;
|
||||
use sailfish::runtime::RenderResult;
|
||||
use sailfish::RenderOnce;
|
||||
use sailfish::{RenderOnce, RenderResult};
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn assert_render_result(name: &str, result: RenderResult) {
|
||||
|
@ -220,7 +216,7 @@ fn test_rust_macro() {
|
|||
}
|
||||
|
||||
#[derive(RenderOnce)]
|
||||
#[template(path = "formatting.stpl", escape = false)]
|
||||
#[template(path = "formatting.stpl")]
|
||||
struct Formatting;
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -24,6 +24,7 @@ json = ["serde", "serde_json"]
|
|||
perf-inline = []
|
||||
|
||||
[dependencies]
|
||||
array-vec = { git = "https://git.pfaff.dev/michael/array-vec.rs" }
|
||||
itoap = "1.0.1"
|
||||
ryu = "1.0.13"
|
||||
serde = { version = "1.0.159", optional = true }
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
use array_vec::ArrayStr;
|
||||
|
||||
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 = ArrayStr<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(ArrayStr::try_from(r#"\""#).unwrap()),
|
||||
'\\' => Some(ArrayStr::try_from(r"\\").unwrap()),
|
||||
'\u{0000}'..='\u{001F}' => {
|
||||
let c = c as u8;
|
||||
let mut s = ArrayStr::try_from(r"\u").unwrap();
|
||||
unsafe {
|
||||
const HEX_DIGITS: [u8; 16] = *b"0123456789ABCDEF";
|
||||
// SAFETY: we only write valid UTF-8
|
||||
let arr = s.data_mut();
|
||||
arr.unused_mut()[0].write(HEX_DIGITS[usize::from(c >> 4)]);
|
||||
arr.unused_mut()[1].write(HEX_DIGITS[usize::from(c & 0xF)]);
|
||||
// SAFETY: we just initialized the last 2 bytes
|
||||
arr.set_len(arr.len() + 2);
|
||||
}
|
||||
Some(s)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 mut tmp = Buffer::new();
|
||||
self.0.render_once(&mut tmp)?;
|
||||
|
@ -77,7 +76,12 @@ 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 mut tmp = Buffer::new();
|
||||
|
@ -88,7 +92,7 @@ impl<T: RenderOnce> RenderOnce for Upper<T> {
|
|||
tmp.push(c);
|
||||
}
|
||||
unsafe { b._set_len(old_len) };
|
||||
escape::escape_to_buf(tmp.as_str(), b);
|
||||
e.escape_to_buf(b, tmp.as_str());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -98,7 +102,7 @@ impl<T: RenderOnce> RenderOnce for Upper<T> {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```text
|
||||
/// <%= "tschüß" | upper %>
|
||||
/// <%\html "tschüß" | upper %>
|
||||
/// ```
|
||||
///
|
||||
/// result:
|
||||
|
@ -116,6 +120,7 @@ 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 mut tmp = Buffer::new();
|
||||
self.0.render_once(&mut tmp)?;
|
||||
|
@ -123,8 +128,10 @@ impl<T: RenderOnce> RenderOnce for Lower<T> {
|
|||
b.reserve(tmp.len());
|
||||
let old_len = b.len();
|
||||
for c in tmp.as_str().chars() {
|
||||
// see comments in str::to_lowercase
|
||||
// See comments in str::to_lowercase
|
||||
if c == 'Σ' {
|
||||
// This is very inefficient, but it seems unlikely to be worth the effort
|
||||
// duplicating what already exists in the standard library for unicode support.
|
||||
let lower = tmp.as_str().to_lowercase();
|
||||
unsafe { b._set_len(old_len) };
|
||||
b.push_str(&lower);
|
||||
|
@ -137,7 +144,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)?;
|
||||
let mut tmp = Buffer::new();
|
||||
|
@ -149,7 +161,7 @@ impl<T: RenderOnce> RenderOnce for Lower<T> {
|
|||
if c == 'Σ' {
|
||||
let lower = s.to_lowercase();
|
||||
unsafe { b._set_len(old_len) };
|
||||
b.push_str(&lower);
|
||||
e.escape_to_buf(b, &lower);
|
||||
return Ok(());
|
||||
}
|
||||
for c in c.to_lowercase() {
|
||||
|
@ -157,7 +169,7 @@ impl<T: RenderOnce> RenderOnce for Lower<T> {
|
|||
}
|
||||
}
|
||||
unsafe { b._set_len(old_len) };
|
||||
escape::escape_to_buf(tmp.as_str(), b);
|
||||
e.escape_to_buf(b, tmp.as_str());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -167,7 +179,7 @@ impl<T: RenderOnce> RenderOnce for Lower<T> {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```text
|
||||
/// <%= "ὈΔΥΣΣΕΎΣ" | lower %>
|
||||
/// <%\html "ὈΔΥΣΣΕΎΣ" | lower %>
|
||||
/// ```
|
||||
///
|
||||
/// result:
|
||||
|
@ -193,9 +205,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)
|
||||
}
|
||||
}
|
||||
|
@ -238,7 +254,7 @@ fn trim_impl(b: &mut Buffer, old_len: usize) -> Result<(), RenderError> {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```text
|
||||
/// <%= " Hello world\n" | trim %>
|
||||
/// <%\html " Hello world\n" | trim %>
|
||||
/// ```
|
||||
///
|
||||
/// result:
|
||||
|
@ -264,24 +280,30 @@ 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 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(())
|
||||
}
|
||||
|
@ -293,7 +315,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:
|
||||
|
@ -340,16 +362,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())
|
||||
}
|
||||
|
||||
|
@ -364,7 +388,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()))
|
||||
}
|
||||
}
|
||||
|
@ -387,6 +411,8 @@ cfg_json! {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::EscapeHtml;
|
||||
|
||||
use super::*;
|
||||
|
||||
fn assert_render<T: RenderOnce>(expr: T, expected: &str) {
|
||||
|
@ -397,7 +423,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,21 +34,54 @@ static ESCAPE_LUT: [u8; 256] = [
|
|||
const ESCAPED: [&str; 5] = [""", "&", "'", "<", ">"];
|
||||
const ESCAPED_LEN: usize = 5;
|
||||
|
||||
use super::buffer::Buffer;
|
||||
use super::{Buffer, Escape};
|
||||
|
||||
/// A scheme for escaping strings for safe insertion into HTML.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct EscapeHtml;
|
||||
|
||||
impl Escape for EscapeHtml {
|
||||
type Escaped = &'static str;
|
||||
|
||||
const IDENT_BOOLS: bool = true;
|
||||
const IDENT_UINTS: bool = true;
|
||||
const IDENT_INTS: bool = true;
|
||||
const IDENT_FLOATS: bool = true;
|
||||
|
||||
#[inline(always)]
|
||||
fn escape(&self, c: char) -> Option<Self::Escaped> {
|
||||
match c {
|
||||
'\"' => Some("""),
|
||||
'&' => Some("&"),
|
||||
'<' => Some("<"),
|
||||
'>' => Some(">"),
|
||||
'\'' => Some("'"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// 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"))]
|
||||
fn escape_to_buf(&self, buf: &mut Buffer, string: &str) {
|
||||
unsafe {
|
||||
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 {
|
||||
#[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(feed: &str, buf: &mut Buffer) {
|
||||
debug_assert!(feed.len() >= 16);
|
||||
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") {
|
||||
|
@ -58,37 +91,19 @@ pub fn escape_to_buf(feed: &str, buf: &mut Buffer) {
|
|||
};
|
||||
|
||||
FN.store(fun as FnRaw, Ordering::Relaxed);
|
||||
unsafe { fun(feed, buf) };
|
||||
unsafe { fun(string, buf) };
|
||||
}
|
||||
|
||||
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 {
|
||||
let fun = FN.load(Ordering::Relaxed);
|
||||
std::mem::transmute::<FnRaw, fn(&str, &mut Buffer)>(fun)(feed, buf);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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) {
|
||||
fn escape_to_buf(&self, buffer: &mut Buffer, string: &str) {
|
||||
unsafe {
|
||||
if cfg!(miri) {
|
||||
let bp = feed.as_ptr();
|
||||
|
@ -102,26 +117,13 @@ pub fn escape_to_buf(feed: &str, buf: &mut Buffer) {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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);
|
||||
/// write the escaped contents into `Buffer`
|
||||
#[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;
|
||||
|
@ -33,12 +33,29 @@
|
|||
)]
|
||||
#![cfg_attr(sailfish_nightly, feature(core_intrinsics))]
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
#![feature(maybe_uninit_slice)]
|
||||
#![feature(specialization)]
|
||||
#![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, RenderOnce, RenderResult};
|
||||
pub use size_hint::SizeHint;
|
||||
|
||||
pub use runtime::{Buffer, Render, RenderError, RenderOnce, RenderResult};
|
||||
#[cfg(feature = "derive")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "derive")))]
|
||||
pub use sailfish_macros::{Render, 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.
|
||||
|
@ -29,7 +26,7 @@ use super::escape;
|
|||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use sailfish::runtime::{Buffer, Render, RenderError};
|
||||
/// use sailfish::{Buffer, Render, RenderError};
|
||||
///
|
||||
/// struct MyU64(u64);
|
||||
///
|
||||
|
@ -46,15 +43,16 @@ pub trait Render {
|
|||
|
||||
/// render to `Buffer` with HTML escaping
|
||||
#[inline]
|
||||
fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
let mut tmp = Buffer::new();
|
||||
self.render(&mut tmp)?;
|
||||
escape::escape_to_buf(tmp.as_str(), b);
|
||||
Ok(())
|
||||
fn render_escaped<E: Escape>(
|
||||
&self,
|
||||
b: &mut Buffer,
|
||||
e: &E,
|
||||
) -> Result<(), RenderError> {
|
||||
default_render_escaped(self, b, e)
|
||||
}
|
||||
}
|
||||
|
||||
/// types which can be rendered inside buffer block (`<%= %>`)
|
||||
/// types which can be rendered inside buffer block (`<%- %>`)
|
||||
///
|
||||
/// See [`Render`] for more information.
|
||||
pub trait RenderOnce: Sized {
|
||||
|
@ -90,11 +88,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`
|
||||
|
@ -118,6 +117,30 @@ pub trait RenderOnce: Sized {
|
|||
}
|
||||
}
|
||||
|
||||
#[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(())
|
||||
}
|
||||
|
||||
#[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(())
|
||||
}
|
||||
|
||||
// impl<'a, T: ?Sized> Render for &'a T
|
||||
// where
|
||||
// T: Render,
|
||||
|
@ -126,8 +149,8 @@ pub trait RenderOnce: Sized {
|
|||
// T::render(self, b)
|
||||
// }
|
||||
|
||||
// fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
// T::render_escaped(self, b)
|
||||
// fn render_escaped<E: Escape>(&self, b: &mut Buffer, e: &E) -> Result<(), RenderError> {
|
||||
// T::render_escaped(self, b, e)
|
||||
// }
|
||||
// }
|
||||
|
||||
|
@ -138,34 +161,15 @@ impl<T: Render> RenderOnce for T {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
fn render_once_escaped(self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
self.render_escaped(b)
|
||||
fn render_once_escaped<E: Escape>(
|
||||
self,
|
||||
b: &mut Buffer,
|
||||
e: &E,
|
||||
) -> Result<(), RenderError> {
|
||||
self.render_escaped(b, e)
|
||||
}
|
||||
}
|
||||
|
||||
// /// Autoref-based stable specialization
|
||||
// ///
|
||||
// /// Explanation can be found [here](https://github.com/dtolnay/case-studies/blob/master/autoref-specialization/README.md)
|
||||
// impl<T: Display> Render for &T {
|
||||
// fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
// fmt::write(b, format_args!("{}", self))
|
||||
// }
|
||||
//
|
||||
// fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
// struct Wrapper<'a>(&'a mut Buffer);
|
||||
//
|
||||
// impl<'a> fmt::Write for Wrapper<'a> {
|
||||
// #[inline]
|
||||
// fn push_str(&mut self, s: &str) -> Result<(), RenderError> {
|
||||
// escape::escape_to_buf(s, self.0);
|
||||
// Ok(())
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// fmt::write(&mut Wrapper(b), format_args!("{}", self))
|
||||
// }
|
||||
// }
|
||||
|
||||
impl Render for String {
|
||||
#[inline]
|
||||
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
|
@ -174,8 +178,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(())
|
||||
}
|
||||
}
|
||||
|
@ -188,8 +196,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(())
|
||||
}
|
||||
}
|
||||
|
@ -202,14 +214,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(())
|
||||
}
|
||||
|
@ -224,8 +236,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(())
|
||||
}
|
||||
}
|
||||
|
@ -239,8 +255,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(())
|
||||
}
|
||||
}
|
||||
|
@ -278,13 +298,26 @@ impl Render for bool {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
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)]
|
||||
|
@ -307,16 +340,21 @@ macro_rules! render_int {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
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)]
|
||||
|
@ -341,9 +379,16 @@ impl Render for f32 {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
// escape string
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -370,9 +415,42 @@ impl Render for f64 {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
// escape string
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -389,8 +467,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)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -399,17 +477,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,)*) => {
|
||||
|
@ -421,8 +499,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)
|
||||
}
|
||||
}
|
||||
)*
|
||||
|
@ -451,8 +529,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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -476,10 +558,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(())
|
||||
}
|
||||
}
|
||||
|
@ -505,10 +587,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(())
|
||||
}
|
||||
}
|
||||
|
@ -574,9 +656,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();
|
||||
|
@ -589,24 +674,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();
|
||||
}
|
||||
|
@ -619,10 +704,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");
|
||||
}
|
||||
|
@ -631,17 +716,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");
|
||||
}
|
||||
|
||||
|
@ -650,7 +737,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();
|
||||
|
@ -675,7 +764,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");
|
||||
}
|
||||
|
|
@ -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, RenderOnce, RenderResult};
|
||||
pub use size_hint::SizeHint;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub use alias_funcs::{render, render_escaped, render_text};
|
|
@ -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
|
||||
|
|
|
@ -14,7 +14,8 @@
|
|||
],
|
||||
"repository": {
|
||||
"commentblock": {
|
||||
"patterns": [{
|
||||
"patterns": [
|
||||
{
|
||||
"name": "comment.block.embedded.html",
|
||||
"begin": "<(%|\\?)#",
|
||||
"end": "(%|\\?)>",
|
||||
|
@ -23,15 +24,20 @@
|
|||
"name": "punctuation.definition.comment.html"
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
]
|
||||
},
|
||||
"codeblock": {
|
||||
"patterns": [{
|
||||
"patterns": [
|
||||
{
|
||||
"name": "source.rust.embedded.html",
|
||||
"begin": "<(%|\\?)(=|-)?",
|
||||
"begin": "<(%|\\?)(\\(\\w+)|-)?",
|
||||
"beginCaptures": {
|
||||
"0": {
|
||||
"name": "punctuation.definition.tag.begin.html"
|
||||
},
|
||||
"2": {
|
||||
"name": "entity.name.function.sailfish"
|
||||
}
|
||||
},
|
||||
"end": "(%|\\?)>",
|
||||
|
@ -40,10 +46,13 @@
|
|||
"name": "punctuation.definition.tag.end.html"
|
||||
}
|
||||
},
|
||||
"patterns": [{
|
||||
"patterns": [
|
||||
{
|
||||
"include": "source.rust"
|
||||
}]
|
||||
}]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"scopeName": "source.sailfish"
|
||||
|
|
Loading…
Reference in New Issue