diff --git a/Cargo.lock b/Cargo.lock
index 50fba3c..924f079 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -96,50 +96,6 @@ version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
-[[package]]
-name = "askama"
-version = "0.12.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b79091df18a97caea757e28cd2d5fda49c6cd4bd01ddffd7ff01ace0c0ad2c28"
-dependencies = [
- "askama_derive",
- "askama_escape",
- "humansize",
- "num-traits",
- "percent-encoding",
-]
-
-[[package]]
-name = "askama_derive"
-version = "0.12.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "19fe8d6cb13c4714962c072ea496f3392015f0989b1a2847bb4b2d9effd71d83"
-dependencies = [
- "askama_parser",
- "basic-toml",
- "mime",
- "mime_guess",
- "proc-macro2",
- "quote",
- "serde",
- "syn",
-]
-
-[[package]]
-name = "askama_escape"
-version = "0.10.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341"
-
-[[package]]
-name = "askama_parser"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "acb1161c6b64d1c3d83108213c2a2533a342ac225aabd0bda218278c2ddb00c0"
-dependencies = [
- "nom",
-]
-
[[package]]
name = "autocfg"
version = "1.4.0"
@@ -161,15 +117,6 @@ dependencies = [
"windows-targets",
]
-[[package]]
-name = "basic-toml"
-version = "0.1.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "823388e228f614e9558c6804262db37960ec8821856535f5c3f59913140558f8"
-dependencies = [
- "serde",
-]
-
[[package]]
name = "bitflags"
version = "1.3.2"
@@ -182,6 +129,25 @@ version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
+[[package]]
+name = "block-buffer"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "bstr"
+version = "1.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "786a307d683a5bf92e6fd5fd69a7eb613751668d1d8d67d802846dfe367c62c8"
+dependencies = [
+ "memchr",
+ "serde",
+]
+
[[package]]
name = "bumpalo"
version = "3.16.0"
@@ -230,6 +196,28 @@ dependencies = [
"windows-targets",
]
+[[package]]
+name = "chrono-tz"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93698b29de5e97ad0ae26447b344c482a7284c737d9ddc5f9e52b74a336671bb"
+dependencies = [
+ "chrono",
+ "chrono-tz-build",
+ "phf",
+]
+
+[[package]]
+name = "chrono-tz-build"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c088aee841df9c3041febbb73934cfc39708749bf96dc827e3359cd39ef11b1"
+dependencies = [
+ "parse-zoneinfo",
+ "phf",
+ "phf_codegen",
+]
+
[[package]]
name = "clap"
version = "4.5.23"
@@ -289,6 +277,50 @@ version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+[[package]]
+name = "cpufeatures"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crossbeam-deque"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
+dependencies = [
+ "crossbeam-epoch",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
+
+[[package]]
+name = "crypto-common"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
+dependencies = [
+ "generic-array",
+ "typenum",
+]
+
[[package]]
name = "debounced"
version = "0.2.0"
@@ -306,6 +338,22 @@ dependencies = [
"compute_graph",
]
+[[package]]
+name = "deunicode"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "339544cc9e2c4dc3fc7149fd630c5f22263a4fdf18a98afd0075784968b5cf00"
+
+[[package]]
+name = "digest"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+dependencies = [
+ "block-buffer",
+ "crypto-common",
+]
+
[[package]]
name = "displaydoc"
version = "0.2.5"
@@ -487,6 +535,16 @@ dependencies = [
"slab",
]
+[[package]]
+name = "generic-array"
+version = "0.14.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
[[package]]
name = "getopts"
version = "0.2.21"
@@ -513,6 +571,30 @@ 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",
+ "regex-syntax",
+]
+
+[[package]]
+name = "globwalk"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757"
+dependencies = [
+ "bitflags 2.6.0",
+ "ignore",
+ "walkdir",
+]
+
[[package]]
name = "hashbrown"
version = "0.14.5"
@@ -710,6 +792,22 @@ dependencies = [
"icu_properties",
]
+[[package]]
+name = "ignore"
+version = "0.4.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b"
+dependencies = [
+ "crossbeam-deque",
+ "globset",
+ "log",
+ "memchr",
+ "regex-automata",
+ "same-file",
+ "walkdir",
+ "winapi-util",
+]
+
[[package]]
name = "indexmap"
version = "2.5.0"
@@ -791,6 +889,12 @@ dependencies = [
"libc",
]
+[[package]]
+name = "lazy_static"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
+
[[package]]
name = "libc"
version = "0.2.169"
@@ -874,28 +978,6 @@ version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
-[[package]]
-name = "mime"
-version = "0.3.17"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
-
-[[package]]
-name = "mime_guess"
-version = "2.0.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e"
-dependencies = [
- "mime",
- "unicase",
-]
-
-[[package]]
-name = "minimal-lexical"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
-
[[package]]
name = "miniz_oxide"
version = "0.8.2"
@@ -923,16 +1005,6 @@ version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
-[[package]]
-name = "nom"
-version = "7.1.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
-dependencies = [
- "memchr",
- "minimal-lexical",
-]
-
[[package]]
name = "notify"
version = "7.0.0"
@@ -1008,12 +1080,66 @@ dependencies = [
"windows-targets",
]
+[[package]]
+name = "parse-zoneinfo"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f2a05b18d44e2957b88f96ba460715e295bc1d7510468a2f3d3b44535d26c24"
+dependencies = [
+ "regex",
+]
+
[[package]]
name = "percent-encoding"
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 = "petgraph"
version = "0.6.6"
@@ -1281,6 +1407,17 @@ dependencies = [
"serde",
]
+[[package]]
+name = "sha2"
+version = "0.10.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
[[package]]
name = "shlex"
version = "1.3.0"
@@ -1311,6 +1448,16 @@ dependencies = [
"autocfg",
]
+[[package]]
+name = "slug"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "882a80f72ee45de3cc9a5afeb2da0331d58df69e4e7d8eeb5d3c7784ae67e724"
+dependencies = [
+ "deunicode",
+ "wasm-bindgen",
+]
+
[[package]]
name = "smallvec"
version = "1.13.2"
@@ -1398,6 +1545,48 @@ dependencies = [
"utf-8",
]
+[[package]]
+name = "tera"
+version = "1.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab9d851b45e865f178319da0abdbfe6acbc4328759ff18dafc3a41c16b4cd2ee"
+dependencies = [
+ "chrono",
+ "chrono-tz",
+ "globwalk",
+ "humansize",
+ "lazy_static",
+ "percent-encoding",
+ "pest",
+ "pest_derive",
+ "rand",
+ "regex",
+ "serde",
+ "serde_json",
+ "slug",
+ "unic-segment",
+]
+
+[[package]]
+name = "thiserror"
+version = "2.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f072643fd0190df67a8bab670c20ef5d8737177d6ac6b2e9a236cb096206b2cc"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "2.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b50fa271071aae2e6ee85f842e2e28ba8cd2c5fb67f11fcb1fd70b276f9e7d4"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
[[package]]
name = "tinystr"
version = "0.7.6"
@@ -1497,6 +1686,68 @@ dependencies = [
"winnow",
]
+[[package]]
+name = "typenum"
+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 = "unic-char-property"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221"
+dependencies = [
+ "unic-char-range",
+]
+
+[[package]]
+name = "unic-char-range"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc"
+
+[[package]]
+name = "unic-common"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc"
+
+[[package]]
+name = "unic-segment"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e4ed5d26be57f84f176157270c112ef57b86debac9cd21daaabbe56db0f88f23"
+dependencies = [
+ "unic-ucd-segment",
+]
+
+[[package]]
+name = "unic-ucd-segment"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2079c122a62205b421f499da10f3ee0f7697f012f55b675e002483c73ea34700"
+dependencies = [
+ "unic-char-property",
+ "unic-char-range",
+ "unic-ucd-version",
+]
+
+[[package]]
+name = "unic-ucd-version"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4"
+dependencies = [
+ "unic-common",
+]
+
[[package]]
name = "unicase"
version = "2.8.1"
@@ -1564,7 +1815,6 @@ name = "v7"
version = "0.1.0"
dependencies = [
"anyhow",
- "askama",
"chrono",
"clap",
"compute_graph",
@@ -1580,6 +1830,7 @@ dependencies = [
"regex",
"serde",
"serde_json",
+ "tera",
"tokio",
"tokio-stream",
"toml",
@@ -1587,6 +1838,12 @@ dependencies = [
"url",
]
+[[package]]
+name = "version_check"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
+
[[package]]
name = "walkdir"
version = "2.5.0"
diff --git a/Cargo.toml b/Cargo.toml
index dfa420e..7ca9bb1 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -17,7 +17,6 @@ serde_json = "1.0"
[dependencies]
anyhow = "1.0.95"
-askama = "0.12.1"
chrono = { version = "0.4.39", features = ["serde"] }
clap = { version = "4.5.23", features = ["cargo"] }
compute_graph = { path = "crates/compute_graph" }
@@ -32,6 +31,7 @@ once_cell = "1.20.2"
pulldown-cmark = "0.12.2"
regex = "1.11.1"
serde = { version = "1.0", features = ["derive"] }
+tera = "1.20.0"
tokio = { version = "1.42.0", features = ["full"] }
tokio-stream = "0.1.17"
toml = "0.8.19"
diff --git a/askama.toml b/askama.toml
deleted file mode 100644
index 7efad7e..0000000
--- a/askama.toml
+++ /dev/null
@@ -1,2 +0,0 @@
-[general]
-dirs = ["site_test"]
diff --git a/site_test/archive.html b/site_test/archive.html
index bf20e19..c6f0f5b 100644
--- a/site_test/archive.html
+++ b/site_test/archive.html
@@ -1,13 +1,15 @@
-{% extends "layout/default.html" %}
+{% extends "default" %}
-{% block title %}Archive{% endblock %}
+{% block titlevariable %}
+{% set title = "Archive" %}
+{% endblock %}
{% block content -%}
-{% for year in self.years() %}
+{% for year in years %}
{{ year }}
- {% for entry in self.posts_for_year(year) %}
+ {% for entry in posts_by_year[year] %}
-
{{ entry.title }}
diff --git a/site_test/layout/article.html b/site_test/layout/article.html
index dd8ed75..d0e599c 100644
--- a/site_test/layout/article.html
+++ b/site_test/layout/article.html
@@ -1,30 +1,36 @@
-{% extends "layout/default.html" %}
+{% extends "default" %}
+
+{% block titlevariable %}
+{% set title = metadata.title %}
+{% endblock %}
{% block head -%}
-{% match post.metadata.short_desc %}
- {% when Some with (val) %}
-
- {% when None %}
-
-{% endmatch %}
+{% if metadata.short_desc %}
+
+{% else %}
+
+{% endif %}
{%- endblock %}
{% block image %}
-{% match post.metadata.card_image_path %}
- {% when Some with (path) %}
-
-
- {% when None %}
-
-
-{% endmatch %}
+{% if metadata.card_image_path %}
+
+
+{% else %}
+
+
+{% endif %}
{% endblock %}
{% block title %}{{ post.metadata.title }}{% endblock %}
+{% block content -%}
+
-
+
+
+{%- endblock %}
diff --git a/site_test/layout/default.html b/site_test/layout/default.html
index e6ef771..f1eec2c 100644
--- a/site_test/layout/default.html
+++ b/site_test/layout/default.html
@@ -5,10 +5,13 @@
- {% block title %}Shadowfacts{% endblock %}
+ {% block titlevariable %}
+ {% set title = "Shadowfacts" %}
+ {% endblock %}
+ {{ title }}
-
-
+
+
@@ -16,17 +19,17 @@
-
+
{% block image %}
-
-
+
+
{% endblock %}
-
+
{% block head %}{% endblock %}
-
+
diff --git a/site_test/posts/2024-11-30-swiftui-lifecycle.md b/site_test/posts/2024-11-30-swiftui-lifecycle.md
index 3e7eec8..1552e03 100644
--- a/site_test/posts/2024-11-30-swiftui-lifecycle.md
+++ b/site_test/posts/2024-11-30-swiftui-lifecycle.md
@@ -7,7 +7,7 @@ slug = "swiftui-lifecycle"
When SwiftUI was announced in 2019 (oh boy, more than 5 years ago), one of the big things that the Apple engineers emphasized was that a SwiftUI `View` is not like a UIKit/AppKit view. As Apple was at pains to note, SwiftUI may evaluate the `body` property of a `View` arbitrarily often. It's easy to miss an important consequence this has, though: your `View` will also be initilaized arbitrarily often. This is why SwiftUI views are supposed to be structs which are simple value types and can be stack-allocated: constructing a `View` needs to be cheap.
-More precisely, what I mean by the title of this post is that the lifetime of a struct that conforms to `View` is unmoored from that of the conceptual thing representing a piece of your user interface.
+More precisely, what I mean by the title of this post is that the lifetime of a struct that conforms to `View` is unmoored from that of the conceptual thing representing a piece of your user interface.
@@ -19,7 +19,7 @@ A note on definitions: in this post, I'm using the word 'lifecycle' and 'lifetim
-This comes up on social media with some regularity. Every few months there'll be questions about (either directly, or in the form of questions that boil down to) why some view's initializer is being called more than expected. One particularly common case is people migrating from Combine/`ObservableObject` to the iOS 17+ `@Observable`.
+This comes up on social media with some regularity. Every few months there'll be questions about (either directly, or in the form of questions that boil down to) why some view's initializer is being called more than expected. One particularly common case is people migrating from Combine/`ObservableObject` to the iOS 17+ `@Observable`.
If you were not paying attention to SwiftUI in the early days, or have only started learning it more recently, there are some important details about the nature of the framework you might have missed. One of the things the SwiftUI engineers emphasized when it was announced was that the `body` property can be called at any time by the framework and may be called as often as the framework likes. What follows from this—that's not entirely obvious or always explicitly stated—is that `View` initializers run with the same frequency. Consider the minimal possible case:
diff --git a/site_test/tag.html b/site_test/tag.html
index 2941728..a490d05 100644
--- a/site_test/tag.html
+++ b/site_test/tag.html
@@ -1,9 +1,21 @@
-{% extends "layout/default.html" %}
+{% extends "default" %}
-{% block title %}{{ tag.name }} posts{% endblock %}
+{% block titlevariable %}
+{% set title = tag_name ~ " posts" %}
+{% endblock %}
{% block content -%}
-
{{ tag.name }} posts
+{{ tag_name }} posts
+
+
{%- endblock %}
diff --git a/src/generator/archive.rs b/src/generator/archive.rs
index 6c72385..bc9f338 100644
--- a/src/generator/archive.rs
+++ b/src/generator/archive.rs
@@ -1,25 +1,54 @@
use std::collections::HashMap;
-use askama::Template;
use chrono::Datelike;
use compute_graph::{
builder::GraphBuilder,
rule::{Input, InputVisitable, Rule},
synchronicity::Asynchronous,
};
+use serde::Serialize;
+
+use crate::generator::templates::{BuildTemplateContext, RenderTemplate};
use super::{
+ FileWatcher,
posts::content::{HtmlContent, Post},
- util::{output_rendered_template, templates::TemplateCommon},
+ templates::{AddTemplate, Templates},
+ util::content_path,
};
pub fn make_graph(
builder: &mut GraphBuilder<(), Asynchronous>,
posts: Input>>,
+ default_template: Input,
+ watcher: &mut FileWatcher,
) -> Input<()> {
let entries = builder.add_rule(Entries(posts));
let posts_by_year = builder.add_rule(PostsByYear(entries));
- builder.add_rule(Archive(posts_by_year))
+
+ let archive_path = content_path("archive.html");
+ let (archive_template, invalidate_template) = builder.add_invalidatable_rule(AddTemplate::new(
+ "archive",
+ archive_path.clone(),
+ default_template,
+ ));
+ watcher.watch(archive_path, move || invalidate_template.invalidate());
+
+ let context = builder.add_rule(BuildTemplateContext::new(
+ "/archive/".into(),
+ posts_by_year,
+ |map, ctx| {
+ ctx.insert("years", &map.years());
+ ctx.insert("posts_by_year", &map.0);
+ },
+ ));
+
+ builder.add_rule(RenderTemplate {
+ name: "archive",
+ output_path: "/archive/index.html".into(),
+ templates: archive_template,
+ context,
+ })
}
#[derive(InputVisitable)]
@@ -54,53 +83,18 @@ impl Rule for PostsByYear {
#[derive(PartialEq)]
struct PostsYearMap(HashMap>);
-#[derive(PartialEq, Clone)]
+impl PostsYearMap {
+ fn years(&self) -> Vec {
+ let mut years = self.0.keys().cloned().collect::>();
+ years.sort();
+ years.reverse();
+ years
+ }
+}
+
+#[derive(PartialEq, Clone, Serialize)]
struct Entry {
permalink: String,
title: String,
year: i32,
}
-
-#[derive(InputVisitable)]
-struct Archive(Input);
-impl Rule for Archive {
- type Output = ();
- fn evaluate(&mut self) -> Self::Output {
- output_rendered_template(
- &ArchiveTemplate {
- map: &*self.input_0(),
- },
- "archive/index.html",
- )
- .expect("writing archive")
- }
-}
-
-#[derive(Template)]
-#[template(path = "archive.html")]
-struct ArchiveTemplate<'a> {
- map: &'a PostsYearMap,
-}
-
-impl<'a> TemplateCommon for ArchiveTemplate<'a> {}
-
-impl<'a> ArchiveTemplate<'a> {
- fn permalink(&self) -> &'static str {
- "/archive/"
- }
-
- fn years(&self) -> Vec {
- let mut years = self.map.0.keys().cloned().collect::>();
- years.sort();
- years.reverse();
- years
- }
-
- fn posts_for_year(&self, year: &i32) -> &[Entry] {
- self.map
- .0
- .get(year)
- .map(|vec| vec.as_slice())
- .unwrap_or(&[])
- }
-}
diff --git a/src/generator/mod.rs b/src/generator/mod.rs
index 15aa363..8f8c25f 100644
--- a/src/generator/mod.rs
+++ b/src/generator/mod.rs
@@ -2,6 +2,7 @@ mod archive;
mod markdown;
mod posts;
mod tags;
+mod templates;
mod util;
use std::cell::RefCell;
@@ -28,11 +29,24 @@ pub async fn generate(watcher: Rc>) -> anyhow::Result>) -> anyhow::Result> {
let mut builder = GraphBuilder::new_async();
- let (void_outputs, posts, all_posts, post_metadatas) = posts::make_graph(&mut builder, watcher);
+ let default_template = templates::make_graph(&mut builder, &mut *watcher.borrow_mut());
- let archive = archive::make_graph(&mut builder, all_posts);
+ let (void_outputs, posts, all_posts, post_metadatas) =
+ posts::make_graph(&mut builder, default_template.clone(), Rc::clone(&watcher));
- let tag_output = tags::make_graph(&mut builder, posts);
+ let archive = archive::make_graph(
+ &mut builder,
+ all_posts,
+ default_template.clone(),
+ &mut *watcher.borrow_mut(),
+ );
+
+ let tag_output = tags::make_graph(
+ &mut builder,
+ posts,
+ default_template,
+ &mut *watcher.borrow_mut(),
+ );
let post_metadatas_voided = builder.add_rule(MapToVoid(post_metadatas));
let output = Combine::make(&mut builder, &[
diff --git a/src/generator/posts.rs b/src/generator/posts.rs
index 99371d5..20d2d15 100644
--- a/src/generator/posts.rs
+++ b/src/generator/posts.rs
@@ -5,7 +5,6 @@ use std::cell::RefCell;
use std::path::PathBuf;
use std::rc::Rc;
-use askama::Template;
use compute_graph::{
builder::GraphBuilder,
rule::{
@@ -17,16 +16,17 @@ use compute_graph::{
use content::{HtmlContent, Post};
use log::error;
use metadata::PostMetadata;
-
-use crate::generator::util::output_rendered_template;
+use tera::Context;
use super::{
FileWatcher,
- util::{MapDynamicToVoid, content_path, templates::TemplateCommon},
+ templates::{AddTemplate, BuildTemplateContext, RenderTemplate, Templates},
+ util::{MapDynamicToVoid, content_path},
};
pub fn make_graph(
builder: &mut GraphBuilder<(), Asynchronous>,
+ default_template: Input,
watcher: Rc>,
) -> (
Input<()>,
@@ -39,11 +39,22 @@ pub fn make_graph(
invalidate_posts.invalidate();
});
- let posts = builder.add_dynamic_rule(MakeReadNodes::new(post_files, watcher));
+ let posts = builder.add_dynamic_rule(MakeReadNodes::new(post_files, Rc::clone(&watcher)));
let extract_metadatas = builder.add_dynamic_rule(MakeExtractMetadatas::new(posts.clone()));
- let write_posts = builder.add_dynamic_rule(MakeWritePosts::new(posts.clone()));
+ let article_path = content_path("layout/article.html");
+ let (article_template, invalidate_template) = builder.add_invalidatable_rule(AddTemplate::new(
+ "article",
+ article_path.clone(),
+ default_template,
+ ));
+ watcher
+ .borrow_mut()
+ .watch(article_path, move || invalidate_template.invalidate());
+
+ let write_posts =
+ builder.add_dynamic_rule(MakeWritePosts::new(posts.clone(), article_template));
(
builder.add_rule(MapDynamicToVoid(write_posts)),
@@ -105,7 +116,7 @@ impl DynamicRule for MakeReadNodes {
type ChildOutput = ReadPostOutput;
fn evaluate(&mut self, ctx: &mut impl DynamicRuleContext) -> Vec> {
for file in self.files.value().iter() {
- self.node_factory.add_rule(ctx, file.clone(), |ctx| {
+ self.node_factory.add_node(ctx, file.clone(), |ctx| {
let (input, signal) = ctx.add_invalidatable_rule(ReadPost { path: file.clone() });
self.watcher
.borrow_mut()
@@ -170,7 +181,7 @@ impl DynamicRule for MakeExtractMetadatas {
for post_input in self.posts.value().inputs.iter() {
let post_ = post_input.value();
let post = post_.as_ref().unwrap();
- self.node_factory.add_rule(ctx, post.path.clone(), |ctx| {
+ self.node_factory.add_node(ctx, post.path.clone(), |ctx| {
ctx.add_rule(ExtractMetadata(post_input.clone()))
});
}
@@ -201,13 +212,19 @@ impl Rule for ExtractMetadata {
#[derive(InputVisitable)]
struct MakeWritePosts {
posts: DynamicInput,
- node_factory: DynamicNodeFactory,
+ article_template: Input,
+ unwrapped_factory: DynamicNodeFactory>,
+ build_context_factory: DynamicNodeFactory,
+ render_factory: DynamicNodeFactory,
}
impl MakeWritePosts {
- fn new(posts: DynamicInput) -> Self {
+ fn new(posts: DynamicInput, templates: Input) -> Self {
Self {
posts,
- node_factory: DynamicNodeFactory::new(),
+ article_template: templates,
+ unwrapped_factory: DynamicNodeFactory::new(),
+ build_context_factory: DynamicNodeFactory::new(),
+ render_factory: DynamicNodeFactory::new(),
}
}
}
@@ -216,44 +233,35 @@ impl DynamicRule for MakeWritePosts {
fn evaluate(&mut self, ctx: &mut impl DynamicRuleContext) -> Vec> {
for post_input in self.posts.value().inputs.iter() {
if let Some(post) = post_input.value().as_ref() {
- self.node_factory.add_rule(ctx, post.path.clone(), |ctx| {
- ctx.add_rule(WritePost(post_input.clone()))
+ let context = self
+ .build_context_factory
+ .add_node(ctx, post.path.clone(), |ctx| {
+ ctx.add_rule(BuildTemplateContext::new(
+ post.permalink().into(),
+ post_input.clone(),
+ |post_opt, ctx| {
+ let post = post_opt.as_ref().unwrap();
+ ctx.insert("metadata", &post.metadata);
+ ctx.insert("content", post.content.html());
+ },
+ ))
+ });
+
+ let mut output_path = PathBuf::from(post.permalink());
+ output_path.push("index.html");
+ self.render_factory.add_node(ctx, post.path.clone(), |ctx| {
+ ctx.add_rule(RenderTemplate {
+ name: "article",
+ output_path,
+ templates: self.article_template.clone(),
+ context,
+ })
});
}
}
- self.node_factory.all_nodes(ctx)
- }
-}
-
-#[derive(InputVisitable)]
-struct WritePost(Input);
-impl Rule for WritePost {
- type Output = ();
-
- fn evaluate(&mut self) -> Self::Output {
- let post_ = &self.input_0();
- let post = post_.as_ref().unwrap();
- let mut path = PathBuf::from(post.permalink());
- path.push("index.html");
- output_rendered_template(&ArticleTemplate { post }, path).expect("writing post");
- }
-
- fn node_label(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
- write!(f, "{}", self.input_0().as_ref().unwrap().slug)
- }
-}
-
-#[derive(Template)]
-#[template(path = "layout/article.html")]
-struct ArticleTemplate<'a> {
- post: &'a Post,
-}
-
-impl<'a> TemplateCommon for ArticleTemplate<'a> {}
-
-impl<'a> ArticleTemplate<'a> {
- fn permalink(&self) -> String {
- self.post.permalink()
+ self.unwrapped_factory.finalize_nodes(ctx);
+ self.build_context_factory.finalize_nodes(ctx);
+ self.render_factory.all_nodes(ctx)
}
}
diff --git a/src/generator/posts/metadata.rs b/src/generator/posts/metadata.rs
index 7d43a2a..89f23f8 100644
--- a/src/generator/posts/metadata.rs
+++ b/src/generator/posts/metadata.rs
@@ -1,12 +1,12 @@
use std::hash::Hash;
use chrono::{DateTime, FixedOffset};
-use serde::Deserialize;
use serde::de::{SeqAccess, Visitor};
+use serde::{Deserialize, Serialize};
use crate::generator::util::slugify::slugify;
-#[derive(Debug, Deserialize, PartialEq, Clone)]
+#[derive(Debug, Deserialize, Serialize, PartialEq, Clone)]
#[serde(deny_unknown_fields)]
pub struct PostMetadata {
pub title: String,
@@ -68,7 +68,7 @@ where
deserializer.deserialize_any(StringOrVec)
}
-#[derive(Debug, Eq, Clone)]
+#[derive(Debug, Eq, Clone, Serialize)]
pub struct Tag {
pub name: String,
pub slug: String,
diff --git a/src/generator/tags.rs b/src/generator/tags.rs
index ccb6da2..dda3b06 100644
--- a/src/generator/tags.rs
+++ b/src/generator/tags.rs
@@ -1,26 +1,37 @@
-use std::{collections::HashMap, path::PathBuf};
+use std::collections::HashMap;
-use askama::Template;
use compute_graph::{
builder::GraphBuilder,
rule::{DynamicInput, DynamicNodeFactory, DynamicRule, Input, InputVisitable, Rule},
synchronicity::Asynchronous,
};
+use serde::Serialize;
+use tera::Context;
use super::{
- posts::{
- ReadPostOutput,
- metadata::{PostMetadata, Tag},
- },
- util::{MapDynamicToVoid, output_rendered_template, templates::TemplateCommon},
+ FileWatcher,
+ posts::{ReadPostOutput, metadata::Tag},
+ templates::{AddTemplate, BuildTemplateContext, RenderTemplate, Templates},
+ util::{MapDynamicToVoid, content_path},
};
pub fn make_graph(
builder: &mut GraphBuilder<(), Asynchronous>,
posts: DynamicInput,
+ default_template: Input,
+ watcher: &mut FileWatcher,
) -> Input<()> {
let by_tags = builder.add_dynamic_rule(MakePostsByTags::new(posts));
- let write_tags = builder.add_dynamic_rule(MakeWriteTagPages::new(by_tags));
+
+ let template_path = content_path("tag.html");
+ let (tag_template, invalidate_template) = builder.add_invalidatable_rule(AddTemplate::new(
+ "tag",
+ template_path.clone(),
+ default_template,
+ ));
+ watcher.watch(template_path, move || invalidate_template.invalidate());
+
+ let write_tags = builder.add_dynamic_rule(MakeWriteTagPages::new(by_tags, tag_template));
builder.add_rule(MapDynamicToVoid(write_tags))
}
@@ -51,7 +62,7 @@ impl DynamicRule for MakePostsByTags {
}
}
for (slug, name) in all_tags {
- self.node_factory.add_rule(ctx, slug.clone(), |ctx| {
+ self.node_factory.add_node(ctx, slug.clone(), |ctx| {
ctx.add_rule(PostsByTag {
posts: self.posts.clone(),
tag: Tag { slug, name },
@@ -82,7 +93,7 @@ impl Rule for PostsByTag {
if tags.any(|t| t.slug == self.tag.slug) {
Some(Entry {
permalink: post.permalink(),
- metadata: post.metadata.clone(),
+ title: post.metadata.title.clone(),
})
} else {
None
@@ -106,22 +117,27 @@ struct TagAndPosts {
entries: Vec,
}
-#[derive(PartialEq, Clone)]
+#[derive(PartialEq, Clone, Serialize)]
struct Entry {
permalink: String,
- metadata: PostMetadata,
+ title: String,
}
#[derive(InputVisitable)]
struct MakeWriteTagPages {
tags: DynamicInput,
- node_factory: DynamicNodeFactory,
+ #[ignore_input]
+ templates: Input,
+ build_context_factory: DynamicNodeFactory,
+ render_factory: DynamicNodeFactory,
}
impl MakeWriteTagPages {
- fn new(tags: DynamicInput) -> Self {
+ fn new(tags: DynamicInput, templates: Input) -> Self {
Self {
tags,
- node_factory: DynamicNodeFactory::new(),
+ templates,
+ build_context_factory: DynamicNodeFactory::new(),
+ render_factory: DynamicNodeFactory::new(),
}
}
}
@@ -133,50 +149,29 @@ impl DynamicRule for MakeWriteTagPages {
) -> Vec> {
for tag_input in self.tags.value().inputs.iter() {
let tag_and_posts = tag_input.value();
- self.node_factory
- .add_rule(ctx, tag_and_posts.tag.slug.clone(), |ctx| {
- ctx.add_rule(WriteTag(tag_input.clone()))
+ let slug = &tag_and_posts.tag.slug;
+ let template_context = self
+ .build_context_factory
+ .add_node(ctx, slug.clone(), |ctx| {
+ ctx.add_rule(BuildTemplateContext::new(
+ format!("/{slug}/").into(),
+ tag_input.clone(),
+ |tag_and_posts, ctx| {
+ ctx.insert("tag_name", &tag_and_posts.tag.name);
+ ctx.insert("posts", &tag_and_posts.entries);
+ },
+ ))
});
+ self.render_factory.add_node(ctx, slug.clone(), |ctx| {
+ ctx.add_rule(RenderTemplate {
+ name: "tag",
+ output_path: format!("/{slug}/index.html").into(),
+ templates: self.templates.clone(),
+ context: template_context,
+ })
+ });
}
- self.node_factory.all_nodes(ctx)
- }
-}
-
-#[derive(InputVisitable)]
-struct WriteTag(Input);
-impl Rule for WriteTag {
- type Output = ();
-
- fn evaluate(&mut self) -> Self::Output {
- let tag_and_posts = self.input_0();
- let mut path = PathBuf::from(&tag_and_posts.tag.slug);
- path.push("index.html");
- output_rendered_template(
- &TagTemplate {
- tag: &tag_and_posts.tag,
- posts: &tag_and_posts.entries,
- },
- path,
- )
- .expect("writing tag page");
- }
-
- fn node_label(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
- write!(f, "{}", self.input_0().tag.slug)
- }
-}
-
-#[derive(Template)]
-#[template(path = "tag.html")]
-struct TagTemplate<'a> {
- tag: &'a Tag,
- posts: &'a [Entry],
-}
-
-impl<'a> TemplateCommon for TagTemplate<'a> {}
-
-impl<'a> TagTemplate<'a> {
- fn permalink(&self) -> String {
- format!("/{}/", self.tag.slug)
+ self.build_context_factory.finalize_nodes(ctx);
+ self.render_factory.all_nodes(ctx)
}
}
diff --git a/src/generator/templates.rs b/src/generator/templates.rs
new file mode 100644
index 0000000..2bb6077
--- /dev/null
+++ b/src/generator/templates.rs
@@ -0,0 +1,131 @@
+use std::{borrow::Cow, path::PathBuf, time::SystemTime};
+
+use compute_graph::{
+ builder::GraphBuilder,
+ node::NodeValue,
+ rule::{Input, InputVisitable, Rule},
+ synchronicity::Asynchronous,
+};
+use log::error;
+use once_cell::sync::Lazy;
+use tera::{Context, Tera};
+
+use crate::generator::util::output_writer;
+
+use super::{FileWatcher, util::content_path};
+
+pub fn make_graph(
+ builder: &mut GraphBuilder<(), Asynchronous>,
+ watcher: &mut FileWatcher,
+) -> Input {
+ let empty_templates = builder.add_value(Templates::default());
+
+ let default_path = content_path("layout/default.html");
+ let (default, invalidate_default) = builder.add_invalidatable_rule(AddTemplate::new(
+ "default",
+ default_path.clone(),
+ empty_templates,
+ ));
+ watcher.watch(default_path, move || invalidate_default.invalidate());
+
+ default
+}
+
+#[derive(InputVisitable)]
+pub struct AddTemplate(String, PathBuf, Input);
+impl AddTemplate {
+ pub fn new(name: &str, path: PathBuf, base: Input) -> Self {
+ Self(name.into(), path, base)
+ }
+}
+impl Rule for AddTemplate {
+ type Output = Templates;
+ fn evaluate(&mut self) -> Self::Output {
+ let mut templates = self.input_2().clone();
+ let content = std::fs::read_to_string(&self.1).expect("reading template");
+ let result = templates.tera.add_raw_template(&self.0, &content);
+ if let Err(e) = result {
+ error!("Error adding template {:?}: {:?}", &self.1, e)
+ }
+ templates.templates.push(content);
+ templates
+ }
+}
+
+#[derive(Default, Clone)]
+pub struct Templates {
+ templates: Vec,
+ tera: Tera,
+}
+
+impl NodeValue for Templates {
+ fn node_value_eq(&self, other: &Self) -> bool {
+ self.templates == other.templates
+ }
+}
+
+static DOMAIN: Lazy =
+ Lazy::new(|| std::env::var("DOMAIN").unwrap_or("shadowfacts.net".to_owned()));
+static CB: Lazy = Lazy::new(|| {
+ SystemTime::now()
+ .duration_since(SystemTime::UNIX_EPOCH)
+ .unwrap()
+ .as_secs()
+});
+
+#[derive(InputVisitable)]
+pub struct BuildTemplateContext {
+ permalink: Cow<'static, str>,
+ input: Input,
+ func: F,
+}
+impl ()> BuildTemplateContext {
+ pub fn new(permalink: Cow<'static, str>, input: Input, func: F) -> Self {
+ Self {
+ permalink,
+ input,
+ func,
+ }
+ }
+}
+impl () + 'static> Rule for BuildTemplateContext {
+ type Output = Context;
+ fn evaluate(&mut self) -> Self::Output {
+ let mut context = Context::new();
+ (self.func)(&*self.input(), &mut context);
+ context.insert("_domain", &*DOMAIN);
+ context.insert("_permalink", &self.permalink);
+ context.insert("_stylesheet_cache_buster", &*CB);
+ context
+ }
+}
+
+#[derive(InputVisitable)]
+pub struct RenderTemplate {
+ pub name: &'static str,
+ pub output_path: PathBuf,
+ pub templates: Input,
+ pub context: Input,
+}
+impl Rule for RenderTemplate {
+ type Output = ();
+
+ fn evaluate(&mut self) -> Self::Output {
+ let templates = self.templates();
+ assert!(templates.tera.get_template_names().any(|n| n == self.name));
+ let writer = output_writer(&self.output_path).expect("output writer");
+ let result = templates
+ .tera
+ .render_to(&self.name, &*self.context(), writer);
+ if let Err(e) = result {
+ error!(
+ "Error rendering template to {:?}: {:?}",
+ &self.output_path, e
+ );
+ }
+ }
+
+ fn node_label(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(f, "{}", self.output_path.display())
+ }
+}
diff --git a/src/generator/util/mod.rs b/src/generator/util/mod.rs
index df9ae4c..864981b 100644
--- a/src/generator/util/mod.rs
+++ b/src/generator/util/mod.rs
@@ -8,7 +8,6 @@ use std::io::{BufWriter, Write};
use std::path::{Component, Path, PathBuf};
use anyhow::anyhow;
-use askama::Template;
use compute_graph::builder::GraphBuilder;
use compute_graph::rule::{DynamicInput, Input, InputVisitable, Rule};
use compute_graph::synchronicity::Synchronicity;
@@ -23,38 +22,6 @@ pub fn output_writer(path: impl AsRef) -> Result,
-) -> Result<(), std::io::Error> {
- let path = file.as_ref();
- let writer = output_writer(path)?;
- template.render_into(&mut FmtWriter(writer)).map_err(|e| {
- std::io::Error::new(
- std::io::ErrorKind::Other,
- format!("writing template {}: {}", path.display(), e),
- )
- })
-}
-
-struct FmtWriter(W);
-
-impl std::fmt::Write for FmtWriter {
- fn write_str(&mut self, s: &str) -> std::fmt::Result {
- self.0.write_all(s.as_bytes()).map_err(|_| std::fmt::Error)
- }
-
- fn write_char(&mut self, c: char) -> std::fmt::Result {
- let mut buf = [0u8; 4];
- c.encode_utf8(&mut buf);
- self.0.write_all(&buf).map_err(|_| std::fmt::Error)
- }
-
- fn write_fmt(&mut self, args: std::fmt::Arguments<'_>) -> std::fmt::Result {
- self.0.write_fmt(args).map_err(|_| std::fmt::Error)
- }
-}
-
pub fn from_frontmatter(contents: &str) -> anyhow::Result<(D, &str)> {
let mut chars = contents.char_indices();
for i in 0..=2 {
diff --git a/src/generator/util/templates.rs b/src/generator/util/templates.rs
index 7bd7260..653eeb4 100644
--- a/src/generator/util/templates.rs
+++ b/src/generator/util/templates.rs
@@ -1,59 +1,53 @@
-use chrono::{DateTime, Local};
-use once_cell::sync::Lazy;
-use std::time::SystemTime;
+// static DOMAIN: Lazy =
+// Lazy::new(|| std::env::var("DOMAIN").unwrap_or("shadowfacts.net".to_owned()));
-static DOMAIN: Lazy =
- Lazy::new(|| std::env::var("DOMAIN").unwrap_or("shadowfacts.net".to_owned()));
+// static CB: Lazy = Lazy::new(|| {
+// SystemTime::now()
+// .duration_since(SystemTime::UNIX_EPOCH)
+// .unwrap()
+// .as_secs()
+// });
-static CB: Lazy = Lazy::new(|| {
- SystemTime::now()
- .duration_since(SystemTime::UNIX_EPOCH)
- .unwrap()
- .as_secs()
-});
+// static GENERATED_AT: Lazy> = Lazy::new(|| Local::now());
-static GENERATED_AT: Lazy> = Lazy::new(|| Local::now());
+// pub trait TemplateCommon {
+// fn domain() -> String {
+// DOMAIN.to_owned()
+// }
-pub trait TemplateCommon {
- fn domain() -> String {
- DOMAIN.to_owned()
- }
+// fn stylesheet_cache_buster() -> u64 {
+// *CB
+// }
- fn stylesheet_cache_buster() -> u64 {
- *CB
- }
-
- fn generated_at() -> &'static DateTime {
- &*GENERATED_AT
- }
-}
+// fn generated_at() -> &'static DateTime {
+// &*GENERATED_AT
+// }
+// }
pub mod filters {
use std::fmt::Display;
use chrono::{DateTime, Datelike, NaiveDate, TimeZone, Utc};
- pub fn iso_date(date: &NaiveDate) -> askama::Result {
- Ok(
- Utc::from_utc_datetime(&Utc, &date.and_hms_opt(12, 0, 0).unwrap())
- .format("%+:0")
- .to_string(),
- )
+ pub fn iso_date(date: &NaiveDate) -> String {
+ Utc::from_utc_datetime(&Utc, &date.and_hms_opt(12, 0, 0).unwrap())
+ .format("%+:0")
+ .to_string()
}
- pub fn iso_datetime(datetime: &DateTime) -> askama::Result
+ pub fn iso_datetime(datetime: &DateTime) -> String
where
Tz: TimeZone,
Tz::Offset: Display,
{
- Ok(datetime.format("%+:0").to_string())
+ datetime.format("%+:0").to_string()
}
const MONTHS: &[&str; 12] = &[
"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
];
- pub fn pretty_date(date: &impl Datelike) -> askama::Result {
+ pub fn pretty_date(date: &impl Datelike) -> String {
let month = MONTHS[date.month0() as usize];
let suffix = match date.day() {
1 | 21 | 31 => "st",
@@ -61,29 +55,23 @@ pub mod filters {
3 | 23 => "rd",
_ => "th",
};
- Ok(format!(
- "{} {}{}, {}",
- month,
- date.day(),
- suffix,
- date.year()
- ))
+ format!("{} {}{}, {}", month, date.day(), suffix, date.year())
}
- pub fn pretty_datetime(datetime: &DateTime) -> askama::Result
+ pub fn pretty_datetime(datetime: &DateTime) -> String
where
Tz: TimeZone,
Tz::Offset: Display,
{
- Ok(format!(
+ format!(
"{} {}",
datetime.format("%-I:%M:%S %p"),
- pretty_date(datetime)?
- ))
+ pretty_date(datetime)
+ )
}
- pub fn reading_time(words: &u32) -> askama::Result {
+ pub fn reading_time(words: &u32) -> u32 {
let wpm = 225.0;
- return Ok((*words as f32 / wpm).max(1.0) as u32);
+ (*words as f32 / wpm).max(1.0) as u32
}
}