From 485abd8d8163196d7d0b0927aaff09350092c06b Mon Sep 17 00:00:00 2001
From: spencer
Date: Tue, 28 Apr 2026 16:16:59 -0400
Subject: [PATCH] add markdown template rendering
---
Cargo.lock | 289 +++++++++++++++++++++++++++
Cargo.toml | 2 +
src/functions.rs | 15 ++
src/main.rs | 33 ++-
static/en/content.md | 13 ++
static/en/test2.md | 2 +
templates/common.html | 4 +-
templates/en/homelab/index.html | 2 +
templates/en/index.html | 8 +-
templates/en/publications/index.html | 2 +
templates/en/resume/index.html | 3 +-
templates/fr/index.html | 2 +
templates/fr/resume/index.html | 6 +-
update_static.sh | 8 +
14 files changed, 371 insertions(+), 18 deletions(-)
create mode 100644 src/functions.rs
create mode 100644 static/en/content.md
create mode 100644 static/en/test2.md
create mode 100755 update_static.sh
diff --git a/Cargo.lock b/Cargo.lock
index d86062f..d3aa536 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -20,6 +20,23 @@ dependencies = [
"libc",
]
+[[package]]
+name = "arraydeque"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236"
+
+[[package]]
+name = "async-trait"
+version = "0.1.89"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
[[package]]
name = "atomic-waker"
version = "1.1.2"
@@ -89,6 +106,9 @@ name = "bitflags"
version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
+dependencies = [
+ "serde_core",
+]
[[package]]
name = "block-buffer"
@@ -170,6 +190,55 @@ dependencies = [
"phf_codegen",
]
+[[package]]
+name = "config"
+version = "0.15.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e68cfe19cd7d23ffde002c24ffa5cda73931913ef394d5eaaa32037dc940c0c"
+dependencies = [
+ "async-trait",
+ "convert_case",
+ "json5",
+ "pathdiff",
+ "ron",
+ "rust-ini",
+ "serde-untagged",
+ "serde_core",
+ "serde_json",
+ "toml",
+ "winnow",
+ "yaml-rust2",
+]
+
+[[package]]
+name = "const-random"
+version = "0.1.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359"
+dependencies = [
+ "const-random-macro",
+]
+
+[[package]]
+name = "const-random-macro"
+version = "0.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e"
+dependencies = [
+ "getrandom",
+ "once_cell",
+ "tiny-keccak",
+]
+
+[[package]]
+name = "convert_case"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca"
+dependencies = [
+ "unicode-segmentation",
+]
+
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
@@ -210,6 +279,12 @@ version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
+[[package]]
+name = "crunchy"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
+
[[package]]
name = "crypto-common"
version = "0.1.7"
@@ -236,6 +311,35 @@ dependencies = [
"crypto-common",
]
+[[package]]
+name = "dlv-list"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f"
+dependencies = [
+ "const-random",
+]
+
+[[package]]
+name = "encoding_rs"
+version = "0.8.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "erased-serde"
+version = "0.4.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2add8a07dd6a8d93ff627029c51de145e12686fbc36ecb298ac22e74cf02dec"
+dependencies = [
+ "serde",
+ "serde_core",
+ "typeid",
+]
+
[[package]]
name = "errno"
version = "0.3.14"
@@ -252,6 +356,12 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
+[[package]]
+name = "foldhash"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
+
[[package]]
name = "form_urlencoded"
version = "1.2.2"
@@ -345,6 +455,30 @@ dependencies = [
"walkdir",
]
+[[package]]
+name = "hashbrown"
+version = "0.14.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
+
+[[package]]
+name = "hashbrown"
+version = "0.15.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
+dependencies = [
+ "foldhash",
+]
+
+[[package]]
+name = "hashlink"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1"
+dependencies = [
+ "hashbrown 0.15.5",
+]
+
[[package]]
name = "http"
version = "1.4.0"
@@ -496,6 +630,17 @@ dependencies = [
"wasm-bindgen",
]
+[[package]]
+name = "json5"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1"
+dependencies = [
+ "pest",
+ "pest_derive",
+ "serde",
+]
+
[[package]]
name = "lazy_static"
version = "1.5.0"
@@ -529,6 +674,15 @@ version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
+[[package]]
+name = "markdown"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a5cab8f2cadc416a82d2e783a1946388b31654d391d1c7d92cc1f03e295b1deb"
+dependencies = [
+ "unicode-id",
+]
+
[[package]]
name = "matchit"
version = "0.8.4"
@@ -583,6 +737,16 @@ version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
+[[package]]
+name = "ordered-multimap"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79"
+dependencies = [
+ "dlv-list",
+ "hashbrown 0.14.5",
+]
+
[[package]]
name = "parking_lot"
version = "0.12.5"
@@ -615,6 +779,12 @@ dependencies = [
"regex",
]
+[[package]]
+name = "pathdiff"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3"
+
[[package]]
name = "percent-encoding"
version = "2.3.2"
@@ -803,6 +973,30 @@ version = "0.8.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
+[[package]]
+name = "ron"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4147b952f3f819eca0e99527022f7d6a8d05f111aeb0a62960c74eb283bec8fc"
+dependencies = [
+ "bitflags",
+ "once_cell",
+ "serde",
+ "serde_derive",
+ "typeid",
+ "unicode-ident",
+]
+
+[[package]]
+name = "rust-ini"
+version = "0.21.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "796e8d2b6696392a43bea58116b667fb4c29727dc5abd27d6acf338bb4f688c7"
+dependencies = [
+ "cfg-if",
+ "ordered-multimap",
+]
+
[[package]]
name = "rustversion"
version = "1.0.22"
@@ -839,6 +1033,18 @@ dependencies = [
"serde_core",
]
+[[package]]
+name = "serde-untagged"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9faf48a4a2d2693be24c6289dbe26552776eb7737074e6722891fadbe6c5058"
+dependencies = [
+ "erased-serde",
+ "serde",
+ "serde_core",
+ "typeid",
+]
+
[[package]]
name = "serde_core"
version = "1.0.228"
@@ -883,6 +1089,15 @@ dependencies = [
"serde_core",
]
+[[package]]
+name = "serde_spanned"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26"
+dependencies = [
+ "serde_core",
+]
+
[[package]]
name = "serde_urlencoded"
version = "0.7.1"
@@ -999,6 +1214,15 @@ dependencies = [
"unicode-segmentation",
]
+[[package]]
+name = "tiny-keccak"
+version = "2.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
+dependencies = [
+ "crunchy",
+]
+
[[package]]
name = "tokio"
version = "1.52.1"
@@ -1040,6 +1264,37 @@ dependencies = [
"tokio",
]
+[[package]]
+name = "toml"
+version = "1.1.2+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee"
+dependencies = [
+ "serde_core",
+ "serde_spanned",
+ "toml_datetime",
+ "toml_parser",
+ "winnow",
+]
+
+[[package]]
+name = "toml_datetime"
+version = "1.1.1+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7"
+dependencies = [
+ "serde_core",
+]
+
+[[package]]
+name = "toml_parser"
+version = "1.1.2+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526"
+dependencies = [
+ "winnow",
+]
+
[[package]]
name = "tower"
version = "0.5.3"
@@ -1114,6 +1369,12 @@ dependencies = [
"once_cell",
]
+[[package]]
+name = "typeid"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c"
+
[[package]]
name = "typenum"
version = "1.20.0"
@@ -1132,6 +1393,12 @@ version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142"
+[[package]]
+name = "unicode-id"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70ba288e709927c043cbe476718d37be306be53fb1fafecd0dbe36d072be2580"
+
[[package]]
name = "unicode-ident"
version = "1.0.24"
@@ -1216,7 +1483,9 @@ name = "website"
version = "0.1.0"
dependencies = [
"axum",
+ "config",
"lazy_static",
+ "markdown",
"tera",
"tokio",
"tower-http",
@@ -1299,6 +1568,26 @@ dependencies = [
"windows-link",
]
+[[package]]
+name = "winnow"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "yaml-rust2"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2462ea039c445496d8793d052e13787f2b90e750b833afee748e601c17621ed9"
+dependencies = [
+ "arraydeque",
+ "encoding_rs",
+ "hashlink",
+]
+
[[package]]
name = "zerocopy"
version = "0.8.48"
diff --git a/Cargo.toml b/Cargo.toml
index 440cb86..6b5b2bc 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -5,7 +5,9 @@ edition = "2021"
[dependencies]
axum = "0.8.9"
+config = "0.15.22"
lazy_static = "1.5.0"
+markdown = "1.0.0"
tera = "1.20.1"
tokio = { version = "1", features = ["full"] }
tower-http = { version = "0.6.8", features = ["fs"] }
diff --git a/src/functions.rs b/src/functions.rs
new file mode 100644
index 0000000..8a2b54b
--- /dev/null
+++ b/src/functions.rs
@@ -0,0 +1,15 @@
+use std::collections::HashMap;
+use tera::{
+ Value, to_value, try_get_value
+};
+
+pub fn load_markdown(args: &HashMap) -> tera::Result {
+ let value = args.get("path").ok_or("path not provided")?;
+ let path = try_get_value!("load_markdown", "path", String, value);
+ let content = std::fs::read_to_string(format!("static/{}", path))?;
+
+ let html = markdown::to_html(&content);
+ dbg!(&html);
+ Ok(to_value(&html)?)
+}
+
diff --git a/src/main.rs b/src/main.rs
index e037ba1..a231be7 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,3 +1,5 @@
+pub mod functions;
+
use std::collections::HashMap;
use tera::{Context, Tera};
use axum::{
@@ -16,7 +18,11 @@ use axum::{
use tower_http::services::{ServeDir, ServeFile};
lazy_static::lazy_static! {
- pub static ref TEMPLATES: Tera = Tera::new("templates/**/*").expect("failed to parse templates");
+ pub static ref TEMPLATES: Tera = {
+ let mut tera = Tera::new("templates/**/*").expect("failed to parse templates");
+ tera.register_function("load_markdown", functions::load_markdown);
+ tera
+ };
}
#[tokio::main]
@@ -51,15 +57,20 @@ async fn render_template(Path(params): Path>) -> Result Ok(Html(html)),
Err(e) => {
eprintln!("Tera error: {}", e);
@@ -67,3 +78,13 @@ async fn render_template(Path(params): Path>) -> Result std::path::PathBuf {
+ let mut pb = std::path::PathBuf::from(input.clone());
+ if !input.ends_with("index.html") {
+ pb.push("index.html");
+ }
+ pb
+}
diff --git a/static/en/content.md b/static/en/content.md
new file mode 100644
index 0000000..5cd7434
--- /dev/null
+++ b/static/en/content.md
@@ -0,0 +1,13 @@
+## Home Page — Spencer Whitehead
+
+Pour accéder à la version française, veuillez cliquer [ici](/fr/).
+
+This is the personal website of Spencer Whitehead. I am a fourth-year Ph.D. candidate at Duke University, expecting to graduate in early 2027. My primary research interests are in gauge theory, four- and eight-dimensional differential geometry, and the applications of computer algebra systems to solving problems in geometry.
+
+[Resume](resume)
+
+[Publications](publications)
+
+[Homelab](homelab)
+
+[View the code for this website](https://git.touhou.ca/spencer/website)
diff --git a/static/en/test2.md b/static/en/test2.md
new file mode 100644
index 0000000..897b9f3
--- /dev/null
+++ b/static/en/test2.md
@@ -0,0 +1,2 @@
+* dynamically scoped
+* very cool
diff --git a/templates/common.html b/templates/common.html
index 02fe8dc..8966e17 100644
--- a/templates/common.html
+++ b/templates/common.html
@@ -6,7 +6,9 @@
{{ title }}
-
+
{% endmacro %}
diff --git a/templates/en/homelab/index.html b/templates/en/homelab/index.html
index 7f8e97e..12ac43f 100644
--- a/templates/en/homelab/index.html
+++ b/templates/en/homelab/index.html
@@ -3,6 +3,7 @@
{{ common::nav() }}
Homelab
+
The website you are currently viewing is being served by my personal computing homelab, a cluster of three nodes orchestrated by k3s.
crusader
@@ -20,4 +21,5 @@
blackprince
blackprince was built in 2025. It has an AMD Ryzen 7 7700XD chip, an Nvidia GeForce 1080 GPU, and 64GB of DDR5 3200 RAM, running Linux Mint 22.
It is primarily used as a personal computing node, and for running more intense computations for research purposes.
+
diff --git a/templates/en/index.html b/templates/en/index.html
index 77423eb..faf12f2 100644
--- a/templates/en/index.html
+++ b/templates/en/index.html
@@ -1,11 +1,5 @@
{% import "common.html" as common %}
{{ common::meta(title="Home Page", lang="en") }}
- Home Page — Spencer Whitehead
- Pour accéder à la version française, veuillez cliquer ici.
- This is the personal website of Spencer Whitehead. I am a fourth-year Ph.D. candidate at Duke University, expecting to graduate in early 2027.
- My primary research interests are in gauge theory, four- and eight-dimensional differential geometry, and the applications of computer algebra systems to solving problems in geometry.
- Resume
- Publications
- Homelab
+ {{ load_markdown(path=path ~ "content.md") | safe }}
diff --git a/templates/en/publications/index.html b/templates/en/publications/index.html
index 53a76d4..b9dc86e 100644
--- a/templates/en/publications/index.html
+++ b/templates/en/publications/index.html
@@ -3,6 +3,7 @@
{{ common::nav() }}
Publications
+
This page contains a list of my published (scholarly) works with plain-language descriptions of the results.
On a smoothness characterization for good moduli spaces , joint with Dan Edidin and Matt Satriano, published in Advances in Mathematics in April 2024.
@@ -38,5 +39,6 @@
Any time one builds a four-dimensional object in three-dimensions, it is necessary to "project a dimension away": for example, by placing the skeleton of a three-dimensional cube in front of a light source, one can project it to a two-dimensional image on a wall.
Not all projections are equal: some (like the stereographic projection distort lengths, while others (such as the orthogonal projection, where we imagine an infinitely strong light source placed infinitely far away) may cause edges to cross each other. In this project, we created a software package in Maple to determine what projections could actually be constructed in the Zometool building system, and to generate plans for building complicated geometric projections in Zometool. We focused especially on a class of shapes called Wythoff constructions , which are the result of looking at images of a point in a system of mirrors called a kaleidoscope.
+
diff --git a/templates/en/resume/index.html b/templates/en/resume/index.html
index 944c664..2612eb3 100644
--- a/templates/en/resume/index.html
+++ b/templates/en/resume/index.html
@@ -11,6 +11,7 @@
+
Education
@@ -148,7 +149,7 @@
-
+