From a769e12650f6066d39456b278469c4edf5165022 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Kijewski?= Date: Wed, 14 Aug 2024 04:09:02 +0200 Subject: [PATCH] Initial --- .github/dependabot.yml | 11 ++ .github/workflows/rust.yml | 147 +++++++++++++++ .gitignore | 2 + .rustfmt.toml | 8 + Cargo.toml | 43 +++++ LICENSE.md | 194 ++++++++++++++++++++ benches/template-benchmark.rs | 5 + src/lib.rs | 129 +++++++++++++ tmpls/Cargo.toml | 9 + tmpls/askama/Cargo.toml | 11 ++ tmpls/askama/src/lib.rs | 50 +++++ tmpls/askama/templates | 1 + tmpls/markup/Cargo.toml | 11 ++ tmpls/markup/src/lib.rs | 56 ++++++ tmpls/minijinja/Cargo.toml | 11 ++ tmpls/minijinja/src/lib.rs | 43 +++++ tmpls/minijinja/templates | 1 + tmpls/rinja/Cargo.toml | 11 ++ tmpls/rinja/src/lib.rs | 50 +++++ tmpls/rinja/templates/big-table.html | 9 + tmpls/rinja/templates/teams.html | 15 ++ tmpls/src/lib.rs | 59 ++++++ tmpls/tinytemplate/Cargo.toml | 11 ++ tmpls/tinytemplate/src/lib.rs | 37 ++++ tmpls/tinytemplate/templates/big-table.html | 9 + tmpls/tinytemplate/templates/teams.html | 15 ++ tomlfmt.toml | 1 + 27 files changed, 949 insertions(+) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/rust.yml create mode 100644 .gitignore create mode 100644 .rustfmt.toml create mode 100644 Cargo.toml create mode 100644 LICENSE.md create mode 100644 benches/template-benchmark.rs create mode 100644 src/lib.rs create mode 100644 tmpls/Cargo.toml create mode 100644 tmpls/askama/Cargo.toml create mode 100644 tmpls/askama/src/lib.rs create mode 120000 tmpls/askama/templates create mode 100644 tmpls/markup/Cargo.toml create mode 100644 tmpls/markup/src/lib.rs create mode 100644 tmpls/minijinja/Cargo.toml create mode 100644 tmpls/minijinja/src/lib.rs create mode 120000 tmpls/minijinja/templates create mode 100644 tmpls/rinja/Cargo.toml create mode 100644 tmpls/rinja/src/lib.rs create mode 100644 tmpls/rinja/templates/big-table.html create mode 100644 tmpls/rinja/templates/teams.html create mode 100644 tmpls/src/lib.rs create mode 100644 tmpls/tinytemplate/Cargo.toml create mode 100644 tmpls/tinytemplate/src/lib.rs create mode 100644 tmpls/tinytemplate/templates/big-table.html create mode 100644 tmpls/tinytemplate/templates/teams.html create mode 100644 tomlfmt.toml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..5d0ffc6 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +version: 2 +updates: + - package-ecosystem: "cargo" + directory: "/" + schedule: + interval: "weekly" + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 0000000..cfb9739 --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,147 @@ +name: CI + +on: + push: + branches: + - main + pull_request: + schedule: + - cron: "32 4 * * 5" + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + Fmt: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install rust + uses: dtolnay/rust-toolchain@master + with: + toolchain: nightly + components: rustfmt + + - name: fmt + run: cargo fmt -- --check + + CargoSort: + name: Check order in Cargo.toml + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: taiki-e/install-action@v2 + with: + tool: cargo-sort + - run: cargo sort --workspace --grouped --check --check-format + + Build: + needs: + - Fmt + - CargoSort + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install rust + uses: dtolnay/rust-toolchain@master + with: + toolchain: stable + + - uses: Swatinem/rust-cache@v2 + + - name: Compile project + run: cargo build --bench template-benchmark --release + + Clippy: + needs: + - Fmt + - CargoSort + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install rust + uses: dtolnay/rust-toolchain@master + with: + toolchain: stable + components: clippy + + - uses: Swatinem/rust-cache@v2 + + - name: clippy + run: cargo clippy -- -D warnings + + Audit: + needs: + - Fmt + - CargoSort + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Audit + uses: actions-rs/audit-check@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + + DevSkim: + needs: + - Fmt + - CargoSort + runs-on: ubuntu-20.04 + permissions: + actions: read + contents: read + security-events: write + steps: + - uses: actions/checkout@v4 + + - name: Run DevSkim scanner + uses: microsoft/DevSkim-Action@v1 + + - name: Upload DevSkim scan results to GitHub Security tab + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: devskim-results.sarif + + deploy: + if: github.ref == 'refs/heads/main' + needs: + - Build + - Clippy + - Audit + - DevSkim + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install rust + uses: dtolnay/rust-toolchain@master + with: + toolchain: stable + + - name: Run benchmarks + run: cargo bench --bench template-benchmark + + - name: Fixup paths + run: sed -e 's,href="../,href=",' < target/criterion/report/index.html > target/criterion/index.html + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: 'target/criterion' + + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a9d37c5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +target +Cargo.lock diff --git a/.rustfmt.toml b/.rustfmt.toml new file mode 100644 index 0000000..ba8543e --- /dev/null +++ b/.rustfmt.toml @@ -0,0 +1,8 @@ +edition = "2021" +group_imports = "StdExternalCrate" +imports_granularity = "Module" +newline_style = "Unix" +normalize_comments = true +unstable_features = true +use_field_init_shorthand = true +version = "Two" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..270e3d8 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,43 @@ +[workspace] +members = [ + ".", + "tmpls", + "tmpls/askama", + "tmpls/markup", + "tmpls/minijinja", + "tmpls/rinja", + "tmpls/tinytemplate", +] +resolver = "2" + +[package] +name = "template-benchmark" +version = "0.1.0" +edition = "2021" +publish = false +license = "Apache-2.0" + +[features] +default = ["askama", "markup", "minijinja", "rinja", "tinytemplate"] +askama = ["dep:askama"] +markup = ["dep:markup"] +minijinja = ["dep:minijinja"] +rinja = ["dep:rinja"] +tinytemplate = ["dep:tinytemplate"] + +[dependencies] +tmpls = { version = "*", path = "tmpls" } + +askama = { version = "*", optional = true, path = "tmpls/askama", package = "tmpl-askama" } +markup = { version = "*", optional = true, path = "tmpls/markup", package = "tmpl-markup" } +minijinja = { version = "*", optional = true, path = "tmpls/minijinja", package = "tmpl-minijinja" } +rinja = { version = "*", optional = true, path = "tmpls/rinja", package = "tmpl-rinja" } +tinytemplate = { version = "*", optional = true, path = "tmpls/tinytemplate", package = "tmpl-tinytemplate" } + +ahash = { version = "0.8.11", features = ["no-rng"] } +criterion = { version = "0.5.1", features = ["html_reports"] } +quanta = "0.12.3" + +[[bench]] +name = "template-benchmark" +harness = false diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..d0af96c --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,194 @@ +Apache License +============== + +_Version 2.0, January 2004_ +_<>_ + +### Terms and Conditions for use, reproduction, and distribution + +#### 1. Definitions + +“License” shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +“Licensor” shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +“Legal Entity” shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, “control” means **(i)** the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the +outstanding shares, or **(iii)** beneficial ownership of such entity. + +“You” (or “Your”) shall mean an individual or Legal Entity exercising +permissions granted by this License. + +“Source” form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +“Object” form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +“Work” shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +“Derivative Works” shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +“Contribution” shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +“submitted” means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as “Not a Contribution.” + +“Contributor” shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +#### 2. Grant of Copyright License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +#### 3. Grant of Patent License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +#### 4. Redistribution + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +* **(a)** You must give any other recipients of the Work or Derivative Works a copy of +this License; and +* **(b)** You must cause any modified files to carry prominent notices stating that You +changed the files; and +* **(c)** You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. + +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +#### 5. Submission of Contributions + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +#### 6. Trademarks + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +#### 7. Disclaimer of Warranty + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +#### 8. Limitation of Liability + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +#### 9. Accepting Warranty or Additional Liability + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +_END OF TERMS AND CONDITIONS_ + +### APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets `[]` replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same “printed page” as the copyright notice for easier identification within +third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/benches/template-benchmark.rs b/benches/template-benchmark.rs new file mode 100644 index 0000000..0298c40 --- /dev/null +++ b/benches/template-benchmark.rs @@ -0,0 +1,5 @@ +use criterion::{criterion_group, criterion_main}; +use template_benchmark::{big_table, teams}; + +criterion_main!(benches); +criterion_group!(benches, big_table, teams); diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..68ba609 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,129 @@ +use std::hint::black_box; +use std::time::Duration; + +use ahash::RandomState; +use criterion::{Bencher, Criterion}; +use quanta::Instant; +use tmpls::{Benchmark, BigTable, Output, Team, Teams}; + +macro_rules! for_each { + ($mod:ident, $c:ident, $input:ident : $Input:ident, $func:ident) => {{ + let mut this = $mod::Benchmark::default(); + $c.bench_function( + stringify!($mod), + |b| run( + b, + &mut this, + &$input, + |this, output, input| this.$func(output, input), + ), + ); + }}; + + ($c:ident, $input:ident : $Input:ident, $func:ident) => {{ + let mut group = $c.benchmark_group(stringify!($func)); + + #[cfg(feature = "askama")] + for_each!(askama, group, $input:$Input, $func); + #[cfg(feature = "markup")] + for_each!(markup, group, $input:$Input, $func); + #[cfg(feature = "minijinja")] + for_each!(minijinja, group, $input:$Input, $func); + #[cfg(feature = "rinja")] + for_each!(rinja, group, $input:$Input, $func); + #[cfg(feature = "tinytemplate")] + for_each!(tinytemplate, group, $input:$Input, $func); + + group.finish(); + }}; +} + +pub fn big_table(c: &mut Criterion) { + const SIZE: usize = 100; + + let mut table = Vec::with_capacity(SIZE); + for _ in 0..SIZE { + let mut inner = Vec::with_capacity(SIZE); + for i in 0..SIZE { + inner.push(i); + } + table.push(inner); + } + let input = BigTable { table }; + + for_each!(c, input:BigTable, big_table); +} + +pub fn teams(c: &mut Criterion) { + let input = 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, + }, + ], + }; + + for_each!(c, input:Teams, teams); +} + +fn run( + b: &mut Bencher<'_>, + this: &mut B, + input: &I, + func: impl Fn(&mut B, &mut B::Output, &I) -> Result<(), B::Error>, +) { + let mut output = B::Output::default(); + for _ in 0..BATCH_SIZE { + func(this, &mut output, input).unwrap(); + } + let expected_hash = collect_output(&mut output); + + b.iter_custom(|iters| { + let mut total = 0; + for _ in 0..iters.div_ceil(BATCH_SIZE as u64) { + let start = Instant::now(); + for _ in 0..BATCH_SIZE { + let output = black_box(&mut output); + let input = black_box(input); + func(this, output, input).unwrap(); + } + total += start.elapsed().as_nanos() as u64; + + let hash = collect_output(&mut output); + assert_eq!( + hash, expected_hash, + "hash mismatch: 0x{expected_hash:08x} != 0x{hash:08x}", + ); + } + Duration::from_nanos(total) + }); +} + +fn collect_output(output: &mut impl Output) -> u64 { + const PI: [u64; 4] = [ + 0x243f_6a88_85a3_08d3, + 0x1319_8a2e_0370_7344, + 0xa409_3822_299f_31d0, + 0x082e_fa98_ec4e_6c89, + ]; + + let hash = RandomState::with_seeds(PI[0], PI[1], PI[2], PI[3]).hash_one(output.as_bytes()); + output.clear(); + hash +} + +const BATCH_SIZE: usize = 100; diff --git a/tmpls/Cargo.toml b/tmpls/Cargo.toml new file mode 100644 index 0000000..e51aade --- /dev/null +++ b/tmpls/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "tmpls" +version = "0.1.0" +edition = "2021" +publish = false +license = "Apache-2.0" + +[dependencies] +serde = { version = "1.0.207", features = ["derive"] } diff --git a/tmpls/askama/Cargo.toml b/tmpls/askama/Cargo.toml new file mode 100644 index 0000000..4b151a4 --- /dev/null +++ b/tmpls/askama/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "tmpl-askama" +version = "0.1.0" +edition = "2021" +publish = false +license = "Apache-2.0" + +[dependencies] +tmpls = { version = "*", path = ".." } + +askama = { version = "0.13.0", git = "https://github.com/djc/askama", branch = "main" } diff --git a/tmpls/askama/src/lib.rs b/tmpls/askama/src/lib.rs new file mode 100644 index 0000000..11b3b90 --- /dev/null +++ b/tmpls/askama/src/lib.rs @@ -0,0 +1,50 @@ +use std::ops::Deref; + +use askama::{Error, Template}; +use tmpls::{BigTable, Teams}; + +#[derive(Debug, Default)] +pub struct Benchmark; + +impl tmpls::Benchmark for Benchmark { + type Output = String; + type Error = Error; + + fn big_table( + &mut self, + output: &mut Self::Output, + input: &BigTable, + ) -> Result<(), Self::Error> { + #[derive(Template)] + #[template(path = "big-table.html")] + struct Tmpl<'a>(&'a BigTable); + + impl Deref for Tmpl<'_> { + type Target = BigTable; + + #[inline] + fn deref(&self) -> &Self::Target { + self.0 + } + } + + Tmpl(input).render_into(output) + } + + fn teams(&mut self, output: &mut Self::Output, input: &Teams) -> Result<(), Self::Error> { + #[derive(Template)] + #[template(path = "teams.html")] + struct Tmpl<'a>(&'a Teams); + + impl Deref for Tmpl<'_> { + type Target = Teams; + + #[inline] + fn deref(&self) -> &Self::Target { + self.0 + } + } + + Tmpl(input).render_into(output) + } +} diff --git a/tmpls/askama/templates b/tmpls/askama/templates new file mode 120000 index 0000000..8465398 --- /dev/null +++ b/tmpls/askama/templates @@ -0,0 +1 @@ +../rinja/templates/ \ No newline at end of file diff --git a/tmpls/markup/Cargo.toml b/tmpls/markup/Cargo.toml new file mode 100644 index 0000000..1a62157 --- /dev/null +++ b/tmpls/markup/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "tmpl-markup" +version = "0.1.0" +edition = "2021" +publish = false +license = "Apache-2.0" + +[dependencies] +tmpls = { version = "*", path = ".." } + +markup = { version = "0.15.0", features = ["itoa"] } diff --git a/tmpls/markup/src/lib.rs b/tmpls/markup/src/lib.rs new file mode 100644 index 0000000..07380e2 --- /dev/null +++ b/tmpls/markup/src/lib.rs @@ -0,0 +1,56 @@ +use markup::Render; +use tmpls::{BigTable, Teams}; + +#[derive(Debug, Default)] +pub struct Benchmark; + +impl tmpls::Benchmark for Benchmark { + type Output = String; + type Error = std::fmt::Error; + + fn big_table( + &mut self, + output: &mut Self::Output, + input: &BigTable, + ) -> Result<(), Self::Error> { + markup::define! { + Tmpl<'a>(input: &'a BigTable) { + table { + @for row in &input.table { + tr { + @for col in row { + td { @col } + } + } + } + } + } + } + + Tmpl { input }.render(output) + } + + fn teams(&mut self, output: &mut Self::Output, input: &Teams) -> Result<(), Self::Error> { + markup::define! { + Tmpl<'a>(input: &'a Teams) { + html { + head { + title { @input.year } + } + body { + h1 { "CLS " @input.year } + ul { + @for (idx, team) in input.teams.iter().enumerate() { + li[class = (idx == 0).then_some("champion")] { + b { @team.name } ": " @team.score + } + } + } + } + } + } + } + + Tmpl { input }.render(output) + } +} diff --git a/tmpls/minijinja/Cargo.toml b/tmpls/minijinja/Cargo.toml new file mode 100644 index 0000000..fc964ef --- /dev/null +++ b/tmpls/minijinja/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "tmpl-minijinja" +version = "0.1.0" +edition = "2021" +publish = false +license = "Apache-2.0" + +[dependencies] +tmpls = { version = "*", path = ".." } + +minijinja = { version = "2.1.2", default-features = false, features = ["serde", "speedups"] } diff --git a/tmpls/minijinja/src/lib.rs b/tmpls/minijinja/src/lib.rs new file mode 100644 index 0000000..f1fc607 --- /dev/null +++ b/tmpls/minijinja/src/lib.rs @@ -0,0 +1,43 @@ +use minijinja::{Environment, Error}; +use tmpls::{BigTable, Teams}; + +#[derive(Debug)] +pub struct Benchmark { + env: Environment<'static>, +} + +impl Default for Benchmark { + fn default() -> Self { + let mut env = Environment::new(); + env.add_template("big-table", include_str!("../templates/big-table.html")) + .unwrap(); + env.add_template("teams", include_str!("../templates/teams.html")) + .unwrap(); + Self { env } + } +} + +impl tmpls::Benchmark for Benchmark { + type Output = Vec; + type Error = Error; + + fn big_table( + &mut self, + output: &mut Self::Output, + input: &BigTable, + ) -> Result<(), Self::Error> { + self.env + .get_template("big-table") + .unwrap() + .render_to_write(input, output) + .map(|_| ()) + } + + fn teams(&mut self, output: &mut Self::Output, input: &Teams) -> Result<(), Self::Error> { + self.env + .get_template("teams") + .unwrap() + .render_to_write(input, output) + .map(|_| ()) + } +} diff --git a/tmpls/minijinja/templates b/tmpls/minijinja/templates new file mode 120000 index 0000000..8465398 --- /dev/null +++ b/tmpls/minijinja/templates @@ -0,0 +1 @@ +../rinja/templates/ \ No newline at end of file diff --git a/tmpls/rinja/Cargo.toml b/tmpls/rinja/Cargo.toml new file mode 100644 index 0000000..2ba320d --- /dev/null +++ b/tmpls/rinja/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "tmpl-rinja" +version = "0.1.0" +edition = "2021" +publish = false +license = "Apache-2.0" + +[dependencies] +tmpls = { version = "*", path = ".." } + +rinja = { version = "0.3.0", git = "https://github.com/rinja-rs/rinja", branch = "master" } diff --git a/tmpls/rinja/src/lib.rs b/tmpls/rinja/src/lib.rs new file mode 100644 index 0000000..6e7baa7 --- /dev/null +++ b/tmpls/rinja/src/lib.rs @@ -0,0 +1,50 @@ +use std::ops::Deref; + +use rinja::{Error, Template}; +use tmpls::{BigTable, Teams}; + +#[derive(Debug, Default)] +pub struct Benchmark; + +impl tmpls::Benchmark for Benchmark { + type Output = String; + type Error = Error; + + fn big_table( + &mut self, + output: &mut Self::Output, + input: &BigTable, + ) -> Result<(), Self::Error> { + #[derive(Template)] + #[template(path = "big-table.html")] + struct Tmpl<'a>(&'a BigTable); + + impl Deref for Tmpl<'_> { + type Target = BigTable; + + #[inline] + fn deref(&self) -> &Self::Target { + self.0 + } + } + + Tmpl(input).render_into(output) + } + + fn teams(&mut self, output: &mut Self::Output, input: &Teams) -> Result<(), Self::Error> { + #[derive(Template)] + #[template(path = "teams.html")] + struct Tmpl<'a>(&'a Teams); + + impl Deref for Tmpl<'_> { + type Target = Teams; + + #[inline] + fn deref(&self) -> &Self::Target { + self.0 + } + } + + Tmpl(input).render_into(output) + } +} diff --git a/tmpls/rinja/templates/big-table.html b/tmpls/rinja/templates/big-table.html new file mode 100644 index 0000000..60f566e --- /dev/null +++ b/tmpls/rinja/templates/big-table.html @@ -0,0 +1,9 @@ + + {%- for row in table -%} + + {%- for col in row -%} + + {%- endfor -%} + + {%- endfor -%} +
{{ col }}
diff --git a/tmpls/rinja/templates/teams.html b/tmpls/rinja/templates/teams.html new file mode 100644 index 0000000..4371da8 --- /dev/null +++ b/tmpls/rinja/templates/teams.html @@ -0,0 +1,15 @@ + + + {{ year }} + + +

