@@ -96,17 +96,14 @@ pub fn after_content_change(site: &mut Site, path: &Path) -> Result<()> { | |||||
// - any page that was referencing the section (index, etc) | // - any page that was referencing the section (index, etc) | ||||
let relative_path = site.pages[path].relative_path.clone(); | let relative_path = site.pages[path].relative_path.clone(); | ||||
site.permalinks.remove(&relative_path); | site.permalinks.remove(&relative_path); | ||||
match site.pages.remove(path) { | |||||
Some(p) => { | |||||
if p.meta.has_tags() || p.meta.category.is_some() { | |||||
site.populate_tags_and_categories(); | |||||
} | |||||
if let Some(p) = site.pages.remove(path) { | |||||
if p.meta.has_tags() || p.meta.category.is_some() { | |||||
site.populate_tags_and_categories(); | |||||
} | |||||
if site.find_parent_section(&p).is_some() { | |||||
site.populate_sections(); | |||||
} | |||||
}, | |||||
None => () | |||||
if site.find_parent_section(&p).is_some() { | |||||
site.populate_sections(); | |||||
} | |||||
}; | }; | ||||
} | } | ||||
// Deletion is something that doesn't happen all the time so we | // Deletion is something that doesn't happen all the time so we | ||||
@@ -4,7 +4,11 @@ | |||||
mod page; | mod page; | ||||
mod pagination; | mod pagination; | ||||
mod section; | mod section; | ||||
mod sorting; | |||||
mod utils; | |||||
pub use self::page::{Page, sort_pages, populate_previous_and_next_pages}; | |||||
pub use self::page::{Page}; | |||||
pub use self::section::{Section}; | pub use self::section::{Section}; | ||||
pub use self::pagination::{Paginator, Pager}; | pub use self::pagination::{Paginator, Pager}; | ||||
pub use self::sorting::{SortBy, sort_pages, populate_previous_and_next_pages}; | |||||
@@ -1,6 +1,5 @@ | |||||
/// A page, can be a blog post or a basic page | /// A page, can be a blog post or a basic page | ||||
use std::collections::HashMap; | use std::collections::HashMap; | ||||
use std::fs::{read_dir}; | |||||
use std::path::{Path, PathBuf}; | use std::path::{Path, PathBuf}; | ||||
use std::result::Result as StdResult; | use std::result::Result as StdResult; | ||||
@@ -11,32 +10,12 @@ use slug::slugify; | |||||
use errors::{Result, ResultExt}; | use errors::{Result, ResultExt}; | ||||
use config::Config; | use config::Config; | ||||
use front_matter::{PageFrontMatter, SortBy, split_page_content}; | |||||
use front_matter::{PageFrontMatter, split_page_content}; | |||||
use markdown::markdown_to_html; | use markdown::markdown_to_html; | ||||
use utils::{read_file, find_content_components}; | use utils::{read_file, find_content_components}; | ||||
use content::utils::{find_related_assets, get_reading_analytics}; | |||||
/// Looks into the current folder for the path and see if there's anything that is not a .md | |||||
/// file. Those will be copied next to the rendered .html file | |||||
fn find_related_assets(path: &Path) -> Vec<PathBuf> { | |||||
let mut assets = vec![]; | |||||
for entry in read_dir(path).unwrap().filter_map(|e| e.ok()) { | |||||
let entry_path = entry.path(); | |||||
if entry_path.is_file() { | |||||
match entry_path.extension() { | |||||
Some(e) => match e.to_str() { | |||||
Some("md") => continue, | |||||
_ => assets.push(entry_path.to_path_buf()), | |||||
}, | |||||
None => continue, | |||||
} | |||||
} | |||||
} | |||||
assets | |||||
} | |||||
#[derive(Clone, Debug, PartialEq)] | #[derive(Clone, Debug, PartialEq)] | ||||
pub struct Page { | pub struct Page { | ||||
@@ -102,20 +81,6 @@ impl Page { | |||||
} | } | ||||
} | } | ||||
pub fn has_date(&self) -> bool { | |||||
self.meta.date.is_some() | |||||
} | |||||
/// Get word count and estimated reading time | |||||
pub fn get_reading_analytics(&self) -> (usize, usize) { | |||||
// Only works for latin language but good enough for a start | |||||
let word_count: usize = self.raw_content.split_whitespace().count(); | |||||
// https://help.medium.com/hc/en-us/articles/214991667-Read-time | |||||
// 275 seems a bit too high though | |||||
(word_count, (word_count / 200)) | |||||
} | |||||
/// Parse a page given the content of the .md file | /// Parse a page given the content of the .md file | ||||
/// Files without front matter or with invalid front matter are considered | /// Files without front matter or with invalid front matter are considered | ||||
/// erroneous | /// erroneous | ||||
@@ -253,7 +218,7 @@ impl ser::Serialize for Page { | |||||
state.serialize_field("draft", &self.meta.draft)?; | state.serialize_field("draft", &self.meta.draft)?; | ||||
state.serialize_field("category", &self.meta.category)?; | state.serialize_field("category", &self.meta.category)?; | ||||
state.serialize_field("extra", &self.meta.extra)?; | state.serialize_field("extra", &self.meta.extra)?; | ||||
let (word_count, reading_time) = self.get_reading_analytics(); | |||||
let (word_count, reading_time) = get_reading_analytics(&self.raw_content); | |||||
state.serialize_field("word_count", &word_count)?; | state.serialize_field("word_count", &word_count)?; | ||||
state.serialize_field("reading_time", &reading_time)?; | state.serialize_field("reading_time", &reading_time)?; | ||||
state.serialize_field("previous", &self.previous)?; | state.serialize_field("previous", &self.previous)?; | ||||
@@ -261,182 +226,3 @@ impl ser::Serialize for Page { | |||||
state.end() | state.end() | ||||
} | } | ||||
} | } | ||||
/// Sort pages using the method for the given section | |||||
/// | |||||
/// Any pages that doesn't have a date when the sorting method is date or order | |||||
/// when the sorting method is order will be ignored. | |||||
pub fn sort_pages(pages: Vec<Page>, sort_by: SortBy) -> (Vec<Page>, Vec<Page>) { | |||||
match sort_by { | |||||
SortBy::Date => { | |||||
let mut can_be_sorted = vec![]; | |||||
let mut cannot_be_sorted = vec![]; | |||||
for page in pages { | |||||
if page.meta.date.is_some() { | |||||
can_be_sorted.push(page); | |||||
} else { | |||||
cannot_be_sorted.push(page); | |||||
} | |||||
} | |||||
can_be_sorted.sort_by(|a, b| b.meta.date().unwrap().cmp(&a.meta.date().unwrap())); | |||||
(can_be_sorted, cannot_be_sorted) | |||||
}, | |||||
SortBy::Order => { | |||||
let mut can_be_sorted = vec![]; | |||||
let mut cannot_be_sorted = vec![]; | |||||
for page in pages { | |||||
if page.meta.order.is_some() { | |||||
can_be_sorted.push(page); | |||||
} else { | |||||
cannot_be_sorted.push(page); | |||||
} | |||||
} | |||||
can_be_sorted.sort_by(|a, b| b.meta.order().cmp(&a.meta.order())); | |||||
(can_be_sorted, cannot_be_sorted) | |||||
}, | |||||
SortBy::None => (pages, vec![]) | |||||
} | |||||
} | |||||
/// Horribly inefficient way to set previous and next on each pages | |||||
/// So many clones | |||||
pub fn populate_previous_and_next_pages(input: &[Page]) -> Vec<Page> { | |||||
let pages = input.to_vec(); | |||||
let mut res = Vec::new(); | |||||
// the input is already sorted | |||||
// We might put prev/next randomly if a page is missing date/order, probably fine | |||||
for (i, page) in input.iter().enumerate() { | |||||
let mut new_page = page.clone(); | |||||
if i > 0 { | |||||
let next = &pages[i - 1]; | |||||
new_page.next = Some(Box::new(next.clone())); | |||||
} | |||||
if i < input.len() - 1 { | |||||
let previous = &pages[i + 1]; | |||||
new_page.previous = Some(Box::new(previous.clone())); | |||||
} | |||||
res.push(new_page); | |||||
} | |||||
res | |||||
} | |||||
#[cfg(test)] | |||||
mod tests { | |||||
use std::fs::File; | |||||
use tempdir::TempDir; | |||||
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 = 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 = PageFrontMatter::default(); | |||||
front_matter.order = Some(order); | |||||
Page::new(front_matter) | |||||
} | |||||
#[test] | |||||
fn can_find_related_assets() { | |||||
let tmp_dir = TempDir::new("example").expect("create temp dir"); | |||||
File::create(tmp_dir.path().join("index.md")).unwrap(); | |||||
File::create(tmp_dir.path().join("example.js")).unwrap(); | |||||
File::create(tmp_dir.path().join("graph.jpg")).unwrap(); | |||||
File::create(tmp_dir.path().join("fail.png")).unwrap(); | |||||
let assets = find_related_assets(tmp_dir.path()); | |||||
assert_eq!(assets.len(), 3); | |||||
assert_eq!(assets.iter().filter(|p| p.extension().unwrap() != "md").count(), 3); | |||||
assert_eq!(assets.iter().filter(|p| p.file_name().unwrap() == "example.js").count(), 1); | |||||
assert_eq!(assets.iter().filter(|p| p.file_name().unwrap() == "graph.jpg").count(), 1); | |||||
assert_eq!(assets.iter().filter(|p| p.file_name().unwrap() == "fail.png").count(), 1); | |||||
} | |||||
#[test] | |||||
fn can_sort_by_dates() { | |||||
let input = vec![ | |||||
create_page_with_date("2018-01-01"), | |||||
create_page_with_date("2017-01-01"), | |||||
create_page_with_date("2019-01-01"), | |||||
]; | |||||
let (pages, _) = sort_pages(input, SortBy::Date); | |||||
// Should be sorted by date | |||||
assert_eq!(pages[0].clone().meta.date.unwrap(), "2019-01-01"); | |||||
assert_eq!(pages[1].clone().meta.date.unwrap(), "2018-01-01"); | |||||
assert_eq!(pages[2].clone().meta.date.unwrap(), "2017-01-01"); | |||||
} | |||||
#[test] | |||||
fn can_sort_by_order() { | |||||
let input = vec![ | |||||
create_page_with_order(2), | |||||
create_page_with_order(3), | |||||
create_page_with_order(1), | |||||
]; | |||||
let (pages, _) = sort_pages(input, SortBy::Order); | |||||
// Should be sorted by date | |||||
assert_eq!(pages[0].clone().meta.order.unwrap(), 3); | |||||
assert_eq!(pages[1].clone().meta.order.unwrap(), 2); | |||||
assert_eq!(pages[2].clone().meta.order.unwrap(), 1); | |||||
} | |||||
#[test] | |||||
fn can_sort_by_none() { | |||||
let input = vec![ | |||||
create_page_with_order(2), | |||||
create_page_with_order(3), | |||||
create_page_with_order(1), | |||||
]; | |||||
let (pages, _) = sort_pages(input, SortBy::None); | |||||
// Should be sorted by date | |||||
assert_eq!(pages[0].clone().meta.order.unwrap(), 2); | |||||
assert_eq!(pages[1].clone().meta.order.unwrap(), 3); | |||||
assert_eq!(pages[2].clone().meta.order.unwrap(), 1); | |||||
} | |||||
#[test] | |||||
fn ignore_page_with_missing_field() { | |||||
let input = vec![ | |||||
create_page_with_order(2), | |||||
create_page_with_order(3), | |||||
create_page_with_date("2019-01-01"), | |||||
]; | |||||
let (pages, unsorted) = sort_pages(input, SortBy::Order); | |||||
assert_eq!(pages.len(), 2); | |||||
assert_eq!(unsorted.len(), 1); | |||||
} | |||||
#[test] | |||||
fn can_populate_previous_and_next_pages() { | |||||
let input = vec![ | |||||
create_page_with_order(3), | |||||
create_page_with_order(2), | |||||
create_page_with_order(1), | |||||
]; | |||||
let pages = populate_previous_and_next_pages(input.as_slice()); | |||||
assert!(pages[0].clone().next.is_none()); | |||||
assert!(pages[0].clone().previous.is_some()); | |||||
assert_eq!(pages[0].clone().previous.unwrap().meta.order.unwrap(), 2); | |||||
assert!(pages[1].clone().next.is_some()); | |||||
assert!(pages[1].clone().previous.is_some()); | |||||
assert_eq!(pages[1].clone().next.unwrap().meta.order.unwrap(), 3); | |||||
assert_eq!(pages[1].clone().previous.unwrap().meta.order.unwrap(), 1); | |||||
assert!(pages[2].clone().next.is_some()); | |||||
assert!(pages[2].clone().previous.is_none()); | |||||
assert_eq!(pages[2].clone().next.unwrap().meta.order.unwrap(), 2); | |||||
} | |||||
} |
@@ -0,0 +1,169 @@ | |||||
use content::Page; | |||||
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] | |||||
#[serde(rename_all = "lowercase")] | |||||
pub enum SortBy { | |||||
Date, | |||||
Order, | |||||
None, | |||||
} | |||||
/// Sort pages using the method for the given section | |||||
/// | |||||
/// Any pages that doesn't have a date when the sorting method is date or order | |||||
/// when the sorting method is order will be ignored. | |||||
pub fn sort_pages(pages: Vec<Page>, sort_by: SortBy) -> (Vec<Page>, Vec<Page>) { | |||||
match sort_by { | |||||
SortBy::Date => { | |||||
let mut can_be_sorted = vec![]; | |||||
let mut cannot_be_sorted = vec![]; | |||||
for page in pages { | |||||
if page.meta.date.is_some() { | |||||
can_be_sorted.push(page); | |||||
} else { | |||||
cannot_be_sorted.push(page); | |||||
} | |||||
} | |||||
can_be_sorted.sort_by(|a, b| b.meta.date().unwrap().cmp(&a.meta.date().unwrap())); | |||||
(can_be_sorted, cannot_be_sorted) | |||||
}, | |||||
SortBy::Order => { | |||||
let mut can_be_sorted = vec![]; | |||||
let mut cannot_be_sorted = vec![]; | |||||
for page in pages { | |||||
if page.meta.order.is_some() { | |||||
can_be_sorted.push(page); | |||||
} else { | |||||
cannot_be_sorted.push(page); | |||||
} | |||||
} | |||||
can_be_sorted.sort_by(|a, b| b.meta.order().cmp(&a.meta.order())); | |||||
(can_be_sorted, cannot_be_sorted) | |||||
}, | |||||
SortBy::None => (pages, vec![]) | |||||
} | |||||
} | |||||
/// Horribly inefficient way to set previous and next on each pages | |||||
/// So many clones | |||||
pub fn populate_previous_and_next_pages(input: &[Page]) -> Vec<Page> { | |||||
let pages = input.to_vec(); | |||||
let mut res = Vec::new(); | |||||
// the input is already sorted | |||||
// We might put prev/next randomly if a page is missing date/order, probably fine | |||||
for (i, page) in input.iter().enumerate() { | |||||
let mut new_page = page.clone(); | |||||
if i > 0 { | |||||
let next = &pages[i - 1]; | |||||
new_page.next = Some(Box::new(next.clone())); | |||||
} | |||||
if i < input.len() - 1 { | |||||
let previous = &pages[i + 1]; | |||||
new_page.previous = Some(Box::new(previous.clone())); | |||||
} | |||||
res.push(new_page); | |||||
} | |||||
res | |||||
} | |||||
#[cfg(test)] | |||||
mod tests { | |||||
use front_matter::{PageFrontMatter}; | |||||
use content::Page; | |||||
use super::{SortBy, sort_pages, populate_previous_and_next_pages}; | |||||
fn create_page_with_date(date: &str) -> Page { | |||||
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 = PageFrontMatter::default(); | |||||
front_matter.order = Some(order); | |||||
Page::new(front_matter) | |||||
} | |||||
#[test] | |||||
fn can_sort_by_dates() { | |||||
let input = vec![ | |||||
create_page_with_date("2018-01-01"), | |||||
create_page_with_date("2017-01-01"), | |||||
create_page_with_date("2019-01-01"), | |||||
]; | |||||
let (pages, _) = sort_pages(input, SortBy::Date); | |||||
// Should be sorted by date | |||||
assert_eq!(pages[0].clone().meta.date.unwrap(), "2019-01-01"); | |||||
assert_eq!(pages[1].clone().meta.date.unwrap(), "2018-01-01"); | |||||
assert_eq!(pages[2].clone().meta.date.unwrap(), "2017-01-01"); | |||||
} | |||||
#[test] | |||||
fn can_sort_by_order() { | |||||
let input = vec![ | |||||
create_page_with_order(2), | |||||
create_page_with_order(3), | |||||
create_page_with_order(1), | |||||
]; | |||||
let (pages, _) = sort_pages(input, SortBy::Order); | |||||
// Should be sorted by date | |||||
assert_eq!(pages[0].clone().meta.order.unwrap(), 3); | |||||
assert_eq!(pages[1].clone().meta.order.unwrap(), 2); | |||||
assert_eq!(pages[2].clone().meta.order.unwrap(), 1); | |||||
} | |||||
#[test] | |||||
fn can_sort_by_none() { | |||||
let input = vec![ | |||||
create_page_with_order(2), | |||||
create_page_with_order(3), | |||||
create_page_with_order(1), | |||||
]; | |||||
let (pages, _) = sort_pages(input, SortBy::None); | |||||
// Should be sorted by date | |||||
assert_eq!(pages[0].clone().meta.order.unwrap(), 2); | |||||
assert_eq!(pages[1].clone().meta.order.unwrap(), 3); | |||||
assert_eq!(pages[2].clone().meta.order.unwrap(), 1); | |||||
} | |||||
#[test] | |||||
fn ignore_page_with_missing_field() { | |||||
let input = vec![ | |||||
create_page_with_order(2), | |||||
create_page_with_order(3), | |||||
create_page_with_date("2019-01-01"), | |||||
]; | |||||
let (pages, unsorted) = sort_pages(input, SortBy::Order); | |||||
assert_eq!(pages.len(), 2); | |||||
assert_eq!(unsorted.len(), 1); | |||||
} | |||||
#[test] | |||||
fn can_populate_previous_and_next_pages() { | |||||
let input = vec![ | |||||
create_page_with_order(3), | |||||
create_page_with_order(2), | |||||
create_page_with_order(1), | |||||
]; | |||||
let pages = populate_previous_and_next_pages(input.as_slice()); | |||||
assert!(pages[0].clone().next.is_none()); | |||||
assert!(pages[0].clone().previous.is_some()); | |||||
assert_eq!(pages[0].clone().previous.unwrap().meta.order.unwrap(), 2); | |||||
assert!(pages[1].clone().next.is_some()); | |||||
assert!(pages[1].clone().previous.is_some()); | |||||
assert_eq!(pages[1].clone().next.unwrap().meta.order.unwrap(), 3); | |||||
assert_eq!(pages[1].clone().previous.unwrap().meta.order.unwrap(), 1); | |||||
assert!(pages[2].clone().next.is_some()); | |||||
assert!(pages[2].clone().previous.is_none()); | |||||
assert_eq!(pages[2].clone().next.unwrap().meta.order.unwrap(), 2); | |||||
} | |||||
} |
@@ -0,0 +1,77 @@ | |||||
use std::fs::read_dir; | |||||
use std::path::{Path, PathBuf}; | |||||
/// Looks into the current folder for the path and see if there's anything that is not a .md | |||||
/// file. Those will be copied next to the rendered .html file | |||||
pub fn find_related_assets(path: &Path) -> Vec<PathBuf> { | |||||
let mut assets = vec![]; | |||||
for entry in read_dir(path).unwrap().filter_map(|e| e.ok()) { | |||||
let entry_path = entry.path(); | |||||
if entry_path.is_file() { | |||||
match entry_path.extension() { | |||||
Some(e) => match e.to_str() { | |||||
Some("md") => continue, | |||||
_ => assets.push(entry_path.to_path_buf()), | |||||
}, | |||||
None => continue, | |||||
} | |||||
} | |||||
} | |||||
assets | |||||
} | |||||
/// Get word count and estimated reading time | |||||
pub fn get_reading_analytics(content: &str) -> (usize, usize) { | |||||
// Only works for latin language but good enough for a start | |||||
let word_count: usize = content.split_whitespace().count(); | |||||
// https://help.medium.com/hc/en-us/articles/214991667-Read-time | |||||
// 275 seems a bit too high though | |||||
(word_count, (word_count / 200)) | |||||
} | |||||
#[cfg(test)] | |||||
mod tests { | |||||
use std::fs::File; | |||||
use tempdir::TempDir; | |||||
use super::{find_related_assets, get_reading_analytics}; | |||||
#[test] | |||||
fn can_find_related_assets() { | |||||
let tmp_dir = TempDir::new("example").expect("create temp dir"); | |||||
File::create(tmp_dir.path().join("index.md")).unwrap(); | |||||
File::create(tmp_dir.path().join("example.js")).unwrap(); | |||||
File::create(tmp_dir.path().join("graph.jpg")).unwrap(); | |||||
File::create(tmp_dir.path().join("fail.png")).unwrap(); | |||||
let assets = find_related_assets(tmp_dir.path()); | |||||
assert_eq!(assets.len(), 3); | |||||
assert_eq!(assets.iter().filter(|p| p.extension().unwrap() != "md").count(), 3); | |||||
assert_eq!(assets.iter().filter(|p| p.file_name().unwrap() == "example.js").count(), 1); | |||||
assert_eq!(assets.iter().filter(|p| p.file_name().unwrap() == "graph.jpg").count(), 1); | |||||
assert_eq!(assets.iter().filter(|p| p.file_name().unwrap() == "fail.png").count(), 1); | |||||
} | |||||
#[test] | |||||
fn reading_analytics_short_text() { | |||||
let (word_count, reading_time) = get_reading_analytics("Hello World"); | |||||
assert_eq!(word_count, 2); | |||||
assert_eq!(reading_time, 0); | |||||
} | |||||
#[test] | |||||
fn reading_analytics_long_text() { | |||||
let mut content = String::new(); | |||||
for _ in 0..1000 { | |||||
content.push_str(" Hello world"); | |||||
} | |||||
let (word_count, reading_time) = get_reading_analytics(&content); | |||||
assert_eq!(word_count, 2000); | |||||
assert_eq!(reading_time, 10); | |||||
} | |||||
} |
@@ -8,7 +8,7 @@ mod page; | |||||
mod section; | mod section; | ||||
pub use self::page::PageFrontMatter; | pub use self::page::PageFrontMatter; | ||||
pub use self::section::{SectionFrontMatter, SortBy}; | |||||
pub use self::section::{SectionFrontMatter}; | |||||
lazy_static! { | lazy_static! { | ||||
static ref PAGE_RE: Regex = Regex::new(r"^[[:space:]]*\+\+\+\r?\n((?s).*?(?-s))\+\+\+\r?\n?((?s).*(?-s))$").unwrap(); | static ref PAGE_RE: Regex = Regex::new(r"^[[:space:]]*\+\+\+\r?\n((?s).*?(?-s))\+\+\+\r?\n?((?s).*(?-s))$").unwrap(); | ||||
@@ -30,7 +30,7 @@ fn split_content(file_path: &Path, content: &str) -> Result<(String, String)> { | |||||
} | } | ||||
/// Split a file between the front matter and its content. | /// Split a file between the front matter and its content. | ||||
/// Returns a parsed SectionFrontMatter and the rest of the content | |||||
/// Returns a parsed `SectionFrontMatter` and the rest of the content | |||||
pub fn split_section_content(file_path: &Path, content: &str) -> Result<(SectionFrontMatter, String)> { | pub fn split_section_content(file_path: &Path, content: &str) -> Result<(SectionFrontMatter, String)> { | ||||
let (front_matter, content) = split_content(file_path, content)?; | let (front_matter, content) = split_content(file_path, content)?; | ||||
let meta = SectionFrontMatter::parse(&front_matter) | let meta = SectionFrontMatter::parse(&front_matter) | ||||
@@ -39,7 +39,7 @@ pub fn split_section_content(file_path: &Path, content: &str) -> Result<(Section | |||||
} | } | ||||
/// Split a file between the front matter and its content | /// Split a file between the front matter and its content | ||||
/// Returns a parsed PageFrontMatter and the rest of the content | |||||
/// Returns a parsed `PageFrontMatter` and the rest of the content | |||||
pub fn split_page_content(file_path: &Path, content: &str) -> Result<(PageFrontMatter, String)> { | pub fn split_page_content(file_path: &Path, content: &str) -> Result<(PageFrontMatter, String)> { | ||||
let (front_matter, content) = split_content(file_path, content)?; | let (front_matter, content) = split_content(file_path, content)?; | ||||
let meta = PageFrontMatter::parse(&front_matter) | let meta = PageFrontMatter::parse(&front_matter) | ||||
@@ -4,16 +4,10 @@ use tera::Value; | |||||
use toml; | use toml; | ||||
use errors::{Result}; | use errors::{Result}; | ||||
use content::SortBy; | |||||
static DEFAULT_PAGINATE_PATH: &'static str = "page"; | 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 | /// The front matter of every section | ||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] | ||||
@@ -31,7 +31,7 @@ mod templates; | |||||
pub use site::{Site}; | pub use site::{Site}; | ||||
pub use config::{Config, get_config}; | pub use config::{Config, get_config}; | ||||
pub use front_matter::{PageFrontMatter, SectionFrontMatter, split_page_content, split_section_content, SortBy}; | |||||
pub use content::{Page, Section, sort_pages}; | |||||
pub use front_matter::{PageFrontMatter, SectionFrontMatter, split_page_content, split_section_content}; | |||||
pub use content::{Page, Section, SortBy, sort_pages, populate_previous_and_next_pages}; | |||||
pub use utils::{create_file}; | pub use utils::{create_file}; | ||||
pub use markdown::markdown_to_html; | pub use markdown::markdown_to_html; |
@@ -11,8 +11,7 @@ use walkdir::WalkDir; | |||||
use errors::{Result, ResultExt}; | use errors::{Result, ResultExt}; | ||||
use config::{Config, get_config}; | use config::{Config, get_config}; | ||||
use utils::{create_file, create_directory}; | use utils::{create_file, create_directory}; | ||||
use content::{Page, Section, Paginator, populate_previous_and_next_pages, sort_pages}; | |||||
use front_matter::{SortBy}; | |||||
use content::{Page, Section, Paginator, SortBy, populate_previous_and_next_pages, sort_pages}; | |||||
use templates::{GUTENBERG_TERA, global_fns, render_redirect_template}; | use templates::{GUTENBERG_TERA, global_fns, render_redirect_template}; | ||||
@@ -239,7 +238,7 @@ impl Site { | |||||
/// Sorts the pages of the section at the given path | /// Sorts the pages of the section at the given path | ||||
/// By default will sort all sections but can be made to only sort a single one by providing a path | /// By default will sort all sections but can be made to only sort a single one by providing a path | ||||
pub fn sort_sections_pages(&mut self, only: Option<&Path>) { | pub fn sort_sections_pages(&mut self, only: Option<&Path>) { | ||||
for (path, section) in self.sections.iter_mut() { | |||||
for (path, section) in &mut self.sections { | |||||
if let Some(p) = only { | if let Some(p) = only { | ||||
if p != path { | if p != path { | ||||
continue; | continue; | ||||
@@ -21,7 +21,6 @@ pub fn create_directory<P: AsRef<Path>>(path: P) -> Result<()> { | |||||
Ok(()) | Ok(()) | ||||
} | } | ||||
/// Return the content of a file, with error handling added | /// Return the content of a file, with error handling added | ||||
pub fn read_file<P: AsRef<Path>>(path: P) -> Result<String> { | pub fn read_file<P: AsRef<Path>>(path: P) -> Result<String> { | ||||
let path = path.as_ref(); | let path = path.as_ref(); | ||||
@@ -163,43 +163,6 @@ Hello world"#; | |||||
assert_eq!(page.permalink, format!("{}{}", Config::default().base_url, "file-with-space")); | assert_eq!(page.permalink, format!("{}{}", Config::default().base_url, "file-with-space")); | ||||
} | } | ||||
#[test] | |||||
fn test_reading_analytics_short() { | |||||
let content = r#" | |||||
+++ | |||||
title = "Hello" | |||||
description = "hey there" | |||||
+++ | |||||
Hello world"#; | |||||
let res = Page::parse(Path::new("hello.md"), content, &Config::default()); | |||||
assert!(res.is_ok()); | |||||
let mut page = res.unwrap(); | |||||
page.render_markdown(&HashMap::default(), &Tera::default(), &Config::default()).unwrap(); | |||||
let (word_count, reading_time) = page.get_reading_analytics(); | |||||
assert_eq!(word_count, 2); | |||||
assert_eq!(reading_time, 0); | |||||
} | |||||
#[test] | |||||
fn test_reading_analytics_long() { | |||||
let mut content = r#" | |||||
+++ | |||||
title = "Hello" | |||||
description = "hey there" | |||||
+++ | |||||
Hello world"#.to_string(); | |||||
for _ in 0..1000 { | |||||
content.push_str(" Hello world"); | |||||
} | |||||
let res = Page::parse(Path::new("hello.md"), &content, &Config::default()); | |||||
assert!(res.is_ok()); | |||||
let mut page = res.unwrap(); | |||||
page.render_markdown(&HashMap::default(), &Tera::default(), &Config::default()).unwrap(); | |||||
let (word_count, reading_time) = page.get_reading_analytics(); | |||||
assert_eq!(word_count, 2002); | |||||
assert_eq!(reading_time, 10); | |||||
} | |||||
#[test] | #[test] | ||||
fn test_automatic_summary_is_empty_string() { | fn test_automatic_summary_is_empty_string() { | ||||
let content = r#" | let content = r#" | ||||