too much stuff
This commit is contained in:
parent
cbfc505649
commit
e0a3f35caf
21 changed files with 262 additions and 86 deletions
12
404.html
12
404.html
|
@ -1,12 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>404 Not Found</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>404 Not Found</h1>
|
||||
</body>
|
||||
</html>
|
126
Cargo.lock
generated
126
Cargo.lock
generated
|
@ -17,6 +17,17 @@ version = "1.0.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.0.2"
|
||||
|
@ -153,6 +164,12 @@ dependencies = [
|
|||
"rustc-demangle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.21.2"
|
||||
|
@ -354,6 +371,25 @@ dependencies = [
|
|||
"tracing-error",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "config"
|
||||
version = "0.13.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d379af7f68bfc21714c6c7dea883544201741d2ce8274bb12fa54f89507f52a7"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"json5",
|
||||
"lazy_static",
|
||||
"nom",
|
||||
"pathdiff",
|
||||
"ron",
|
||||
"rust-ini",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"toml 0.5.11",
|
||||
"yaml-rust",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.4"
|
||||
|
@ -471,6 +507,12 @@ dependencies = [
|
|||
"crypto-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dlv-list"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.1"
|
||||
|
@ -708,6 +750,9 @@ name = "hashbrown"
|
|||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
|
@ -942,6 +987,17 @@ dependencies = [
|
|||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "json5"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"pest_derive",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
|
@ -1034,6 +1090,12 @@ 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.7.1"
|
||||
|
@ -1054,6 +1116,16 @@ dependencies = [
|
|||
"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 = "nu-ansi-term"
|
||||
version = "0.46.0"
|
||||
|
@ -1166,6 +1238,16 @@ dependencies = [
|
|||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ordered-multimap"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccd746e37177e1711c20dd619a1620f34f5c8b569c53590a72dedd5344d8924a"
|
||||
dependencies = [
|
||||
"dlv-list",
|
||||
"hashbrown 0.12.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "overload"
|
||||
version = "0.1.1"
|
||||
|
@ -1210,6 +1292,12 @@ dependencies = [
|
|||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pathdiff"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.0"
|
||||
|
@ -1343,7 +1431,7 @@ version = "1.5.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bdc0001cfea3db57a2e24bc0d818e9e20e554b5f97fabb9bc231dc240269ae06"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"base64 0.21.2",
|
||||
"indexmap 1.9.3",
|
||||
"line-wrap",
|
||||
"quick-xml",
|
||||
|
@ -1515,6 +1603,27 @@ version = "0.7.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2"
|
||||
|
||||
[[package]]
|
||||
name = "ron"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88073939a61e5b7680558e6be56b419e208420c2adb92be54921fa6b72283f1a"
|
||||
dependencies = [
|
||||
"base64 0.13.1",
|
||||
"bitflags 1.3.2",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-ini"
|
||||
version = "0.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6d5f2436026b4f6e79dc829837d467cc7e9a55ee40e750d716713540715a2df"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"ordered-multimap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.23"
|
||||
|
@ -1891,6 +2000,15 @@ dependencies = [
|
|||
"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.7.6"
|
||||
|
@ -1953,7 +2071,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "55ae70283aba8d2a8b411c695c437fe25b8b5e44e23e780662002fc72fb47a82"
|
||||
dependencies = [
|
||||
"async-compression",
|
||||
"base64",
|
||||
"base64 0.21.2",
|
||||
"bitflags 2.3.3",
|
||||
"bytes",
|
||||
"futures-core",
|
||||
|
@ -2306,8 +2424,8 @@ dependencies = [
|
|||
"cached",
|
||||
"chrono",
|
||||
"color-eyre",
|
||||
"config",
|
||||
"glob",
|
||||
"lazy_static",
|
||||
"opentelemetry",
|
||||
"prometheus",
|
||||
"pulldown-cmark",
|
||||
|
@ -2318,7 +2436,7 @@ dependencies = [
|
|||
"syntect",
|
||||
"tera",
|
||||
"tokio",
|
||||
"toml",
|
||||
"toml 0.7.6",
|
||||
"tower",
|
||||
"tower-http",
|
||||
"tracing",
|
||||
|
|
|
@ -10,9 +10,8 @@ axum = { version = "0.6.12", features = ["http2", "original-uri"] }
|
|||
cached = "0.44.0"
|
||||
chrono = { version = "0.4.24", features = ["serde"] }
|
||||
color-eyre = "0.6.1"
|
||||
config = "0.13.3"
|
||||
glob = "0.3.0"
|
||||
# hyper = { version = "0.14.19", features = ["full"] }
|
||||
lazy_static = "1.4.0"
|
||||
opentelemetry = { version = "0.19.0", features = ["metrics"] }
|
||||
prometheus = { version = "0.13.3", features = ["process"] }
|
||||
pulldown-cmark = "0.9.2"
|
||||
|
|
3
config.toml
Normal file
3
config.toml
Normal file
|
@ -0,0 +1,3 @@
|
|||
base_url = "http://localhost:8080/"
|
||||
bind_address = "0.0.0.0:8080"
|
||||
logging = "info"
|
7
posts/draft-test.md
Normal file
7
posts/draft-test.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
+++
|
||||
title="draft test"
|
||||
draft=true
|
||||
date=2023-07-29T17:25:20+02:00
|
||||
+++
|
||||
|
||||
wow look it's a hidden post because it's marked as a draft
|
|
@ -13,6 +13,8 @@ here have a squid kid miku to test relative paths:
|
|||
modified post test, see if docker skips build using
|
||||
its cache if only a post has changed.
|
||||
|
||||
testing "smart" punctuation --- I don't know if I want it. 'it should' do some fancy stuff.
|
||||
|
||||
code hilighting test:
|
||||
|
||||
```rs
|
||||
|
@ -20,3 +22,11 @@ fn main() {
|
|||
println!("Hello world!")
|
||||
}
|
||||
```
|
||||
|
||||
uh oh, here comes a screenshot from a different post!
|
||||
|
||||
![dungeon screenshot](../dungeon/screenshot.png)
|
||||
|
||||
and here it is again, except it should 404!
|
||||
|
||||
![missing dungeon screenshot](../dungeon/screenshot.jpeg)
|
||||
|
|
|
@ -16,7 +16,7 @@ struct FeedContext<'a> {
|
|||
|
||||
#[instrument(skip(state))]
|
||||
pub fn render_atom_feed(state: &AppState) -> Result<String> {
|
||||
let mut posts: Vec<_> = state.posts.values().filter(|p| p.is_published()).collect();
|
||||
let mut posts: Vec<_> = state.posts.values().filter(|p| !p.draft && p.is_published()).collect();
|
||||
|
||||
posts.sort_by_key(|p| &p.date);
|
||||
posts.reverse();
|
||||
|
@ -41,7 +41,7 @@ pub fn render_atom_tag_feed(tag: &Tag, state: &AppState) -> Result<String> {
|
|||
let mut posts: Vec<_> = state
|
||||
.posts
|
||||
.values()
|
||||
.filter(|p| p.is_published() && p.tags.contains(&tag.slug))
|
||||
.filter(|p| !p.draft && p.is_published() && p.tags.contains(&tag.slug))
|
||||
.collect();
|
||||
|
||||
posts.sort_by_key(|p| &p.date);
|
||||
|
|
|
@ -7,14 +7,14 @@ use axum::{
|
|||
routing::get,
|
||||
Router,
|
||||
};
|
||||
use cached::once_cell::sync::Lazy;
|
||||
use chrono::{DateTime, FixedOffset};
|
||||
use lazy_static::lazy_static;
|
||||
use prometheus::{opts, Encoder, IntCounterVec, TextEncoder};
|
||||
use std::sync::Arc;
|
||||
use tower_http::services::ServeFile;
|
||||
use tower_http::services::ServeDir;
|
||||
use tracing::{
|
||||
instrument,
|
||||
log::{error, info},
|
||||
log::{error, info, debug},
|
||||
};
|
||||
|
||||
use crate::{AppState, WebsiteError};
|
||||
|
@ -22,16 +22,14 @@ use crate::{AppState, WebsiteError};
|
|||
pub mod posts;
|
||||
pub mod tags;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref HIT_COUNTER: IntCounterVec = prometheus::register_int_counter_vec!(
|
||||
pub static HIT_COUNTER: Lazy<IntCounterVec> = Lazy::new(|| prometheus::register_int_counter_vec!(
|
||||
opts!(
|
||||
"http_requests_total",
|
||||
"Total amount of http requests received"
|
||||
),
|
||||
&["route", "method", "status"]
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
.unwrap());
|
||||
|
||||
pub fn routes(state: &Arc<AppState>) -> Router<Arc<AppState>> {
|
||||
Router::new()
|
||||
|
@ -44,13 +42,12 @@ pub fn routes(state: &Arc<AppState>) -> Router<Arc<AppState>> {
|
|||
.route("/metrics", get(metrics))
|
||||
.route_service(
|
||||
"/posts/:slug/*path",
|
||||
tower_http::services::ServeDir::new("./").fallback(ServeFile::new("./404.html")),
|
||||
ServeDir::new("./"),
|
||||
)
|
||||
.route_service(
|
||||
"/static/*path",
|
||||
tower_http::services::ServeDir::new("./").fallback(ServeFile::new("./404.html")),
|
||||
ServeDir::new("./"),
|
||||
)
|
||||
.fallback_service(ServeFile::new("./404.html"))
|
||||
}
|
||||
|
||||
#[instrument(skip(state))]
|
||||
|
@ -61,10 +58,11 @@ pub async fn index(
|
|||
if should_return_304(&headers, Some(state.startup_time.into())) {
|
||||
return Ok(StatusCode::NOT_MODIFIED.into_response());
|
||||
}
|
||||
let ctx = tera::Context::new();
|
||||
let mut ctx = tera::Context::new();
|
||||
ctx.insert("base_url", &state.base_url.to_string());
|
||||
let res = state.tera.render("index.html", &ctx).map_err(|e| {
|
||||
error!("Failed rendering index: {}", e);
|
||||
WebsiteError::NotFound
|
||||
WebsiteError::InternalError(e.into())
|
||||
})?;
|
||||
Ok((
|
||||
StatusCode::OK,
|
||||
|
@ -97,17 +95,17 @@ pub async fn not_found() -> impl IntoResponse {
|
|||
|
||||
pub async fn metrics_middleware<B>(request: Request<B>, next: Next<B>) -> Response {
|
||||
let path = request.uri().path().to_string();
|
||||
let method = request.method().to_string();
|
||||
let method = request.method().clone();
|
||||
|
||||
let response = next.run(request).await;
|
||||
|
||||
if !response.status().is_client_error() {
|
||||
HIT_COUNTER
|
||||
.with_label_values(&[&path, &method, response.status().as_str()])
|
||||
.with_label_values(&[&path, method.as_str(), response.status().as_str()])
|
||||
.inc();
|
||||
} else if response.status() == StatusCode::NOT_FOUND {
|
||||
HIT_COUNTER
|
||||
.with_label_values(&["not found", &method, response.status().as_str()])
|
||||
.with_label_values(&["not found", method.as_str(), response.status().as_str()])
|
||||
.inc();
|
||||
}
|
||||
|
||||
|
@ -116,16 +114,16 @@ pub async fn metrics_middleware<B>(request: Request<B>, next: Next<B>) -> Respon
|
|||
|
||||
fn should_return_304(headers: &HeaderMap, last_changed: Option<DateTime<FixedOffset>>) -> bool {
|
||||
let Some(date) = last_changed else {
|
||||
info!("no last modified date");
|
||||
debug!("no last modified date");
|
||||
return false;
|
||||
};
|
||||
let Some(since) = headers.get(header::IF_MODIFIED_SINCE) else {
|
||||
info!("no IF_MODIFIED_SINCE header");
|
||||
debug!("no IF_MODIFIED_SINCE header");
|
||||
return false;
|
||||
};
|
||||
|
||||
let Ok(parsed) = DateTime::<FixedOffset>::parse_from_rfc2822(since.to_str().unwrap()) else {
|
||||
info!("failed to parse IF_MODIFIED_SINCE header");
|
||||
debug!("failed to parse IF_MODIFIED_SINCE header");
|
||||
return false;
|
||||
};
|
||||
|
||||
|
@ -160,7 +158,8 @@ mod tests {
|
|||
#[test]
|
||||
fn render_index() {
|
||||
let tera = tera::Tera::new("templates/**/*").unwrap();
|
||||
let ctx = tera::Context::new();
|
||||
let mut ctx = tera::Context::new();
|
||||
ctx.insert("base_url", "http://localhost/");
|
||||
let _res = tera.render("index.html", &ctx).unwrap();
|
||||
}
|
||||
|
||||
|
|
|
@ -56,7 +56,7 @@ pub async fn index(
|
|||
State(state): State<Arc<AppState>>,
|
||||
headers: HeaderMap,
|
||||
) -> Result<Response, WebsiteError> {
|
||||
let mut posts: Vec<&Post> = state.posts.values().filter(|p| p.is_published()).collect();
|
||||
let mut posts: Vec<&Post> = state.posts.values().filter(|p| !p.draft && p.is_published()).collect();
|
||||
|
||||
let last_changed = posts.iter().filter_map(|p| p.last_modified()).max();
|
||||
|
||||
|
@ -135,7 +135,7 @@ pub async fn feed(
|
|||
State(state): State<Arc<AppState>>,
|
||||
headers: HeaderMap,
|
||||
) -> Result<Response, WebsiteError> {
|
||||
let mut posts: Vec<&Post> = state.posts.values().filter(|p| p.is_published()).collect();
|
||||
let mut posts: Vec<&Post> = state.posts.values().filter(|p| !p.draft && p.is_published()).collect();
|
||||
|
||||
let last_changed = posts.iter().filter_map(|p| p.last_modified()).max();
|
||||
|
||||
|
@ -203,6 +203,7 @@ mod tests {
|
|||
];
|
||||
let page = PageContext { title: "Posts" };
|
||||
let mut ctx = tera::Context::new();
|
||||
ctx.insert("base_url", "http://localhost/");
|
||||
ctx.insert("page", &page);
|
||||
ctx.insert("posts", &posts);
|
||||
|
||||
|
|
|
@ -57,7 +57,7 @@ pub async fn view(
|
|||
let mut posts: Vec<&Post> = state
|
||||
.posts
|
||||
.values()
|
||||
.filter(|p| p.is_published() && p.tags.contains(&tag))
|
||||
.filter(|p| !p.draft && p.is_published() && p.tags.contains(&tag))
|
||||
.collect();
|
||||
|
||||
let last_changed = posts.iter().filter_map(|p| p.last_modified()).max();
|
||||
|
|
|
@ -31,3 +31,28 @@ pub fn uri_with_path(uri: &Uri, path: &str) -> Uri {
|
|||
.build()
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use axum::http::Uri;
|
||||
|
||||
use crate::helpers::uri_with_path;
|
||||
|
||||
#[test]
|
||||
fn uri_with_relative_path() {
|
||||
let uri: Uri = "http://localhost/baba/".parse().unwrap();
|
||||
assert_eq!(uri_with_path(&uri, "is/you").to_string(), "http://localhost/baba/is/you");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uri_with_absolute_path() {
|
||||
let uri: Uri = "http://localhost/baba/is/you".parse().unwrap();
|
||||
assert_eq!(uri_with_path(&uri, "/keke/is/move").to_string(), "http://localhost/keke/is/move");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uri_with_index_relative_path() {
|
||||
let uri: Uri = "http://localhost/baba/is/you".parse().unwrap();
|
||||
assert_eq!(uri_with_path(&uri, "happy").to_string(), "http://localhost/baba/is/happy");
|
||||
}
|
||||
}
|
||||
|
|
29
src/main.rs
29
src/main.rs
|
@ -11,6 +11,7 @@ use axum::{
|
|||
use chrono::DateTime;
|
||||
use color_eyre::eyre::{Error, Result};
|
||||
|
||||
use config::Config;
|
||||
use post::Post;
|
||||
|
||||
use tag::Tag;
|
||||
|
@ -39,15 +40,20 @@ pub struct AppState {
|
|||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
let cfg = Config::builder()
|
||||
.add_source(config::File::with_name("config.toml"))
|
||||
.add_source(config::Environment::with_prefix("APP"))
|
||||
.build()?;
|
||||
|
||||
color_eyre::install()?;
|
||||
init_tracing();
|
||||
init_tracing(&cfg);
|
||||
|
||||
info!("Starting server...");
|
||||
|
||||
let base_url: Uri = option_env!("SITE_BASE_URL")
|
||||
.unwrap_or("http://localhost:8080")
|
||||
let base_url: Uri = cfg.get_string("base_url")?
|
||||
.parse()
|
||||
.unwrap();
|
||||
|
||||
let tera = Tera::new("templates/**/*")?;
|
||||
let mut state = AppState {
|
||||
startup_time: chrono::offset::Utc::now(),
|
||||
|
@ -55,6 +61,7 @@ async fn main() -> Result<()> {
|
|||
tera,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let posts = post::load_all(&state).await?;
|
||||
let tags = tag::get_tags(posts.values());
|
||||
state.posts = posts;
|
||||
|
@ -73,17 +80,23 @@ async fn main() -> Result<()> {
|
|||
|
||||
info!("Now listening at {}", state.base_url);
|
||||
|
||||
axum::Server::bind(&"0.0.0.0:8080".parse().unwrap())
|
||||
axum::Server::bind(&cfg.get_string("bind_address")?.parse().unwrap())
|
||||
.serve(app.into_make_service())
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn init_tracing() {
|
||||
let filter = EnvFilter::builder()
|
||||
.with_default_directive("into".parse().unwrap())
|
||||
.from_env_lossy();
|
||||
fn init_tracing(cfg: &Config) {
|
||||
let filter = if let Ok(filter) = cfg.get_string("logging") {
|
||||
EnvFilter::builder()
|
||||
.with_default_directive("info".parse().unwrap())
|
||||
.parse_lossy(filter)
|
||||
} else {
|
||||
EnvFilter::builder()
|
||||
.with_default_directive("info".parse().unwrap())
|
||||
.from_env_lossy()
|
||||
};
|
||||
|
||||
tracing_subscriber::registry()
|
||||
.with(filter)
|
||||
|
|
|
@ -1,14 +1,26 @@
|
|||
use crate::helpers;
|
||||
use crate::hilighting;
|
||||
use axum::http::Uri;
|
||||
use cached::once_cell::sync::Lazy;
|
||||
use pulldown_cmark::Event;
|
||||
use pulldown_cmark::Tag;
|
||||
use pulldown_cmark::{Options, Parser};
|
||||
use regex::Regex;
|
||||
|
||||
static STARTS_WITH_SCHEMA_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"^\w+:").unwrap());
|
||||
static EMAIL_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"^.+?@\w+(\.\w+)*$").unwrap());
|
||||
|
||||
pub fn render_markdown_to_html(base_uri: Option<&Uri>, markdown: &str) -> String {
|
||||
let options = Options::all();
|
||||
let mut opt = Options::empty();
|
||||
opt.insert(Options::ENABLE_FOOTNOTES);
|
||||
opt.insert(Options::ENABLE_HEADING_ATTRIBUTES);
|
||||
opt.insert(Options::ENABLE_STRIKETHROUGH);
|
||||
opt.insert(Options::ENABLE_TABLES);
|
||||
opt.insert(Options::ENABLE_TASKLISTS);
|
||||
opt.insert(Options::ENABLE_SMART_PUNCTUATION);
|
||||
|
||||
let mut content_html = String::new();
|
||||
let parser = Parser::new_ext(markdown, options);
|
||||
let parser = Parser::new_ext(markdown, opt);
|
||||
|
||||
let mut code_block = false;
|
||||
let mut code_lang = None;
|
||||
|
@ -25,7 +37,7 @@ pub fn render_markdown_to_html(base_uri: Option<&Uri>, markdown: &str) -> String
|
|||
}
|
||||
Event::Start(Tag::Link(t, mut link, title)) => {
|
||||
if let Some(uri) = base_uri {
|
||||
if !link.contains("://") && !link.contains('@') {
|
||||
if !link.starts_with('#') && !STARTS_WITH_SCHEMA_RE.is_match(&link) && !EMAIL_RE.is_match(&link) {
|
||||
// convert relative URIs to absolute URIs
|
||||
link = helpers::uri_with_path(uri, &link).to_string().into();
|
||||
}
|
||||
|
|
20
src/post.rs
20
src/post.rs
|
@ -1,10 +1,9 @@
|
|||
use std::{collections::HashMap, path::Path};
|
||||
|
||||
use cached::once_cell::sync::Lazy;
|
||||
use chrono::{DateTime, FixedOffset};
|
||||
use glob::glob;
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
use regex::Regex;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use tokio::fs;
|
||||
|
@ -16,6 +15,11 @@ use tracing::{
|
|||
|
||||
use crate::{helpers, markdown, AppState, WebsiteError};
|
||||
|
||||
static FRONTMATTER_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(
|
||||
r"^[\s]*\+{3}(\r?\n(?s).*?(?-s))\+{3}[\s]*(?:$|(?:\r?\n((?s).*(?-s))$))"
|
||||
)
|
||||
.unwrap());
|
||||
|
||||
#[derive(Deserialize, Debug, Default)]
|
||||
pub struct TomlFrontMatter {
|
||||
pub title: String,
|
||||
|
@ -29,6 +33,7 @@ pub struct TomlFrontMatter {
|
|||
#[derive(Serialize, Clone, Debug, Default)]
|
||||
pub struct Post {
|
||||
pub title: String,
|
||||
pub draft: bool,
|
||||
pub date: Option<DateTime<FixedOffset>>,
|
||||
pub updated: Option<DateTime<FixedOffset>>,
|
||||
pub aliases: Vec<String>,
|
||||
|
@ -45,6 +50,7 @@ impl Post {
|
|||
slug,
|
||||
content,
|
||||
title: fm.title,
|
||||
draft: fm.draft.unwrap_or(false),
|
||||
date: fm
|
||||
.date
|
||||
.map(|d| DateTime::parse_from_rfc3339(&d.to_string()).expect("bad toml datetime")),
|
||||
|
@ -124,13 +130,6 @@ pub async fn load_post(state: &AppState, slug: &str) -> color_eyre::eyre::Result
|
|||
fn parse_frontmatter(
|
||||
src: String,
|
||||
) -> color_eyre::eyre::Result<(Option<TomlFrontMatter>, Option<String>)> {
|
||||
lazy_static! {
|
||||
static ref FRONTMATTER_REGEX: Regex = regex::Regex::new(
|
||||
r"^[\s]*\+{3}(\r?\n(?s).*?(?-s))\+{3}[\s]*(?:$|(?:\r?\n((?s).*(?-s))$))"
|
||||
)
|
||||
.unwrap();
|
||||
};
|
||||
|
||||
Ok(if let Some(captures) = FRONTMATTER_REGEX.captures(&src) {
|
||||
(
|
||||
Some(toml::from_str(captures.get(1).unwrap().as_str())?),
|
||||
|
@ -162,10 +161,11 @@ mod tests {
|
|||
#[tokio::test]
|
||||
async fn render_all_posts() {
|
||||
let mut state = AppState {
|
||||
base_url: "localhost:8180".parse().unwrap(),
|
||||
base_url: "http://localhost:8180".parse().unwrap(),
|
||||
tera: Tera::new("templates/**/*").unwrap(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
state.posts = super::load_all(&state).await.unwrap();
|
||||
for post in state.posts.values() {
|
||||
super::render_post(&state, post).await.unwrap();
|
||||
|
|
|
@ -2,16 +2,10 @@
|
|||
<html lang="en">
|
||||
{% include "partials/head.html" %}
|
||||
<body>
|
||||
<header>
|
||||
{% include "partials/header.html" -%}
|
||||
</header>
|
||||
<hr>
|
||||
<main>
|
||||
{% block main %}{% endblock main -%}
|
||||
</main>
|
||||
<hr>
|
||||
<footer>
|
||||
{% include "partials/footer.html" -%}
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -1 +1,4 @@
|
|||
<footer>
|
||||
<hr style="border-style: dashed;">
|
||||
<p>footer stuff goes here</p>
|
||||
</footer>
|
|
@ -1,3 +1,5 @@
|
|||
<header>
|
||||
<nav>
|
||||
<img src="{{base_url | safe}}static/avatar.png" class="avatar"/> <a href="{{base_url | safe}}">tollyx</a> - <a href="{{base_url | safe}}posts/">posts</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
|
|
@ -6,9 +6,11 @@
|
|||
<h1>{{ page.title }}</h1>
|
||||
{% endif -%}
|
||||
{% if page.date -%}
|
||||
<small>Posted on <time datetime="{{ page.date }}">{{ page.date | date(format="%Y-%m-%d %H:%M") }}</time>
|
||||
<small>
|
||||
{%- if page.draft %}Draft {% endif -%}
|
||||
Published <time datetime="{{ page.date }}">{{ page.date | date(format="%F %R%:::z") }}</time>
|
||||
{%- if page.updated -%}
|
||||
, Updated <time datetime="{{ page.updated }}">{{ page.updated | date(format="%Y-%m-%d %H:%M") }}</time>
|
||||
, Updated <time datetime="{{ page.updated }}">{{ page.updated | date(format="%F %R%:::z") }}</time>
|
||||
{%- endif -%}
|
||||
</small>
|
||||
{%- endif %}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<ul>
|
||||
{% for post in posts -%}
|
||||
<li><a href="{{base_url | trim_end_matches(pat='/') | safe}}{{post.absolute_path | safe}}">{% if post.date -%}
|
||||
<time datetime="{{ post.date }}">{{ post.date | date(format="%Y-%m-%d") }}</time> - {{ post.title -}}
|
||||
<time datetime="{{ post.date }}">{{ post.date | date(format="%F") }}</time> – {{ post.title -}}
|
||||
{% else -%}
|
||||
{{ post.title -}}
|
||||
{% endif -%}
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<ul>
|
||||
{% for post in posts -%}
|
||||
<li><a href="{{base_url | trim_end_matches(pat='/') | safe}}{{post.absolute_path | safe}}">{% if post.date -%}
|
||||
<time datetime="{{ post.date }}">{{ post.date | date(format="%Y-%m-%d") }}</time> - {{ post.title -}}
|
||||
<time datetime="{{ post.date }}">{{ post.date | date(format="%F") }}</time> - {{ post.title -}}
|
||||
{% else -%}
|
||||
{{ post.title -}}
|
||||
{% endif -%}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{% extends "base.html" %}
|
||||
{% block main -%}
|
||||
<h1>Tags</h1>
|
||||
<h2>Tags</h2>
|
||||
<ul>
|
||||
{% for tag in tags -%}
|
||||
<li><a href="{{base_url | trim_end_matches(pat='/') | safe}}{{tag.absolute_path | safe}}">#{{ tag.slug }}</a></li>
|
||||
|
|
Loading…
Reference in a new issue