From 3e7fbea3bbc7499cb0f7045f42c9140a2dbe4101 Mon Sep 17 00:00:00 2001
From: slonkazoid
Date: Thu, 18 Apr 2024 04:05:38 +0300
Subject: [PATCH] initial slonkmit
---
.gitignore | 7 +
Cargo.lock | 2639 +++++++++++++++++++++++++++++++
Cargo.toml | 50 +
README.md | 19 +
posts/README.md | 1 +
src/append_path.rs | 20 +
src/bin/syntect-to-css.rs | 76 +
src/compress.rs | 60 +
src/config.rs | 121 ++
src/error.rs | 50 +
src/filters.rs | 11 +
src/hash_arc_store.rs | 51 +
src/main.rs | 352 +++++
src/markdown_render.rs | 63 +
src/post/cache.rs | 160 ++
src/post/mod.rs | 229 +++
src/watcher.rs | 76 +
static/post.css | 29 +
static/style.css | 134 ++
templates/error.html | 16 +
templates/index.html | 36 +
templates/post.html | 20 +
templates/post_list.html | 0
templates/post_preview.html | 0
templates/view_post.html | 35 +
themes/Catppuccin Mocha.tmTheme | 2021 +++++++++++++++++++++++
26 files changed, 6276 insertions(+)
create mode 100644 .gitignore
create mode 100644 Cargo.lock
create mode 100644 Cargo.toml
create mode 100644 README.md
create mode 120000 posts/README.md
create mode 100644 src/append_path.rs
create mode 100644 src/bin/syntect-to-css.rs
create mode 100644 src/compress.rs
create mode 100644 src/config.rs
create mode 100644 src/error.rs
create mode 100644 src/filters.rs
create mode 100644 src/hash_arc_store.rs
create mode 100644 src/main.rs
create mode 100644 src/markdown_render.rs
create mode 100644 src/post/cache.rs
create mode 100644 src/post/mod.rs
create mode 100644 src/watcher.rs
create mode 100644 static/post.css
create mode 100644 static/style.css
create mode 100644 templates/error.html
create mode 100644 templates/index.html
create mode 100644 templates/post.html
create mode 100644 templates/post_list.html
create mode 100644 templates/post_preview.html
create mode 100644 templates/view_post.html
create mode 100644 themes/Catppuccin Mocha.tmTheme
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..fefcd46
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,7 @@
+/target
+/static/**/*.gz
+/media/*
+/posts/*
+!/posts/README.md
+/.slbg-cache
+/config.toml
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..1e020b4
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,2639 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "addr2line"
+version = "0.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
+dependencies = [
+ "gimli",
+]
+
+[[package]]
+name = "adler"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "android-tzdata"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
+
+[[package]]
+name = "android_system_properties"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "anstream"
+version = "0.6.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb"
+dependencies = [
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc"
+
+[[package]]
+name = "anstyle-parse"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle-query"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
+dependencies = [
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "3.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
+dependencies = [
+ "anstyle",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.82"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519"
+
+[[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_axum"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a41603f7cdbf5ac4af60760f17253eb6adf6ec5b6f14a7ed830cf687d375f163"
+dependencies = [
+ "askama",
+ "axum-core 0.4.3",
+ "http 1.1.0",
+]
+
+[[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 2.0.60",
+]
+
+[[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 = "async-compression"
+version = "0.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07dbbf24db18d609b1462965249abdf49129ccad073ec257da372adc83259c60"
+dependencies = [
+ "flate2",
+ "futures-core",
+ "memchr",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "async-stream"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51"
+dependencies = [
+ "async-stream-impl",
+ "futures-core",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "async-stream-impl"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.60",
+]
+
+[[package]]
+name = "async-trait"
+version = "0.1.80"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.60",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80"
+
+[[package]]
+name = "axum"
+version = "0.6.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf"
+dependencies = [
+ "async-trait",
+ "axum-core 0.3.4",
+ "bitflags 1.3.2",
+ "bytes",
+ "futures-util",
+ "http 0.2.12",
+ "http-body 0.4.6",
+ "hyper 0.14.28",
+ "itoa",
+ "matchit",
+ "memchr",
+ "mime",
+ "percent-encoding",
+ "pin-project-lite",
+ "rustversion",
+ "serde",
+ "sync_wrapper 0.1.2",
+ "tower",
+ "tower-layer",
+ "tower-service",
+]
+
+[[package]]
+name = "axum"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf"
+dependencies = [
+ "async-trait",
+ "axum-core 0.4.3",
+ "axum-macros",
+ "bytes",
+ "futures-util",
+ "http 1.1.0",
+ "http-body 1.0.0",
+ "http-body-util",
+ "hyper 1.3.1",
+ "hyper-util",
+ "itoa",
+ "matchit",
+ "memchr",
+ "mime",
+ "percent-encoding",
+ "pin-project-lite",
+ "rustversion",
+ "serde",
+ "serde_json",
+ "serde_path_to_error",
+ "serde_urlencoded",
+ "sync_wrapper 1.0.1",
+ "tokio",
+ "tower",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "axum-core"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c"
+dependencies = [
+ "async-trait",
+ "bytes",
+ "futures-util",
+ "http 0.2.12",
+ "http-body 0.4.6",
+ "mime",
+ "rustversion",
+ "tower-layer",
+ "tower-service",
+]
+
+[[package]]
+name = "axum-core"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3"
+dependencies = [
+ "async-trait",
+ "bytes",
+ "futures-util",
+ "http 1.1.0",
+ "http-body 1.0.0",
+ "http-body-util",
+ "mime",
+ "pin-project-lite",
+ "rustversion",
+ "sync_wrapper 0.1.2",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "axum-macros"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00c055ee2d014ae5981ce1016374e8213682aa14d9bf40e48ab48b5f3ef20eaa"
+dependencies = [
+ "heck 0.4.1",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.60",
+]
+
+[[package]]
+name = "backtrace"
+version = "0.3.71"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d"
+dependencies = [
+ "addr2line",
+ "cc",
+ "cfg-if",
+ "libc",
+ "miniz_oxide",
+ "object",
+ "rustc-demangle",
+]
+
+[[package]]
+name = "base64"
+version = "0.21.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
+
+[[package]]
+name = "basic-toml"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "823388e228f614e9558c6804262db37960ec8821856535f5c3f59913140558f8"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "bincode"
+version = "1.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "bit-set"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1"
+dependencies = [
+ "bit-vec",
+]
+
+[[package]]
+name = "bit-vec"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
+
+[[package]]
+name = "bitcode"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48bc1c27654127a24c476d40198746860ef56475f41a601bfa5c4d0f832968f0"
+dependencies = [
+ "bitcode_derive",
+ "bytemuck",
+ "serde",
+]
+
+[[package]]
+name = "bitcode_derive"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2966755a19aad59ee2aae91e2d48842c667a99d818ec72168efdab07200701cc"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.60",
+]
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bitflags"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
+
+[[package]]
+name = "bumpalo"
+version = "3.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
+
+[[package]]
+name = "bytemuck"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15"
+
+[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
+[[package]]
+name = "bytes"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9"
+
+[[package]]
+name = "cc"
+version = "1.0.94"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17f6e324229dc011159fcc089755d1e2e216a90d43a7dea6853ca740b84f35e7"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "chrono"
+version = "0.4.38"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
+dependencies = [
+ "android-tzdata",
+ "iana-time-zone",
+ "js-sys",
+ "num-traits",
+ "serde",
+ "wasm-bindgen",
+ "windows-targets 0.52.5",
+]
+
+[[package]]
+name = "clap"
+version = "4.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0"
+dependencies = [
+ "clap_builder",
+ "clap_derive",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "clap_lex",
+ "strsim 0.11.1",
+ "terminal_size",
+]
+
+[[package]]
+name = "clap_derive"
+version = "4.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64"
+dependencies = [
+ "heck 0.5.0",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.60",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
+
+[[package]]
+name = "color-eyre"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "55146f5e46f237f7423d74111267d4597b59b0dad0ffaf7303bce9945d843ad5"
+dependencies = [
+ "backtrace",
+ "color-spantrace",
+ "eyre",
+ "indenter",
+ "once_cell",
+ "owo-colors",
+ "tracing-error",
+]
+
+[[package]]
+name = "color-spantrace"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd6be1b2a7e382e2b98b43b2adcca6bb0e465af0bdd38123873ae61eb17a72c2"
+dependencies = [
+ "once_cell",
+ "owo-colors",
+ "tracing-core",
+ "tracing-error",
+]
+
+[[package]]
+name = "colorchoice"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
+
+[[package]]
+name = "comrak"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0436149c9f6a1935b13306206c739b1ba84fa81f551b5eb87fc2ca7a13700af"
+dependencies = [
+ "clap",
+ "derive_builder",
+ "entities",
+ "memchr",
+ "once_cell",
+ "regex",
+ "shell-words",
+ "slug",
+ "syntect",
+ "typed-arena",
+ "unicode_categories",
+ "xdg",
+]
+
+[[package]]
+name = "console-api"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd326812b3fd01da5bb1af7d340d0d555fd3d4b641e7f1dfcf5962a902952787"
+dependencies = [
+ "futures-core",
+ "prost",
+ "prost-types",
+ "tonic",
+ "tracing-core",
+]
+
+[[package]]
+name = "console-subscriber"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7481d4c57092cd1c19dd541b92bdce883de840df30aa5d03fd48a3935c01842e"
+dependencies = [
+ "console-api",
+ "crossbeam-channel",
+ "crossbeam-utils",
+ "futures-task",
+ "hdrhistogram",
+ "humantime",
+ "prost-types",
+ "serde",
+ "serde_json",
+ "thread_local",
+ "tokio",
+ "tokio-stream",
+ "tonic",
+ "tracing",
+ "tracing-core",
+ "tracing-subscriber",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
+
+[[package]]
+name = "crc32fast"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "crossbeam-channel"
+version = "0.5.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab3db02a9c5b5121e1e42fbdb1aeb65f5e02624cc58c43f2884c6ccac0b82f95"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
+
+[[package]]
+name = "darling"
+version = "0.14.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850"
+dependencies = [
+ "darling_core",
+ "darling_macro",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.14.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0"
+dependencies = [
+ "fnv",
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim 0.10.0",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.14.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e"
+dependencies = [
+ "darling_core",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "deranged"
+version = "0.3.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
+dependencies = [
+ "powerfmt",
+]
+
+[[package]]
+name = "derive_builder"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d67778784b508018359cbc8696edb3db78160bab2c2a28ba7f56ef6932997f8"
+dependencies = [
+ "derive_builder_macro",
+]
+
+[[package]]
+name = "derive_builder_core"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c11bdc11a0c47bc7d37d582b5285da6849c96681023680b906673c5707af7b0f"
+dependencies = [
+ "darling",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "derive_builder_macro"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebcda35c7a396850a55ffeac740804b40ffec779b98fffbb1738f4033f0ee79e"
+dependencies = [
+ "derive_builder_core",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "deunicode"
+version = "1.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "322ef0094744e63628e6f0eb2295517f79276a5b342a4c2ff3042566ca181d4e"
+
+[[package]]
+name = "either"
+version = "1.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2"
+
+[[package]]
+name = "entities"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5320ae4c3782150d900b79807611a59a99fc9a1d61d686faafc24b93fc8d7ca"
+
+[[package]]
+name = "equivalent"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
+
+[[package]]
+name = "errno"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
+dependencies = [
+ "libc",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "eyre"
+version = "0.6.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec"
+dependencies = [
+ "indenter",
+ "once_cell",
+]
+
+[[package]]
+name = "fancy-regex"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b95f7c0680e4142284cf8b22c14a476e87d61b004a3a0861872b32ef7ead40a2"
+dependencies = [
+ "bit-set",
+ "regex",
+]
+
+[[package]]
+name = "filetime"
+version = "0.2.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "flate2"
+version = "1.0.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e"
+dependencies = [
+ "crc32fast",
+ "miniz_oxide",
+]
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "form_urlencoded"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
+dependencies = [
+ "percent-encoding",
+]
+
+[[package]]
+name = "fronma"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da11047dc6b6b3f21012056a0f9177e435a0ce4f34e1c5e7990b01342c0d4e49"
+dependencies = [
+ "serde",
+ "serde_yaml",
+ "toml 0.5.11",
+]
+
+[[package]]
+name = "fsevent-sys"
+version = "4.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
+dependencies = [
+ "futures-core",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
+
+[[package]]
+name = "futures-macro"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.60",
+]
+
+[[package]]
+name = "futures-sink"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
+
+[[package]]
+name = "futures-task"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
+
+[[package]]
+name = "futures-util"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
+dependencies = [
+ "futures-core",
+ "futures-macro",
+ "futures-task",
+ "pin-project-lite",
+ "pin-utils",
+ "slab",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "gimli"
+version = "0.28.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
+
+[[package]]
+name = "h2"
+version = "0.3.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8"
+dependencies = [
+ "bytes",
+ "fnv",
+ "futures-core",
+ "futures-sink",
+ "futures-util",
+ "http 0.2.12",
+ "indexmap 2.2.6",
+ "slab",
+ "tokio",
+ "tokio-util",
+ "tracing",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+
+[[package]]
+name = "hashbrown"
+version = "0.14.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
+
+[[package]]
+name = "hdrhistogram"
+version = "7.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "765c9198f173dd59ce26ff9f95ef0aafd0a0fe01fb9d72841bc5066a4c06511d"
+dependencies = [
+ "base64",
+ "byteorder",
+ "flate2",
+ "nom",
+ "num-traits",
+]
+
+[[package]]
+name = "heck"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
+
+[[package]]
+name = "heck"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+
+[[package]]
+name = "hermit-abi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
+
+[[package]]
+name = "http"
+version = "0.2.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1"
+dependencies = [
+ "bytes",
+ "fnv",
+ "itoa",
+]
+
+[[package]]
+name = "http"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258"
+dependencies = [
+ "bytes",
+ "fnv",
+ "itoa",
+]
+
+[[package]]
+name = "http-body"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2"
+dependencies = [
+ "bytes",
+ "http 0.2.12",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "http-body"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643"
+dependencies = [
+ "bytes",
+ "http 1.1.0",
+]
+
+[[package]]
+name = "http-body-util"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "http 1.1.0",
+ "http-body 1.0.0",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "http-range-header"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ce4ef31cda248bbdb6e6820603b82dfcd9e833db65a43e997a0ccec777d11fe"
+
+[[package]]
+name = "httparse"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
+
+[[package]]
+name = "httpdate"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
+
+[[package]]
+name = "humansize"
+version = "2.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7"
+dependencies = [
+ "libm",
+]
+
+[[package]]
+name = "humantime"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
+
+[[package]]
+name = "hyper"
+version = "0.14.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80"
+dependencies = [
+ "bytes",
+ "futures-channel",
+ "futures-core",
+ "futures-util",
+ "h2",
+ "http 0.2.12",
+ "http-body 0.4.6",
+ "httparse",
+ "httpdate",
+ "itoa",
+ "pin-project-lite",
+ "socket2",
+ "tokio",
+ "tower-service",
+ "tracing",
+ "want",
+]
+
+[[package]]
+name = "hyper"
+version = "1.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d"
+dependencies = [
+ "bytes",
+ "futures-channel",
+ "futures-util",
+ "http 1.1.0",
+ "http-body 1.0.0",
+ "httparse",
+ "httpdate",
+ "itoa",
+ "pin-project-lite",
+ "smallvec",
+ "tokio",
+]
+
+[[package]]
+name = "hyper-timeout"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1"
+dependencies = [
+ "hyper 0.14.28",
+ "pin-project-lite",
+ "tokio",
+ "tokio-io-timeout",
+]
+
+[[package]]
+name = "hyper-util"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa"
+dependencies = [
+ "bytes",
+ "futures-util",
+ "http 1.1.0",
+ "http-body 1.0.0",
+ "hyper 1.3.1",
+ "pin-project-lite",
+ "socket2",
+ "tokio",
+]
+
+[[package]]
+name = "iana-time-zone"
+version = "0.1.60"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "wasm-bindgen",
+ "windows-core",
+]
+
+[[package]]
+name = "iana-time-zone-haiku"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "ident_case"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
+
+[[package]]
+name = "indenter"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
+
+[[package]]
+name = "indexmap"
+version = "1.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
+dependencies = [
+ "autocfg",
+ "hashbrown 0.12.3",
+]
+
+[[package]]
+name = "indexmap"
+version = "2.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
+dependencies = [
+ "equivalent",
+ "hashbrown 0.14.3",
+]
+
+[[package]]
+name = "inotify"
+version = "0.9.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff"
+dependencies = [
+ "bitflags 1.3.2",
+ "inotify-sys",
+ "libc",
+]
+
+[[package]]
+name = "inotify-sys"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "itertools"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
+
+[[package]]
+name = "js-sys"
+version = "0.3.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
+dependencies = [
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "kqueue"
+version = "1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c"
+dependencies = [
+ "kqueue-sys",
+ "libc",
+]
+
+[[package]]
+name = "kqueue-sys"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b"
+dependencies = [
+ "bitflags 1.3.2",
+ "libc",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "libc"
+version = "0.2.153"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
+
+[[package]]
+name = "libm"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
+
+[[package]]
+name = "line-wrap"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd1bc4d24ad230d21fb898d1116b1801d7adfc449d42026475862ab48b11e70e"
+
+[[package]]
+name = "linked-hash-map"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.4.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
+
+[[package]]
+name = "lock_api"
+version = "0.4.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45"
+dependencies = [
+ "autocfg",
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
+
+[[package]]
+name = "matchers"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
+dependencies = [
+ "regex-automata 0.1.10",
+]
+
+[[package]]
+name = "matchit"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94"
+
+[[package]]
+name = "memchr"
+version = "2.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
+
+[[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.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef"
+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.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7"
+dependencies = [
+ "adler",
+]
+
+[[package]]
+name = "mio"
+version = "0.8.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
+dependencies = [
+ "libc",
+ "log",
+ "wasi",
+ "windows-sys 0.48.0",
+]
+
+[[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 = "6.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d"
+dependencies = [
+ "bitflags 2.5.0",
+ "crossbeam-channel",
+ "filetime",
+ "fsevent-sys",
+ "inotify",
+ "kqueue",
+ "libc",
+ "log",
+ "mio",
+ "walkdir",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "nu-ansi-term"
+version = "0.46.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
+dependencies = [
+ "overload",
+ "winapi",
+]
+
+[[package]]
+name = "num-conv"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
+
+[[package]]
+name = "num-traits"
+version = "0.2.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "num_cpus"
+version = "1.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
+dependencies = [
+ "hermit-abi",
+ "libc",
+]
+
+[[package]]
+name = "object"
+version = "0.32.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
+
+[[package]]
+name = "onig"
+version = "6.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c4b31c8722ad9171c6d77d3557db078cab2bd50afcc9d09c8b315c59df8ca4f"
+dependencies = [
+ "bitflags 1.3.2",
+ "libc",
+ "once_cell",
+ "onig_sys",
+]
+
+[[package]]
+name = "onig_sys"
+version = "69.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b829e3d7e9cc74c7e315ee8edb185bf4190da5acde74afd7fc59c35b1f086e7"
+dependencies = [
+ "cc",
+ "pkg-config",
+]
+
+[[package]]
+name = "overload"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
+
+[[package]]
+name = "owo-colors"
+version = "3.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f"
+
+[[package]]
+name = "parking_lot"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
+dependencies = [
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "percent-encoding"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
+
+[[package]]
+name = "pin-project"
+version = "1.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3"
+dependencies = [
+ "pin-project-internal",
+]
+
+[[package]]
+name = "pin-project-internal"
+version = "1.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.60",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "pkg-config"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
+
+[[package]]
+name = "plist"
+version = "1.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9d34169e64b3c7a80c8621a48adaf44e0cf62c78a9b25dd9dd35f1881a17cf9"
+dependencies = [
+ "base64",
+ "indexmap 2.2.6",
+ "line-wrap",
+ "quick-xml",
+ "serde",
+ "time",
+]
+
+[[package]]
+name = "powerfmt"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.81"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "prost"
+version = "0.12.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0f5d036824e4761737860779c906171497f6d55681139d8312388f8fe398922"
+dependencies = [
+ "bytes",
+ "prost-derive",
+]
+
+[[package]]
+name = "prost-derive"
+version = "0.12.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19de2de2a00075bf566bee3bd4db014b11587e84184d3f7a791bc17f1a8e9e48"
+dependencies = [
+ "anyhow",
+ "itertools",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.60",
+]
+
+[[package]]
+name = "prost-types"
+version = "0.12.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3235c33eb02c1f1e212abdbe34c78b264b038fb58ca612664343271e36e55ffe"
+dependencies = [
+ "prost",
+]
+
+[[package]]
+name = "quick-xml"
+version = "0.31.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
+dependencies = [
+ "bitflags 1.3.2",
+]
+
+[[package]]
+name = "regex"
+version = "1.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata 0.4.6",
+ "regex-syntax 0.8.3",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
+dependencies = [
+ "regex-syntax 0.6.29",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax 0.8.3",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
+
+[[package]]
+name = "rustc-demangle"
+version = "0.1.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
+
+[[package]]
+name = "rustix"
+version = "0.38.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89"
+dependencies = [
+ "bitflags 2.5.0",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "rustversion"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80af6f9131f277a45a3fba6ce8e2258037bb0477a67e610d3c1fe046ab31de47"
+
+[[package]]
+name = "ryu"
+version = "1.0.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"
+
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "scc"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec96560eea317a9cc4e0bb1f6a2c93c09a19b8c4fc5cb3fcc0ec1c094cd783e2"
+dependencies = [
+ "sdd",
+]
+
+[[package]]
+name = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
+[[package]]
+name = "sdd"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b84345e4c9bd703274a082fb80caaa99b7612be48dfaa1dd9266577ec412309d"
+
+[[package]]
+name = "serde"
+version = "1.0.198"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.198"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.60",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.116"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "serde_path_to_error"
+version = "0.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6"
+dependencies = [
+ "itoa",
+ "serde",
+]
+
+[[package]]
+name = "serde_spanned"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "serde_urlencoded"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
+dependencies = [
+ "form_urlencoded",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "serde_yaml"
+version = "0.8.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "578a7433b776b56a35785ed5ce9a7e777ac0598aac5a6dd1b4b18a307c7fc71b"
+dependencies = [
+ "indexmap 1.9.3",
+ "ryu",
+ "serde",
+ "yaml-rust",
+]
+
+[[package]]
+name = "sharded-slab"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
+name = "shell-words"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde"
+
+[[package]]
+name = "signal-hook-registry"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "silly-blog"
+version = "0.1.0"
+dependencies = [
+ "askama",
+ "askama_axum",
+ "async-compression",
+ "axum 0.7.5",
+ "bitcode",
+ "chrono",
+ "clap",
+ "color-eyre",
+ "comrak",
+ "console-subscriber",
+ "fronma",
+ "futures-util",
+ "notify",
+ "scc",
+ "serde",
+ "syntect",
+ "thiserror",
+ "tokio",
+ "tokio-util",
+ "toml 0.8.12",
+ "tower-http",
+ "tracing",
+ "tracing-subscriber",
+]
+
+[[package]]
+name = "slab"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "slug"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3bd94acec9c8da640005f8e135a39fc0372e74535e6b368b7a04b875f784c8c4"
+dependencies = [
+ "deunicode",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "smallvec"
+version = "1.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
+
+[[package]]
+name = "socket2"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871"
+dependencies = [
+ "libc",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "strsim"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
+
+[[package]]
+name = "strsim"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
+
+[[package]]
+name = "syn"
+version = "1.0.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.60"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "sync_wrapper"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
+
+[[package]]
+name = "sync_wrapper"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394"
+
+[[package]]
+name = "syntect"
+version = "5.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "874dcfa363995604333cf947ae9f751ca3af4522c60886774c4963943b4746b1"
+dependencies = [
+ "bincode",
+ "bitflags 1.3.2",
+ "fancy-regex",
+ "flate2",
+ "fnv",
+ "once_cell",
+ "onig",
+ "plist",
+ "regex-syntax 0.8.3",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "thiserror",
+ "walkdir",
+ "yaml-rust",
+]
+
+[[package]]
+name = "terminal_size"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7"
+dependencies = [
+ "rustix",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.58"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.58"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.60",
+]
+
+[[package]]
+name = "thread_local"
+version = "1.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+]
+
+[[package]]
+name = "time"
+version = "0.3.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885"
+dependencies = [
+ "deranged",
+ "itoa",
+ "num-conv",
+ "powerfmt",
+ "serde",
+ "time-core",
+ "time-macros",
+]
+
+[[package]]
+name = "time-core"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
+
+[[package]]
+name = "time-macros"
+version = "0.2.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf"
+dependencies = [
+ "num-conv",
+ "time-core",
+]
+
+[[package]]
+name = "tokio"
+version = "1.37.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787"
+dependencies = [
+ "backtrace",
+ "bytes",
+ "libc",
+ "mio",
+ "num_cpus",
+ "parking_lot",
+ "pin-project-lite",
+ "signal-hook-registry",
+ "socket2",
+ "tokio-macros",
+ "tracing",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "tokio-io-timeout"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf"
+dependencies = [
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-macros"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.60",
+]
+
+[[package]]
+name = "tokio-stream"
+version = "0.1.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af"
+dependencies = [
+ "futures-core",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-util"
+version = "0.7.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "futures-sink",
+ "pin-project-lite",
+ "tokio",
+ "tracing",
+]
+
+[[package]]
+name = "toml"
+version = "0.5.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "toml"
+version = "0.8.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3"
+dependencies = [
+ "serde",
+ "serde_spanned",
+ "toml_datetime",
+ "toml_edit",
+]
+
+[[package]]
+name = "toml_datetime"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "toml_edit"
+version = "0.22.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e40bb779c5187258fd7aad0eb68cb8706a0a81fa712fbea808ab43c4b8374c4"
+dependencies = [
+ "indexmap 2.2.6",
+ "serde",
+ "serde_spanned",
+ "toml_datetime",
+ "winnow",
+]
+
+[[package]]
+name = "tonic"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d560933a0de61cf715926b9cac824d4c883c2c43142f787595e48280c40a1d0e"
+dependencies = [
+ "async-stream",
+ "async-trait",
+ "axum 0.6.20",
+ "base64",
+ "bytes",
+ "h2",
+ "http 0.2.12",
+ "http-body 0.4.6",
+ "hyper 0.14.28",
+ "hyper-timeout",
+ "percent-encoding",
+ "pin-project",
+ "prost",
+ "tokio",
+ "tokio-stream",
+ "tower",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "tower"
+version = "0.4.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
+dependencies = [
+ "futures-core",
+ "futures-util",
+ "indexmap 1.9.3",
+ "pin-project",
+ "pin-project-lite",
+ "rand",
+ "slab",
+ "tokio",
+ "tokio-util",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "tower-http"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5"
+dependencies = [
+ "async-compression",
+ "bitflags 2.5.0",
+ "bytes",
+ "futures-core",
+ "futures-util",
+ "http 1.1.0",
+ "http-body 1.0.0",
+ "http-body-util",
+ "http-range-header",
+ "httpdate",
+ "mime",
+ "mime_guess",
+ "percent-encoding",
+ "pin-project-lite",
+ "tokio",
+ "tokio-util",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "tower-layer"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0"
+
+[[package]]
+name = "tower-service"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
+
+[[package]]
+name = "tracing"
+version = "0.1.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
+dependencies = [
+ "log",
+ "pin-project-lite",
+ "tracing-attributes",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-attributes"
+version = "0.1.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.60",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
+dependencies = [
+ "once_cell",
+ "valuable",
+]
+
+[[package]]
+name = "tracing-error"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e"
+dependencies = [
+ "tracing",
+ "tracing-subscriber",
+]
+
+[[package]]
+name = "tracing-log"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
+dependencies = [
+ "log",
+ "once_cell",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-subscriber"
+version = "0.3.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
+dependencies = [
+ "matchers",
+ "nu-ansi-term",
+ "once_cell",
+ "regex",
+ "sharded-slab",
+ "smallvec",
+ "thread_local",
+ "tracing",
+ "tracing-core",
+ "tracing-log",
+]
+
+[[package]]
+name = "try-lock"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
+
+[[package]]
+name = "typed-arena"
+version = "2.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a"
+
+[[package]]
+name = "unicase"
+version = "2.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89"
+dependencies = [
+ "version_check",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+
+[[package]]
+name = "unicode_categories"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
+
+[[package]]
+name = "utf8parse"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
+
+[[package]]
+name = "valuable"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
+
+[[package]]
+name = "version_check"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+
+[[package]]
+name = "walkdir"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
+[[package]]
+name = "want"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
+dependencies = [
+ "try-lock",
+]
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
+dependencies = [
+ "bumpalo",
+ "log",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.60",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.60",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows-core"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
+dependencies = [
+ "windows-targets 0.52.5",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets 0.52.5",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
+dependencies = [
+ "windows_aarch64_gnullvm 0.48.5",
+ "windows_aarch64_msvc 0.48.5",
+ "windows_i686_gnu 0.48.5",
+ "windows_i686_msvc 0.48.5",
+ "windows_x86_64_gnu 0.48.5",
+ "windows_x86_64_gnullvm 0.48.5",
+ "windows_x86_64_msvc 0.48.5",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.5",
+ "windows_aarch64_msvc 0.52.5",
+ "windows_i686_gnu 0.52.5",
+ "windows_i686_gnullvm",
+ "windows_i686_msvc 0.52.5",
+ "windows_x86_64_gnu 0.52.5",
+ "windows_x86_64_gnullvm 0.52.5",
+ "windows_x86_64_msvc 0.52.5",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
+
+[[package]]
+name = "winnow"
+version = "0.6.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0c976aaaa0e1f90dbb21e9587cdaf1d9679a1cde8875c0d6bd83ab96a208352"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "xdg"
+version = "2.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "213b7324336b53d2414b2db8537e56544d981803139155afa84f76eeebb7a546"
+
+[[package]]
+name = "yaml-rust"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
+dependencies = [
+ "linked-hash-map",
+]
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..57e70ae
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,50 @@
+[package]
+name = "silly-blog"
+version = "0.1.0"
+edition = "2021"
+default-run = "silly-blog"
+
+[[bin]]
+name = "syntect-to-css"
+required-features = ["clap"]
+
+[features]
+default = ["precompression"]
+tokio-console = ["dep:console-subscriber"]
+clap = ["dep:clap"]
+precompression = ["dep:async-compression"]
+
+[profile.release]
+lto = "fat"
+opt-level = 3
+codegen-units = 1
+strip = true
+
+[dependencies]
+askama = { version = "0.12.1", features = ["with-axum"] }
+askama_axum = "0.4.0"
+async-compression = { version = "0.4.8", optional = true }
+axum = { version = "0.7.5", features = ["macros"] }
+bitcode = { version = "0.6.0", features = ["serde"] }
+chrono = { version = "0.4.37", features = ["serde"] }
+clap = { version = "4.5.4", features = ["derive"], optional = true }
+color-eyre = "0.6.3"
+comrak = { version = "0.22.0", features = ["syntect"] }
+console-subscriber = { version = "0.2.0", optional = true }
+fronma = { version = "0.2.0", features = ["toml"] }
+futures-util = "0.3.30"
+notify = "6.1.1"
+scc = "2.1.0"
+serde = { version = "1.0.197", features = ["derive"] }
+syntect = "5.2.0"
+thiserror = "1.0.58"
+tokio = { version = "1.37.0", features = ["full"] }
+tokio-util = "0.7.10"
+toml = "0.8.12"
+tower-http = { version = "0.5.2", features = [
+ "compression-gzip",
+ "fs",
+ "trace",
+], default-features = false }
+tracing = "0.1.40"
+tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..225d963
--- /dev/null
+++ b/README.md
@@ -0,0 +1,19 @@
+---
+title = "README"
+description = "the README.md file of this project"
+author = "slonkazoid"
+---
+
+# silly-blog
+
+blazingly fast markdown blog software written in rust memory safe
+
+## TODO
+
+- [ ] finish writing this document
+- [ ] document config
+- [ ] extend syntect options
+- [ ] general cleanup of code
+- [x] be blazingly fast
+- [x] 100+ MiB binary size
+
diff --git a/posts/README.md b/posts/README.md
new file mode 120000
index 0000000..32d46ee
--- /dev/null
+++ b/posts/README.md
@@ -0,0 +1 @@
+../README.md
\ No newline at end of file
diff --git a/src/append_path.rs b/src/append_path.rs
new file mode 100644
index 0000000..ea742b8
--- /dev/null
+++ b/src/append_path.rs
@@ -0,0 +1,20 @@
+use std::{
+ ffi::{OsStr, OsString},
+ path::{Path, PathBuf},
+};
+
+// i will kill you rust stdlib
+pub trait Append
+where
+ Self: Into,
+ T: From,
+{
+ fn append(self, ext: impl AsRef) -> T {
+ let mut buffer: OsString = self.into();
+ buffer.push(ext.as_ref());
+ T::from(buffer)
+ }
+}
+
+impl Append for PathBuf {}
+impl Append for &Path {}
diff --git a/src/bin/syntect-to-css.rs b/src/bin/syntect-to-css.rs
new file mode 100644
index 0000000..3124cbb
--- /dev/null
+++ b/src/bin/syntect-to-css.rs
@@ -0,0 +1,76 @@
+use std::fs::File;
+use std::io::BufReader;
+use std::path::PathBuf;
+
+use clap::Parser;
+use color_eyre::eyre::{self, Context, Ok, OptionExt};
+use syntect::highlighting::{Theme, ThemeSet};
+use syntect::html::{css_for_theme_with_class_style, ClassStyle};
+
+#[derive(Parser, Debug)]
+#[command(about = "generate CSS from a syntect theme")]
+struct Args {
+ #[command(subcommand)]
+ command: Command,
+ #[arg(
+ short,
+ long,
+ help = "prefix for generated classes",
+ default_value = "syntect-"
+ )]
+ prefix: String,
+ #[arg(
+ long,
+ help = "don't add a prefix to generated classes",
+ default_value_t = false
+ )]
+ no_prefix: bool,
+}
+
+#[derive(Parser, Debug)]
+enum Command {
+ #[command(about = "generate CSS from a theme in the default theme set")]
+ Default {
+ #[arg(help = "name of theme (no .tmTheme)")]
+ theme_name: String,
+ },
+ #[command(about = "generate CSS from a .tmTheme file")]
+ File {
+ #[arg(help = "path to theme (including .tmTheme)")]
+ path: PathBuf,
+ },
+}
+
+fn main() -> eyre::Result<()> {
+ let args = Args::parse();
+ color_eyre::install()?;
+
+ let theme = match args.command {
+ Command::Default { theme_name } => {
+ let ts = ThemeSet::load_defaults();
+ ts.themes
+ .get(&theme_name)
+ .ok_or_eyre(format!("theme {:?} doesn't exist", theme_name))?
+ .to_owned()
+ }
+ Command::File { path } => {
+ let mut file = BufReader::new(
+ File::open(&path).with_context(|| format!("failed to open {:?}", path))?,
+ );
+ ThemeSet::load_from_reader(&mut file).with_context(|| "failed to parse theme")?
+ }
+ };
+
+ let class_style = if args.no_prefix {
+ ClassStyle::Spaced
+ } else {
+ ClassStyle::SpacedPrefixed {
+ prefix: args.prefix.leak(),
+ }
+ };
+
+ let css = css_for_theme_with_class_style(&theme, class_style)
+ .with_context(|| "failed to generate css")?;
+ println!("{css}");
+ Ok(())
+}
diff --git a/src/compress.rs b/src/compress.rs
new file mode 100644
index 0000000..eb42ebd
--- /dev/null
+++ b/src/compress.rs
@@ -0,0 +1,60 @@
+// TODO: make this bearable
+
+use std::{
+ fs::{self, Metadata},
+ io::{self, Result},
+ path::Path,
+ process::{Child, Command},
+ sync::Mutex,
+};
+
+fn compress_file(path: &Path, metadata: Metadata, handles: &Mutex>) -> Result<()> {
+ let compressed_file = format!("{}.gz", path.to_str().unwrap());
+ if match fs::metadata(compressed_file) {
+ Ok(existing_metadata) => metadata.modified()? > existing_metadata.modified()?,
+ Err(err) => match err.kind() {
+ io::ErrorKind::NotFound => true,
+ _ => return Err(err),
+ },
+ } {
+ let mut handles_guard = handles.lock().unwrap();
+ handles_guard.push(Command::new("gzip").arg("-kf5").arg(path).spawn()?);
+ }
+ Ok(())
+}
+
+fn compress_recursively(path: &Path, handles: &Mutex>) -> Result<()> {
+ let metadata = fs::metadata(path)?;
+
+ if metadata.is_dir() {
+ for entry in fs::read_dir(path)? {
+ compress_recursively(&entry?.path(), handles)?
+ }
+ Ok(())
+ } else if match path.extension() {
+ Some(ext) => ext == "gz",
+ None => false,
+ } || metadata.is_symlink()
+ {
+ Ok(())
+ } else {
+ compress_file(path, metadata, handles)
+ }
+}
+
+pub fn compress_epicly>(path: P) -> Result {
+ let mut i = 0;
+
+ let handles = Mutex::new(Vec::new());
+
+ compress_recursively(AsRef::::as_ref(&path), &handles)?;
+
+ let handles = handles.into_inner().unwrap();
+
+ for mut handle in handles {
+ assert!(handle.wait().unwrap().success());
+ i += 1;
+ }
+
+ Ok(i)
+}
diff --git a/src/config.rs b/src/config.rs
new file mode 100644
index 0000000..97c8dfa
--- /dev/null
+++ b/src/config.rs
@@ -0,0 +1,121 @@
+use std::{
+ env,
+ net::{IpAddr, Ipv4Addr},
+ path::PathBuf,
+};
+
+use color_eyre::eyre::{bail, Context, Result};
+use serde::{Deserialize, Serialize};
+use tokio::io::{AsyncReadExt, AsyncWriteExt};
+use tracing::{error, info};
+
+#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
+#[serde(default)]
+pub struct RenderConfig {
+ pub syntect_load_defaults: bool,
+ pub syntect_themes_dir: Option,
+ pub syntect_theme: Option,
+}
+
+#[cfg(feature = "precompression")]
+#[derive(Serialize, Deserialize, Debug, Clone)]
+#[serde(default)]
+pub struct PrecompressionConfig {
+ pub enable: bool,
+ pub watch: bool,
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone)]
+#[serde(default)]
+pub struct Config {
+ pub host: IpAddr,
+ pub port: u16,
+ pub title: String,
+ pub description: String,
+ pub posts_dir: PathBuf,
+ pub render: RenderConfig,
+ #[cfg(feature = "precompression")]
+ pub precompression: PrecompressionConfig,
+ pub cache_file: Option,
+}
+
+impl Default for Config {
+ fn default() -> Self {
+ Self {
+ host: IpAddr::V4(Ipv4Addr::UNSPECIFIED),
+ port: 3000,
+ title: "silly-blog".into(),
+ description: "blazingly fast markdown blog software written in rust memory safe".into(),
+ render: Default::default(),
+ posts_dir: "posts".into(),
+ #[cfg(feature = "precompression")]
+ precompression: Default::default(),
+ cache_file: None,
+ }
+ }
+}
+
+impl Default for RenderConfig {
+ fn default() -> Self {
+ Self {
+ syntect_load_defaults: false,
+ syntect_themes_dir: Some("themes".into()),
+ syntect_theme: Some("Catppuccin Mocha".into()),
+ }
+ }
+}
+
+#[cfg(feature = "precompression")]
+impl Default for PrecompressionConfig {
+ fn default() -> Self {
+ Self {
+ enable: false,
+ watch: true,
+ }
+ }
+}
+
+pub async fn load() -> Result {
+ let config_file = env::var(format!("{}_CONFIG", env!("CARGO_BIN_NAME")))
+ .unwrap_or(String::from("config.toml"));
+ match tokio::fs::OpenOptions::new()
+ .read(true)
+ .open(&config_file)
+ .await
+ {
+ Ok(mut file) => {
+ let mut buf = String::new();
+ file.read_to_string(&mut buf)
+ .await
+ .with_context(|| "couldn't read configuration file")?;
+ toml::from_str(&buf).with_context(|| "couldn't parse configuration")
+ }
+ Err(err) => match err.kind() {
+ std::io::ErrorKind::NotFound => {
+ let config = Config::default();
+ info!("configuration file doesn't exist, creating");
+ match tokio::fs::OpenOptions::new()
+ .write(true)
+ .create(true)
+ .truncate(true)
+ .open(&config_file)
+ .await
+ {
+ Ok(mut file) => file
+ .write_all(
+ toml::to_string_pretty(&config)
+ .with_context(|| "couldn't serialize configuration")?
+ .as_bytes(),
+ )
+ .await
+ .unwrap_or_else(|err| error!("couldn't write configuration: {}", err)),
+ Err(err) => {
+ error!("couldn't open file {:?} for writing: {}", &config_file, err)
+ }
+ }
+ Ok(config)
+ }
+ _ => bail!("couldn't open config file: {}", err),
+ },
+ }
+}
diff --git a/src/error.rs b/src/error.rs
new file mode 100644
index 0000000..2c34be4
--- /dev/null
+++ b/src/error.rs
@@ -0,0 +1,50 @@
+use std::fmt::Display;
+
+use axum::{http::StatusCode, response::IntoResponse};
+use thiserror::Error;
+
+// fronma is too lazy to implement std::error::Error for their own types
+#[derive(Debug)]
+#[repr(transparent)]
+pub struct FronmaBalls(fronma::error::Error);
+
+impl std::error::Error for FronmaBalls {}
+
+impl Display for FronmaBalls {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.write_str("failed to parse front matter: ")?;
+ match &self.0 {
+ fronma::error::Error::MissingBeginningLine => f.write_str("missing beginning line"),
+ fronma::error::Error::MissingEndingLine => f.write_str("missing ending line"),
+ fronma::error::Error::SerdeYaml(_) => {
+ unimplemented!("no yaml allowed in this household")
+ }
+ fronma::error::Error::Toml(toml_error) => write!(f, "{}", toml_error),
+ }
+ }
+}
+
+#[derive(Error, Debug)]
+#[allow(clippy::enum_variant_names)]
+pub enum PostError {
+ #[error(transparent)]
+ IoError(#[from] std::io::Error),
+ #[error(transparent)]
+ AskamaError(#[from] askama::Error),
+ #[error(transparent)]
+ ParseError(#[from] FronmaBalls),
+ #[error("post {0:?} not found")]
+ NotFound(String),
+}
+
+impl From for PostError {
+ fn from(value: fronma::error::Error) -> Self {
+ Self::ParseError(FronmaBalls(value))
+ }
+}
+
+impl IntoResponse for PostError {
+ fn into_response(self) -> axum::response::Response {
+ (StatusCode::INTERNAL_SERVER_ERROR, self.to_string()).into_response()
+ }
+}
diff --git a/src/filters.rs b/src/filters.rs
new file mode 100644
index 0000000..75d39cf
--- /dev/null
+++ b/src/filters.rs
@@ -0,0 +1,11 @@
+use std::time::Duration;
+
+use chrono::{DateTime, TimeZone};
+
+pub fn date(date: &DateTime) -> Result {
+ Ok(date.to_rfc3339_opts(chrono::SecondsFormat::Secs, true))
+}
+
+pub fn duration(duration: &&Duration) -> Result {
+ Ok(format!("{:?}", duration))
+}
diff --git a/src/hash_arc_store.rs b/src/hash_arc_store.rs
new file mode 100644
index 0000000..e2398a2
--- /dev/null
+++ b/src/hash_arc_store.rs
@@ -0,0 +1,51 @@
+use std::hash::{DefaultHasher, Hash, Hasher};
+use std::marker::PhantomData;
+use std::sync::Arc;
+
+pub struct HashArcStore
+where
+ Lookup: Hash,
+{
+ inner: Option>,
+ hash: Option,
+ _phantom: PhantomData,
+}
+
+impl HashArcStore
+where
+ Lookup: Hash,
+{
+ pub fn new() -> Self {
+ Self {
+ inner: None,
+ hash: None,
+ _phantom: PhantomData,
+ }
+ }
+
+ /*pub fn get(&self, key: &Lookup) -> Option> {
+ self.hash.and_then(|hash| {
+ let mut h = DefaultHasher::new();
+ key.hash(&mut h);
+ if hash == h.finish() {
+ self.inner.clone()
+ } else {
+ None
+ }
+ })
+ }*/
+
+ pub fn get_or_init(&mut self, key: &Lookup, init: impl Fn(&Lookup) -> Arc) -> Arc {
+ let mut h = DefaultHasher::new();
+ key.hash(&mut h);
+ let hash = h.finish();
+ if !self.hash.is_some_and(|inner_hash| inner_hash == hash) {
+ let mut h = DefaultHasher::new();
+ key.hash(&mut h);
+ self.inner = Some(init(key));
+ self.hash = Some(h.finish());
+ }
+ // safety: please.
+ unsafe { self.inner.as_ref().unwrap_unchecked().clone() }
+ }
+}
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..f6a861a
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,352 @@
+#![feature(let_chains, stmt_expr_attributes, proc_macro_hygiene)]
+
+mod append_path;
+mod compress;
+mod config;
+mod error;
+mod filters;
+mod hash_arc_store;
+mod markdown_render;
+mod post;
+mod watcher;
+
+use std::future::IntoFuture;
+use std::net::SocketAddr;
+use std::process::exit;
+use std::sync::Arc;
+use std::time::Duration;
+
+use askama_axum::Template;
+use axum::extract::{MatchedPath, Path, State};
+use axum::http::{Request, StatusCode};
+use axum::response::{IntoResponse, Redirect, Response};
+use axum::routing::{get, Router};
+use axum::Json;
+use color_eyre::eyre::{self, Context};
+use thiserror::Error;
+use tokio::io::{AsyncReadExt, AsyncWriteExt};
+use tokio::net::TcpListener;
+use tokio::signal;
+use tokio::task::JoinSet;
+use tokio_util::sync::CancellationToken;
+use tower_http::services::ServeDir;
+use tower_http::trace::TraceLayer;
+use tracing::level_filters::LevelFilter;
+use tracing::{error, info, info_span, warn, Span};
+use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
+
+use crate::compress::compress_epicly;
+use crate::config::Config;
+use crate::error::PostError;
+use crate::post::{PostManager, PostMetadata, RenderStats};
+use crate::watcher::watch;
+
+type ArcState = Arc;
+
+#[derive(Clone)]
+struct AppState {
+ pub config: Config,
+ pub posts: PostManager,
+}
+
+#[derive(Template)]
+#[template(path = "index.html")]
+struct IndexTemplate {
+ title: String,
+ description: String,
+ posts: Vec,
+}
+
+#[derive(Template)]
+#[template(path = "view_post.html")]
+struct ViewPostTemplate {
+ meta: PostMetadata,
+ rendered: String,
+ rendered_in: RenderStats,
+}
+
+type AppResult = Result;
+
+#[derive(Error, Debug)]
+enum AppError {
+ #[error("failed to fetch post: {0}")]
+ PostError(#[from] PostError),
+}
+
+#[derive(Template)]
+#[template(path = "error.html")]
+struct ErrorTemplate {
+ error: String,
+}
+
+impl IntoResponse for AppError {
+ fn into_response(self) -> Response {
+ let status_code = match &self {
+ AppError::PostError(err) => match err {
+ PostError::NotFound(_) => StatusCode::NOT_FOUND,
+ _ => StatusCode::INTERNAL_SERVER_ERROR,
+ },
+ //_ => StatusCode::INTERNAL_SERVER_ERROR,
+ };
+ (
+ status_code,
+ ErrorTemplate {
+ error: self.to_string(),
+ },
+ )
+ .into_response()
+ }
+}
+
+async fn index(State(state): State) -> AppResult {
+ Ok(IndexTemplate {
+ title: state.config.title.clone(),
+ description: state.config.description.clone(),
+ posts: state.posts.list_posts().await?,
+ })
+}
+
+async fn post(State(state): State, Path(name): Path) -> AppResult {
+ let post = state.posts.get_post(&name).await?;
+
+ let post = ViewPostTemplate {
+ meta: post.0,
+ rendered: post.1,
+ rendered_in: post.2,
+ }
+ .into_response();
+
+ Ok(post)
+}
+
+async fn all_posts(State(state): State) -> AppResult>> {
+ let posts = state.posts.list_posts().await?;
+ Ok(Json(posts))
+}
+
+#[tokio::main]
+async fn main() -> eyre::Result<()> {
+ #[cfg(feature = "tokio-console")]
+ console_subscriber::init();
+ color_eyre::install()?;
+ #[cfg(not(feature = "tokio-console"))]
+ tracing_subscriber::registry()
+ .with(
+ EnvFilter::builder()
+ .with_default_directive(LevelFilter::INFO.into())
+ .from_env_lossy(),
+ )
+ .with(tracing_subscriber::fmt::layer())
+ .init();
+
+ let config = config::load()
+ .await
+ .with_context(|| "couldn't load configuration")?;
+
+ let mut tasks = JoinSet::new();
+ let mut cancellation_tokens = Vec::new();
+
+ #[cfg(feature = "precompression")]
+ if config.precompression.enable {
+ let span = info_span!("compression");
+ info!(parent: span.clone(), "compressing static");
+
+ let compressed = tokio::task::spawn_blocking(|| compress_epicly("static"))
+ .await
+ .unwrap()
+ .with_context(|| "couldn't compress static")?;
+
+ let _handle = span.enter();
+
+ if compressed > 0 {
+ info!(compressed_files=%compressed, "compressed {compressed} files");
+ }
+
+ if config.precompression.watch {
+ info!("starting compressor task");
+ let span = span.clone();
+ let token = CancellationToken::new();
+ let passed_token = token.clone();
+ tasks.spawn(async move {
+ watch(span, passed_token, Default::default())
+ .await
+ .with_context(|| "failed to watch static")
+ .unwrap()
+ });
+ cancellation_tokens.push(token);
+ }
+ }
+
+ let posts = if let Some(path) = config.cache_file.as_ref()
+ && tokio::fs::try_exists(&path)
+ .await
+ .with_context(|| format!("failed to check if {} exists", path.display()))?
+ {
+ info!("loading cache from file");
+ let load_cache = async {
+ let mut cache_file = tokio::fs::File::open(&path)
+ .await
+ .with_context(|| "failed to open cache file")?;
+ let mut serialized = Vec::with_capacity(4096);
+ cache_file
+ .read_to_end(&mut serialized)
+ .await
+ .with_context(|| "failed to read cache file")?;
+ let cache = bitcode::deserialize(serialized.as_slice())
+ .with_context(|| "failed to parse cache")?;
+ Ok::(PostManager::new_with_cache(
+ config.posts_dir.clone(),
+ config.render.clone(),
+ cache,
+ ))
+ }
+ .await;
+ match load_cache {
+ Ok(posts) => posts,
+ Err(err) => {
+ error!("failed to load cache: {}", err);
+ info!("using empty cache");
+ PostManager::new(config.posts_dir.clone(), config.render.clone())
+ }
+ }
+ } else {
+ PostManager::new(config.posts_dir.clone(), config.render.clone())
+ };
+
+ let state = Arc::new(AppState { config, posts });
+
+ let app = Router::new()
+ .route("/", get(index))
+ .route(
+ "/post/:name",
+ get(
+ |Path(name): Path| async move { Redirect::to(&format!("/posts/{}", name)) },
+ ),
+ )
+ .route("/posts/:name", get(post))
+ .route("/posts", get(all_posts))
+ .nest_service("/static", ServeDir::new("static").precompressed_gzip())
+ .nest_service("/media", ServeDir::new("media"))
+ .layer(
+ TraceLayer::new_for_http()
+ .make_span_with(|request: &Request<_>| {
+ let matched_path = request
+ .extensions()
+ .get::()
+ .map(MatchedPath::as_str);
+
+ info_span!(
+ "request",
+ method = ?request.method(),
+ path = ?request.uri().path(),
+ matched_path,
+ )
+ })
+ .on_response(|response: &Response<_>, duration: Duration, span: &Span| {
+ let _ = span.enter();
+ let status = response.status();
+ info!(?status, ?duration, "response");
+ }),
+ )
+ .with_state(state.clone());
+
+ let listener = TcpListener::bind((state.config.host, state.config.port))
+ .await
+ .with_context(|| {
+ format!(
+ "couldn't listen on {}",
+ SocketAddr::new(state.config.host, state.config.port)
+ )
+ })?;
+ let local_addr = listener
+ .local_addr()
+ .with_context(|| "couldn't get socket address")?;
+ info!("listening on http://{}", local_addr);
+
+ let sigint = signal::ctrl_c();
+ #[cfg(unix)]
+ let mut sigterm_handler =
+ tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())?;
+ #[cfg(unix)]
+ let sigterm = sigterm_handler.recv();
+ #[cfg(not(unix))] // TODO: kill all windows server users
+ let sigterm = std::future::pending::<()>();
+
+ let axum_token = CancellationToken::new();
+ cancellation_tokens.push(axum_token.clone());
+
+ let mut server = axum::serve(
+ listener,
+ app.into_make_service_with_connect_info::(),
+ )
+ .with_graceful_shutdown(async move { axum_token.cancelled().await })
+ .into_future();
+
+ tokio::select! {
+ result = &mut server => {
+ result.with_context(|| "failed to serve app")?;
+ },
+ _ = sigint => {
+ info!("received SIGINT, exiting gracefully");
+ },
+ _ = sigterm => {
+ info!("received SIGTERM, exiting gracefully");
+ }
+ };
+
+ let cleanup = async move {
+ // stop tasks
+ for token in cancellation_tokens {
+ token.cancel();
+ }
+ server.await.with_context(|| "failed to serve app")?;
+ while let Some(task) = tasks.join_next().await {
+ task.with_context(|| "failed to join task")?;
+ }
+
+ // write cache to file
+ let AppState { config, posts } = Arc::::try_unwrap(state).unwrap_or_else(|state| {
+ warn!("couldn't unwrap Arc over AppState, more than one strong reference exists for Arc. cloning instead");
+ AppState::clone(state.as_ref())
+ });
+ if let Some(path) = config.cache_file.as_ref() {
+ let cache = posts.into_cache();
+ let mut serialized =
+ bitcode::serialize(&cache).with_context(|| "failed to serialize cache")?;
+ let mut cache_file = tokio::fs::File::create(path)
+ .await
+ .with_context(|| format!("failed to open cache at {}", path.display()))?;
+ cache_file
+ .write_all(serialized.as_mut_slice())
+ .await
+ .with_context(|| "failed to write cache to file")?;
+ info!("wrote cache to {}", path.display());
+ }
+ Ok::<(), color_eyre::Report>(())
+ };
+
+ let sigint = signal::ctrl_c();
+ #[cfg(unix)]
+ let mut sigterm_handler =
+ tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())?;
+ #[cfg(unix)]
+ let sigterm = sigterm_handler.recv();
+ #[cfg(not(unix))]
+ let sigterm = std::future::pending::<()>();
+
+ tokio::select! {
+ result = cleanup => {
+ result.with_context(|| "cleanup failed, oh well")?;
+ },
+ _ = sigint => {
+ warn!("received second signal, exiting");
+ exit(1);
+ },
+ _ = sigterm => {
+ warn!("received second signal, exiting");
+ exit(1);
+ }
+ }
+
+ Ok(())
+}
diff --git a/src/markdown_render.rs b/src/markdown_render.rs
new file mode 100644
index 0000000..4158f33
--- /dev/null
+++ b/src/markdown_render.rs
@@ -0,0 +1,63 @@
+use std::sync::{Arc, OnceLock, RwLock};
+
+use comrak::markdown_to_html_with_plugins;
+use comrak::plugins::syntect::{SyntectAdapter, SyntectAdapterBuilder};
+use comrak::ComrakOptions;
+use comrak::Plugins;
+use comrak::RenderPlugins;
+use syntect::highlighting::ThemeSet;
+
+use crate::config::RenderConfig;
+use crate::hash_arc_store::HashArcStore;
+
+fn syntect_adapter(config: &RenderConfig) -> Arc {
+ static STATE: OnceLock>> = OnceLock::new();
+ let lock = STATE.get_or_init(|| RwLock::new(HashArcStore::new()));
+ let mut guard = lock.write().unwrap();
+ guard.get_or_init(config, build_syntect)
+}
+
+fn build_syntect(config: &RenderConfig) -> Arc {
+ let mut theme_set = if config.syntect_load_defaults {
+ ThemeSet::load_defaults()
+ } else {
+ ThemeSet::new()
+ };
+ if let Some(path) = config.syntect_themes_dir.as_ref() {
+ theme_set.add_from_folder(path).unwrap();
+ }
+ let mut builder = SyntectAdapterBuilder::new().theme_set(theme_set);
+ if let Some(theme) = config.syntect_theme.as_ref() {
+ builder = builder.theme(theme);
+ }
+ Arc::new(builder.build())
+}
+
+pub fn render_with_config(markdown: &str, config: &RenderConfig, front_matter: bool) -> String {
+ let mut options = ComrakOptions::default();
+ options.extension.table = true;
+ options.extension.autolink = true;
+ options.extension.tasklist = true;
+ options.extension.superscript = true;
+ options.extension.multiline_block_quotes = true;
+ options.extension.header_ids = Some(String::new());
+ if front_matter {
+ options.extension.front_matter_delimiter = Some(String::from("---"));
+ };
+
+ let mut render_plugins = RenderPlugins::default();
+ let syntect = syntect_adapter(config);
+ render_plugins.codefence_syntax_highlighter = Some(syntect.as_ref());
+
+ let plugins = comrak::PluginsBuilder::default()
+ .render(render_plugins)
+ .build()
+ .unwrap();
+
+ render(markdown, &options, &plugins)
+}
+
+pub fn render(markdown: &str, options: &ComrakOptions, plugins: &Plugins) -> String {
+ // TODO: post-processing
+ markdown_to_html_with_plugins(markdown, options, plugins)
+}
diff --git a/src/post/cache.rs b/src/post/cache.rs
new file mode 100644
index 0000000..4b32b77
--- /dev/null
+++ b/src/post/cache.rs
@@ -0,0 +1,160 @@
+use std::hash::{DefaultHasher, Hash, Hasher};
+
+use scc::HashMap;
+use serde::de::{SeqAccess, Visitor};
+use serde::{ser::SerializeSeq, Deserialize, Deserializer, Serialize, Serializer};
+
+use crate::config::RenderConfig;
+use crate::post::PostMetadata;
+
+#[derive(Serialize, Deserialize, Clone)]
+pub struct CacheValue {
+ pub metadata: PostMetadata,
+ pub rendered: String,
+ pub mtime: u64,
+ config_hash: u64,
+}
+
+#[derive(Default, Clone)]
+pub struct Cache(HashMap);
+
+impl Cache {
+ pub fn from_map(cache: HashMap) -> Self {
+ Self(cache)
+ }
+
+ pub async fn lookup(
+ &self,
+ name: &str,
+ mtime: u64,
+ config: &RenderConfig,
+ ) -> Option {
+ match self.0.get_async(name).await {
+ Some(entry) => {
+ let cached = entry.get();
+ if mtime <= cached.mtime && {
+ let mut hasher = DefaultHasher::new();
+ config.hash(&mut hasher);
+ hasher.finish()
+ } == cached.config_hash
+ {
+ Some(cached.clone())
+ } else {
+ let _ = entry.remove();
+ None
+ }
+ }
+ None => None,
+ }
+ }
+
+ pub async fn lookup_metadata(&self, name: &str, mtime: u64) -> Option {
+ match self.0.get_async(name).await {
+ Some(entry) => {
+ let cached = entry.get();
+ if mtime <= cached.mtime {
+ Some(cached.metadata.clone())
+ } else {
+ let _ = entry.remove();
+ None
+ }
+ }
+ None => None,
+ }
+ }
+
+ pub async fn insert(
+ &self,
+ name: String,
+ metadata: PostMetadata,
+ mtime: u64,
+ rendered: String,
+ config: &RenderConfig,
+ ) -> Result<(), (String, (PostMetadata, String))> {
+ let mut hasher = DefaultHasher::new();
+ config.hash(&mut hasher);
+ let hash = hasher.finish();
+
+ let value = CacheValue {
+ metadata,
+ rendered,
+ mtime,
+ config_hash: hash,
+ };
+
+ if self
+ .0
+ .update_async(&name, |_, _| value.clone())
+ .await
+ .is_none()
+ {
+ self.0
+ .insert_async(name, value)
+ .await
+ .map_err(|x| (x.0, (x.1.metadata, x.1.rendered)))
+ } else {
+ Ok(())
+ }
+ }
+
+ pub async fn remove(&self, name: &str) -> Option<(String, CacheValue)> {
+ self.0.remove_async(name).await
+ }
+
+ #[inline(always)]
+ pub fn into_inner(self) -> HashMap {
+ self.0
+ }
+}
+
+impl Serialize for Cache {
+ fn serialize(&self, serializer: S) -> Result
+ where
+ S: Serializer,
+ {
+ let cache = self.clone().into_inner();
+ let mut seq = serializer.serialize_seq(Some(cache.len()))?;
+ let mut entry = cache.first_entry();
+ while let Some(occupied) = entry {
+ let key = occupied.key().clone();
+ let value = occupied.get().clone();
+ seq.serialize_element(&(key, value))?;
+ entry = occupied.next();
+ }
+ seq.end()
+ }
+}
+
+impl<'de> Deserialize<'de> for Cache {
+ fn deserialize(deserializer: D) -> Result
+ where
+ D: Deserializer<'de>,
+ {
+ struct CoolVisitor;
+ impl<'de> Visitor<'de> for CoolVisitor {
+ type Value = Cache;
+
+ fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(formatter, "meow")
+ }
+
+ fn visit_seq(self, mut seq: A) -> Result
+ where
+ A: SeqAccess<'de>,
+ {
+ let cache = match seq.size_hint() {
+ Some(size) => HashMap::with_capacity(size),
+ None => HashMap::new(),
+ };
+
+ while let Some((key, value)) = seq.next_element::<(String, CacheValue)>()? {
+ cache.insert(key, value).ok();
+ }
+
+ Ok(Cache::from_map(cache))
+ }
+ }
+
+ deserializer.deserialize_seq(CoolVisitor)
+ }
+}
diff --git a/src/post/mod.rs b/src/post/mod.rs
new file mode 100644
index 0000000..152e8e1
--- /dev/null
+++ b/src/post/mod.rs
@@ -0,0 +1,229 @@
+mod cache;
+
+use std::io;
+use std::path::{Path, PathBuf};
+use std::time::{Duration, Instant, SystemTime};
+
+use askama::Template;
+use chrono::{DateTime, Utc};
+use fronma::engines::Toml;
+use fronma::parser::{parse_with_engine, ParsedData};
+use serde::{Deserialize, Serialize};
+use tokio::fs;
+use tokio::io::AsyncReadExt;
+use tracing::warn;
+
+use crate::config::RenderConfig;
+use crate::markdown_render;
+use crate::post::cache::Cache;
+use crate::PostError;
+
+#[derive(Deserialize)]
+struct FrontMatter {
+ pub title: String,
+ pub description: String,
+ pub author: String,
+ pub icon: Option,
+ pub created_at: Option>,
+ pub modified_at: Option>,
+}
+
+impl FrontMatter {
+ pub fn into_full(
+ self,
+ name: String,
+ created: Option,
+ modified: Option,
+ ) -> PostMetadata {
+ PostMetadata {
+ name,
+ title: self.title,
+ description: self.description,
+ author: self.author,
+ icon: self.icon,
+ created_at: self.created_at.or_else(|| created.map(|t| t.into())),
+ modified_at: self.modified_at.or_else(|| modified.map(|t| t.into())),
+ }
+ }
+}
+
+#[derive(Serialize, Deserialize, Clone)]
+pub struct PostMetadata {
+ pub name: String,
+ pub title: String,
+ pub description: String,
+ pub author: String,
+ pub icon: Option,
+ pub created_at: Option>,
+ pub modified_at: Option>,
+}
+
+use crate::filters;
+#[derive(Template)]
+#[template(path = "post.html")]
+struct Post<'a> {
+ pub meta: &'a PostMetadata,
+ pub rendered_markdown: String,
+}
+
+// format: TOTAL OP1 OP2
+#[allow(unused)]
+pub enum RenderStats {
+ Cached(Duration),
+ ParsedAndRendered(Duration, Duration, Duration),
+}
+
+#[derive(Clone)]
+pub struct PostManager {
+ dir: PathBuf,
+ cache: Cache,
+ config: RenderConfig,
+}
+
+impl PostManager {
+ pub fn new(dir: PathBuf, config: RenderConfig) -> PostManager {
+ PostManager {
+ dir,
+ cache: Default::default(),
+ config,
+ }
+ }
+
+ pub fn new_with_cache(dir: PathBuf, config: RenderConfig, cache: Cache) -> PostManager {
+ PostManager { dir, cache, config }
+ }
+
+ async fn parse_and_render(
+ &self,
+ name: String,
+ path: impl AsRef,
+ ) -> Result<(PostMetadata, String, (Duration, Duration)), PostError> {
+ let parsing_start = Instant::now();
+ let mut file = match tokio::fs::OpenOptions::new().read(true).open(&path).await {
+ Ok(val) => val,
+ Err(err) => match err.kind() {
+ io::ErrorKind::NotFound => return Err(PostError::NotFound(name)),
+ _ => return Err(PostError::IoError(err)),
+ },
+ };
+ let stat = file.metadata().await?;
+ let modified = stat.modified()?;
+ let created = stat.created().ok();
+
+ let mut content = String::with_capacity(stat.len() as usize);
+ file.read_to_string(&mut content).await?;
+
+ let ParsedData { headers, body } = parse_with_engine::(&content)?;
+ let metadata = headers.into_full(name.to_owned(), created, Some(modified));
+ let parsing = parsing_start.elapsed();
+
+ let before_render = Instant::now();
+ let rendered_markdown = markdown_render::render_with_config(body, &self.config, false);
+ let post = Post {
+ meta: &metadata,
+ rendered_markdown,
+ }
+ .render()?;
+ let rendering = before_render.elapsed();
+
+ self.cache
+ .insert(
+ name.to_string(),
+ metadata.clone(),
+ modified
+ .duration_since(SystemTime::UNIX_EPOCH)
+ .unwrap()
+ .as_secs(),
+ post.clone(),
+ &self.config,
+ )
+ .await
+ .unwrap_or_else(|err| warn!("failed to insert {:?} into cache", err.0));
+
+ Ok((metadata, post, (parsing, rendering)))
+ }
+
+ async fn list_posts_recursive(
+ &self,
+ dir: impl AsRef,
+ ) -> Result, PostError> {
+ let mut posts = Vec::new();
+
+ let mut read_dir = fs::read_dir(dir).await?;
+ while let Some(entry) = read_dir.next_entry().await? {
+ let path = entry.path();
+ let stat = fs::metadata(&path).await?;
+
+ if stat.is_file() && path.extension().is_some_and(|ext| ext == "md") {
+ let mtime = stat
+ .modified()?
+ .duration_since(SystemTime::UNIX_EPOCH)
+ .unwrap()
+ .as_secs();
+ let name = path
+ .clone()
+ .file_stem()
+ .unwrap()
+ .to_string_lossy()
+ .to_string();
+
+ if let Some(hit) = self.cache.lookup_metadata(&name, mtime).await {
+ posts.push(hit)
+ } else if let Ok((metadata, ..)) = self.parse_and_render(name, path).await {
+ posts.push(metadata);
+ }
+ }
+ }
+
+ Ok(posts)
+ }
+
+ #[allow(unused)]
+ pub async fn list_posts(&self) -> Result, PostError> {
+ self.list_posts_recursive(&self.dir).await
+ }
+
+ // third entry in the tuple is whether it got rendered and if so, how long did it take
+ pub async fn get_post(
+ &self,
+ name: &str,
+ ) -> Result<(PostMetadata, String, RenderStats), PostError> {
+ let start = Instant::now();
+ let path = self.dir.join(name.to_owned() + ".md");
+
+ let stat = match tokio::fs::metadata(&path).await {
+ Ok(value) => value,
+ Err(err) => match err.kind() {
+ io::ErrorKind::NotFound => {
+ self.cache.remove(name).await;
+ return Err(PostError::NotFound(name.to_string()));
+ }
+ _ => return Err(PostError::IoError(err)),
+ },
+ };
+ let mtime = stat
+ .modified()?
+ .duration_since(SystemTime::UNIX_EPOCH)
+ .unwrap()
+ .as_secs();
+
+ if let Some(hit) = self.cache.lookup(name, mtime, &self.config).await {
+ Ok((
+ hit.metadata,
+ hit.rendered,
+ RenderStats::Cached(start.elapsed()),
+ ))
+ } else {
+ let (metadata, rendered, stats) = self.parse_and_render(name.to_string(), path).await?;
+ Ok((
+ metadata,
+ rendered,
+ RenderStats::ParsedAndRendered(start.elapsed(), stats.0, stats.1),
+ ))
+ }
+ }
+
+ pub fn into_cache(self) -> Cache {
+ self.cache
+ }
+}
diff --git a/src/watcher.rs b/src/watcher.rs
new file mode 100644
index 0000000..59cd6a3
--- /dev/null
+++ b/src/watcher.rs
@@ -0,0 +1,76 @@
+use notify::{event::RemoveKind, Config, EventKind, RecommendedWatcher, RecursiveMode, Watcher};
+use tokio_util::sync::CancellationToken;
+use tracing::{info, Span};
+
+use crate::append_path::Append;
+use crate::compress::compress_epicly;
+
+pub async fn watch(
+ span: Span,
+ token: CancellationToken,
+ config: Config,
+) -> Result<(), notify::Error> {
+ let (tx, mut rx) = tokio::sync::mpsc::channel(12);
+ let mut watcher = RecommendedWatcher::new(
+ move |res| {
+ tx.blocking_send(res)
+ .expect("failed to send message over channel")
+ },
+ config,
+ )?;
+
+ watcher.watch(std::path::Path::new("static"), RecursiveMode::Recursive)?;
+
+ while let Some(received) = tokio::select! {
+ received = rx.recv() => received,
+ _ = token.cancelled() => return Ok(())
+ } {
+ match received {
+ Ok(event) => {
+ if event.kind.is_create() || event.kind.is_modify() {
+ let cloned_span = span.clone();
+ let compressed =
+ tokio::task::spawn_blocking(move || -> std::io::Result {
+ let _handle = cloned_span.enter();
+ let mut i = 0;
+ for path in event.paths {
+ if path.extension().is_some_and(|ext| ext == "gz") {
+ continue;
+ }
+ info!("{} changed, compressing", path.display());
+ i += compress_epicly(&path)?;
+ }
+ Ok(i)
+ })
+ .await
+ .unwrap()?;
+
+ if compressed > 0 {
+ let _handle = span.enter();
+ info!(compressed_files=%compressed, "compressed {compressed} files");
+ }
+ } else if let EventKind::Remove(remove_event) = event.kind // UNSTABLE
+ && matches!(remove_event, RemoveKind::File)
+ {
+ for path in event.paths {
+ if path.extension().is_some_and(|ext| ext == "gz") {
+ continue;
+ }
+ let gz_path = path.clone().append(".gz");
+ if tokio::fs::try_exists(&gz_path).await? {
+ info!(
+ "{} removed, also removing {}",
+ path.display(),
+ gz_path.display()
+ );
+ tokio::fs::remove_file(&gz_path).await?
+ }
+ }
+ }
+ }
+ Err(err) => return Err(err),
+ }
+ }
+
+ Ok(())
+}
diff --git a/static/post.css b/static/post.css
new file mode 100644
index 0000000..eb4cae1
--- /dev/null
+++ b/static/post.css
@@ -0,0 +1,29 @@
+.anchor {
+ text-decoration: none;
+}
+.anchor::before {
+ content: "§";
+}
+.anchor::after {
+ content: " ";
+}
+
+code {
+ font-size: larger;
+ padding: 0.15em 0.4em;
+
+ background-color: var(--surface0);
+ color: var(--subtext1);
+}
+
+/* code blocks */
+pre > code {
+ border: 2px solid var(--surface0);
+ padding: 1.25em 1.5em;
+ display: block;
+ overflow-wrap: break-word;
+ white-space: pre-wrap;
+
+ background-color: var(--base);
+ color: var(--text);
+}
diff --git a/static/style.css b/static/style.css
new file mode 100644
index 0000000..178efe5
--- /dev/null
+++ b/static/style.css
@@ -0,0 +1,134 @@
+/* colors */
+:root {
+ --base: #1e1e2e;
+ --text: #cdd6f4;
+ --crust: #11111b;
+ --surface0: #313244;
+ --subtext0: #a6adc8;
+ --subtext1: #bac2de;
+ --pink: #f5c2e7;
+ --rosewater: #f5e0dc;
+ --blue: #89b4fa;
+ --mauve: #cba6f7;
+}
+
+@media (prefers-color-scheme: light) {
+ :root {
+ --base: #eff1f5;
+ --text: #4c4f69;
+ --crust: #dce0e8;
+ --surface0: #ccd0da;
+ --subtext0: #6c6f85;
+ --subtext1: #5c5f77;
+ --pink: #ea76cb;
+ --rosewater: #dc8a78;
+ --blue: #1e66f5;
+ --mauve: #8839ef;
+ }
+}
+
+:root {
+ /* please have one at least one good monospace font */
+ font-family: "Hack Nerd Font", "Hack", "JetBrains Mono",
+ "JetBrainsMono Nerd Font", "Ubuntu Mono", monospace, sans-serif;
+
+ background-color: var(--base);
+ color: var(--text);
+}
+
+a {
+ color: var(--pink);
+}
+
+a:hover {
+ color: var(--rosewater);
+}
+
+a:active {
+ color: var(--blue);
+}
+
+a:visited {
+ color: var(--mauve);
+}
+
+code {
+ padding: 0.15em 0.4em;
+
+ background-color: var(--surface0);
+ color: var(--subtext0);
+}
+
+.tooltipped {
+ border-bottom: 1px dotted var(--text);
+}
+
+hr {
+ color: var(--subtext1);
+}
+
+footer {
+ text-align: end;
+ font-size: small;
+ opacity: 50%;
+}
+
+.post-author {
+ font-size: smaller;
+ opacity: 0.65;
+}
+
+/* BEGIN cool effect everyone liked */
+
+body {
+ margin: 0;
+ padding: 0;
+
+ background-color: var(--base);
+}
+
+main {
+ padding: 1em;
+
+ background-color: var(--base);
+}
+
+body > main > h1:first-child {
+ margin-top: 0;
+}
+
+@media (min-width: 956px) {
+ :root {
+ --target-ratio: 0.7; /* 669px - 1344x */
+ --width: min(100% * var(--target-ratio), 1920px * var(--target-ratio));
+ --padding: 4em;
+ --padded-width: calc(var(--width) - var(--padding) * 2);
+ }
+
+ body {
+ padding: 4em 0;
+ min-height: calc(100vh - 8em);
+
+ background: var(--crust);
+ background: linear-gradient(
+ 90deg,
+ var(--base) 0%,
+ var(--crust) calc((100% - var(--width)) / 2),
+ var(--crust) calc(50% + var(--width) / 2),
+ var(--base) 100%
+ );
+ }
+
+ body > * {
+ margin: auto;
+ padding: var(--padding);
+ width: var(--padded-width);
+ }
+
+ body > footer {
+ padding: initial;
+ width: var(--width);
+ }
+}
+
+/* END cool effect everyone liked */
diff --git a/templates/error.html b/templates/error.html
new file mode 100644
index 0000000..6e1bdea
--- /dev/null
+++ b/templates/error.html
@@ -0,0 +1,16 @@
+
+
+
+
+
+ error
+
+
+
+
+ error
+ {{ error }}
+ go back to home
+
+
+
diff --git a/templates/index.html b/templates/index.html
new file mode 100644
index 0000000..4315cca
--- /dev/null
+++ b/templates/index.html
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+ {{ title }}
+
+
+
+
+ {{ title }}
+ {{ description }}
+ posts
+
+
+ {% for post in posts %}
+
+ {{ post.title }}
+ - by {{ post.author }}
+
+ {{ post.description }}
+ {% match post.created_at %} {% when Some(created_at) %}
+ written: {{ created_at|date }}
+ {% when None %} {% endmatch %}
+ {% match post.modified_at %} {% when Some(modified_at) %}
+ last modified: {{ modified_at|date }}
+ {% when None %} {% endmatch %}
+
+ {% endfor %}
+
+
+
+
diff --git a/templates/post.html b/templates/post.html
new file mode 100644
index 0000000..f851db7
--- /dev/null
+++ b/templates/post.html
@@ -0,0 +1,20 @@
+
+ {{ meta.title }}
+ - by {{ meta.author }}
+
+{{ meta.description }}
+
+
+
+ {% match meta.created_at %} {% when Some(created_at) %}
+ written: {{ created_at|date }}
+ {% when None %} {% endmatch %}
+ {% match meta.modified_at %} {% when Some(modified_at) %}
+ last modified: {{ modified_at|date }}
+ {% when None %} {% endmatch %}
+
+ link
+ back to home
+
+
+{{ rendered_markdown|escape("none") }}
diff --git a/templates/post_list.html b/templates/post_list.html
new file mode 100644
index 0000000..e69de29
diff --git a/templates/post_preview.html b/templates/post_preview.html
new file mode 100644
index 0000000..e69de29
diff --git a/templates/view_post.html b/templates/view_post.html
new file mode 100644
index 0000000..819a480
--- /dev/null
+++ b/templates/view_post.html
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+ {% match meta.icon %} {% when Some with (url) %}
+
+
+ {% when None %} {% endmatch %}
+ {{ meta.title }}
+
+
+
+
+
+ {{ rendered|escape("none") }}
+
+
+
+
diff --git a/themes/Catppuccin Mocha.tmTheme b/themes/Catppuccin Mocha.tmTheme
new file mode 100644
index 0000000..364617b
--- /dev/null
+++ b/themes/Catppuccin Mocha.tmTheme
@@ -0,0 +1,2021 @@
+
+
+
+
+ name
+ Catppuccin Mocha
+ semanticClass
+ theme.dark.catppuccin-mocha
+ uuid
+ 627ce890-fabb-4d39-9819-7be71f4bdca7
+ author
+ Catppuccin Org
+ colorSpaceName
+ sRGB
+ settings
+
+
+ settings
+
+ background
+ #1e1e2e
+ foreground
+ #cdd6f4
+ caret
+ #f5e0dc
+ lineHighlight
+ #cdd6f412
+ misspelling
+ #f38ba8
+ accent
+ #cba6f7
+ selection
+ #9399b240
+ activeGuide
+ #45475a
+ findHighlight
+ #3e5767
+ gutterForeground
+ #7f849c
+
+
+
+ name
+ Basic text & variable names (incl. leading punctuation)
+ scope
+ text, source, variable.other.readwrite, punctuation.definition.variable
+ settings
+
+ foreground
+ #cdd6f4
+
+
+
+ name
+ Parentheses, Brackets, Braces
+ scope
+ punctuation
+ settings
+
+ foreground
+ #9399b2
+ fontStyle
+
+
+
+
+ name
+ Comments
+ scope
+ comment, punctuation.definition.comment
+ settings
+
+ foreground
+ #6c7086
+ fontStyle
+ italic
+
+
+
+ scope
+ string, punctuation.definition.string
+ settings
+
+ foreground
+ #a6e3a1
+
+
+
+ scope
+ constant.character.escape
+ settings
+
+ foreground
+ #f5c2e7
+
+
+
+ name
+ Booleans, constants, numbers
+ scope
+ constant.numeric, variable.other.constant, entity.name.constant, constant.language.boolean, constant.language.false, constant.language.true, keyword.other.unit.user-defined, keyword.other.unit.suffix.floating-point
+ settings
+
+ foreground
+ #fab387
+
+
+
+ scope
+ keyword, keyword.operator.word, keyword.operator.new, variable.language.super, support.type.primitive, storage.type, storage.modifier, punctuation.definition.keyword
+ settings
+
+ foreground
+ #cba6f7
+ fontStyle
+
+
+
+
+ scope
+ entity.name.tag.documentation
+ settings
+
+ foreground
+ #cba6f7
+
+
+
+ name
+ Punctuation
+ scope
+ keyword.operator, punctuation.accessor, punctuation.definition.generic, meta.function.closure punctuation.section.parameters, punctuation.definition.tag, punctuation.separator.key-value
+ settings
+
+ foreground
+ #94e2d5
+
+
+
+ scope
+ entity.name.function, meta.function-call.method, support.function, support.function.misc, variable.function
+ settings
+
+ foreground
+ #89b4fa
+ fontStyle
+ italic
+
+
+
+ name
+ Classes
+ scope
+ entity.name.class, entity.other.inherited-class, support.class, meta.function-call.constructor, entity.name.struct
+ settings
+
+ foreground
+ #f9e2af
+ fontStyle
+ italic
+
+
+
+ name
+ Enum
+ scope
+ entity.name.enum
+ settings
+
+ foreground
+ #f9e2af
+ fontStyle
+ italic
+
+
+
+ name
+ Enum member
+ scope
+ meta.enum variable.other.readwrite, variable.other.enummember
+ settings
+
+ foreground
+ #94e2d5
+
+
+
+ name
+ Object properties
+ scope
+ meta.property.object
+ settings
+
+ foreground
+ #94e2d5
+
+
+
+ name
+ Types
+ scope
+ meta.type, meta.type-alias, support.type, entity.name.type
+ settings
+
+ foreground
+ #f9e2af
+ fontStyle
+ italic
+
+
+
+ name
+ Decorators
+ scope
+ meta.annotation variable.function, meta.annotation variable.annotation.function, meta.annotation punctuation.definition.annotation, meta.decorator, punctuation.decorator
+ settings
+
+ foreground
+ #fab387
+
+
+
+ scope
+ variable.parameter, meta.function.parameters
+ settings
+
+ foreground
+ #eba0ac
+ fontStyle
+ italic
+
+
+
+ name
+ Built-ins
+ scope
+ constant.language, support.function.builtin
+ settings
+
+ foreground
+ #f38ba8
+
+
+
+ scope
+ entity.other.attribute-name.documentation
+ settings
+
+ foreground
+ #f38ba8
+
+
+
+ name
+ Preprocessor directives
+ scope
+ keyword.control.directive, punctuation.definition.directive
+ settings
+
+ foreground
+ #f9e2af
+
+
+
+ name
+ Type parameters
+ scope
+ punctuation.definition.typeparameters
+ settings
+
+ foreground
+ #89dceb
+
+
+
+ name
+ Namespaces
+ scope
+ entity.name.namespace
+ settings
+
+ foreground
+ #f9e2af
+
+
+
+ name
+ Property names (left hand assignments in json/yaml/css)
+ scope
+ support.type.property-name.css
+ settings
+
+ foreground
+ #89b4fa
+ fontStyle
+
+
+
+
+ name
+ This/Self keyword
+ scope
+ variable.language.this, variable.language.this punctuation.definition.variable
+ settings
+
+ foreground
+ #f38ba8
+
+
+
+ name
+ Object properties
+ scope
+ variable.object.property
+ settings
+
+ foreground
+ #cdd6f4
+
+
+
+ name
+ String template interpolation
+ scope
+ string.template variable, string variable
+ settings
+
+ foreground
+ #cdd6f4
+
+
+
+ name
+ `new` as bold
+ scope
+ keyword.operator.new
+ settings
+
+ fontStyle
+ bold
+
+
+
+ name
+ C++ extern keyword
+ scope
+ storage.modifier.specifier.extern.cpp
+ settings
+
+ foreground
+ #cba6f7
+
+
+
+ name
+ C++ scope resolution
+ scope
+ entity.name.scope-resolution.template.call.cpp, entity.name.scope-resolution.parameter.cpp, entity.name.scope-resolution.cpp, entity.name.scope-resolution.function.definition.cpp
+ settings
+
+ foreground
+ #f9e2af
+
+
+
+ name
+ C++ doc keywords
+ scope
+ storage.type.class.doxygen
+ settings
+
+ fontStyle
+
+
+
+
+ name
+ C++ operators
+ scope
+ storage.modifier.reference.cpp
+ settings
+
+ foreground
+ #94e2d5
+
+
+
+ name
+ C# Interpolated Strings
+ scope
+ meta.interpolation.cs
+ settings
+
+ foreground
+ #cdd6f4
+
+
+
+ name
+ C# xml-style docs
+ scope
+ comment.block.documentation.cs
+ settings
+
+ foreground
+ #cdd6f4
+
+
+
+ name
+ Classes, reflecting the className color in JSX
+ scope
+ source.css entity.other.attribute-name.class.css, entity.other.attribute-name.parent-selector.css punctuation.definition.entity.css
+ settings
+
+ foreground
+ #f9e2af
+
+
+
+ name
+ Operators
+ scope
+ punctuation.separator.operator.css
+ settings
+
+ foreground
+ #94e2d5
+
+
+
+ name
+ Pseudo classes
+ scope
+ source.css entity.other.attribute-name.pseudo-class
+ settings
+
+ foreground
+ #94e2d5
+
+
+
+ scope
+ source.css constant.other.unicode-range
+ settings
+
+ foreground
+ #fab387
+
+
+
+ scope
+ source.css variable.parameter.url
+ settings
+
+ foreground
+ #a6e3a1
+ fontStyle
+
+
+
+
+ name
+ CSS vendored property names
+ scope
+ support.type.vendored.property-name
+ settings
+
+ foreground
+ #89dceb
+
+
+
+ name
+ Less/SCSS right-hand variables (@/$-prefixed)
+ scope
+ source.css meta.property-value variable, source.css meta.property-value variable.other.less, source.css meta.property-value variable.other.less punctuation.definition.variable.less, meta.definition.variable.scss
+ settings
+
+ foreground
+ #eba0ac
+
+
+
+ name
+ CSS variables (--prefixed)
+ scope
+ source.css meta.property-list variable, meta.property-list variable.other.less, meta.property-list variable.other.less punctuation.definition.variable.less
+ settings
+
+ foreground
+ #89b4fa
+
+
+
+ name
+ CSS Percentage values, styled the same as numbers
+ scope
+ keyword.other.unit.percentage.css
+ settings
+
+ foreground
+ #fab387
+
+
+
+ name
+ CSS Attribute selectors, styled the same as strings
+ scope
+ source.css meta.attribute-selector
+ settings
+
+ foreground
+ #a6e3a1
+
+
+
+ name
+ JSON/YAML keys, other left-hand assignments
+ scope
+ keyword.other.definition.ini, punctuation.support.type.property-name.json, support.type.property-name.json, punctuation.support.type.property-name.toml, support.type.property-name.toml, entity.name.tag.yaml, punctuation.support.type.property-name.yaml, support.type.property-name.yaml
+ settings
+
+ foreground
+ #89b4fa
+ fontStyle
+
+
+
+
+ name
+ JSON/YAML constants
+ scope
+ constant.language.json, constant.language.yaml
+ settings
+
+ foreground
+ #fab387
+
+
+
+ name
+ YAML anchors
+ scope
+ entity.name.type.anchor.yaml, variable.other.alias.yaml
+ settings
+
+ foreground
+ #f9e2af
+ fontStyle
+
+
+
+
+ name
+ TOML tables / ini groups
+ scope
+ support.type.property-name.table, entity.name.section.group-title.ini
+ settings
+
+ foreground
+ #f9e2af
+
+
+
+ name
+ TOML dates
+ scope
+ constant.other.time.datetime.offset.toml
+ settings
+
+ foreground
+ #f5c2e7
+
+
+
+ name
+ YAML anchor puctuation
+ scope
+ punctuation.definition.anchor.yaml, punctuation.definition.alias.yaml
+ settings
+
+ foreground
+ #f5c2e7
+
+
+
+ name
+ YAML triple dashes
+ scope
+ entity.other.document.begin.yaml
+ settings
+
+ foreground
+ #f5c2e7
+
+
+
+ name
+ Markup Diff
+ scope
+ markup.changed.diff
+ settings
+
+ foreground
+ #fab387
+
+
+
+ name
+ Diff
+ scope
+ meta.diff.header.from-file, meta.diff.header.to-file, punctuation.definition.from-file.diff, punctuation.definition.to-file.diff
+ settings
+
+ foreground
+ #89b4fa
+
+
+
+ name
+ Diff Inserted
+ scope
+ markup.inserted.diff
+ settings
+
+ foreground
+ #a6e3a1
+
+
+
+ name
+ Diff Deleted
+ scope
+ markup.deleted.diff
+ settings
+
+ foreground
+ #f38ba8
+
+
+
+ name
+ dotenv left-hand side assignments
+ scope
+ variable.other.env
+ settings
+
+ foreground
+ #89b4fa
+
+
+
+ name
+ dotenv reference to existing env variable
+ scope
+ string.quoted variable.other.env
+ settings
+
+ foreground
+ #cdd6f4
+
+
+
+ name
+ GDScript functions
+ scope
+ support.function.builtin.gdscript
+ settings
+
+ foreground
+ #89b4fa
+
+
+
+ name
+ GDScript constants
+ scope
+ constant.language.gdscript
+ settings
+
+ foreground
+ #fab387
+
+
+
+ name
+ Comment keywords
+ scope
+ comment meta.annotation.go
+ settings
+
+ foreground
+ #eba0ac
+
+
+
+ name
+ go:embed, go:build, etc.
+ scope
+ comment meta.annotation.parameters.go
+ settings
+
+ foreground
+ #fab387
+
+
+
+ name
+ Go constants (nil, true, false)
+ scope
+ constant.language.go
+ settings
+
+ foreground
+ #fab387
+
+
+
+ name
+ GraphQL variables
+ scope
+ variable.graphql
+ settings
+
+ foreground
+ #cdd6f4
+
+
+
+ name
+ GraphQL aliases
+ scope
+ string.unquoted.alias.graphql
+ settings
+
+ foreground
+ #f2cdcd
+
+
+
+ name
+ GraphQL enum members
+ scope
+ constant.character.enum.graphql
+ settings
+
+ foreground
+ #94e2d5
+
+
+
+ name
+ GraphQL field in types
+ scope
+ meta.objectvalues.graphql constant.object.key.graphql string.unquoted.graphql
+ settings
+
+ foreground
+ #f2cdcd
+
+
+
+ name
+ HTML/XML DOCTYPE as keyword
+ scope
+ keyword.other.doctype, meta.tag.sgml.doctype punctuation.definition.tag, meta.tag.metadata.doctype entity.name.tag, meta.tag.metadata.doctype punctuation.definition.tag
+ settings
+
+ foreground
+ #cba6f7
+
+
+
+ name
+ HTML/XML-like <tags/>
+ scope
+ entity.name.tag
+ settings
+
+ foreground
+ #89b4fa
+ fontStyle
+
+
+
+
+ name
+ Special characters like &
+ scope
+ text.html constant.character.entity, text.html constant.character.entity punctuation, constant.character.entity.xml, constant.character.entity.xml punctuation, constant.character.entity.js.jsx, constant.charactger.entity.js.jsx punctuation, constant.character.entity.tsx, constant.character.entity.tsx punctuation
+ settings
+
+ foreground
+ #f38ba8
+
+
+
+ name
+ HTML/XML tag attribute values
+ scope
+ entity.other.attribute-name
+ settings
+
+ foreground
+ #f9e2af
+
+
+
+ name
+ Components
+ scope
+ support.class.component, support.class.component.jsx, support.class.component.tsx, support.class.component.vue
+ settings
+
+ foreground
+ #f5c2e7
+ fontStyle
+
+
+
+
+ name
+ Annotations
+ scope
+ punctuation.definition.annotation, storage.type.annotation
+ settings
+
+ foreground
+ #fab387
+
+
+
+ name
+ Java enums
+ scope
+ constant.other.enum.java
+ settings
+
+ foreground
+ #94e2d5
+
+
+
+ name
+ Java imports
+ scope
+ storage.modifier.import.java
+ settings
+
+ foreground
+ #cdd6f4
+
+
+
+ name
+ Javadoc
+ scope
+ comment.block.javadoc.java keyword.other.documentation.javadoc.java
+ settings
+
+ fontStyle
+
+
+
+
+ name
+ Exported Variable
+ scope
+ meta.export variable.other.readwrite.js
+ settings
+
+ foreground
+ #eba0ac
+
+
+
+ name
+ JS/TS constants & properties
+ scope
+ variable.other.constant.js, variable.other.constant.ts, variable.other.property.js, variable.other.property.ts
+ settings
+
+ foreground
+ #cdd6f4
+
+
+
+ name
+ JSDoc; these are mainly params, so styled as such
+ scope
+ variable.other.jsdoc, comment.block.documentation variable.other
+ settings
+
+ foreground
+ #eba0ac
+ fontStyle
+
+
+
+
+ name
+ JSDoc keywords
+ scope
+ storage.type.class.jsdoc
+ settings
+
+ fontStyle
+
+
+
+
+ scope
+ support.type.object.console.js
+ settings
+
+ foreground
+ #cdd6f4
+
+
+
+ name
+ Node constants as keywords (module, etc.)
+ scope
+ support.constant.node, support.type.object.module.js
+ settings
+
+ foreground
+ #cba6f7
+
+
+
+ name
+ implements as keyword
+ scope
+ storage.modifier.implements
+ settings
+
+ foreground
+ #cba6f7
+
+
+
+ name
+ Builtin types
+ scope
+ constant.language.null.js, constant.language.null.ts, constant.language.undefined.js, constant.language.undefined.ts, support.type.builtin.ts
+ settings
+
+ foreground
+ #cba6f7
+
+
+
+ scope
+ variable.parameter.generic
+ settings
+
+ foreground
+ #f9e2af
+
+
+
+ name
+ Arrow functions
+ scope
+ keyword.declaration.function.arrow.js, storage.type.function.arrow.ts
+ settings
+
+ foreground
+ #94e2d5
+
+
+
+ name
+ Decorator punctuations (decorators inherit from blue functions, instead of styleguide peach)
+ scope
+ punctuation.decorator.ts
+ settings
+
+ foreground
+ #89b4fa
+ fontStyle
+ italic
+
+
+
+ name
+ Extra JS/TS keywords
+ scope
+ keyword.operator.expression.in.js, keyword.operator.expression.in.ts, keyword.operator.expression.infer.ts, keyword.operator.expression.instanceof.js, keyword.operator.expression.instanceof.ts, keyword.operator.expression.is, keyword.operator.expression.keyof.ts, keyword.operator.expression.of.js, keyword.operator.expression.of.ts, keyword.operator.expression.typeof.ts
+ settings
+
+ foreground
+ #cba6f7
+
+
+
+ name
+ Julia macros
+ scope
+ support.function.macro.julia
+ settings
+
+ foreground
+ #94e2d5
+ fontStyle
+ italic
+
+
+
+ name
+ Julia language constants (true, false)
+ scope
+ constant.language.julia
+ settings
+
+ foreground
+ #fab387
+
+
+
+ name
+ Julia other constants (these seem to be arguments inside arrays)
+ scope
+ constant.other.symbol.julia
+ settings
+
+ foreground
+ #eba0ac
+
+
+
+ name
+ LaTeX preamble
+ scope
+ text.tex keyword.control.preamble
+ settings
+
+ foreground
+ #94e2d5
+
+
+
+ name
+ LaTeX be functions
+ scope
+ text.tex support.function.be
+ settings
+
+ foreground
+ #89dceb
+
+
+
+ name
+ LaTeX math
+ scope
+ constant.other.general.math.tex
+ settings
+
+ foreground
+ #f2cdcd
+
+
+
+ name
+ Lua docstring keywords
+ scope
+ comment.line.double-dash.documentation.lua storage.type.annotation.lua
+ settings
+
+ foreground
+ #cba6f7
+ fontStyle
+
+
+
+
+ name
+ Lua docstring variables
+ scope
+ comment.line.double-dash.documentation.lua entity.name.variable.lua, comment.line.double-dash.documentation.lua variable.lua
+ settings
+
+ foreground
+ #cdd6f4
+
+
+
+ scope
+ heading.1.markdown punctuation.definition.heading.markdown, heading.1.markdown, markup.heading.atx.1.mdx, markup.heading.atx.1.mdx punctuation.definition.heading.mdx, markup.heading.setext.1.markdown, markup.heading.heading-0.asciidoc
+ settings
+
+ foreground
+ #f38ba8
+
+
+
+ scope
+ heading.2.markdown punctuation.definition.heading.markdown, heading.2.markdown, markup.heading.atx.2.mdx, markup.heading.atx.2.mdx punctuation.definition.heading.mdx, markup.heading.setext.2.markdown, markup.heading.heading-1.asciidoc
+ settings
+
+ foreground
+ #fab387
+
+
+
+ scope
+ heading.3.markdown punctuation.definition.heading.markdown, heading.3.markdown, markup.heading.atx.3.mdx, markup.heading.atx.3.mdx punctuation.definition.heading.mdx, markup.heading.heading-2.asciidoc
+ settings
+
+ foreground
+ #f9e2af
+
+
+
+ scope
+ heading.4.markdown punctuation.definition.heading.markdown, heading.4.markdown, markup.heading.atx.4.mdx, markup.heading.atx.4.mdx punctuation.definition.heading.mdx, markup.heading.heading-3.asciidoc
+ settings
+
+ foreground
+ #a6e3a1
+
+
+
+ scope
+ heading.5.markdown punctuation.definition.heading.markdown, heading.5.markdown, markup.heading.atx.5.mdx, markup.heading.atx.5.mdx punctuation.definition.heading.mdx, markup.heading.heading-4.asciidoc
+ settings
+
+ foreground
+ #89b4fa
+
+
+
+ scope
+ heading.6.markdown punctuation.definition.heading.markdown, heading.6.markdown, markup.heading.atx.6.mdx, markup.heading.atx.6.mdx punctuation.definition.heading.mdx, markup.heading.heading-5.asciidoc
+ settings
+
+ foreground
+ #cba6f7
+
+
+
+ scope
+ markup.bold
+ settings
+
+ foreground
+ #f38ba8
+ fontStyle
+ bold
+
+
+
+ scope
+ markup.italic
+ settings
+
+ foreground
+ #f38ba8
+ fontStyle
+ italic
+
+
+
+ scope
+ markup.strikethrough
+ settings
+
+ foreground
+ #a6adc8
+ fontStyle
+ strikethrough
+
+
+
+ name
+ Markdown auto links
+ scope
+ punctuation.definition.link, markup.underline.link
+ settings
+
+ foreground
+ #89b4fa
+
+
+
+ name
+ Markdown links
+ scope
+ text.html.markdown punctuation.definition.link.title, string.other.link.title.markdown, markup.link, punctuation.definition.constant.markdown, constant.other.reference.link.markdown, markup.substitution.attribute-reference
+ settings
+
+ foreground
+ #b4befe
+
+
+
+ name
+ Markdown code spans
+ scope
+ punctuation.definition.raw.markdown, markup.inline.raw.string.markdown, markup.raw.block.markdown
+ settings
+
+ foreground
+ #a6e3a1
+
+
+
+ name
+ Markdown triple backtick language identifier
+ scope
+ fenced_code.block.language
+ settings
+
+ foreground
+ #89dceb
+
+
+
+ name
+ Markdown triple backticks
+ scope
+ markup.fenced_code.block punctuation.definition, markup.raw support.asciidoc
+ settings
+
+ foreground
+ #9399b2
+
+
+
+ name
+ Markdown quotes
+ scope
+ markup.quote, punctuation.definition.quote.begin
+ settings
+
+ foreground
+ #f5c2e7
+
+
+
+ name
+ Markdown separators
+ scope
+ meta.separator.markdown
+ settings
+
+ foreground
+ #94e2d5
+
+
+
+ name
+ Markdown list bullets
+ scope
+ punctuation.definition.list.begin.markdown, markup.list.bullet
+ settings
+
+ foreground
+ #94e2d5
+
+
+
+ name
+ Nix attribute names
+ scope
+ entity.other.attribute-name.multipart.nix, entity.other.attribute-name.single.nix
+ settings
+
+ foreground
+ #89b4fa
+
+
+
+ name
+ Nix parameter names
+ scope
+ variable.parameter.name.nix
+ settings
+
+ foreground
+ #cdd6f4
+ fontStyle
+
+
+
+
+ name
+ Nix interpolated parameter names
+ scope
+ meta.embedded variable.parameter.name.nix
+ settings
+
+ foreground
+ #b4befe
+ fontStyle
+
+
+
+
+ name
+ Nix paths
+ scope
+ string.unquoted.path.nix
+ settings
+
+ foreground
+ #f5c2e7
+ fontStyle
+
+
+
+
+ name
+ PHP Attributes
+ scope
+ support.attribute.builtin, meta.attribute.php
+ settings
+
+ foreground
+ #f9e2af
+
+
+
+ name
+ PHP Parameters (needed for the leading dollar sign)
+ scope
+ meta.function.parameters.php punctuation.definition.variable.php
+ settings
+
+ foreground
+ #eba0ac
+
+
+
+ name
+ PHP Constants (null, __FILE__, etc.)
+ scope
+ constant.language.php
+ settings
+
+ foreground
+ #cba6f7
+
+
+
+ name
+ PHP functions
+ scope
+ text.html.php support.function
+ settings
+
+ foreground
+ #89dceb
+
+
+
+ name
+ PHPdoc keywords
+ scope
+ keyword.other.phpdoc.php
+ settings
+
+ fontStyle
+
+
+
+
+ name
+ Python argument functions reset to text, otherwise they inherit blue from function-call
+ scope
+ support.variable.magic.python, meta.function-call.arguments.python
+ settings
+
+ foreground
+ #cdd6f4
+
+
+
+ name
+ Python double underscore functions
+ scope
+ support.function.magic.python
+ settings
+
+ foreground
+ #89dceb
+ fontStyle
+ italic
+
+
+
+ name
+ Python `self` keyword
+ scope
+ variable.parameter.function.language.special.self.python, variable.language.special.self.python
+ settings
+
+ foreground
+ #f38ba8
+ fontStyle
+ italic
+
+
+
+ name
+ python keyword flow/logical (for ... in)
+ scope
+ keyword.control.flow.python, keyword.operator.logical.python
+ settings
+
+ foreground
+ #cba6f7
+
+
+
+ name
+ python storage type
+ scope
+ storage.type.function.python
+ settings
+
+ foreground
+ #cba6f7
+
+
+
+ name
+ python function support
+ scope
+ support.token.decorator.python, meta.function.decorator.identifier.python
+ settings
+
+ foreground
+ #89dceb
+
+
+
+ name
+ python function calls
+ scope
+ meta.function-call.python
+ settings
+
+ foreground
+ #89b4fa
+
+
+
+ name
+ python function decorators
+ scope
+ entity.name.function.decorator.python, punctuation.definition.decorator.python
+ settings
+
+ foreground
+ #fab387
+ fontStyle
+ italic
+
+
+
+ name
+ python placeholder reset to normal string
+ scope
+ constant.character.format.placeholder.other.python
+ settings
+
+ foreground
+ #f5c2e7
+
+
+
+ name
+ Python exception & builtins such as exit()
+ scope
+ support.type.exception.python, support.function.builtin.python
+ settings
+
+ foreground
+ #fab387
+
+
+
+ name
+ entity.name.type
+ scope
+ support.type.python
+ settings
+
+ foreground
+ #fab387
+
+
+
+ name
+ python constants (True/False)
+ scope
+ constant.language.python
+ settings
+
+ foreground
+ #cba6f7
+
+
+
+ name
+ Arguments accessed later in the function body
+ scope
+ meta.indexed-name.python, meta.item-access.python
+ settings
+
+ foreground
+ #eba0ac
+ fontStyle
+ italic
+
+
+
+ name
+ Python f-strings/binary/unicode storage types
+ scope
+ storage.type.string.python
+ settings
+
+ foreground
+ #a6e3a1
+ fontStyle
+ italic
+
+
+
+ name
+ Python type hints
+ scope
+ meta.function.parameters.python
+ settings
+
+ fontStyle
+
+
+
+
+ name
+ Regex string begin/end in JS/TS
+ scope
+ string.regexp punctuation.definition.string.begin, string.regexp punctuation.definition.string.end
+ settings
+
+ foreground
+ #f5c2e7
+
+
+
+ name
+ Regex anchors (^, $)
+ scope
+ keyword.control.anchor.regexp
+ settings
+
+ foreground
+ #cba6f7
+
+
+
+ name
+ Regex regular string match
+ scope
+ string.regexp.ts
+ settings
+
+ foreground
+ #cdd6f4
+
+
+
+ name
+ Regex group parenthesis & backreference (\1, \2, \3, ...)
+ scope
+ punctuation.definition.group.regexp, keyword.other.back-reference.regexp
+ settings
+
+ foreground
+ #a6e3a1
+
+
+
+ name
+ Regex character class []
+ scope
+ punctuation.definition.character-class.regexp
+ settings
+
+ foreground
+ #f9e2af
+
+
+
+ name
+ Regex character classes (\d, \w, \s)
+ scope
+ constant.other.character-class.regexp
+ settings
+
+ foreground
+ #f5c2e7
+
+
+
+ name
+ Regex range
+ scope
+ constant.other.character-class.range.regexp
+ settings
+
+ foreground
+ #f5e0dc
+
+
+
+ name
+ Regex quantifier
+ scope
+ keyword.operator.quantifier.regexp
+ settings
+
+ foreground
+ #94e2d5
+
+
+
+ name
+ Regex constant/numeric
+ scope
+ constant.character.numeric.regexp
+ settings
+
+ foreground
+ #fab387
+
+
+
+ name
+ Regex lookaheads, negative lookaheads, lookbehinds, negative lookbehinds
+ scope
+ punctuation.definition.group.no-capture.regexp, meta.assertion.look-ahead.regexp, meta.assertion.negative-look-ahead.regexp
+ settings
+
+ foreground
+ #89b4fa
+
+
+
+ name
+ Rust attribute
+ scope
+ meta.annotation.rust, meta.annotation.rust punctuation, meta.attribute.rust, punctuation.definition.attribute.rust
+ settings
+
+ foreground
+ #f9e2af
+ fontStyle
+ italic
+
+
+
+ name
+ Rust attribute strings
+ scope
+ meta.attribute.rust string.quoted.double.rust, meta.attribute.rust string.quoted.single.char.rust
+ settings
+
+ fontStyle
+
+
+
+
+ name
+ Rust keyword
+ scope
+ entity.name.function.macro.rules.rust, storage.type.module.rust, storage.modifier.rust, storage.type.struct.rust, storage.type.enum.rust, storage.type.trait.rust, storage.type.union.rust, storage.type.impl.rust, storage.type.rust, storage.type.function.rust, storage.type.type.rust
+ settings
+
+ foreground
+ #cba6f7
+ fontStyle
+
+
+
+
+ name
+ Rust u/i32, u/i64, etc.
+ scope
+ entity.name.type.numeric.rust
+ settings
+
+ foreground
+ #cba6f7
+ fontStyle
+
+
+
+
+ name
+ Rust generic
+ scope
+ meta.generic.rust
+ settings
+
+ foreground
+ #fab387
+
+
+
+ name
+ Rust impl
+ scope
+ entity.name.impl.rust
+ settings
+
+ foreground
+ #f9e2af
+ fontStyle
+ italic
+
+
+
+ name
+ Rust module
+ scope
+ entity.name.module.rust
+ settings
+
+ foreground
+ #fab387
+
+
+
+ name
+ Rust trait
+ scope
+ entity.name.trait.rust
+ settings
+
+ foreground
+ #f9e2af
+ fontStyle
+ italic
+
+
+
+ name
+ Rust struct
+ scope
+ storage.type.source.rust
+ settings
+
+ foreground
+ #f9e2af
+
+
+
+ name
+ Rust union
+ scope
+ entity.name.union.rust
+ settings
+
+ foreground
+ #f9e2af
+
+
+
+ name
+ Rust enum member
+ scope
+ meta.enum.rust storage.type.source.rust
+ settings
+
+ foreground
+ #94e2d5
+
+
+
+ name
+ Rust macro
+ scope
+ support.macro.rust, meta.macro.rust support.function.rust, entity.name.function.macro.rust
+ settings
+
+ foreground
+ #89b4fa
+ fontStyle
+ italic
+
+
+
+ name
+ Rust lifetime
+ scope
+ storage.modifier.lifetime.rust, entity.name.type.lifetime
+ settings
+
+ foreground
+ #89b4fa
+ fontStyle
+ italic
+
+
+
+ name
+ Rust string formatting
+ scope
+ string.quoted.double.rust constant.other.placeholder.rust
+ settings
+
+ foreground
+ #f5c2e7
+
+
+
+ name
+ Rust return type generic
+ scope
+ meta.function.return-type.rust meta.generic.rust storage.type.rust
+ settings
+
+ foreground
+ #cdd6f4
+
+
+
+ name
+ Rust functions
+ scope
+ meta.function.call.rust
+ settings
+
+ foreground
+ #89b4fa
+
+
+
+ name
+ Rust angle brackets
+ scope
+ punctuation.brackets.angle.rust
+ settings
+
+ foreground
+ #89dceb
+
+
+
+ name
+ Rust constants
+ scope
+ constant.other.caps.rust
+ settings
+
+ foreground
+ #fab387
+
+
+
+ name
+ Rust function parameters
+ scope
+ meta.function.definition.rust variable.other.rust
+ settings
+
+ foreground
+ #eba0ac
+
+
+
+ name
+ Rust closure variables
+ scope
+ meta.function.call.rust variable.other.rust
+ settings
+
+ foreground
+ #cdd6f4
+
+
+
+ name
+ Rust self
+ scope
+ variable.language.self.rust
+ settings
+
+ foreground
+ #f38ba8
+
+
+
+ name
+ Rust metavariable names
+ scope
+ variable.other.metavariable.name.rust, meta.macro.metavariable.rust keyword.operator.macro.dollar.rust
+ settings
+
+ foreground
+ #f5c2e7
+
+
+
+ name
+ Shell shebang
+ scope
+ comment.line.shebang, comment.line.shebang punctuation.definition.comment, comment.line.shebang, punctuation.definition.comment.shebang.shell, meta.shebang.shell
+ settings
+
+ foreground
+ #f5c2e7
+ fontStyle
+ italic
+
+
+
+ name
+ Shell shebang command
+ scope
+ comment.line.shebang constant.language
+ settings
+
+ foreground
+ #94e2d5
+ fontStyle
+ italic
+
+
+
+ name
+ Shell interpolated command
+ scope
+ meta.function-call.arguments.shell punctuation.definition.variable.shell, meta.function-call.arguments.shell punctuation.section.interpolation, meta.function-call.arguments.shell punctuation.definition.variable.shell, meta.function-call.arguments.shell punctuation.section.interpolation
+ settings
+
+ foreground
+ #f38ba8
+
+
+
+ name
+ Shell interpolated command variable
+ scope
+ meta.string meta.interpolation.parameter.shell variable.other.readwrite
+ settings
+
+ foreground
+ #fab387
+ fontStyle
+ italic
+
+
+
+ scope
+ source.shell punctuation.section.interpolation, punctuation.definition.evaluation.backticks.shell
+ settings
+
+ foreground
+ #94e2d5
+
+
+
+ name
+ Shell EOF
+ scope
+ entity.name.tag.heredoc.shell
+ settings
+
+ foreground
+ #cba6f7
+
+
+
+ name
+ Shell quoted variable
+ scope
+ string.quoted.double.shell variable.other.normal.shell
+ settings
+
+ foreground
+ #cdd6f4
+
+
+
+ scope
+ markup.heading.synopsis.man, markup.heading.title.man, markup.heading.other.man, markup.heading.env.man
+ settings
+
+ foreground
+ #cba6f7
+
+
+
+ scope
+ markup.heading.commands.man
+ settings
+
+ foreground
+ #89b4fa
+
+
+
+ scope
+ markup.heading.env.man
+ settings
+
+ foreground
+ #f5c2e7
+
+
+
+
+
\ No newline at end of file