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.

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