CSL {{ year }}

+
    + {%- for team in teams %} +
  • + {{ team.name }}: {{ team.score }} +
  • + {%- endfor %} +
+ + diff --git a/tmpls/src/lib.rs b/tmpls/src/lib.rs new file mode 100644 index 0000000..7a8857b --- /dev/null +++ b/tmpls/src/lib.rs @@ -0,0 +1,59 @@ +use std::fmt::Debug; + +use serde::{Deserialize, Serialize}; + +pub trait Benchmark: Default { + type Output: Output; + type Error: std::error::Error; + + fn big_table(&mut self, output: &mut Self::Output, input: &BigTable) + -> Result<(), Self::Error>; + + fn teams(&mut self, output: &mut Self::Output, input: &Teams) -> Result<(), Self::Error>; +} + +#[derive(Debug, Clone, PartialEq, Hash, Serialize, Deserialize)] +pub struct BigTable { + pub table: Vec>, +} + +#[derive(Debug, Clone, PartialEq, Hash, Serialize, Deserialize)] +pub struct Teams { + pub year: u16, + pub teams: Vec, +} + +#[derive(Debug, Clone, PartialEq, Hash, Serialize, Deserialize)] +pub struct Team { + pub name: String, + pub score: u8, +} + +pub trait Output: Default { + fn clear(&mut self); + fn as_bytes(&self) -> &[u8]; +} + +impl Output for String { + #[inline] + fn clear(&mut self) { + self.clear(); + } + + #[inline] + fn as_bytes(&self) -> &[u8] { + self.as_bytes() + } +} + +impl Output for Vec { + #[inline] + fn clear(&mut self) { + self.clear(); + } + + #[inline] + fn as_bytes(&self) -> &[u8] { + self.as_slice() + } +} diff --git a/tmpls/tinytemplate/Cargo.toml b/tmpls/tinytemplate/Cargo.toml new file mode 100644 index 0000000..7ee96a1 --- /dev/null +++ b/tmpls/tinytemplate/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "tmpl-tinytemplate" +version = "0.1.0" +edition = "2021" +publish = false +license = "Apache-2.0" + +[dependencies] +tmpls = { version = "*", path = ".." } + +tinytemplate = "1.2.1" diff --git a/tmpls/tinytemplate/src/lib.rs b/tmpls/tinytemplate/src/lib.rs new file mode 100644 index 0000000..a409aa7 --- /dev/null +++ b/tmpls/tinytemplate/src/lib.rs @@ -0,0 +1,37 @@ +use tinytemplate::error::Error; +use tinytemplate::TinyTemplate; +use tmpls::{BigTable, Teams}; + +pub struct Benchmark { + tt: TinyTemplate<'static>, +} + +impl Default for Benchmark { + fn default() -> Self { + let mut tt = TinyTemplate::new(); + tt.add_template("big-table", include_str!("../templates/big-table.html")) + .unwrap(); + tt.add_template("teams", include_str!("../templates/teams.html")) + .unwrap(); + Self { tt } + } +} + +impl tmpls::Benchmark for Benchmark { + type Output = String; + type Error = Error; + + fn big_table( + &mut self, + output: &mut Self::Output, + input: &BigTable, + ) -> Result<(), Self::Error> { + output.push_str(&self.tt.render("big-table", input)?); + Ok(()) + } + + fn teams(&mut self, output: &mut Self::Output, input: &Teams) -> Result<(), Self::Error> { + output.push_str(&self.tt.render("teams", input)?); + Ok(()) + } +} diff --git a/tmpls/tinytemplate/templates/big-table.html b/tmpls/tinytemplate/templates/big-table.html new file mode 100644 index 0000000..26e7c09 --- /dev/null +++ b/tmpls/tinytemplate/templates/big-table.html @@ -0,0 +1,9 @@ + + {{ for row in table }} + + {{ for col in row }} + + {{ endfor }} + + {{ endfor }} +
{col}
diff --git a/tmpls/tinytemplate/templates/teams.html b/tmpls/tinytemplate/templates/teams.html new file mode 100644 index 0000000..90588cb --- /dev/null +++ b/tmpls/tinytemplate/templates/teams.html @@ -0,0 +1,15 @@ + + + {year} + + +

CSL {year}

+
    + {{ for team in teams }} +
  • + {team.name}: {team.score} +
  • + {{ endfor }} +
+ + diff --git a/tomlfmt.toml b/tomlfmt.toml new file mode 100644 index 0000000..7422276 --- /dev/null +++ b/tomlfmt.toml @@ -0,0 +1 @@ +table_order = ["workspace", "package", "badges", "lib", "features", "dependencies", "build-dependencies", "dev-dependencies", "bench"]