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.

220 lines
6.0KB

  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. /// Whether this page is a draft and should be published or not
  25. pub draft: Option<bool>,
  26. /// Only one category allowed. Can't be an empty string if present
  27. pub category: Option<String>,
  28. /// Integer to use to order content. Lowest is at the bottom, highest first
  29. pub order: Option<usize>,
  30. /// Specify a template different from `page.html` to use for that page
  31. #[serde(skip_serializing)]
  32. pub template: Option<String>,
  33. /// Any extra parameter present in the front matter
  34. pub extra: Option<HashMap<String, Value>>,
  35. }
  36. impl PageFrontMatter {
  37. pub fn parse(toml: &str) -> Result<PageFrontMatter> {
  38. let f: PageFrontMatter = 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. if let Some(ref category) = f.category {
  53. if category == "" {
  54. bail!("`category` can't be empty if present")
  55. }
  56. }
  57. Ok(f)
  58. }
  59. /// Converts the date in the front matter, which can be in 2 formats, into a NaiveDateTime
  60. pub fn date(&self) -> Option<NaiveDateTime> {
  61. match self.date {
  62. Some(ref d) => {
  63. if d.contains('T') {
  64. DateTime::parse_from_rfc3339(d).ok().and_then(|s| Some(s.naive_local()))
  65. } else {
  66. NaiveDate::parse_from_str(d, "%Y-%m-%d").ok().and_then(|s| Some(s.and_hms(0,0,0)))
  67. }
  68. },
  69. None => None,
  70. }
  71. }
  72. pub fn order(&self) -> usize {
  73. self.order.unwrap()
  74. }
  75. pub fn has_tags(&self) -> bool {
  76. match self.tags {
  77. Some(ref t) => !t.is_empty(),
  78. None => false
  79. }
  80. }
  81. }
  82. impl Default for PageFrontMatter {
  83. fn default() -> PageFrontMatter {
  84. PageFrontMatter {
  85. title: None,
  86. description: None,
  87. date: None,
  88. slug: None,
  89. url: None,
  90. tags: None,
  91. draft: None,
  92. category: None,
  93. order: None,
  94. template: None,
  95. extra: None,
  96. }
  97. }
  98. }
  99. #[cfg(test)]
  100. mod tests {
  101. use super::PageFrontMatter;
  102. #[test]
  103. fn can_have_empty_front_matter() {
  104. let content = r#" "#;
  105. let res = PageFrontMatter::parse(content);
  106. assert!(res.is_ok());
  107. }
  108. #[test]
  109. fn can_parse_valid_front_matter() {
  110. let content = r#"
  111. title = "Hello"
  112. description = "hey there""#;
  113. let res = PageFrontMatter::parse(content);
  114. assert!(res.is_ok());
  115. let res = res.unwrap();
  116. assert_eq!(res.title.unwrap(), "Hello".to_string());
  117. assert_eq!(res.description.unwrap(), "hey there".to_string())
  118. }
  119. #[test]
  120. fn can_parse_tags() {
  121. let content = r#"
  122. title = "Hello"
  123. description = "hey there"
  124. slug = "hello-world"
  125. tags = ["rust", "html"]"#;
  126. let res = PageFrontMatter::parse(content);
  127. assert!(res.is_ok());
  128. let res = res.unwrap();
  129. assert_eq!(res.title.unwrap(), "Hello".to_string());
  130. assert_eq!(res.slug.unwrap(), "hello-world".to_string());
  131. assert_eq!(res.tags.unwrap(), ["rust".to_string(), "html".to_string()]);
  132. }
  133. #[test]
  134. fn errors_with_invalid_front_matter() {
  135. let content = r#"title = 1\n"#;
  136. let res = PageFrontMatter::parse(content);
  137. assert!(res.is_err());
  138. }
  139. #[test]
  140. fn errors_on_non_string_tag() {
  141. let content = r#"
  142. title = "Hello"
  143. description = "hey there"
  144. slug = "hello-world"
  145. tags = ["rust", 1]"#;
  146. let res = PageFrontMatter::parse(content);
  147. assert!(res.is_err());
  148. }
  149. #[test]
  150. fn errors_on_present_but_empty_slug() {
  151. let content = r#"
  152. title = "Hello"
  153. description = "hey there"
  154. slug = """#;
  155. let res = PageFrontMatter::parse(content);
  156. assert!(res.is_err());
  157. }
  158. #[test]
  159. fn errors_on_present_but_empty_url() {
  160. let content = r#"
  161. title = "Hello"
  162. description = "hey there"
  163. url = """#;
  164. let res = PageFrontMatter::parse(content);
  165. assert!(res.is_err());
  166. }
  167. #[test]
  168. fn can_parse_date_yyyy_mm_dd() {
  169. let content = r#"
  170. title = "Hello"
  171. description = "hey there"
  172. date = "2016-10-10""#;
  173. let res = PageFrontMatter::parse(content).unwrap();
  174. assert!(res.date().is_some());
  175. }
  176. #[test]
  177. fn can_parse_date_rfc3339() {
  178. let content = r#"
  179. title = "Hello"
  180. description = "hey there"
  181. date = "2002-10-02T15:00:00Z""#;
  182. let res = PageFrontMatter::parse(content).unwrap();
  183. assert!(res.date().is_some());
  184. }
  185. #[test]
  186. fn cannot_parse_random_date_format() {
  187. let content = r#"
  188. title = "Hello"
  189. description = "hey there"
  190. date = "2002/10/12""#;
  191. let res = PageFrontMatter::parse(content).unwrap();
  192. assert!(res.date().is_none());
  193. }
  194. }