From 5d60b843009c6b6b869fb89eeff960dd4cf872dd Mon Sep 17 00:00:00 2001 From: Gergely Nagy Date: Tue, 28 Jan 2025 08:50:22 +0100 Subject: [PATCH] Make the generated HTML templatable Use Handlebars to generate the HTML output, to make it easier to change. Signed-off-by: Gergely Nagy --- Cargo.lock | 264 ++++++++++++++++++++++++++++++++++ Cargo.toml | 2 + REUSE.toml | 2 +- docs/content/configuration.md | 7 +- src/iocaine/app.rs | 54 +++++-- src/iocaine/config.rs | 7 + templates/main.hbs | 17 +++ 7 files changed, 342 insertions(+), 11 deletions(-) create mode 100644 templates/main.hbs diff --git a/Cargo.lock b/Cargo.lock index 7b091e0..cca87f8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -278,6 +278,16 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bstr" +version = "1.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "bytemuck" version = "1.20.0" @@ -430,6 +440,72 @@ dependencies = [ "typenum", ] +[[package]] +name = "darling" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[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", +] + +[[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", +] + [[package]] name = "digest" version = "0.10.7" @@ -566,6 +642,19 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +[[package]] +name = "globset" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15f1ce686646e7f1e19bf7d5533fe443a45dbfb990e00629110797578b42fb19" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", +] + [[package]] name = "h2" version = "0.4.7" @@ -585,6 +674,24 @@ dependencies = [ "tracing", ] +[[package]] +name = "handlebars" +version = "6.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6b224b95c1e668ac0270325ad563b2eef1469fbbb8959bc7c692c844b813d9" +dependencies = [ + "derive_builder", + "log", + "num-order", + "pest", + "pest_derive", + "rust-embed", + "serde", + "serde_json", + "thiserror", + "walkdir", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -721,6 +828,12 @@ dependencies = [ "tracing", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "indexmap" version = "1.9.3" @@ -757,8 +870,10 @@ dependencies = [ "console-subscriber", "figment", "figment_file_provider_adapter", + "handlebars", "rand", "rand_chacha", + "rust-embed", "serde", "sha2", "tokio", @@ -885,6 +1000,21 @@ dependencies = [ "winapi", ] +[[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" @@ -944,6 +1074,51 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "pest" +version = "2.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "816518421cfc6887a0d62bf441b6ffb4536fcc926395a69e1a85852d4363f57e" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d1396fd3a870fc7838768d171b4616d5c91f6cc25e377b673d714567d99377b" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pest_meta" +version = "2.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1e58089ea25d717bfd31fb534e4f3afcc2cc569c70de3e239778991ea3b7dea" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + [[package]] name = "pin-project" version = "1.1.7" @@ -1122,6 +1297,41 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "rust-embed" +version = "8.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa66af4a4fdd5e7ebc276f115e895611a34739a9c1c01028383d612d550953c0" +dependencies = [ + "rust-embed-impl", + "rust-embed-utils", + "walkdir", +] + +[[package]] +name = "rust-embed-impl" +version = "8.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6125dbc8867951125eec87294137f4e9c2c96566e61bf72c45095a7c77761478" +dependencies = [ + "proc-macro2", + "quote", + "rust-embed-utils", + "syn", + "walkdir", +] + +[[package]] +name = "rust-embed-utils" +version = "8.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e5347777e9aacb56039b0e1f28785929a8a3b709e87482e7442c72e7c12529d" +dependencies = [ + "globset", + "sha2", + "walkdir", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -1140,6 +1350,15 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "serde" version = "1.0.217" @@ -1280,6 +1499,26 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +[[package]] +name = "thiserror" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "thread_local" version = "1.1.8" @@ -1558,6 +1797,12 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + [[package]] name = "uncased" version = "0.9.10" @@ -1591,6 +1836,16 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" @@ -1622,6 +1877,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index 6347bdb..ca41ee1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,8 +19,10 @@ console-subscriber = { version = "0.4.1", optional = true } figment = { version = "0.10.19", features = ["toml", "env"] } figment_file_provider_adapter = "0.1.1" + handlebars = { version = "6.3.0", features = ["dir_source", "rust-embed"] } rand = "0.8.5" rand_chacha = "0.3.1" + rust-embed = "8.5.0" serde = { version = "1.0.217", features = ["derive"] } sha2 = "0.10.8" tokio = { version = "1.42.0", features = [ diff --git a/REUSE.toml b/REUSE.toml index 5df321d..e9e8d98 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -21,7 +21,7 @@ SPDX-PackageDownloadLocation = "https://git.madhouse-project.org/algernon/iocain SPDX-License-Identifier = "MIT" [[annotations]] - path = ["docs/templates/**", "docs/sass/**"] + path = ["docs/templates/**", "docs/sass/**", "templates/*.hbs"] precedence = "aggregate" SPDX-FileCopyrightText = "2025 Gergely Nagy" SPDX-License-Identifier = "MIT" diff --git a/docs/content/configuration.md b/docs/content/configuration.md index 23b2f62..13086ae 100644 --- a/docs/content/configuration.md +++ b/docs/content/configuration.md @@ -53,8 +53,13 @@ min = 2 max = 5 backlink = true +[generator.template] +#directory = + [generator] initial_seed = "" ``` -When configuring through environment variables, these settings are available via `IOCAINE__GENERATOR__MARKOV__PARAGRAPHS__MIN`, `IOCAINE__GENERATOR__MARKOV__PARAGRAPHS_MAX`, `IOCAINE__GENERATOR__MARKOV__WORDS__MIN`, `IOCAINE__GENERATOR__MARKOV__WORDS__MAX`, `IOCAINE__GENERATOR__LINKS__MIN`, `IOCAINE__GENERATOR__LINKS__MAX`, and `IOCAINE__GENERATOR__LINKS__BACKLINK`, `IOCAINE__GENERATOR__INITIAL_SEED` respectively. +The `[generator.template].directory` property can be set to a directory containing custom templates. If not set (the default), `iocaine` will use its own [default template](https://git.madhouse-project.org/algernon/iocaine/src/branch/main/templates/main.hbs). If configured, the directory **must** contain a `main.hbs` file, which will be used as the template for all generated pages. See the default template for hints, for now. + +When configuring through environment variables, these settings are available via `IOCAINE__GENERATOR__MARKOV__PARAGRAPHS__MIN`, `IOCAINE__GENERATOR__MARKOV__PARAGRAPHS_MAX`, `IOCAINE__GENERATOR__MARKOV__WORDS__MIN`, `IOCAINE__GENERATOR__MARKOV__WORDS__MAX`, `IOCAINE__GENERATOR__LINKS__MIN`, `IOCAINE__GENERATOR__LINKS__MAX`, and `IOCAINE__GENERATOR__LINKS__BACKLINK`, `IOCAINE__GENERATOR__TEMPLATE__DIRECTORY`, `IOCAINE__GENERATOR__INITIAL_SEED`, respectively. diff --git a/src/iocaine/app.rs b/src/iocaine/app.rs index f92241a..d621bba 100644 --- a/src/iocaine/app.rs +++ b/src/iocaine/app.rs @@ -10,7 +10,10 @@ use axum::{ routing::get, Router, }; +use handlebars::Handlebars; use rand::{seq::SliceRandom, Rng}; +use rust_embed::Embed; +use serde::Serialize; use crate::iocaine::{ config::Config, @@ -26,6 +29,11 @@ pub struct Iocaine { words: GargleBargle, } +#[derive(Embed)] +#[folder = "templates/"] +#[include = "*.hbs"] +struct Asset; + impl Iocaine { pub fn new(config: Config) -> Result { let mut chain = WurstsalatGeneratorPro::new(); @@ -87,6 +95,20 @@ fn poison(iocaine: &Iocaine, headers: axum::http::HeaderMap, path: &str) -> Html .unwrap(); let initial_seed = &iocaine.config.generator.initial_seed; + #[derive(Serialize)] + struct Link<'a> { + href: &'a str, + title: String, + } + + #[derive(Serialize)] + struct Page<'a> { + pub request_uri: &'a str, + pub backlink: &'static str, + pub garbage: Vec, + pub links: Vec>, + } + // Generate paragraphs let mut rng = GobbledyGook::for_url(format!("markov://{}/{}#{}", host, path, &initial_seed)); @@ -116,14 +138,13 @@ fn poison(iocaine: &Iocaine, headers: axum::http::HeaderMap, path: &str) -> Html .clone() .skip(i * iocaine.config.generator.markov.words.max as usize) .take(word_count as usize); - format!("

{}

", join_words(words)) + join_words(words) }) - .collect::>() - .join(""); + .collect::>(); // Generate links - let mut links = String::new(); + let mut links = Vec::new(); let rng = GobbledyGook::for_url(format!("titles://{}/{}#{}", host, path, initial_seed)); let titles = iocaine .chain @@ -139,7 +160,7 @@ fn poison(iocaine: &Iocaine, headers: axum::http::HeaderMap, path: &str) -> Html let title = titles.clone().skip(i as usize * 5).take(5); let title = join_words(title); - links.push_str(&format!("
  • {}
  • ", word, title)) + links.push(Link { href: word, title }); } let backlink = if iocaine.config.generator.links.backlink { @@ -148,10 +169,25 @@ fn poison(iocaine: &Iocaine, headers: axum::http::HeaderMap, path: &str) -> Html "" }; - Html(format!( - "{}{}{}
      {}
    ", - path, backlink, markov, links - )) + let mut handlebars = Handlebars::new(); + if let Some(dir) = &iocaine.config.generator.template.directory { + handlebars + .register_templates_directory(dir, handlebars::DirectorySourceOptions::default()) + .unwrap(); + } else { + handlebars + .register_embed_templates_with_extension::(".hbs") + .unwrap(); + } + + let data = Page { + request_uri: path, + backlink, + garbage: markov, + links, + }; + + Html(handlebars.render("main", &data).unwrap()) } async fn poison_root( diff --git a/src/iocaine/config.rs b/src/iocaine/config.rs index 336ecb3..e0cdc39 100644 --- a/src/iocaine/config.rs +++ b/src/iocaine/config.rs @@ -40,6 +40,8 @@ pub struct GeneratorConfig { pub links: LinkGeneratorConfig, #[serde(default)] pub initial_seed: String, + #[serde(default)] + pub template: GeneratorTemplateConfig, } #[derive(Clone, Debug, Deserialize, Default)] @@ -133,3 +135,8 @@ impl LinkGeneratorConfig { true } } + +#[derive(Clone, Debug, Deserialize, Default)] +pub struct GeneratorTemplateConfig { + pub directory: Option, +} diff --git a/templates/main.hbs b/templates/main.hbs new file mode 100644 index 0000000..ae7a149 --- /dev/null +++ b/templates/main.hbs @@ -0,0 +1,17 @@ + + + + {{ request_uri }} + + + {{{ backlink }}} + {{#each garbage}} +

    {{{ this }}}

    + {{/each}} + + +