Add a FileInfo struct to hold some common data about the files
This commit is contained in:
parent
d9ed7df118
commit
056bf55881
|
@ -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();
|
||||||
|
|
116
src/content/file_info.rs
Normal file
116
src/content/file_info.rs
Normal file
|
@ -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);
|
let mut page = Page::new(file_path, meta);
|
||||||
page.file_path = file_path.to_path_buf();
|
|
||||||
page.parent_path = page.file_path.parent().unwrap().to_path_buf();
|
|
||||||
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;
|
} else {
|
||||||
|
page.path = if page.file.components.is_empty() {
|
||||||
|
page.slug.clone()
|
||||||
|
} else {
|
||||||
|
format!("{}/{}", page.file.components.join("/"), page.slug)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
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.file.components.join("/");
|
||||||
section.path = section.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.pages.iter().map(|p| p.file.path.clone()));
|
||||||
paths.extend(self.ignored_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 {
|
self.all_pages_path().contains(&page.file.path)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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()]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
35
src/site.rs
35
src/site.rs
|
@ -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
|
// Insert a default index section if necessary so we don't need to create
|
||||||
// the index page
|
// 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());
|
self.permalinks.insert(page.file.relative.clone(), page.permalink.clone());
|
||||||
let prev = self.pages.insert(page.file_path.clone(), page);
|
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());
|
self.permalinks.insert(section.file.relative.clone(), section.permalink.clone());
|
||||||
let prev = self.sections.insert(section.file_path.clone(), section);
|
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")) {
|
let parent_section_path = page.file.parent.join("_index.md");
|
||||||
self.sections.get_mut(&page.parent_path.join("_index.md")).unwrap().pages.push(page.clone());
|
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();
|
||||||
|
|
Loading…
Reference in a new issue