Make the generated HTML templatable

Use Handlebars to generate the HTML output, to make it easier to change.

Signed-off-by: Gergely Nagy <me@gergo.csillger.hu>
This commit is contained in:
Gergely Nagy 2025-01-28 08:50:22 +01:00
parent b66a60ab30
commit 5d60b84300
No known key found for this signature in database
7 changed files with 342 additions and 11 deletions

264
Cargo.lock generated
View file

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

View file

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

View file

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

View file

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

View file

@ -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<Self> {
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<String>,
pub links: Vec<Link<'a>>,
}
// 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!("<p>{}</p>", join_words(words))
join_words(words)
})
.collect::<Vec<String>>()
.join("");
.collect::<Vec<String>>();
// 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!("<li><a href=\"{}/\">{}</a></li>", 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!(
"<!doctype html><html><head><title>{}</title></head><body>{}{}<ul>{}</ul></body></html>",
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::<Asset>(".hbs")
.unwrap();
}
let data = Page {
request_uri: path,
backlink,
garbage: markov,
links,
};
Html(handlebars.render("main", &data).unwrap())
}
async fn poison_root(

View file

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

17
templates/main.hbs Normal file
View file

@ -0,0 +1,17 @@
<!doctype html>
<html>
<head>
<title>{{ request_uri }}</title>
</head>
<body>
{{{ backlink }}}
{{#each garbage}}
<p>{{{ this }}}</p>
{{/each}}
<ul>
{{#each links}}
<li><a href=\"{{ this.href }}\">{{ this.title }}</a></li>
{{/each}}
</ul>
</body>
</html>