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.

196 lines
5.1KB

  1. use std::collections::HashMap;
  2. use toml;
  3. use tera::Value;
  4. use errors::{Result};
  5. /// The front matter of every page
  6. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
  7. pub struct FrontMatter {
  8. // Mandatory fields
  9. /// <title> of the page
  10. pub title: String,
  11. /// Description that appears when linked, e.g. on twitter
  12. pub description: String,
  13. // Optional stuff
  14. /// Date if we want to order pages (ie blog post)
  15. pub date: Option<String>,
  16. /// The page slug. Will be used instead of the filename if present
  17. /// Can't be an empty string if present
  18. pub slug: Option<String>,
  19. /// The url the page appears at, overrides the slug if set in the front-matter
  20. /// otherwise is set after parsing front matter and sections
  21. /// Can't be an empty string if present
  22. pub url: Option<String>,
  23. /// Tags, not to be confused with categories
  24. pub tags: Option<Vec<String>>,
  25. /// Whether this page is a draft and should be published or not
  26. pub draft: Option<bool>,
  27. /// Only one category allowed
  28. pub category: Option<String>,
  29. /// Optional layout, if we want to specify which tpl to render for that page
  30. #[serde(skip_serializing)]
  31. pub layout: Option<String>,
  32. /// Any extra parameter present in the front matter
  33. pub extra: Option<HashMap<String, Value>>,
  34. }
  35. impl FrontMatter {
  36. pub fn parse(toml: &str) -> Result<FrontMatter> {
  37. if toml.trim() == "" {
  38. bail!("Front matter of file is missing");
  39. }
  40. let f: FrontMatter = match toml::from_str(toml) {
  41. Ok(d) => d,
  42. Err(e) => bail!(e),
  43. };
  44. if let Some(ref slug) = f.slug {
  45. if slug == "" {
  46. bail!("`slug` can't be empty if present")
  47. }
  48. }
  49. if let Some(ref url) = f.url {
  50. if url == "" {
  51. bail!("`url` can't be empty if present")
  52. }
  53. }
  54. Ok(f)
  55. }
  56. }
  57. #[cfg(test)]
  58. mod tests {
  59. use super::{FrontMatter};
  60. use tera::to_value;
  61. #[test]
  62. fn test_can_parse_a_valid_front_matter() {
  63. let content = r#"
  64. title = "Hello"
  65. description = "hey there""#;
  66. let res = FrontMatter::parse(content);
  67. println!("{:?}", res);
  68. assert!(res.is_ok());
  69. let res = res.unwrap();
  70. assert_eq!(res.title, "Hello".to_string());
  71. assert_eq!(res.description, "hey there".to_string());
  72. }
  73. #[test]
  74. fn test_can_parse_tags() {
  75. let content = r#"
  76. title = "Hello"
  77. description = "hey there"
  78. slug = "hello-world"
  79. tags = ["rust", "html"]"#;
  80. let res = FrontMatter::parse(content);
  81. assert!(res.is_ok());
  82. let res = res.unwrap();
  83. assert_eq!(res.title, "Hello".to_string());
  84. assert_eq!(res.slug.unwrap(), "hello-world".to_string());
  85. assert_eq!(res.tags.unwrap(), ["rust".to_string(), "html".to_string()]);
  86. }
  87. #[test]
  88. fn test_can_parse_extra_attributes_in_frontmatter() {
  89. let content = r#"
  90. title = "Hello"
  91. description = "hey there"
  92. slug = "hello-world"
  93. [extra]
  94. language = "en"
  95. authors = ["Bob", "Alice"]"#;
  96. let res = FrontMatter::parse(content);
  97. assert!(res.is_ok());
  98. let res = res.unwrap();
  99. assert_eq!(res.title, "Hello".to_string());
  100. assert_eq!(res.slug.unwrap(), "hello-world".to_string());
  101. let extra = res.extra.unwrap();
  102. assert_eq!(extra.get("language").unwrap(), &to_value("en").unwrap());
  103. assert_eq!(
  104. extra.get("authors").unwrap(),
  105. &to_value(["Bob".to_string(), "Alice".to_string()]).unwrap()
  106. );
  107. }
  108. #[test]
  109. fn test_is_ok_with_url_instead_of_slug() {
  110. let content = r#"
  111. title = "Hello"
  112. description = "hey there"
  113. url = "hello-world""#;
  114. let res = FrontMatter::parse(content);
  115. assert!(res.is_ok());
  116. let res = res.unwrap();
  117. assert!(res.slug.is_none());
  118. assert_eq!(res.url.unwrap(), "hello-world".to_string());
  119. }
  120. #[test]
  121. fn test_errors_with_empty_front_matter() {
  122. let content = r#" "#;
  123. let res = FrontMatter::parse(content);
  124. assert!(res.is_err());
  125. }
  126. #[test]
  127. fn test_errors_with_invalid_front_matter() {
  128. let content = r#"title = 1\n"#;
  129. let res = FrontMatter::parse(content);
  130. assert!(res.is_err());
  131. }
  132. #[test]
  133. fn test_errors_with_missing_required_value_front_matter() {
  134. let content = r#"title = """#;
  135. let res = FrontMatter::parse(content);
  136. assert!(res.is_err());
  137. }
  138. #[test]
  139. fn test_errors_on_non_string_tag() {
  140. let content = r#"
  141. title = "Hello"
  142. description = "hey there"
  143. slug = "hello-world"
  144. tags = ["rust", 1]"#;
  145. let res = FrontMatter::parse(content);
  146. assert!(res.is_err());
  147. }
  148. #[test]
  149. fn test_errors_on_present_but_empty_slug() {
  150. let content = r#"
  151. title = "Hello"
  152. description = "hey there"
  153. slug = """#;
  154. let res = FrontMatter::parse(content);
  155. assert!(res.is_err());
  156. }
  157. #[test]
  158. fn test_errors_on_present_but_empty_url() {
  159. let content = r#"
  160. title = "Hello"
  161. description = "hey there"
  162. url = """#;
  163. let res = FrontMatter::parse(content);
  164. assert!(res.is_err());
  165. }
  166. }