Restructure project
This commit is contained in:
1 parent
0c158652c9
commit
14726ca7d4
28 files changed
+330
-306
No files matched your search
Generated
+6
-13
@@ -158,8 +158,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "blog_server"
|
name = "blog"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"atom_syndication",
|
"atom_syndication",
|
||||||
"axum",
|
"axum",
|
||||||
@@ -232,13 +232,6 @@ dependencies = [
|
|||||||
"cfg-if 1.0.0",
|
"cfg-if 1.0.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "css_gen"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"syntect",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "darling"
|
name = "darling"
|
||||||
version = "0.12.4"
|
version = "0.12.4"
|
||||||
@@ -773,9 +766,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "miniz_oxide"
|
name = "miniz_oxide"
|
||||||
version = "0.5.1"
|
version = "0.5.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d2b29bd4bc3f33391105ebee3589c19197c4271e3e5a9ec9bfe8127eeff8f082"
|
checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"adler",
|
"adler",
|
||||||
]
|
]
|
||||||
@@ -953,9 +946,9 @@ checksum = "decf7381921fea4dcb2549c5667eda59b3ec297ab7e2b5fc33eac69d2e7da87b"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking_lot"
|
name = "parking_lot"
|
||||||
version = "0.12.0"
|
version = "0.12.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58"
|
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"lock_api",
|
"lock_api",
|
||||||
"parking_lot_core",
|
"parking_lot_core",
|
||||||
|
|||||||
+34
-5
@@ -1,6 +1,35 @@
|
|||||||
[workspace]
|
[package]
|
||||||
|
name = "blog"
|
||||||
|
version = "0.2.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
members = [
|
[lib]
|
||||||
"blog_server",
|
name = "blog"
|
||||||
"utils/css_gen"
|
path = "src/lib/lib.rs"
|
||||||
]
|
|
||||||
|
[[bin]]
|
||||||
|
name = "blog_server"
|
||||||
|
path = "src/bin/blog_server/main.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "css_gen"
|
||||||
|
path = "src/bin/css_gen/main.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
libshire = { git = "https://github.com/pantonshire/libshire" }
|
||||||
|
tokio = { version = "1", features = ["full"] }
|
||||||
|
axum = "0.5"
|
||||||
|
tower = { version = "0.4", features = ["limit"] }
|
||||||
|
tower-http = { version = "0.3", features = ["fs", "trace"] }
|
||||||
|
mime = "0.3"
|
||||||
|
maud = "0.23"
|
||||||
|
atom_syndication = "0.11"
|
||||||
|
rss = "2"
|
||||||
|
knuffel = "2"
|
||||||
|
pulldown-cmark = "0.9"
|
||||||
|
syntect = "4"
|
||||||
|
notify = "4"
|
||||||
|
chrono = "0.4"
|
||||||
|
tracing = "0.1"
|
||||||
|
tracing-subscriber = "0.3"
|
||||||
|
miette = { version = "4", features = ["fancy"] }
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "blog_server"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
# My own utilities library
|
|
||||||
libshire = { git = "https://github.com/pantonshire/libshire" }
|
|
||||||
# Async runtime for Axum
|
|
||||||
tokio = { version = "1", features = ["full"] }
|
|
||||||
# Web server framework
|
|
||||||
axum = "0.5"
|
|
||||||
# Middleware for the web server
|
|
||||||
tower = { version = "0.4", features = ["limit"] }
|
|
||||||
tower-http = { version = "0.3", features = ["fs", "trace"] }
|
|
||||||
# MIME type implementation
|
|
||||||
mime = "0.3"
|
|
||||||
# Compile-time HTTP templating
|
|
||||||
maud = "0.23"
|
|
||||||
# Serialisation for RSS and Atom
|
|
||||||
atom_syndication = "0.11"
|
|
||||||
rss = "2"
|
|
||||||
# KDL parsing
|
|
||||||
knuffel = "2"
|
|
||||||
# CommonMark parsing
|
|
||||||
pulldown-cmark = "0.9"
|
|
||||||
# Syntax highlighting
|
|
||||||
syntect = "4"
|
|
||||||
# Filesystem event watcher
|
|
||||||
notify = "4"
|
|
||||||
# Time library
|
|
||||||
chrono = "0.4"
|
|
||||||
# Logging for observability
|
|
||||||
tracing = "0.1"
|
|
||||||
tracing-subscriber = "0.3"
|
|
||||||
# Pretty errors
|
|
||||||
miette = { version = "4", features = ["fancy"] }
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use atom_syndication as atom;
|
|
||||||
use axum::{
|
|
||||||
body::Bytes,
|
|
||||||
extract::Extension,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::response::Atom;
|
|
||||||
use crate::{
|
|
||||||
Config,
|
|
||||||
posts_store::ConcurrentPostsStore,
|
|
||||||
time::unix_epoch,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub async fn handle(
|
|
||||||
Extension(config): Extension<Arc<Config>>,
|
|
||||||
Extension(posts): Extension<ConcurrentPostsStore>,
|
|
||||||
) -> Atom<Bytes> {
|
|
||||||
let (atom_entries, updated) = {
|
|
||||||
let guard = posts.read().await;
|
|
||||||
|
|
||||||
let atom_entries = guard.iter_by_created()
|
|
||||||
.take(config.atom.num_posts)
|
|
||||||
.map(|post| {
|
|
||||||
atom::EntryBuilder::default()
|
|
||||||
.id(format!("urn:uuid:{}", post.uuid()))
|
|
||||||
.title(post.title().to_owned())
|
|
||||||
.updated(post.updated())
|
|
||||||
.links(vec![
|
|
||||||
atom::LinkBuilder::default()
|
|
||||||
.href(format!(
|
|
||||||
"{}://{}/articles/{}",
|
|
||||||
config.self_ref.protocol,
|
|
||||||
config.self_ref.domain,
|
|
||||||
post.id()
|
|
||||||
))
|
|
||||||
.rel("alternate".to_owned())
|
|
||||||
.mime_type(Some("text/html".to_owned()))
|
|
||||||
.build()
|
|
||||||
])
|
|
||||||
.author(atom::PersonBuilder::default()
|
|
||||||
.name(post.author().to_owned())
|
|
||||||
.build())
|
|
||||||
.build()
|
|
||||||
})
|
|
||||||
.collect::<Vec<atom::Entry>>();
|
|
||||||
|
|
||||||
let updated = guard.last_updated()
|
|
||||||
.unwrap_or_else(unix_epoch);
|
|
||||||
|
|
||||||
(atom_entries, updated)
|
|
||||||
};
|
|
||||||
|
|
||||||
Atom(atom::FeedBuilder::default()
|
|
||||||
.id(format!("urn:uuid:{}", *config.namespace_uuid))
|
|
||||||
.title(config.atom.title.clone())
|
|
||||||
.updated(updated)
|
|
||||||
.links(vec![
|
|
||||||
atom::LinkBuilder::default()
|
|
||||||
.href(format!(
|
|
||||||
"{}://{}/atom.xml",
|
|
||||||
config.self_ref.protocol,
|
|
||||||
config.self_ref.domain
|
|
||||||
))
|
|
||||||
.rel("self".to_owned())
|
|
||||||
.build(),
|
|
||||||
atom::LinkBuilder::default()
|
|
||||||
.href(format!(
|
|
||||||
"{}://{}/articles/",
|
|
||||||
config.self_ref.protocol,
|
|
||||||
config.self_ref.domain
|
|
||||||
))
|
|
||||||
.rel("alternate".to_owned())
|
|
||||||
.mime_type(Some("text/html".to_owned()))
|
|
||||||
.build()
|
|
||||||
])
|
|
||||||
.entries(atom_entries)
|
|
||||||
.build()
|
|
||||||
.to_string()
|
|
||||||
.into())
|
|
||||||
}
|
|
||||||
File renamed without changes.
@@ -1,12 +1,7 @@
|
|||||||
mod codeblock;
|
|
||||||
mod fs_watcher;
|
mod fs_watcher;
|
||||||
mod post;
|
|
||||||
mod posts_store;
|
|
||||||
mod render;
|
mod render;
|
||||||
mod service;
|
mod service;
|
||||||
mod template;
|
mod template;
|
||||||
mod time;
|
|
||||||
mod uuid;
|
|
||||||
|
|
||||||
use std::{env, fs, path::PathBuf, sync::Arc, thread};
|
use std::{env, fs, path::PathBuf, sync::Arc, thread};
|
||||||
|
|
||||||
@@ -14,8 +9,12 @@ use axum::Server;
|
|||||||
use miette::{IntoDiagnostic, Context};
|
use miette::{IntoDiagnostic, Context};
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
use codeblock::CodeBlockRenderer;
|
use blog::{
|
||||||
use posts_store::ConcurrentPostsStore;
|
codeblock::CodeBlockRenderer,
|
||||||
|
db::ConcurrentPostsStore,
|
||||||
|
uuid,
|
||||||
|
};
|
||||||
|
|
||||||
use render::Renderer;
|
use render::Renderer;
|
||||||
|
|
||||||
#[derive(knuffel::Decode, Clone, Debug)]
|
#[derive(knuffel::Decode, Clone, Debug)]
|
||||||
@@ -10,14 +10,15 @@ use chrono::DateTime;
|
|||||||
use notify::DebouncedEvent;
|
use notify::DebouncedEvent;
|
||||||
use tracing::{info, warn, error};
|
use tracing::{info, warn, error};
|
||||||
|
|
||||||
use crate::{
|
use blog::{
|
||||||
codeblock::CodeBlockRenderer,
|
codeblock::CodeBlockRenderer,
|
||||||
Config,
|
post::{parse as parse_post, ParseError, Post, PostId},
|
||||||
post::{ParseError, Post, PostId},
|
db::ConcurrentPostsStore,
|
||||||
posts_store::ConcurrentPostsStore,
|
|
||||||
time::unix_epoch,
|
time::unix_epoch,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::Config;
|
||||||
|
|
||||||
pub struct Renderer {
|
pub struct Renderer {
|
||||||
config: Arc<Config>,
|
config: Arc<Config>,
|
||||||
posts: ConcurrentPostsStore,
|
posts: ConcurrentPostsStore,
|
||||||
@@ -206,7 +207,7 @@ impl Renderer {
|
|||||||
|
|
||||||
drop(fd);
|
drop(fd);
|
||||||
|
|
||||||
Post::parse(
|
parse_post(
|
||||||
&self.code_renderer,
|
&self.code_renderer,
|
||||||
*self.config.namespace_uuid,
|
*self.config.namespace_uuid,
|
||||||
target.id.clone(),
|
target.id.clone(),
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use atom_syndication as atom;
|
||||||
|
use axum::{body::Bytes, extract::Extension};
|
||||||
|
|
||||||
|
use blog::{db::ConcurrentPostsStore, time::unix_epoch};
|
||||||
|
|
||||||
|
use crate::Config;
|
||||||
|
|
||||||
|
use super::response::Atom;
|
||||||
|
|
||||||
|
pub async fn handle(
|
||||||
|
Extension(config): Extension<Arc<Config>>,
|
||||||
|
Extension(posts): Extension<ConcurrentPostsStore>,
|
||||||
|
) -> Atom<Bytes> {
|
||||||
|
let (atom_entries, updated) = {
|
||||||
|
let guard = posts.read().await;
|
||||||
|
|
||||||
|
let atom_entries = guard
|
||||||
|
.iter_by_created()
|
||||||
|
.take(config.atom.num_posts)
|
||||||
|
.map(|post| {
|
||||||
|
atom::EntryBuilder::default()
|
||||||
|
.id(format!("urn:uuid:{}", post.uuid()))
|
||||||
|
.title(post.title().to_owned())
|
||||||
|
.updated(post.updated())
|
||||||
|
.links(vec![atom::LinkBuilder::default()
|
||||||
|
.href(format!(
|
||||||
|
"{}://{}/articles/{}",
|
||||||
|
config.self_ref.protocol,
|
||||||
|
config.self_ref.domain,
|
||||||
|
post.id()
|
||||||
|
))
|
||||||
|
.rel("alternate".to_owned())
|
||||||
|
.mime_type(Some("text/html".to_owned()))
|
||||||
|
.build()])
|
||||||
|
.author(
|
||||||
|
atom::PersonBuilder::default()
|
||||||
|
.name(post.author().to_owned())
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
})
|
||||||
|
.collect::<Vec<atom::Entry>>();
|
||||||
|
|
||||||
|
let updated = guard.last_updated().unwrap_or_else(unix_epoch);
|
||||||
|
|
||||||
|
(atom_entries, updated)
|
||||||
|
};
|
||||||
|
|
||||||
|
Atom(
|
||||||
|
atom::FeedBuilder::default()
|
||||||
|
.id(format!("urn:uuid:{}", *config.namespace_uuid))
|
||||||
|
.title(config.atom.title.clone())
|
||||||
|
.updated(updated)
|
||||||
|
.links(vec![
|
||||||
|
atom::LinkBuilder::default()
|
||||||
|
.href(format!(
|
||||||
|
"{}://{}/atom.xml",
|
||||||
|
config.self_ref.protocol, config.self_ref.domain
|
||||||
|
))
|
||||||
|
.rel("self".to_owned())
|
||||||
|
.build(),
|
||||||
|
atom::LinkBuilder::default()
|
||||||
|
.href(format!(
|
||||||
|
"{}://{}/articles/",
|
||||||
|
config.self_ref.protocol, config.self_ref.domain
|
||||||
|
))
|
||||||
|
.rel("alternate".to_owned())
|
||||||
|
.mime_type(Some("text/html".to_owned()))
|
||||||
|
.build(),
|
||||||
|
])
|
||||||
|
.entries(atom_entries)
|
||||||
|
.build()
|
||||||
|
.to_string()
|
||||||
|
.into(),
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
use maud::html;
|
use maud::html;
|
||||||
|
|
||||||
use crate::template;
|
use crate::template;
|
||||||
|
|
||||||
use super::response::Html;
|
use super::response::Html;
|
||||||
|
|
||||||
pub async fn handle() -> Html {
|
pub async fn handle() -> Html {
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
use axum::extract::Extension;
|
use axum::extract::Extension;
|
||||||
use maud::html;
|
use maud::html;
|
||||||
|
|
||||||
use crate::{
|
use blog::db::ConcurrentPostsStore;
|
||||||
posts_store::ConcurrentPostsStore,
|
|
||||||
template,
|
use crate::template;
|
||||||
};
|
|
||||||
use super::response::Html;
|
use super::response::Html;
|
||||||
|
|
||||||
pub async fn handle(Extension(posts): Extension<ConcurrentPostsStore>) -> Html {
|
pub async fn handle(Extension(posts): Extension<ConcurrentPostsStore>) -> Html {
|
||||||
File renamed without changes.
@@ -1,10 +1,10 @@
|
|||||||
use axum::extract::{Extension, Path};
|
use axum::extract::{Extension, Path};
|
||||||
use maud::html;
|
use maud::html;
|
||||||
|
|
||||||
use crate::{
|
use blog::db::ConcurrentPostsStore;
|
||||||
posts_store::ConcurrentPostsStore,
|
|
||||||
template,
|
use crate::template;
|
||||||
};
|
|
||||||
use super::response::{Error, Html};
|
use super::response::{Error, Html};
|
||||||
|
|
||||||
pub async fn handle(
|
pub async fn handle(
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
use axum::extract::Extension;
|
use axum::extract::Extension;
|
||||||
use maud::html;
|
use maud::html;
|
||||||
|
|
||||||
use crate::{
|
use blog::db::ConcurrentPostsStore;
|
||||||
posts_store::ConcurrentPostsStore,
|
|
||||||
template,
|
use crate::template;
|
||||||
};
|
|
||||||
use super::response::Html;
|
use super::response::Html;
|
||||||
|
|
||||||
pub async fn handle(Extension(posts): Extension<ConcurrentPostsStore>) -> Html {
|
pub async fn handle(Extension(posts): Extension<ConcurrentPostsStore>) -> Html {
|
||||||
File renamed without changes.
@@ -5,13 +5,15 @@ use axum::{
|
|||||||
extract::Extension,
|
extract::Extension,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::response::Rss;
|
use blog::{
|
||||||
use crate::{
|
db::ConcurrentPostsStore,
|
||||||
Config,
|
|
||||||
posts_store::ConcurrentPostsStore,
|
|
||||||
time::unix_epoch,
|
time::unix_epoch,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::Config;
|
||||||
|
|
||||||
|
use super::response::Rss;
|
||||||
|
|
||||||
pub async fn handle(
|
pub async fn handle(
|
||||||
Extension(config): Extension<Arc<Config>>,
|
Extension(config): Extension<Arc<Config>>,
|
||||||
Extension(posts): Extension<ConcurrentPostsStore>,
|
Extension(posts): Extension<ConcurrentPostsStore>,
|
||||||
@@ -11,10 +11,10 @@ use tower::limit::ConcurrencyLimitLayer;
|
|||||||
use tower_http::trace::TraceLayer;
|
use tower_http::trace::TraceLayer;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
use crate::{
|
use blog::db::ConcurrentPostsStore;
|
||||||
Config,
|
|
||||||
posts_store::ConcurrentPostsStore
|
use crate::Config;
|
||||||
};
|
|
||||||
use super::{
|
use super::{
|
||||||
atom,
|
atom,
|
||||||
contact,
|
contact,
|
||||||
File renamed without changes.
File renamed without changes.
@@ -1,10 +1,11 @@
|
|||||||
use std::env;
|
use std::{env, process};
|
||||||
use std::process;
|
|
||||||
|
|
||||||
use syntect::highlighting::ThemeSet;
|
use syntect::{
|
||||||
use syntect::html::{css_for_theme_with_class_style, ClassStyle};
|
highlighting::ThemeSet,
|
||||||
|
html::css_for_theme_with_class_style,
|
||||||
|
};
|
||||||
|
|
||||||
const CLASS_STYLE: ClassStyle = ClassStyle::SpacedPrefixed { prefix: "cb_" };
|
use blog::codeblock::CLASS_STYLE;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let theme_set = ThemeSet::load_defaults();
|
let theme_set = ThemeSet::load_defaults();
|
||||||
@@ -3,7 +3,7 @@ use syntect::html::{ClassedHTMLGenerator, ClassStyle};
|
|||||||
use syntect::parsing::SyntaxSet;
|
use syntect::parsing::SyntaxSet;
|
||||||
use syntect::util::LinesWithEndings;
|
use syntect::util::LinesWithEndings;
|
||||||
|
|
||||||
const CLASS_STYLE: ClassStyle = ClassStyle::SpacedPrefixed { prefix: "cb_" };
|
pub const CLASS_STYLE: ClassStyle = ClassStyle::SpacedPrefixed { prefix: "cb_" };
|
||||||
|
|
||||||
pub struct CodeBlockRenderer {
|
pub struct CodeBlockRenderer {
|
||||||
syntax_set: SyntaxSet,
|
syntax_set: SyntaxSet,
|
||||||
@@ -71,3 +71,9 @@ impl CodeBlockRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for CodeBlockRenderer {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
File renamed without changes.
@@ -0,0 +1,5 @@
|
|||||||
|
pub mod codeblock;
|
||||||
|
pub mod post;
|
||||||
|
pub mod db;
|
||||||
|
pub mod time;
|
||||||
|
pub mod uuid;
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
use std::{borrow, fmt, ops};
|
||||||
|
|
||||||
|
use libshire::strings::ShString22;
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||||
|
pub struct PostId(ShString22);
|
||||||
|
|
||||||
|
impl PostId {
|
||||||
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
|
pub fn from_file_name(file_name: &str) -> Option<Self> {
|
||||||
|
fn is_invalid_char(c: char) -> bool {
|
||||||
|
c == '/' || c == '\\' || c == '.'
|
||||||
|
}
|
||||||
|
|
||||||
|
let prefix = file_name
|
||||||
|
.strip_suffix(super::POST_FILE_EXTENSION)?;
|
||||||
|
|
||||||
|
if prefix.contains(is_invalid_char) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(Self(ShString22::new_from_str(prefix)))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
|
pub fn into_inner(self) -> ShString22 {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
|
pub fn as_inner(&self) -> &ShString22 {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ops::Deref for PostId {
|
||||||
|
type Target = str;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&*self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ops::DerefMut for PostId {
|
||||||
|
#[inline]
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut *self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<str> for PostId {
|
||||||
|
#[inline]
|
||||||
|
fn as_ref(&self) -> &str {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsMut<str> for PostId {
|
||||||
|
#[inline]
|
||||||
|
fn as_mut(&mut self) -> &mut str {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl borrow::Borrow<str> for PostId {
|
||||||
|
#[inline]
|
||||||
|
fn borrow(&self) -> &str {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl borrow::BorrowMut<str> for PostId {
|
||||||
|
#[inline]
|
||||||
|
fn borrow_mut(&mut self) -> &mut str {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for PostId {
|
||||||
|
#[inline]
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
fmt::Display::fmt(&**self, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
mod id;
|
||||||
|
mod parse;
|
||||||
|
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
use libshire::{strings::ShString22, uuid::Uuid};
|
||||||
|
use maud::{Markup, PreEscaped};
|
||||||
|
|
||||||
|
pub use id::PostId;
|
||||||
|
pub use parse::{parse, ParseError};
|
||||||
|
|
||||||
|
const POST_FILE_EXTENSION: &str = ".kdl.md";
|
||||||
|
|
||||||
|
pub struct Post {
|
||||||
|
uuid: Uuid,
|
||||||
|
id: PostId,
|
||||||
|
title: String,
|
||||||
|
subtitle: Option<String>,
|
||||||
|
author: ShString22,
|
||||||
|
html: Markup,
|
||||||
|
tags: Vec<ShString22>,
|
||||||
|
created: DateTime<Utc>,
|
||||||
|
updated: DateTime<Utc>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Post {
|
||||||
|
pub fn uuid(&self) -> Uuid {
|
||||||
|
self.uuid
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn id(&self) -> &PostId {
|
||||||
|
&self.id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn title(&self) -> &str {
|
||||||
|
&self.title
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn subtitle(&self) -> Option<&str> {
|
||||||
|
self.subtitle.as_deref()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn author(&self) -> &str {
|
||||||
|
&self.author
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn html(&self) -> PreEscaped<&str> {
|
||||||
|
PreEscaped(&self.html.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tags(&self) -> &[ShString22] {
|
||||||
|
&self.tags
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn created(&self) -> DateTime<Utc> {
|
||||||
|
self.created
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn updated(&self) -> DateTime<Utc> {
|
||||||
|
self.updated
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,125 +1,12 @@
|
|||||||
use std::{borrow, error, fmt, ops};
|
use std::{error, fmt};
|
||||||
|
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use libshire::{strings::ShString22, uuid::{Uuid, UuidV5Error}};
|
use libshire::{strings::ShString22, uuid::{Uuid, UuidV5Error}};
|
||||||
use maud::{Markup, PreEscaped, html};
|
use maud::{html, PreEscaped};
|
||||||
|
|
||||||
use crate::codeblock::CodeBlockRenderer;
|
use crate::codeblock::CodeBlockRenderer;
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
use super::{id::PostId, Post};
|
||||||
pub struct PostId(ShString22);
|
|
||||||
|
|
||||||
impl PostId {
|
|
||||||
pub fn from_file_name(file_name: &str) -> Option<Self> {
|
|
||||||
const POST_FILE_EXTENSION: &str = ".kdl.md";
|
|
||||||
|
|
||||||
fn is_invalid_char(c: char) -> bool {
|
|
||||||
c == '/' || c == '\\' || c == '.'
|
|
||||||
}
|
|
||||||
|
|
||||||
let prefix = file_name
|
|
||||||
.strip_suffix(POST_FILE_EXTENSION)?;
|
|
||||||
|
|
||||||
if prefix.contains(is_invalid_char) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(Self(ShString22::new_from_str(prefix)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ops::Deref for PostId {
|
|
||||||
type Target = str;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&*self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ops::DerefMut for PostId {
|
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
||||||
&mut *self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsRef<str> for PostId {
|
|
||||||
fn as_ref(&self) -> &str {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsMut<str> for PostId {
|
|
||||||
fn as_mut(&mut self) -> &mut str {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl borrow::Borrow<str> for PostId {
|
|
||||||
fn borrow(&self) -> &str {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl borrow::BorrowMut<str> for PostId {
|
|
||||||
fn borrow_mut(&mut self) -> &mut str {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for PostId {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
fmt::Display::fmt(&**self, f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Post {
|
|
||||||
uuid: Uuid,
|
|
||||||
id: PostId,
|
|
||||||
title: String,
|
|
||||||
subtitle: Option<String>,
|
|
||||||
author: String,
|
|
||||||
html: Markup,
|
|
||||||
tags: Vec<ShString22>,
|
|
||||||
created: DateTime<Utc>,
|
|
||||||
updated: DateTime<Utc>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Post {
|
|
||||||
pub fn uuid(&self) -> Uuid {
|
|
||||||
self.uuid
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn id(&self) -> &PostId {
|
|
||||||
&self.id
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn title(&self) -> &str {
|
|
||||||
&self.title
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn subtitle(&self) -> Option<&str> {
|
|
||||||
self.subtitle.as_deref()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn author(&self) -> &str {
|
|
||||||
&self.author
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn html(&self) -> PreEscaped<&str> {
|
|
||||||
PreEscaped(&self.html.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn tags(&self) -> &[ShString22] {
|
|
||||||
&self.tags
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn created(&self) -> DateTime<Utc> {
|
|
||||||
self.created
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn updated(&self) -> DateTime<Utc> {
|
|
||||||
self.updated
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse(
|
pub fn parse(
|
||||||
code_renderer: &CodeBlockRenderer,
|
code_renderer: &CodeBlockRenderer,
|
||||||
@@ -129,10 +16,10 @@ impl Post {
|
|||||||
created: DateTime<Utc>,
|
created: DateTime<Utc>,
|
||||||
updated: DateTime<Utc>,
|
updated: DateTime<Utc>,
|
||||||
source: &str,
|
source: &str,
|
||||||
) -> Result<Self, ParseError>
|
) -> Result<Post, ParseError>
|
||||||
{
|
{
|
||||||
MdPost::parse(file_name, source)
|
MdPost::parse(file_name, source)
|
||||||
.and_then(|post| Self::from_mdpost(
|
.and_then(|post| render_mdpost(
|
||||||
code_renderer,
|
code_renderer,
|
||||||
namespace,
|
namespace,
|
||||||
post_id,
|
post_id,
|
||||||
@@ -142,14 +29,14 @@ impl Post {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_mdpost(
|
fn render_mdpost(
|
||||||
code_renderer: &CodeBlockRenderer,
|
code_renderer: &CodeBlockRenderer,
|
||||||
namespace: Uuid,
|
namespace: Uuid,
|
||||||
id: PostId,
|
id: PostId,
|
||||||
created: DateTime<Utc>,
|
created: DateTime<Utc>,
|
||||||
updated: DateTime<Utc>,
|
updated: DateTime<Utc>,
|
||||||
mdpost: MdPost,
|
mdpost: MdPost,
|
||||||
) -> Result<Self, ParseError>
|
) -> Result<Post, ParseError>
|
||||||
{
|
{
|
||||||
use pulldown_cmark::{Options, Parser, html::push_html};
|
use pulldown_cmark::{Options, Parser, html::push_html};
|
||||||
|
|
||||||
@@ -170,7 +57,7 @@ impl Post {
|
|||||||
let mut html_buf = String::new();
|
let mut html_buf = String::new();
|
||||||
push_html(&mut html_buf, parser.by_ref());
|
push_html(&mut html_buf, parser.by_ref());
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Post {
|
||||||
uuid,
|
uuid,
|
||||||
id,
|
id,
|
||||||
title: mdpost.title,
|
title: mdpost.title,
|
||||||
@@ -182,7 +69,6 @@ impl Post {
|
|||||||
updated,
|
updated,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// Iterator struct which wraps another event iterator in order to render code blocks, collect the links
|
/// Iterator struct which wraps another event iterator in order to render code blocks, collect the links
|
||||||
/// encountered and generate a summary of the text content.
|
/// encountered and generate a summary of the text content.
|
||||||
@@ -283,7 +169,7 @@ struct MdPost {
|
|||||||
markdown: String,
|
markdown: String,
|
||||||
title: String,
|
title: String,
|
||||||
subtitle: Option<String>,
|
subtitle: Option<String>,
|
||||||
author: String,
|
author: ShString22,
|
||||||
tags: Vec<ShString22>,
|
tags: Vec<ShString22>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,7 +189,7 @@ impl MdPost {
|
|||||||
markdown: md.to_owned(),
|
markdown: md.to_owned(),
|
||||||
title: header.title,
|
title: header.title,
|
||||||
subtitle: header.subtitle,
|
subtitle: header.subtitle,
|
||||||
author: header.author,
|
author: header.author.into(),
|
||||||
tags: header.tags.into_iter().map(|tag| tag.tag.into()).collect(),
|
tags: header.tags.into_iter().map(|tag| tag.tag.into()).collect(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
File renamed without changes.
File renamed without changes.
@@ -1,7 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "css_gen"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
syntect = "4"
|
|
||||||
Reference in new issue
Block a user