From 299c3c8b2289a2fb7fd9740736cfba983c3e2851 Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Sat, 13 May 2017 13:01:38 +0900 Subject: [PATCH] Separate Page and Section front matter into 2 structs Fix #61 --- src/front_matter.rs | 177 --------------------------- src/front_matter/mod.rs | 122 +++++++++++++++++++ src/front_matter/page.rs | 206 +++++++++++++++++++++++++++++++ src/front_matter/section.rs | 99 +++++++++++++++ src/lib.rs | 2 +- src/page.rs | 40 ++++-- src/pagination.rs | 22 ++-- src/section.rs | 14 +-- tests/front_matter.rs | 236 ------------------------------------ 9 files changed, 477 insertions(+), 441 deletions(-) delete mode 100644 src/front_matter.rs create mode 100644 src/front_matter/mod.rs create mode 100644 src/front_matter/page.rs create mode 100644 src/front_matter/section.rs delete mode 100644 tests/front_matter.rs diff --git a/src/front_matter.rs b/src/front_matter.rs deleted file mode 100644 index 1c3a82d..0000000 --- a/src/front_matter.rs +++ /dev/null @@ -1,177 +0,0 @@ -use std::collections::HashMap; -use std::path::Path; - -use toml; -use tera::Value; -use chrono::prelude::*; -use regex::Regex; - - -use errors::{Result, ResultExt}; - - -lazy_static! { - static ref PAGE_RE: Regex = Regex::new(r"^\r?\n?\+\+\+\r?\n((?s).*?(?-s))\+\+\+\r?\n?((?s).*(?-s))$").unwrap(); -} - -#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "lowercase")] -pub enum SortBy { - Date, - Order, - None, -} - -/// The front matter of every page -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct FrontMatter { - /// of the page - pub title: Option<String>, - /// Description in <meta> that appears when linked, e.g. on twitter - pub description: Option<String>, - /// Date if we want to order pages (ie blog post) - pub date: Option<String>, - /// The page slug. Will be used instead of the filename if present - /// Can't be an empty string if present - pub slug: Option<String>, - /// The url the page appears at, overrides the slug if set in the front-matter - /// otherwise is set after parsing front matter and sections - /// Can't be an empty string if present - pub url: Option<String>, - /// Tags, not to be confused with categories - pub tags: Option<Vec<String>>, - /// Whether this page is a draft and should be published or not - pub draft: Option<bool>, - /// Only one category allowed - pub category: Option<String>, - /// Whether to sort by "date", "order" or "none". Defaults to `none`. - #[serde(skip_serializing)] - pub sort_by: Option<SortBy>, - /// Integer to use to order content. Lowest is at the bottom, highest first - pub order: Option<usize>, - /// Optional template, if we want to specify which template to render for that page - #[serde(skip_serializing)] - pub template: Option<String>, - /// How many pages to be displayed per paginated page. No pagination will happen if this isn't set - #[serde(skip_serializing)] - pub paginate_by: Option<usize>, - /// Path to be used by pagination: the page number will be appended after it. Defaults to `page`. - #[serde(skip_serializing)] - pub paginate_path: Option<String>, - /// Whether to render that page/section or not. Defaults to `true`. - #[serde(skip_serializing)] - pub render: Option<bool>, - /// Any extra parameter present in the front matter - pub extra: Option<HashMap<String, Value>>, -} - -impl FrontMatter { - pub fn parse(toml: &str) -> Result<FrontMatter> { - let mut f: FrontMatter = match toml::from_str(toml) { - Ok(d) => d, - Err(e) => bail!(e), - }; - - if let Some(ref slug) = f.slug { - if slug == "" { - bail!("`slug` can't be empty if present") - } - } - - if let Some(ref url) = f.url { - if url == "" { - bail!("`url` can't be empty if present") - } - } - - if f.paginate_path.is_none() { - f.paginate_path = Some("page".to_string()); - } - - if f.render.is_none() { - f.render = Some(true); - } - - Ok(f) - } - - /// Converts the date in the front matter, which can be in 2 formats, into a NaiveDateTime - pub fn date(&self) -> Option<NaiveDateTime> { - match self.date { - Some(ref d) => { - if d.contains('T') { - DateTime::parse_from_rfc3339(d).ok().and_then(|s| Some(s.naive_local())) - } else { - NaiveDate::parse_from_str(d, "%Y-%m-%d").ok().and_then(|s| Some(s.and_hms(0,0,0))) - } - }, - None => None, - } - } - - pub fn order(&self) -> usize { - self.order.unwrap() - } - - /// Returns the current sorting method, defaults to `None` (== no sorting) - pub fn sort_by(&self) -> SortBy { - match self.sort_by { - Some(ref s) => *s, - None => SortBy::None, - } - } - - /// Only applies to section, whether it is paginated or not. - pub fn is_paginated(&self) -> bool { - match self.paginate_by { - Some(v) => v > 0, - None => false - } - } - - pub fn should_render(&self) -> bool { - self.render.unwrap() - } -} - -impl Default for FrontMatter { - fn default() -> FrontMatter { - FrontMatter { - title: None, - description: None, - date: None, - slug: None, - url: None, - tags: None, - draft: None, - category: None, - sort_by: None, - order: None, - template: None, - paginate_by: None, - paginate_path: Some("page".to_string()), - render: Some(true), - extra: None, - } - } -} - -/// Split a file between the front matter and its content -/// It will parse the front matter as well and returns any error encountered -pub fn split_content(file_path: &Path, content: &str) -> Result<(FrontMatter, String)> { - if !PAGE_RE.is_match(content) { - bail!("Couldn't find front matter in `{}`. Did you forget to add `+++`?", file_path.to_string_lossy()); - } - - // 2. extract the front matter and the content - let caps = PAGE_RE.captures(content).unwrap(); - // caps[0] is the full match - let front_matter = &caps[1]; - let content = &caps[2]; - - // 3. create our page, parse front matter and assign all of that - let meta = FrontMatter::parse(front_matter) - .chain_err(|| format!("Error when parsing front matter of file `{}`", file_path.to_string_lossy()))?; - - Ok((meta, content.to_string())) -} diff --git a/src/front_matter/mod.rs b/src/front_matter/mod.rs new file mode 100644 index 0000000..7e20a93 --- /dev/null +++ b/src/front_matter/mod.rs @@ -0,0 +1,122 @@ +use std::path::Path; + +use regex::Regex; + +use errors::{Result, ResultExt}; + +mod page; +mod section; + +pub use self::page::PageFrontMatter; +pub use self::section::{SectionFrontMatter, SortBy}; + +lazy_static! { + static ref PAGE_RE: Regex = Regex::new(r"^[[:space:]]*\+\+\+\r?\n((?s).*?(?-s))\+\+\+\r?\n?((?s).*(?-s))$").unwrap(); +} + +/// Split a file between the front matter and its content +/// Will return an error if the front matter wasn't found +fn split_content(file_path: &Path, content: &str) -> Result<(String, String)> { + if !PAGE_RE.is_match(content) { + bail!("Couldn't find front matter in `{}`. Did you forget to add `+++`?", file_path.to_string_lossy()); + } + + // 2. extract the front matter and the content + let caps = PAGE_RE.captures(content).unwrap(); + // caps[0] is the full match + // caps[1] => front matter + // caps[2] => content + Ok((caps[1].to_string(), caps[2].to_string())) +} + +/// Split a file between the front matter and its content. +/// Returns a parsed SectionFrontMatter and the rest of the content +pub fn split_section_content(file_path: &Path, content: &str) -> Result<(SectionFrontMatter, String)> { + let (front_matter, content) = split_content(file_path, content)?; + let meta = SectionFrontMatter::parse(&front_matter) + .chain_err(|| format!("Error when parsing front matter of section `{}`", file_path.to_string_lossy()))?; + Ok((meta, content)) +} + +/// Split a file between the front matter and its content +/// Returns a parsed PageFrontMatter and the rest of the content +pub fn split_page_content(file_path: &Path, content: &str) -> Result<(PageFrontMatter, String)> { + let (front_matter, content) = split_content(file_path, content)?; + let meta = PageFrontMatter::parse(&front_matter) + .chain_err(|| format!("Error when parsing front matter of section `{}`", file_path.to_string_lossy()))?; + Ok((meta, content)) +} + +#[cfg(test)] +mod tests { + use std::path::Path; + + use super::{split_section_content, split_page_content}; + + #[test] + fn can_split_page_content_valid() { + let content = r#" ++++ +title = "Title" +description = "hey there" +date = "2002/10/12" ++++ +Hello +"#; + let (front_matter, content) = split_page_content(Path::new(""), content).unwrap(); + assert_eq!(content, "Hello\n"); + assert_eq!(front_matter.title.unwrap(), "Title"); + } + + #[test] + fn can_split_section_content_valid() { + let content = r#" ++++ +paginate_by = 10 ++++ +Hello +"#; + let (front_matter, content) = split_section_content(Path::new(""), content).unwrap(); + assert_eq!(content, "Hello\n"); + assert!(front_matter.is_paginated()); + } + + #[test] + fn can_split_content_with_only_frontmatter_valid() { + let content = r#" ++++ +title = "Title" +description = "hey there" +date = "2002/10/12" ++++"#; + let (front_matter, content) = split_page_content(Path::new(""), content).unwrap(); + assert_eq!(content, ""); + assert_eq!(front_matter.title.unwrap(), "Title"); + } + + #[test] + fn can_split_content_lazily() { + let content = r#" ++++ +title = "Title" +description = "hey there" +date = "2002-10-02T15:00:00Z" ++++ ++++"#; + let (front_matter, content) = split_page_content(Path::new(""), content).unwrap(); + assert_eq!(content, "+++"); + assert_eq!(front_matter.title.unwrap(), "Title"); + } + + #[test] + fn errors_if_cannot_locate_frontmatter() { + let content = r#" ++++ +title = "Title" +description = "hey there" +date = "2002/10/12""#; + let res = split_page_content(Path::new(""), content); + assert!(res.is_err()); + } + +} diff --git a/src/front_matter/page.rs b/src/front_matter/page.rs new file mode 100644 index 0000000..3596b42 --- /dev/null +++ b/src/front_matter/page.rs @@ -0,0 +1,206 @@ +use std::collections::HashMap; + +use chrono::prelude::*; +use tera::Value; +use toml; + +use errors::{Result}; + +/// The front matter of every page +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct PageFrontMatter { + /// <title> of the page + pub title: Option<String>, + /// Description in <meta> that appears when linked, e.g. on twitter + pub description: Option<String>, + /// Date if we want to order pages (ie blog post) + pub date: Option<String>, + /// The page slug. Will be used instead of the filename if present + /// Can't be an empty string if present + pub slug: Option<String>, + /// The url the page appears at, overrides the slug if set in the front-matter + /// otherwise is set after parsing front matter and sections + /// Can't be an empty string if present + pub url: Option<String>, + /// Tags, not to be confused with categories + pub tags: Option<Vec<String>>, + /// Whether this page is a draft and should be published or not + pub draft: Option<bool>, + /// Only one category allowed + pub category: Option<String>, + /// Integer to use to order content. Lowest is at the bottom, highest first + pub order: Option<usize>, + /// Optional template, if we want to specify which template to render for that page + #[serde(skip_serializing)] + pub template: Option<String>, + /// Any extra parameter present in the front matter + pub extra: Option<HashMap<String, Value>>, +} + +impl PageFrontMatter { + pub fn parse(toml: &str) -> Result<PageFrontMatter> { + let f: PageFrontMatter = match toml::from_str(toml) { + Ok(d) => d, + Err(e) => bail!(e), + }; + + if let Some(ref slug) = f.slug { + if slug == "" { + bail!("`slug` can't be empty if present") + } + } + + if let Some(ref url) = f.url { + if url == "" { + bail!("`url` can't be empty if present") + } + } + + Ok(f) + } + + /// Converts the date in the front matter, which can be in 2 formats, into a NaiveDateTime + pub fn date(&self) -> Option<NaiveDateTime> { + match self.date { + Some(ref d) => { + if d.contains('T') { + DateTime::parse_from_rfc3339(d).ok().and_then(|s| Some(s.naive_local())) + } else { + NaiveDate::parse_from_str(d, "%Y-%m-%d").ok().and_then(|s| Some(s.and_hms(0,0,0))) + } + }, + None => None, + } + } + + pub fn order(&self) -> usize { + self.order.unwrap() + } +} + +impl Default for PageFrontMatter { + fn default() -> PageFrontMatter { + PageFrontMatter { + title: None, + description: None, + date: None, + slug: None, + url: None, + tags: None, + draft: None, + category: None, + order: None, + template: None, + extra: None, + } + } +} + +#[cfg(test)] +mod tests { + use super::PageFrontMatter; + + #[test] + fn can_have_empty_front_matter() { + let content = r#" "#; + let res = PageFrontMatter::parse(content); + assert!(res.is_ok()); + } + + #[test] + fn can_parse_valid_front_matter() { + let content = r#" + title = "Hello" + description = "hey there""#; + let res = PageFrontMatter::parse(content); + assert!(res.is_ok()); + let res = res.unwrap(); + assert_eq!(res.title.unwrap(), "Hello".to_string()); + assert_eq!(res.description.unwrap(), "hey there".to_string()) + } + + #[test] + fn can_parse_tags() { + let content = r#" + title = "Hello" + description = "hey there" + slug = "hello-world" + tags = ["rust", "html"]"#; + let res = PageFrontMatter::parse(content); + assert!(res.is_ok()); + let res = res.unwrap(); + + assert_eq!(res.title.unwrap(), "Hello".to_string()); + assert_eq!(res.slug.unwrap(), "hello-world".to_string()); + assert_eq!(res.tags.unwrap(), ["rust".to_string(), "html".to_string()]); + } + + #[test] + fn errors_with_invalid_front_matter() { + let content = r#"title = 1\n"#; + let res = PageFrontMatter::parse(content); + assert!(res.is_err()); + } + + #[test] + fn errors_on_non_string_tag() { + let content = r#" + title = "Hello" + description = "hey there" + slug = "hello-world" + tags = ["rust", 1]"#; + let res = PageFrontMatter::parse(content); + assert!(res.is_err()); + } + + #[test] + fn errors_on_present_but_empty_slug() { + let content = r#" + title = "Hello" + description = "hey there" + slug = """#; + let res = PageFrontMatter::parse(content); + assert!(res.is_err()); + } + + #[test] + fn errors_on_present_but_empty_url() { + let content = r#" + title = "Hello" + description = "hey there" + url = """#; + let res = PageFrontMatter::parse(content); + assert!(res.is_err()); + } + + #[test] + fn can_parse_date_yyyy_mm_dd() { + let content = r#" + title = "Hello" + description = "hey there" + date = "2016-10-10""#; + let res = PageFrontMatter::parse(content).unwrap(); + assert!(res.date().is_some()); + } + + #[test] + fn can_parse_date_rfc3339() { + let content = r#" + title = "Hello" + description = "hey there" + date = "2002-10-02T15:00:00Z""#; + let res = PageFrontMatter::parse(content).unwrap(); + assert!(res.date().is_some()); + } + + #[test] + fn cannot_parse_random_date_format() { + let content = r#" + title = "Hello" + description = "hey there" + date = "2002/10/12""#; + let res = PageFrontMatter::parse(content).unwrap(); + assert!(res.date().is_none()); + } + +} diff --git a/src/front_matter/section.rs b/src/front_matter/section.rs new file mode 100644 index 0000000..1e4fd13 --- /dev/null +++ b/src/front_matter/section.rs @@ -0,0 +1,99 @@ +use std::collections::HashMap; + +use tera::Value; +use toml; + +use errors::{Result}; + +static DEFAULT_PAGINATE_PATH: &'static str = "page"; + +#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum SortBy { + Date, + Order, + None, +} + +/// The front matter of every section +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct SectionFrontMatter { + /// <title> of the page + pub title: Option<String>, + /// Description in <meta> that appears when linked, e.g. on twitter + pub description: Option<String>, + /// Whether to sort by "date", "order" or "none". Defaults to `none`. + #[serde(skip_serializing)] + pub sort_by: Option<SortBy>, + /// Optional template, if we want to specify which template to render for that page + #[serde(skip_serializing)] + pub template: Option<String>, + /// How many pages to be displayed per paginated page. No pagination will happen if this isn't set + #[serde(skip_serializing)] + pub paginate_by: Option<usize>, + /// Path to be used by pagination: the page number will be appended after it. Defaults to `page`. + #[serde(skip_serializing)] + pub paginate_path: Option<String>, + /// Whether to render that section or not. Defaults to `true`. + /// Useful when the section is only there to organize things but is not meant + /// to be used directly, like a posts section in a personal site + #[serde(skip_serializing)] + pub render: Option<bool>, + /// Any extra parameter present in the front matter + pub extra: Option<HashMap<String, Value>>, +} + +impl SectionFrontMatter { + pub fn parse(toml: &str) -> Result<SectionFrontMatter> { + let mut f: SectionFrontMatter = match toml::from_str(toml) { + Ok(d) => d, + Err(e) => bail!(e), + }; + + if f.paginate_path.is_none() { + f.paginate_path = Some(DEFAULT_PAGINATE_PATH.to_string()); + } + + if f.render.is_none() { + f.render = Some(true); + } + + if f.sort_by.is_none() { + f.sort_by = Some(SortBy::None); + } + + Ok(f) + } + + /// Returns the current sorting method, defaults to `None` (== no sorting) + pub fn sort_by(&self) -> SortBy { + self.sort_by.unwrap() + } + + /// Only applies to section, whether it is paginated or not. + pub fn is_paginated(&self) -> bool { + match self.paginate_by { + Some(v) => v > 0, + None => false + } + } + + pub fn should_render(&self) -> bool { + self.render.unwrap() + } +} + +impl Default for SectionFrontMatter { + fn default() -> SectionFrontMatter { + SectionFrontMatter { + title: None, + description: None, + sort_by: None, + template: None, + paginate_by: None, + paginate_path: Some(DEFAULT_PAGINATE_PATH.to_string()), + render: Some(true), + extra: None, + } + } +} diff --git a/src/lib.rs b/src/lib.rs index a84a891..80fc6e1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,7 +33,7 @@ mod templates; pub use site::{Site}; pub use config::{Config, get_config}; -pub use front_matter::{FrontMatter, split_content, SortBy}; +pub use front_matter::{PageFrontMatter, SectionFrontMatter, split_page_content, split_section_content, SortBy}; pub use page::{Page, populate_previous_and_next_pages}; pub use section::{Section}; pub use utils::{create_file}; diff --git a/src/page.rs b/src/page.rs index c63f63c..49384b3 100644 --- a/src/page.rs +++ b/src/page.rs @@ -11,7 +11,7 @@ use slug::slugify; use errors::{Result, ResultExt}; use config::Config; -use front_matter::{FrontMatter, SortBy, split_content}; +use front_matter::{PageFrontMatter, SortBy, split_page_content}; use markdown::markdown_to_html; use utils::{read_file, find_content_components}; @@ -41,6 +41,8 @@ fn find_related_assets(path: &Path) -> Vec<PathBuf> { #[derive(Clone, Debug, PartialEq)] pub struct Page { + /// The front matter meta-data + pub meta: PageFrontMatter, /// The .md path pub file_path: PathBuf, /// The .md path, starting from the content directory, with / slashes @@ -60,8 +62,6 @@ pub struct Page { pub assets: Vec<PathBuf>, /// The HTML rendered of the page pub content: String, - /// The front matter meta-data - pub meta: FrontMatter, /// The slug of that page. /// First tries to find the slug in the meta and defaults to filename otherwise @@ -83,8 +83,9 @@ pub struct Page { impl Page { - pub fn new(meta: FrontMatter) -> Page { + pub fn new(meta: PageFrontMatter) -> Page { Page { + meta: meta, file_path: PathBuf::new(), relative_path: String::new(), parent_path: PathBuf::new(), @@ -97,7 +98,6 @@ impl Page { path: "".to_string(), permalink: "".to_string(), summary: None, - meta: meta, previous: None, next: None, } @@ -122,7 +122,7 @@ impl Page { /// erroneous pub fn parse(file_path: &Path, content: &str, config: &Config) -> Result<Page> { // 1. separate front matter from content - let (meta, content) = split_content(file_path, content)?; + let (meta, content) = split_page_content(file_path, content)?; let mut page = Page::new(meta); page.file_path = file_path.to_path_buf(); page.parent_path = page.file_path.parent().unwrap().to_path_buf(); @@ -217,6 +217,28 @@ impl Page { } } +impl Default for Page { + fn default() -> Page { + Page { + meta: PageFrontMatter::default(), + file_path: PathBuf::new(), + relative_path: String::new(), + parent_path: PathBuf::new(), + file_name: "".to_string(), + components: vec![], + raw_content: "".to_string(), + assets: vec![], + content: "".to_string(), + slug: "".to_string(), + path: "".to_string(), + permalink: "".to_string(), + summary: None, + previous: None, + next: None, + } + } +} + impl ser::Serialize for Page { fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error> where S: ser::Serializer { let mut state = serializer.serialize_struct("page", 16)?; @@ -318,17 +340,17 @@ mod tests { use std::fs::File; - use front_matter::{FrontMatter, SortBy}; + use front_matter::{PageFrontMatter, SortBy}; use super::{Page, find_related_assets, sort_pages, populate_previous_and_next_pages}; fn create_page_with_date(date: &str) -> Page { - let mut front_matter = FrontMatter::default(); + let mut front_matter = PageFrontMatter::default(); front_matter.date = Some(date.to_string()); Page::new(front_matter) } fn create_page_with_order(order: usize) -> Page { - let mut front_matter = FrontMatter::default(); + let mut front_matter = PageFrontMatter::default(); front_matter.order = Some(order); Page::new(front_matter) } diff --git a/src/pagination.rs b/src/pagination.rs index b348137..483fad8 100644 --- a/src/pagination.rs +++ b/src/pagination.rs @@ -154,14 +154,14 @@ impl<'a> Paginator<'a> { mod tests { use tera::{to_value}; - use front_matter::FrontMatter; + use front_matter::SectionFrontMatter; use page::Page; use section::Section; use super::{Paginator}; fn create_section(is_index: bool) -> Section { - let mut f = FrontMatter::default(); + let mut f = SectionFrontMatter::default(); f.paginate_by = Some(2); f.paginate_path = Some("page".to_string()); let mut s = Section::new("content/_index.md", f); @@ -178,9 +178,9 @@ mod tests { #[test] fn test_can_create_paginator() { let pages = vec![ - Page::new(FrontMatter::default()), - Page::new(FrontMatter::default()), - Page::new(FrontMatter::default()), + Page::default(), + Page::default(), + Page::default(), ]; let section = create_section(false); let paginator = Paginator::new(pages.as_slice(), §ion); @@ -200,9 +200,9 @@ mod tests { #[test] fn test_can_create_paginator_for_index() { let pages = vec![ - Page::new(FrontMatter::default()), - Page::new(FrontMatter::default()), - Page::new(FrontMatter::default()), + Page::default(), + Page::default(), + Page::default(), ]; let section = create_section(true); let paginator = Paginator::new(pages.as_slice(), §ion); @@ -222,9 +222,9 @@ mod tests { #[test] fn test_can_build_paginator_context() { let pages = vec![ - Page::new(FrontMatter::default()), - Page::new(FrontMatter::default()), - Page::new(FrontMatter::default()), + Page::default(), + Page::default(), + Page::default(), ]; let section = create_section(false); let paginator = Paginator::new(pages.as_slice(), §ion); diff --git a/src/section.rs b/src/section.rs index 1d261c3..e8a408f 100644 --- a/src/section.rs +++ b/src/section.rs @@ -6,7 +6,7 @@ use tera::{Tera, Context}; use serde::ser::{SerializeStruct, self}; use config::Config; -use front_matter::{FrontMatter, split_content}; +use front_matter::{SectionFrontMatter, split_section_content}; use errors::{Result, ResultExt}; use utils::{read_file, find_content_components}; use markdown::markdown_to_html; @@ -15,6 +15,8 @@ use page::{Page}; #[derive(Clone, Debug, PartialEq)] pub struct Section { + /// The front matter meta-data + pub meta: SectionFrontMatter, /// The _index.md full path pub file_path: PathBuf, /// The .md path, starting from the content directory, with / slashes @@ -31,8 +33,6 @@ pub struct Section { pub raw_content: String, /// The HTML rendered of the page pub content: String, - /// The front matter meta-data - pub meta: FrontMatter, /// All direct pages of that section pub pages: Vec<Page>, /// All pages that cannot be sorted in this section @@ -42,10 +42,11 @@ pub struct Section { } impl Section { - pub fn new<P: AsRef<Path>>(file_path: P, meta: FrontMatter) -> Section { + pub fn new<P: AsRef<Path>>(file_path: P, meta: SectionFrontMatter) -> Section { let file_path = file_path.as_ref(); Section { + meta: meta, file_path: file_path.to_path_buf(), relative_path: "".to_string(), parent_path: file_path.parent().unwrap().to_path_buf(), @@ -54,7 +55,6 @@ impl Section { permalink: "".to_string(), raw_content: "".to_string(), content: "".to_string(), - meta: meta, pages: vec![], ignored_pages: vec![], subsections: vec![], @@ -62,7 +62,7 @@ impl Section { } pub fn parse(file_path: &Path, content: &str, config: &Config) -> Result<Section> { - let (meta, content) = split_content(file_path, content)?; + let (meta, content) = split_section_content(file_path, content)?; let mut section = Section::new(file_path, meta); section.raw_content = content.clone(); section.components = find_content_components(§ion.file_path); @@ -154,6 +154,7 @@ impl Default for Section { /// Used to create a default index section if there is no _index.md in the root content directory fn default() -> Section { Section { + meta: SectionFrontMatter::default(), file_path: PathBuf::new(), relative_path: "".to_string(), parent_path: PathBuf::new(), @@ -162,7 +163,6 @@ impl Default for Section { permalink: "".to_string(), raw_content: "".to_string(), content: "".to_string(), - meta: FrontMatter::default(), pages: vec![], ignored_pages: vec![], subsections: vec![], diff --git a/tests/front_matter.rs b/tests/front_matter.rs deleted file mode 100644 index cb09953..0000000 --- a/tests/front_matter.rs +++ /dev/null @@ -1,236 +0,0 @@ -extern crate gutenberg; -extern crate tera; - -use std::path::Path; - -use gutenberg::{FrontMatter, split_content, SortBy}; -use tera::to_value; - - -#[test] -fn test_can_parse_a_valid_front_matter() { - let content = r#" -title = "Hello" -description = "hey there""#; - let res = FrontMatter::parse(content); - println!("{:?}", res); - assert!(res.is_ok()); - let res = res.unwrap(); - assert_eq!(res.title.unwrap(), "Hello".to_string()); - assert_eq!(res.description.unwrap(), "hey there".to_string()); -} - -#[test] -fn test_can_parse_tags() { - let content = r#" -title = "Hello" -description = "hey there" -slug = "hello-world" -tags = ["rust", "html"]"#; - let res = FrontMatter::parse(content); - assert!(res.is_ok()); - let res = res.unwrap(); - - assert_eq!(res.title.unwrap(), "Hello".to_string()); - assert_eq!(res.slug.unwrap(), "hello-world".to_string()); - assert_eq!(res.tags.unwrap(), ["rust".to_string(), "html".to_string()]); -} - -#[test] -fn test_can_parse_extra_attributes_in_frontmatter() { - let content = r#" -title = "Hello" -description = "hey there" -slug = "hello-world" - -[extra] -language = "en" -authors = ["Bob", "Alice"]"#; - let res = FrontMatter::parse(content); - assert!(res.is_ok()); - let res = res.unwrap(); - - assert_eq!(res.title.unwrap(), "Hello".to_string()); - assert_eq!(res.slug.unwrap(), "hello-world".to_string()); - let extra = res.extra.unwrap(); - assert_eq!(extra["language"], to_value("en").unwrap()); - assert_eq!( - extra["authors"], - to_value(["Bob".to_string(), "Alice".to_string()]).unwrap() - ); -} - -#[test] -fn test_is_ok_with_url_instead_of_slug() { - let content = r#" -title = "Hello" -description = "hey there" -url = "hello-world""#; - let res = FrontMatter::parse(content); - assert!(res.is_ok()); - let res = res.unwrap(); - assert!(res.slug.is_none()); - assert_eq!(res.url.unwrap(), "hello-world".to_string()); -} - -#[test] -fn test_is_ok_with_empty_front_matter() { - let content = r#" "#; - let res = FrontMatter::parse(content); - assert!(res.is_ok()); -} - -#[test] -fn test_errors_with_invalid_front_matter() { - let content = r#"title = 1\n"#; - let res = FrontMatter::parse(content); - assert!(res.is_err()); -} - -#[test] -fn test_errors_on_non_string_tag() { - let content = r#" -title = "Hello" -description = "hey there" -slug = "hello-world" -tags = ["rust", 1]"#; - let res = FrontMatter::parse(content); - assert!(res.is_err()); -} - -#[test] -fn test_errors_on_present_but_empty_slug() { - let content = r#" -title = "Hello" -description = "hey there" -slug = """#; - let res = FrontMatter::parse(content); - assert!(res.is_err()); -} - -#[test] -fn test_errors_on_present_but_empty_url() { - let content = r#" -title = "Hello" -description = "hey there" -url = """#; - let res = FrontMatter::parse(content); - assert!(res.is_err()); -} - -#[test] -fn test_parse_date_yyyy_mm_dd() { - let content = r#" -title = "Hello" -description = "hey there" -date = "2016-10-10""#; - let res = FrontMatter::parse(content).unwrap(); - assert!(res.date().is_some()); -} - -#[test] -fn test_parse_date_rfc3339() { - let content = r#" -title = "Hello" -description = "hey there" -date = "2002-10-02T15:00:00Z""#; - let res = FrontMatter::parse(content).unwrap(); - assert!(res.date().is_some()); -} - -#[test] -fn test_cant_parse_random_date_format() { - let content = r#" -title = "Hello" -description = "hey there" -date = "2002/10/12""#; - let res = FrontMatter::parse(content).unwrap(); - assert!(res.date().is_none()); -} - -#[test] -fn test_cant_parse_sort_by_date() { - let content = r#" -title = "Hello" -description = "hey there" -sort_by = "date""#; - let res = FrontMatter::parse(content).unwrap(); - assert!(res.sort_by.is_some()); - assert_eq!(res.sort_by.unwrap(), SortBy::Date); -} - -#[test] -fn test_cant_parse_sort_by_order() { - let content = r#" -title = "Hello" -description = "hey there" -sort_by = "order""#; - let res = FrontMatter::parse(content).unwrap(); - assert!(res.sort_by.is_some()); - assert_eq!(res.sort_by.unwrap(), SortBy::Order); -} - -#[test] -fn test_cant_parse_sort_by_none() { - let content = r#" -title = "Hello" -description = "hey there" -sort_by = "none""#; - let res = FrontMatter::parse(content).unwrap(); - assert!(res.sort_by.is_some()); - assert_eq!(res.sort_by.unwrap(), SortBy::None); -} - -#[test] -fn test_can_split_content_valid() { - let content = r#" -+++ -title = "Title" -description = "hey there" -date = "2002/10/12" -+++ -Hello -"#; - let (front_matter, content) = split_content(Path::new(""), content).unwrap(); - assert_eq!(content, "Hello\n"); - assert_eq!(front_matter.title.unwrap(), "Title"); -} - -#[test] -fn test_can_split_content_with_only_frontmatter_valid() { - let content = r#" -+++ -title = "Title" -description = "hey there" -date = "2002/10/12" -+++"#; - let (front_matter, content) = split_content(Path::new(""), content).unwrap(); - assert_eq!(content, ""); - assert_eq!(front_matter.title.unwrap(), "Title"); -} - -#[test] -fn test_can_split_content_lazily() { - let content = r#" -+++ -title = "Title" -description = "hey there" -date = "2002-10-02T15:00:00Z" -+++ -+++"#; - let (front_matter, content) = split_content(Path::new(""), content).unwrap(); - assert_eq!(content, "+++"); - assert_eq!(front_matter.title.unwrap(), "Title"); -} - -#[test] -fn test_error_if_cannot_locate_frontmatter() { - let content = r#" -+++ -title = "Title" -description = "hey there" -date = "2002/10/12" -"#; - let res = split_content(Path::new(""), content); - assert!(res.is_err()); -}