parent
bb3cba1ad5
commit
299c3c8b22
|
@ -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 {
|
||||
/// <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>,
|
||||
/// 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()))
|
||||
}
|
122
src/front_matter/mod.rs
Normal file
122
src/front_matter/mod.rs
Normal file
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
206
src/front_matter/page.rs
Normal file
206
src/front_matter/page.rs
Normal file
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
99
src/front_matter/section.rs
Normal file
99
src/front_matter/section.rs
Normal file
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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};
|
||||
|
|
40
src/page.rs
40
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)
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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![],
|
||||
|
|
|
@ -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());
|
||||
}
|
Loading…
Reference in a new issue