From 2504cb1bb1353fae25de2d5ac8448f9c4ac97b8b Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 4 Mar 2025 22:35:07 +0100 Subject: [PATCH 1/3] replace handlebars with tera --- Cargo.lock | 277 ++++++++++++++++++++++++++++++++++++++++------------- Cargo.toml | 2 +- src/lib.rs | 88 ++++++++++------- 3 files changed, 270 insertions(+), 97 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 87b2c3d3a..ea6a0b3f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -203,7 +203,6 @@ dependencies = [ "color-eyre", "comrak", "eyre", - "handlebars", "lazy_static", "rayon", "regex", @@ -212,6 +211,7 @@ dependencies = [ "serde_derive", "serde_json", "serde_yaml", + "tera", ] [[package]] @@ -239,6 +239,16 @@ dependencies = [ "syn 2.0.98", ] +[[package]] +name = "bstr" +version = "1.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "build_html" version = "2.6.0" @@ -301,6 +311,28 @@ dependencies = [ "windows-link", ] +[[package]] +name = "chrono-tz" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93698b29de5e97ad0ae26447b344c482a7284c737d9ddc5f9e52b74a336671bb" +dependencies = [ + "chrono", + "chrono-tz-build", + "phf", +] + +[[package]] +name = "chrono-tz-build" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c088aee841df9c3041febbb73934cfc39708749bf96dc827e3359cd39ef11b1" +dependencies = [ + "parse-zoneinfo", + "phf", + "phf_codegen", +] + [[package]] name = "clap" version = "2.34.0" @@ -540,37 +572,6 @@ dependencies = [ "powerfmt", ] -[[package]] -name = "derive_builder" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" -dependencies = [ - "derive_builder_macro", -] - -[[package]] -name = "derive_builder_core" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "derive_builder_macro" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" -dependencies = [ - "derive_builder_core", - "syn 2.0.98", -] - [[package]] name = "deunicode" version = "1.6.0" @@ -772,6 +773,30 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +[[package]] +name = "globset" +version = "0.4.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54a1028dfc5f5df5da8a56a73e6c153c9a9708ec57232470703592a3f18e49f5" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "globwalk" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757" +dependencies = [ + "bitflags 2.9.0", + "ignore", + "walkdir", +] + [[package]] name = "h2" version = "0.3.26" @@ -791,23 +816,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "handlebars" -version = "6.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d752747ddabc4c1a70dd28e72f2e3c218a816773e0d7faf67433f1acfa6cba7c" -dependencies = [ - "derive_builder", - "log", - "num-order", - "pest", - "pest_derive", - "serde", - "serde_json", - "thiserror 2.0.11", - "walkdir", -] - [[package]] name = "hashbrown" version = "0.15.2" @@ -913,6 +921,15 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "humansize" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7" +dependencies = [ + "libm", +] + [[package]] name = "humantime" version = "2.1.0" @@ -1111,6 +1128,22 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "ignore" +version = "0.4.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b" +dependencies = [ + "crossbeam-deque", + "globset", + "log", + "memchr", + "regex-automata", + "same-file", + "walkdir", + "winapi-util", +] + [[package]] name = "indenter" version = "0.3.3" @@ -1161,6 +1194,12 @@ version = "0.2.170" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" +[[package]] +name = "libm" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" + [[package]] name = "linked-hash-map" version = "0.5.6" @@ -1276,21 +1315,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" -[[package]] -name = "num-modular" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17bb261bf36fa7d83f4c294f834e91256769097b3cb505d44831e0a179ac647f" - -[[package]] -name = "num-order" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "537b596b97c40fcf8056d153049eb22f481c17ebce72a513ec9286e4986d1bb6" -dependencies = [ - "num-modular", -] - [[package]] name = "num-traits" version = "0.2.19" @@ -1376,6 +1400,15 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "parse-zoneinfo" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f2a05b18d44e2957b88f96ba460715e295bc1d7510468a2f3d3b44535d26c24" +dependencies = [ + "regex", +] + [[package]] name = "pem" version = "3.0.5" @@ -1437,6 +1470,44 @@ dependencies = [ "sha2", ] +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project" version = "1.1.9" @@ -1926,6 +1997,12 @@ dependencies = [ "libc", ] +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + [[package]] name = "slab" version = "0.4.9" @@ -2071,6 +2148,28 @@ dependencies = [ "yaml-rust", ] +[[package]] +name = "tera" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab9d851b45e865f178319da0abdbfe6acbc4328759ff18dafc3a41c16b4cd2ee" +dependencies = [ + "chrono", + "chrono-tz", + "globwalk", + "humansize", + "lazy_static", + "percent-encoding", + "pest", + "pest_derive", + "rand", + "regex", + "serde", + "serde_json", + "slug", + "unic-segment", +] + [[package]] name = "terminal_size" version = "0.4.1" @@ -2351,6 +2450,56 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" +[[package]] +name = "unic-char-property" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" +dependencies = [ + "unic-char-range", +] + +[[package]] +name = "unic-char-range" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" + +[[package]] +name = "unic-common" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" + +[[package]] +name = "unic-segment" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4ed5d26be57f84f176157270c112ef57b86debac9cd21daaabbe56db0f88f23" +dependencies = [ + "unic-ucd-segment", +] + +[[package]] +name = "unic-ucd-segment" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2079c122a62205b421f499da10f3ee0f7697f012f55b675e002483c73ea34700" +dependencies = [ + "unic-char-property", + "unic-char-range", + "unic-ucd-version", +] + +[[package]] +name = "unic-ucd-version" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" +dependencies = [ + "unic-common", +] + [[package]] name = "unicase" version = "2.8.1" diff --git a/Cargo.toml b/Cargo.toml index 771a215e3..1b47803c0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,6 @@ edition = "2021" [dependencies] color-eyre = "=0.6.3" eyre = "=0.6.12" -handlebars = { version = "=6.3.1", features = ["dir_source"] } lazy_static = "=1.5.0" serde = "=1.0.218" serde_derive = "=1.0.218" @@ -18,6 +17,7 @@ rayon = "=1.10.0" regex = "=1.11.1" sass-rs = "=0.2.2" chrono = "=0.4.40" +tera = "=1.20.0" [workspace] members = ["serve"] diff --git a/src/lib.rs b/src/lib.rs index 4c110024f..01ceaf8dc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,17 +5,18 @@ use self::blogs::Blog; use self::posts::Post; use chrono::Timelike; use eyre::{eyre, WrapErr}; -use handlebars::{handlebars_helper, DirectorySourceOptions, Handlebars}; use rayon::prelude::*; use sass_rs::{compile_file, Options}; use serde_derive::Serialize; -use serde_json::json; +use serde_json::{json, Value}; +use std::collections::HashMap; use std::fs::{self, File}; use std::io::{self, Write}; use std::path::{Path, PathBuf}; +use tera::Tera; -struct Generator<'a> { - handlebars: Handlebars<'a>, +struct Generator { + tera: Tera, blogs: Vec, out_directory: PathBuf, } @@ -31,34 +32,56 @@ struct ReleasePost { title: String, url: String, } -handlebars_helper!(hb_month_name_helper: |month_num: u64| match month_num { - 1 => "Jan.", - 2 => "Feb.", - 3 => "Mar.", - 4 => "Apr.", - 5 => "May", - 6 => "June", - 7 => "July", - 8 => "Aug.", - 9 => "Sept.", - 10 => "Oct.", - 11 => "Nov.", - 12 => "Dec.", - _ => "Error!", -}); - -impl Generator<'_> { + +fn month_name(month_num: &Value, _args: &HashMap) -> tera::Result { + let month_num = month_num + .as_u64() + .expect("month_num should be an unsigned integer"); + let name = match month_num { + 1 => "Jan.", + 2 => "Feb.", + 3 => "Mar.", + 4 => "Apr.", + 5 => "May", + 6 => "June", + 7 => "July", + 8 => "Aug.", + 9 => "Sept.", + 10 => "Oct.", + 11 => "Nov.", + 12 => "Dec.", + _ => panic!("invalid month! ({month_num})"), + }; + Ok(name.into()) +} + +// Tera and Handlebars escape HTML differently by default. +// Tera: &<>"'/ +// Handlebars: &<>"'`= +// To make the transition testable, this function escapes just like Handlebars. +fn escape_hbs(input: &Value, _args: &HashMap) -> tera::Result { + let input = input.as_str().expect("input should be a string"); + Ok(input + .replace("&", "&") + .replace("<", "<") + .replace(">", ">") + .replace("\"", """) + .replace("'", "'") + .replace("`", "`") + .replace("=", "=") + .into()) +} + +impl Generator { fn new( out_directory: impl AsRef, posts_directory: impl AsRef, ) -> eyre::Result { - let mut handlebars = Handlebars::new(); - handlebars.set_strict_mode(true); - handlebars.register_templates_directory("templates", DirectorySourceOptions::default())?; - handlebars.register_helper("month_name", Box::new(hb_month_name_helper)); - + let mut tera = Tera::new("templates/*")?; + tera.register_filter("month_name", month_name); + tera.register_filter("escape_hbs", escape_hbs); Ok(Generator { - handlebars, + tera, blogs: self::blogs::load(posts_directory.as_ref())?, out_directory: out_directory.as_ref().into(), }) @@ -165,7 +188,7 @@ impl Generator<'_> { "root": blog.path_back_to_root(), }); let path = blog.prefix().join("index.html"); - self.render_template(&path, "index", data)?; + self.render_template(&path, "index.tera", data)?; Ok(path) } @@ -189,7 +212,7 @@ impl Generator<'_> { }); let path = path.join(filename); - self.render_template(&path, &post.layout, data)?; + self.render_template(&path, &format!("{}.tera", post.layout), data)?; Ok(path) } @@ -201,7 +224,7 @@ impl Generator<'_> { "feed_updated": chrono::Utc::now().with_nanosecond(0).unwrap().to_rfc3339(), }); - self.render_template(blog.prefix().join("feed.xml"), "feed", data)?; + self.render_template(blog.prefix().join("feed.xml"), "feed.tera", data)?; Ok(()) } @@ -242,11 +265,12 @@ impl Generator<'_> { &self, name: impl AsRef, template: &str, - data: serde_json::Value, + data: Value, ) -> eyre::Result<()> { let out_file = self.out_directory.join(name.as_ref()); let file = File::create(out_file)?; - self.handlebars.render_to_write(template, &data, file)?; + self.tera + .render_to(template, &tera::Context::from_value(data)?, file)?; Ok(()) } } From bfcf58af688407329e9d0acd30279c0ab1c2bd34 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Wed, 5 Mar 2025 01:46:38 +0100 Subject: [PATCH 2/3] rename templates from .hbs to .tera --- templates/{feed.hbs => feed.tera} | 0 templates/{footer.hbs => footer.tera} | 0 templates/{headers.hbs => headers.tera} | 0 templates/{index.hbs => index.tera} | 0 templates/{layout.hbs => layout.tera} | 0 templates/{nav.hbs => nav.tera} | 0 templates/{post.hbs => post.tera} | 0 7 files changed, 0 insertions(+), 0 deletions(-) rename templates/{feed.hbs => feed.tera} (100%) rename templates/{footer.hbs => footer.tera} (100%) rename templates/{headers.hbs => headers.tera} (100%) rename templates/{index.hbs => index.tera} (100%) rename templates/{layout.hbs => layout.tera} (100%) rename templates/{nav.hbs => nav.tera} (100%) rename templates/{post.hbs => post.tera} (100%) diff --git a/templates/feed.hbs b/templates/feed.tera similarity index 100% rename from templates/feed.hbs rename to templates/feed.tera diff --git a/templates/footer.hbs b/templates/footer.tera similarity index 100% rename from templates/footer.hbs rename to templates/footer.tera diff --git a/templates/headers.hbs b/templates/headers.tera similarity index 100% rename from templates/headers.hbs rename to templates/headers.tera diff --git a/templates/index.hbs b/templates/index.tera similarity index 100% rename from templates/index.hbs rename to templates/index.tera diff --git a/templates/layout.hbs b/templates/layout.tera similarity index 100% rename from templates/layout.hbs rename to templates/layout.tera diff --git a/templates/nav.hbs b/templates/nav.tera similarity index 100% rename from templates/nav.hbs rename to templates/nav.tera diff --git a/templates/post.hbs b/templates/post.tera similarity index 100% rename from templates/post.hbs rename to templates/post.tera From 289218f668bcd1006ee2cea9281ef563ba9019dd Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Sat, 8 Mar 2025 01:28:35 +0100 Subject: [PATCH 3/3] translate templates from handlebars to tera --- templates/feed.tera | 18 +++++++++--------- templates/footer.tera | 2 ++ templates/headers.tera | 6 ++++-- templates/index.tera | 28 ++++++++++++++-------------- templates/layout.tera | 13 ++++++++----- templates/nav.tera | 2 ++ templates/post.tera | 16 ++++++++-------- 7 files changed, 47 insertions(+), 38 deletions(-) diff --git a/templates/feed.tera b/templates/feed.tera index 2511a8d0d..235361025 100644 --- a/templates/feed.tera +++ b/templates/feed.tera @@ -12,18 +12,18 @@ {{feed_updated}} - {{#each posts}} + {% for post in posts %} - {{title}} - - {{published}} - {{updated}} - https://blog.rust-lang.org/{{../blog.prefix}}{{url}} - {{contents}} + {{post.title}} + + {{post.published}} + {{post.updated}} + https://blog.rust-lang.org/{{blog.prefix}}{{post.url}} + {{post.contents}} - {{author}} + {{post.author}} - {{/each}} + {% endfor %} diff --git a/templates/footer.tera b/templates/footer.tera index 30899b465..e5bd89fdc 100644 --- a/templates/footer.tera +++ b/templates/footer.tera @@ -1,3 +1,4 @@ +{% macro footer(root) -%}