From a01cda456640f6ea8bd4ea6a618cb801062d2785 Mon Sep 17 00:00:00 2001 From: charles Date: Wed, 17 Dec 2025 22:53:23 -0800 Subject: [PATCH] add: support a template.html file --- Cargo.lock | 25 +++++++++++++++++++ Cargo.toml | 2 ++ README.md | 3 +++ src/main.rs | 69 ++++++++++++++++++++++++++++++++++++++++++++++------- 4 files changed, 90 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1be8339..a59bc2b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -631,6 +631,12 @@ version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +[[package]] +name = "memo-map" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d1115007560874e373613744c6fba374c17688327a71c1476d1a5954cc857b" + [[package]] name = "mime" version = "0.3.17" @@ -647,6 +653,17 @@ dependencies = [ "unicase", ] +[[package]] +name = "minijinja" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12ea9ac0a51fb5112607099560fdf0f90366ab088a2a9e6e8ae176794e9806aa" +dependencies = [ + "memo-map", + "self_cell", + "serde", +] + [[package]] name = "multipart" version = "0.18.0" @@ -841,7 +858,9 @@ dependencies = [ "clap", "git2", "markdown", + "minijinja", "rouille", + "serde", ] [[package]] @@ -899,6 +918,12 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" +[[package]] +name = "self_cell" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16c2f82143577edb4921b71ede051dac62ca3c16084e918bf7b40c96ae10eb33" + [[package]] name = "serde" version = "1.0.228" diff --git a/Cargo.toml b/Cargo.toml index 77183ce..566a23d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,3 +8,5 @@ clap = { version = "4.5.4", features = ["derive"] } git2 = "0.20.3" markdown = "1.0.0" rouille = "3.6.2" +minijinja = { version = "2.1.0", features = ["loader"] } +serde = { version = "1.0.203", features = ["derive"] } diff --git a/README.md b/README.md index 355e15f..d658220 100644 --- a/README.md +++ b/README.md @@ -16,3 +16,6 @@ The arguments supported include: - --git-repo the URL of the git repo to load - --listen address to listen on, in the form of 0.0.0.0:8080 + +Additionally, if there is a './template.html' file in the repo, is will be used to wrap +the generated HTML. Specifically, the 'body' block will be replaced by the markdown content. diff --git a/src/main.rs b/src/main.rs index bda18ff..e545546 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,12 @@ use clap::Parser; use git2::Repository; +use minijinja::Environment; + use rouille::Response; +use serde::Serialize; use std::env; use std::fs; -use std::path::Path; +use std::path::{Path, PathBuf}; /// A Rust-based wiki with markdown. #[derive(Parser, Debug)] @@ -18,12 +21,60 @@ struct Args { listen: String, } -fn render_markdown(path: &Path) -> Response { - match fs::read_to_string(path) { - Ok(md) => Response::html(markdown::to_html(&md)), +#[derive(Serialize)] +struct Context {} + +fn render_page(repo_path: &PathBuf, path: &Path) -> Response { + let md = match fs::read_to_string(path) { + Ok(md) => md, Err(e) => { eprintln!("Error reading file {:?}: {}", path, e); - Response::text("Error reading file").with_status_code(500) + return Response::text("Error reading file").with_status_code(500); + } + }; + + let body_html = markdown::to_html(&md); + let template_path = repo_path.join("template.html"); + + if !template_path.is_file() { + return Response::html(body_html); + } + + let template_str = match fs::read_to_string(&template_path) { + Ok(s) => s, + Err(e) => { + eprintln!("Error reading template file {:?}: {}", &template_path, e); + // Fallback to no template + return Response::html(body_html); + } + }; + + let context = Context {}; + let mut env = Environment::new(); + if let Err(e) = env.add_template("template", &template_str) { + eprintln!("Error adding template: {}", e); + return Response::text("Error rendering template").with_status_code(500); + }; + let content = format!( + r#" +{{% extends 'template' %}} +{{% block body %}} +{} +{{% endblock %}} +"#, + body_html + ); + if let Err(e) = env.add_template("content", &content) { + eprintln!("Error adding template: {}", e); + return Response::text("Error rendering populated template").with_status_code(500); + } + let tmpl = env.get_template("content").unwrap(); + + match tmpl.render(context) { + Ok(rendered) => Response::html(rendered), + Err(e) => { + eprintln!("Error rendering template: {}", e); + Response::text("Error rendering template").with_status_code(500) } } } @@ -52,18 +103,18 @@ fn main() { if requested_path.is_dir() { let index_path = requested_path.join("index.md"); if index_path.is_file() { - return render_markdown(&index_path); + return render_page(&repo_path, &index_path); } let readme_path = requested_path.join("README.md"); if readme_path.is_file() { - return render_markdown(&readme_path); + return render_page(&repo_path, &readme_path); } } // Check if the path as-is is a file. e.g. /README.md if requested_path.is_file() { if requested_path.extension().and_then(std::ffi::OsStr::to_str) == Some("md") { - return render_markdown(&requested_path); + return render_page(&repo_path, &requested_path); } // For now, 404 on other file types. A real implementation might serve static files. return Response::empty_404(); @@ -73,7 +124,7 @@ fn main() { let mut md_path = requested_path; md_path.set_extension("md"); if md_path.is_file() { - return render_markdown(&md_path); + return render_page(&repo_path, &md_path); } Response::empty_404()