use prov::{Mapping, Value};
const DOC_PATH: &str = "frontmatter.md";
#[derive(Debug, Clone, Default)]
pub struct ParsedFile {
pub frontmatter: Mapping,
pub body: String,
}
pub fn parse_or_empty(content: &str) -> Result<ParsedFile, prov::Error> {
let doc = prov::Document::parse(DOC_PATH, content)?;
Ok(ParsedFile {
frontmatter: doc.meta.as_mapping().cloned().unwrap_or_default(),
body: doc.body,
})
}
pub fn get_string<'a>(frontmatter: &'a Mapping, key: &str) -> Option<&'a str> {
frontmatter.get(key).and_then(|v| v.as_str())
}
pub fn get_string_array(frontmatter: &Mapping, key: &str) -> Vec<String> {
match frontmatter.get(key) {
Some(Value::Sequence(seq)) => seq
.iter()
.filter_map(|v| v.as_str().map(String::from))
.collect(),
_ => Vec::new(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn no_metadata_block_is_an_empty_mapping_and_a_whole_body() {
let parsed = parse_or_empty("# Just a heading\n").unwrap();
assert!(parsed.frontmatter.is_empty());
assert_eq!(parsed.body, "# Just a heading\n");
}
#[test]
fn reads_scalars_and_sequences() {
let parsed =
parse_or_empty("---\ntitle: Hi\ncontents:\n - a.md\n - b.md\n---\n\nbody\n").unwrap();
assert_eq!(get_string(&parsed.frontmatter, "title"), Some("Hi"));
assert_eq!(
get_string_array(&parsed.frontmatter, "contents"),
vec!["a.md".to_string(), "b.md".to_string()]
);
assert_eq!(parsed.body.trim(), "body");
}
#[test]
fn a_missing_or_wrongly_typed_key_reads_as_absent() {
let parsed = parse_or_empty("---\ntitle: Hi\ncount: 3\n---\n").unwrap();
assert_eq!(get_string(&parsed.frontmatter, "nope"), None);
assert_eq!(get_string(&parsed.frontmatter, "count"), None);
assert!(get_string_array(&parsed.frontmatter, "title").is_empty());
}
}