Switch to TOML for configuration

main
Pantonshire 4 years ago
parent ad3a0ff956
commit d7a521e581

2
.gitignore vendored

@ -1,6 +1,6 @@
.DS_Store
/target/
/config.kdl
/config.toml
/content/static/fonts/
/article_media/
/test_posts/

301
Cargo.lock generated

@ -2,30 +2,12 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "addr2line"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b"
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 = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
dependencies = [
"memchr",
]
[[package]]
name = "ansi_term"
version = "0.12.1"
@ -37,9 +19,9 @@ dependencies = [
[[package]]
name = "async-trait"
version = "0.1.53"
version = "0.1.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed6aa3524a2dfcf9fe180c51eae2b58738348d819517ceadf95789c51fff7600"
checksum = "e24af9c57a5a2463ffe401d5340433d21487b76238bae64ee5992ed688b4e753"
dependencies = [
"proc-macro2",
"quote",
@ -59,17 +41,6 @@ dependencies = [
"quick-xml",
]
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi 0.3.9",
]
[[package]]
name = "autocfg"
version = "1.1.0"
@ -121,21 +92,6 @@ dependencies = [
"mime",
]
[[package]]
name = "backtrace"
version = "0.3.65"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11a17d453482a265fd5f8479f2a3f405566e6ca627837aaddb85af8b1ab8ef61"
dependencies = [
"addr2line",
"cc",
"cfg-if 1.0.0",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
]
[[package]]
name = "base64"
version = "0.13.0"
@ -164,17 +120,17 @@ dependencies = [
"atom_syndication",
"axum",
"chrono",
"kdl",
"knuffel",
"hyper",
"libshire",
"maud",
"miette",
"mime",
"notify",
"pulldown-cmark",
"rss",
"serde",
"syntect",
"tokio",
"toml",
"tower",
"tower-http",
"tracing",
@ -214,16 +170,11 @@ dependencies = [
"libc",
"num-integer",
"num-traits",
"serde",
"time 0.1.44",
"winapi 0.3.9",
]
[[package]]
name = "chumsky"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d02796e4586c6c41aeb68eae9bfb4558a522c35f1430c14b40136c3706e09e4"
[[package]]
name = "crc32fast"
version = "1.3.2"
@ -438,24 +389,12 @@ dependencies = [
"unicode-width",
]
[[package]]
name = "gimli"
version = "0.26.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4"
[[package]]
name = "hashbrown"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
[[package]]
name = "heck"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
[[package]]
name = "hermit-abi"
version = "0.1.19"
@ -573,29 +512,12 @@ dependencies = [
"libc",
]
[[package]]
name = "is_ci"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "616cde7c720bb2bb5824a224687d8f77bfd38922027f01d825cd7453be5099fb"
[[package]]
name = "itoa"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d"
[[package]]
name = "kdl"
version = "4.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7ba123fe3f30838b649efd5606531e8326623c5f44491c7e631f3b970e20cdb"
dependencies = [
"miette",
"nom",
"thiserror",
]
[[package]]
name = "kernel32-sys"
version = "0.2.2"
@ -606,33 +528,6 @@ dependencies = [
"winapi-build",
]
[[package]]
name = "knuffel"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f9f7a07459e9dc5d07f5dabfc2c2a965bf39195451eb785b61460c65f386eef"
dependencies = [
"base64",
"chumsky",
"knuffel-derive",
"miette",
"thiserror",
"unicode-width",
]
[[package]]
name = "knuffel-derive"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbcbdb0b6f26a4e5ecb0dd9074a430398a41b2c1624c205bcc202541ddc15488"
dependencies = [
"heck",
"proc-macro-error",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
@ -654,7 +549,10 @@ checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
[[package]]
name = "libshire"
version = "0.1.0"
source = "git+https://github.com/pantonshire/libshire#c0f0a3a01716ec48d91fa3daf035e506f3a36165"
source = "git+https://github.com/pantonshire/libshire#378800ce890c1e936d4923b2fc92255876bacdc3"
dependencies = [
"serde",
]
[[package]]
name = "line-wrap"
@ -729,37 +627,6 @@ version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "miette"
version = "4.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c90329e44f9208b55f45711f9558cec15d7ef8295cc65ecd6d4188ae8edc58c"
dependencies = [
"atty",
"backtrace",
"miette-derive",
"once_cell",
"owo-colors",
"supports-color",
"supports-hyperlinks",
"supports-unicode",
"terminal_size",
"textwrap",
"thiserror",
"unicode-width",
]
[[package]]
name = "miette-derive"
version = "4.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b5bc45b761bcf1b5e6e6c4128cd93b84c218721a8d9b894aa0aff4ed180174c"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "mime"
version = "0.3.16"
@ -776,12 +643,6 @@ dependencies = [
"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.5.3"
@ -863,16 +724,6 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c96aba5aa877601bb3f6dd6a63a969e1f82e60646e81e71b14496995e9853c91"
[[package]]
name = "nom"
version = "7.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "notify"
version = "4.0.17"
@ -929,15 +780,6 @@ dependencies = [
"libc",
]
[[package]]
name = "object"
version = "0.28.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e42c982f2d955fac81dd7e1d0e1426a7d702acd9c98d19ab01083a6a0328c424"
dependencies = [
"memchr",
]
[[package]]
name = "once_cell"
version = "1.12.0"
@ -966,12 +808,6 @@ dependencies = [
"pkg-config",
]
[[package]]
name = "owo-colors"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "decf7381921fea4dcb2549c5667eda59b3ec297ab7e2b5fc33eac69d2e7da87b"
[[package]]
name = "parking_lot"
version = "0.12.1"
@ -1126,17 +962,6 @@ dependencies = [
"bitflags",
]
[[package]]
name = "regex"
version = "1.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.26"
@ -1155,12 +980,6 @@ dependencies = [
"quick-xml",
]
[[package]]
name = "rustc-demangle"
version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
[[package]]
name = "ryu"
version = "1.0.10"
@ -1193,6 +1012,9 @@ name = "serde"
version = "1.0.137"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
@ -1258,12 +1080,6 @@ version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83"
[[package]]
name = "smawk"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043"
[[package]]
name = "socket2"
version = "0.4.4"
@ -1280,39 +1096,11 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "supports-color"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4872ced36b91d47bae8a214a683fe54e7078875b399dfa251df346c9b547d1f9"
dependencies = [
"atty",
"is_ci",
]
[[package]]
name = "supports-hyperlinks"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "590b34f7c5f01ecc9d78dba4b3f445f31df750a67621cf31626f3b7441ce6406"
dependencies = [
"atty",
]
[[package]]
name = "supports-unicode"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8b945e45b417b125a8ec51f1b7df2f8df7920367700d1f98aedd21e5735f8b2"
dependencies = [
"atty",
]
[[package]]
name = "syn"
version = "1.0.95"
version = "1.0.96"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbaf6116ab8924f39d52792136fb74fd60a80194cf1b1c6ffa6453eef1c3f942"
checksum = "0748dd251e24453cb8717f0354206b91557e4ec8703673a4b30208f2abaf1ebf"
dependencies = [
"proc-macro2",
"quote",
@ -1347,47 +1135,6 @@ dependencies = [
"yaml-rust",
]
[[package]]
name = "terminal_size"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df"
dependencies = [
"libc",
"winapi 0.3.9",
]
[[package]]
name = "textwrap"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
dependencies = [
"smawk",
"unicode-linebreak",
"unicode-width",
]
[[package]]
name = "thiserror"
version = "1.0.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "thread_local"
version = "1.1.4"
@ -1463,6 +1210,15 @@ dependencies = [
"tokio",
]
[[package]]
name = "toml"
version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7"
dependencies = [
"serde",
]
[[package]]
name = "tower"
version = "0.4.12"
@ -1598,15 +1354,6 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee"
[[package]]
name = "unicode-linebreak"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a52dcaab0c48d931f7cc8ef826fa51690a08e1ea55117ef26f89864f532383f"
dependencies = [
"regex",
]
[[package]]
name = "unicode-width"
version = "0.1.9"

@ -15,9 +15,14 @@ path = "src/bin/blog_server/main.rs"
name = "css_gen"
path = "src/bin/css_gen/main.rs"
[[bin]]
name = "prepublish"
path = "src/bin/prepublish/main.rs"
[dependencies]
libshire = { git = "https://github.com/pantonshire/libshire" }
libshire = { git = "https://github.com/pantonshire/libshire", features = ["serde"] }
tokio = { version = "1", features = ["full"] }
hyper = "0.14"
axum = "0.5"
tower = { version = "0.4", features = ["limit"] }
tower-http = { version = "0.3", features = ["fs", "trace"] }
@ -25,12 +30,11 @@ mime = "0.3"
maud = "0.23"
atom_syndication = "0.11"
rss = "2"
kdl = "4"
knuffel = "2" # TODO: replace with kdl crate
serde = { version = "1", features = ["derive"] }
toml = "0.5"
pulldown-cmark = "0.9"
syntect = "4"
notify = "4"
chrono = "0.4"
chrono = { version = "0.4", features = ["serde"] }
tracing = "0.1"
tracing-subscriber = "0.3"
miette = { version = "4", features = ["fancy"] }

@ -1,26 +0,0 @@
bind "127.0.0.1:8080"
concurrency-limit 128
static-dir "./content/static/"
favicon-dir "./content/favicon/"
robots-path "./content/robots.txt"
posts-dir "./posts/"
post-media-dir "./article_media/"
namespace-uuid "00000000-0000-0000-0000-000000000000"
self-ref {
protocol "http"
domain "127.0.0.1:8080"
}
rss {
num-posts 20
title "Pantonshire"
ttl 360
}
atom {
num-posts 20
title "Pantonshire"
}

@ -0,0 +1,23 @@
bind = "127.0.0.1:8080"
concurrency_limit = 128
static_dir = "./content/static/"
favicon_dir = "./content/favicon/"
robots_path = "./content/robots.txt"
posts_dir = "./articles/src/"
post_media_dir = "./articles/media/"
namespace_uuid = "00000000-0000-0000-0000-000000000000"
[self_ref]
protocol = "http"
domain = "localhost:8080"
[rss]
num_posts = 20
title = "Pantonshire"
ttl = 360
[atom]
num_posts = 20
title = "Pantonshire"

@ -1,114 +1,46 @@
use std::{path::PathBuf, ops};
use std::{net::SocketAddr, path::PathBuf, str};
use knuffel::{
ast::{Literal, TypeName},
decode::{Context, Kind},
errors::{DecodeError, ExpectedType},
span::Spanned,
traits::ErrorSpan,
DecodeScalar,
};
use libshire::uuid::Uuid;
use serde::Deserialize;
//TODO: replace knuffel crate with kdl crate
#[derive(knuffel::Decode, Clone, Debug)]
pub struct Config {
#[knuffel(child, unwrap(argument))]
pub bind: String,
#[knuffel(child, unwrap(argument))]
#[derive(Deserialize, Clone, Debug)]
pub(crate) struct Config {
pub bind: SocketAddr,
pub concurrency_limit: usize,
#[knuffel(child, unwrap(argument))]
pub static_dir: PathBuf,
#[knuffel(child, unwrap(argument))]
pub favicon_dir: PathBuf,
#[knuffel(child, unwrap(argument))]
pub robots_path: PathBuf,
#[knuffel(child, unwrap(argument))]
pub posts_dir: PathBuf,
#[knuffel(child, unwrap(argument))]
pub post_media_dir: PathBuf,
#[knuffel(child, unwrap(argument))]
pub namespace_uuid: Uuid,
#[knuffel(child)]
pub self_ref: SelfRefConfig,
#[knuffel(child)]
pub rss: RssConfig,
#[knuffel(child)]
pub atom: AtomConfig,
}
#[derive(knuffel::Decode, Clone, Debug)]
pub struct SelfRefConfig {
#[knuffel(child, unwrap(argument))]
#[derive(Deserialize, Clone, Debug)]
pub(crate) struct SelfRefConfig {
pub protocol: String,
#[knuffel(child, unwrap(argument))]
pub domain: String,
}
#[derive(knuffel::Decode, Clone, Debug)]
pub struct RssConfig {
#[knuffel(child, unwrap(argument))]
#[derive(Deserialize, Clone, Debug)]
pub(crate) struct RssConfig {
pub num_posts: usize,
#[knuffel(child, unwrap(argument))]
pub title: String,
#[knuffel(child, unwrap(argument))]
pub ttl: u32,
}
#[derive(knuffel::Decode, Clone, Debug)]
pub struct AtomConfig {
#[knuffel(child, unwrap(argument))]
#[derive(Deserialize, Clone, Debug)]
pub(crate) struct AtomConfig {
pub num_posts: usize,
#[knuffel(child, unwrap(argument))]
pub title: String,
}
#[derive(Clone, Copy, Default, Debug)]
#[repr(transparent)]
pub struct Uuid(pub libshire::uuid::Uuid);
impl Uuid {
pub fn as_inner(&self) -> &libshire::uuid::Uuid {
&self.0
}
}
impl ops::Deref for Uuid {
type Target = libshire::uuid::Uuid;
fn deref(&self) -> &Self::Target {
self.as_inner()
}
}
impl<S: ErrorSpan> DecodeScalar<S> for Uuid {
fn type_check(type_name: &Option<Spanned<TypeName, S>>, ctx: &mut Context<S>) {
if let Some(type_name) = type_name {
ctx.emit_error(DecodeError::TypeName {
span: type_name.span().clone(),
found: Some((&**type_name).clone()),
expected: ExpectedType::no_type(),
rust_type: "Uuid",
});
}
}
impl str::FromStr for Config {
type Err = toml::de::Error;
fn raw_decode(
value: &Spanned<Literal, S>,
ctx: &mut Context<S>,
) -> Result<Self, DecodeError<S>> {
match &**value {
Literal::String(s) => match s.parse() {
Ok(uuid) => Ok(Self(uuid)),
Err(err) => {
ctx.emit_error(DecodeError::conversion(value, err));
Ok(Default::default())
}
},
_ => {
ctx.emit_error(DecodeError::scalar_kind(Kind::String, value));
Ok(Default::default())
}
}
fn from_str(s: &str) -> Result<Self, Self::Err> {
toml::from_str(s)
}
}

@ -4,7 +4,6 @@ use std::{
time::Duration,
};
use miette::{IntoDiagnostic, WrapErr};
use notify::{
DebouncedEvent,
RecommendedWatcher,
@ -14,20 +13,20 @@ use notify::{
};
use tracing::info;
pub fn start_watching(
use crate::Error;
pub(crate) fn start_watching(
tx: mpsc::Sender<DebouncedEvent>,
watch_path: &Path
) -> miette::Result<RecommendedWatcher>
) -> Result<RecommendedWatcher, Error>
{
let mut watcher = watcher(tx, Duration::from_secs(2))
.into_diagnostic()
.wrap_err("Failed to create filesystem watcher")?;
.map_err(Error::CreateWatcher)?;
// Watch the path in non-recursive mode, so events are not generated for nodes in
// sub-directories.
watcher.watch(watch_path, RecursiveMode::NonRecursive)
.into_diagnostic()
.wrap_err_with(|| format!("Failed to watch directory {}", watch_path.to_string_lossy()))?;
.map_err(|err| Error::WatchDir(watch_path.to_owned(), err))?;
info!(path = %watch_path.to_string_lossy(), "Watching directory");

@ -4,10 +4,20 @@ mod render;
mod service;
mod template;
use std::{env, fs, sync::Arc, thread};
use std::{
env,
error,
fmt,
fs,
io,
net::SocketAddr,
path::PathBuf,
process,
sync::Arc,
thread,
};
use axum::Server;
use miette::{IntoDiagnostic, Context};
use hyper::Server;
use tracing::info;
use blog::{
@ -18,23 +28,30 @@ use blog::{
use config::Config;
use render::Renderer;
fn main() -> miette::Result<()> {
fn main() {
if let Err(err) = run() {
eprintln!("***** Fatal error *****");
eprintln!("{}", err);
process::exit(1);
}
}
fn run() -> Result<(), Error> {
tracing_subscriber::fmt::init();
// Load the configuration from the KDL config file specified by the first command-line
// Load the configuration from the TOML config file specified by the first command-line
// argument.
let config = Arc::new({
let config_path = env::args().nth(1)
.ok_or_else(|| miette::Error::msg("No config file specified"))?;
.ok_or(Error::NoConfig)?;
info!(path = %config_path, "Loading config");
let contents = fs::read_to_string(&config_path)
.into_diagnostic()
.wrap_err_with(|| format!("Failed to read config file {}", config_path))?;
.map_err(Error::ReadConfig)?;
knuffel::parse::<Config>(&config_path, &contents)
.wrap_err_with(|| format!("Failed to parse config file {}", config_path))?
contents.parse::<Config>()
.map_err(Error::BadConfig)?
});
// Create the data structure used to store the rendered posts. This uses an `Arc` internally,
@ -65,30 +82,67 @@ fn main() -> miette::Result<()> {
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.into_diagnostic()
.wrap_err("Failed to create async runtime")?
.block_on(run(config, posts_store))
.map_err(Error::TokioRuntime)?
.block_on(run_server(config, posts_store))
}
async fn run(
async fn run_server(
config: Arc<Config>,
posts_store: ConcurrentPostsStore,
) -> miette::Result<()>
) -> Result<(), Error>
{
let bind_address = &config.bind
.parse()
.into_diagnostic()
.wrap_err_with(|| format!("Failed to parse socket address \"{}\"", config.bind))?;
let service = service::site_service(config.clone(), posts_store);
let service = service::site_service(config, posts_store);
info!(address = %config.bind, "Starting server");
info!(address = %bind_address, "Starting server");
Server::try_bind(bind_address)
.into_diagnostic()
.wrap_err_with(|| format!("Failed to bind {}", bind_address))?
Server::try_bind(&config.bind)
.map_err(|err| Error::Bind(config.bind, err))?
.serve(service.into_make_service())
.await
.into_diagnostic()
.wrap_err("Fatal error while running the server")
.map_err(Error::Server)
}
#[derive(Debug)]
enum Error {
NoConfig,
ReadConfig(io::Error),
BadConfig(toml::de::Error),
CreateWatcher(notify::Error),
WatchDir(PathBuf, notify::Error),
TokioRuntime(io::Error),
Bind(SocketAddr, hyper::Error),
Server(hyper::Error),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NoConfig => {
write!(f, "no config file specified")
},
Self::ReadConfig(err) => {
write!(f, "failed to read config file: {}", err)
},
Self::BadConfig(err) => {
write!(f, "error in config: {}", err)
},
Self::CreateWatcher(err) => {
write!(f, "failed to create filesystem watcher: {}", err)
},
Self::WatchDir(path, err) => {
write!(f, "failed to watch directory {}: {}", path.to_string_lossy(), err)
},
Self::TokioRuntime(err) => {
write!(f, "failed to create async runtime: {}", err)
},
Self::Bind(addr, err) => {
write!(f, "failed to bind {}: {}", addr, err)
},
Self::Server(err) => {
write!(f, "error while running server: {}", err)
},
}
}
}
impl error::Error for Error {}

@ -18,7 +18,7 @@ use blog::{
use crate::Config;
pub struct Renderer {
pub(crate) struct Renderer {
config: Arc<Config>,
posts: ConcurrentPostsStore,
code_renderer: CodeBlockRenderer,
@ -27,7 +27,7 @@ pub struct Renderer {
}
impl Renderer {
pub fn new(
pub(crate) fn new(
config: Arc<Config>,
posts: ConcurrentPostsStore,
code_renderer: CodeBlockRenderer,
@ -51,7 +51,7 @@ impl Renderer {
}
#[tracing::instrument(skip(self))]
pub fn handle_events(self) {
pub(crate) fn handle_events(self) {
while let Ok(notify_event) = self.rx.recv() {
let fs_event = match notify_event {
// Convert create & write events for valid post file names to update events.
@ -204,7 +204,7 @@ impl Renderer {
Post::new_from_str(
&self.code_renderer,
*self.config.namespace_uuid,
self.config.namespace_uuid,
target.id.clone(),
updated,
&contents
@ -221,8 +221,8 @@ enum Event {
}
struct EventTarget {
pub path: PathBuf,
pub id: Id,
path: PathBuf,
id: Id,
}
impl fmt::Debug for EventTarget {
@ -232,7 +232,7 @@ impl fmt::Debug for EventTarget {
}
impl EventTarget {
pub fn from_path(path: PathBuf) -> Option<Self> {
fn from_path(path: PathBuf) -> Option<Self> {
path.file_name()
.and_then(|file_name| file_name.to_str())
.and_then(Id::from_file_name)
@ -243,7 +243,7 @@ impl EventTarget {
}
}
pub enum Error {
pub(crate) enum Error {
Io(Box<io::Error>),
NotAFile,
Parsing(Box<ParseError>),

@ -9,7 +9,7 @@ use crate::Config;
use super::response::Atom;
pub async fn handle(
pub(super) async fn handle(
Extension(config): Extension<Arc<Config>>,
Extension(posts): Extension<ConcurrentPostsStore>,
) -> Atom<Bytes> {
@ -50,7 +50,7 @@ pub async fn handle(
Atom(
atom::FeedBuilder::default()
.id(format!("urn:uuid:{}", *config.namespace_uuid))
.id(format!("urn:uuid:{}", config.namespace_uuid))
.title(config.atom.title.clone())
.updated(updated)
.links(vec![

@ -4,7 +4,7 @@ use crate::template;
use super::response::Html;
pub async fn handle() -> Html {
pub(super) async fn handle() -> Html {
Html::new()
.with_title_static("Contact")
.with_crawler_permissive()

@ -7,7 +7,7 @@ use crate::template;
use super::response::Html;
pub async fn handle(Extension(posts): Extension<ConcurrentPostsStore>) -> Html {
pub(super) async fn handle(Extension(posts): Extension<ConcurrentPostsStore>) -> Html {
Html::new()
.with_title_static("Pantonshire")
.with_crawler_permissive()

@ -8,4 +8,4 @@ mod rss;
mod site;
mod static_content;
pub use site::service as site_service;
pub(crate) use site::service as site_service;

@ -7,7 +7,7 @@ use crate::template;
use super::response::{Error, Html};
pub async fn handle(
pub(super) async fn handle(
Path(post_id): Path<String>,
Extension(posts): Extension<ConcurrentPostsStore>
) -> Result<Html, Error>

@ -7,7 +7,7 @@ use crate::template;
use super::response::Html;
pub async fn handle(Extension(posts): Extension<ConcurrentPostsStore>) -> Html {
pub(super) async fn handle(Extension(posts): Extension<ConcurrentPostsStore>) -> Html {
Html::new()
.with_title_static("Articles")
.with_crawler_permissive()

@ -11,7 +11,7 @@ use axum::{
use maud::{html, Markup, Render, Escaper, DOCTYPE};
#[derive(Debug)]
pub enum Error {
pub(super) enum Error {
Internal,
PostNotFound,
StaticResourceNotFound,
@ -62,7 +62,7 @@ impl IntoResponse for Error {
}
}
pub struct Html {
pub(super) struct Html {
status: StatusCode,
title: Cow<'static, str>,
head: Option<Markup>,
@ -71,7 +71,7 @@ pub struct Html {
}
impl Html {
pub fn new() -> Self {
pub(super) fn new() -> Self {
Self {
status: StatusCode::OK,
title: Cow::Borrowed("untitled"),
@ -81,39 +81,39 @@ impl Html {
}
}
pub fn with_status(self, status: StatusCode) -> Self {
pub(super) fn with_status(self, status: StatusCode) -> Self {
Self { status, ..self }
}
pub fn with_title(self, title: Cow<'static, str>) -> Self {
pub(super) fn with_title(self, title: Cow<'static, str>) -> Self {
Self { title, ..self }
}
pub fn with_title_static(self, title: &'static str) -> Self {
pub(super) fn with_title_static(self, title: &'static str) -> Self {
self.with_title(Cow::Borrowed(title))
}
pub fn with_title_owned(self, title: String) -> Self {
pub(super) fn with_title_owned(self, title: String) -> Self {
self.with_title(Cow::Owned(title))
}
pub fn with_head(self, head: Markup) -> Self {
pub(super) fn with_head(self, head: Markup) -> Self {
Self { head: Some(head), ..self }
}
pub fn with_body(self, body: Markup) -> Self {
pub(super) fn with_body(self, body: Markup) -> Self {
Self { body: Some(body), ..self }
}
pub fn with_crawler_hints(self, crawler_hints: CrawlerHints) -> Self {
pub(super) fn with_crawler_hints(self, crawler_hints: CrawlerHints) -> Self {
Self { crawler_hints, ..self }
}
pub fn with_crawler_restrictive(self) -> Self {
pub(super) fn with_crawler_restrictive(self) -> Self {
self.with_crawler_hints(CrawlerHints::restrictive())
}
pub fn with_crawler_permissive(self) -> Self {
pub(super) fn with_crawler_permissive(self) -> Self {
self.with_crawler_hints(CrawlerHints::permissive())
}
}
@ -160,7 +160,7 @@ impl IntoResponse for Html {
}
#[derive(Clone, Copy, Debug)]
pub struct CrawlerHints {
pub(super) struct CrawlerHints {
index: bool,
follow: bool,
archive: bool,
@ -169,7 +169,7 @@ pub struct CrawlerHints {
}
impl CrawlerHints {
pub const fn restrictive() -> Self {
pub(super) const fn restrictive() -> Self {
Self {
index: false,
follow: false,
@ -179,7 +179,7 @@ impl CrawlerHints {
}
}
pub const fn permissive() -> Self {
pub(super) const fn permissive() -> Self {
Self {
index: true,
follow: true,
@ -189,23 +189,23 @@ impl CrawlerHints {
}
}
pub const fn with_index(self, index: bool) -> Self {
pub(super) const fn with_index(self, index: bool) -> Self {
Self { index, ..self }
}
pub const fn with_follow(self, follow: bool) -> Self {
pub(super) const fn with_follow(self, follow: bool) -> Self {
Self { follow, ..self }
}
pub const fn with_archive(self, archive: bool) -> Self {
pub(super) const fn with_archive(self, archive: bool) -> Self {
Self { archive, ..self }
}
pub const fn with_snippet(self, snippet: bool) -> Self {
pub(super) const fn with_snippet(self, snippet: bool) -> Self {
Self { snippet, ..self }
}
pub const fn with_image_index(self, image_index: bool) -> Self {
pub(super) const fn with_image_index(self, image_index: bool) -> Self {
Self { image_index, ..self }
}
@ -271,7 +271,7 @@ impl Render for CrawlerHints {
}
}
pub struct Rss<T>(pub T);
pub(super) struct Rss<T>(pub T);
impl<T: Into<Full<Bytes>>> IntoResponse for Rss<T> {
fn into_response(self) -> Response {
@ -284,7 +284,7 @@ impl<T: Into<Full<Bytes>>> IntoResponse for Rss<T> {
}
}
pub struct Atom<T>(pub T);
pub(super) struct Atom<T>(pub T);
impl<T: Into<Full<Bytes>>> IntoResponse for Atom<T> {
fn into_response(self) -> Response {

@ -14,7 +14,7 @@ use crate::Config;
use super::response::Rss;
pub async fn handle(
pub(super) async fn handle(
Extension(config): Extension<Arc<Config>>,
Extension(posts): Extension<ConcurrentPostsStore>,
) -> Rss<Bytes> {

@ -26,7 +26,7 @@ use super::{
static_content,
};
pub fn service(
pub(crate) fn service(
config: Arc<Config>,
posts_store: ConcurrentPostsStore,
) -> Router
@ -55,7 +55,7 @@ pub fn service(
.layer(Extension(posts_store))
}
pub async fn handle_fallback(uri: Uri) -> Error {
async fn handle_fallback(uri: Uri) -> Error {
info!(path = %uri.path(), "Requested resource not found");
Error::RouteNotFound
}

@ -18,7 +18,7 @@ use tracing::{info, error};
use super::response::Error;
pub fn file_service(file_path: &Path, mime: Option<&Mime>) -> MethodRouter<Body, Infallible> {
pub(super) fn file_service(file_path: &Path, mime: Option<&Mime>) -> MethodRouter<Body, Infallible> {
let serve_file = match mime {
Some(mime) => ServeFile::new_with_mime(file_path, mime),
None => ServeFile::new(file_path),
@ -28,7 +28,7 @@ pub fn file_service(file_path: &Path, mime: Option<&Mime>) -> MethodRouter<Body,
.handle_error(handle_error)
}
pub fn dir_service(dir_path: &Path) -> MethodRouter<Body, Infallible> {
pub(super) fn dir_service(dir_path: &Path) -> MethodRouter<Body, Infallible> {
let fallback_service = handle_fallback
.into_service()
.map_err(Empty::elim::<io::Error>);
@ -40,12 +40,12 @@ pub fn dir_service(dir_path: &Path) -> MethodRouter<Body, Infallible> {
.handle_error(handle_error)
}
pub async fn handle_fallback(uri: Uri) -> Error {
pub(super) async fn handle_fallback(uri: Uri) -> Error {
info!(path = %uri.path(), "Requested static file not found");
Error::StaticResourceNotFound
}
pub async fn handle_error(uri: Uri, err: io::Error) -> Error {
pub(super) async fn handle_error(uri: Uri, err: io::Error) -> Error {
error!(path = %uri.path(), err = %err, "IO error");
Error::Internal
}

@ -1,6 +1,6 @@
use maud::{html, Markup};
pub fn main_page(content: Markup) -> Markup {
pub(crate) fn main_page(content: Markup) -> Markup {
html! {
header #page_header {
nav #page_nav {

@ -0,0 +1,4 @@
fn main() {
}

@ -1,18 +1,9 @@
use std::{error, fmt};
use kdl::KdlError;
#[derive(Debug)]
pub enum Error {
NoDelim,
Syntax(Box<KdlError>),
FieldMissing {
field: &'static str,
},
BadType {
field: &'static str,
expected: &'static str,
},
Header(Box<toml::de::Error>),
IdTooLong(usize),
}
@ -22,14 +13,8 @@ impl fmt::Display for Error {
Self::NoDelim => {
write!(f, "post has no header; no delimiter `\\n---\\n` found")
},
Self::Syntax(err) => {
write!(f, "syntax error in post header: {}", err)
},
Self::FieldMissing { field } => {
write!(f, "missing required post header field `{}`", field)
},
Self::BadType { field, expected } => {
write!(f, "expected post header field `{}` to be {}", field, expected)
Self::Header(err) => {
write!(f, "error decoding post header: {}", err)
},
Self::IdTooLong(len) => {
write!(f, "post id too long: {} bytes", len)
@ -40,8 +25,8 @@ impl fmt::Display for Error {
impl error::Error for Error {}
impl From<KdlError> for Error {
fn from(err: KdlError) -> Self {
Self::Syntax(Box::new(err))
impl From<toml::de::Error> for Error {
fn from(err: toml::de::Error) -> Self {
Self::Header(Box::new(err))
}
}

@ -1,16 +1,19 @@
use std::{fmt, str};
use chrono::{DateTime, Utc};
use kdl::KdlDocument;
use libshire::strings::ShString22;
use crate::time::{datetime_unix_seconds, unix_epoch};
use serde::{Serialize, Deserialize};
use super::error::Error;
#[derive(Clone, Deserialize, Serialize, Debug)]
pub struct Header {
pub(super) title: String,
pub(super) subtitle: Option<String>,
pub(super) author: ShString22,
#[serde(default)]
pub(super) tags: Vec<ShString22>,
#[serde(default = "crate::time::unix_epoch")]
pub(super) published: DateTime<Utc>,
}
@ -21,97 +24,74 @@ impl Header {
&self.title
}
#[inline]
#[must_use]
pub fn title_mut(&mut self) -> &mut String {
&mut self.title
}
#[inline]
#[must_use]
pub fn subtitle(&self) -> Option<&str> {
self.subtitle.as_deref()
}
#[inline]
#[must_use]
pub fn subtitle_mut(&mut self) -> &mut Option<String> {
&mut self.subtitle
}
#[inline]
#[must_use]
pub fn author(&self) -> &str {
&self.author
}
#[inline]
#[must_use]
pub fn author_mut(&mut self) -> &mut ShString22 {
&mut self.author
}
#[inline]
#[must_use]
pub fn tags(&self) -> &[ShString22] {
&self.tags
}
#[inline]
#[must_use]
pub fn tags_mut(&mut self) -> &mut Vec<ShString22> {
&mut self.tags
}
#[inline]
#[must_use]
pub fn published(&self) -> DateTime<Utc> {
self.published
}
}
impl<'a> TryFrom<&'a KdlDocument> for Header {
type Error = Error;
fn try_from(doc: &'a KdlDocument) -> Result<Self, Self::Error> {
let title = doc
.get_arg("title")
.ok_or(Error::FieldMissing { field: "title" })
.and_then(|value| value
.as_string()
.ok_or(Error::BadType { field: "title", expected: "string" })
.map(|title| title.to_owned()))?;
let subtitle = doc
.get_arg("subtitle")
.map(|value| value
.as_string()
.ok_or(Error::BadType { field: "subtitle", expected: "string" })
.map(|subtitle| subtitle.to_owned()))
.transpose()?;
let author = doc
.get_arg("title")
.ok_or(Error::FieldMissing { field: "author" })
.and_then(|value| value
.as_string()
.ok_or(Error::BadType { field: "author", expected: "string" })
.map(ShString22::from))?;
let tags = doc
.get("tags")
.map(|node| node.entries())
.unwrap_or_default()
.iter()
.filter_map(|entry| match entry.name() {
Some(_) => None,
None => Some(entry.value()),
})
.map(|value| value
.as_string()
.ok_or(Error::BadType { field: "tag", expected: "string" })
.map(ShString22::from))
.collect::<Result<_, _>>()?;
let published = doc
.get_arg("published")
.map(|value| value
.as_i64()
.ok_or(Error::BadType { field: "published", expected: "integer unix timestamp (seconds)" })
.map(datetime_unix_seconds))
.transpose()?
.unwrap_or_else(unix_epoch);
Ok(Header {
title,
subtitle,
author,
tags,
published,
})
#[inline]
#[must_use]
pub fn published_mut(&mut self) -> &mut DateTime<Utc> {
&mut self.published
}
}
impl TryFrom<KdlDocument> for Header {
type Error = Error;
impl str::FromStr for Header {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
toml::from_str(s)
.map_err(Error::from)
}
}
fn try_from(value: KdlDocument) -> Result<Self, Self::Error> {
Self::try_from(&value)
impl fmt::Display for Header {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
toml::to_string_pretty(self)
.map_err(|_| fmt::Error)
.and_then(|s| f.write_str(&s))
}
}

@ -1,31 +0,0 @@
use super::{error::Error, header::Header, source::PostSource};
pub struct MarkdownPost {
pub(super) header: Header,
pub(super) markdown: String,
}
impl MarkdownPost {
#[inline]
#[must_use]
pub fn header(&self) -> &Header {
&self.header
}
#[inline]
#[must_use]
pub fn markdown(&self) -> &str {
&self.markdown
}
}
impl TryFrom<PostSource> for MarkdownPost {
type Error = Error;
fn try_from(source: PostSource) -> Result<Self, Self::Error> {
Ok(Self {
header: source.header.try_into()?,
markdown: source.markdown,
})
}
}

@ -1,7 +1,6 @@
mod error;
mod id;
mod header;
mod markdown_post;
mod render;
mod rendered_post;
mod source;
@ -9,10 +8,9 @@ mod source;
pub use error::Error;
pub use header::Header;
pub use id::Id;
pub use markdown_post::MarkdownPost;
pub use rendered_post::RenderedPost;
pub use source::PostSource;
const POST_FILE_EXTENSION: &str = ".kdl.md";
const POST_FILE_EXTENSION: &str = ".toml.md";
pub type Post = RenderedPost;

@ -8,9 +8,8 @@ use super::{
error::Error,
header::Header,
id::Id,
markdown_post::MarkdownPost,
render::render_markdown,
source::PostSource,
render::render_markdown,
};
pub struct RenderedPost {
@ -30,19 +29,16 @@ impl RenderedPost {
source: &str
) -> Result<Self, Error>
{
let markdown_post = source
.parse::<PostSource>()
.and_then(MarkdownPost::try_from)?;
Self::new_from_markdown_post(code_renderer, namespace, id, updated, markdown_post)
let source = source.parse::<PostSource>()?;
Self::new_from_source(code_renderer, namespace, id, updated, source)
}
pub fn new_from_markdown_post(
pub fn new_from_source(
code_renderer: &CodeBlockRenderer,
namespace: Uuid,
id: Id,
updated: Option<DateTime<Utc>>,
markdown_post: MarkdownPost
source: PostSource
) -> Result<Self, Error>
{
let uuid = Uuid::new_v5(namespace, &*id)
@ -53,9 +49,9 @@ impl RenderedPost {
Ok(Self {
uuid,
id,
header: markdown_post.header,
header: source.header,
updated: updated.unwrap_or_else(unix_epoch),
html: render_markdown(code_renderer, &markdown_post.markdown),
html: render_markdown(code_renderer, &source.markdown),
})
}

@ -1,27 +1,25 @@
use std::{fmt, str};
use kdl::KdlDocument;
use super::error::Error;
use super::{error::Error, header::Header};
const DELIM: &str = "\n---\n";
#[derive(Clone, Debug)]
pub struct PostSource {
pub(super) header: KdlDocument,
pub(super) header: Header,
pub(super) markdown: String,
}
impl PostSource {
#[inline]
#[must_use]
pub fn header(&self) -> &KdlDocument {
pub fn header(&self) -> &Header {
&self.header
}
#[inline]
#[must_use]
pub fn header_mut(&mut self) -> &mut KdlDocument {
pub fn header_mut(&mut self) -> &mut Header {
&mut self.header
}
@ -53,7 +51,7 @@ impl str::FromStr for PostSource {
}
impl fmt::Display for PostSource {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}{}{}", self.header, DELIM, self.markdown)
}
}

Loading…
Cancel
Save