Compare commits

...

12 Commits

Author SHA1 Message Date
Michael Pfaff 4e2232f2de Unify Render* and Template* traits 2024-03-11 17:33:11 -04:00
Michael Pfaff e5a471d9ca Improve API 2024-03-11 16:57:52 -04:00
Vince Pike d27b415d7a
Merge pull request #144 from rust-sailfish/dependabot/cargo/examples/mio-0.8.11
build(deps): bump mio from 0.8.6 to 0.8.11 in /examples
2024-03-05 10:02:24 -05:00
dependabot[bot] 9133849820
build(deps): bump mio from 0.8.6 to 0.8.11 in /examples
Bumps [mio](https://github.com/tokio-rs/mio) from 0.8.6 to 0.8.11.
- [Release notes](https://github.com/tokio-rs/mio/releases)
- [Changelog](https://github.com/tokio-rs/mio/blob/master/CHANGELOG.md)
- [Commits](https://github.com/tokio-rs/mio/compare/v0.8.6...v0.8.11)

---
updated-dependencies:
- dependency-name: mio
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-04 21:45:23 +00:00
Vince Pike 7a0c492434
Merge pull request #142 from rust-sailfish/dependabot/cargo/examples/h2-0.3.24
build(deps): bump h2 from 0.3.17 to 0.3.24 in /examples
2024-01-24 13:01:07 -05:00
dependabot[bot] b98c2a4568
build(deps): bump h2 from 0.3.17 to 0.3.24 in /examples
Bumps [h2](https://github.com/hyperium/h2) from 0.3.17 to 0.3.24.
- [Release notes](https://github.com/hyperium/h2/releases)
- [Changelog](https://github.com/hyperium/h2/blob/v0.3.24/CHANGELOG.md)
- [Commits](https://github.com/hyperium/h2/compare/v0.3.17...v0.3.24)

---
updated-dependencies:
- dependency-name: h2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-19 16:09:35 +00:00
vthg2themax 2e39299483 bump version in preparation for upload to crates.io 2023-10-22 05:01:13 -04:00
Vince Pike ef79697c6e
Merge pull request #138 from levkk/levkk-fix-filename-conflict
Fix filename conflict
2023-10-22 04:54:44 -04:00
Lev af91dc5243 remove space 2023-10-21 14:10:31 -07:00
Lev 4c554d1a57 Fix filename conflict 2023-10-21 14:09:00 -07:00
Vince Pike 3bb7b3e042
Merge pull request #136 from miranbastaja/toml-0.8
dep: update toml to 0.8.2
2023-10-17 16:20:11 -04:00
Miran Bastaja 28972209cd
dep: update toml to 0.8.2 2023-10-17 16:55:47 +02:00
49 changed files with 719 additions and 674 deletions

116
Cargo.lock generated
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

58
examples/Cargo.lock generated
View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
use sailfish::TemplateOnce;
use sailfish::RenderOnce;
#[derive(TemplateOnce)]
#[derive(RenderOnce)]
#[template(path = "include.stpl")]
struct Include {
title: String,

View File

@ -1,6 +1,6 @@
use sailfish::TemplateOnce;
use sailfish::RenderOnce;
#[derive(TemplateOnce)]
#[derive(RenderOnce)]
#[template(path = "simple.stpl")]
struct Simple {
messages: Vec<String>,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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>"), "&lt;h1&gt;title&lt;/h1&gt;");
assert_render_escaped(&lower("<<&\"\">>"), "&lt;&lt;&amp;&quot;&quot;&gt;&gt;");
assert_render_escaped(lower("<h1>TITLE</h1>"), "&lt;h1&gt;title&lt;/h1&gt;");
assert_render_escaped(lower("<<&\"\">>"), "&lt;&lt;&amp;&quot;&quot;&gt;&gt;");
// non-ascii
assert_render(&lower("aBc"), "abc");
assert_render(&lower("ὈΔΥΣΣΕΎΣ"), "ὀδυσσεύς");
assert_render(lower("aBc"), "abc");
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("aBc"), "ABC");
assert_render(&upper("tschüß"), "TSCHÜSS");
assert_render(upper("aBc"), "ABC");
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> "), "&lt;html&gt;");
assert_render_escaped(&lower("<<&\"\">>"), "&lt;&lt;&amp;&quot;&quot;&gt;&gt;");
assert_render_escaped(trim(" <html> "), "&lt;html&gt;");
assert_render_escaped(lower("<<&\"\">>"), "&lt;&lt;&amp;&quot;&quot;&gt;&gt;");
// 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&lt;br&...");
assert_render_escaped(&truncate("foo<br>bar", 20), "foo&lt;br&gt;bar");
assert_render_escaped(truncate("foo<br>bar", 10), "foo&lt;br&...");
assert_render_escaped(truncate("foo<br>bar", 20), "foo&lt;br&gt;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"), "&quot;Pokémon&quot;");
assert_render_escaped(json(123_i32), "123");
assert_render_escaped(json("Pokémon"), "&quot;Pokémon&quot;");
}
#[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...");
}
}

View File

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

View File

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

View File

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