razorbill 0.1.0

A toolkit for bespoke static site generators.
Documentation
use std::sync::OnceLock;

use regex::Regex;
use serde::{Deserialize, Deserializer};

static TOML_REGEX: OnceLock<Regex> = OnceLock::new();

fn toml_regex() -> &'static Regex {
    &TOML_REGEX.get_or_init(|| {
        let pattern = r"^[[:space:]]*\+\+\+(\r?\n(?s).*?(?-s))\+\+\+[[:space:]]*(?:$|(?:\r?\n((?s).*(?-s))$))";
        Regex::new(pattern).expect("failed to compile regex for TOML front matter")
    })
}

#[derive(Debug)]
pub struct RawTomlFrontMatter<'a>(&'a str);

impl RawTomlFrontMatter<'_> {
    fn deserialize<T>(&self) -> Result<T, toml::de::Error>
    where
        T: serde::de::DeserializeOwned,
    {
        toml::from_str(self.0)
    }
}

pub fn parse_front_matter<'a, T>(content: &'a str) -> Option<(T, &'a str)>
where
    T: serde::de::DeserializeOwned,
{
    if let Some(captures) = toml_regex().captures(content) {
        let front_matter = RawTomlFrontMatter(captures.get(1).unwrap().as_str());
        let content = captures.get(2).map_or("", |m| m.as_str());

        let front_matter: T = front_matter.deserialize().unwrap();

        Some((front_matter, content))
    } else {
        None
    }
}

pub fn from_toml_datetime<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
where
    D: Deserializer<'de>,
{
    use serde::de::Error;
    use std::str::FromStr;

    #[derive(Deserialize)]
    #[serde(untagged)]
    enum DatetimeOrString {
        Datetime(toml::value::Datetime),
        String(String),
    }

    match DatetimeOrString::deserialize(deserializer)? {
        DatetimeOrString::Datetime(datetime) => Ok(Some(datetime.to_string())),
        DatetimeOrString::String(string) => match toml::value::Datetime::from_str(&string) {
            Ok(datetime) => Ok(Some(datetime.to_string())),
            Err(err) => Err(D::Error::custom(err)),
        },
    }
}