Use tera templates, incorporate templates into the graph
This commit is contained in:
parent
1253999961
commit
60858bde24
429
Cargo.lock
generated
429
Cargo.lock
generated
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -1,2 +0,0 @@
|
||||
[general]
|
||||
dirs = ["site_test"]
|
@ -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 %}
|
||||
<h2>{{ year }}</h2>
|
||||
<ul>
|
||||
{% for entry in self.posts_for_year(year) %}
|
||||
{% for entry in posts_by_year[year] %}
|
||||
<li>
|
||||
<a href="{{ entry.permalink }}">
|
||||
{{ entry.title }}
|
||||
|
@ -1,30 +1,36 @@
|
||||
{% extends "layout/default.html" %}
|
||||
{% extends "default" %}
|
||||
|
||||
{% block titlevariable %}
|
||||
{% set title = metadata.title %}
|
||||
{% endblock %}
|
||||
|
||||
{% block head -%}
|
||||
|
||||
<meta property="og:type" content="article">
|
||||
{% match post.metadata.short_desc %}
|
||||
{% when Some with (val) %}
|
||||
<meta property="og:description" content="{{ val }}">
|
||||
{% when None %}
|
||||
<meta property="og:description" content="The outer part of a shadow is called the penumbra.">
|
||||
{% endmatch %}
|
||||
{% if metadata.short_desc %}
|
||||
<meta property="og:description" content="{{ metadata.short_desc }}">
|
||||
{% else %}
|
||||
<meta property="og:description" content="The outer part of a shadow is called the penumbra.">
|
||||
{% endif %}
|
||||
|
||||
{%- endblock %}
|
||||
|
||||
{% block image %}
|
||||
{% match post.metadata.card_image_path %}
|
||||
{% when Some with (path) %}
|
||||
<meta property="twitter:image" content="https://{{ Self::domain() }}{{ path }}">
|
||||
<meta property="og:image" content="https://{{ Self::domain() }}{{ path }}">
|
||||
{% when None %}
|
||||
<meta property="twitter:image" content="https://{{ Self::domain() }}/shadowfacts.png">
|
||||
<meta property="og:image" content="https://{{ Self::domain() }}/shadowfacts.png">
|
||||
{% endmatch %}
|
||||
{% if metadata.card_image_path %}
|
||||
<meta property="twitter:image" content="https://{{ _domain }}{{ metadata.card_image_path }}">
|
||||
<meta property="og:image" content="https://{{ _domain }}{{ metadata.card_image_path }}">
|
||||
{% else %}
|
||||
<meta property="twitter:image" content="https://{{ _domain }}/shadowfacts.png">
|
||||
<meta property="og:image" content="https://{{ _domain }}/shadowfacts.png">
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block title %}{{ post.metadata.title }}{% endblock %}
|
||||
|
||||
{% block content -%}
|
||||
|
||||
<article itemprop="blogPost" itemscope itemtype="https://schema.org/BlogPosting">
|
||||
<meta itemprop="mainEntityOfPage" content="https://{{ Self::domain() }}{{ self.permalink() }}">
|
||||
<meta itemprop="mainEntityOfPage" content="https://{{ _domain }}{{ _permalink }}">
|
||||
</article>
|
||||
|
||||
{%- endblock %}
|
||||
|
@ -5,10 +5,13 @@
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
|
||||
<title>{% block title %}Shadowfacts{% endblock %}</title>
|
||||
{% block titlevariable %}
|
||||
{% set title = "Shadowfacts" %}
|
||||
{% endblock %}
|
||||
<title>{{ title }}</title>
|
||||
|
||||
<link rel="cannonical" href="https://{{ Self::domain() }}{{ self.permalink() }}">
|
||||
<link rel="alternate" type="application/rss+xml" title="Shadowfacts" href="https://{{ Self::domain() }}/feed.xml">
|
||||
<link rel="cannonical" href="https://{{ _domain }}{{ _permalink }}">
|
||||
<link rel="alternate" type="application/rss+xml" title="Shadowfacts" href="https://{{ _domain }}/feed.xml">
|
||||
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<link rel="apple-touch-icon-precomposed" href="/favicon-152.png">
|
||||
@ -16,17 +19,17 @@
|
||||
<meta name="msapplication-TileImage" content="/favicon-152.png">
|
||||
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta property="og:title" content="{% block title %}{% endblock %}">
|
||||
<meta property="og:title" content="{{ title }}">
|
||||
{% block image %}
|
||||
<meta property="twitter:image" content="https://{{ Self::domain() }}/shadowfacts.png">
|
||||
<meta property="og:image" content="https://{{ Self::domain() }}/shadowfacts.png">
|
||||
<meta property="twitter:image" content="https://{{ _domain }}/shadowfacts.png">
|
||||
<meta property="og:image" content="https://{{ _domain }}/shadowfacts.png">
|
||||
{% endblock %}
|
||||
<meta property="og:url" content="https://{{ Self::domain() }}{{ self.permalink() }}">
|
||||
<meta property="og:url" content="https://{{ _domain }}{{ _permalink }}">
|
||||
<meta property="og:site_name" content="Shadowfacts">
|
||||
|
||||
{% block head %}{% endblock %}
|
||||
|
||||
<link rel="stylesheet" href="/css/main.css?{{ Self::stylesheet_cache_buster() }}">
|
||||
<link rel="stylesheet" href="/css/main.css?{{ _stylesheet_cache_buster }}">
|
||||
</head>
|
||||
<body itemscope itemtype="https://schema.org/Blog">
|
||||
|
||||
|
@ -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 -%}
|
||||
|
||||
<h1>{{ tag.name }} posts</h1>
|
||||
<h1>{{ tag_name }} posts</h1>
|
||||
|
||||
<ul>
|
||||
{% for entry in posts %}
|
||||
<li>
|
||||
<a href="{{ entry.permalink }}">
|
||||
{{ entry.title }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
{%- endblock %}
|
||||
|
@ -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<Vec<Post<HtmlContent>>>,
|
||||
default_template: Input<Templates>,
|
||||
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<i32, Vec<Entry>>);
|
||||
|
||||
#[derive(PartialEq, Clone)]
|
||||
impl PostsYearMap {
|
||||
fn years(&self) -> Vec<i32> {
|
||||
let mut years = self.0.keys().cloned().collect::<Vec<_>>();
|
||||
years.sort();
|
||||
years.reverse();
|
||||
years
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Serialize)]
|
||||
struct Entry {
|
||||
permalink: String,
|
||||
title: String,
|
||||
year: i32,
|
||||
}
|
||||
|
||||
#[derive(InputVisitable)]
|
||||
struct Archive(Input<PostsYearMap>);
|
||||
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<i32> {
|
||||
let mut years = self.map.0.keys().cloned().collect::<Vec<_>>();
|
||||
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(&[])
|
||||
}
|
||||
}
|
||||
|
@ -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<RefCell<FileWatcher>>) -> anyhow::Result<Async
|
||||
fn make_graph(watcher: Rc<RefCell<FileWatcher>>) -> anyhow::Result<AsyncGraph<()>> {
|
||||
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, &[
|
||||
|
@ -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<Templates>,
|
||||
watcher: Rc<RefCell<FileWatcher>>,
|
||||
) -> (
|
||||
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<Input<Self::ChildOutput>> {
|
||||
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<ReadPostOutput>,
|
||||
node_factory: DynamicNodeFactory<PathBuf, ()>,
|
||||
article_template: Input<Templates>,
|
||||
unwrapped_factory: DynamicNodeFactory<PathBuf, Post<HtmlContent>>,
|
||||
build_context_factory: DynamicNodeFactory<PathBuf, Context>,
|
||||
render_factory: DynamicNodeFactory<PathBuf, ()>,
|
||||
}
|
||||
impl MakeWritePosts {
|
||||
fn new(posts: DynamicInput<ReadPostOutput>) -> Self {
|
||||
fn new(posts: DynamicInput<ReadPostOutput>, templates: Input<Templates>) -> 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<Input<Self::ChildOutput>> {
|
||||
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<ReadPostOutput>);
|
||||
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<HtmlContent>,
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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<ReadPostOutput>,
|
||||
default_template: Input<Templates>,
|
||||
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<Entry>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone)]
|
||||
#[derive(PartialEq, Clone, Serialize)]
|
||||
struct Entry {
|
||||
permalink: String,
|
||||
metadata: PostMetadata,
|
||||
title: String,
|
||||
}
|
||||
|
||||
#[derive(InputVisitable)]
|
||||
struct MakeWriteTagPages {
|
||||
tags: DynamicInput<TagAndPosts>,
|
||||
node_factory: DynamicNodeFactory<String, ()>,
|
||||
#[ignore_input]
|
||||
templates: Input<Templates>,
|
||||
build_context_factory: DynamicNodeFactory<String, Context>,
|
||||
render_factory: DynamicNodeFactory<String, ()>,
|
||||
}
|
||||
impl MakeWriteTagPages {
|
||||
fn new(tags: DynamicInput<TagAndPosts>) -> Self {
|
||||
fn new(tags: DynamicInput<TagAndPosts>, templates: Input<Templates>) -> 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<Input<Self::ChildOutput>> {
|
||||
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<TagAndPosts>);
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
131
src/generator/templates.rs
Normal file
131
src/generator/templates.rs
Normal file
@ -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<Templates> {
|
||||
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<Templates>);
|
||||
impl AddTemplate {
|
||||
pub fn new(name: &str, path: PathBuf, base: Input<Templates>) -> 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<String>,
|
||||
tera: Tera,
|
||||
}
|
||||
|
||||
impl NodeValue for Templates {
|
||||
fn node_value_eq(&self, other: &Self) -> bool {
|
||||
self.templates == other.templates
|
||||
}
|
||||
}
|
||||
|
||||
static DOMAIN: Lazy<String> =
|
||||
Lazy::new(|| std::env::var("DOMAIN").unwrap_or("shadowfacts.net".to_owned()));
|
||||
static CB: Lazy<u64> = Lazy::new(|| {
|
||||
SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs()
|
||||
});
|
||||
|
||||
#[derive(InputVisitable)]
|
||||
pub struct BuildTemplateContext<T, F> {
|
||||
permalink: Cow<'static, str>,
|
||||
input: Input<T>,
|
||||
func: F,
|
||||
}
|
||||
impl<T, F: Fn(&T, &mut Context) -> ()> BuildTemplateContext<T, F> {
|
||||
pub fn new(permalink: Cow<'static, str>, input: Input<T>, func: F) -> Self {
|
||||
Self {
|
||||
permalink,
|
||||
input,
|
||||
func,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T: 'static, F: Fn(&T, &mut Context) -> () + 'static> Rule for BuildTemplateContext<T, F> {
|
||||
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<Templates>,
|
||||
pub context: Input<Context>,
|
||||
}
|
||||
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())
|
||||
}
|
||||
}
|
@ -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<Path>) -> Result<impl Write, std::io::Erro
|
||||
Ok(BufWriter::new(file))
|
||||
}
|
||||
|
||||
pub fn output_rendered_template(
|
||||
template: &impl Template,
|
||||
file: impl AsRef<Path>,
|
||||
) -> 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: Write>(W);
|
||||
|
||||
impl<W: Write> std::fmt::Write for FmtWriter<W> {
|
||||
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<D: DeserializeOwned>(contents: &str) -> anyhow::Result<(D, &str)> {
|
||||
let mut chars = contents.char_indices();
|
||||
for i in 0..=2 {
|
||||
|
@ -1,59 +1,53 @@
|
||||
use chrono::{DateTime, Local};
|
||||
use once_cell::sync::Lazy;
|
||||
use std::time::SystemTime;
|
||||
// static DOMAIN: Lazy<String> =
|
||||
// Lazy::new(|| std::env::var("DOMAIN").unwrap_or("shadowfacts.net".to_owned()));
|
||||
|
||||
static DOMAIN: Lazy<String> =
|
||||
Lazy::new(|| std::env::var("DOMAIN").unwrap_or("shadowfacts.net".to_owned()));
|
||||
// static CB: Lazy<u64> = Lazy::new(|| {
|
||||
// SystemTime::now()
|
||||
// .duration_since(SystemTime::UNIX_EPOCH)
|
||||
// .unwrap()
|
||||
// .as_secs()
|
||||
// });
|
||||
|
||||
static CB: Lazy<u64> = Lazy::new(|| {
|
||||
SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs()
|
||||
});
|
||||
// static GENERATED_AT: Lazy<DateTime<Local>> = Lazy::new(|| Local::now());
|
||||
|
||||
static GENERATED_AT: Lazy<DateTime<Local>> = 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<Local> {
|
||||
&*GENERATED_AT
|
||||
}
|
||||
}
|
||||
// fn generated_at() -> &'static DateTime<Local> {
|
||||
// &*GENERATED_AT
|
||||
// }
|
||||
// }
|
||||
|
||||
pub mod filters {
|
||||
use std::fmt::Display;
|
||||
|
||||
use chrono::{DateTime, Datelike, NaiveDate, TimeZone, Utc};
|
||||
|
||||
pub fn iso_date(date: &NaiveDate) -> askama::Result<String> {
|
||||
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<Tz>(datetime: &DateTime<Tz>) -> askama::Result<String>
|
||||
pub fn iso_datetime<Tz>(datetime: &DateTime<Tz>) -> 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<String> {
|
||||
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<Tz>(datetime: &DateTime<Tz>) -> askama::Result<String>
|
||||
pub fn pretty_datetime<Tz>(datetime: &DateTime<Tz>) -> 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<u32> {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user