Merge branch 'master' into stable
This commit is contained in:
commit
c226f4bb7f
|
@ -0,0 +1,22 @@
|
|||
# Number of days of inactivity before an issue becomes stale
|
||||
daysUntilStale: 90
|
||||
# Number of days of inactivity before a stale issue is closed
|
||||
daysUntilClose: 7
|
||||
# Issues with these labels will never be considered stale
|
||||
exemptLabels:
|
||||
- 'help wanted'
|
||||
- 'Status: Blocked'
|
||||
- 'Status: In Progress'
|
||||
- 'Status: PR Welcome'
|
||||
- 'Status: Proposal'
|
||||
- 'Status: Review Needed'
|
||||
- 'Type: Bug'
|
||||
# Label to use when marking an issue as stale
|
||||
staleLabel: wontfix
|
||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity. It will be closed if no further activity occurs. Thank you
|
||||
for your contributions.
|
||||
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||
closeComment: false
|
|
@ -0,0 +1,29 @@
|
|||
name: Badges
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- '**/Cargo.toml'
|
||||
|
||||
jobs:
|
||||
update-badges:
|
||||
name: Update Badges
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.repository_owner == 'Kogia-sima' }}
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v2
|
||||
- name: Get the Numbers
|
||||
run: |
|
||||
echo "dep_counts=$(./scripts/count-dependencies.sh)" >> $GITHUB_ENV
|
||||
- name: Create Dependency-Count-Badge
|
||||
uses: schneegans/dynamic-badges-action@v1.0.0
|
||||
with:
|
||||
auth: ${{ secrets.GIST_SECRET }}
|
||||
gistID: a2128afe12bf05d85a0d68346236a4f5
|
||||
filename: sailfish-dep-counts.json
|
||||
label: Dependencies
|
||||
message: ${{ env.dep_counts }}
|
||||
color: blueviolet
|
|
@ -0,0 +1,41 @@
|
|||
name: Coverage
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
coverage:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
target: x86_64-unknown-linux-gnu
|
||||
toolchain: nightly
|
||||
override: true
|
||||
components: rustfmt
|
||||
- name: Install grcov
|
||||
run: curl -L https://github.com/mozilla/grcov/releases/download/v0.6.1/grcov-linux-x86_64.tar.bz2 | tar jxf -
|
||||
- name: Install rust-covfix
|
||||
run: |
|
||||
curl -L https://github.com/Kogia-sima/rust-covfix/releases/download/v0.2.2/rust-covfix-linux-x86_64.tar.xz |tar Jxf -
|
||||
mv rust-covfix-linux-x86_64/rust-covfix ./
|
||||
- name: Test all crates
|
||||
env:
|
||||
CARGO_INCREMENTAL: 0
|
||||
RUSTFLAGS: -Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -C panic=abort
|
||||
RUSTDOCFLAGS: -C panic=abort
|
||||
run: |
|
||||
cargo build --all-features --workspace
|
||||
cargo test --all-features --workspace
|
||||
- name: collect coverages
|
||||
run: |
|
||||
zip -0 ccov.zip `find . \( -name "sailfish*.gc*" -o -name "integration_tests*.gc*" \) -print`
|
||||
./grcov ccov.zip --llvm --branch -t lcov -o lcov.info --ignore "/*" --ignore "sailfish-tests/*"
|
||||
- name: fix coverages
|
||||
run: ./rust-covfix -o lcov.info lcov.info
|
||||
- name: upload coverage
|
||||
uses: codecov/codecov-action@v1
|
||||
with:
|
||||
file: ./lcov.info
|
|
@ -24,9 +24,6 @@ jobs:
|
|||
- os: ubuntu-latest
|
||||
target: x86_64-unknown-linux-gnu
|
||||
toolchain: 1.42.0 # MSRV
|
||||
- os: ubuntu-latest
|
||||
target: x86_64-unknown-linux-gnu
|
||||
toolchain: nightly
|
||||
- os: ubuntu-latest
|
||||
deps: sudo apt update ; sudo apt install gcc-multilib
|
||||
target: i686-unknown-linux-gnu
|
||||
|
|
40
CHANGELOG.md
40
CHANGELOG.md
|
@ -1,12 +1,48 @@
|
|||
<a name="v0.3.0"></a>
|
||||
## [v0.3.0](https://github.com/Kogia-sima/sailfish/compare/v0.2.2...v0.3.0) (2020-12-20)
|
||||
|
||||
## Breaking changes
|
||||
|
||||
* No longer requires `extern crate sailfish_macros` (which raise compilation warnings with v0.3.0)
|
||||
* Remove `TemplaceOnce::render_to_string` method (already deprecated in v0.2.1)
|
||||
* Forbid implementing `TemplateOnce` trait by yourself
|
||||
* Change `RenderError` into enum
|
||||
* Update error format in `sailfish-compiler`
|
||||
|
||||
## New features
|
||||
|
||||
* New filters: `json`, `truncate`
|
||||
* Impl `Send`/`Sync` for `Buffer`
|
||||
|
||||
## Fix
|
||||
|
||||
* Fix rendering issue on continue/break statements
|
||||
* Do not panic when buffer size decreased
|
||||
* Remove unsafe usage of `ptr::add()`
|
||||
* Properly handle slices with size greater than `isize::MAX`
|
||||
|
||||
<a name="v0.2.3"></a>
|
||||
## [v0.2.3](https://github.com/Kogia-sima/sailfish/compare/v0.2.2...v0.2.3) (2020-11-29)
|
||||
|
||||
## Fix
|
||||
|
||||
* Use `std::result::Result` in derive macro to allow custom Result types (#34)
|
||||
|
||||
<a name="v0.2.2"></a>
|
||||
## [v0.2.2](https://github.com/Kogia-sima/sailfish/compare/v0.2.1...v0.2.2) (2020-11-11)
|
||||
|
||||
## Fix
|
||||
|
||||
* Update proc-macro2 version (#32)
|
||||
|
||||
<a name="v0.2.1"></a>
|
||||
## [v0.2.1](https://github.com/Kogia-sima/sailfish/compare/v0.2.0...v0.2.1) (2020-07-17)
|
||||
## [v0.2.1](https://github.com/Kogia-sima/sailfish/compare/v0.2.0...v0.2.1) (2020-08-04)
|
||||
|
||||
### Features
|
||||
|
||||
* Add trim filter
|
||||
|
||||
### Bug fix
|
||||
### Fix
|
||||
|
||||
* Fix incorrect syntax highlighting in vim
|
||||
* Avoid capacity overflow in `Buffer::with_capacity`
|
||||
|
|
|
@ -9,6 +9,18 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "ctor"
|
||||
version = "0.1.14"
|
||||
|
@ -25,6 +37,18 @@ version = "2.0.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
|
||||
|
||||
[[package]]
|
||||
name = "filetime"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d34cfa13a63ae058bfa601fe9e313bbdb3746427c1459185464ce0fcf62e1e8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.0"
|
||||
|
@ -42,7 +66,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "integration-tests"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
dependencies = [
|
||||
"pretty_assertions",
|
||||
"sailfish",
|
||||
|
@ -70,6 +94,12 @@ version = "1.4.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.82"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89203f3fba0a3795506acaad8ebce3c80c0af93f994d5a1d7a0b1eeb23271929"
|
||||
|
||||
[[package]]
|
||||
name = "linked-hash-map"
|
||||
version = "0.5.3"
|
||||
|
@ -121,6 +151,15 @@ dependencies = [
|
|||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05ec8ca9416c5ea37062b502703cd7fcb207736bc294f6e0cf367ac6fc234570"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.5"
|
||||
|
@ -129,7 +168,7 @@ checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
|
|||
|
||||
[[package]]
|
||||
name = "sailfish"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
dependencies = [
|
||||
"itoap",
|
||||
"ryu",
|
||||
|
@ -141,8 +180,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "sailfish-compiler"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
dependencies = [
|
||||
"filetime",
|
||||
"home",
|
||||
"memchr",
|
||||
"pretty_assertions",
|
||||
|
@ -154,7 +194,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "sailfish-macros"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"sailfish-compiler",
|
||||
|
|
21
README.md
21
README.md
|
@ -6,6 +6,9 @@ Simple, small, and extremely fast template engine for Rust
|
|||
|
||||
[![Tests](https://github.com/Kogia-sima/sailfish/workflows/Tests/badge.svg)](https://github.com/Kogia-sima/sailfish/actions?query=workflow%3ATests)
|
||||
[![Version](https://img.shields.io/crates/v/sailfish)](https://crates.io/crates/sailfish)
|
||||
![Dependency counts](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/Kogia-sima/a2128afe12bf05d85a0d68346236a4f5/raw/sailfish-dep-counts.json)
|
||||
[![dependency status](https://deps.rs/repo/github/Kogia-sima/sailfish/status.svg)](https://deps.rs/repo/github/Kogia-sima/sailfish)
|
||||
[![Rust 1.42](https://img.shields.io/badge/rust-1.42+-lightgray.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.42.html)
|
||||
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/Kogia-sima/sailfish/blob/master/LICENSE)
|
||||
|
||||
[User Guide](https://sailfish.netlify.app/en/) | [API Docs](https://docs.rs/sailfish) | [Examples](./examples)
|
||||
|
@ -15,12 +18,12 @@ Simple, small, and extremely fast template engine for Rust
|
|||
## ✨ Features
|
||||
|
||||
- Simple and intuitive syntax inspired by [EJS](https://ejs.co/)
|
||||
- Builtin filters
|
||||
- Relatively small number of dependencies (<15 crates in total)
|
||||
- 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))
|
||||
- Automatically re-compile sources when template file is updated.
|
||||
- Works on Rust 1.42 or later
|
||||
|
||||
## 🐟 Example
|
||||
|
@ -29,8 +32,7 @@ Dependencies:
|
|||
|
||||
```toml
|
||||
[dependencies]
|
||||
sailfish = "0.3.0"
|
||||
sailfish-macros = "0.3.0"
|
||||
sailfish = "0.3.1"
|
||||
```
|
||||
|
||||
Template file (templates/hello.stpl):
|
||||
|
@ -48,9 +50,6 @@ Template file (templates/hello.stpl):
|
|||
Code:
|
||||
|
||||
```rust
|
||||
#[macro_use]
|
||||
extern crate sailfish_macros; // enable derive macro
|
||||
|
||||
use sailfish::TemplateOnce;
|
||||
|
||||
#[derive(TemplateOnce)]
|
||||
|
@ -61,8 +60,8 @@ struct HelloTemplate {
|
|||
|
||||
fn main() {
|
||||
let ctx = HelloTemplate {
|
||||
messages: vec![String::from("foo"), String::from("bar")]
|
||||
}
|
||||
messages: vec![String::from("foo"), String::from("bar")],
|
||||
};
|
||||
println!("{}", ctx.render_once().unwrap());
|
||||
}
|
||||
```
|
||||
|
@ -78,7 +77,7 @@ You can find more examples in [examples](./examples) directory.
|
|||
|
||||
🇯🇵 **Ryohei Machida**
|
||||
|
||||
* Github: [@Kogia-sima](https://github.com/Kogia-sima)
|
||||
* GitHub: [@Kogia-sima](https://github.com/Kogia-sima)
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
Sailfish contains some third-party content.
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
* Some test data come from TechEmpower Framework Benchmarks
|
||||
|
||||
Copyright (c) 2020, TechEmpower, Inc.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name TechEmpower, Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL TECHEMPOWER, INC. BE LIABLE FOR ANY DIRECT,
|
||||
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
||||
OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
||||
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -1 +1 @@
|
|||
benchmark programs were removed in favor of https://github.com/djc/template-benchmarks-rs
|
||||
benchmark programs were removed in favour of https://github.com/djc/template-benchmarks-rs
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
coverage:
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
threshold: 3%
|
||||
branches:
|
||||
- master
|
||||
only_pulls: false
|
||||
|
||||
patch:
|
||||
default:
|
||||
target: auto
|
||||
branches:
|
||||
- master
|
||||
only_pulls: true
|
|
@ -0,0 +1,41 @@
|
|||
.md-typeset__table table {
|
||||
font-size: .75rem !important;
|
||||
}
|
||||
|
||||
.md-typeset__table table tr td:first-child {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.md-typeset code {
|
||||
background-color: rgb(240, 240, 240);
|
||||
font-size: .95em;
|
||||
}
|
||||
|
||||
.md-typeset pre > code {
|
||||
background-color: rgb(245, 245, 245);
|
||||
font-size: .90em;
|
||||
}
|
||||
|
||||
.md-typeset .admonition {
|
||||
font-size: .70rem;
|
||||
}
|
||||
|
||||
body {
|
||||
-webkit-font-smoothing: antialiased !important;
|
||||
-moz-font-smoothing: antialiased !important;
|
||||
}
|
||||
|
||||
.md-content h2 {
|
||||
border-bottom-color: rgb(234, 236, 239);
|
||||
border-bottom-style: solid;
|
||||
border-bottom-width: 1px;
|
||||
padding-bottom: .3rem;
|
||||
}
|
||||
|
||||
.md-content a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.highlight code .cp {
|
||||
color: #a83;
|
||||
}
|
|
@ -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`.
|
||||
|
||||
```ejs
|
||||
``` rhtml
|
||||
<html>
|
||||
<body>
|
||||
<% for msg in &messages { %>
|
||||
|
@ -26,16 +26,13 @@ templates/
|
|||
|
||||
## Render the template
|
||||
|
||||
Import the sailfish crates:
|
||||
<ol><li>Import the sailfish crates:</li></ol>
|
||||
|
||||
```rust
|
||||
#[macro_use]
|
||||
extern crate sailfish_macros; // enable derive macros
|
||||
|
||||
use sailfish::TemplateOnce; // import `TemplateOnce` trait
|
||||
use sailfish::TemplateOnce;
|
||||
```
|
||||
|
||||
Define the template struct to be rendered:
|
||||
<ol start="2"><li>Define the template struct to be rendered:</li></ol>
|
||||
|
||||
```rust
|
||||
#[derive(TemplateOnce)] // automatically implement `TemplateOnce` trait
|
||||
|
@ -46,7 +43,7 @@ struct HelloTemplate {
|
|||
}
|
||||
```
|
||||
|
||||
And render the data with `render_once()` method.
|
||||
<ol start="3"><li>Render the data with <code>render_once()</code> method.</li></ol>
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
|
|
|
@ -4,27 +4,23 @@ Sailfish is a simple, small, and extremely fast template engine for Rust. This d
|
|||
|
||||
This documentation mainly focuses on concepts of the library, general usage, and template syntax. If you've read this documentation and need more specific information, you might want to read the [sailfish API docs](https://docs.rs/sailfish).
|
||||
|
||||
Currently the documentation is uncompleted. If you want to improve our documentation, feel free to create a [Pull Request](https://github.com/Kogia-sima/sailfish/pulls) on the repository. I'll be happy if someone contributes to writing documents (English is not my first language and creating a documentation is a hard task for me).
|
||||
|
||||
## Why Sailfish ?
|
||||
|
||||
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 definision, closure, macro invocation, etc.)
|
||||
- Relatively small number of dependencies (<15 crates in total)
|
||||
- Extremely fast (See [benchmarks](http://github.com/Kogia-sima/sailfish/blob/master/benches))
|
||||
- Better error message
|
||||
- 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/Kogia-sima/sailfish/blob/master/syntax/vscode), [vim](http://github.com/Kogia-sima/sailfish/blob/master/syntax/vim))
|
||||
- Automatically re-compile sources when template file is updated.
|
||||
|
||||
## 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/Kogia-sima/sailfish/issues?q=is%3Aissue+is%3Aopen+label%3A%22Type%3A+RFC%22)s in my repository. These RFCs include:
|
||||
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/Kogia-sima/sailfish/issues?q=is%3Aissue+is%3Aopen+label%3A%22Type%3A+RFC%22)s in my repository. These RFC include:
|
||||
|
||||
- Dynamic Template Loading
|
||||
- Filter
|
||||
- `Template` trait (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!
|
||||
|
||||
|
|
|
@ -2,15 +2,17 @@
|
|||
|
||||
In order to use sailfish templates, you have add two dependencies in your `Cargo.toml`.
|
||||
|
||||
```toml
|
||||
``` toml
|
||||
[dependencies]
|
||||
sailfish = "0.3.0"
|
||||
sailfish-macros = "0.3.0"
|
||||
sailfish = "0.3.1"
|
||||
```
|
||||
|
||||
`sailfish` crate contains runtime for rendering contents, and `sailfish-macros` serves you derive macros to compile and import the template files.
|
||||
## Feature Flags
|
||||
|
||||
These crates are separated so that Rust compiler can compile them independently. This separation makes your compilation faster!
|
||||
Sailfish accepts the following feature flags
|
||||
|
||||
!!! Warning
|
||||
Make sure that the `sailfish-macros` version is larger than `sailfish`, otherwise the compilation may fail.
|
||||
|Feature|Description|
|
||||
|--|--|
|
||||
|derive|enable derive macros (enabled by default)|
|
||||
|json|enable `json` filter|
|
||||
|perf-inline|Add more `#[inline]` attributes. This may improve rendering performance, but generates a bit larger binary (enabled by default)|
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
You can control the rendering behaviour via `template` attribute.
|
||||
|
||||
```rust
|
||||
``` rust
|
||||
#[derive(TemplateOnce)]
|
||||
#[template(path = "template.stpl", escape = false)]
|
||||
struct TemplateStruct {
|
||||
|
@ -21,7 +21,7 @@ struct TemplateStruct {
|
|||
|
||||
You can split the options into multiple `template` attributes.
|
||||
|
||||
```rust
|
||||
``` rust
|
||||
#[derive(TemplateOnce)]
|
||||
#[template(path = "template.stpl")]
|
||||
#[template(delimiter = '?')]
|
||||
|
@ -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 YAML 1.2 format. Here is the default configuration.
|
||||
|
||||
```
|
||||
``` yaml
|
||||
template_dir: "templates"
|
||||
escape: true
|
||||
delimiter: "%"
|
||||
|
|
|
@ -4,31 +4,33 @@ Filters are used to format the rendered contents.
|
|||
|
||||
Example:
|
||||
|
||||
```ejs
|
||||
message: <%= "foo\nbar" | dbg %>
|
||||
```
|
||||
=== "Template"
|
||||
|
||||
Output:
|
||||
``` rhtml
|
||||
message: <%= "foo\nbar" | dbg %>
|
||||
```
|
||||
|
||||
```html
|
||||
message: "foo\nbar"
|
||||
```
|
||||
=== "Result"
|
||||
|
||||
``` html
|
||||
message: "foo\nbar"
|
||||
```
|
||||
|
||||
!!! Note
|
||||
Since `dbg` filter accepts '<T: std::fmt::Debug>' types, that type isn't required to implement [`Render`](https://docs.rs/sailfish/latest/sailfish/runtime/trait.Render.html) trait. That means you can pass the type which doen't implement `Render` trait.
|
||||
Since `dbg` filter accepts `<T: std::fmt::Debug>` types, that type isn't required to implement [`Render`](https://docs.rs/sailfish/latest/sailfish/runtime/trait.Render.html) trait. That means you can pass the type which doesn't implement `Render` trait.
|
||||
|
||||
|
||||
## Syntax
|
||||
|
||||
- Apply filter and HTML escaping
|
||||
|
||||
```ejs
|
||||
``` rhtml
|
||||
<%= expression | filter %>
|
||||
```
|
||||
|
||||
- Apply filter only
|
||||
|
||||
```ejs
|
||||
``` rhtml
|
||||
<%- expression | filter %>
|
||||
```
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ Consider the following example.
|
|||
|
||||
- `templates/header.stpl`
|
||||
|
||||
```html
|
||||
``` html
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
|
@ -15,7 +15,7 @@ Consider the following example.
|
|||
|
||||
- `templates/index.stpl`
|
||||
|
||||
```html
|
||||
``` rhtml
|
||||
<html>
|
||||
<head>
|
||||
<% include!("./header.stpl"); %>
|
||||
|
@ -28,7 +28,7 @@ Consider the following example.
|
|||
|
||||
Then you can see the `header.stpl` is embedded in the output.
|
||||
|
||||
```html
|
||||
``` html
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
## Condition
|
||||
|
||||
```ejs
|
||||
``` rhtml
|
||||
<% if messages.is_empty() { %>
|
||||
<div>No messages</div>
|
||||
<% } %>
|
||||
|
@ -18,7 +18,7 @@
|
|||
|
||||
## loop
|
||||
|
||||
```ejs
|
||||
``` rhtml
|
||||
<% for (i, msg) in messages.iter().enumerate() { %>
|
||||
<div><%= i %>: <%= msg %></div>
|
||||
<% } %>
|
||||
|
@ -26,21 +26,19 @@
|
|||
|
||||
## Includes
|
||||
|
||||
```ejs
|
||||
``` rhtml
|
||||
<% include!("path/to/template"); %>
|
||||
```
|
||||
|
||||
Unlike EJS, you cannot omit the file extension.
|
||||
|
||||
## Filters
|
||||
|
||||
```ejs
|
||||
``` rhtml
|
||||
<%= message | upper %>
|
||||
```
|
||||
|
||||
```ejs
|
||||
``` rhtml
|
||||
{
|
||||
"id": <%= id %>
|
||||
"comment": <%- comment | dbg %>
|
||||
"comment": <%- comment | json %>
|
||||
}
|
||||
```
|
||||
|
|
|
@ -4,30 +4,56 @@
|
|||
|
||||
You can write Rust statement inside `<% %>` tag.
|
||||
|
||||
```ejs
|
||||
<% let mut total = 0; %>
|
||||
<% for elem in arr.iter().filter(|e| e.is_valid()) { %>
|
||||
<% total += elem.value() as u64; %>
|
||||
<% dbg!(total); %>
|
||||
<% if total > 100 { break; } %>
|
||||
Printed until the total value exceeds 100.
|
||||
<% } %>
|
||||
```
|
||||
=== "Template"
|
||||
|
||||
``` rhtml
|
||||
<%
|
||||
let mut total = 0;
|
||||
for i in 1.. {
|
||||
total += i;
|
||||
if i > 100 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
%>
|
||||
<div>total = <%= total %></div>
|
||||
```
|
||||
|
||||
=== "Result"
|
||||
|
||||
``` html
|
||||
<div>total = 105</div>
|
||||
```
|
||||
|
||||
!!! Note
|
||||
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.
|
||||
|
||||
```text
|
||||
<% /* Tag does not ends at %>! */ %>
|
||||
```
|
||||
=== "Template"
|
||||
|
||||
``` text
|
||||
<% /* Tag does not ends at %>! */ %>
|
||||
```
|
||||
|
||||
=== "Result"
|
||||
|
||||
``` text
|
||||
```
|
||||
|
||||
If you need to simply render `<%` character, you can escape it, or use evaluation block (described below).
|
||||
|
||||
```text
|
||||
<%% is converted into <%= "<%" %> character.
|
||||
```
|
||||
=== "Template"
|
||||
|
||||
``` text
|
||||
<%% is converted into <%- "<%" %> character.
|
||||
```
|
||||
|
||||
=== "Result"
|
||||
|
||||
``` text
|
||||
<% is converted into <% character
|
||||
```
|
||||
|
||||
Although almost all Rust statement is supported, the following statements inside templates may cause a strange compilation error.
|
||||
|
||||
|
@ -41,32 +67,41 @@ Although almost all Rust statement is supported, the following statements inside
|
|||
|
||||
Rust expression inside `<%= %>` tag is evaluated and the result will be rendered.
|
||||
|
||||
```ejs
|
||||
<%# The following code simple renders `3` %>
|
||||
<% let a = 1; %><%= a + 2 %>
|
||||
```
|
||||
=== "Template"
|
||||
|
||||
``` rhtml
|
||||
<% let a = 1; %><%= a + 2 %>
|
||||
```
|
||||
|
||||
=== "Result"
|
||||
|
||||
``` text
|
||||
3
|
||||
```
|
||||
|
||||
If the result contains `&"'<>` characters, sailfish replaces these characters with the equivalent html.
|
||||
|
||||
If you want to render the results without escaping, you can use `<%- %>` tag or [configure sailfish to not escape by default](../options.md). For example,
|
||||
If you want to render the results without escaping, you can use `<%- %>` tag or [configure sailfish to not escape by default](../options.md).
|
||||
|
||||
```ejs
|
||||
<div>
|
||||
<%- "<h1>Hello, World!</h1>" %>
|
||||
</div>
|
||||
```
|
||||
=== "Template"
|
||||
|
||||
This template results in the following output.
|
||||
``` rhtml
|
||||
<div>
|
||||
<%- "<h1>Hello, World!</h1>" %>
|
||||
</div>
|
||||
```
|
||||
|
||||
```ejs
|
||||
<div>
|
||||
<h1>Hello, World!</h1>
|
||||
</div>
|
||||
```
|
||||
=== "Result"
|
||||
|
||||
``` html
|
||||
<div>
|
||||
<h1>Hello, World!</h1>
|
||||
</div>
|
||||
```
|
||||
|
||||
!!! 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.
|
||||
|
||||
```
|
||||
``` rhtml
|
||||
<% let result = %><%= 1 %><% ; %>
|
||||
```
|
||||
|
|
|
@ -24,16 +24,22 @@ theme:
|
|||
font:
|
||||
text: 'Ubuntu'
|
||||
code: 'Ubuntu Mono'
|
||||
features:
|
||||
- 'navigation.expand'
|
||||
|
||||
# Extensions
|
||||
markdown_extensions:
|
||||
- admonition
|
||||
- codehilite
|
||||
- footnotes
|
||||
- 'admonition'
|
||||
- 'footnotes'
|
||||
- 'pymdownx.highlight'
|
||||
- 'pymdownx.tabbed'
|
||||
- 'pymdownx.superfences':
|
||||
custom_fences:
|
||||
- name: mermaid
|
||||
class: mermaid
|
||||
|
||||
# Customization
|
||||
# extra_css:
|
||||
# - 'assets/css/custom.css'
|
||||
extra_css:
|
||||
- 'assets/css/custom.css'
|
||||
|
||||
extra:
|
||||
social:
|
||||
|
|
|
@ -63,7 +63,7 @@ AllocVtable is passed to template function, and then VBuffer is constructed insi
|
|||
|
||||
VBuffer should always use AllocVTable to allocate/reallocate a new memory. That cannot achieve with `std::string::String` struct only. We must re-implement the `RawVec` struct.
|
||||
|
||||
## Rust standard library confliction problem
|
||||
## Rust standard library conflict problem
|
||||
|
||||
Rarely, but not never, dynamically compiled templates may use different version of standard library.
|
||||
|
||||
|
@ -77,7 +77,7 @@ We must ensure that all of the data passed to templates should satisfy the follo
|
|||
|
||||
- completely immutable
|
||||
- does not allocate/deallocate memory
|
||||
- can be serialized to/deserialized from byte array (All data is serealized to byte array, and then decoded inside templates)
|
||||
- can be serialized to/deserialized from byte array (All data is serialized to byte array, and then decoded inside templates)
|
||||
- can be defined inside `#![no_std]` crate
|
||||
|
||||
Sailfish provide `TemplateData` trait which satisfies the above restrictions.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "sailfish-examples"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
authors = ["Ryohei Machida <orcinus4627@gmail.com>"]
|
||||
edition = "2018"
|
||||
publish = false
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "sailfish-compiler"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
authors = ["Ryohei Machida <orcinus4627@gmail.com>"]
|
||||
description = "Really fast, intuitive template engine for Rust"
|
||||
homepage = "https://github.com/Kogia-sima/sailfish"
|
||||
|
@ -26,6 +26,7 @@ memchr = "2.3.3"
|
|||
quote = { version = "1.0.6", default-features = false }
|
||||
yaml-rust = { version = "0.4.4", optional = true }
|
||||
home = "0.5.3"
|
||||
filetime = "0.2.14"
|
||||
|
||||
[dependencies.syn]
|
||||
version = "1.0.21"
|
||||
|
|
|
@ -11,7 +11,7 @@ use crate::optimizer::Optimizer;
|
|||
use crate::parser::Parser;
|
||||
use crate::resolver::Resolver;
|
||||
use crate::translator::{TranslatedSource, Translator};
|
||||
use crate::util::{read_to_string, rustfmt_block};
|
||||
use crate::util::{copy_filetimes, read_to_string, rustfmt_block};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Compiler {
|
||||
|
@ -81,6 +81,12 @@ impl Compiler {
|
|||
.chain_err(|| format!("Failed to create artifact: {:?}", output))?;
|
||||
writeln!(f, "{}", rustfmt_block(&*string).unwrap_or(string))
|
||||
.chain_err(|| format!("Failed to write artifact into {:?}", output))?;
|
||||
drop(f);
|
||||
|
||||
// FIXME: This is a silly hack to prevent output file from being tracking by
|
||||
// cargo. Another better solution should be considered.
|
||||
let _ = copy_filetimes(input, output);
|
||||
|
||||
Ok(report)
|
||||
};
|
||||
|
||||
|
|
|
@ -88,6 +88,15 @@ impl VisitMut for OptmizerImpl {
|
|||
let mut concat = sl;
|
||||
concat += sf.as_str();
|
||||
|
||||
let mut previous;
|
||||
if let Some(prev) = results.last().and_then(get_rendertext_value_from_stmt) {
|
||||
results.pop();
|
||||
previous = prev;
|
||||
previous += sf.as_str();
|
||||
} else {
|
||||
previous = sf;
|
||||
}
|
||||
|
||||
fl.body.stmts.remove(0);
|
||||
*fl.body.stmts.last_mut().unwrap() = syn::parse2(quote! {
|
||||
__sf_rt::render_text!(__sf_buf, #concat);
|
||||
|
@ -95,7 +104,7 @@ impl VisitMut for OptmizerImpl {
|
|||
.unwrap();
|
||||
|
||||
let mut new_stmts = syn::parse2::<Block>(quote! {{
|
||||
__sf_rt::render_text!(__sf_buf, #sf);
|
||||
__sf_rt::render_text!(__sf_buf, #previous);
|
||||
#stmt
|
||||
unsafe { __sf_buf._set_len(__sf_buf.len() - #sf_len); }
|
||||
}})
|
||||
|
|
|
@ -20,7 +20,6 @@ struct DeriveTemplateOptions {
|
|||
delimiter: Option<LitChar>,
|
||||
escape: Option<LitBool>,
|
||||
rm_whitespace: Option<LitBool>,
|
||||
type_: Option<LitStr>,
|
||||
}
|
||||
|
||||
impl DeriveTemplateOptions {
|
||||
|
@ -49,8 +48,6 @@ impl DeriveTemplateOptions {
|
|||
self.escape = Some(s.parse::<LitBool>()?);
|
||||
} else if key == "rm_whitespace" {
|
||||
self.rm_whitespace = Some(s.parse::<LitBool>()?);
|
||||
} else if key == "type" {
|
||||
self.type_ = Some(s.parse::<LitStr>()?);
|
||||
} else {
|
||||
return Err(syn::Error::new(
|
||||
key.span(),
|
||||
|
@ -188,8 +185,7 @@ fn derive_template_impl(tokens: TokenStream) -> Result<TokenStream, syn::Error>
|
|||
)?
|
||||
};
|
||||
|
||||
let out_dir = PathBuf::from(env!("OUT_DIR"));
|
||||
let mut output_file = out_dir.clone();
|
||||
let mut output_file = PathBuf::from(env!("OUT_DIR"));
|
||||
output_file.push("templates");
|
||||
output_file.push(filename_hash(&*input_file));
|
||||
|
||||
|
@ -245,9 +241,7 @@ fn derive_template_impl(tokens: TokenStream) -> Result<TokenStream, syn::Error>
|
|||
use sailfish::runtime::{Buffer, SizeHint};
|
||||
static SIZE_HINT: SizeHint = SizeHint::new();
|
||||
|
||||
let mut buf = Buffer::new();
|
||||
buf.reserve(SIZE_HINT.get());
|
||||
|
||||
let mut buf = Buffer::with_capacity(SIZE_HINT.get());
|
||||
self.render_once_to(&mut buf)?;
|
||||
SIZE_HINT.update(buf.len());
|
||||
|
||||
|
|
|
@ -347,6 +347,7 @@ mod tests {
|
|||
use crate::parser::Parser;
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn translate() {
|
||||
let src = "<% pub fn sample() { %> <%% <%=//%>\n1%><% } %>";
|
||||
let lexer = Parser::new();
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use filetime::FileTime;
|
||||
use std::fs;
|
||||
use std::io::{self, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
@ -76,3 +77,11 @@ pub fn rustfmt_block(source: &str) -> io::Result<String> {
|
|||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn copy_filetimes(input: &Path, output: &Path) -> io::Result<()> {
|
||||
let mtime = fs::metadata(input)
|
||||
.and_then(|metadata| metadata.modified())
|
||||
.map_or(FileTime::zero(), |time| FileTime::from_system_time(time));
|
||||
|
||||
filetime::set_file_times(output, mtime, mtime)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "sailfish-macros"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
authors = ["Ryohei Machida <orcinus4627@gmail.com>"]
|
||||
description = "Really fast, intuitive template engine for Rust"
|
||||
homepage = "https://github.com/Kogia-sima/sailfish"
|
||||
|
@ -30,6 +30,6 @@ proc-macro2 = "1.0.11"
|
|||
|
||||
[dependencies.sailfish-compiler]
|
||||
path = "../sailfish-compiler"
|
||||
version = "=0.3.0"
|
||||
version = "0.3.1"
|
||||
default-features = false
|
||||
features = ["procmacro"]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "fuzzing-tests"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
authors = ["Ryohei Machida <orcinus4627@gmail.com>"]
|
||||
edition = "2018"
|
||||
publish = false
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "integration-tests"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
authors = ["Kogia-sima <orcinus4627@gmail.com>"]
|
||||
edition = "2018"
|
||||
publish = false
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
<table>
|
||||
<tr><td>0</td><td>1</td><td>2</td><td>3</td><td>4</td><td>5</td><td>6</td><td>7</td><td>8</td><td>9</td></tr><tr><td>0</td><td>1</td><td>2</td><td>3</td><td>4</td><td>5</td><td>6</td><td>7</td><td>8</td><td>9</td></tr><tr><td>0</td><td>1</td><td>2</td><td>3</td><td>4</td><td>5</td><td>6</td><td>7</td><td>8</td><td>9</td></tr><tr><td>0</td><td>1</td><td>2</td><td>3</td><td>4</td><td>5</td><td>6</td><td>7</td><td>8</td><td>9</td></tr><tr><td>0</td><td>1</td><td>2</td><td>3</td><td>4</td><td>5</td><td>6</td><td>7</td><td>8</td><td>9</td></tr><tr><td>0</td><td>1</td><td>2</td><td>3</td><td>4</td><td>5</td><td>6</td><td>7</td><td>8</td><td>9</td></tr><tr><td>0</td><td>1</td><td>2</td><td>3</td><td>4</td><td>5</td><td>6</td><td>7</td><td>8</td><td>9</td></tr><tr><td>0</td><td>1</td><td>2</td><td>3</td><td>4</td><td>5</td><td>6</td><td>7</td><td>8</td><td>9</td></tr><tr><td>0</td><td>1</td><td>2</td><td>3</td><td>4</td><td>5</td><td>6</td><td>7</td><td>8</td><td>9</td></tr><tr><td>0</td><td>1</td><td>2</td><td>3</td><td>4</td><td>5</td><td>6</td><td>7</td><td>8</td><td>9</td></tr>
|
||||
</table>
|
|
@ -1,3 +0,0 @@
|
|||
<table>
|
||||
<% for row in table { %><tr><% for col in row { %><td><%= col %></td><% } %></tr><% } %>
|
||||
</table>
|
|
@ -1,11 +0,0 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>2015</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>CSL 2015</h1>
|
||||
<ul>
|
||||
<li class="champion"><b>Jiangsu</b>: 43</li><li class=""><b>Beijing</b>: 27</li><li class=""><b>Guangzhou</b>: 22</li><li class=""><b>Shandong</b>: 12</li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
|
@ -1,11 +0,0 @@
|
|||
<html>
|
||||
<head>
|
||||
<title><%= year %></title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>CSL <%= year %></h1>
|
||||
<ul>
|
||||
<% for (i, team) in teams.iter().enumerate() { %><li class="<% if i == 0 { %>champion<% } %>"><b><%- team.name %></b>: <%= team.score %></li><% } %>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
|
@ -112,57 +112,6 @@ fn continue_break() {
|
|||
assert_render("continue-break", ContinueBreak);
|
||||
}
|
||||
|
||||
#[derive(TemplateOnce)]
|
||||
#[template(path = "big-table.stpl", rm_whitespace = true)]
|
||||
struct BigTable {
|
||||
table: Vec<Vec<usize>>,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_big_table() {
|
||||
let table = (0..10).map(|_| (0..10).collect()).collect();
|
||||
assert_render("big-table", BigTable { table });
|
||||
}
|
||||
|
||||
#[derive(TemplateOnce)]
|
||||
#[template(path = "teams.stpl", rm_whitespace = true)]
|
||||
struct Teams {
|
||||
year: u16,
|
||||
teams: Vec<Team>,
|
||||
}
|
||||
|
||||
struct Team {
|
||||
name: String,
|
||||
score: u8,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_teams() {
|
||||
let teams = Teams {
|
||||
year: 2015,
|
||||
teams: vec![
|
||||
Team {
|
||||
name: "Jiangsu".into(),
|
||||
|
||||
score: 43,
|
||||
},
|
||||
Team {
|
||||
name: "Beijing".into(),
|
||||
score: 27,
|
||||
},
|
||||
Team {
|
||||
name: "Guangzhou".into(),
|
||||
score: 22,
|
||||
},
|
||||
Team {
|
||||
name: "Shandong".into(),
|
||||
score: 12,
|
||||
},
|
||||
],
|
||||
};
|
||||
assert_render("teams", teams);
|
||||
}
|
||||
|
||||
#[derive(TemplateOnce)]
|
||||
#[template(path = "techempower.stpl", rm_whitespace = true)]
|
||||
struct Techempower {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "sailfish"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
authors = ["Ryohei Machida <orcinus4627@gmail.com>"]
|
||||
description = "Really fast, intuitive template engine for Rust"
|
||||
homepage = "https://github.com/Kogia-sima/sailfish"
|
||||
|
@ -29,7 +29,7 @@ serde_json = { version = "1.0.53", optional = true }
|
|||
|
||||
[dependencies.sailfish-macros]
|
||||
path = "../sailfish-macros"
|
||||
version = "=0.3.0"
|
||||
version = "0.3.1"
|
||||
optional = true
|
||||
|
||||
[build-dependencies]
|
||||
|
|
|
@ -69,6 +69,7 @@ impl Buffer {
|
|||
#[inline]
|
||||
#[doc(hidden)]
|
||||
pub unsafe fn _set_len(&mut self, new_len: usize) {
|
||||
debug_assert!(new_len <= self.capacity);
|
||||
self.len = new_len;
|
||||
}
|
||||
|
||||
|
@ -332,7 +333,9 @@ unsafe impl Sync for Buffer {}
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Buffer;
|
||||
use super::*;
|
||||
use std::sync::{Arc, Barrier, Mutex};
|
||||
use std::thread;
|
||||
|
||||
#[test]
|
||||
fn push_str() {
|
||||
|
@ -347,6 +350,13 @@ mod tests {
|
|||
buffer.push_str("pie");
|
||||
assert_eq!(buffer.len(), 8);
|
||||
assert_eq!(buffer.capacity(), 10);
|
||||
|
||||
for _ in 0..16 {
|
||||
buffer.push_str("zomg");
|
||||
}
|
||||
|
||||
assert_eq!(buffer.len(), 72);
|
||||
assert_eq!(buffer.capacity(), 80);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -360,9 +370,15 @@ mod tests {
|
|||
#[test]
|
||||
fn string_conversion() {
|
||||
// from empty string
|
||||
let s = String::new();
|
||||
let s = String::with_capacity(2);
|
||||
assert!(s.capacity() >= 2);
|
||||
|
||||
let mut buf = Buffer::from(s);
|
||||
assert_eq!(buf.as_str(), "");
|
||||
|
||||
// capacity should be shrinked for safety
|
||||
assert_eq!(buf.capacity(), 0);
|
||||
|
||||
buf.push_str("abc");
|
||||
assert_eq!(buf.as_str(), "abc");
|
||||
|
||||
|
@ -370,7 +386,7 @@ mod tests {
|
|||
let mut s = buf.into_string();
|
||||
assert_eq!(s, "abc");
|
||||
|
||||
s.push_str("defghijklmn");
|
||||
s += "defghijklmn";
|
||||
assert_eq!(s, "abcdefghijklmn");
|
||||
|
||||
// from non-empty string
|
||||
|
@ -388,6 +404,12 @@ mod tests {
|
|||
assert_eq!(s, "apple");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_str() {
|
||||
let buf = Buffer::from("abcdefgh");
|
||||
assert_eq!(buf.as_str(), "abcdefgh");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clone() {
|
||||
use std::fmt::Write;
|
||||
|
@ -427,4 +449,45 @@ mod tests {
|
|||
assert_eq!(s.as_str(), "aéA🄫");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multi_thread() {
|
||||
const THREADS: usize = 8;
|
||||
const ITERS: usize = 100;
|
||||
|
||||
let barrier = Arc::new(Barrier::new(THREADS));
|
||||
let buffer = Arc::new(Mutex::new(Buffer::new()));
|
||||
let mut handles = Vec::with_capacity(THREADS);
|
||||
|
||||
for _ in 0..THREADS {
|
||||
let barrier = barrier.clone();
|
||||
let buffer = buffer.clone();
|
||||
|
||||
handles.push(thread::spawn(move || {
|
||||
barrier.wait();
|
||||
for _ in 0..ITERS {
|
||||
buffer.lock().unwrap().push_str("a");
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
for handle in handles {
|
||||
handle.join().unwrap();
|
||||
}
|
||||
|
||||
assert_eq!(buffer.lock().unwrap().as_str(), "a".repeat(ITERS * THREADS));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn reserve_overflow() {
|
||||
let mut buf = Buffer::new();
|
||||
buf.reserve(std::isize::MAX as usize + 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn empty_alloc() {
|
||||
safe_alloc(0);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,8 +7,8 @@ use std::arch::x86_64::*;
|
|||
use std::slice;
|
||||
|
||||
use super::super::Buffer;
|
||||
use super::{ESCAPED, ESCAPED_LEN, ESCAPE_LUT};
|
||||
use super::naive::push_escaped_str;
|
||||
use super::{ESCAPED, ESCAPED_LEN, ESCAPE_LUT};
|
||||
|
||||
const VECTOR_BYTES: usize = std::mem::size_of::<__m256i>();
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ const USIZE_BYTES: usize = 8;
|
|||
|
||||
const USIZE_ALIGN: usize = USIZE_BYTES - 1;
|
||||
|
||||
#[inline(always)]
|
||||
#[inline]
|
||||
fn contains_zero_byte(x: usize) -> bool {
|
||||
const LO_U64: u64 = 0x0101_0101_0101_0101;
|
||||
const HI_U64: u64 = 0x8080_8080_8080_8080;
|
||||
|
|
|
@ -2,8 +2,21 @@
|
|||
//!
|
||||
//! By default sailfish replaces the characters `&"'<>` with the equivalent html.
|
||||
|
||||
#![cfg_attr(
|
||||
all(
|
||||
any(target_arch = "x86", target_arch = "x86_64"),
|
||||
not(miri),
|
||||
target_feature = "avx2"
|
||||
),
|
||||
allow(dead_code)
|
||||
)]
|
||||
|
||||
#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), not(miri)))]
|
||||
mod avx2;
|
||||
mod fallback;
|
||||
mod naive;
|
||||
#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), not(miri)))]
|
||||
mod sse2;
|
||||
|
||||
static ESCAPE_LUT: [u8; 256] = [
|
||||
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
|
||||
|
@ -21,20 +34,20 @@ static ESCAPE_LUT: [u8; 256] = [
|
|||
const ESCAPED: [&str; 5] = [""", "&", "'", "<", ">"];
|
||||
const ESCAPED_LEN: usize = 5;
|
||||
|
||||
#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), not(miri)))]
|
||||
macro_rules! generate_impl {
|
||||
() => {
|
||||
mod avx2;
|
||||
mod sse2;
|
||||
use super::buffer::Buffer;
|
||||
|
||||
use super::buffer::Buffer;
|
||||
/// write the escaped contents into `Buffer`
|
||||
#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), not(miri)))]
|
||||
#[cfg_attr(feature = "perf-inline", inline)]
|
||||
pub fn escape_to_buf(feed: &str, buf: &mut Buffer) {
|
||||
#[cfg(not(target_feature = "avx2"))]
|
||||
{
|
||||
use std::sync::atomic::{AtomicPtr, Ordering};
|
||||
|
||||
type FnRaw = *mut ();
|
||||
static FN: AtomicPtr<()> = AtomicPtr::new(detect as FnRaw);
|
||||
|
||||
static FN: AtomicPtr<()> = AtomicPtr::new(escape as FnRaw);
|
||||
|
||||
fn escape(feed: &str, buf: &mut Buffer) {
|
||||
fn detect(feed: &str, buf: &mut Buffer) {
|
||||
debug_assert!(feed.len() >= 16);
|
||||
let fun = if is_x86_feature_detected!("avx2") {
|
||||
avx2::escape
|
||||
|
@ -48,51 +61,48 @@ macro_rules! generate_impl {
|
|||
unsafe { fun(feed, buf) };
|
||||
}
|
||||
|
||||
/// write the escaped contents into `Buffer`
|
||||
#[cfg_attr(feature = "perf-inline", inline)]
|
||||
pub fn escape_to_buf(feed: &str, buf: &mut Buffer) {
|
||||
unsafe {
|
||||
if feed.len() < 16 {
|
||||
buf.reserve_small(feed.len() * 6);
|
||||
let l = naive::escape_small(feed, buf.as_mut_ptr().add(buf.len()));
|
||||
buf.advance(l);
|
||||
} else if cfg!(target_feature = "avx2") {
|
||||
avx2::escape(feed, buf);
|
||||
} else {
|
||||
let fun = FN.load(Ordering::Relaxed);
|
||||
std::mem::transmute::<FnRaw, fn(&str, &mut Buffer)>(fun)(feed, buf);
|
||||
}
|
||||
unsafe {
|
||||
if feed.len() < 16 {
|
||||
buf.reserve_small(feed.len() * 6);
|
||||
let l = naive::escape_small(feed, buf.as_mut_ptr().add(buf.len()));
|
||||
buf.advance(l);
|
||||
} else {
|
||||
let fun = FN.load(Ordering::Relaxed);
|
||||
std::mem::transmute::<FnRaw, fn(&str, &mut Buffer)>(fun)(feed, buf);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(target_feature = "avx2")]
|
||||
unsafe {
|
||||
if feed.len() < 16 {
|
||||
buf.reserve_small(feed.len() * 6);
|
||||
let l = naive::escape_small(feed, buf.as_mut_ptr().add(buf.len()));
|
||||
buf.advance(l);
|
||||
} else if cfg!(target_feature = "avx2") {
|
||||
avx2::escape(feed, buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// write the escaped contents into `Buffer`
|
||||
#[cfg(not(all(any(target_arch = "x86", target_arch = "x86_64"), not(miri))))]
|
||||
macro_rules! generate_impl {
|
||||
() => {
|
||||
use super::buffer::Buffer;
|
||||
|
||||
/// write the escaped contents into `Buffer`
|
||||
#[cfg_attr(feature = "perf-inline", inline)]
|
||||
pub fn escape_to_buf(feed: &str, buf: &mut Buffer) {
|
||||
unsafe {
|
||||
if cfg!(miri) {
|
||||
let bp = feed.as_ptr();
|
||||
naive::escape(buf, bp, bp, bp.add(feed.len()))
|
||||
} else if feed.len() < 16 {
|
||||
buf.reserve_small(feed.len() * 6);
|
||||
let l = naive::escape_small(feed, buf.as_mut_ptr().add(buf.len()));
|
||||
buf.advance(l);
|
||||
} else {
|
||||
fallback::escape(feed, buf)
|
||||
}
|
||||
}
|
||||
#[cfg_attr(feature = "perf-inline", inline)]
|
||||
pub fn escape_to_buf(feed: &str, buf: &mut Buffer) {
|
||||
unsafe {
|
||||
if cfg!(miri) {
|
||||
let bp = feed.as_ptr();
|
||||
naive::escape(buf, bp, bp, bp.add(feed.len()))
|
||||
} else if feed.len() < 16 {
|
||||
buf.reserve_small(feed.len() * 6);
|
||||
let l = naive::escape_small(feed, buf.as_mut_ptr().add(buf.len()));
|
||||
buf.advance(l);
|
||||
} else {
|
||||
fallback::escape(feed, buf)
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
generate_impl!();
|
||||
|
||||
/// write the escaped contents into `String`
|
||||
///
|
||||
/// # Examples
|
||||
|
|
|
@ -7,8 +7,8 @@ use std::arch::x86_64::*;
|
|||
use std::slice;
|
||||
|
||||
use super::super::Buffer;
|
||||
use super::{ESCAPED, ESCAPED_LEN, ESCAPE_LUT};
|
||||
use super::naive::push_escaped_str;
|
||||
use super::{ESCAPED, ESCAPED_LEN, ESCAPE_LUT};
|
||||
|
||||
const VECTOR_BYTES: usize = std::mem::size_of::<__m128i>();
|
||||
|
||||
|
|
|
@ -1,16 +1,14 @@
|
|||
//! Build-in filters
|
||||
|
||||
// TODO: performance improvement
|
||||
|
||||
use std::fmt;
|
||||
use std::ptr;
|
||||
|
||||
use super::{Buffer, Render, RenderError};
|
||||
|
||||
/// Helper struct for 'display' filter
|
||||
pub struct Display<'a, T>(&'a T);
|
||||
pub struct Display<'a, T: ?Sized>(&'a T);
|
||||
|
||||
impl<'a, T: fmt::Display> Render for Display<'a, T> {
|
||||
impl<'a, T: fmt::Display + ?Sized> Render for Display<'a, T> {
|
||||
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
use fmt::Write;
|
||||
|
||||
|
@ -26,14 +24,14 @@ impl<'a, T: fmt::Display> Render for Display<'a, T> {
|
|||
/// filename: <%= filename.display() | disp %>
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn disp<T: fmt::Display>(expr: &T) -> Display<T> {
|
||||
pub fn disp<T: fmt::Display + ?Sized>(expr: &T) -> Display<T> {
|
||||
Display(expr)
|
||||
}
|
||||
|
||||
/// Helper struct for 'dbg' filter
|
||||
pub struct Debug<'a, T>(&'a T);
|
||||
pub struct Debug<'a, T: ?Sized>(&'a T);
|
||||
|
||||
impl<'a, T: fmt::Debug> Render for Debug<'a, T> {
|
||||
impl<'a, T: fmt::Debug + ?Sized> Render for Debug<'a, T> {
|
||||
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
use fmt::Write;
|
||||
|
||||
|
@ -52,30 +50,37 @@ impl<'a, T: fmt::Debug> Render for Debug<'a, T> {
|
|||
/// ```
|
||||
///
|
||||
/// ```text
|
||||
/// table content: <%= format!("{:?}", table); %>
|
||||
/// table content: <%= format!("{:?}", table) %>
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn dbg<T: fmt::Debug>(expr: &T) -> Debug<T> {
|
||||
pub fn dbg<T: fmt::Debug + ?Sized>(expr: &T) -> Debug<T> {
|
||||
Debug(expr)
|
||||
}
|
||||
|
||||
/// Helper struct for 'upper' filter
|
||||
pub struct Upper<'a, T>(&'a T);
|
||||
pub struct Upper<'a, T: ?Sized>(&'a T);
|
||||
|
||||
impl<'a, T: Render> Render for Upper<'a, 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_else(|| RenderError::BufSize)?;
|
||||
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);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn render_escaped(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
let old_len = b.len();
|
||||
self.0.render_escaped(b)?;
|
||||
|
||||
let s = b.as_str()[old_len..].to_uppercase();
|
||||
unsafe { b._set_len(old_len) };
|
||||
b.push_str(&*s);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// convert the rendered contents to uppercase
|
||||
|
@ -92,22 +97,19 @@ impl<'a, T: Render> Render for Upper<'a, T> {
|
|||
/// TSCHÜSS
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn upper<T: Render>(expr: &T) -> Upper<T> {
|
||||
pub fn upper<T: Render + ?Sized>(expr: &T) -> Upper<T> {
|
||||
Upper(expr)
|
||||
}
|
||||
|
||||
/// Helper struct for 'lower' filter
|
||||
pub struct Lower<'a, T>(&'a T);
|
||||
pub struct Lower<'a, T: ?Sized>(&'a T);
|
||||
|
||||
impl<'a, T: Render> Render for Lower<'a, T> {
|
||||
impl<'a, T: Render + ?Sized> Render for Lower<'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_else(|| RenderError::BufSize)?;
|
||||
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);
|
||||
|
@ -139,14 +141,14 @@ impl<'a, T: Render> Render for Lower<'a, T> {
|
|||
/// ὀδυσσεύς
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn lower<T: Render>(expr: &T) -> Lower<T> {
|
||||
pub fn lower<T: Render + ?Sized>(expr: &T) -> Lower<T> {
|
||||
Lower(expr)
|
||||
}
|
||||
|
||||
/// Helper struct for 'trim' filter
|
||||
pub struct Trim<'a, T>(&'a T);
|
||||
pub struct Trim<'a, T: ?Sized>(&'a T);
|
||||
|
||||
impl<'a, T: Render> Render for Trim<'a, T> {
|
||||
impl<'a, T: Render + ?Sized> Render for Trim<'a, T> {
|
||||
#[inline]
|
||||
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
let old_len = b.len();
|
||||
|
@ -163,10 +165,7 @@ impl<'a, T: Render> Render for Trim<'a, T> {
|
|||
}
|
||||
|
||||
fn trim_impl(b: &mut Buffer, old_len: usize) -> Result<(), RenderError> {
|
||||
let new_contents = b
|
||||
.as_str()
|
||||
.get(old_len..)
|
||||
.ok_or_else(|| RenderError::BufSize)?;
|
||||
let new_contents = b.as_str().get(old_len..).ok_or(RenderError::BufSize)?;
|
||||
|
||||
let trimmed = new_contents.trim();
|
||||
let trimmed_len = trimmed.len();
|
||||
|
@ -212,14 +211,14 @@ fn trim_impl(b: &mut Buffer, old_len: usize) -> Result<(), RenderError> {
|
|||
/// Hello world
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn trim<T: Render>(expr: &T) -> Trim<T> {
|
||||
pub fn trim<T: Render + ?Sized>(expr: &T) -> Trim<T> {
|
||||
Trim(expr)
|
||||
}
|
||||
|
||||
/// Helper struct for 'truncate' filter
|
||||
pub struct Truncate<'a, T>(&'a T, usize);
|
||||
pub struct Truncate<'a, T: ?Sized>(&'a T, usize);
|
||||
|
||||
impl<'a, T: Render> Render for Truncate<'a, T> {
|
||||
impl<'a, T: Render + ?Sized> Render for Truncate<'a, T> {
|
||||
#[inline]
|
||||
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
let old_len = b.len();
|
||||
|
@ -240,10 +239,7 @@ fn truncate_impl(
|
|||
old_len: usize,
|
||||
limit: usize,
|
||||
) -> Result<(), RenderError> {
|
||||
let new_contents = b
|
||||
.as_str()
|
||||
.get(old_len..)
|
||||
.ok_or_else(|| RenderError::BufSize)?;
|
||||
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)) };
|
||||
|
@ -260,18 +256,24 @@ fn truncate_impl(
|
|||
/// The following example renders the first 20 characters of `message`
|
||||
///
|
||||
/// ```test
|
||||
/// <%= message | truncate(20) %>
|
||||
/// <%= "Hello, world!" | truncate(5) %>
|
||||
/// ```
|
||||
///
|
||||
/// result:
|
||||
///
|
||||
/// ```text
|
||||
/// Hello...
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn truncate<T: Render>(expr: &T, limit: usize) -> Truncate<T> {
|
||||
pub fn truncate<T: Render + ?Sized>(expr: &T, limit: usize) -> Truncate<T> {
|
||||
Truncate(expr, limit)
|
||||
}
|
||||
|
||||
cfg_json! {
|
||||
/// Helper struct for 'json' filter
|
||||
pub struct Json<'a, T>(&'a T);
|
||||
pub struct Json<'a, T: ?Sized>(&'a T);
|
||||
|
||||
impl<'a, T: serde::Serialize> Render for Json<'a, T> {
|
||||
impl<'a, T: serde::Serialize + ?Sized> Render for Json<'a, T> {
|
||||
#[inline]
|
||||
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
|
||||
struct Writer<'a>(&'a mut Buffer);
|
||||
|
@ -340,7 +342,7 @@ cfg_json! {
|
|||
/// }
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn json<T: serde::Serialize>(expr: &T) -> Json<T> {
|
||||
pub fn json<T: serde::Serialize + ?Sized>(expr: &T) -> Json<T> {
|
||||
Json(expr)
|
||||
}
|
||||
}
|
||||
|
@ -349,37 +351,124 @@ cfg_json! {
|
|||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn case() {
|
||||
fn assert_render<T: Render>(expr: &T, expected: &str) {
|
||||
let mut buf = Buffer::new();
|
||||
upper(&"hElLO, WOrLd!").render(&mut buf).unwrap();
|
||||
assert_eq!(buf.as_str(), "HELLO, WORLD!");
|
||||
Render::render(expr, &mut buf).unwrap();
|
||||
assert_eq!(buf.as_str(), expected);
|
||||
}
|
||||
|
||||
buf.clear();
|
||||
lower(&"hElLO, WOrLd!").render(&mut buf).unwrap();
|
||||
assert_eq!(buf.as_str(), "hello, world!");
|
||||
|
||||
buf.clear();
|
||||
lower(&"<h1>TITLE</h1>").render_escaped(&mut buf).unwrap();
|
||||
assert_eq!(buf.as_str(), "<h1>title</h1>");
|
||||
fn assert_render_escaped<T: Render>(expr: &T, expected: &str) {
|
||||
let mut buf = Buffer::new();
|
||||
Render::render_escaped(expr, &mut buf).unwrap();
|
||||
assert_eq!(buf.as_str(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trim_test() {
|
||||
let mut buf = Buffer::new();
|
||||
trim(&" hello ").render(&mut buf).unwrap();
|
||||
trim(&"hello ").render(&mut buf).unwrap();
|
||||
trim(&" hello").render(&mut buf).unwrap();
|
||||
assert_eq!(buf.as_str(), "hellohellohello");
|
||||
fn test_lower() {
|
||||
assert_render(&lower(""), "");
|
||||
assert_render_escaped(&lower(""), "");
|
||||
|
||||
let mut buf = Buffer::new();
|
||||
trim(&"hello ").render(&mut buf).unwrap();
|
||||
trim(&" hello").render(&mut buf).unwrap();
|
||||
trim(&"hello").render(&mut buf).unwrap();
|
||||
assert_eq!(buf.as_str(), "hellohellohello");
|
||||
assert_render(&lower("lorem ipsum"), "lorem ipsum");
|
||||
assert_render(&lower("LOREM IPSUM"), "lorem ipsum");
|
||||
|
||||
let mut buf = Buffer::new();
|
||||
trim(&" hello").render(&mut buf).unwrap();
|
||||
assert_eq!(buf.as_str(), "hello");
|
||||
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("<<&\"\">>"), "<<&"">>");
|
||||
|
||||
// non-ascii
|
||||
assert_render(&lower("aBcAbc"), "abcabc");
|
||||
assert_render(&lower("ὈΔΥΣΣΕΎΣ"), "ὀδυσσεύς");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_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("hElLo, WOrLd!"), "HELLO, WORLD!");
|
||||
assert_render(&upper("hElLo, WOrLd!"), "HELLO, WORLD!");
|
||||
|
||||
// non-ascii
|
||||
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("\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_escaped(&trim(" <html> "), "<html>");
|
||||
assert_render_escaped(&lower("<<&\"\">>"), "<<&"">>");
|
||||
|
||||
// non-ascii whitespace
|
||||
assert_render(&trim("\u{A0}空白\u{3000}\u{205F}"), "空白");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_truncate() {
|
||||
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(&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");
|
||||
|
||||
// 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), "魑魅魍魎");
|
||||
}
|
||||
|
||||
#[cfg(feature = "json")]
|
||||
#[test]
|
||||
fn test_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"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compine() {
|
||||
assert_render(
|
||||
&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(&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...");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -89,16 +89,16 @@ impl Render for String {
|
|||
}
|
||||
}
|
||||
|
||||
impl Render for &str {
|
||||
impl Render for str {
|
||||
#[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(())
|
||||
}
|
||||
}
|
||||
|
@ -431,8 +431,8 @@ mod tests {
|
|||
|
||||
Render::render(&true, &mut b).unwrap();
|
||||
Render::render(&&false, &mut b).unwrap();
|
||||
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();
|
||||
assert_eq!(b.as_str(), "truefalsetruefalse");
|
||||
b.clear();
|
||||
|
||||
|
@ -455,7 +455,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn deref_coercion() {
|
||||
use std::path::PathBuf;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::rc::Rc;
|
||||
|
||||
let mut b = Buffer::new();
|
||||
|
@ -463,8 +463,10 @@ mod tests {
|
|||
Render::render(&&PathBuf::from("b"), &mut b).unwrap();
|
||||
Render::render_escaped(&Rc::new(4u32), &mut b).unwrap();
|
||||
Render::render_escaped(&Rc::new(2.3f32), &mut b).unwrap();
|
||||
Render::render_escaped(Path::new("<"), &mut b).unwrap();
|
||||
Render::render_escaped(&Path::new("d"), &mut b).unwrap();
|
||||
|
||||
assert_eq!(b.as_str(), "ab42.3");
|
||||
assert_eq!(b.as_str(), "ab42.3<d");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -485,6 +487,40 @@ mod tests {
|
|||
assert_eq!(b.as_str(), "0.0inf-infNaN");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_char() {
|
||||
let mut b = Buffer::new();
|
||||
|
||||
let funcs: Vec<fn(&char, &mut Buffer) -> Result<(), RenderError>> =
|
||||
vec![Render::render, Render::render_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();
|
||||
|
||||
assert_eq!(b.as_str(), "abcd");
|
||||
b.clear();
|
||||
|
||||
func(&'あ', &mut b).unwrap();
|
||||
func(&'い', &mut b).unwrap();
|
||||
func(&'う', &mut b).unwrap();
|
||||
func(&'え', &mut b).unwrap();
|
||||
|
||||
assert_eq!(b.as_str(), "あいうえ");
|
||||
b.clear();
|
||||
}
|
||||
}
|
||||
|
||||
#[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();
|
||||
assert_eq!(b.as_str(), "10-20");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_error() {
|
||||
let err = RenderError::new("custom error");
|
||||
|
@ -493,5 +529,13 @@ mod tests {
|
|||
|
||||
let err = RenderError::from(std::fmt::Error::default());
|
||||
assert!(err.source().is_some());
|
||||
assert_eq!(
|
||||
format!("{}", err),
|
||||
format!("{}", std::fmt::Error::default())
|
||||
);
|
||||
|
||||
let err = RenderError::BufSize;
|
||||
assert!(err.source().is_none());
|
||||
format!("{}", err);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
#!/bin/bash
|
||||
|
||||
IFS=$'\n'
|
||||
PACKAGES=("sailfish" "sailfish-macros" "sailfish-compiler")
|
||||
|
||||
git-root () {
|
||||
if git rev-parse --is-inside-work-tree > /dev/null 2>&1; then
|
||||
cd `git rev-parse --show-toplevel`;
|
||||
fi
|
||||
}
|
||||
|
||||
get_dependencies() {
|
||||
cargo tree -p "$1" | while read line; do
|
||||
dev_dependencies_re="\[dev-dependencies\]"
|
||||
crate_re="[a-zA-Z0-9_-]+ v[^ ]+"
|
||||
|
||||
if [[ "$line" =~ $crate_re ]]; then
|
||||
echo ${BASH_REMATCH[0]}
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ "$line" =~ $dev_dependencies_re ]]; then
|
||||
break
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
remove_packages() {
|
||||
local found
|
||||
|
||||
for dep in $@; do
|
||||
found=0
|
||||
for pkg in ${PACKAGES[@]}; do
|
||||
pat="$pkg v[^ ]"
|
||||
if [[ "$dep" =~ $pat ]]; then
|
||||
found=1
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ $found == 0 ]]; then
|
||||
echo $dep
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# go to root directory
|
||||
cd `git rev-parse --show-toplevel`
|
||||
|
||||
deps=()
|
||||
|
||||
for pkg in ${PACKAGES[@]}; do
|
||||
deps+=( `get_dependencies $pkg` )
|
||||
done
|
||||
|
||||
deps=( $(printf "%s\n" "${deps[@]}" | sort -u) )
|
||||
|
||||
remove_packages ${deps[*]} |wc -l
|
Loading…
Reference in New Issue