From 6488f3ddb7e0bc8f1661202575d3562c2b70f608 Mon Sep 17 00:00:00 2001 From: jutty Date: Mon, 9 Mar 2026 11:15:54 -0300 Subject: [PATCH] Embed default templates into the binary --- .justfile | 4 +- Cargo.lock | 10 ++-- Cargo.toml | 2 +- src/router/handlers/template.rs | 91 ++++++++++++++++++++++++++++++++- static/graph.toml | 5 +- 5 files changed, 100 insertions(+), 12 deletions(-) diff --git a/.justfile b/.justfile index b864e92..3e56196 100644 --- a/.justfile +++ b/.justfile @@ -170,7 +170,7 @@ tag: update echo "Last tag {{ last_tag }} and manifest ({{ manifest_version }}) already match" if [ "{{ last_tag }}" != "{{ tagged_latest }}" ]; then echo "Last tag {{ last_tag }} and 'latest' tag ({{ tagged_latest }}) diverge" - git tag latest "v{{ manifest_version }}" + git tag --force latest "v{{ manifest_version }}" {{ just_cmd }} version-assess fi exit @@ -180,7 +180,7 @@ tag: update fi git tag "v{{ manifest_version }}" HEAD - git tag latest "v{{ manifest_version }}" + git tag --force latest "v{{ manifest_version }}" {{ just_cmd }} version-assess # Verify and push diff --git a/Cargo.lock b/Cargo.lock index 1efe981..abbfc71 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -259,7 +259,7 @@ dependencies = [ [[package]] name = "en" -version = "0.1.0-alpha" +version = "0.2.0-alpha" dependencies = [ "axum", "serde", @@ -1476,18 +1476,18 @@ checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" [[package]] name = "zerocopy" -version = "0.8.41" +version = "0.8.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96e13bc581734df6250836c59a5f44f3c57db9f9acb9dc8e3eaabdaf6170254d" +checksum = "f2578b716f8a7a858b7f02d5bd870c14bf4ddbbcf3a4c05414ba6503640505e3" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.41" +version = "0.8.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3545ea9e86d12ab9bba9fcd99b54c1556fd3199007def5a03c375623d05fac1c" +checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 19bb40c..cd73715 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "en" -version = "0.1.0-alpha" +version = "0.2.0-alpha" description = "A non-linear writing instrument." license = "AGPL-3.0-only" diff --git a/src/router/handlers/template.rs b/src/router/handlers/template.rs index b7dee5e..53fe046 100644 --- a/src/router/handlers/template.rs +++ b/src/router/handlers/template.rs @@ -1,3 +1,5 @@ +use std::{collections::HashMap, fs, io::ErrorKind, path::PathBuf}; + use axum::{ body::Body, http::{Response, StatusCode, header}, @@ -101,6 +103,77 @@ impl std::fmt::Display for RenderingError { } } +static DEFAULTS: &[(&str, &str)] = &[ + ("base.html", include_str!("../../../templates/base.html")), + ("index.html", include_str!("../../../templates/index.html")), + ("about.html", include_str!("../../../templates/about.html")), + ("data.html", include_str!("../../../templates/data.html")), + ("empty.html", include_str!("../../../templates/empty.html")), + ("error.html", include_str!("../../../templates/error.html")), + ("node.html", include_str!("../../../templates/node.html")), + ("tree.html", include_str!("../../../templates/tree.html")), +]; + +fn read_template(name: &str, path: PathBuf) -> Result { + let defaults: HashMap<&str, &str> = DEFAULTS.iter().copied().collect(); + + match fs::read_to_string(path) { + Ok(content) => Ok(content), + Err(error) if error.kind() == ErrorKind::NotFound => { + match defaults.get(name) { + Some(default) => Ok(default.to_string()), + None => Err(error), + } + }, + Err(error) => Err(error), + } +} + +fn load_templates() -> Result { + let mut tera = tera::Tera::default(); + + let root = PathBuf::from("templates"); + let default_names: Vec<&str> = DEFAULTS.iter().map(|(n, _)| *n).collect(); + + match fs::read_dir(&root) { + Ok(dir) => { + for file_opt in dir { + let file = file_opt?; + let path = file.path(); + if path.is_file() { + if let Some(name) = path.clone().file_name() { + let Some(name_str) = name.to_str() else { + return Err(tera::Error::msg(format!( + "Template filename {} is not valid unicode", + name.display() + ))) + }; + if !default_names.contains(&name_str) { + tera.add_raw_template( + name_str, + &read_template(name_str, path)?, + )?; + } + } + } + } + }, + Err(error) => { + if error.kind() != ErrorKind::NotFound { + return Err(tera::Error::msg(error.to_string())) + } + }, + } + + for tuple in DEFAULTS { + let path = root.join(tuple.0); + let name = tuple.0; + tera.add_raw_template(name, &read_template(name, path)?)?; + } + + Ok(tera) +} + /// Renders a template into a String and error code. /// /// The template name **must not** contain the extension (e.g. `.html`). @@ -110,7 +183,7 @@ pub(in crate::router::handlers) fn render( error_message: Option, ) -> Result { let instant = now(); - let tera = match tera::Tera::new("./templates/**/*") { + let tera = match load_templates() { Ok(engine) => engine, Err(error) => { return Err(RenderingError::new( @@ -330,7 +403,7 @@ mod tests { fn render_bad_context() { let (body, status) = match render("node", &tera::Context::default(), None) { - Ok(rendered) => panic!("Got Ok, expected Error"), + Ok(_) => panic!("Got Ok, expected Error"), Err(error) => (error.template.html, error.template.code), }; assert!(body.matches("Template render failed.").count() > 0); @@ -344,4 +417,18 @@ mod tests { let html = emergency_wrap(&error, ""); assert!(html.matches(payload).count() == 1); } + + #[test] + fn default_templates_exist_and_match() { + let templates_dir = + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("templates"); + + for (map_name, map_contents) in DEFAULTS { + let path = templates_dir.join(map_name); + assert!(path.exists()); + assert!(path.is_file()); + let contents = fs::read_to_string(&path).unwrap(); + assert_eq!(&contents, map_contents); + } + } } diff --git a/static/graph.toml b/static/graph.toml index f9c29a8..ab3a5f2 100644 --- a/static/graph.toml +++ b/static/graph.toml @@ -856,7 +856,7 @@ text = """ - [ ] Most linked - [ ] Rendering - [ ] Sorting of tree, index list and drop-down navigation - - [ ] Alphabetic + - [x] Alphabetic - [ ] By most linked to - [ ] By most linked - [ ] Tree @@ -866,7 +866,8 @@ text = """ - [ ] Custom assets (favicon, CSS) - [x] Drop all hardcoded assets endpoints - [ ] Custom header include - - [ ] Custom templates + - [x] Custom templates + - [ ] user-supplied loading order (e.g. through filenames) - [ ] Themes - [x] Anchors and connections - [x] Render detached anchors differently