use enum_iterator_derive::IntoEnumIterator;
use getset::{Getters, Setters};
use crate::{error::Error, Adapter};
pub type MarkdownResult = Result<Markdown, Error>;
#[derive(Debug, Getters, Setters, Clone)]
pub struct Markdown {
#[getset(get = "pub", set = "pub")]
content: String,
#[getset(get = "pub", set = "pub")]
front_matter: String,
#[getset(get = "pub", set = "pub")]
format: Format,
}
impl Markdown {
#[inline]
pub fn new(content: String, front_matter: String, format: Format) -> Self {
Markdown {
format,
content,
front_matter,
}
}
pub fn write_file<P>(&self, path: P) -> Result<(), crate::error::Error>
where
P: AsRef<std::path::Path>,
{
crate::fs::write_file(self, path)
}
#[inline]
pub fn bytes(&self) -> Vec<u8> {
let mut v = vec![];
let sp = self.format().separator();
v.extend_from_slice(sp.as_bytes());
v.extend_from_slice(self.front_matter().as_bytes());
v.extend_from_slice(b"\n");
v.extend_from_slice(sp.as_bytes());
v.extend_from_slice(b"\n");
v.extend_from_slice(self.content().as_bytes());
v
}
#[inline]
pub fn display(&self) -> String {
unsafe { String::from_utf8_unchecked(self.bytes()) }
}
#[cfg(feature = "adapt")]
pub fn adapt<A, T>(self) -> MarkdownResult
where
A: Adapter + Default,
T: serde::ser::Serialize + serde::de::DeserializeOwned,
{
A::default().adapt::<T>(self)
}
}
#[derive(Debug, Clone, IntoEnumIterator, PartialEq)]
pub enum Format {
JSON,
YAML,
TOML,
}
impl Format {
#[inline]
fn separator(&self) -> &str {
match self {
Format::YAML => "---\n",
Format::TOML => "+++\n",
_ => "",
}
}
#[inline]
fn regex_patten(&self) -> &str {
match self {
Format::YAML => r"^[[:space:]]*\-\-\-\r?\n((?s).*?(?-s))\-\-\-\r?\n((?s).*(?-s))$",
Format::TOML => r"^[[:space:]]*\+\+\+\r?\n((?s).*?(?-s))\+\+\+\r?\n((?s).*(?-s))$",
Format::JSON => r"^[[:space:]]*\{\r?\n((?s).*?(?-s))\}\r?\n((?s).*(?-s))$",
}
}
}
pub fn parse(input: &str) -> MarkdownResult {
use enum_iterator::IntoEnumIterator;
for format in Format::into_enum_iter() {
let md = parse_format(input, format);
if md.is_ok() {
return md;
}
}
Err(crate::ParseError::MissingAllFormat.into())
}
pub fn parse_format(input: &str, format: Format) -> MarkdownResult {
let cap = regex::Regex::new(format.regex_patten())?
.captures(input)
.ok_or(crate::ParseError::BadFormat(format.clone()))?;
let front_matter = if Format::JSON.eq(&format) {
format!("{{\n{0}\n}}", cap[1].trim())
} else {
cap[1].trim().to_string()
};
Ok(Markdown {
format,
front_matter,
content: cap[2].trim().to_string(),
})
}