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.

221 lines
6.1KB

  1. use std::collections::HashMap;
  2. use chrono::prelude::*;
  3. use tera::Value;
  4. use toml;
  5. use errors::{Result};
  6. /// The front matter of every page
  7. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
  8. pub struct PageFrontMatter {
  9. /// <title> of the page
  10. pub title: Option<String>,
  11. /// Description in <meta> that appears when linked, e.g. on twitter
  12. pub description: Option<String>,
  13. /// Date if we want to order pages (ie blog post)
  14. pub date: Option<String>,
  15. /// The page slug. Will be used instead of the filename if present
  16. /// Can't be an empty string if present
  17. pub slug: Option<String>,
  18. /// The url the page appears at, overrides the slug if set in the front-matter
  19. /// otherwise is set after parsing front matter and sections
  20. /// Can't be an empty string if present
  21. pub url: Option<String>,
  22. /// Tags, not to be confused with categories
  23. pub tags: Option<Vec<String>>,
  24. /// Only one category allowed. Can't be an empty string if present
  25. pub category: Option<String>,
  26. /// Integer to use to order content. Lowest is at the bottom, highest first
  27. pub order: Option<usize>,
  28. /// All aliases for that page. Gutenberg will create HTML templates that will
  29. #[serde(skip_serializing)]
  30. pub aliases: Option<Vec<String>>,
  31. /// Specify a template different from `page.html` to use for that page
  32. #[serde(skip_serializing)]
  33. pub template: Option<String>,
  34. /// Any extra parameter present in the front matter
  35. pub extra: Option<HashMap<String, Value>>,
  36. }
  37. impl PageFrontMatter {
  38. pub fn parse(toml: &str) -> Result<PageFrontMatter> {
  39. let f: PageFrontMatter = match toml::from_str(toml) {
  40. Ok(d) => d,
  41. Err(e) => bail!(e),
  42. };
  43. if let Some(ref slug) = f.slug {
  44. if slug == "" {
  45. bail!("`slug` can't be empty if present")
  46. }
  47. }
  48. if let Some(ref url) = f.url {
  49. if url == "" {
  50. bail!("`url` can't be empty if present")
  51. }
  52. }
  53. if let Some(ref category) = f.category {
  54. if category == "" {
  55. bail!("`category` can't be empty if present")
  56. }
  57. }
  58. Ok(f)
  59. }
  60. /// Converts the date in the front matter, which can be in 2 formats, into a NaiveDateTime
  61. pub fn date(&self) -> Option<NaiveDateTime> {
  62. match self.date {
  63. Some(ref d) => {
  64. if d.contains('T') {
  65. DateTime::parse_from_rfc3339(d).ok().and_then(|s| Some(s.naive_local()))
  66. } else {
  67. NaiveDate::parse_from_str(d, "%Y-%m-%d").ok().and_then(|s| Some(s.and_hms(0,0,0)))
  68. }
  69. },
  70. None => None,
  71. }
  72. }
  73. pub fn order(&self) -> usize {
  74. self.order.unwrap()
  75. }
  76. pub fn has_tags(&self) -> bool {
  77. match self.tags {
  78. Some(ref t) => !t.is_empty(),
  79. None => false
  80. }
  81. }
  82. }
  83. impl Default for PageFrontMatter {
  84. fn default() -> PageFrontMatter {
  85. PageFrontMatter {
  86. title: None,
  87. description: None,
  88. date: None,
  89. slug: None,
  90. url: None,
  91. tags: None,
  92. category: None,
  93. order: None,
  94. aliases: None,
  95. template: None,
  96. extra: None,
  97. }
  98. }
  99. }
  100. #[cfg(test)]
  101. mod tests {
  102. use super::PageFrontMatter;
  103. #[test]
  104. fn can_have_empty_front_matter() {
  105. let content = r#" "#;
  106. let res = PageFrontMatter::parse(content);
  107. assert!(res.is_ok());
  108. }
  109. #[test]
  110. fn can_parse_valid_front_matter() {
  111. let content = r#"
  112. title = "Hello"
  113. description = "hey there""#;
  114. let res = PageFrontMatter::parse(content);
  115. assert!(res.is_ok());
  116. let res = res.unwrap();
  117. assert_eq!(res.title.unwrap(), "Hello".to_string());
  118. assert_eq!(res.description.unwrap(), "hey there".to_string())
  119. }
  120. #[test]
  121. fn can_parse_tags() {
  122. let content = r#"
  123. title = "Hello"
  124. description = "hey there"
  125. slug = "hello-world"
  126. tags = ["rust", "html"]"#;
  127. let res = PageFrontMatter::parse(content);
  128. assert!(res.is_ok());
  129. let res = res.unwrap();
  130. assert_eq!(res.title.unwrap(), "Hello".to_string());
  131. assert_eq!(res.slug.unwrap(), "hello-world".to_string());
  132. assert_eq!(res.tags.unwrap(), ["rust".to_string(), "html".to_string()]);
  133. }
  134. #[test]
  135. fn errors_with_invalid_front_matter() {
  136. let content = r#"title = 1\n"#;
  137. let res = PageFrontMatter::parse(content);
  138. assert!(res.is_err());
  139. }
  140. #[test]
  141. fn errors_on_non_string_tag() {
  142. let content = r#"
  143. title = "Hello"
  144. description = "hey there"
  145. slug = "hello-world"
  146. tags = ["rust", 1]"#;
  147. let res = PageFrontMatter::parse(content);
  148. assert!(res.is_err());
  149. }
  150. #[test]
  151. fn errors_on_present_but_empty_slug() {
  152. let content = r#"
  153. title = "Hello"
  154. description = "hey there"
  155. slug = """#;
  156. let res = PageFrontMatter::parse(content);
  157. assert!(res.is_err());
  158. }
  159. #[test]
  160. fn errors_on_present_but_empty_url() {
  161. let content = r#"
  162. title = "Hello"
  163. description = "hey there"
  164. url = """#;
  165. let res = PageFrontMatter::parse(content);
  166. assert!(res.is_err());
  167. }
  168. #[test]
  169. fn can_parse_date_yyyy_mm_dd() {
  170. let content = r#"
  171. title = "Hello"
  172. description = "hey there"
  173. date = "2016-10-10""#;
  174. let res = PageFrontMatter::parse(content).unwrap();
  175. assert!(res.date().is_some());
  176. }
  177. #[test]
  178. fn can_parse_date_rfc3339() {
  179. let content = r#"
  180. title = "Hello"
  181. description = "hey there"
  182. date = "2002-10-02T15:00:00Z""#;
  183. let res = PageFrontMatter::parse(content).unwrap();
  184. assert!(res.date().is_some());
  185. }
  186. #[test]
  187. fn cannot_parse_random_date_format() {
  188. let content = r#"
  189. title = "Hello"
  190. description = "hey there"
  191. date = "2002/10/12""#;
  192. let res = PageFrontMatter::parse(content).unwrap();
  193. assert!(res.date().is_none());
  194. }
  195. }