@@ -85,7 +85,7 @@ pub fn after_content_change(site: &mut Site, path: &Path) -> Result<()> { | |||||
// A section was deleted, many things can be impacted: | // A section was deleted, many things can be impacted: | ||||
// - the pages of the section are becoming orphans | // - the pages of the section are becoming orphans | ||||
// - any page that was referencing the section (index, etc) | // - any page that was referencing the section (index, etc) | ||||
let relative_path = site.sections[path].relative_path.clone(); | |||||
let relative_path = site.sections[path].file.relative.clone(); | |||||
// Remove the link to it and the section itself from the Site | // Remove the link to it and the section itself from the Site | ||||
site.permalinks.remove(&relative_path); | site.permalinks.remove(&relative_path); | ||||
site.sections.remove(path); | site.sections.remove(path); | ||||
@@ -94,7 +94,7 @@ pub fn after_content_change(site: &mut Site, path: &Path) -> Result<()> { | |||||
// A page was deleted, many things can be impacted: | // A page was deleted, many things can be impacted: | ||||
// - the section the page is in | // - the section the page is in | ||||
// - 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].file.relative.clone(); | |||||
site.permalinks.remove(&relative_path); | site.permalinks.remove(&relative_path); | ||||
if let Some(p) = site.pages.remove(path) { | if let Some(p) = site.pages.remove(path) { | ||||
if p.meta.has_tags() || p.meta.category.is_some() { | if p.meta.has_tags() || p.meta.category.is_some() { | ||||
@@ -172,7 +172,7 @@ pub fn after_content_change(site: &mut Site, path: &Path) -> Result<()> { | |||||
}, | }, | ||||
PageChangesNeeded::Sort => { | PageChangesNeeded::Sort => { | ||||
let section_path = match site.find_parent_section(&site.pages[path]) { | let section_path = match site.find_parent_section(&site.pages[path]) { | ||||
Some(s) => s.file_path.clone(), | |||||
Some(s) => s.file.path.clone(), | |||||
None => continue // Do nothing if it's an orphan page | None => continue // Do nothing if it's an orphan page | ||||
}; | }; | ||||
site.populate_sections(); | site.populate_sections(); | ||||
@@ -0,0 +1,116 @@ | |||||
use std::path::{Path, PathBuf}; | |||||
/// Takes a full path to a file and returns only the components after the first `content` directory | |||||
/// Will not return the filename as last component | |||||
pub fn find_content_components<P: AsRef<Path>>(path: P) -> Vec<String> { | |||||
let path = path.as_ref(); | |||||
let mut is_in_content = false; | |||||
let mut components = vec![]; | |||||
for section in path.parent().unwrap().components() { | |||||
let component = section.as_ref().to_string_lossy(); | |||||
if is_in_content { | |||||
components.push(component.to_string()); | |||||
continue; | |||||
} | |||||
if component == "content" { | |||||
is_in_content = true; | |||||
} | |||||
} | |||||
components | |||||
} | |||||
/// Struct that contains all the information about the actual file | |||||
#[derive(Debug, Clone, PartialEq)] | |||||
pub struct FileInfo { | |||||
/// The full path to the .md file | |||||
pub path: PathBuf, | |||||
/// The name of the .md file without the extension, always `_index` for sections | |||||
pub name: String, | |||||
/// The .md path, starting from the content directory, with `/` slashes | |||||
pub relative: String, | |||||
/// Path of the directory containing the .md file | |||||
pub parent: PathBuf, | |||||
/// Path of the grand parent directory for that file. Only used in sections to find subsections. | |||||
pub grand_parent: Option<PathBuf>, | |||||
/// The folder names to this section file, starting from the `content` directory | |||||
/// For example a file at content/kb/solutions/blabla.md will have 2 components: | |||||
/// `kb` and `solutions` | |||||
pub components: Vec<String>, | |||||
} | |||||
impl FileInfo { | |||||
pub fn new_page(path: &Path) -> FileInfo { | |||||
let file_path = path.to_path_buf(); | |||||
let mut parent = file_path.parent().unwrap().to_path_buf(); | |||||
let name = path.file_stem().unwrap().to_string_lossy().to_string(); | |||||
let mut components = find_content_components(&file_path); | |||||
let relative = format!("{}/{}.md", components.join("/"), name); | |||||
// If we have a folder with an asset, don't consider it as a component | |||||
if !components.is_empty() && name == "index" { | |||||
components.pop(); | |||||
// also set parent_path to grandparent instead | |||||
parent = parent.parent().unwrap().to_path_buf(); | |||||
} | |||||
FileInfo { | |||||
path: file_path, | |||||
// We don't care about grand parent for pages | |||||
grand_parent: None, | |||||
parent, | |||||
name, | |||||
components, | |||||
relative, | |||||
} | |||||
} | |||||
pub fn new_section(path: &Path) -> FileInfo { | |||||
let parent = path.parent().unwrap().to_path_buf(); | |||||
let components = find_content_components(path); | |||||
let relative = if components.is_empty() { | |||||
// the index one | |||||
"_index.md".to_string() | |||||
} else { | |||||
format!("{}/_index.md", components.join("/")) | |||||
}; | |||||
let grand_parent = parent.parent().map(|p| p.to_path_buf()); | |||||
FileInfo { | |||||
path: path.to_path_buf(), | |||||
parent, | |||||
grand_parent, | |||||
name: "_index".to_string(), | |||||
components, | |||||
relative, | |||||
} | |||||
} | |||||
} | |||||
#[doc(hidden)] | |||||
impl Default for FileInfo { | |||||
fn default() -> FileInfo { | |||||
FileInfo { | |||||
path: PathBuf::new(), | |||||
parent: PathBuf::new(), | |||||
grand_parent: None, | |||||
name: String::new(), | |||||
components: vec![], | |||||
relative: String::new(), | |||||
} | |||||
} | |||||
} | |||||
#[cfg(test)] | |||||
mod tests { | |||||
use super::find_content_components; | |||||
#[test] | |||||
fn can_find_content_components() { | |||||
let res = find_content_components("/home/vincent/code/site/content/posts/tutorials/python.md"); | |||||
assert_eq!(res, ["posts".to_string(), "tutorials".to_string()]); | |||||
} | |||||
} |
@@ -1,11 +1,9 @@ | |||||
// TODO: move section/page and maybe pagination in this mod | |||||
// Not sure where pagination stands if I add a render mod | |||||
mod page; | mod page; | ||||
mod pagination; | mod pagination; | ||||
mod section; | mod section; | ||||
mod sorting; | mod sorting; | ||||
mod utils; | mod utils; | ||||
mod file_info; | |||||
pub use self::page::{Page}; | pub use self::page::{Page}; | ||||
pub use self::section::{Section}; | pub use self::section::{Section}; | ||||
@@ -13,34 +13,22 @@ use config::Config; | |||||
use front_matter::{PageFrontMatter, split_page_content}; | use front_matter::{PageFrontMatter, split_page_content}; | ||||
use markdown::markdown_to_html; | use markdown::markdown_to_html; | ||||
use utils::{read_file}; | use utils::{read_file}; | ||||
use content::utils::{find_related_assets, find_content_components, get_reading_analytics}; | |||||
use content::utils::{find_related_assets, get_reading_analytics}; | |||||
use content::file_info::FileInfo; | |||||
#[derive(Clone, Debug, PartialEq)] | #[derive(Clone, Debug, PartialEq)] | ||||
pub struct Page { | pub struct Page { | ||||
/// All info about the actual file | |||||
pub file: FileInfo, | |||||
/// The front matter meta-data | /// The front matter meta-data | ||||
pub meta: PageFrontMatter, | pub meta: PageFrontMatter, | ||||
/// The .md path | |||||
pub file_path: PathBuf, | |||||
/// The .md path, starting from the content directory, with / slashes | |||||
pub relative_path: String, | |||||
/// The parent directory of the file. Is actually the grand parent directory | |||||
/// if it's an asset folder | |||||
pub parent_path: PathBuf, | |||||
/// The name of the .md file | |||||
pub file_name: String, | |||||
/// The directories above our .md file | |||||
/// for example a file at content/kb/solutions/blabla.md will have 2 components: | |||||
/// `kb` and `solutions` | |||||
pub components: Vec<String>, | |||||
/// The actual content of the page, in markdown | /// The actual content of the page, in markdown | ||||
pub raw_content: String, | pub raw_content: String, | ||||
/// All the non-md files we found next to the .md file | /// All the non-md files we found next to the .md file | ||||
pub assets: Vec<PathBuf>, | pub assets: Vec<PathBuf>, | ||||
/// The HTML rendered of the page | /// The HTML rendered of the page | ||||
pub content: String, | pub content: String, | ||||
/// The slug of that page. | /// The slug of that page. | ||||
/// First tries to find the slug in the meta and defaults to filename otherwise | /// First tries to find the slug in the meta and defaults to filename otherwise | ||||
pub slug: String, | pub slug: String, | ||||
@@ -52,7 +40,6 @@ pub struct Page { | |||||
/// When <!-- more --> is found in the text, will take the content up to that part | /// When <!-- more --> is found in the text, will take the content up to that part | ||||
/// as summary | /// as summary | ||||
pub summary: Option<String>, | pub summary: Option<String>, | ||||
/// The previous page, by whatever sorting is used for the index/section | /// The previous page, by whatever sorting is used for the index/section | ||||
pub previous: Option<Box<Page>>, | pub previous: Option<Box<Page>>, | ||||
/// The next page, by whatever sorting is used for the index/section | /// The next page, by whatever sorting is used for the index/section | ||||
@@ -61,14 +48,12 @@ pub struct Page { | |||||
impl Page { | impl Page { | ||||
pub fn new(meta: PageFrontMatter) -> Page { | |||||
pub fn new<P: AsRef<Path>>(file_path: P, meta: PageFrontMatter) -> Page { | |||||
let file_path = file_path.as_ref(); | |||||
Page { | Page { | ||||
file: FileInfo::new_page(file_path), | |||||
meta: meta, | meta: meta, | ||||
file_path: PathBuf::new(), | |||||
relative_path: String::new(), | |||||
parent_path: PathBuf::new(), | |||||
file_name: "".to_string(), | |||||
components: vec![], | |||||
raw_content: "".to_string(), | raw_content: "".to_string(), | ||||
assets: vec![], | assets: vec![], | ||||
content: "".to_string(), | content: "".to_string(), | ||||
@@ -85,49 +70,26 @@ impl Page { | |||||
/// Files without front matter or with invalid front matter are considered | /// Files without front matter or with invalid front matter are considered | ||||
/// erroneous | /// erroneous | ||||
pub fn parse(file_path: &Path, content: &str, config: &Config) -> Result<Page> { | pub fn parse(file_path: &Path, content: &str, config: &Config) -> Result<Page> { | ||||
// 1. separate front matter from content | |||||
let (meta, content) = split_page_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(); | |||||
let mut page = Page::new(file_path, meta); | |||||
page.raw_content = content; | page.raw_content = content; | ||||
let path = Path::new(file_path); | |||||
page.file_name = path.file_stem().unwrap().to_string_lossy().to_string(); | |||||
page.slug = { | page.slug = { | ||||
if let Some(ref slug) = page.meta.slug { | if let Some(ref slug) = page.meta.slug { | ||||
slug.trim().to_string() | slug.trim().to_string() | ||||
} else { | } else { | ||||
slugify(page.file_name.clone()) | |||||
slugify(page.file.name.clone()) | |||||
} | } | ||||
}; | }; | ||||
page.components = find_content_components(&page.file_path); | |||||
page.relative_path = format!("{}/{}.md", page.components.join("/"), page.file_name); | |||||
// 4. Find sections | |||||
// Pages with custom urls exists outside of sections | |||||
let mut path_set = false; | |||||
if let Some(ref u) = page.meta.url { | if let Some(ref u) = page.meta.url { | ||||
page.path = u.trim().to_string(); | page.path = u.trim().to_string(); | ||||
path_set = true; | |||||
} | |||||
if !page.components.is_empty() { | |||||
// If we have a folder with an asset, don't consider it as a component | |||||
if page.file_name == "index" { | |||||
page.components.pop(); | |||||
// also set parent_path to grandparent instead | |||||
page.parent_path = page.parent_path.parent().unwrap().to_path_buf(); | |||||
} | |||||
if !path_set { | |||||
// Don't add a trailing slash to sections | |||||
page.path = format!("{}/{}", page.components.join("/"), page.slug); | |||||
} | |||||
} else if !path_set { | |||||
page.path = page.slug.clone(); | |||||
} else { | |||||
page.path = if page.file.components.is_empty() { | |||||
page.slug.clone() | |||||
} else { | |||||
format!("{}/{}", page.file.components.join("/"), page.slug) | |||||
}; | |||||
} | } | ||||
page.permalink = config.make_permalink(&page.path); | page.permalink = config.make_permalink(&page.path); | ||||
Ok(page) | Ok(page) | ||||
@@ -140,7 +102,7 @@ impl Page { | |||||
let mut page = Page::parse(path, &content, config)?; | let mut page = Page::parse(path, &content, config)?; | ||||
page.assets = find_related_assets(path.parent().unwrap()); | page.assets = find_related_assets(path.parent().unwrap()); | ||||
if !page.assets.is_empty() && page.file_name != "index" { | |||||
if !page.assets.is_empty() && page.file.name != "index" { | |||||
bail!("Page `{}` has assets ({:?}) but is not named index.md", path.display(), page.assets); | bail!("Page `{}` has assets ({:?}) but is not named index.md", path.display(), page.assets); | ||||
} | } | ||||
@@ -177,19 +139,15 @@ impl Page { | |||||
context.add("current_path", &self.path); | context.add("current_path", &self.path); | ||||
tera.render(&tpl_name, &context) | tera.render(&tpl_name, &context) | ||||
.chain_err(|| format!("Failed to render page '{}'", self.file_path.display())) | |||||
.chain_err(|| format!("Failed to render page '{}'", self.file.path.display())) | |||||
} | } | ||||
} | } | ||||
impl Default for Page { | impl Default for Page { | ||||
fn default() -> Page { | fn default() -> Page { | ||||
Page { | Page { | ||||
file: FileInfo::default(), | |||||
meta: PageFrontMatter::default(), | 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(), | raw_content: "".to_string(), | ||||
assets: vec![], | assets: vec![], | ||||
content: "".to_string(), | content: "".to_string(), | ||||
@@ -352,7 +310,7 @@ Hello world | |||||
); | ); | ||||
assert!(res.is_ok()); | assert!(res.is_ok()); | ||||
let page = res.unwrap(); | let page = res.unwrap(); | ||||
assert_eq!(page.parent_path, path.join("content").join("posts")); | |||||
assert_eq!(page.file.parent, path.join("content").join("posts")); | |||||
} | } | ||||
#[test] | #[test] | ||||
@@ -145,7 +145,7 @@ impl<'a> Paginator<'a> { | |||||
} | } | ||||
site.tera.render(&self.section.get_template_name(), &context) | site.tera.render(&self.section.get_template_name(), &context) | ||||
.chain_err(|| format!("Failed to render pager {} of section '{}'", pager.index, self.section.file_path.display())) | |||||
.chain_err(|| format!("Failed to render pager {} of section '{}'", pager.index, self.section.file.path.display())) | |||||
} | } | ||||
} | } | ||||
@@ -166,7 +166,7 @@ mod tests { | |||||
if !is_index { | if !is_index { | ||||
s.path = "posts".to_string(); | s.path = "posts".to_string(); | ||||
s.permalink = "https://vincent.is/posts".to_string(); | s.permalink = "https://vincent.is/posts".to_string(); | ||||
s.components = vec!["posts".to_string()]; | |||||
s.file.components = vec!["posts".to_string()]; | |||||
} else { | } else { | ||||
s.permalink = "https://vincent.is".to_string(); | s.permalink = "https://vincent.is".to_string(); | ||||
} | } | ||||
@@ -11,21 +11,15 @@ use errors::{Result, ResultExt}; | |||||
use utils::{read_file}; | use utils::{read_file}; | ||||
use markdown::markdown_to_html; | use markdown::markdown_to_html; | ||||
use content::Page; | use content::Page; | ||||
use content::utils::find_content_components; | |||||
use content::file_info::FileInfo; | |||||
#[derive(Clone, Debug, PartialEq)] | #[derive(Clone, Debug, PartialEq)] | ||||
pub struct Section { | pub struct Section { | ||||
/// All info about the actual file | |||||
pub file: FileInfo, | |||||
/// The front matter meta-data | /// The front matter meta-data | ||||
pub meta: SectionFrontMatter, | pub meta: SectionFrontMatter, | ||||
/// The _index.md full path | |||||
pub file_path: PathBuf, | |||||
/// The .md path, starting from the content directory, with / slashes | |||||
pub relative_path: String, | |||||
/// Path of the directory containing the _index.md file | |||||
pub parent_path: PathBuf, | |||||
/// The folder names from `content` to this section file | |||||
pub components: Vec<String>, | |||||
/// The URL path of the page | /// The URL path of the page | ||||
pub path: String, | pub path: String, | ||||
/// The full URL for that page | /// The full URL for that page | ||||
@@ -47,11 +41,8 @@ impl Section { | |||||
let file_path = file_path.as_ref(); | let file_path = file_path.as_ref(); | ||||
Section { | Section { | ||||
file: FileInfo::new_section(file_path), | |||||
meta: meta, | meta: meta, | ||||
file_path: file_path.to_path_buf(), | |||||
relative_path: "".to_string(), | |||||
parent_path: file_path.parent().unwrap().to_path_buf(), | |||||
components: vec![], | |||||
path: "".to_string(), | path: "".to_string(), | ||||
permalink: "".to_string(), | permalink: "".to_string(), | ||||
raw_content: "".to_string(), | raw_content: "".to_string(), | ||||
@@ -66,16 +57,8 @@ impl Section { | |||||
let (meta, content) = split_section_content(file_path, content)?; | let (meta, content) = split_section_content(file_path, content)?; | ||||
let mut section = Section::new(file_path, meta); | let mut section = Section::new(file_path, meta); | ||||
section.raw_content = content.clone(); | section.raw_content = content.clone(); | ||||
section.components = find_content_components(§ion.file_path); | |||||
section.path = section.components.join("/"); | |||||
section.path = section.file.components.join("/"); | |||||
section.permalink = config.make_permalink(§ion.path); | section.permalink = config.make_permalink(§ion.path); | ||||
if section.components.is_empty() { | |||||
// the index one | |||||
section.relative_path = "_index.md".to_string(); | |||||
} else { | |||||
section.relative_path = format!("{}/_index.md", section.components.join("/")); | |||||
} | |||||
Ok(section) | Ok(section) | ||||
} | } | ||||
@@ -120,46 +103,36 @@ impl Section { | |||||
} | } | ||||
tera.render(&tpl_name, &context) | tera.render(&tpl_name, &context) | ||||
.chain_err(|| format!("Failed to render section '{}'", self.file_path.display())) | |||||
.chain_err(|| format!("Failed to render section '{}'", self.file.path.display())) | |||||
} | } | ||||
/// Is this the index section? | /// Is this the index section? | ||||
pub fn is_index(&self) -> bool { | pub fn is_index(&self) -> bool { | ||||
self.components.is_empty() | |||||
self.file.components.is_empty() | |||||
} | } | ||||
/// Returns all the paths for the pages belonging to that section | |||||
/// Returns all the paths of the pages belonging to that section | |||||
pub fn all_pages_path(&self) -> Vec<PathBuf> { | pub fn all_pages_path(&self) -> Vec<PathBuf> { | ||||
let mut paths = vec![]; | let mut paths = vec![]; | ||||
paths.extend(self.pages.iter().map(|p| p.file_path.clone())); | |||||
paths.extend(self.ignored_pages.iter().map(|p| p.file_path.clone())); | |||||
paths.extend(self.pages.iter().map(|p| p.file.path.clone())); | |||||
paths.extend(self.ignored_pages.iter().map(|p| p.file.path.clone())); | |||||
paths | paths | ||||
} | } | ||||
/// Whether the page given belongs to that section | /// Whether the page given belongs to that section | ||||
pub fn is_child_page(&self, page: &Page) -> bool { | pub fn is_child_page(&self, page: &Page) -> bool { | ||||
for p in &self.pages { | |||||
if p.file_path == page.file_path { | |||||
return true; | |||||
} | |||||
} | |||||
for p in &self.ignored_pages { | |||||
if p.file_path == page.file_path { | |||||
return true; | |||||
} | |||||
} | |||||
false | |||||
self.all_pages_path().contains(&page.file.path) | |||||
} | } | ||||
} | } | ||||
impl ser::Serialize for Section { | impl ser::Serialize for Section { | ||||
fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error> where S: ser::Serializer { | fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error> where S: ser::Serializer { | ||||
let mut state = serializer.serialize_struct("section", 7)?; | |||||
let mut state = serializer.serialize_struct("section", 9)?; | |||||
state.serialize_field("content", &self.content)?; | state.serialize_field("content", &self.content)?; | ||||
state.serialize_field("permalink", &self.permalink)?; | |||||
state.serialize_field("title", &self.meta.title)?; | state.serialize_field("title", &self.meta.title)?; | ||||
state.serialize_field("description", &self.meta.description)?; | state.serialize_field("description", &self.meta.description)?; | ||||
state.serialize_field("extra", &self.meta.extra)?; | |||||
state.serialize_field("path", &format!("/{}", self.path))?; | state.serialize_field("path", &format!("/{}", self.path))?; | ||||
state.serialize_field("permalink", &self.permalink)?; | state.serialize_field("permalink", &self.permalink)?; | ||||
state.serialize_field("pages", &self.pages)?; | state.serialize_field("pages", &self.pages)?; | ||||
@@ -168,15 +141,12 @@ impl ser::Serialize for Section { | |||||
} | } | ||||
} | } | ||||
/// Used to create a default index section if there is no _index.md in the root content directory | |||||
impl Default for Section { | 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 { | fn default() -> Section { | ||||
Section { | Section { | ||||
file: FileInfo::default(), | |||||
meta: SectionFrontMatter::default(), | meta: SectionFrontMatter::default(), | ||||
file_path: PathBuf::new(), | |||||
relative_path: "".to_string(), | |||||
parent_path: PathBuf::new(), | |||||
components: vec![], | |||||
path: "".to_string(), | path: "".to_string(), | ||||
permalink: "".to_string(), | permalink: "".to_string(), | ||||
raw_content: "".to_string(), | raw_content: "".to_string(), | ||||
@@ -81,13 +81,13 @@ mod tests { | |||||
fn create_page_with_date(date: &str) -> Page { | fn create_page_with_date(date: &str) -> Page { | ||||
let mut front_matter = PageFrontMatter::default(); | let mut front_matter = PageFrontMatter::default(); | ||||
front_matter.date = Some(date.to_string()); | front_matter.date = Some(date.to_string()); | ||||
Page::new(front_matter) | |||||
Page::new("content/hello.md", front_matter) | |||||
} | } | ||||
fn create_page_with_order(order: usize) -> Page { | fn create_page_with_order(order: usize) -> Page { | ||||
let mut front_matter = PageFrontMatter::default(); | let mut front_matter = PageFrontMatter::default(); | ||||
front_matter.order = Some(order); | front_matter.order = Some(order); | ||||
Page::new(front_matter) | |||||
Page::new("content/hello.md", front_matter) | |||||
} | } | ||||
#[test] | #[test] | ||||
@@ -32,37 +32,13 @@ pub fn get_reading_analytics(content: &str) -> (usize, usize) { | |||||
(word_count, (word_count / 200)) | (word_count, (word_count / 200)) | ||||
} | } | ||||
/// Takes a full path to a .md and returns only the components after the first `content` directory | |||||
/// Will not return the filename as last component | |||||
pub fn find_content_components<P: AsRef<Path>>(path: P) -> Vec<String> { | |||||
let path = path.as_ref(); | |||||
let mut is_in_content = false; | |||||
let mut components = vec![]; | |||||
for section in path.parent().unwrap().components() { | |||||
let component = section.as_ref().to_string_lossy(); | |||||
if is_in_content { | |||||
components.push(component.to_string()); | |||||
continue; | |||||
} | |||||
if component == "content" { | |||||
is_in_content = true; | |||||
} | |||||
} | |||||
components | |||||
} | |||||
#[cfg(test)] | #[cfg(test)] | ||||
mod tests { | mod tests { | ||||
use std::fs::File; | use std::fs::File; | ||||
use tempdir::TempDir; | use tempdir::TempDir; | ||||
use super::{find_related_assets, find_content_components, get_reading_analytics}; | |||||
use super::{find_related_assets, get_reading_analytics}; | |||||
#[test] | #[test] | ||||
fn can_find_related_assets() { | fn can_find_related_assets() { | ||||
@@ -97,10 +73,4 @@ mod tests { | |||||
assert_eq!(word_count, 2000); | assert_eq!(word_count, 2000); | ||||
assert_eq!(reading_time, 10); | assert_eq!(reading_time, 10); | ||||
} | } | ||||
#[test] | |||||
fn can_find_content_components() { | |||||
let res = find_content_components("/home/vincent/code/site/content/posts/tutorials/python.md"); | |||||
assert_eq!(res, ["posts".to_string(), "tutorials".to_string()]); | |||||
} | |||||
} | } |
@@ -93,7 +93,7 @@ impl Site { | |||||
pub fn get_ignored_pages(&self) -> Vec<PathBuf> { | pub fn get_ignored_pages(&self) -> Vec<PathBuf> { | ||||
self.sections | self.sections | ||||
.values() | .values() | ||||
.flat_map(|s| s.ignored_pages.iter().map(|p| p.file_path.clone())) | |||||
.flat_map(|s| s.ignored_pages.iter().map(|p| p.file.path.clone())) | |||||
.collect() | .collect() | ||||
} | } | ||||
@@ -107,7 +107,7 @@ impl Site { | |||||
} | } | ||||
for page in self.pages.values() { | for page in self.pages.values() { | ||||
if !pages_in_sections.contains(&page.file_path) { | |||||
if !pages_in_sections.contains(&page.file.path) { | |||||
orphans.push(page); | orphans.push(page); | ||||
} | } | ||||
} | } | ||||
@@ -146,8 +146,8 @@ impl Site { | |||||
self.add_page(path, false)?; | self.add_page(path, false)?; | ||||
} | } | ||||
} | } | ||||
// Insert a default index section so we don't need to create a _index.md to render | |||||
// the index page | |||||
// Insert a default index section if necessary so we don't need to create | |||||
// a _index.md to render the index page | |||||
let index_path = self.base_path.join("content").join("_index.md"); | let index_path = self.base_path.join("content").join("_index.md"); | ||||
if !self.sections.contains_key(&index_path) { | if !self.sections.contains_key(&index_path) { | ||||
let mut index_section = Section::default(); | let mut index_section = Section::default(); | ||||
@@ -178,8 +178,8 @@ impl Site { | |||||
/// Returns the previous page struct if there was one | /// Returns the previous page struct if there was one | ||||
pub fn add_page(&mut self, path: &Path, render: bool) -> Result<Option<Page>> { | pub fn add_page(&mut self, path: &Path, render: bool) -> Result<Option<Page>> { | ||||
let page = Page::from_file(&path, &self.config)?; | let page = Page::from_file(&path, &self.config)?; | ||||
self.permalinks.insert(page.relative_path.clone(), page.permalink.clone()); | |||||
let prev = self.pages.insert(page.file_path.clone(), page); | |||||
self.permalinks.insert(page.file.relative.clone(), page.permalink.clone()); | |||||
let prev = self.pages.insert(page.file.path.clone(), page); | |||||
if render { | if render { | ||||
let mut page = self.pages.get_mut(path).unwrap(); | let mut page = self.pages.get_mut(path).unwrap(); | ||||
@@ -192,11 +192,11 @@ impl Site { | |||||
/// Add a section to the site | /// Add a section to the site | ||||
/// The `render` parameter is used in the serve command, when rebuilding a page. | /// The `render` parameter is used in the serve command, when rebuilding a page. | ||||
/// If `true`, it will also render the markdown for that page | /// If `true`, it will also render the markdown for that page | ||||
/// Returns the previous page struct if there was one | |||||
/// Returns the previous section struct if there was one | |||||
pub fn add_section(&mut self, path: &Path, render: bool) -> Result<Option<Section>> { | pub fn add_section(&mut self, path: &Path, render: bool) -> Result<Option<Section>> { | ||||
let section = Section::from_file(path, &self.config)?; | let section = Section::from_file(path, &self.config)?; | ||||
self.permalinks.insert(section.relative_path.clone(), section.permalink.clone()); | |||||
let prev = self.sections.insert(section.file_path.clone(), section); | |||||
self.permalinks.insert(section.file.relative.clone(), section.permalink.clone()); | |||||
let prev = self.sections.insert(section.file.path.clone(), section); | |||||
if render { | if render { | ||||
let mut section = self.sections.get_mut(path).unwrap(); | let mut section = self.sections.get_mut(path).unwrap(); | ||||
@@ -211,7 +211,7 @@ impl Site { | |||||
pub fn populate_sections(&mut self) { | pub fn populate_sections(&mut self) { | ||||
let mut grandparent_paths = HashMap::new(); | let mut grandparent_paths = HashMap::new(); | ||||
for section in self.sections.values_mut() { | for section in self.sections.values_mut() { | ||||
if let Some(grand_parent) = section.parent_path.parent() { | |||||
if let Some(ref grand_parent) = section.file.grand_parent { | |||||
grandparent_paths.entry(grand_parent.to_path_buf()).or_insert_with(|| vec![]).push(section.clone()); | grandparent_paths.entry(grand_parent.to_path_buf()).or_insert_with(|| vec![]).push(section.clone()); | ||||
} | } | ||||
// Make sure the pages of a section are empty since we can call that many times on `serve` | // Make sure the pages of a section are empty since we can call that many times on `serve` | ||||
@@ -220,13 +220,14 @@ impl Site { | |||||
} | } | ||||
for page in self.pages.values() { | for page in self.pages.values() { | ||||
if self.sections.contains_key(&page.parent_path.join("_index.md")) { | |||||
self.sections.get_mut(&page.parent_path.join("_index.md")).unwrap().pages.push(page.clone()); | |||||
let parent_section_path = page.file.parent.join("_index.md"); | |||||
if self.sections.contains_key(&parent_section_path) { | |||||
self.sections.get_mut(&parent_section_path).unwrap().pages.push(page.clone()); | |||||
} | } | ||||
} | } | ||||
for section in self.sections.values_mut() { | for section in self.sections.values_mut() { | ||||
match grandparent_paths.get(§ion.parent_path) { | |||||
match grandparent_paths.get(§ion.file.parent) { | |||||
Some(paths) => section.subsections.extend(paths.clone()), | Some(paths) => section.subsections.extend(paths.clone()), | ||||
None => continue, | None => continue, | ||||
}; | }; | ||||
@@ -257,7 +258,7 @@ impl Site { | |||||
self.categories | self.categories | ||||
.entry(category.to_string()) | .entry(category.to_string()) | ||||
.or_insert_with(|| vec![]) | .or_insert_with(|| vec![]) | ||||
.push(page.file_path.clone()); | |||||
.push(page.file.path.clone()); | |||||
} | } | ||||
if let Some(ref tags) = page.meta.tags { | if let Some(ref tags) = page.meta.tags { | ||||
@@ -265,7 +266,7 @@ impl Site { | |||||
self.tags | self.tags | ||||
.entry(tag.to_string()) | .entry(tag.to_string()) | ||||
.or_insert_with(|| vec![]) | .or_insert_with(|| vec![]) | ||||
.push(page.file_path.clone()); | |||||
.push(page.file.path.clone()); | |||||
} | } | ||||
} | } | ||||
} | } | ||||
@@ -554,7 +555,7 @@ impl Site { | |||||
fn get_sections_map(&self) -> HashMap<String, Section> { | fn get_sections_map(&self) -> HashMap<String, Section> { | ||||
self.sections | self.sections | ||||
.values() | .values() | ||||
.map(|s| (s.components.join("/"), s.clone())) | |||||
.map(|s| (s.file.components.join("/"), s.clone())) | |||||
.collect() | .collect() | ||||
} | } | ||||
@@ -564,7 +565,7 @@ impl Site { | |||||
let public = self.output_path.clone(); | let public = self.output_path.clone(); | ||||
let mut output_path = public.to_path_buf(); | let mut output_path = public.to_path_buf(); | ||||
for component in §ion.components { | |||||
for component in §ion.file.components { | |||||
output_path.push(component); | output_path.push(component); | ||||
if !output_path.exists() { | if !output_path.exists() { | ||||
@@ -9,7 +9,7 @@ use content::Page; | |||||
pub fn make_get_page(all_pages: &HashMap<PathBuf, Page>) -> GlobalFn { | pub fn make_get_page(all_pages: &HashMap<PathBuf, Page>) -> GlobalFn { | ||||
let mut pages = HashMap::new(); | let mut pages = HashMap::new(); | ||||
for page in all_pages.values() { | for page in all_pages.values() { | ||||
pages.insert(page.relative_path.clone(), page.clone()); | |||||
pages.insert(page.file.relative.clone(), page.clone()); | |||||
} | } | ||||
Box::new(move |args| -> Result<Value> { | Box::new(move |args| -> Result<Value> { | ||||
@@ -12,7 +12,7 @@ use gutenberg::{Site}; | |||||
#[test] | #[test] | ||||
fn test_can_parse_site() { | |||||
fn can_parse_site() { | |||||
let mut path = env::current_dir().unwrap().to_path_buf(); | let mut path = env::current_dir().unwrap().to_path_buf(); | ||||
path.push("test_site"); | path.push("test_site"); | ||||
let mut site = Site::new(&path, "config.toml").unwrap(); | let mut site = Site::new(&path, "config.toml").unwrap(); | ||||
@@ -24,7 +24,7 @@ fn test_can_parse_site() { | |||||
// Make sure we remove all the pwd + content from the sections | // Make sure we remove all the pwd + content from the sections | ||||
let basic = &site.pages[&posts_path.join("simple.md")]; | let basic = &site.pages[&posts_path.join("simple.md")]; | ||||
assert_eq!(basic.components, vec!["posts".to_string()]); | |||||
assert_eq!(basic.file.components, vec!["posts".to_string()]); | |||||
// Make sure the page with a url doesn't have any sections | // Make sure the page with a url doesn't have any sections | ||||
let url_post = &site.pages[&posts_path.join("fixed-url.md")]; | let url_post = &site.pages[&posts_path.join("fixed-url.md")]; | ||||
@@ -32,7 +32,7 @@ fn test_can_parse_site() { | |||||
// Make sure the article in a folder with only asset doesn't get counted as a section | // Make sure the article in a folder with only asset doesn't get counted as a section | ||||
let asset_folder_post = &site.pages[&posts_path.join("with-assets").join("index.md")]; | let asset_folder_post = &site.pages[&posts_path.join("with-assets").join("index.md")]; | ||||
assert_eq!(asset_folder_post.components, vec!["posts".to_string()]); | |||||
assert_eq!(asset_folder_post.file.components, vec!["posts".to_string()]); | |||||
// That we have the right number of sections | // That we have the right number of sections | ||||
assert_eq!(site.sections.len(), 6); | assert_eq!(site.sections.len(), 6); | ||||
@@ -89,7 +89,7 @@ macro_rules! file_contains { | |||||
} | } | ||||
#[test] | #[test] | ||||
fn test_can_build_site_without_live_reload() { | |||||
fn can_build_site_without_live_reload() { | |||||
let mut path = env::current_dir().unwrap().to_path_buf(); | let mut path = env::current_dir().unwrap().to_path_buf(); | ||||
path.push("test_site"); | path.push("test_site"); | ||||
let mut site = Site::new(&path, "config.toml").unwrap(); | let mut site = Site::new(&path, "config.toml").unwrap(); | ||||
@@ -131,7 +131,7 @@ fn test_can_build_site_without_live_reload() { | |||||
} | } | ||||
#[test] | #[test] | ||||
fn test_can_build_site_with_live_reload() { | |||||
fn can_build_site_with_live_reload() { | |||||
let mut path = env::current_dir().unwrap().to_path_buf(); | let mut path = env::current_dir().unwrap().to_path_buf(); | ||||
path.push("test_site"); | path.push("test_site"); | ||||
let mut site = Site::new(&path, "config.toml").unwrap(); | let mut site = Site::new(&path, "config.toml").unwrap(); | ||||
@@ -169,7 +169,7 @@ fn test_can_build_site_with_live_reload() { | |||||
} | } | ||||
#[test] | #[test] | ||||
fn test_can_build_site_with_categories() { | |||||
fn can_build_site_with_categories() { | |||||
let mut path = env::current_dir().unwrap().to_path_buf(); | let mut path = env::current_dir().unwrap().to_path_buf(); | ||||
path.push("test_site"); | path.push("test_site"); | ||||
let mut site = Site::new(&path, "config.toml").unwrap(); | let mut site = Site::new(&path, "config.toml").unwrap(); | ||||
@@ -221,7 +221,7 @@ fn test_can_build_site_with_categories() { | |||||
} | } | ||||
#[test] | #[test] | ||||
fn test_can_build_site_with_tags() { | |||||
fn can_build_site_with_tags() { | |||||
let mut path = env::current_dir().unwrap().to_path_buf(); | let mut path = env::current_dir().unwrap().to_path_buf(); | ||||
path.push("test_site"); | path.push("test_site"); | ||||
let mut site = Site::new(&path, "config.toml").unwrap(); | let mut site = Site::new(&path, "config.toml").unwrap(); | ||||
@@ -273,7 +273,7 @@ fn test_can_build_site_with_tags() { | |||||
} | } | ||||
#[test] | #[test] | ||||
fn test_can_build_site_and_insert_anchor_links() { | |||||
fn can_build_site_and_insert_anchor_links() { | |||||
let mut path = env::current_dir().unwrap().to_path_buf(); | let mut path = env::current_dir().unwrap().to_path_buf(); | ||||
path.push("test_site"); | path.push("test_site"); | ||||
let mut site = Site::new(&path, "config.toml").unwrap(); | let mut site = Site::new(&path, "config.toml").unwrap(); | ||||
@@ -290,7 +290,7 @@ fn test_can_build_site_and_insert_anchor_links() { | |||||
} | } | ||||
#[test] | #[test] | ||||
fn test_can_build_site_with_pagination_for_section() { | |||||
fn can_build_site_with_pagination_for_section() { | |||||
let mut path = env::current_dir().unwrap().to_path_buf(); | let mut path = env::current_dir().unwrap().to_path_buf(); | ||||
path.push("test_site"); | path.push("test_site"); | ||||
let mut site = Site::new(&path, "config.toml").unwrap(); | let mut site = Site::new(&path, "config.toml").unwrap(); | ||||
@@ -349,7 +349,7 @@ fn test_can_build_site_with_pagination_for_section() { | |||||
} | } | ||||
#[test] | #[test] | ||||
fn test_can_build_site_with_pagination_for_index() { | |||||
fn can_build_site_with_pagination_for_index() { | |||||
let mut path = env::current_dir().unwrap().to_path_buf(); | let mut path = env::current_dir().unwrap().to_path_buf(); | ||||
path.push("test_site"); | path.push("test_site"); | ||||
let mut site = Site::new(&path, "config.toml").unwrap(); | let mut site = Site::new(&path, "config.toml").unwrap(); | ||||