Compare commits
12 Commits
222f69c38a
...
4e2232f2de
Author | SHA1 | Date |
---|---|---|
Michael Pfaff | 4e2232f2de | |
Michael Pfaff | e5a471d9ca | |
Vince Pike | d27b415d7a | |
dependabot[bot] | 9133849820 | |
Vince Pike | 7a0c492434 | |
dependabot[bot] | b98c2a4568 | |
vthg2themax | 2e39299483 | |
Vince Pike | ef79697c6e | |
Lev | af91dc5243 | |
Lev | 4c554d1a57 | |
Vince Pike | 3bb7b3e042 | |
Miran Bastaja | 28972209cd |
|
@ -37,9 +37,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
|||
|
||||
[[package]]
|
||||
name = "filetime"
|
||||
version = "0.2.21"
|
||||
version = "0.2.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5cbc844cecaee9d4443931972e1289c8ff485cb4cc2767cb03ca139ed6885153"
|
||||
checksum = "d4029edd3e734da6fe05b6cd7bd2960760a616bd2ddd0d59a0124746d6272af0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
|
@ -55,9 +55,9 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
|
|||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.0"
|
||||
version = "0.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a"
|
||||
checksum = "7dfda62a12f55daeae5015f81b0baea145391cb4520f86c248fc615d72640d12"
|
||||
|
||||
[[package]]
|
||||
name = "home"
|
||||
|
@ -70,9 +70,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.0.0"
|
||||
version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d"
|
||||
checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
|
@ -80,7 +80,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "integration-tests"
|
||||
version = "0.8.1"
|
||||
version = "0.8.3"
|
||||
dependencies = [
|
||||
"pretty_assertions",
|
||||
"sailfish",
|
||||
|
@ -104,15 +104,15 @@ checksum = "9028f49264629065d057f340a86acb84867925865f73bbf8d47b4d149a7e88b8"
|
|||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.147"
|
||||
version = "0.2.149"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
|
||||
checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.5.0"
|
||||
version = "2.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||
checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
|
@ -132,27 +132,27 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.66"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
|
||||
checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.32"
|
||||
version = "1.0.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965"
|
||||
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.16"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
|
||||
checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
@ -165,7 +165,7 @@ checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
|
|||
|
||||
[[package]]
|
||||
name = "sailfish"
|
||||
version = "0.8.1"
|
||||
version = "0.8.3"
|
||||
dependencies = [
|
||||
"itoap",
|
||||
"ryu",
|
||||
|
@ -177,7 +177,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "sailfish-compiler"
|
||||
version = "0.8.1"
|
||||
version = "0.8.3"
|
||||
dependencies = [
|
||||
"filetime",
|
||||
"home",
|
||||
|
@ -192,7 +192,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "sailfish-macros"
|
||||
version = "0.8.1"
|
||||
version = "0.8.3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"sailfish-compiler",
|
||||
|
@ -200,18 +200,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.177"
|
||||
version = "1.0.189"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "63ba2516aa6bf82e0b19ca8b50019d52df58455d3cf9bdaf6315225fdd0c560a"
|
||||
checksum = "8e422a44e74ad4001bdc8eede9a4570ab52f71190e9c076d14369f38b9200537"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.177"
|
||||
version = "1.0.189"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "401797fe7833d72109fedec6bfcbe67c0eed9b99772f26eb8afd261f0abc6fd3"
|
||||
checksum = "1e48d1f918009ce3145511378cf68d613e3b3d9137d67272562080d68a2b32d5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -220,9 +220,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.104"
|
||||
version = "1.0.107"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c"
|
||||
checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
|
@ -240,9 +240,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.27"
|
||||
version = "2.0.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b60f673f44a8255b9c8c657daf66a596d435f2da81a555b06dc644d080ba45e0"
|
||||
checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -251,18 +251,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.2.0"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6"
|
||||
checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.7.6"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542"
|
||||
checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
|
@ -281,9 +281,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.19.14"
|
||||
version = "0.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a"
|
||||
checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
|
@ -294,9 +294,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "trybuild"
|
||||
version = "1.0.82"
|
||||
version = "1.0.85"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a84e0202ea606ba5ebee8507ab2bfbe89b98551ed9b8f0be198109275cff284b"
|
||||
checksum = "196a58260a906cedb9bf6d8034b6379d0c11f552416960452f267402ceeddff1"
|
||||
dependencies = [
|
||||
"basic-toml",
|
||||
"glob",
|
||||
|
@ -309,9 +309,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.11"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
|
@ -337,9 +337,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
|||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.5"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
|
||||
checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
@ -361,9 +361,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.1"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f"
|
||||
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
|
@ -376,51 +376,51 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.48.0"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
|
||||
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.0"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
|
||||
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.0"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
|
||||
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.0"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
|
||||
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.0"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
|
||||
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.0"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
|
||||
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.0"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
|
||||
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.5.1"
|
||||
version = "0.5.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25b5872fa2e10bd067ae946f927e726d7d603eaeb6e02fa6a350e0722d2b8c11"
|
||||
checksum = "a3b801d0e0a6726477cc207f60162da452f3a95adb368399bef20a946e06f65c"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
|
31
README.md
31
README.md
|
@ -16,14 +16,14 @@ Simple, small, and extremely fast template engine for Rust
|
|||
|
||||
## ✨ Features
|
||||
|
||||
- Simple and intuitive syntax inspired by [EJS](https://ejs.co/)
|
||||
- Include another template file inside template
|
||||
- Built-in filters
|
||||
- Minimal dependencies (<15 crates in total)
|
||||
- Extremely fast (See [benchmarks](https://github.com/djc/template-benchmarks-rs))
|
||||
- Better error message
|
||||
- Syntax highlighting support ([vscode](./syntax/vscode), [vim](./syntax/vim))
|
||||
- Works on Rust 1.60 or later
|
||||
- Simple and intuitive syntax inspired by [EJS](https://ejs.co/)
|
||||
- Include another template file inside template
|
||||
- Built-in filters
|
||||
- Minimal dependencies (<15 crates in total)
|
||||
- Extremely fast (See [benchmarks](https://github.com/djc/template-benchmarks-rs))
|
||||
- Better error message
|
||||
- Syntax highlighting support ([vscode](./syntax/vscode), [vim](./syntax/vim))
|
||||
- Works on Rust 1.60 or later
|
||||
|
||||
## 🐟 Example
|
||||
|
||||
|
@ -31,7 +31,7 @@ Dependencies:
|
|||
|
||||
```toml
|
||||
[dependencies]
|
||||
sailfish = "0.8.1"
|
||||
sailfish = "0.8.3"
|
||||
```
|
||||
|
||||
Template file (templates/hello.stpl):
|
||||
|
@ -49,9 +49,9 @@ Template file (templates/hello.stpl):
|
|||
Code:
|
||||
|
||||
```rust
|
||||
use sailfish::TemplateOnce;
|
||||
use sailfish::RenderOnce;
|
||||
|
||||
#[derive(TemplateOnce)]
|
||||
#[derive(RenderOnce)]
|
||||
#[template(path = "hello.stpl")]
|
||||
struct HelloTemplate {
|
||||
messages: Vec<String>
|
||||
|
@ -69,14 +69,14 @@ You can find more examples in [examples](./examples) directory.
|
|||
|
||||
## 🐾 Roadmap
|
||||
|
||||
- `Template` trait ([RFC](https://github.com/rust-sailfish/sailfish/issues/3))
|
||||
- Template inheritance (block, partials, etc.)
|
||||
- `Render` derive macro ([RFC](https://github.com/rust-sailfish/sailfish/issues/3))
|
||||
- Template inheritance (block, partials, etc.)
|
||||
|
||||
## 👤 Author
|
||||
|
||||
🇯🇵 **Ryohei Machida**
|
||||
|
||||
* GitHub: [@Kogia-sima](https://github.com/Kogia-sima)
|
||||
- GitHub: [@Kogia-sima](https://github.com/Kogia-sima)
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
|
@ -96,5 +96,6 @@ Copyright © 2020 [Ryohei Machida](https://github.com/Kogia-sima).
|
|||
|
||||
This project is [MIT](https://github.com/rust-sailfish/sailfish/blob/master/LICENSE) licensed.
|
||||
|
||||
***
|
||||
---
|
||||
|
||||
_This README was generated with ❤️ by [readme-md-generator](https://github.com/kefranabg/readme-md-generator)_
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
Create a new directory named `templates` in the same directory as `Cargo.toml`. Copy the following contents and paste it to a new file named `templates/hello.stpl`.
|
||||
|
||||
``` rhtml
|
||||
```rhtml
|
||||
<html>
|
||||
<body>
|
||||
<% for msg in &messages { %>
|
||||
|
@ -29,13 +29,13 @@ templates/
|
|||
<ol><li>Import the sailfish crates:</li></ol>
|
||||
|
||||
```rust
|
||||
use sailfish::TemplateOnce;
|
||||
use sailfish::RenderOnce;
|
||||
```
|
||||
|
||||
<ol start="2"><li>Define the template struct to be rendered:</li></ol>
|
||||
|
||||
```rust
|
||||
#[derive(TemplateOnce)] // automatically implement `TemplateOnce` trait
|
||||
#[derive(RenderOnce)] // automatically implement `TemplateOnce` trait
|
||||
#[template(path = "hello.stpl")] // specify the path to template
|
||||
struct HelloTemplate {
|
||||
// data to be passed to the template
|
||||
|
|
|
@ -8,19 +8,19 @@ This documentation mainly focuses on concepts of the library, general usage, and
|
|||
|
||||
There are many libraries for template rendering in Rust. Among those libraries, sailfish aims at **rapid development** and **rapid rendering**. Sailfish has many features that other libraries might not support.
|
||||
|
||||
- Write a Rust code directly inside templates, supporting many Rust syntax (struct definition, closure, macro invocation, etc.)
|
||||
- [Built-in filters](https://docs.rs/sailfish/latest/sailfish/runtime/filter/index.html)
|
||||
- Minimal dependencies (<15 crates in total)
|
||||
- Extremely fast (See [benchmarks](https://github.com/djc/template-benchmarks-rs))
|
||||
- Template rendering is always type-safe because templates are statically compiled.
|
||||
- Syntax highlighting ([vscode](http://github.com/rust-sailfish/sailfish/blob/master/syntax/vscode), [vim](http://github.com/rust-sailfish/sailfish/blob/master/syntax/vim))
|
||||
- Write a Rust code directly inside templates, supporting many Rust syntax (struct definition, closure, macro invocation, etc.)
|
||||
- [Built-in filters](https://docs.rs/sailfish/latest/sailfish/runtime/filter/index.html)
|
||||
- Minimal dependencies (<15 crates in total)
|
||||
- Extremely fast (See [benchmarks](https://github.com/djc/template-benchmarks-rs))
|
||||
- Template rendering is always type-safe because templates are statically compiled.
|
||||
- Syntax highlighting ([vscode](http://github.com/rust-sailfish/sailfish/blob/master/syntax/vscode), [vim](http://github.com/rust-sailfish/sailfish/blob/master/syntax/vim))
|
||||
|
||||
## Upcoming features
|
||||
|
||||
Since sailfish is on early stage of development, there are many upcoming features that is not supported yet. You can find many [RFC](https://github.com/rust-sailfish/sailfish/issues?q=is%3Aissue+is%3Aopen+label%3A%22Status%3A+RFC%22)s in my repository. These RFC include:
|
||||
|
||||
- `Template` trait (which does not consume itself)
|
||||
- Template inheritance (block, partials, etc.)
|
||||
- `Render` derive macro (which does not consume itself)
|
||||
- Template inheritance (block, partials, etc.)
|
||||
|
||||
If you have any idea about them or want to implement that feature, please send a comment on the issue!
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ In order to use sailfish templates, you have add two dependencies in your `Cargo
|
|||
|
||||
``` toml
|
||||
[dependencies]
|
||||
sailfish = "0.8.1"
|
||||
sailfish = "0.8.3"
|
||||
```
|
||||
|
||||
## Feature Flags
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
|
||||
You can control the rendering behaviour via `template` attribute.
|
||||
|
||||
``` rust
|
||||
#[derive(TemplateOnce)]
|
||||
```rust
|
||||
#[derive(RenderOnce)]
|
||||
#[template(path = "template.stpl", escape = false)]
|
||||
struct TemplateStruct {
|
||||
...
|
||||
|
@ -14,15 +14,15 @@ 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.
|
||||
- `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.
|
||||
|
||||
You can split the options into multiple `template` attributes.
|
||||
|
||||
``` rust
|
||||
#[derive(TemplateOnce)]
|
||||
```rust
|
||||
#[derive(RenderOnce)]
|
||||
#[template(path = "template.stpl")]
|
||||
#[template(delimiter = '?')]
|
||||
#[template(rm_whitespace = true)]
|
||||
|
@ -36,10 +36,10 @@ struct TemplateStruct {
|
|||
Sailfish allows global and local configuration in a file named `sailfish.toml`. Sailfish looks for this file in same directory as `Cargo.toml` and all parent directories.
|
||||
If, for example, `Cargo.toml` exists in `/foo/bar/baz` directory, then the following configuration files would be scanned in this order.
|
||||
|
||||
- `/foo/bar/baz/sailfish.toml`
|
||||
- `/foo/bar/sailfish.toml`
|
||||
- `/foo/sailfish.toml`
|
||||
- `/sailfish.toml`
|
||||
- `/foo/bar/baz/sailfish.toml`
|
||||
- `/foo/bar/sailfish.toml`
|
||||
- `/foo/sailfish.toml`
|
||||
- `/sailfish.toml`
|
||||
|
||||
If a key is specified in multiple configuration files, the value in the deeper directory takes precedence over ancestor directories.
|
||||
|
||||
|
@ -49,7 +49,7 @@ If a key is specified in both configuration file and derive options, then the va
|
|||
|
||||
Configuration files are written in the TOML 0.5 format. Here is the default configuration:
|
||||
|
||||
``` toml
|
||||
```toml
|
||||
template_dirs = ["templates"]
|
||||
escape = true
|
||||
delimiter = "%"
|
||||
|
|
|
@ -2,16 +2,15 @@
|
|||
|
||||
## Tags
|
||||
|
||||
- `<% %>`: Inline tag, you can write Rust code inside this tag
|
||||
- `<%= %>`: Evaluate the Rust expression and outputs the value into the template (HTML escaped)
|
||||
- `<%- %>`: Evaluate the Rust expression and outputs the unescaped value into the template
|
||||
- `<%+ %>`: Evaluate the Rust expression producing a `TemplateOnce` value, and render that value into the template
|
||||
- `<%# %>`: Comment tag
|
||||
- `<%%`: Outputs a literal '<%'
|
||||
- `<% %>`: Inline tag, you can write Rust code inside this tag
|
||||
- `<%= %>`: Evaluate the Rust expression and outputs the value into the template (HTML escaped)
|
||||
- `<%- %>`: Evaluate the Rust expression and outputs the unescaped value into the template
|
||||
- `<%# %>`: Comment tag
|
||||
- `<%%`: Outputs a literal '<%'
|
||||
|
||||
## Condition
|
||||
|
||||
``` rhtml
|
||||
```rhtml
|
||||
<% if messages.is_empty() { %>
|
||||
<div>No messages</div>
|
||||
<% } %>
|
||||
|
@ -19,7 +18,7 @@
|
|||
|
||||
## loop
|
||||
|
||||
``` rhtml
|
||||
```rhtml
|
||||
<% for (i, msg) in messages.iter().enumerate() { %>
|
||||
<div><%= i %>: <%= msg %></div>
|
||||
<% } %>
|
||||
|
@ -27,17 +26,17 @@
|
|||
|
||||
## Includes
|
||||
|
||||
``` rhtml
|
||||
```rhtml
|
||||
<% include!("path/to/template"); %>
|
||||
```
|
||||
|
||||
## Filters
|
||||
|
||||
``` rhtml
|
||||
```rhtml
|
||||
<%= message | upper %>
|
||||
```
|
||||
|
||||
``` rhtml
|
||||
```rhtml
|
||||
{
|
||||
"id": <%= id %>
|
||||
"comment": <%- comment | json %>
|
||||
|
|
|
@ -26,7 +26,7 @@ You can write Rust statement inside `<% %>` tag.
|
|||
```
|
||||
|
||||
!!! Note
|
||||
Make sure that you cannot omit braces, parenthesis, and semicolons.
|
||||
Make sure that you cannot omit braces, parenthesis, and semicolons.
|
||||
|
||||
Sailfish is smart enough to figure out where the code block ends, so you can even include `%>` inside Rust comments or string literals.
|
||||
|
||||
|
@ -57,11 +57,11 @@ If you need to simply render `<%` character, you can escape it, or use evaluatio
|
|||
|
||||
Although almost all Rust statement is supported, the following statements inside templates may cause a strange compilation error.
|
||||
|
||||
- Function/Macro definition that render some contents
|
||||
- `impl` item
|
||||
- Macro call which defines some local variable.
|
||||
- Macro call which behaviour depends on the path to source file
|
||||
- Generator expression (yield)
|
||||
- Function/Macro definition that render some contents
|
||||
- `impl` item
|
||||
- Macro call which defines some local variable.
|
||||
- Macro call which behaviour depends on the path to source file
|
||||
- Generator expression (yield)
|
||||
|
||||
## Evaluation block
|
||||
|
||||
|
@ -100,35 +100,8 @@ If you want to render the results without escaping, you can use `<%- %>` tag or
|
|||
```
|
||||
|
||||
!!! Note
|
||||
Evaluation block does not return any value, so you cannot use the block to pass the render result to another code block. The following code is invalid.
|
||||
Evaluation block does not return any value, so you cannot use the block to pass the render result to another code block. The following code is invalid.
|
||||
|
||||
``` rhtml
|
||||
<% let result = %><%= 1 %><% ; %>
|
||||
```
|
||||
|
||||
## Component block
|
||||
|
||||
Rust expression inside `<%+ %>` tag is evaluated and then rendered by
|
||||
calling its `render_once()` method. If the value does not have an
|
||||
appropriate method, a compile-time error will be reported.
|
||||
|
||||
This makes it easy to use types which are `TemplateOnce` as components
|
||||
which can be embedded into other templates.
|
||||
|
||||
=== "Template A"
|
||||
|
||||
``` rhtml
|
||||
<strong>A <%= val %></strong>
|
||||
```
|
||||
|
||||
=== "Template B"
|
||||
|
||||
``` rhtml
|
||||
B <%+ A { val: "example" } %>
|
||||
```
|
||||
|
||||
=== "Result"
|
||||
|
||||
``` text
|
||||
B <strong>A example</strong>
|
||||
```
|
||||
|
|
|
@ -322,6 +322,12 @@ dependencies = [
|
|||
"cfg-if 0.1.10",
|
||||
]
|
||||
|
||||
[[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.21"
|
||||
|
@ -403,9 +409,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.3.17"
|
||||
version = "0.3.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "66b91535aa35fea1523ad1b86cb6b53c28e0ae566ba4a460f4457e936cad7c6f"
|
||||
checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9"
|
||||
dependencies = [
|
||||
"bytes 1.4.0",
|
||||
"fnv",
|
||||
|
@ -422,9 +428,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
version = "0.14.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
|
@ -480,11 +486,11 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.9.3"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
|
||||
checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
|
@ -514,9 +520,9 @@ checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388"
|
|||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.141"
|
||||
version = "0.2.153"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5"
|
||||
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
|
||||
|
||||
[[package]]
|
||||
name = "local-channel"
|
||||
|
@ -574,14 +580,14 @@ checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
|
|||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "0.8.6"
|
||||
version = "0.8.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9"
|
||||
checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"wasi",
|
||||
"windows-sys 0.45.0",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -735,7 +741,7 @@ checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
|
|||
|
||||
[[package]]
|
||||
name = "sailfish"
|
||||
version = "0.6.0"
|
||||
version = "0.8.3"
|
||||
dependencies = [
|
||||
"itoap",
|
||||
"ryu",
|
||||
|
@ -745,7 +751,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "sailfish-compiler"
|
||||
version = "0.6.0"
|
||||
version = "0.8.3"
|
||||
dependencies = [
|
||||
"filetime",
|
||||
"home",
|
||||
|
@ -759,7 +765,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "sailfish-examples"
|
||||
version = "0.6.0"
|
||||
version = "0.8.3"
|
||||
dependencies = [
|
||||
"actix-rt",
|
||||
"actix-web",
|
||||
|
@ -769,7 +775,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "sailfish-macros"
|
||||
version = "0.6.0"
|
||||
version = "0.8.3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"sailfish-compiler",
|
||||
|
@ -814,9 +820,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.1"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4"
|
||||
checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
@ -964,9 +970,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.7.3"
|
||||
version = "0.8.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b403acf6f2bb0859c93c7f0d967cb4a75a7ac552100f9322faf64dc047669b21"
|
||||
checksum = "a1a195ec8c9da26928f773888e0742ca3ca1040c6cd859c919c9f59c1954ab35"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
|
@ -976,18 +982,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.1"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622"
|
||||
checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.19.8"
|
||||
version = "0.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13"
|
||||
checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
|
@ -1232,9 +1238,9 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
|
|||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.4.1"
|
||||
version = "0.5.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae8970b36c66498d8ff1d66685dc86b91b29db0c7739899012f63a63814b4b28"
|
||||
checksum = "b7cf47b659b318dccbd69cc4797a39ae128f533dce7902a1096044d1967b9c16"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "sailfish-examples"
|
||||
version = "0.8.1"
|
||||
version = "0.8.3"
|
||||
authors = ["Ryohei Machida <orcinus4627@gmail.com>"]
|
||||
edition = "2018"
|
||||
publish = false
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use actix_web::error::InternalError;
|
||||
use actix_web::http::StatusCode;
|
||||
use actix_web::{web, App, HttpRequest, HttpResponse, HttpServer};
|
||||
use sailfish::TemplateOnce;
|
||||
use sailfish::RenderOnce;
|
||||
|
||||
#[derive(TemplateOnce)]
|
||||
#[derive(RenderOnce)]
|
||||
#[template(path = "actix.stpl")]
|
||||
struct Greet<'a> {
|
||||
name: &'a str,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use sailfish::TemplateOnce;
|
||||
use sailfish::RenderOnce;
|
||||
|
||||
#[derive(TemplateOnce)]
|
||||
#[derive(RenderOnce)]
|
||||
#[template(path = "include.stpl")]
|
||||
struct Include {
|
||||
title: String,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use sailfish::TemplateOnce;
|
||||
use sailfish::RenderOnce;
|
||||
|
||||
#[derive(TemplateOnce)]
|
||||
#[derive(RenderOnce)]
|
||||
#[template(path = "simple.stpl")]
|
||||
struct Simple {
|
||||
messages: Vec<String>,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "sailfish-compiler"
|
||||
version = "0.8.1"
|
||||
version = "0.8.3"
|
||||
authors = ["Ryohei Machida <orcinus4627@gmail.com>"]
|
||||
description = "Simple, small, and extremely fast template engine for Rust"
|
||||
homepage = "https://github.com/rust-sailfish/sailfish"
|
||||
|
@ -25,7 +25,7 @@ config = ["serde", "toml"]
|
|||
memchr = "2.5.0"
|
||||
quote = { version = "1.0.26", default-features = false }
|
||||
serde = { version = "1.0", features = ["derive"], optional = true }
|
||||
toml = { version = "0.7.3", optional = true }
|
||||
toml = { version = "0.8.2", optional = true }
|
||||
home = "0.5.4"
|
||||
filetime = "0.2.21"
|
||||
|
||||
|
|
|
@ -83,6 +83,8 @@ impl Compiler {
|
|||
|
||||
let mut f = fs::File::create(output)
|
||||
.chain_err(|| format!("Failed to create artifact: {:?}", output))?;
|
||||
writeln!(f, "// Template compiled from: {}", input.display())
|
||||
.chain_err(|| format!("Failed to write artifact into {:?}", output))?;
|
||||
writeln!(f, "{}", rustfmt_block(&*string).unwrap_or(string))
|
||||
.chain_err(|| format!("Failed to write artifact into {:?}", output))?;
|
||||
drop(f);
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
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, ExprContinue, ExprMacro, Ident, LitStr, Macro, Stmt,
|
||||
StmtMacro, Token,
|
||||
Block, Expr, ExprBreak, ExprCall, ExprContinue, ExprLit, ExprPath, Ident, Lit,
|
||||
LitStr, Stmt, Token,
|
||||
};
|
||||
|
||||
pub struct Optimizer {
|
||||
|
@ -102,12 +104,12 @@ impl VisitMut for OptmizerImpl {
|
|||
|
||||
fl.body.stmts.remove(0);
|
||||
*fl.body.stmts.last_mut().unwrap() = syn::parse2(quote! {
|
||||
__sf_rt::render_text!(__sf_buf, #concat);
|
||||
__sf_rt::render_text(__sf_buf, #concat);
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let mut new_stmts = syn::parse2::<Block>(quote! {{
|
||||
__sf_rt::render_text!(__sf_buf, #previous);
|
||||
__sf_rt::render_text(__sf_buf, #previous);
|
||||
#stmt
|
||||
unsafe { __sf_buf._set_len(__sf_buf.len() - #sf_len); }
|
||||
}})
|
||||
|
@ -119,38 +121,23 @@ impl VisitMut for OptmizerImpl {
|
|||
i.stmts = results;
|
||||
}
|
||||
|
||||
fn visit_stmt_macro_mut(&mut self, i: &mut StmtMacro) {
|
||||
fn visit_expr_call_mut(&mut self, i: &mut ExprCall) {
|
||||
if self.rm_whitespace {
|
||||
if let Some(v) = get_rendertext_value(&i.mac) {
|
||||
if let Some(v) = get_rendertext_value(&i) {
|
||||
let ts = match remove_whitespace(v) {
|
||||
Some(value) => value,
|
||||
None => return,
|
||||
};
|
||||
i.mac.tokens = ts;
|
||||
i.args[1] = ts;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
syn::visit_mut::visit_stmt_macro_mut(self, i);
|
||||
}
|
||||
|
||||
fn visit_expr_macro_mut(&mut self, i: &mut ExprMacro) {
|
||||
if self.rm_whitespace {
|
||||
if let Some(v) = get_rendertext_value(&i.mac) {
|
||||
let ts = match remove_whitespace(v) {
|
||||
Some(value) => value,
|
||||
None => return,
|
||||
};
|
||||
i.mac.tokens = ts;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
syn::visit_mut::visit_expr_macro_mut(self, i);
|
||||
syn::visit_mut::visit_expr_call_mut(self, i);
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_whitespace(v: String) -> Option<TokenStream> {
|
||||
fn remove_whitespace(v: String) -> Option<Expr> {
|
||||
let mut buffer = String::new();
|
||||
let mut it = v.lines().peekable();
|
||||
if let Some(line) = it.next() {
|
||||
|
@ -175,48 +162,64 @@ fn remove_whitespace(v: String) -> Option<TokenStream> {
|
|||
}
|
||||
}
|
||||
|
||||
Some(quote! { __sf_buf, #buffer })
|
||||
Some(Expr::Lit(ExprLit {
|
||||
attrs: vec![],
|
||||
lit: LitStr::new(&buffer, Span::call_site()).into(),
|
||||
}))
|
||||
}
|
||||
|
||||
fn get_rendertext_value(mac: &Macro) -> Option<String> {
|
||||
struct RenderTextMacroArgument {
|
||||
fn get_rendertext_value(call: &ExprCall) -> Option<String> {
|
||||
struct RenderTextArguments<'a> {
|
||||
#[allow(dead_code)]
|
||||
context: Ident,
|
||||
arg: LitStr,
|
||||
buf: &'a Ident,
|
||||
text: &'a LitStr,
|
||||
}
|
||||
|
||||
impl Parse for RenderTextMacroArgument {
|
||||
fn parse(s: ParseStream) -> ParseResult<Self> {
|
||||
let context = s.parse()?;
|
||||
s.parse::<Token![,]>()?;
|
||||
let arg = s.parse()?;
|
||||
|
||||
Ok(Self { context, arg })
|
||||
impl<'a> RenderTextArguments<'a> {
|
||||
pub fn parse(expr: &'a Punctuated<Expr, Comma>) -> Option<Self> {
|
||||
if expr.len() != 2 {
|
||||
panic!("bad arguments: {:?}", expr.to_token_stream());
|
||||
return None;
|
||||
}
|
||||
let Expr::Path(ExprPath { path: buf, .. }) = &expr[0] else {
|
||||
panic!("bad arguments: {:?}", expr.to_token_stream());
|
||||
return None;
|
||||
};
|
||||
let Some(buf) = buf.get_ident() else {
|
||||
panic!("bad arguments: {:?}", expr.to_token_stream());
|
||||
return None;
|
||||
};
|
||||
let Expr::Lit(ExprLit {
|
||||
lit: Lit::Str(text),
|
||||
..
|
||||
}) = &expr[1]
|
||||
else {
|
||||
panic!("bad arguments: {:?}", expr.to_token_stream());
|
||||
return None;
|
||||
};
|
||||
Some(Self { buf, text })
|
||||
}
|
||||
}
|
||||
|
||||
let mut it = mac.path.segments.iter();
|
||||
|
||||
if it.next().map_or(false, |s| s.ident == "__sf_rt")
|
||||
&& it.next().map_or(false, |s| s.ident == "render_text")
|
||||
&& it.next().is_none()
|
||||
let Expr::Path(ExprPath { path, .. }) = &*call.func else {
|
||||
return None;
|
||||
};
|
||||
if path.segments.len() != 2
|
||||
|| path.segments[0].ident != "__sf_rt"
|
||||
|| path.segments[1].ident != "render_text"
|
||||
{
|
||||
let tokens = mac.tokens.clone();
|
||||
if let Ok(macro_arg) = syn::parse2::<RenderTextMacroArgument>(tokens) {
|
||||
return Some(macro_arg.arg.value());
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
None
|
||||
let args = RenderTextArguments::parse(&call.args)?;
|
||||
Some(args.text.value())
|
||||
}
|
||||
|
||||
fn get_rendertext_value_from_stmt(stmt: &Stmt) -> Option<String> {
|
||||
let em = match stmt {
|
||||
Stmt::Expr(Expr::Macro(ref mac), Some(_)) => mac,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
get_rendertext_value(&em.mac)
|
||||
match stmt {
|
||||
Stmt::Expr(Expr::Call(ref ec), Some(_)) => get_rendertext_value(ec),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn block_has_continue_or_break(i: &mut Block) -> bool {
|
||||
|
|
|
@ -56,7 +56,6 @@ impl Default for Parser {
|
|||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum TokenKind {
|
||||
NestedTemplateOnce,
|
||||
BufferedCode { escape: bool },
|
||||
Code,
|
||||
Comment,
|
||||
|
@ -162,10 +161,6 @@ impl<'a> ParseStream<'a> {
|
|||
token_kind = TokenKind::BufferedCode { escape: false };
|
||||
start += 1;
|
||||
}
|
||||
Some(b'+') => {
|
||||
token_kind = TokenKind::NestedTemplateOnce;
|
||||
start += 1;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
|
@ -409,7 +404,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn nested_render_once() {
|
||||
let src = r#"outer <%+ inner|upper %> outer"#;
|
||||
let src = r#"outer <%- inner | upper %> outer"#;
|
||||
let parser = Parser::default();
|
||||
let tokens = parser.parse(src).into_vec().unwrap();
|
||||
assert_eq!(
|
||||
|
@ -421,13 +416,13 @@ mod tests {
|
|||
kind: TokenKind::Text,
|
||||
},
|
||||
Token {
|
||||
content: "inner|upper",
|
||||
content: "inner | upper",
|
||||
offset: 10,
|
||||
kind: TokenKind::NestedTemplateOnce,
|
||||
kind: TokenKind::BufferedCode { escape: false },
|
||||
},
|
||||
Token {
|
||||
content: " outer",
|
||||
offset: 24,
|
||||
offset: 26,
|
||||
kind: TokenKind::Text,
|
||||
},
|
||||
]
|
||||
|
|
|
@ -105,25 +105,14 @@ fn resolve_template_file(path: &str, template_dirs: &[PathBuf]) -> Option<PathBu
|
|||
}
|
||||
|
||||
fn filename_hash(path: &Path, config: &Config) -> String {
|
||||
use std::fmt::Write;
|
||||
|
||||
let mut path_with_hash = String::with_capacity(16);
|
||||
|
||||
if let Some(n) = path.file_name() {
|
||||
let mut filename = &*n.to_string_lossy();
|
||||
if let Some(p) = filename.find('.') {
|
||||
filename = &filename[..p];
|
||||
}
|
||||
path_with_hash.push_str(filename);
|
||||
path_with_hash.push('-');
|
||||
}
|
||||
|
||||
let mut hasher = DefaultHasher::new();
|
||||
config.hash(&mut hasher);
|
||||
let hash = hasher.finish();
|
||||
let _ = write!(path_with_hash, "{:016x}", hash);
|
||||
let config_hash = hasher.finish();
|
||||
|
||||
path_with_hash
|
||||
path.hash(&mut hasher);
|
||||
let path_hash = hasher.finish();
|
||||
|
||||
format!("{:016x}-{:016x}", config_hash, path_hash)
|
||||
}
|
||||
|
||||
fn with_compiler<T, F: FnOnce(Compiler) -> Result<T, Error>>(
|
||||
|
@ -353,33 +342,25 @@ fn derive_template_impl(tokens: TokenStream) -> Result<TokenStream, syn::Error>
|
|||
_ => {
|
||||
return Err(syn::Error::new(
|
||||
Span::call_site(),
|
||||
"You cannot derive `Template` or `TemplateOnce` for tuple struct",
|
||||
"You cannot derive `Render` or `RenderOnce` for tuple struct",
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
let (impl_generics, ty_generics, where_clause) = strct.generics.split_for_impl();
|
||||
|
||||
// render_once method always results in the same code.
|
||||
// This method can be implemented in `sailfish` crate, but I found that performance
|
||||
// drops when the implementation is written in `sailfish` crate.
|
||||
let inline = if cfg!(feature = "perf-inline") {
|
||||
Some(quote!(#[inline]))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let tokens = quote! {
|
||||
impl #impl_generics sailfish::TemplateOnce for #name #ty_generics #where_clause {
|
||||
fn render_once(self) -> sailfish::RenderResult {
|
||||
use sailfish::runtime::{Buffer, SizeHint};
|
||||
static SIZE_HINT: SizeHint = SizeHint::new();
|
||||
|
||||
let mut buf = Buffer::with_capacity(SIZE_HINT.get());
|
||||
self.render_once_to(&mut buf)?;
|
||||
SIZE_HINT.update(buf.len());
|
||||
|
||||
Ok(buf.into_string())
|
||||
}
|
||||
|
||||
fn render_once_to(self, __sf_buf: &mut sailfish::runtime::Buffer) -> std::result::Result<(), sailfish::runtime::RenderError> {
|
||||
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> {
|
||||
// This line is required for cargo to track child templates
|
||||
#include_bytes_seq;
|
||||
|
||||
use sailfish::runtime as __sf_rt;
|
||||
let #name { #field_names } = self;
|
||||
include!(#output_file_string);
|
||||
|
@ -387,8 +368,6 @@ fn derive_template_impl(tokens: TokenStream) -> Result<TokenStream, syn::Error>
|
|||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl #impl_generics sailfish::private::Sealed for #name #ty_generics #where_clause {}
|
||||
};
|
||||
|
||||
Ok(tokens)
|
||||
|
|
|
@ -123,7 +123,7 @@ impl SourceBuilder {
|
|||
length: 1,
|
||||
});
|
||||
|
||||
self.source.push_str("__sf_rt::render_text!(__sf_buf, ");
|
||||
self.source.push_str("__sf_rt::render_text(__sf_buf, ");
|
||||
// write text token with Debug::fmt
|
||||
write!(self.source, "{:?}", token.as_str()).unwrap();
|
||||
self.source.push_str(");\n");
|
||||
|
@ -159,7 +159,7 @@ impl SourceBuilder {
|
|||
|
||||
self.source.push_str("__sf_rt::");
|
||||
self.source.push_str(method);
|
||||
self.source.push_str("!(__sf_buf, ");
|
||||
self.source.push_str("(__sf_buf, ");
|
||||
|
||||
if let Some(filter) = code_block.filter {
|
||||
let expr_str = format!("{}{}", code_block.expr.into_token_stream(), suffix);
|
||||
|
@ -177,7 +177,7 @@ impl SourceBuilder {
|
|||
|
||||
// arguments to filter function
|
||||
{
|
||||
self.source.push_str("&(");
|
||||
self.source.push_str("(");
|
||||
let entry = SourceMapEntry {
|
||||
original: token.offset(),
|
||||
new: self.source.len(),
|
||||
|
@ -199,7 +199,7 @@ impl SourceBuilder {
|
|||
self.source.push_str(suffix);
|
||||
}
|
||||
|
||||
self.source.push_str(");\n");
|
||||
self.source.push_str(")?;\n");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -214,11 +214,6 @@ impl SourceBuilder {
|
|||
TokenKind::BufferedCode { escape } => {
|
||||
self.write_buffered_code(&token, escape)?
|
||||
}
|
||||
TokenKind::NestedTemplateOnce => self.write_buffered_code_with_suffix(
|
||||
&token,
|
||||
false,
|
||||
".render_once()?",
|
||||
)?,
|
||||
TokenKind::Text => {
|
||||
// concatenate repeated text token
|
||||
let offset = token.offset();
|
||||
|
@ -384,7 +379,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn translate_nested_render_once() {
|
||||
let src = r#"outer <%+ inner %> outer"#;
|
||||
let src = r#"outer <%- inner %> outer"#;
|
||||
let lexer = Parser::new();
|
||||
let token_iter = lexer.parse(src);
|
||||
let mut ps = SourceBuilder {
|
||||
|
@ -400,13 +395,13 @@ mod tests {
|
|||
.ast
|
||||
.into_token_stream()
|
||||
.to_string(),
|
||||
r#"{ __sf_rt :: render_text ! (__sf_buf , "outer ") ; __sf_rt :: render ! (__sf_buf , inner . render_once () ?) ; __sf_rt :: render_text ! (__sf_buf , " outer") ; }"#
|
||||
r#"{ __sf_rt :: render_text (__sf_buf , "outer ") ; __sf_rt :: render (__sf_buf , inner) ? ; __sf_rt :: render_text (__sf_buf , " outer") ; }"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn translate_nested_render_once_with_filter() {
|
||||
let src = r#"outer <%+ inner|upper %> outer"#;
|
||||
let src = r#"outer <%- inner | upper %> outer"#;
|
||||
let lexer = Parser::new();
|
||||
let token_iter = lexer.parse(src);
|
||||
let mut ps = SourceBuilder {
|
||||
|
@ -422,7 +417,7 @@ mod tests {
|
|||
.ast
|
||||
.into_token_stream()
|
||||
.to_string(),
|
||||
r#"{ __sf_rt :: render_text ! (__sf_buf , "outer ") ; __sf_rt :: render ! (__sf_buf , sailfish :: runtime :: filter :: upper (& (inner . render_once () ?))) ; __sf_rt :: render_text ! (__sf_buf , " outer") ; }"#
|
||||
r#"{ __sf_rt :: render_text (__sf_buf , "outer ") ; __sf_rt :: render (__sf_buf , sailfish :: runtime :: filter :: upper ((inner))) ? ; __sf_rt :: render_text (__sf_buf , " outer") ; }"#
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "sailfish-macros"
|
||||
version = "0.8.1"
|
||||
version = "0.8.3"
|
||||
authors = ["Ryohei Machida <orcinus4627@gmail.com>"]
|
||||
description = "Simple, small, and extremely fast template engine for Rust"
|
||||
homepage = "https://github.com/rust-sailfish/sailfish"
|
||||
|
@ -30,6 +30,6 @@ proc-macro2 = "1.0.56"
|
|||
|
||||
[dependencies.sailfish-compiler]
|
||||
path = "../sailfish-compiler"
|
||||
version = "0.8.1"
|
||||
version = "0.8.3"
|
||||
default-features = false
|
||||
features = ["procmacro"]
|
||||
|
|
|
@ -4,7 +4,7 @@ extern crate proc_macro;
|
|||
|
||||
use proc_macro::TokenStream;
|
||||
|
||||
#[proc_macro_derive(TemplateOnce, attributes(template))]
|
||||
#[proc_macro_derive(RenderOnce, attributes(template))]
|
||||
pub fn derive_template_once(tokens: TokenStream) -> TokenStream {
|
||||
let input = proc_macro2::TokenStream::from(tokens);
|
||||
let output = sailfish_compiler::procmacro::derive_template(input);
|
||||
|
@ -12,7 +12,7 @@ pub fn derive_template_once(tokens: TokenStream) -> TokenStream {
|
|||
}
|
||||
|
||||
/// WIP
|
||||
#[proc_macro_derive(Template, attributes(template))]
|
||||
#[proc_macro_derive(Render, attributes(template))]
|
||||
pub fn derive_template(tokens: TokenStream) -> TokenStream {
|
||||
let input = proc_macro2::TokenStream::from(tokens);
|
||||
let output = sailfish_compiler::procmacro::derive_template(input);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "fuzzing-tests"
|
||||
version = "0.8.1"
|
||||
version = "0.8.3"
|
||||
authors = ["Ryohei Machida <orcinus4627@gmail.com>"]
|
||||
edition = "2018"
|
||||
publish = false
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "integration-tests"
|
||||
version = "0.8.1"
|
||||
version = "0.8.3"
|
||||
authors = ["Ryohei Machida <orcinus4627@gmail.com>"]
|
||||
edition = "2018"
|
||||
publish = false
|
||||
|
|
|
@ -1,12 +1,19 @@
|
|||
use sailfish::TemplateOnce;
|
||||
use sailfish_macros::TemplateOnce;
|
||||
use sailfish::RenderOnce;
|
||||
use sailfish_macros::RenderOnce;
|
||||
|
||||
#[derive(TemplateOnce)]
|
||||
#[template(path = "foo.stpl", escape=1)]
|
||||
#[derive(RenderOnce)]
|
||||
#[template(path = "foo.stpl", escape = 1)]
|
||||
struct InvalidOptionValue {
|
||||
name: String
|
||||
name: String,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println!("{}", InvalidOptionValue { name: "Hanako".to_owned() }.render_once().unwrap());
|
||||
println!(
|
||||
"{}",
|
||||
InvalidOptionValue {
|
||||
name: "Hanako".to_owned()
|
||||
}
|
||||
.render_once()
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
|
|
|
@ -15,12 +15,12 @@ error[E0599]: no method named `render_once` found for struct `InvalidOptionValue
|
|||
|
|
||||
= help: items from traits can only be used if the trait is implemented and in scope
|
||||
= note: the following trait defines an item `render_once`, perhaps you need to implement it:
|
||||
candidate #1: `TemplateOnce`
|
||||
candidate #1: `RenderOnce`
|
||||
|
||||
warning: unused import: `sailfish::TemplateOnce`
|
||||
warning: unused import: `sailfish::RenderOnce`
|
||||
--> $DIR/invalid_option_value.rs:1:5
|
||||
|
|
||||
1 | use sailfish::TemplateOnce;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
1 | use sailfish::RenderOnce;
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: `#[warn(unused_imports)]` on by default
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use sailfish::TemplateOnce;
|
||||
use sailfish_macros::TemplateOnce;
|
||||
use sailfish::RenderOnce;
|
||||
use sailfish_macros::RenderOnce;
|
||||
|
||||
#[derive(TemplateOnce)]
|
||||
#[derive(RenderOnce)]
|
||||
#[template(path = "missing_semicolon.stpl")]
|
||||
struct MissingSemicolon {}
|
||||
|
||||
|
|
|
@ -10,8 +10,8 @@ position: line 1, column 17
|
|||
|
||||
--> $DIR/missing_semicolon.rs:4:10
|
||||
|
|
||||
4 | #[derive(TemplateOnce)]
|
||||
| ^^^^^^^^^^^^
|
||||
4 | #[derive(RenderOnce)]
|
||||
| ^^^^^^^^^^
|
||||
|
|
||||
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
|
@ -26,12 +26,12 @@ error[E0599]: no method named `render_once` found for struct `MissingSemicolon`
|
|||
|
|
||||
= help: items from traits can only be used if the trait is implemented and in scope
|
||||
= note: the following trait defines an item `render_once`, perhaps you need to implement it:
|
||||
candidate #1: `TemplateOnce`
|
||||
candidate #1: `RenderOnce`
|
||||
|
||||
warning: unused import: `sailfish::TemplateOnce`
|
||||
warning: unused import: `sailfish::RenderOnce`
|
||||
--> $DIR/missing_semicolon.rs:1:5
|
||||
|
|
||||
1 | use sailfish::TemplateOnce;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
1 | use sailfish::RenderOnce;
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: `#[warn(unused_imports)]` on by default
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use sailfish::TemplateOnce;
|
||||
use sailfish_macros::TemplateOnce;
|
||||
use sailfish::RenderOnce;
|
||||
use sailfish_macros::RenderOnce;
|
||||
|
||||
#[derive(TemplateOnce)]
|
||||
#[derive(RenderOnce)]
|
||||
struct NoTemplate {
|
||||
var: usize
|
||||
var: usize,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
error: `path` option must be specified.
|
||||
--> $DIR/no_path.rs:4:10
|
||||
|
|
||||
4 | #[derive(TemplateOnce)]
|
||||
| ^^^^^^^^^^^^
|
||||
4 | #[derive(RenderOnce)]
|
||||
| ^^^^^^^^^^
|
||||
|
|
||||
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
|
@ -17,12 +17,12 @@ error[E0599]: no method named `render_once` found for struct `NoTemplate` in the
|
|||
|
|
||||
= help: items from traits can only be used if the trait is implemented and in scope
|
||||
= note: the following trait defines an item `render_once`, perhaps you need to implement it:
|
||||
candidate #1: `TemplateOnce`
|
||||
candidate #1: `RenderOnce`
|
||||
|
||||
warning: unused import: `sailfish::TemplateOnce`
|
||||
warning: unused import: `sailfish::RenderOnce`
|
||||
--> $DIR/no_path.rs:1:5
|
||||
|
|
||||
1 | use sailfish::TemplateOnce;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
1 | use sailfish::RenderOnce;
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: `#[warn(unused_imports)]` on by default
|
||||
|
|
|
@ -1,13 +1,20 @@
|
|||
use sailfish::TemplateOnce;
|
||||
use sailfish_macros::TemplateOnce;
|
||||
use sailfish::RenderOnce;
|
||||
use sailfish_macros::RenderOnce;
|
||||
|
||||
#[derive(TemplateOnce)]
|
||||
#[template(path = "foo.stpl", escape=true)]
|
||||
#[derive(RenderOnce)]
|
||||
#[template(path = "foo.stpl", escape = true)]
|
||||
#[template(escape = false)]
|
||||
struct InvalidOptionValue {
|
||||
name: String
|
||||
name: String,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println!("{}", InvalidOptionValue { name: "Hanako".to_owned() }.render_once().unwrap());
|
||||
println!(
|
||||
"{}",
|
||||
InvalidOptionValue {
|
||||
name: "Hanako".to_owned()
|
||||
}
|
||||
.render_once()
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
|
|
|
@ -15,12 +15,12 @@ error[E0599]: no method named `render_once` found for struct `InvalidOptionValue
|
|||
|
|
||||
= help: items from traits can only be used if the trait is implemented and in scope
|
||||
= note: the following trait defines an item `render_once`, perhaps you need to implement it:
|
||||
candidate #1: `TemplateOnce`
|
||||
candidate #1: `RenderOnce`
|
||||
|
||||
warning: unused import: `sailfish::TemplateOnce`
|
||||
warning: unused import: `sailfish::RenderOnce`
|
||||
--> $DIR/repeated_arguments.rs:1:5
|
||||
|
|
||||
1 | use sailfish::TemplateOnce;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
1 | use sailfish::RenderOnce;
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: `#[warn(unused_imports)]` on by default
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
use sailfish::TemplateOnce;
|
||||
use sailfish_macros::TemplateOnce;
|
||||
use sailfish::RenderOnce;
|
||||
use sailfish_macros::RenderOnce;
|
||||
|
||||
#[derive(TemplateOnce)]
|
||||
#[derive(RenderOnce)]
|
||||
#[template(path = "empty.stpl")]
|
||||
struct ExistTemplate;
|
||||
|
||||
#[derive(TemplateOnce)]
|
||||
#[derive(RenderOnce)]
|
||||
#[template(path = "not_exist.stpl")]
|
||||
struct NotExistTemplate {
|
||||
var: usize
|
||||
var: usize,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|
|
|
@ -15,4 +15,4 @@ error[E0599]: no method named `render_once` found for struct `NotExistTemplate`
|
|||
|
|
||||
= help: items from traits can only be used if the trait is implemented and in scope
|
||||
= note: the following trait defines an item `render_once`, perhaps you need to implement it:
|
||||
candidate #1: `TemplateOnce`
|
||||
candidate #1: `RenderOnce`
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
use sailfish::TemplateOnce;
|
||||
use sailfish_macros::TemplateOnce;
|
||||
use sailfish::RenderOnce;
|
||||
use sailfish_macros::RenderOnce;
|
||||
|
||||
struct Player<'a> {
|
||||
name: &'a str,
|
||||
score: u32,
|
||||
}
|
||||
|
||||
#[derive(TemplateOnce)]
|
||||
#[derive(RenderOnce)]
|
||||
#[template(path = "unbalanced_brace.stpl")]
|
||||
struct UnbalancedBrace {
|
||||
players: Vec<Player>,
|
||||
|
@ -16,7 +16,10 @@ fn main() {
|
|||
println!(
|
||||
"{}",
|
||||
UnclosedDelimiter {
|
||||
players: vec![Player { name: "Hanako", score: 97 }]
|
||||
players: vec![Player {
|
||||
name: "Hanako",
|
||||
score: 97
|
||||
}]
|
||||
}
|
||||
.render_once()
|
||||
.unwrap()
|
||||
|
|
|
@ -5,8 +5,8 @@ file: unbalanced_brace.stpl
|
|||
|
||||
--> $DIR/unbalanced_brace.rs:9:10
|
||||
|
|
||||
9 | #[derive(TemplateOnce)]
|
||||
| ^^^^^^^^^^^^
|
||||
9 | #[derive(RenderOnce)]
|
||||
| ^^^^^^^^^^
|
||||
|
|
||||
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use sailfish::TemplateOnce;
|
||||
use sailfish_macros::TemplateOnce;
|
||||
use sailfish::RenderOnce;
|
||||
use sailfish_macros::RenderOnce;
|
||||
|
||||
#[derive(TemplateOnce)]
|
||||
#[derive(RenderOnce)]
|
||||
#[template(path = "unclosed_delimiter.stpl")]
|
||||
struct UnclosedDelimiter {
|
||||
content: String,
|
||||
|
|
|
@ -10,8 +10,8 @@ position: line 3, column 5
|
|||
|
||||
--> $DIR/unclosed_delimter.rs:4:10
|
||||
|
|
||||
4 | #[derive(TemplateOnce)]
|
||||
| ^^^^^^^^^^^^
|
||||
4 | #[derive(RenderOnce)]
|
||||
| ^^^^^^^^^^
|
||||
|
|
||||
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
|
@ -26,12 +26,12 @@ error[E0599]: no method named `render_once` found for struct `UnclosedDelimiter`
|
|||
|
|
||||
= help: items from traits can only be used if the trait is implemented and in scope
|
||||
= note: the following trait defines an item `render_once`, perhaps you need to implement it:
|
||||
candidate #1: `TemplateOnce`
|
||||
candidate #1: `RenderOnce`
|
||||
|
||||
warning: unused import: `sailfish::TemplateOnce`
|
||||
warning: unused import: `sailfish::RenderOnce`
|
||||
--> $DIR/unclosed_delimter.rs:1:5
|
||||
|
|
||||
1 | use sailfish::TemplateOnce;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
1 | use sailfish::RenderOnce;
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: `#[warn(unused_imports)]` on by default
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use sailfish::TemplateOnce;
|
||||
use sailfish_macros::TemplateOnce;
|
||||
use sailfish::RenderOnce;
|
||||
use sailfish_macros::RenderOnce;
|
||||
|
||||
struct Content<'a> {
|
||||
id: u32,
|
||||
|
@ -7,12 +7,12 @@ struct Content<'a> {
|
|||
phone_number: &'a str,
|
||||
}
|
||||
|
||||
#[derive(TemplateOnce)]
|
||||
#[derive(RenderOnce)]
|
||||
#[template(path = "unexpected_token.stpl")]
|
||||
#[template(escape = false)]
|
||||
struct UnexpectedToken<'a> {
|
||||
name: &'a str,
|
||||
content: Content<'a>
|
||||
content: Content<'a>,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|
|
|
@ -10,8 +10,8 @@ position: line 3, column 17
|
|||
|
||||
--> $DIR/unexpected_token.rs:10:10
|
||||
|
|
||||
10 | #[derive(TemplateOnce)]
|
||||
| ^^^^^^^^^^^^
|
||||
10 | #[derive(RenderOnce)]
|
||||
| ^^^^^^^^^^
|
||||
|
|
||||
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
|
@ -21,10 +21,10 @@ error[E0422]: cannot find struct, variant or union type `UnclosedToken` in this
|
|||
21 | UnclosedToken {
|
||||
| ^^^^^^^^^^^^^ not found in this scope
|
||||
|
||||
warning: unused import: `sailfish::TemplateOnce`
|
||||
warning: unused import: `sailfish::RenderOnce`
|
||||
--> $DIR/unexpected_token.rs:1:5
|
||||
|
|
||||
1 | use sailfish::TemplateOnce;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
1 | use sailfish::RenderOnce;
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: `#[warn(unused_imports)]` on by default
|
||||
|
|
|
@ -1,12 +1,19 @@
|
|||
use sailfish::TemplateOnce;
|
||||
use sailfish_macros::TemplateOnce;
|
||||
use sailfish::RenderOnce;
|
||||
use sailfish_macros::RenderOnce;
|
||||
|
||||
#[derive(TemplateOnce)]
|
||||
#[derive(RenderOnce)]
|
||||
#[template(patth = "foo.stpl")]
|
||||
struct UnknownOption {
|
||||
name: String
|
||||
name: String,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println!("{}", UnknownOption { name: "Hanako".to_owned() }.render_once().unwrap());
|
||||
println!(
|
||||
"{}",
|
||||
UnknownOption {
|
||||
name: "Hanako".to_owned()
|
||||
}
|
||||
.render_once()
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
|
|
|
@ -15,12 +15,12 @@ error[E0599]: no method named `render_once` found for struct `UnknownOption` in
|
|||
|
|
||||
= help: items from traits can only be used if the trait is implemented and in scope
|
||||
= note: the following trait defines an item `render_once`, perhaps you need to implement it:
|
||||
candidate #1: `TemplateOnce`
|
||||
candidate #1: `RenderOnce`
|
||||
|
||||
warning: unused import: `sailfish::TemplateOnce`
|
||||
warning: unused import: `sailfish::RenderOnce`
|
||||
--> $DIR/unknown_option.rs:1:5
|
||||
|
|
||||
1 | use sailfish::TemplateOnce;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
1 | use sailfish::RenderOnce;
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: `#[warn(unused_imports)]` on by default
|
||||
|
|
|
@ -3,7 +3,7 @@ extern crate sailfish_macros;
|
|||
|
||||
use integration_tests::assert_string_eq;
|
||||
use sailfish::runtime::RenderResult;
|
||||
use sailfish::TemplateOnce;
|
||||
use sailfish::RenderOnce;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn assert_render_result(name: &str, result: RenderResult) {
|
||||
|
@ -23,8 +23,8 @@ fn assert_render_result(name: &str, result: RenderResult) {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
fn assert_render<T: TemplateOnce>(name: &str, template: T) {
|
||||
assert_render_result(name, template.render_once());
|
||||
fn assert_render<T: RenderOnce>(name: &str, template: T) {
|
||||
assert_render_result(name, template.render_once_to_string());
|
||||
}
|
||||
|
||||
trait ConflictWithSailFishRender {
|
||||
|
@ -34,7 +34,7 @@ trait ConflictWithSailFishRender {
|
|||
impl ConflictWithSailFishRender for u8 {}
|
||||
impl ConflictWithSailFishRender for u16 {}
|
||||
|
||||
#[derive(TemplateOnce)]
|
||||
#[derive(RenderOnce)]
|
||||
#[template(path = "empty.stpl")]
|
||||
struct Empty {}
|
||||
|
||||
|
@ -43,7 +43,7 @@ fn empty() {
|
|||
assert_render("empty", Empty {});
|
||||
}
|
||||
|
||||
#[derive(TemplateOnce)]
|
||||
#[derive(RenderOnce)]
|
||||
#[template(path = "noescape.stpl")]
|
||||
struct Noescape<'a> {
|
||||
raw: &'a str,
|
||||
|
@ -59,7 +59,7 @@ fn noescape() {
|
|||
);
|
||||
}
|
||||
|
||||
#[derive(TemplateOnce)]
|
||||
#[derive(RenderOnce)]
|
||||
#[template(path = "json.stpl")]
|
||||
struct Json {
|
||||
name: String,
|
||||
|
@ -77,7 +77,7 @@ fn json() {
|
|||
);
|
||||
}
|
||||
|
||||
#[derive(TemplateOnce)]
|
||||
#[derive(RenderOnce)]
|
||||
#[template(path = "custom_delimiter.stpl")]
|
||||
#[template(delimiter = '🍣')]
|
||||
struct CustomDelimiter;
|
||||
|
@ -87,7 +87,7 @@ fn custom_delimiter() {
|
|||
assert_render("custom_delimiter", CustomDelimiter);
|
||||
}
|
||||
|
||||
#[derive(TemplateOnce)]
|
||||
#[derive(RenderOnce)]
|
||||
#[template(path = "include.stpl")]
|
||||
struct Include<'a> {
|
||||
strs: &'a [&'a str],
|
||||
|
@ -103,7 +103,7 @@ fn test_include() {
|
|||
);
|
||||
}
|
||||
|
||||
#[derive(TemplateOnce)]
|
||||
#[derive(RenderOnce)]
|
||||
#[template(path = "continue-break.stpl", rm_whitespace = true)]
|
||||
struct ContinueBreak;
|
||||
|
||||
|
@ -112,7 +112,7 @@ fn continue_break() {
|
|||
assert_render("continue-break", ContinueBreak);
|
||||
}
|
||||
|
||||
#[derive(TemplateOnce)]
|
||||
#[derive(RenderOnce)]
|
||||
#[template(path = "techempower.stpl", rm_whitespace = true)]
|
||||
struct Techempower {
|
||||
items: Vec<Fortune>,
|
||||
|
@ -182,7 +182,7 @@ fn test_techempower() {
|
|||
assert_render("techempower", Techempower { items });
|
||||
}
|
||||
|
||||
#[derive(TemplateOnce)]
|
||||
#[derive(RenderOnce)]
|
||||
#[template(path = "rm_whitespace.stpl")]
|
||||
#[template(rm_whitespace = true)]
|
||||
struct RmWhitespace<'a, 'b> {
|
||||
|
@ -199,7 +199,7 @@ fn test_rm_whitespace() {
|
|||
);
|
||||
}
|
||||
|
||||
#[derive(TemplateOnce)]
|
||||
#[derive(RenderOnce)]
|
||||
#[template(path = "comment.stpl")]
|
||||
struct Comment {}
|
||||
|
||||
|
@ -208,7 +208,7 @@ fn test_comment() {
|
|||
assert_render("comment", Comment {})
|
||||
}
|
||||
|
||||
#[derive(TemplateOnce)]
|
||||
#[derive(RenderOnce)]
|
||||
#[template(path = "rust_macro.stpl", rm_whitespace = true)]
|
||||
struct RustMacro {
|
||||
value: Option<i32>,
|
||||
|
@ -219,7 +219,7 @@ fn test_rust_macro() {
|
|||
assert_render("rust_macro", RustMacro { value: Some(10) });
|
||||
}
|
||||
|
||||
#[derive(TemplateOnce)]
|
||||
#[derive(RenderOnce)]
|
||||
#[template(path = "formatting.stpl", escape = false)]
|
||||
struct Formatting;
|
||||
|
||||
|
@ -228,7 +228,7 @@ fn test_formatting() {
|
|||
assert_render("formatting", Formatting);
|
||||
}
|
||||
|
||||
#[derive(TemplateOnce)]
|
||||
#[derive(RenderOnce)]
|
||||
#[template(path = "filter.stpl")]
|
||||
struct Filter<'a> {
|
||||
message: &'a str,
|
||||
|
@ -239,7 +239,7 @@ fn test_filter() {
|
|||
assert_render("filter", Filter { message: "hello" });
|
||||
}
|
||||
|
||||
#[derive(TemplateOnce)]
|
||||
#[derive(RenderOnce)]
|
||||
#[template(path = "filter2.stpl")]
|
||||
struct Filter2;
|
||||
|
||||
|
@ -248,7 +248,7 @@ fn test_filter2() {
|
|||
assert_render("filter2", Filter2);
|
||||
}
|
||||
|
||||
#[derive(TemplateOnce)]
|
||||
#[derive(RenderOnce)]
|
||||
#[template(path = "truncate-filter.stpl")]
|
||||
struct TruncateFilter;
|
||||
|
||||
|
@ -256,7 +256,7 @@ struct TruncateFilter;
|
|||
fn test_truncate_filter() {
|
||||
assert_render("truncate-filter", TruncateFilter);
|
||||
}
|
||||
#[derive(TemplateOnce)]
|
||||
#[derive(RenderOnce)]
|
||||
#[template(path = "json-filter.stpl")]
|
||||
struct JsonFilter {
|
||||
data: serde_json::Value,
|
||||
|
@ -280,7 +280,7 @@ fn test_json_filter() {
|
|||
mod unix {
|
||||
use super::*;
|
||||
|
||||
#[derive(TemplateOnce)]
|
||||
#[derive(RenderOnce)]
|
||||
#[template(path = "include-nest.stpl")]
|
||||
struct IncludeNest<'a> {
|
||||
s: &'a str,
|
||||
|
@ -291,7 +291,7 @@ mod unix {
|
|||
assert_render("include-nest", IncludeNest { s: "foo" });
|
||||
}
|
||||
|
||||
#[derive(TemplateOnce)]
|
||||
#[derive(RenderOnce)]
|
||||
#[template(path = "include_rust.stpl")]
|
||||
struct IncludeRust {
|
||||
value: usize,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "sailfish"
|
||||
version = "0.8.1"
|
||||
version = "0.8.3"
|
||||
authors = ["Ryohei Machida <orcinus4627@gmail.com>"]
|
||||
description = "Simple, small, and extremely fast template engine for Rust"
|
||||
homepage = "https://github.com/rust-sailfish/sailfish"
|
||||
|
@ -31,7 +31,7 @@ serde_json = { version = "1.0.95", optional = true }
|
|||
|
||||
[dependencies.sailfish-macros]
|
||||
path = "../sailfish-macros"
|
||||
version = "0.8.1"
|
||||
version = "0.8.3"
|
||||
default-features = false
|
||||
optional = true
|
||||
|
||||
|
|
|
@ -4,16 +4,16 @@
|
|||
//!
|
||||
//! This crate contains utilities for rendering sailfish template.
|
||||
//! If you want to use sailfish templates, import `sailfish-macros` crate and use
|
||||
//! derive macro `#[derive(TemplateOnce)]` or `#[derive(Template)]`.
|
||||
//! derive macro `#[derive(RenderOnce)]` or `#[derive(Render)]`.
|
||||
//!
|
||||
//! In most cases you don't need to care about the `runtime` module in this crate, but
|
||||
//! if you want to render custom data inside templates, you must implement
|
||||
//! `runtime::Render` trait for that type.
|
||||
//!
|
||||
//! ```ignore
|
||||
//! use sailfish::TemplateOnce;
|
||||
//! use sailfish::RenderOnce;
|
||||
//!
|
||||
//! #[derive(TemplateOnce)]
|
||||
//! #[derive(RenderOnce)]
|
||||
//! #[template(path = "hello.stpl")]
|
||||
//! struct HelloTemplate {
|
||||
//! messages: Vec<String>
|
||||
|
@ -38,69 +38,7 @@
|
|||
|
||||
pub mod runtime;
|
||||
|
||||
use runtime::Buffer;
|
||||
pub use runtime::{RenderError, RenderResult};
|
||||
pub use runtime::{Buffer, Render, RenderError, RenderOnce, RenderResult};
|
||||
#[cfg(feature = "derive")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "derive")))]
|
||||
pub use sailfish_macros::TemplateOnce;
|
||||
|
||||
/// Template that can be rendered with consuming itself.
|
||||
pub trait TemplateOnce: Sized + private::Sealed {
|
||||
/// Render the template and return the rendering result as `RenderResult`
|
||||
///
|
||||
/// This method never returns `Err`, unless you explicitly return RenderError
|
||||
/// inside templates
|
||||
///
|
||||
/// When you use `render_once` method, total rendered size will be cached, and at
|
||||
/// the next time, buffer will be pre-allocated based on the cached length.
|
||||
///
|
||||
/// If you don't want this behaviour, you can use `render_once_to` method instead.
|
||||
fn render_once(self) -> runtime::RenderResult;
|
||||
|
||||
/// Render the template and append the result to `buf`.
|
||||
///
|
||||
/// This method never returns `Err`, unless you explicitly return RenderError
|
||||
/// inside templates
|
||||
///
|
||||
/// ```
|
||||
/// use sailfish::TemplateOnce;
|
||||
/// use sailfish::runtime::Buffer;
|
||||
///
|
||||
/// # pub struct HelloTemplate {
|
||||
/// # messages: Vec<String>,
|
||||
/// # }
|
||||
/// #
|
||||
/// # impl TemplateOnce for HelloTemplate {
|
||||
/// # fn render_once(self) -> Result<String, sailfish::RenderError> {
|
||||
/// # Ok(String::new())
|
||||
/// # }
|
||||
/// #
|
||||
/// # fn render_once_to(self, buf: &mut Buffer)
|
||||
/// # -> Result<(), sailfish::RenderError> {
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # }
|
||||
/// #
|
||||
/// # impl sailfish::private::Sealed for HelloTemplate {}
|
||||
/// #
|
||||
/// let tpl = HelloTemplate {
|
||||
/// messages: vec!["foo".to_string()]
|
||||
/// };
|
||||
///
|
||||
/// // custom pre-allocation
|
||||
/// let mut buffer = Buffer::with_capacity(100);
|
||||
/// tpl.render_once_to(&mut buffer).unwrap();
|
||||
/// ```
|
||||
fn render_once_to(self, buf: &mut Buffer) -> Result<(), RenderError>;
|
||||
}
|
||||
|
||||
/// Work in Progress
|
||||
pub trait Template: private::Sealed {
|
||||
/// Work in progress
|
||||
fn render(&self) -> runtime::RenderResult;
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub mod private {
|
||||
pub trait Sealed {}
|
||||
}
|
||||
pub use sailfish_macros::{Render, RenderOnce};
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
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)
|
||||
}
|
|
@ -3,12 +3,15 @@
|
|||
use std::fmt;
|
||||
use std::ptr;
|
||||
|
||||
use super::escape;
|
||||
use super::render::RenderOnce;
|
||||
use super::{Buffer, Render, RenderError};
|
||||
|
||||
/// Helper struct for 'display' filter
|
||||
pub struct Display<'a, T: ?Sized>(&'a T);
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Display<T>(T);
|
||||
|
||||
impl<'a, T: fmt::Display + ?Sized> Render for Display<'a, T> {
|
||||
impl<T: fmt::Display> Render for Display<T> {
|
||||
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
use fmt::Write;
|
||||
|
||||
|
@ -24,14 +27,15 @@ impl<'a, T: fmt::Display + ?Sized> Render for Display<'a, T> {
|
|||
/// filename: <%= filename.display() | disp %>
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn disp<T: fmt::Display + ?Sized>(expr: &T) -> Display<T> {
|
||||
pub fn disp<T: fmt::Display>(expr: T) -> Display<T> {
|
||||
Display(expr)
|
||||
}
|
||||
|
||||
/// Helper struct for 'dbg' filter
|
||||
pub struct Debug<'a, T: ?Sized>(&'a T);
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Debug<T>(T);
|
||||
|
||||
impl<'a, T: fmt::Debug + ?Sized> Render for Debug<'a, T> {
|
||||
impl<T: fmt::Debug> Render for Debug<T> {
|
||||
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
use fmt::Write;
|
||||
|
||||
|
@ -53,32 +57,38 @@ impl<'a, T: fmt::Debug + ?Sized> Render for Debug<'a, T> {
|
|||
/// table content: <%= format!("{:?}", table) %>
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn dbg<T: fmt::Debug + ?Sized>(expr: &T) -> Debug<T> {
|
||||
pub fn dbg<T: fmt::Debug>(expr: T) -> Debug<T> {
|
||||
Debug(expr)
|
||||
}
|
||||
|
||||
/// Helper struct for 'upper' filter
|
||||
pub struct Upper<'a, T: ?Sized>(&'a T);
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Upper<T>(T);
|
||||
|
||||
impl<'a, T: Render + ?Sized> Render for Upper<'a, T> {
|
||||
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
let old_len = b.len();
|
||||
self.0.render(b)?;
|
||||
|
||||
let content = b.as_str().get(old_len..).ok_or(RenderError::BufSize)?;
|
||||
let s = content.to_uppercase();
|
||||
unsafe { b._set_len(old_len) };
|
||||
b.push_str(&*s);
|
||||
impl<T: RenderOnce> RenderOnce for Upper<T> {
|
||||
fn render_once(self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
let mut tmp = Buffer::new();
|
||||
self.0.render_once(&mut tmp)?;
|
||||
// Estimate assuming ASCII and non-convertible UTF-8 are the most common.
|
||||
b.reserve(tmp.len());
|
||||
for c in tmp.as_str().chars().flat_map(|c| c.to_uppercase()) {
|
||||
b.push(c);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
fn render_once_escaped(self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
let old_len = b.len();
|
||||
self.0.render_escaped(b)?;
|
||||
|
||||
let s = b.as_str()[old_len..].to_uppercase();
|
||||
self.0.render_once(b)?;
|
||||
let mut tmp = Buffer::new();
|
||||
let s = &b.as_str()[old_len..];
|
||||
// Estimate assuming ASCII and non-convertible UTF-8 are the most common.
|
||||
tmp.reserve(s.len());
|
||||
for c in s.chars().flat_map(|c| c.to_uppercase()) {
|
||||
tmp.push(c);
|
||||
}
|
||||
unsafe { b._set_len(old_len) };
|
||||
b.push_str(&*s);
|
||||
escape::escape_to_buf(tmp.as_str(), b);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -97,32 +107,57 @@ impl<'a, T: Render + ?Sized> Render for Upper<'a, T> {
|
|||
/// TSCHÜSS
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn upper<T: Render + ?Sized>(expr: &T) -> Upper<T> {
|
||||
pub fn upper<T: RenderOnce>(expr: T) -> Upper<T> {
|
||||
Upper(expr)
|
||||
}
|
||||
|
||||
/// Helper struct for 'lower' filter
|
||||
pub struct Lower<'a, T: ?Sized>(&'a T);
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Lower<T>(T);
|
||||
|
||||
impl<'a, T: Render + ?Sized> Render for Lower<'a, T> {
|
||||
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
impl<T: RenderOnce> RenderOnce for Lower<T> {
|
||||
fn render_once(self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
let mut tmp = Buffer::new();
|
||||
self.0.render_once(&mut tmp)?;
|
||||
// Estimate assuming ASCII and non-convertible UTF-8 are the most common.
|
||||
b.reserve(tmp.len());
|
||||
let old_len = b.len();
|
||||
self.0.render(b)?;
|
||||
|
||||
let content = b.as_str().get(old_len..).ok_or(RenderError::BufSize)?;
|
||||
let s = content.to_lowercase();
|
||||
unsafe { b._set_len(old_len) };
|
||||
b.push_str(&*s);
|
||||
for c in tmp.as_str().chars() {
|
||||
// see comments in str::to_lowercase
|
||||
if c == 'Σ' {
|
||||
let lower = tmp.as_str().to_lowercase();
|
||||
unsafe { b._set_len(old_len) };
|
||||
b.push_str(&lower);
|
||||
return Ok(());
|
||||
}
|
||||
for c in c.to_lowercase() {
|
||||
b.push(c);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
fn render_once_escaped(self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
let old_len = b.len();
|
||||
self.0.render_escaped(b)?;
|
||||
|
||||
let s = b.as_str()[old_len..].to_lowercase();
|
||||
self.0.render_once(b)?;
|
||||
let mut tmp = Buffer::new();
|
||||
let s = &b.as_str()[old_len..];
|
||||
// Estimate assuming ASCII and non-convertible UTF-8 are the most common.
|
||||
tmp.reserve(s.len());
|
||||
for c in s.chars() {
|
||||
// see comments in str::to_lowercase
|
||||
if c == 'Σ' {
|
||||
let lower = s.to_lowercase();
|
||||
unsafe { b._set_len(old_len) };
|
||||
b.push_str(&lower);
|
||||
return Ok(());
|
||||
}
|
||||
for c in c.to_lowercase() {
|
||||
tmp.push(c);
|
||||
}
|
||||
}
|
||||
unsafe { b._set_len(old_len) };
|
||||
b.push_str(&*s);
|
||||
escape::escape_to_buf(tmp.as_str(), b);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -141,25 +176,26 @@ impl<'a, T: Render + ?Sized> Render for Lower<'a, T> {
|
|||
/// ὀδυσσεύς
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn lower<T: Render + ?Sized>(expr: &T) -> Lower<T> {
|
||||
pub fn lower<T: RenderOnce>(expr: T) -> Lower<T> {
|
||||
Lower(expr)
|
||||
}
|
||||
|
||||
/// Helper struct for 'trim' filter
|
||||
pub struct Trim<'a, T: ?Sized>(&'a T);
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Trim<T>(T);
|
||||
|
||||
impl<'a, T: Render + ?Sized> Render for Trim<'a, T> {
|
||||
impl<T: RenderOnce> RenderOnce for Trim<T> {
|
||||
#[inline]
|
||||
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
fn render_once(self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
let old_len = b.len();
|
||||
self.0.render(b)?;
|
||||
self.0.render_once(b)?;
|
||||
trim_impl(b, old_len)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
fn render_once_escaped(self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
let old_len = b.len();
|
||||
self.0.render_escaped(b)?;
|
||||
self.0.render_once_escaped(b)?;
|
||||
trim_impl(b, old_len)
|
||||
}
|
||||
}
|
||||
|
@ -211,25 +247,26 @@ fn trim_impl(b: &mut Buffer, old_len: usize) -> Result<(), RenderError> {
|
|||
/// Hello world
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn trim<T: Render + ?Sized>(expr: &T) -> Trim<T> {
|
||||
pub fn trim<T: RenderOnce>(expr: T) -> Trim<T> {
|
||||
Trim(expr)
|
||||
}
|
||||
|
||||
/// Helper struct for 'truncate' filter
|
||||
pub struct Truncate<'a, T: ?Sized>(&'a T, usize);
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Truncate<T>(T, usize);
|
||||
|
||||
impl<'a, T: Render + ?Sized> Render for Truncate<'a, T> {
|
||||
impl<T: RenderOnce> RenderOnce for Truncate<T> {
|
||||
#[inline]
|
||||
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
fn render_once(self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
let old_len = b.len();
|
||||
self.0.render(b)?;
|
||||
self.0.render_once(b)?;
|
||||
truncate_impl(b, old_len, self.1)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
fn render_once_escaped(self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
let old_len = b.len();
|
||||
self.0.render_escaped(b)?;
|
||||
self.0.render_once_escaped(b)?;
|
||||
truncate_impl(b, old_len, self.1)
|
||||
}
|
||||
}
|
||||
|
@ -242,7 +279,7 @@ fn truncate_impl(
|
|||
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.wrapping_add(idx)) };
|
||||
unsafe { b._set_len(old_len + idx) };
|
||||
b.push_str("...");
|
||||
}
|
||||
|
||||
|
@ -265,15 +302,16 @@ fn truncate_impl(
|
|||
/// Hello...
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn truncate<T: Render + ?Sized>(expr: &T, limit: usize) -> Truncate<T> {
|
||||
pub fn truncate<T: RenderOnce>(expr: T, limit: usize) -> Truncate<T> {
|
||||
Truncate(expr, limit)
|
||||
}
|
||||
|
||||
cfg_json! {
|
||||
/// Helper struct for 'json' filter
|
||||
pub struct Json<'a, T: ?Sized>(&'a T);
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Json<T>(T);
|
||||
|
||||
impl<'a, T: serde::Serialize + ?Sized> Render for Json<'a, T> {
|
||||
impl<T: serde::Serialize> Render for Json<T> {
|
||||
#[inline]
|
||||
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
struct Writer<'a>(&'a mut Buffer);
|
||||
|
@ -297,7 +335,7 @@ cfg_json! {
|
|||
}
|
||||
}
|
||||
|
||||
serde_json::to_writer(Writer(b), self.0)
|
||||
serde_json::to_writer(Writer(b), &self.0)
|
||||
.map_err(|e| RenderError::new(&e.to_string()))
|
||||
}
|
||||
|
||||
|
@ -326,7 +364,7 @@ cfg_json! {
|
|||
}
|
||||
}
|
||||
|
||||
serde_json::to_writer(Writer(b), self.0)
|
||||
serde_json::to_writer(Writer(b), &self.0)
|
||||
.map_err(|e| RenderError::new(&e.to_string()))
|
||||
}
|
||||
}
|
||||
|
@ -342,7 +380,7 @@ cfg_json! {
|
|||
/// }
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn json<T: serde::Serialize + ?Sized>(expr: &T) -> Json<T> {
|
||||
pub fn json<T: serde::Serialize>(expr: T) -> Json<T> {
|
||||
Json(expr)
|
||||
}
|
||||
}
|
||||
|
@ -351,124 +389,124 @@ cfg_json! {
|
|||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn assert_render<T: Render>(expr: &T, expected: &str) {
|
||||
fn assert_render<T: RenderOnce>(expr: T, expected: &str) {
|
||||
let mut buf = Buffer::new();
|
||||
Render::render(expr, &mut buf).unwrap();
|
||||
RenderOnce::render_once(expr, &mut buf).unwrap();
|
||||
assert_eq!(buf.as_str(), expected);
|
||||
}
|
||||
|
||||
fn assert_render_escaped<T: Render>(expr: &T, expected: &str) {
|
||||
fn assert_render_escaped<T: RenderOnce>(expr: T, expected: &str) {
|
||||
let mut buf = Buffer::new();
|
||||
Render::render_escaped(expr, &mut buf).unwrap();
|
||||
RenderOnce::render_once_escaped(expr, &mut buf).unwrap();
|
||||
assert_eq!(buf.as_str(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lower() {
|
||||
assert_render(&lower(""), "");
|
||||
assert_render_escaped(&lower(""), "");
|
||||
assert_render(lower(""), "");
|
||||
assert_render_escaped(lower(""), "");
|
||||
|
||||
assert_render(&lower("lorem ipsum"), "lorem ipsum");
|
||||
assert_render(&lower("LOREM IPSUM"), "lorem ipsum");
|
||||
assert_render(lower("lorem ipsum"), "lorem ipsum");
|
||||
assert_render(lower("LOREM IPSUM"), "lorem ipsum");
|
||||
|
||||
assert_render_escaped(&lower("hElLo, WOrLd!"), "hello, world!");
|
||||
assert_render_escaped(&lower("hElLo, WOrLd!"), "hello, world!");
|
||||
assert_render_escaped(lower("hElLo, WOrLd!"), "hello, world!");
|
||||
assert_render_escaped(lower("hElLo, WOrLd!"), "hello, world!");
|
||||
|
||||
assert_render_escaped(&lower("<h1>TITLE</h1>"), "<h1>title</h1>");
|
||||
assert_render_escaped(&lower("<<&\"\">>"), "<<&"">>");
|
||||
assert_render_escaped(lower("<h1>TITLE</h1>"), "<h1>title</h1>");
|
||||
assert_render_escaped(lower("<<&\"\">>"), "<<&"">>");
|
||||
|
||||
// non-ascii
|
||||
assert_render(&lower("aBcAbc"), "abcabc");
|
||||
assert_render(&lower("ὈΔΥΣΣΕΎΣ"), "ὀδυσσεύς");
|
||||
assert_render(lower("aBcAbc"), "abcabc");
|
||||
assert_render(lower("ὈΔΥΣΣΕΎΣ"), "ὀδυσσεύς");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_upper() {
|
||||
assert_render(&upper(""), "");
|
||||
assert_render_escaped(&upper(""), "");
|
||||
assert_render(upper(""), "");
|
||||
assert_render_escaped(upper(""), "");
|
||||
|
||||
assert_render(&upper("lorem ipsum"), "LOREM IPSUM");
|
||||
assert_render(&upper("LOREM IPSUM"), "LOREM IPSUM");
|
||||
assert_render(upper("lorem ipsum"), "LOREM IPSUM");
|
||||
assert_render(upper("LOREM IPSUM"), "LOREM IPSUM");
|
||||
|
||||
assert_render(&upper("hElLo, WOrLd!"), "HELLO, WORLD!");
|
||||
assert_render(&upper("hElLo, WOrLd!"), "HELLO, WORLD!");
|
||||
assert_render(upper("hElLo, WOrLd!"), "HELLO, WORLD!");
|
||||
assert_render(upper("hElLo, WOrLd!"), "HELLO, WORLD!");
|
||||
|
||||
// non-ascii
|
||||
assert_render(&upper("aBcAbc"), "ABCABC");
|
||||
assert_render(&upper("tschüß"), "TSCHÜSS");
|
||||
assert_render(upper("aBcAbc"), "ABCABC");
|
||||
assert_render(upper("tschüß"), "TSCHÜSS");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_trim() {
|
||||
assert_render(&trim(""), "");
|
||||
assert_render_escaped(&trim(""), "");
|
||||
assert_render(trim(""), "");
|
||||
assert_render_escaped(trim(""), "");
|
||||
|
||||
assert_render(&trim("\n \t\r\x0C"), "");
|
||||
assert_render(trim("\n \t\r\x0C"), "");
|
||||
|
||||
assert_render(&trim("hello world!"), "hello world!");
|
||||
assert_render(&trim("hello world!\n"), "hello world!");
|
||||
assert_render(&trim("\thello world!"), "hello world!");
|
||||
assert_render(&trim("\thello world!\r\n"), "hello world!");
|
||||
assert_render(trim("hello world!"), "hello world!");
|
||||
assert_render(trim("hello world!\n"), "hello world!");
|
||||
assert_render(trim("\thello world!"), "hello world!");
|
||||
assert_render(trim("\thello world!\r\n"), "hello world!");
|
||||
|
||||
assert_render_escaped(&trim(" <html> "), "<html>");
|
||||
assert_render_escaped(&lower("<<&\"\">>"), "<<&"">>");
|
||||
assert_render_escaped(trim(" <html> "), "<html>");
|
||||
assert_render_escaped(lower("<<&\"\">>"), "<<&"">>");
|
||||
|
||||
// non-ascii whitespace
|
||||
assert_render(&trim("\u{A0}空白\u{3000}\u{205F}"), "空白");
|
||||
assert_render(trim("\u{A0}空白\u{3000}\u{205F}"), "空白");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_truncate() {
|
||||
assert_render(&truncate("", 0), "");
|
||||
assert_render(&truncate("", 5), "");
|
||||
assert_render(truncate("", 0), "");
|
||||
assert_render(truncate("", 5), "");
|
||||
|
||||
assert_render(&truncate("apple ", 0), "...");
|
||||
assert_render(&truncate("apple ", 1), "a...");
|
||||
assert_render(&truncate("apple ", 2), "ap...");
|
||||
assert_render(&truncate("apple ", 3), "app...");
|
||||
assert_render(&truncate("apple ", 4), "appl...");
|
||||
assert_render(&truncate("apple ", 5), "apple...");
|
||||
assert_render(&truncate("apple ", 6), "apple ");
|
||||
assert_render(&truncate("apple ", 7), "apple ");
|
||||
assert_render(truncate("apple ", 0), "...");
|
||||
assert_render(truncate("apple ", 1), "a...");
|
||||
assert_render(truncate("apple ", 2), "ap...");
|
||||
assert_render(truncate("apple ", 3), "app...");
|
||||
assert_render(truncate("apple ", 4), "appl...");
|
||||
assert_render(truncate("apple ", 5), "apple...");
|
||||
assert_render(truncate("apple ", 6), "apple ");
|
||||
assert_render(truncate("apple ", 7), "apple ");
|
||||
|
||||
assert_render(&truncate(&std::f64::consts::PI, 10), "3.14159265...");
|
||||
assert_render(&truncate(&std::f64::consts::PI, 20), "3.141592653589793");
|
||||
assert_render(truncate(std::f64::consts::PI, 10), "3.14159265...");
|
||||
assert_render(truncate(std::f64::consts::PI, 20), "3.141592653589793");
|
||||
|
||||
assert_render_escaped(&truncate("foo<br>bar", 10), "foo<br&...");
|
||||
assert_render_escaped(&truncate("foo<br>bar", 20), "foo<br>bar");
|
||||
assert_render_escaped(truncate("foo<br>bar", 10), "foo<br&...");
|
||||
assert_render_escaped(truncate("foo<br>bar", 20), "foo<br>bar");
|
||||
|
||||
// non-ascii
|
||||
assert_render(&truncate("魑魅魍魎", 0), "...");
|
||||
assert_render(&truncate("魑魅魍魎", 1), "魑...");
|
||||
assert_render(&truncate("魑魅魍魎", 2), "魑魅...");
|
||||
assert_render(&truncate("魑魅魍魎", 3), "魑魅魍...");
|
||||
assert_render(&truncate("魑魅魍魎", 4), "魑魅魍魎");
|
||||
assert_render(&truncate("魑魅魍魎", 5), "魑魅魍魎");
|
||||
assert_render(truncate("魑魅魍魎", 0), "...");
|
||||
assert_render(truncate("魑魅魍魎", 1), "魑...");
|
||||
assert_render(truncate("魑魅魍魎", 2), "魑魅...");
|
||||
assert_render(truncate("魑魅魍魎", 3), "魑魅魍...");
|
||||
assert_render(truncate("魑魅魍魎", 4), "魑魅魍魎");
|
||||
assert_render(truncate("魑魅魍魎", 5), "魑魅魍魎");
|
||||
}
|
||||
|
||||
#[cfg(feature = "json")]
|
||||
#[test]
|
||||
fn test_json() {
|
||||
assert_render(&json(""), "\"\"");
|
||||
assert_render(&json(&serde_json::json!({})), "{}");
|
||||
assert_render(json(""), "\"\"");
|
||||
assert_render(json(serde_json::json!({})), "{}");
|
||||
|
||||
assert_render_escaped(&json(&123_i32), "123");
|
||||
assert_render_escaped(&json("Pokémon"), ""Pokémon"");
|
||||
assert_render_escaped(json(123_i32), "123");
|
||||
assert_render_escaped(json("Pokémon"), ""Pokémon"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compine() {
|
||||
assert_render(
|
||||
&lower(&upper("Li Europan lingues es membres del sam familie.")),
|
||||
lower(upper("Li Europan lingues es membres del sam familie.")),
|
||||
"li europan lingues es membres del sam familie.",
|
||||
);
|
||||
assert_render(&lower(&lower("ハートのA")), "ハートのa");
|
||||
assert_render(&upper(&upper("ハートのA")), "ハートのA");
|
||||
assert_render(lower(lower("ハートのA")), "ハートのa");
|
||||
assert_render(upper(upper("ハートのA")), "ハートのA");
|
||||
|
||||
assert_render(&truncate(&trim("\t起来!\r\n"), 1), "起...");
|
||||
assert_render(&truncate(&trim("\t起来!\r\n"), 3), "起来!");
|
||||
assert_render(truncate(trim("\t起来!\r\n"), 1), "起...");
|
||||
assert_render(truncate(trim("\t起来!\r\n"), 3), "起来!");
|
||||
|
||||
assert_render(&truncate(&lower("Was möchtest du?"), 10), "was möchte...");
|
||||
assert_render(&truncate(&upper("Was möchtest du?"), 10), "WAS MÖCHTE...");
|
||||
assert_render(truncate(lower("Was möchtest du?"), 10), "was möchte...");
|
||||
assert_render(truncate(upper("Was möchtest du?"), 10), "WAS MÖCHTE...");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
#[macro_export]
|
||||
#[doc(hidden)]
|
||||
macro_rules! render {
|
||||
($buf:ident, $value:expr) => {
|
||||
$crate::runtime::Render::render(&($value), $buf)?
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
#[doc(hidden)]
|
||||
macro_rules! render_escaped {
|
||||
($buf:ident, $value:expr) => {
|
||||
$crate::runtime::Render::render_escaped(&($value), $buf)?
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
#[doc(hidden)]
|
||||
macro_rules! render_text {
|
||||
($buf:ident, $value:expr) => {
|
||||
$buf.push_str($value)
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
#[doc(hidden)]
|
||||
macro_rules! render_noop {
|
||||
($buf:ident, $value:expr) => {};
|
||||
}
|
|
@ -3,16 +3,16 @@
|
|||
#[macro_use]
|
||||
mod utils;
|
||||
|
||||
mod alias_funcs;
|
||||
mod buffer;
|
||||
pub mod escape;
|
||||
pub mod filter;
|
||||
mod macros;
|
||||
mod render;
|
||||
mod size_hint;
|
||||
|
||||
pub use buffer::Buffer;
|
||||
pub use render::{Render, RenderError, RenderResult};
|
||||
pub use render::{Render, RenderError, RenderOnce, RenderResult};
|
||||
pub use size_hint::SizeHint;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub use crate::{render, render_escaped, render_noop, render_text};
|
||||
pub use alias_funcs::{render, render_escaped, render_text};
|
||||
|
|
|
@ -9,10 +9,12 @@ use std::path::{Path, PathBuf};
|
|||
use std::rc::Rc;
|
||||
use std::sync::{Arc, MutexGuard, RwLockReadGuard, RwLockWriteGuard};
|
||||
|
||||
use crate::runtime::SizeHint;
|
||||
|
||||
use super::buffer::Buffer;
|
||||
use super::escape;
|
||||
|
||||
/// types which can be rendered inside buffer block (`<%= %>`)
|
||||
/// 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.
|
||||
|
@ -52,6 +54,98 @@ pub trait Render {
|
|||
}
|
||||
}
|
||||
|
||||
/// types which can be rendered inside buffer block (`<%= %>`)
|
||||
///
|
||||
/// See [`Render`] for more information.
|
||||
pub trait RenderOnce: Sized {
|
||||
/// render to `Buffer` without escaping
|
||||
///
|
||||
/// Render the template and append the result to `buf`.
|
||||
///
|
||||
/// This method never returns `Err`, unless you explicitly return RenderError
|
||||
/// inside templates
|
||||
///
|
||||
/// ```should_fail
|
||||
/// use sailfish::{Buffer, RenderOnce};
|
||||
///
|
||||
/// # pub struct HelloTemplate {
|
||||
/// # messages: Vec<String>,
|
||||
/// # }
|
||||
/// #
|
||||
/// # impl RenderOnce for HelloTemplate {
|
||||
/// # fn render_once(self, buf: &mut Buffer) -> Result<(), sailfish::RenderError> {
|
||||
/// # unimplemented!()
|
||||
/// # }
|
||||
/// # }
|
||||
/// #
|
||||
/// let tpl = HelloTemplate {
|
||||
/// messages: vec!["foo".to_string()]
|
||||
/// };
|
||||
///
|
||||
/// // custom pre-allocation
|
||||
/// let mut buffer = Buffer::with_capacity(100);
|
||||
/// tpl.render_once(&mut buffer).unwrap();
|
||||
/// ```
|
||||
fn render_once(self, b: &mut Buffer) -> Result<(), RenderError>;
|
||||
|
||||
/// render to `Buffer` with HTML escaping
|
||||
#[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(())
|
||||
}
|
||||
|
||||
/// Render the template and return the rendering result as `RenderResult`
|
||||
///
|
||||
/// This method never returns `Err`, unless you explicitly return RenderError
|
||||
/// inside templates
|
||||
///
|
||||
/// When you use `render_once_to_string` method, total rendered size will be cached,
|
||||
/// and at the next time, buffer will be pre-allocated based on the cached length.
|
||||
///
|
||||
/// If you don't want this behaviour, you can use `render_once` method instead.
|
||||
#[inline]
|
||||
fn render_once_to_string(self) -> RenderResult {
|
||||
static SIZE_HINT: SizeHint = SizeHint::new();
|
||||
|
||||
let mut buf = Buffer::with_capacity(SIZE_HINT.get());
|
||||
self.render_once(&mut buf)?;
|
||||
SIZE_HINT.update(buf.len());
|
||||
|
||||
Ok(buf.into_string())
|
||||
}
|
||||
}
|
||||
|
||||
// impl<'a, T: ?Sized> Render for &'a T
|
||||
// where
|
||||
// T: Render,
|
||||
// {
|
||||
// fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
// T::render(self, b)
|
||||
// }
|
||||
|
||||
// fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
// T::render_escaped(self, b)
|
||||
// }
|
||||
// }
|
||||
|
||||
impl<T> RenderOnce for T
|
||||
where
|
||||
T: Render,
|
||||
{
|
||||
#[inline]
|
||||
fn render_once(self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
self.render(b)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn render_once_escaped(self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
self.render_escaped(b)
|
||||
}
|
||||
}
|
||||
|
||||
// /// Autoref-based stable specialization
|
||||
// ///
|
||||
// /// Explanation can be found [here](https://github.com/dtolnay/case-studies/blob/master/autoref-specialization/README.md)
|
||||
|
@ -78,13 +172,13 @@ pub trait Render {
|
|||
impl Render for String {
|
||||
#[inline]
|
||||
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
b.push_str(&**self);
|
||||
b.push_str(self);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
escape::escape_to_buf(&**self, b);
|
||||
escape::escape_to_buf(self, b);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -128,13 +222,13 @@ impl Render for PathBuf {
|
|||
#[inline]
|
||||
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
// TODO: speed up on Windows using OsStrExt
|
||||
b.push_str(&*self.to_string_lossy());
|
||||
b.push_str(self.to_str().ok_or(RenderError::Fmt(fmt::Error))?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
escape::escape_to_buf(&*self.to_string_lossy(), b);
|
||||
escape::escape_to_buf(self.to_str().ok_or(RenderError::Fmt(fmt::Error))?, b);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -143,13 +237,13 @@ impl Render for Path {
|
|||
#[inline]
|
||||
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
// TODO: speed up on Windows using OsStrExt
|
||||
b.push_str(&*self.to_string_lossy());
|
||||
b.push_str(self.to_str().ok_or(RenderError::Fmt(fmt::Error))?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
escape::escape_to_buf(&*self.to_string_lossy(), b);
|
||||
escape::escape_to_buf(self.to_str().ok_or(RenderError::Fmt(fmt::Error))?, b);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -288,10 +382,10 @@ impl Render for f64 {
|
|||
macro_rules! render_deref {
|
||||
(
|
||||
$(#[doc = $doc:tt])*
|
||||
[$($bounds:tt)+] $($desc:tt)+
|
||||
$(default[$($default:tt)+])? [$($generics:tt)+] [$($bounds:tt)*] $desc:ty $(, [$($deref:tt)+])?
|
||||
) => {
|
||||
$(#[doc = $doc])*
|
||||
impl <$($bounds)+> Render for $($desc)+ {
|
||||
$($($default)+)? impl <$($generics)+> Render for $desc where $($bounds)* {
|
||||
#[inline]
|
||||
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
(**self).render(b)
|
||||
|
@ -305,17 +399,20 @@ macro_rules! render_deref {
|
|||
};
|
||||
}
|
||||
|
||||
render_deref!(['a, T: Render + ?Sized] &'a T);
|
||||
render_deref!(['a, T: Render + ?Sized] &'a mut T);
|
||||
render_deref!([T: Render + ?Sized] Box<T>);
|
||||
render_deref!([T: Render + ?Sized] Rc<T>);
|
||||
render_deref!([T: Render + ?Sized] Arc<T>);
|
||||
render_deref!(['a, T: Render + ToOwned + ?Sized] Cow<'a, T>);
|
||||
render_deref!(['a, T: Render + ?Sized] Ref<'a, T>);
|
||||
render_deref!(['a, T: Render + ?Sized] RefMut<'a, T>);
|
||||
render_deref!(['a, T: Render + ?Sized] MutexGuard<'a, T>);
|
||||
render_deref!(['a, T: Render + ?Sized] RwLockReadGuard<'a, T>);
|
||||
render_deref!(['a, T: Render + ?Sized] RwLockWriteGuard<'a, T>);
|
||||
// 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] [] Box<T>);
|
||||
render_deref!([T: Render + ?Sized] [] Rc<T>);
|
||||
render_deref!([T: Render + ?Sized] [] Arc<T>);
|
||||
render_deref!(['a, T: Render + ToOwned + ?Sized] [] Cow<'a, T>);
|
||||
render_deref!(['a, T: Render + ?Sized] [] Ref<'a, T>, [*]);
|
||||
render_deref!(['a, T: Render + ?Sized] [] RefMut<'a, T>, [*]);
|
||||
render_deref!(['a, T: Render + ?Sized] [] MutexGuard<'a, T>, [*]);
|
||||
render_deref!(['a, T: Render + ?Sized] [] RwLockReadGuard<'a, T>, [*]);
|
||||
render_deref!(['a, T: Render + ?Sized] [] RwLockWriteGuard<'a, T>, [*]);
|
||||
|
||||
macro_rules! render_nonzero {
|
||||
($($type:ty,)*) => {
|
||||
|
@ -411,7 +508,7 @@ impl From<fmt::Error> for RenderError {
|
|||
}
|
||||
}
|
||||
|
||||
/// Result type returned from `TemplateOnce::render_once` method
|
||||
/// Result type returned from [`RenderOnce::render_once_to_string`] method
|
||||
pub type RenderResult = Result<String, RenderError>;
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -422,33 +519,33 @@ mod tests {
|
|||
#[test]
|
||||
fn receiver_coercion() {
|
||||
let mut b = Buffer::new();
|
||||
Render::render(&1, &mut b).unwrap();
|
||||
Render::render(&&1, &mut b).unwrap();
|
||||
Render::render(&&&1, &mut b).unwrap();
|
||||
Render::render(&&&&1, &mut b).unwrap();
|
||||
RenderOnce::render_once(&1, &mut b).unwrap();
|
||||
RenderOnce::render_once(&&1, &mut b).unwrap();
|
||||
RenderOnce::render_once(&&&1, &mut b).unwrap();
|
||||
RenderOnce::render_once(&&&&1, &mut b).unwrap();
|
||||
assert_eq!(b.as_str(), "1111");
|
||||
b.clear();
|
||||
|
||||
Render::render(&true, &mut b).unwrap();
|
||||
Render::render(&&false, &mut b).unwrap();
|
||||
Render::render_escaped(&&&true, &mut b).unwrap();
|
||||
Render::render_escaped(&&&&false, &mut b).unwrap();
|
||||
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();
|
||||
assert_eq!(b.as_str(), "truefalsetruefalse");
|
||||
b.clear();
|
||||
|
||||
let s = "apple";
|
||||
Render::render_escaped(&s, &mut b).unwrap();
|
||||
Render::render_escaped(&s, &mut b).unwrap();
|
||||
Render::render_escaped(&&s, &mut b).unwrap();
|
||||
Render::render_escaped(&&&s, &mut b).unwrap();
|
||||
Render::render_escaped(&&&&s, &mut b).unwrap();
|
||||
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();
|
||||
assert_eq!(b.as_str(), "appleappleappleappleapple");
|
||||
b.clear();
|
||||
|
||||
Render::render_escaped(&'c', &mut b).unwrap();
|
||||
Render::render_escaped(&&'<', &mut b).unwrap();
|
||||
Render::render_escaped(&&&'&', &mut b).unwrap();
|
||||
Render::render_escaped(&&&&' ', &mut b).unwrap();
|
||||
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();
|
||||
assert_eq!(b.as_str(), "c<& ");
|
||||
b.clear();
|
||||
}
|
||||
|
@ -460,7 +557,7 @@ 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(&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();
|
||||
|
@ -473,17 +570,17 @@ mod tests {
|
|||
fn float() {
|
||||
let mut b = Buffer::new();
|
||||
|
||||
Render::render_escaped(&0.0f64, &mut b).unwrap();
|
||||
Render::render_escaped(&std::f64::INFINITY, &mut b).unwrap();
|
||||
Render::render_escaped(&std::f64::NEG_INFINITY, &mut b).unwrap();
|
||||
Render::render_escaped(&std::f64::NAN, &mut b).unwrap();
|
||||
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();
|
||||
assert_eq!(b.as_str(), "0.0inf-infNaN");
|
||||
b.clear();
|
||||
|
||||
Render::render_escaped(&0.0f32, &mut b).unwrap();
|
||||
Render::render_escaped(&std::f32::INFINITY, &mut b).unwrap();
|
||||
Render::render_escaped(&std::f32::NEG_INFINITY, &mut b).unwrap();
|
||||
Render::render_escaped(&std::f32::NAN, &mut b).unwrap();
|
||||
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();
|
||||
assert_eq!(b.as_str(), "0.0inf-infNaN");
|
||||
}
|
||||
|
||||
|
@ -491,22 +588,22 @@ mod tests {
|
|||
fn test_char() {
|
||||
let mut b = Buffer::new();
|
||||
|
||||
let funcs: Vec<fn(&char, &mut Buffer) -> Result<(), RenderError>> =
|
||||
vec![Render::render, Render::render_escaped];
|
||||
let funcs: Vec<fn(char, &mut Buffer) -> Result<(), RenderError>> =
|
||||
vec![RenderOnce::render_once, RenderOnce::render_once_escaped];
|
||||
|
||||
for func in funcs {
|
||||
func(&'a', &mut b).unwrap();
|
||||
func(&'b', &mut b).unwrap();
|
||||
func(&'c', &mut b).unwrap();
|
||||
func(&'d', &mut b).unwrap();
|
||||
func('a', &mut b).unwrap();
|
||||
func('b', &mut b).unwrap();
|
||||
func('c', &mut b).unwrap();
|
||||
func('d', &mut b).unwrap();
|
||||
|
||||
assert_eq!(b.as_str(), "abcd");
|
||||
b.clear();
|
||||
|
||||
func(&'あ', &mut b).unwrap();
|
||||
func(&'い', &mut b).unwrap();
|
||||
func(&'う', &mut b).unwrap();
|
||||
func(&'え', &mut b).unwrap();
|
||||
func('あ', &mut b).unwrap();
|
||||
func('い', &mut b).unwrap();
|
||||
func('う', &mut b).unwrap();
|
||||
func('え', &mut b).unwrap();
|
||||
|
||||
assert_eq!(b.as_str(), "あいうえ");
|
||||
b.clear();
|
||||
|
@ -516,8 +613,8 @@ mod tests {
|
|||
#[test]
|
||||
fn test_nonzero() {
|
||||
let mut b = Buffer::with_capacity(2);
|
||||
Render::render(&NonZeroU8::new(10).unwrap(), &mut b).unwrap();
|
||||
Render::render_escaped(&NonZeroI16::new(-20).unwrap(), &mut b).unwrap();
|
||||
RenderOnce::render_once(NonZeroU8::new(10).unwrap(), &mut b).unwrap();
|
||||
RenderOnce::render_once_escaped(NonZeroI16::new(-20).unwrap(), &mut b).unwrap();
|
||||
assert_eq!(b.as_str(), "10-20");
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue