1
0
Fork 0
mirror of https://git.sr.ht/~adnano/kiln synced 2024-05-24 22:26:08 +02:00
kiln/src/page.rs
2019-05-18 14:00:12 -04:00

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()
}
}