use std::collections::BTreeMap; use toml::{Parser, Value as TomlValue}; use tera::{Value, to_value}; use errors::{Result}; use page::Page; // Converts from one value (Toml) to another (Tera) // Used to fill the Page::extra map fn toml_to_tera(val: &TomlValue) -> Value { match *val { TomlValue::String(ref s) | TomlValue::Datetime(ref s) => to_value(s), TomlValue::Boolean(ref b) => to_value(b), TomlValue::Integer(ref n) => to_value(n), TomlValue::Float(ref n) => to_value(n), TomlValue::Array(ref arr) => to_value(&arr.into_iter().map(toml_to_tera).collect::>()), TomlValue::Table(ref table) => { to_value(&table.into_iter().map(|(k, v)| { (k, toml_to_tera(v)) }).collect::>()) } } } pub fn parse_front_matter(front_matter: &str, page: &mut Page) -> Result<()> { if front_matter.trim() == "" { bail!("Front matter of file is missing"); } let mut parser = Parser::new(&front_matter); if let Some(value) = parser.parse() { for (key, value) in value.iter() { match key.as_str() { "title" | "slug" | "url" | "category" | "layout" | "description" => match *value { TomlValue::String(ref s) => { if key == "title" { page.title = s.to_string(); } else if key == "slug" { page.slug = s.to_string(); } else if key == "url" { page.url = Some(s.to_string()); } else if key == "category" { page.category = Some(s.to_string()); } else if key == "layout" { page.layout = Some(s.to_string()); } else if key == "description" { page.description = Some(s.to_string()); } } _ => bail!("Field {} should be a string", key) }, "draft" => match *value { TomlValue::Boolean(b) => page.is_draft = b, _ => bail!("Field {} should be a boolean", key) }, "date" => match *value { TomlValue::Datetime(ref d) => page.date = Some(d.to_string()), _ => bail!("Field {} should be a date", key) }, "tags" => match *value { TomlValue::Array(ref a) => { for elem in a { if key == "tags" { match *elem { TomlValue::String(ref s) => page.tags.push(s.to_string()), _ => bail!("Tag `{}` should be a string") } } } }, _ => bail!("Field {} should be an array", key) }, // extra fields _ => { page.extra.insert(key.to_string(), toml_to_tera(value)); } } } } else { bail!("Errors parsing front matter: {:?}", parser.errors); } if page.title == "" || page.slug == "" { bail!("Front matter is missing required fields (title, slug or both)"); } Ok(()) } #[cfg(test)] mod tests { use super::{parse_front_matter}; use tera::to_value; use page::Page; #[test] fn test_can_parse_a_valid_front_matter() { let content = r#" title = "Hello" slug = "hello-world""#; let mut page = Page::default(); let res = parse_front_matter(content, &mut page); assert!(res.is_ok()); assert_eq!(page.title, "Hello".to_string()); assert_eq!(page.slug, "hello-world".to_string()); } #[test] fn test_can_parse_tags() { let content = r#" title = "Hello" slug = "hello-world" tags = ["rust", "html"]"#; let mut page = Page::default(); let res = parse_front_matter(content, &mut page); assert!(res.is_ok()); assert_eq!(page.title, "Hello".to_string()); assert_eq!(page.slug, "hello-world".to_string()); assert_eq!(page.tags, ["rust".to_string(), "html".to_string()]); } #[test] fn test_can_parse_extra_attributes_in_frontmatter() { let content = r#" title = "Hello" slug = "hello-world" language = "en" authors = ["Bob", "Alice"]"#; let mut page = Page::default(); let res = parse_front_matter(content, &mut page); assert!(res.is_ok()); assert_eq!(page.title, "Hello".to_string()); assert_eq!(page.slug, "hello-world".to_string()); assert_eq!(page.extra.get("language").unwrap(), &to_value("en")); assert_eq!( page.extra.get("authors").unwrap(), &to_value(["Bob".to_string(), "Alice".to_string()]) ); } #[test] fn test_ignores_pages_with_empty_front_matter() { let content = r#" "#; let mut page = Page::default(); let res = parse_front_matter(content, &mut page); assert!(res.is_err()); } #[test] fn test_errors_with_invalid_front_matter() { let content = r#"title = 1\n"#; let mut page = Page::default(); let res = parse_front_matter(content, &mut page); assert!(res.is_err()); } #[test] fn test_errors_with_missing_required_value_front_matter() { let content = r#"title = """#; let mut page = Page::default(); let res = parse_front_matter(content, &mut page); assert!(res.is_err()); } #[test] fn test_errors_on_non_string_tag() { let content = r#" title = "Hello" slug = "hello-world" tags = ["rust", 1]"#; let mut page = Page::default(); let res = parse_front_matter(content, &mut page); assert!(res.is_err()); } }