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.

150 lines
4.1KB

  1. /// A page, can be a blog post or a basic page
  2. use std::collections::HashMap;
  3. use std::default::Default;
  4. use std::fs::File;
  5. use std::io::prelude::*;
  6. // use pulldown_cmark as cmark;
  7. use regex::Regex;
  8. use tera::{Tera, Value, Context};
  9. use errors::{Result, ResultExt};
  10. use config::Config;
  11. use front_matter::parse_front_matter;
  12. lazy_static! {
  13. static ref DELIM_RE: Regex = Regex::new(r"\+\+\+\s*\r?\n").unwrap();
  14. }
  15. #[derive(Debug, PartialEq, Serialize, Deserialize)]
  16. pub struct Page {
  17. // .md filepath, excluding the content/ bit
  18. pub filepath: String,
  19. // <title> of the page
  20. pub title: String,
  21. // The page slug
  22. pub slug: String,
  23. // the actual content of the page
  24. pub content: String,
  25. // tags, not to be confused with categories
  26. pub tags: Vec<String>,
  27. // whether this page should be public or not
  28. pub is_draft: bool,
  29. // any extra parameter present in the front matter
  30. // it will be passed to the template context
  31. pub extra: HashMap<String, Value>,
  32. // the url the page appears at, overrides the slug if set
  33. pub url: Option<String>,
  34. // only one category allowed
  35. pub category: Option<String>,
  36. // optional date if we want to order pages (ie blog post)
  37. pub date: Option<String>,
  38. // optional layout, if we want to specify which html to render for that page
  39. pub layout: Option<String>,
  40. // description that appears when linked, e.g. on twitter
  41. pub description: Option<String>,
  42. }
  43. impl Default for Page {
  44. fn default() -> Page {
  45. Page {
  46. filepath: "".to_string(),
  47. title: "".to_string(),
  48. slug: "".to_string(),
  49. content: "".to_string(),
  50. tags: vec![],
  51. is_draft: false,
  52. extra: HashMap::new(),
  53. url: None,
  54. category: None,
  55. date: None,
  56. layout: None,
  57. description: None,
  58. }
  59. }
  60. }
  61. impl Page {
  62. // Parse a page given the content of the .md file
  63. // Files without front matter or with invalid front matter are considered
  64. // erroneous
  65. pub fn from_str(filepath: &str, content: &str) -> Result<Page> {
  66. // 1. separate front matter from content
  67. if !DELIM_RE.is_match(content) {
  68. bail!("Couldn't find front matter in `{}`. Did you forget to add `+++`?", filepath);
  69. }
  70. // 2. extract the front matter and the content
  71. let splits: Vec<&str> = DELIM_RE.splitn(content, 2).collect();
  72. let front_matter = splits[0];
  73. let content = splits[1];
  74. // 2. create our page, parse front matter and assign all of that
  75. let mut page = Page::default();
  76. page.filepath = filepath.to_string();
  77. page.content = content.to_string();
  78. parse_front_matter(front_matter, &mut page)
  79. .chain_err(|| format!("Error when parsing front matter of file `{}`", filepath))?;
  80. Ok(page)
  81. }
  82. pub fn from_file(path: &str) -> Result<Page> {
  83. let mut content = String::new();
  84. File::open(path)
  85. .chain_err(|| format!("Failed to open '{:?}'", path))?
  86. .read_to_string(&mut content)?;
  87. Page::from_str(path, &content)
  88. }
  89. fn get_layout_name(&self) -> String {
  90. // TODO: handle themes
  91. match self.layout {
  92. Some(ref l) => l.to_string(),
  93. None => "_default/single.html".to_string()
  94. }
  95. }
  96. pub fn render_html(&self, tera: &Tera, config: &Config) -> Result<String> {
  97. let tpl = self.get_layout_name();
  98. let mut context = Context::new();
  99. context.add("site", config);
  100. context.add("page", self);
  101. // println!("{:?}", tera);
  102. tera.render(&tpl, context).chain_err(|| "")
  103. }
  104. }
  105. #[cfg(test)]
  106. mod tests {
  107. use super::{Page};
  108. #[test]
  109. fn test_can_parse_a_valid_page() {
  110. let content = r#"
  111. title = "Hello"
  112. slug = "hello-world"
  113. +++
  114. Hello world"#;
  115. let res = Page::from_str("", content);
  116. assert!(res.is_ok());
  117. let page = res.unwrap();
  118. assert_eq!(page.title, "Hello".to_string());
  119. assert_eq!(page.slug, "hello-world".to_string());
  120. assert_eq!(page.content, "Hello world".to_string());
  121. }
  122. }