mirror of
https://git.sr.ht/~adnano/kiln
synced 2024-05-24 22:26:08 +02:00
109 lines
3.0 KiB
Rust
109 lines
3.0 KiB
Rust
use std::path::PathBuf;
|
|
|
|
use chrono::NaiveDateTime;
|
|
use lazy_static::lazy_static;
|
|
use pulldown_cmark::{html, Parser};
|
|
use regex::Regex;
|
|
use serde::{Deserialize, Deserializer, Serializer};
|
|
use serde_derive::{Deserialize, Serialize};
|
|
|
|
#[derive(Clone, Default, Deserialize, Serialize)]
|
|
#[serde(default)]
|
|
pub struct Page {
|
|
pub permalink: String,
|
|
pub title: String,
|
|
pub author: String,
|
|
pub description: String,
|
|
pub draft: bool,
|
|
#[serde(deserialize_with = "parse_datetime", serialize_with = "date_to_string")]
|
|
pub date: Option<NaiveDateTime>,
|
|
|
|
#[serde(skip_deserializing)]
|
|
pub content: String,
|
|
#[serde(skip_deserializing)]
|
|
pub prev: Option<Box<Page>>,
|
|
#[serde(skip_deserializing)]
|
|
pub next: Option<Box<Page>>,
|
|
|
|
#[serde(skip)]
|
|
pub input: PathBuf,
|
|
#[serde(skip)]
|
|
pub output: Option<PathBuf>,
|
|
}
|
|
|
|
impl Page {
|
|
pub fn from(path: PathBuf, content: &[u8]) -> Page {
|
|
let content = String::from_utf8_lossy(&content);
|
|
if !FRONTMATTER_REGEX.is_match(&content) {
|
|
return Page {
|
|
content: markdown_to_html(&content),
|
|
input: path,
|
|
..Default::default()
|
|
};
|
|
}
|
|
|
|
let captures = FRONTMATTER_REGEX.captures(&content).unwrap();
|
|
|
|
let mut page: Page = match toml::from_str(&captures[1]) {
|
|
Ok(page) => page,
|
|
Err(e) => {
|
|
warn!("failed to parse frontmatter in `{}`: {}", path.display(), e);
|
|
Page::default()
|
|
}
|
|
};
|
|
|
|
if page.permalink == "" {
|
|
page.permalink = "/".to_string() + &path.with_extension("").to_string_lossy();
|
|
} else {
|
|
page.output = Some(PathBuf::from(page.permalink.trim_start_matches('/')));
|
|
}
|
|
|
|
page.content = markdown_to_html(&captures[2]);
|
|
page.input = path;
|
|
page
|
|
}
|
|
}
|
|
|
|
lazy_static! {
|
|
static ref FRONTMATTER_REGEX: Regex =
|
|
Regex::new(r"^[[:space:]]*\-\-\-\r?\n((?s).*?(?-s))\-\-\-\r?\n?((?s).*(?-s))$").unwrap();
|
|
}
|
|
|
|
fn markdown_to_html(content: &str) -> String {
|
|
let mut result = String::new();
|
|
html::push_html(&mut result, Parser::new(content));
|
|
result
|
|
}
|
|
|
|
fn parse_datetime<'de, D>(deserializer: D) -> Result<Option<NaiveDateTime>, D::Error>
|
|
where
|
|
D: Deserializer<'de>,
|
|
{
|
|
let date = if let Ok(date) = toml::value::Datetime::deserialize(deserializer) {
|
|
let date = date.to_string();
|
|
if date.contains('T') {
|
|
chrono::DateTime::parse_from_rfc3339(&date)
|
|
.ok()
|
|
.map(|date| date.naive_local())
|
|
} else {
|
|
chrono::NaiveDate::parse_from_str(&date, "%Y-%m-%d")
|
|
.ok()
|
|
.map(|date| date.and_hms(0, 0, 0))
|
|
}
|
|
} else {
|
|
None
|
|
};
|
|
Ok(date)
|
|
}
|
|
|
|
fn date_to_string<S>(date: &Option<NaiveDateTime>, serializer: S) -> Result<S::Ok, S::Error>
|
|
where
|
|
S: Serializer,
|
|
{
|
|
if let Some(date) = date {
|
|
serializer.serialize_some(&date.format("%Y-%m-%dT%H:%M:%S").to_string())
|
|
} else {
|
|
serializer.serialize_none()
|
|
}
|
|
}
|