use std::collections::HashMap;
use std::path::Path;
use toml;
use tera::Value;
use chrono::prelude::*;
use regex::Regex;
use errors::{Result, ResultExt};
lazy_static! {
static ref PAGE_RE: Regex = Regex::new(r"^\n?\+\+\+\n((?s).*(?-s))\+\+\+\n?((?s).*(?-s))$").unwrap();
}
/// The front matter of every page
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FrontMatter {
// Mandatory fields
///
of the page
pub title: String,
/// Description that appears when linked, e.g. on twitter
pub description: String,
// Optional stuff
/// Date if we want to order pages (ie blog post)
pub date: Option,
/// The page slug. Will be used instead of the filename if present
/// Can't be an empty string if present
pub slug: Option,
/// The url the page appears at, overrides the slug if set in the front-matter
/// otherwise is set after parsing front matter and sections
/// Can't be an empty string if present
pub url: Option,
/// Tags, not to be confused with categories
pub tags: Option>,
/// Whether this page is a draft and should be published or not
pub draft: Option,
/// Only one category allowed
pub category: Option,
/// Optional template, if we want to specify which template to render for that page
#[serde(skip_serializing)]
pub template: Option,
/// Any extra parameter present in the front matter
pub extra: Option>,
}
impl FrontMatter {
pub fn parse(toml: &str) -> Result {
if toml.trim() == "" {
bail!("Front matter of file is missing");
}
let f: FrontMatter = match toml::from_str(toml) {
Ok(d) => d,
Err(e) => bail!(e),
};
if let Some(ref slug) = f.slug {
if slug == "" {
bail!("`slug` can't be empty if present")
}
}
if let Some(ref url) = f.url {
if url == "" {
bail!("`url` can't be empty if present")
}
}
Ok(f)
}
/// Converts the date in the front matter, which can be in 2 formats, into a NaiveDateTime
pub fn parse_date(&self) -> Option {
match self.date {
Some(ref d) => {
if d.contains('T') {
DateTime::parse_from_rfc3339(d).ok().and_then(|s| Some(s.naive_local()))
} else {
NaiveDate::parse_from_str(d, "%Y-%m-%d").ok().and_then(|s| Some(s.and_hms(0,0,0)))
}
},
None => None,
}
}
}
/// Split a file between the front matter and its content
/// It will parse the front matter as well and returns any error encountered
/// TODO: add tests
pub fn split_content(file_path: &Path, content: &str) -> Result<(FrontMatter, String)> {
if !PAGE_RE.is_match(content) {
bail!("Couldn't find front matter in `{}`. Did you forget to add `+++`?", file_path.to_string_lossy());
}
// 2. extract the front matter and the content
let caps = PAGE_RE.captures(content).unwrap();
// caps[0] is the full match
let front_matter = &caps[1];
let content = &caps[2];
// 3. create our page, parse front matter and assign all of that
let meta = FrontMatter::parse(front_matter)
.chain_err(|| format!("Error when parsing front matter of file `{}`", file_path.to_string_lossy()))?;
Ok((meta, content.to_string()))
}