You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

186 lines
5.9KB

  1. use std::collections::BTreeMap;
  2. use toml::{Parser, Value as TomlValue};
  3. use tera::{Value, to_value};
  4. use errors::{Result};
  5. use page::Page;
  6. // Converts from one value (Toml) to another (Tera)
  7. // Used to fill the Page::extra map
  8. fn toml_to_tera(val: &TomlValue) -> Value {
  9. match *val {
  10. TomlValue::String(ref s) | TomlValue::Datetime(ref s) => to_value(s),
  11. TomlValue::Boolean(ref b) => to_value(b),
  12. TomlValue::Integer(ref n) => to_value(n),
  13. TomlValue::Float(ref n) => to_value(n),
  14. TomlValue::Array(ref arr) => to_value(&arr.into_iter().map(toml_to_tera).collect::<Vec<_>>()),
  15. TomlValue::Table(ref table) => {
  16. to_value(&table.into_iter().map(|(k, v)| {
  17. (k, toml_to_tera(v))
  18. }).collect::<BTreeMap<_, _>>())
  19. }
  20. }
  21. }
  22. pub fn parse_front_matter(front_matter: &str, page: &mut Page) -> Result<()> {
  23. if front_matter.trim() == "" {
  24. bail!("Front matter of file is missing");
  25. }
  26. let mut parser = Parser::new(&front_matter);
  27. if let Some(value) = parser.parse() {
  28. for (key, value) in value.iter() {
  29. match key.as_str() {
  30. "title" | "slug" | "url" | "category" | "layout" | "description" => match *value {
  31. TomlValue::String(ref s) => {
  32. if key == "title" {
  33. page.title = s.to_string();
  34. } else if key == "slug" {
  35. page.slug = s.to_string();
  36. } else if key == "url" {
  37. page.url = Some(s.to_string());
  38. } else if key == "category" {
  39. page.category = Some(s.to_string());
  40. } else if key == "layout" {
  41. page.layout = Some(s.to_string());
  42. } else if key == "description" {
  43. page.description = Some(s.to_string());
  44. }
  45. }
  46. _ => bail!("Field {} should be a string", key)
  47. },
  48. "draft" => match *value {
  49. TomlValue::Boolean(b) => page.is_draft = b,
  50. _ => bail!("Field {} should be a boolean", key)
  51. },
  52. "date" => match *value {
  53. TomlValue::Datetime(ref d) => page.date = Some(d.to_string()),
  54. _ => bail!("Field {} should be a date", key)
  55. },
  56. "tags" => match *value {
  57. TomlValue::Array(ref a) => {
  58. for elem in a {
  59. if key == "tags" {
  60. match *elem {
  61. TomlValue::String(ref s) => page.tags.push(s.to_string()),
  62. _ => bail!("Tag `{}` should be a string")
  63. }
  64. }
  65. }
  66. },
  67. _ => bail!("Field {} should be an array", key)
  68. },
  69. // extra fields
  70. _ => {
  71. page.extra.insert(key.to_string(), toml_to_tera(value));
  72. }
  73. }
  74. }
  75. } else {
  76. bail!("Errors parsing front matter: {:?}", parser.errors);
  77. }
  78. if page.title == "" || page.slug == "" {
  79. bail!("Front matter is missing required fields (title, slug or both)");
  80. }
  81. Ok(())
  82. }
  83. #[cfg(test)]
  84. mod tests {
  85. use super::{parse_front_matter};
  86. use tera::to_value;
  87. use page::Page;
  88. #[test]
  89. fn test_can_parse_a_valid_front_matter() {
  90. let content = r#"
  91. title = "Hello"
  92. slug = "hello-world""#;
  93. let mut page = Page::default();
  94. let res = parse_front_matter(content, &mut page);
  95. assert!(res.is_ok());
  96. assert_eq!(page.title, "Hello".to_string());
  97. assert_eq!(page.slug, "hello-world".to_string());
  98. }
  99. #[test]
  100. fn test_can_parse_tags() {
  101. let content = r#"
  102. title = "Hello"
  103. slug = "hello-world"
  104. tags = ["rust", "html"]"#;
  105. let mut page = Page::default();
  106. let res = parse_front_matter(content, &mut page);
  107. assert!(res.is_ok());
  108. assert_eq!(page.title, "Hello".to_string());
  109. assert_eq!(page.slug, "hello-world".to_string());
  110. assert_eq!(page.tags, ["rust".to_string(), "html".to_string()]);
  111. }
  112. #[test]
  113. fn test_can_parse_extra_attributes_in_frontmatter() {
  114. let content = r#"
  115. title = "Hello"
  116. slug = "hello-world"
  117. language = "en"
  118. authors = ["Bob", "Alice"]"#;
  119. let mut page = Page::default();
  120. let res = parse_front_matter(content, &mut page);
  121. assert!(res.is_ok());
  122. assert_eq!(page.title, "Hello".to_string());
  123. assert_eq!(page.slug, "hello-world".to_string());
  124. assert_eq!(page.extra.get("language").unwrap(), &to_value("en"));
  125. assert_eq!(
  126. page.extra.get("authors").unwrap(),
  127. &to_value(["Bob".to_string(), "Alice".to_string()])
  128. );
  129. }
  130. #[test]
  131. fn test_ignores_pages_with_empty_front_matter() {
  132. let content = r#" "#;
  133. let mut page = Page::default();
  134. let res = parse_front_matter(content, &mut page);
  135. assert!(res.is_err());
  136. }
  137. #[test]
  138. fn test_errors_with_invalid_front_matter() {
  139. let content = r#"title = 1\n"#;
  140. let mut page = Page::default();
  141. let res = parse_front_matter(content, &mut page);
  142. assert!(res.is_err());
  143. }
  144. #[test]
  145. fn test_errors_with_missing_required_value_front_matter() {
  146. let content = r#"title = """#;
  147. let mut page = Page::default();
  148. let res = parse_front_matter(content, &mut page);
  149. assert!(res.is_err());
  150. }
  151. #[test]
  152. fn test_errors_on_non_string_tag() {
  153. let content = r#"
  154. title = "Hello"
  155. slug = "hello-world"
  156. tags = ["rust", 1]"#;
  157. let mut page = Page::default();
  158. let res = parse_front_matter(content, &mut page);
  159. assert!(res.is_err());
  160. }
  161. }