section.subsections is now an array of pathsindex-subcmd
@@ -6,6 +6,8 @@ | |||||
- Gutenberg has changed name to REPLACE_ME! | - Gutenberg has changed name to REPLACE_ME! | ||||
- The `pagers` variable of Paginator objects has been removed | - The `pagers` variable of Paginator objects has been removed | ||||
- `section.subsections` is now an array of paths to be used with the `get_section` | |||||
Tera function | |||||
### Others | ### Others | ||||
- Update dependencies, fixing a few bugs with templates | - Update dependencies, fixing a few bugs with templates | ||||
@@ -25,6 +27,8 @@ | |||||
- RSS feed now takes all available articles by default instead of limiting to 10000 | - RSS feed now takes all available articles by default instead of limiting to 10000 | ||||
- `templates` directory is now optional | - `templates` directory is now optional | ||||
- Add Reason and F# syntax highlighting | - Add Reason and F# syntax highlighting | ||||
- Add `ancestors` to pages and sections pointing to the relative path of all ancestor | |||||
sections up to the index to be used with the `get_section` Tera function | |||||
## 0.4.2 (2018-09-03) | ## 0.4.2 (2018-09-03) | ||||
@@ -4,7 +4,7 @@ use std::path::{Path, PathBuf}; | |||||
use tera::{Tera, Context as TeraContext, Value, Map}; | use tera::{Tera, Context as TeraContext, Value, Map}; | ||||
use slug::slugify; | use slug::slugify; | ||||
use slotmap::{Key, DenseSlotMap}; | |||||
use slotmap::{Key}; | |||||
use errors::{Result, ResultExt}; | use errors::{Result, ResultExt}; | ||||
use config::Config; | use config::Config; | ||||
@@ -23,6 +23,7 @@ pub struct SerializingPage<'a> { | |||||
content: &'a str, | content: &'a str, | ||||
permalink: &'a str, | permalink: &'a str, | ||||
slug: &'a str, | slug: &'a str, | ||||
ancestors: Vec<String>, | |||||
title: &'a Option<String>, | title: &'a Option<String>, | ||||
description: &'a Option<String>, | description: &'a Option<String>, | ||||
date: &'a Option<String>, | date: &'a Option<String>, | ||||
@@ -47,7 +48,7 @@ pub struct SerializingPage<'a> { | |||||
impl<'a> SerializingPage<'a> { | impl<'a> SerializingPage<'a> { | ||||
/// Grabs all the data from a page, including sibling pages | /// Grabs all the data from a page, including sibling pages | ||||
pub fn from_page(page: &'a Page, pages: &'a DenseSlotMap<Page>) -> Self { | |||||
pub fn from_page(page: &'a Page, library: &'a Library) -> Self { | |||||
let mut year = None; | let mut year = None; | ||||
let mut month = None; | let mut month = None; | ||||
let mut day = None; | let mut day = None; | ||||
@@ -56,12 +57,15 @@ impl<'a> SerializingPage<'a> { | |||||
month = Some(d.1); | month = Some(d.1); | ||||
day = Some(d.2); | day = Some(d.2); | ||||
} | } | ||||
let lighter = page.lighter.map(|k| Box::new(SerializingPage::from_page_basic(pages.get(k).unwrap()))); | |||||
let heavier = page.heavier.map(|k| Box::new(SerializingPage::from_page_basic(pages.get(k).unwrap()))); | |||||
let earlier = page.earlier.map(|k| Box::new(SerializingPage::from_page_basic(pages.get(k).unwrap()))); | |||||
let later = page.later.map(|k| Box::new(SerializingPage::from_page_basic(pages.get(k).unwrap()))); | |||||
let pages = library.pages(); | |||||
let lighter = page.lighter.map(|k| Box::new(Self::from_page_basic(pages.get(k).unwrap(), Some(library)))); | |||||
let heavier = page.heavier.map(|k| Box::new(Self::from_page_basic(pages.get(k).unwrap(), Some(library)))); | |||||
let earlier = page.earlier.map(|k| Box::new(Self::from_page_basic(pages.get(k).unwrap(), Some(library)))); | |||||
let later = page.later.map(|k| Box::new(Self::from_page_basic(pages.get(k).unwrap(), Some(library)))); | |||||
let ancestors = page.ancestors.iter().map(|k| library.get_section_by_key(*k).file.relative.clone()).collect(); | |||||
SerializingPage { | SerializingPage { | ||||
ancestors, | |||||
content: &page.content, | content: &page.content, | ||||
permalink: &page.permalink, | permalink: &page.permalink, | ||||
slug: &page.slug, | slug: &page.slug, | ||||
@@ -89,7 +93,7 @@ impl<'a> SerializingPage<'a> { | |||||
} | } | ||||
/// Same as from_page but does not fill sibling pages | /// Same as from_page but does not fill sibling pages | ||||
pub fn from_page_basic(page: &'a Page) -> Self { | |||||
pub fn from_page_basic(page: &'a Page, library: Option<&'a Library>) -> Self { | |||||
let mut year = None; | let mut year = None; | ||||
let mut month = None; | let mut month = None; | ||||
let mut day = None; | let mut day = None; | ||||
@@ -98,8 +102,14 @@ impl<'a> SerializingPage<'a> { | |||||
month = Some(d.1); | month = Some(d.1); | ||||
day = Some(d.2); | day = Some(d.2); | ||||
} | } | ||||
let ancestors = if let Some(ref lib) = library { | |||||
page.ancestors.iter().map(|k| lib.get_section_by_key(*k).file.relative.clone()).collect() | |||||
} else { | |||||
vec![] | |||||
}; | |||||
SerializingPage { | SerializingPage { | ||||
ancestors, | |||||
content: &page.content, | content: &page.content, | ||||
permalink: &page.permalink, | permalink: &page.permalink, | ||||
slug: &page.slug, | slug: &page.slug, | ||||
@@ -133,6 +143,8 @@ pub struct Page { | |||||
pub file: FileInfo, | pub file: FileInfo, | ||||
/// The front matter meta-data | /// The front matter meta-data | ||||
pub meta: PageFrontMatter, | pub meta: PageFrontMatter, | ||||
/// The list of parent sections | |||||
pub ancestors: Vec<Key>, | |||||
/// 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 | ||||
@@ -177,6 +189,7 @@ impl Page { | |||||
Page { | Page { | ||||
file: FileInfo::new_page(file_path), | file: FileInfo::new_page(file_path), | ||||
meta, | meta, | ||||
ancestors: vec![], | |||||
raw_content: "".to_string(), | raw_content: "".to_string(), | ||||
assets: vec![], | assets: vec![], | ||||
content: "".to_string(), | content: "".to_string(), | ||||
@@ -297,7 +310,7 @@ impl Page { | |||||
anchor_insert, | anchor_insert, | ||||
); | ); | ||||
context.tera_context.insert("page", &SerializingPage::from_page_basic(self)); | |||||
context.tera_context.insert("page", &SerializingPage::from_page_basic(self, None)); | |||||
let res = render_content(&self.raw_content, &context) | let res = render_content(&self.raw_content, &context) | ||||
.chain_err(|| format!("Failed to render content of {}", self.file.path.display()))?; | .chain_err(|| format!("Failed to render content of {}", self.file.path.display()))?; | ||||
@@ -320,7 +333,7 @@ impl Page { | |||||
context.insert("config", config); | context.insert("config", config); | ||||
context.insert("current_url", &self.permalink); | context.insert("current_url", &self.permalink); | ||||
context.insert("current_path", &self.path); | context.insert("current_path", &self.path); | ||||
context.insert("page", &self.to_serialized(library.pages())); | |||||
context.insert("page", &self.to_serialized(library)); | |||||
render_template(&tpl_name, tera, &context, &config.theme) | render_template(&tpl_name, tera, &context, &config.theme) | ||||
.chain_err(|| format!("Failed to render page '{}'", self.file.path.display())) | .chain_err(|| format!("Failed to render page '{}'", self.file.path.display())) | ||||
@@ -335,12 +348,12 @@ impl Page { | |||||
.collect() | .collect() | ||||
} | } | ||||
pub fn to_serialized<'a>(&'a self, pages: &'a DenseSlotMap<Page>) -> SerializingPage<'a> { | |||||
SerializingPage::from_page(self, pages) | |||||
pub fn to_serialized<'a>(&'a self, library: &'a Library) -> SerializingPage<'a> { | |||||
SerializingPage::from_page(self, library) | |||||
} | } | ||||
pub fn to_serialized_basic(&self) -> SerializingPage { | |||||
SerializingPage::from_page_basic(self) | |||||
pub fn to_serialized_basic<'a>(&'a self, library: &'a Library) -> SerializingPage<'a> { | |||||
SerializingPage::from_page_basic(self, Some(library)) | |||||
} | } | ||||
} | } | ||||
@@ -349,6 +362,7 @@ impl Default for Page { | |||||
Page { | Page { | ||||
file: FileInfo::default(), | file: FileInfo::default(), | ||||
meta: PageFrontMatter::default(), | meta: PageFrontMatter::default(), | ||||
ancestors: vec![], | |||||
raw_content: "".to_string(), | raw_content: "".to_string(), | ||||
assets: vec![], | assets: vec![], | ||||
content: "".to_string(), | content: "".to_string(), | ||||
@@ -21,6 +21,7 @@ use library::Library; | |||||
pub struct SerializingSection<'a> { | pub struct SerializingSection<'a> { | ||||
content: &'a str, | content: &'a str, | ||||
permalink: &'a str, | permalink: &'a str, | ||||
ancestors: Vec<String>, | |||||
title: &'a Option<String>, | title: &'a Option<String>, | ||||
description: &'a Option<String>, | description: &'a Option<String>, | ||||
extra: &'a HashMap<String, Value>, | extra: &'a HashMap<String, Value>, | ||||
@@ -31,7 +32,7 @@ pub struct SerializingSection<'a> { | |||||
toc: &'a [Header], | toc: &'a [Header], | ||||
assets: Vec<String>, | assets: Vec<String>, | ||||
pages: Vec<SerializingPage<'a>>, | pages: Vec<SerializingPage<'a>>, | ||||
subsections: Vec<SerializingSection<'a>>, | |||||
subsections: Vec<&'a str>, | |||||
} | } | ||||
impl<'a> SerializingSection<'a> { | impl<'a> SerializingSection<'a> { | ||||
@@ -40,14 +41,17 @@ impl<'a> SerializingSection<'a> { | |||||
let mut subsections = Vec::with_capacity(section.subsections.len()); | let mut subsections = Vec::with_capacity(section.subsections.len()); | ||||
for k in §ion.pages { | for k in §ion.pages { | ||||
pages.push(library.get_page_by_key(*k).to_serialized(library.pages())); | |||||
pages.push(library.get_page_by_key(*k).to_serialized(library)); | |||||
} | } | ||||
for k in §ion.subsections { | for k in §ion.subsections { | ||||
subsections.push(library.get_section_by_key(*k).to_serialized(library)); | |||||
subsections.push(library.get_section_path_by_key(*k)); | |||||
} | } | ||||
let ancestors = section.ancestors.iter().map(|k| library.get_section_by_key(*k).file.relative.clone()).collect(); | |||||
SerializingSection { | SerializingSection { | ||||
ancestors, | |||||
content: §ion.content, | content: §ion.content, | ||||
permalink: §ion.permalink, | permalink: §ion.permalink, | ||||
title: §ion.meta.title, | title: §ion.meta.title, | ||||
@@ -65,8 +69,15 @@ impl<'a> SerializingSection<'a> { | |||||
} | } | ||||
/// Same as from_section but doesn't fetch pages and sections | /// Same as from_section but doesn't fetch pages and sections | ||||
pub fn from_section_basic(section: &'a Section) -> Self { | |||||
pub fn from_section_basic(section: &'a Section, library: Option<&'a Library>) -> Self { | |||||
let ancestors = if let Some(ref lib) = library { | |||||
section.ancestors.iter().map(|k| lib.get_section_by_key(*k).file.relative.clone()).collect() | |||||
} else { | |||||
vec![] | |||||
}; | |||||
SerializingSection { | SerializingSection { | ||||
ancestors, | |||||
content: §ion.content, | content: §ion.content, | ||||
permalink: §ion.permalink, | permalink: §ion.permalink, | ||||
title: §ion.meta.title, | title: §ion.meta.title, | ||||
@@ -106,6 +117,8 @@ pub struct Section { | |||||
pub pages: Vec<Key>, | pub pages: Vec<Key>, | ||||
/// All pages that cannot be sorted in this section | /// All pages that cannot be sorted in this section | ||||
pub ignored_pages: Vec<Key>, | pub ignored_pages: Vec<Key>, | ||||
/// The list of parent sections | |||||
pub ancestors: Vec<Key>, | |||||
/// All direct subsections | /// All direct subsections | ||||
pub subsections: Vec<Key>, | pub subsections: Vec<Key>, | ||||
/// Toc made from the headers of the markdown file | /// Toc made from the headers of the markdown file | ||||
@@ -124,6 +137,7 @@ impl Section { | |||||
Section { | Section { | ||||
file: FileInfo::new_section(file_path), | file: FileInfo::new_section(file_path), | ||||
meta, | meta, | ||||
ancestors: vec![], | |||||
path: "".to_string(), | path: "".to_string(), | ||||
components: vec![], | components: vec![], | ||||
permalink: "".to_string(), | permalink: "".to_string(), | ||||
@@ -214,7 +228,7 @@ impl Section { | |||||
self.meta.insert_anchor_links, | self.meta.insert_anchor_links, | ||||
); | ); | ||||
context.tera_context.insert("section", &SerializingSection::from_section_basic(self)); | |||||
context.tera_context.insert("section", &SerializingSection::from_section_basic(self, None)); | |||||
let res = render_content(&self.raw_content, &context) | let res = render_content(&self.raw_content, &context) | ||||
.chain_err(|| format!("Failed to render content of {}", self.file.path.display()))?; | .chain_err(|| format!("Failed to render content of {}", self.file.path.display()))?; | ||||
@@ -254,6 +268,10 @@ impl Section { | |||||
pub fn to_serialized<'a>(&'a self, library: &'a Library) -> SerializingSection<'a> { | pub fn to_serialized<'a>(&'a self, library: &'a Library) -> SerializingSection<'a> { | ||||
SerializingSection::from_section(self, library) | SerializingSection::from_section(self, library) | ||||
} | } | ||||
pub fn to_serialized_basic<'a>(&'a self, library: &'a Library) -> SerializingSection<'a> { | |||||
SerializingSection::from_section_basic(self, Some(library)) | |||||
} | |||||
} | } | ||||
/// Used to create a default index section if there is no _index.md in the root content directory | /// Used to create a default index section if there is no _index.md in the root content directory | ||||
@@ -262,6 +280,7 @@ impl Default for Section { | |||||
Section { | Section { | ||||
file: FileInfo::default(), | file: FileInfo::default(), | ||||
meta: SectionFrontMatter::default(), | meta: SectionFrontMatter::default(), | ||||
ancestors: vec![], | |||||
path: "".to_string(), | path: "".to_string(), | ||||
components: vec![], | components: vec![], | ||||
permalink: "".to_string(), | permalink: "".to_string(), | ||||
@@ -25,7 +25,7 @@ pub struct Library { | |||||
/// A mapping path -> key for pages so we can easily get their key | /// A mapping path -> key for pages so we can easily get their key | ||||
paths_to_pages: HashMap<PathBuf, Key>, | paths_to_pages: HashMap<PathBuf, Key>, | ||||
/// A mapping path -> key for sections so we can easily get their key | /// A mapping path -> key for sections so we can easily get their key | ||||
paths_to_sections: HashMap<PathBuf, Key>, | |||||
pub paths_to_sections: HashMap<PathBuf, Key>, | |||||
} | } | ||||
impl Library { | impl Library { | ||||
@@ -81,24 +81,58 @@ impl Library { | |||||
/// Find out the direct subsections of each subsection if there are some | /// Find out the direct subsections of each subsection if there are some | ||||
/// as well as the pages for each section | /// as well as the pages for each section | ||||
pub fn populate_sections(&mut self) { | pub fn populate_sections(&mut self) { | ||||
let mut grandparent_paths: HashMap<PathBuf, Vec<PathBuf>> = HashMap::new(); | |||||
let (root_path, index_path) = self.sections | |||||
.values() | |||||
.find(|s| s.is_index()) | |||||
.map(|s| (s.file.parent.clone(), s.file.path.clone())) | |||||
.unwrap(); | |||||
let root_key = self.paths_to_sections[&index_path]; | |||||
// We are going to get both the ancestors and grandparents for each section in one go | |||||
let mut ancestors: HashMap<PathBuf, Vec<_>> = HashMap::new(); | |||||
let mut subsections: HashMap<PathBuf, Vec<_>> = HashMap::new(); | |||||
for section in self.sections.values_mut() { | for section in self.sections.values_mut() { | ||||
// Make sure the pages of a section are empty since we can call that many times on `serve` | |||||
section.pages = vec![]; | |||||
section.ignored_pages = vec![]; | |||||
if let Some(ref grand_parent) = section.file.grand_parent { | if let Some(ref grand_parent) = section.file.grand_parent { | ||||
grandparent_paths | |||||
.entry(grand_parent.to_path_buf()) | |||||
subsections | |||||
.entry(grand_parent.join("_index.md")) | |||||
.or_insert_with(|| vec![]) | .or_insert_with(|| vec![]) | ||||
.push(section.file.path.clone()); | .push(section.file.path.clone()); | ||||
} | } | ||||
// Make sure the pages of a section are empty since we can call that many times on `serve` | |||||
section.pages = vec![]; | |||||
section.ignored_pages = vec![]; | |||||
// Index has no ancestors, no need to go through it | |||||
if section.is_index() { | |||||
ancestors.insert(section.file.path.clone(), vec![]); | |||||
continue; | |||||
} | |||||
let mut path = root_path.clone(); | |||||
// Index section is the first ancestor of every single section | |||||
let mut parents = vec![root_key.clone()]; | |||||
for component in §ion.file.components { | |||||
path = path.join(component); | |||||
// Skip itself | |||||
if path == section.file.parent { | |||||
continue; | |||||
} | |||||
if let Some(section_key) = self.paths_to_sections.get(&path.join("_index.md")) { | |||||
parents.push(*section_key); | |||||
} | |||||
} | |||||
ancestors.insert(section.file.path.clone(), parents); | |||||
} | } | ||||
for (key, page) in &mut self.pages { | for (key, page) in &mut self.pages { | ||||
let parent_section_path = page.file.parent.join("_index.md"); | let parent_section_path = page.file.parent.join("_index.md"); | ||||
if let Some(section_key) = self.paths_to_sections.get(&parent_section_path) { | if let Some(section_key) = self.paths_to_sections.get(&parent_section_path) { | ||||
self.sections.get_mut(*section_key).unwrap().pages.push(key); | self.sections.get_mut(*section_key).unwrap().pages.push(key); | ||||
page.ancestors = ancestors.get(&parent_section_path).cloned().unwrap_or_else(|| vec![]); | |||||
// Don't forget to push the actual parent | |||||
page.ancestors.push(*section_key); | |||||
} | } | ||||
} | } | ||||
@@ -109,15 +143,14 @@ impl Library { | |||||
for (key, section) in &self.sections { | for (key, section) in &self.sections { | ||||
sections_weight.insert(key, section.meta.weight); | sections_weight.insert(key, section.meta.weight); | ||||
} | } | ||||
for section in self.sections.values_mut() { | for section in self.sections.values_mut() { | ||||
if let Some(paths) = grandparent_paths.get(§ion.file.parent) { | |||||
section.subsections = paths | |||||
.iter() | |||||
.map(|p| sections[p]) | |||||
.collect::<Vec<_>>(); | |||||
section.subsections | |||||
.sort_by(|a, b| sections_weight[a].cmp(§ions_weight[b])); | |||||
if let Some(ref children) = subsections.get(§ion.file.path) { | |||||
let mut children: Vec<_> = children.iter().map(|p| sections[p]).collect(); | |||||
children.sort_by(|a, b| sections_weight[a].cmp(§ions_weight[b])); | |||||
section.subsections = children; | |||||
} | } | ||||
section.ancestors = ancestors.get(§ion.file.path).cloned().unwrap_or_else(|| vec![]); | |||||
} | } | ||||
} | } | ||||
@@ -219,6 +252,11 @@ impl Library { | |||||
None | None | ||||
} | } | ||||
/// Only used in tests | |||||
pub fn get_section_key(&self, path: &PathBuf) -> Option<&Key> { | |||||
self.paths_to_sections.get(path) | |||||
} | |||||
pub fn get_section(&self, path: &PathBuf) -> Option<&Section> { | pub fn get_section(&self, path: &PathBuf) -> Option<&Section> { | ||||
self.sections.get(self.paths_to_sections.get(path).cloned().unwrap_or_default()) | self.sections.get(self.paths_to_sections.get(path).cloned().unwrap_or_default()) | ||||
} | } | ||||
@@ -231,6 +269,14 @@ impl Library { | |||||
self.sections.get(key).unwrap() | self.sections.get(key).unwrap() | ||||
} | } | ||||
pub fn get_section_mut_by_key(&mut self, key: Key) -> &mut Section { | |||||
self.sections.get_mut(key).unwrap() | |||||
} | |||||
pub fn get_section_path_by_key(&self, key: Key) -> &str { | |||||
&self.get_section_by_key(key).file.relative | |||||
} | |||||
pub fn get_page(&self, path: &PathBuf) -> Option<&Page> { | pub fn get_page(&self, path: &PathBuf) -> Option<&Page> { | ||||
self.pages.get(self.paths_to_pages.get(path).cloned().unwrap_or_default()) | self.pages.get(self.paths_to_pages.get(path).cloned().unwrap_or_default()) | ||||
} | } | ||||
@@ -241,7 +287,6 @@ impl Library { | |||||
pub fn remove_section(&mut self, path: &PathBuf) -> Option<Section> { | pub fn remove_section(&mut self, path: &PathBuf) -> Option<Section> { | ||||
if let Some(k) = self.paths_to_sections.remove(path) { | if let Some(k) = self.paths_to_sections.remove(path) { | ||||
// TODO: delete section from parent subsection if there is one | |||||
self.sections.remove(k) | self.sections.remove(k) | ||||
} else { | } else { | ||||
None | None | ||||
@@ -250,7 +295,6 @@ impl Library { | |||||
pub fn remove_page(&mut self, path: &PathBuf) -> Option<Page> { | pub fn remove_page(&mut self, path: &PathBuf) -> Option<Page> { | ||||
if let Some(k) = self.paths_to_pages.remove(path) { | if let Some(k) = self.paths_to_pages.remove(path) { | ||||
// TODO: delete page from all parent sections | |||||
self.pages.remove(k) | self.pages.remove(k) | ||||
} else { | } else { | ||||
None | None | ||||
@@ -108,7 +108,7 @@ impl<'a> Paginator<'a> { | |||||
for key in self.all_pages { | for key in self.all_pages { | ||||
let page = library.get_page_by_key(*key); | let page = library.get_page_by_key(*key); | ||||
current_page.push(page.to_serialized_basic()); | |||||
current_page.push(page.to_serialized_basic(library)); | |||||
if current_page.len() == self.paginate_by { | if current_page.len() == self.paginate_by { | ||||
pages.push(current_page); | pages.push(current_page); | ||||
@@ -188,12 +188,12 @@ impl<'a> Paginator<'a> { | |||||
paginator | paginator | ||||
} | } | ||||
pub fn render_pager(&self, pager: &Pager, config: &Config, tera: &Tera) -> Result<String> { | |||||
pub fn render_pager(&self, pager: &Pager, config: &Config, tera: &Tera, library: &Library) -> Result<String> { | |||||
let mut context = Context::new(); | let mut context = Context::new(); | ||||
context.insert("config", &config); | context.insert("config", &config); | ||||
let template_name = match self.root { | let template_name = match self.root { | ||||
PaginationRoot::Section(s) => { | PaginationRoot::Section(s) => { | ||||
context.insert("section", &SerializingSection::from_section_basic(s)); | |||||
context.insert("section", &SerializingSection::from_section_basic(s, Some(library))); | |||||
s.get_template_name() | s.get_template_name() | ||||
} | } | ||||
PaginationRoot::Taxonomy(t) => { | PaginationRoot::Taxonomy(t) => { | ||||
@@ -26,7 +26,7 @@ impl<'a> SerializedTaxonomyItem<'a> { | |||||
for key in &item.pages { | for key in &item.pages { | ||||
let page = library.get_page_by_key(*key); | let page = library.get_page_by_key(*key); | ||||
pages.push(page.to_serialized_basic()); | |||||
pages.push(page.to_serialized_basic(library)); | |||||
} | } | ||||
SerializedTaxonomyItem { | SerializedTaxonomyItem { | ||||
@@ -125,6 +125,7 @@ fn handle_section_editing(site: &mut Site, path: &Path) -> Result<()> { | |||||
s.ignored_pages = prev.ignored_pages; | s.ignored_pages = prev.ignored_pages; | ||||
s.subsections = prev.subsections; | s.subsections = prev.subsections; | ||||
} | } | ||||
site.populate_sections(); | |||||
if site.library.get_section(&pathbuf).unwrap().meta == prev.meta { | if site.library.get_section(&pathbuf).unwrap().meta == prev.meta { | ||||
// Front matter didn't change, only content did | // Front matter didn't change, only content did | ||||
@@ -63,7 +63,7 @@ fn bench_render_paginated(b: &mut test::Bencher) { | |||||
let public = &tmp_dir.path().join("public"); | let public = &tmp_dir.path().join("public"); | ||||
site.set_output_path(&public); | site.set_output_path(&public); | ||||
let section = site.library.sections_values()[0]; | let section = site.library.sections_values()[0]; | ||||
let paginator = Paginator::from_section(§ion, site.library.pages()); | |||||
let paginator = Paginator::from_section(§ion, &site.library); | |||||
b.iter(|| site.render_paginated(public, &paginator)); | b.iter(|| site.render_paginated(public, &paginator)); | ||||
} | } | ||||
@@ -240,10 +240,8 @@ impl Site { | |||||
} | } | ||||
self.register_early_global_fns(); | self.register_early_global_fns(); | ||||
self.render_markdown()?; | |||||
self.populate_sections(); | self.populate_sections(); | ||||
// self.library.cache_all_pages(); | |||||
// self.library.cache_all_sections(); | |||||
self.render_markdown()?; | |||||
self.populate_taxonomies()?; | self.populate_taxonomies()?; | ||||
self.register_tera_global_fns(); | self.register_tera_global_fns(); | ||||
@@ -737,7 +735,7 @@ impl Site { | |||||
let p = pages | let p = pages | ||||
.iter() | .iter() | ||||
.take(num_entries) | .take(num_entries) | ||||
.map(|x| x.to_serialized_basic()) | |||||
.map(|x| x.to_serialized_basic(&self.library)) | |||||
.collect::<Vec<_>>(); | .collect::<Vec<_>>(); | ||||
context.insert("pages", &p); | context.insert("pages", &p); | ||||
@@ -858,7 +856,7 @@ impl Site { | |||||
.map(|pager| { | .map(|pager| { | ||||
let page_path = folder_path.join(&format!("{}", pager.index)); | let page_path = folder_path.join(&format!("{}", pager.index)); | ||||
create_directory(&page_path)?; | create_directory(&page_path)?; | ||||
let output = paginator.render_pager(pager, &self.config, &self.tera)?; | |||||
let output = paginator.render_pager(pager, &self.config, &self.tera, &self.library)?; | |||||
if pager.index > 1 { | if pager.index > 1 { | ||||
create_file(&page_path.join("index.html"), &self.inject_livereload(output))?; | create_file(&page_path.join("index.html"), &self.inject_livereload(output))?; | ||||
} else { | } else { | ||||
@@ -22,10 +22,6 @@ fn can_parse_site() { | |||||
assert_eq!(site.library.pages().len(), 15); | assert_eq!(site.library.pages().len(), 15); | ||||
let posts_path = path.join("content").join("posts"); | let posts_path = path.join("content").join("posts"); | ||||
// Make sure we remove all the pwd + content from the sections | |||||
let basic = site.library.get_page(&posts_path.join("simple.md")).unwrap(); | |||||
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.library.get_page(&posts_path.join("fixed-url.md")).unwrap(); | let url_post = site.library.get_page(&posts_path.join("fixed-url.md")).unwrap(); | ||||
assert_eq!(url_post.path, "a-fixed-url/"); | assert_eq!(url_post.path, "a-fixed-url/"); | ||||
@@ -41,10 +37,23 @@ fn can_parse_site() { | |||||
let index_section = site.library.get_section(&path.join("content").join("_index.md")).unwrap(); | let index_section = site.library.get_section(&path.join("content").join("_index.md")).unwrap(); | ||||
assert_eq!(index_section.subsections.len(), 3); | assert_eq!(index_section.subsections.len(), 3); | ||||
assert_eq!(index_section.pages.len(), 1); | assert_eq!(index_section.pages.len(), 1); | ||||
assert!(index_section.ancestors.is_empty()); | |||||
let posts_section = site.library.get_section(&posts_path.join("_index.md")).unwrap(); | let posts_section = site.library.get_section(&posts_path.join("_index.md")).unwrap(); | ||||
assert_eq!(posts_section.subsections.len(), 1); | assert_eq!(posts_section.subsections.len(), 1); | ||||
assert_eq!(posts_section.pages.len(), 7); | assert_eq!(posts_section.pages.len(), 7); | ||||
assert_eq!(posts_section.ancestors, vec![*site.library.get_section_key(&index_section.file.path).unwrap()]); | |||||
// Make sure we remove all the pwd + content from the sections | |||||
let basic = site.library.get_page(&posts_path.join("simple.md")).unwrap(); | |||||
assert_eq!(basic.file.components, vec!["posts".to_string()]); | |||||
assert_eq!( | |||||
basic.ancestors, | |||||
vec![ | |||||
*site.library.get_section_key(&index_section.file.path).unwrap(), | |||||
*site.library.get_section_key(&posts_section.file.path).unwrap(), | |||||
] | |||||
); | |||||
let tutorials_section = site.library.get_section(&posts_path.join("tutorials").join("_index.md")).unwrap(); | let tutorials_section = site.library.get_section(&posts_path.join("tutorials").join("_index.md")).unwrap(); | ||||
assert_eq!(tutorials_section.subsections.len(), 2); | assert_eq!(tutorials_section.subsections.len(), 2); | ||||
@@ -57,6 +66,14 @@ fn can_parse_site() { | |||||
let devops_section = site.library.get_section(&posts_path.join("tutorials").join("devops").join("_index.md")).unwrap(); | let devops_section = site.library.get_section(&posts_path.join("tutorials").join("devops").join("_index.md")).unwrap(); | ||||
assert_eq!(devops_section.subsections.len(), 0); | assert_eq!(devops_section.subsections.len(), 0); | ||||
assert_eq!(devops_section.pages.len(), 2); | assert_eq!(devops_section.pages.len(), 2); | ||||
assert_eq!( | |||||
devops_section.ancestors, | |||||
vec![ | |||||
*site.library.get_section_key(&index_section.file.path).unwrap(), | |||||
*site.library.get_section_key(&posts_section.file.path).unwrap(), | |||||
*site.library.get_section_key(&tutorials_section.file.path).unwrap(), | |||||
] | |||||
); | |||||
let prog_section = site.library.get_section(&posts_path.join("tutorials").join("programming").join("_index.md")).unwrap(); | let prog_section = site.library.get_section(&posts_path.join("tutorials").join("programming").join("_index.md")).unwrap(); | ||||
assert_eq!(prog_section.subsections.len(), 0); | assert_eq!(prog_section.subsections.len(), 0); | ||||
@@ -56,7 +56,7 @@ pub fn make_get_page(library: &Library) -> GlobalFn { | |||||
for page in library.pages_values() { | for page in library.pages_values() { | ||||
pages.insert( | pages.insert( | ||||
page.file.relative.clone(), | page.file.relative.clone(), | ||||
to_value(library.get_page(&page.file.path).unwrap().to_serialized(library.pages())).unwrap(), | |||||
to_value(library.get_page(&page.file.path).unwrap().to_serialized(library)).unwrap(), | |||||
); | ); | ||||
} | } | ||||
@@ -75,11 +75,17 @@ pub fn make_get_page(library: &Library) -> GlobalFn { | |||||
pub fn make_get_section(library: &Library) -> GlobalFn { | pub fn make_get_section(library: &Library) -> GlobalFn { | ||||
let mut sections = HashMap::new(); | let mut sections = HashMap::new(); | ||||
let mut sections_basic = HashMap::new(); | |||||
for section in library.sections_values() { | for section in library.sections_values() { | ||||
sections.insert( | sections.insert( | ||||
section.file.relative.clone(), | section.file.relative.clone(), | ||||
to_value(library.get_section(§ion.file.path).unwrap().to_serialized(library)).unwrap(), | to_value(library.get_section(§ion.file.path).unwrap().to_serialized(library)).unwrap(), | ||||
); | ); | ||||
sections_basic.insert( | |||||
section.file.relative.clone(), | |||||
to_value(library.get_section(§ion.file.path).unwrap().to_serialized_basic(library)).unwrap(), | |||||
); | |||||
} | } | ||||
Box::new(move |args| -> Result<Value> { | Box::new(move |args| -> Result<Value> { | ||||
@@ -89,7 +95,19 @@ pub fn make_get_section(library: &Library) -> GlobalFn { | |||||
"`get_section` requires a `path` argument with a string value" | "`get_section` requires a `path` argument with a string value" | ||||
); | ); | ||||
match sections.get(&path) { | |||||
let metadata_only = args | |||||
.get("metadata_only") | |||||
.map_or(false, |c| { | |||||
from_value::<bool>(c.clone()).unwrap_or(false) | |||||
}); | |||||
let container = if metadata_only { | |||||
§ions_basic | |||||
} else { | |||||
§ions | |||||
}; | |||||
match container.get(&path) { | |||||
Some(p) => Ok(p.clone()), | Some(p) => Ok(p.clone()), | ||||
None => Err(format!("Section `{}` not found.", path).into()) | None => Err(format!("Section `{}` not found.", path).into()) | ||||
} | } | ||||
@@ -92,6 +92,12 @@ Takes a path to a `_index.md` file and returns the associated section | |||||
{% set section = get_section(path="blog/_index.md") %} | {% set section = get_section(path="blog/_index.md") %} | ||||
``` | ``` | ||||
If you only need the metadata of the section, you can pass `metadata_only=true` to the function: | |||||
```jinja2 | |||||
{% set section = get_section(path="blog/_index.md", metadata_only=true) %} | |||||
``` | |||||
### ` get_url` | ### ` get_url` | ||||
Gets the permalink for the given path. | Gets the permalink for the given path. | ||||
If the path starts with `./`, it will be understood as an internal | If the path starts with `./`, it will be understood as an internal | ||||
@@ -108,11 +114,11 @@ we want to link to the file that is located at `static/css/app.css`: | |||||
{{/* get_url(path="css/app.css") */}} | {{/* get_url(path="css/app.css") */}} | ||||
``` | ``` | ||||
For assets it is reccommended that you pass `trailing_slash=false` to the `get_url` function. This prevents errors | |||||
when dealing with certain hosting providers. An example is: | |||||
By default, assets will not have a trailing slash. You can force one by passing `trailing_slash=true` to the `get_url` function. | |||||
An example is: | |||||
```jinja2 | ```jinja2 | ||||
{{/* get_url(path="css/app.css", trailing_slash=false) */}} | |||||
{{/* get_url(path="css/app.css", trailing_slash=true) */}} | |||||
``` | ``` | ||||
In the case of non-internal links, you can also add a cachebust of the format `?t=1290192` at the end of a URL | In the case of non-internal links, you can also add a cachebust of the format `?t=1290192` at the end of a URL | ||||
@@ -45,6 +45,10 @@ month: Number?; | |||||
day: Number?; | day: Number?; | ||||
// Paths of colocated assets, relative to the content directory | // Paths of colocated assets, relative to the content directory | ||||
assets: Array<String>; | assets: Array<String>; | ||||
// The relative paths of the parent sections until the index onef for use with the `get_section` Tera function | |||||
// The first item is the index section and the last one is the parent section | |||||
// This is filled after rendering a page content so it will be empty in shortcodes | |||||
ancestors: Array<String>; | |||||
``` | ``` | ||||
## Section variables | ## Section variables | ||||
@@ -70,7 +74,9 @@ extra: HashMap<String, Any>; | |||||
// Pages directly in this section, sorted if asked | // Pages directly in this section, sorted if asked | ||||
pages: Array<Pages>; | pages: Array<Pages>; | ||||
// Direct subsections to this section, sorted by subsections weight | // Direct subsections to this section, sorted by subsections weight | ||||
subsections: Array<Section>; | |||||
// This only contains the path to use in the `get_section` Tera function to get | |||||
// the actual section object if you need it | |||||
subsections: Array<String>; | |||||
// Unicode word count | // Unicode word count | ||||
word_count: Number; | word_count: Number; | ||||
// Based on https://help.medium.com/hc/en-us/articles/214991667-Read-time | // Based on https://help.medium.com/hc/en-us/articles/214991667-Read-time | ||||
@@ -79,6 +85,10 @@ reading_time: Number; | |||||
toc: Array<Header>; | toc: Array<Header>; | ||||
// Paths of colocated assets, relative to the content directory | // Paths of colocated assets, relative to the content directory | ||||
assets: Array<String>; | assets: Array<String>; | ||||
// The relative paths of the parent sections until the index onef for use with the `get_section` Tera function | |||||
// The first item is the index section and the last one is the parent section | |||||
// This is filled after rendering a page content so it will be empty in shortcodes | |||||
ancestors: Array<String>; | |||||
``` | ``` | ||||
## Table of contents | ## Table of contents | ||||
@@ -8,7 +8,8 @@ | |||||
<div class="documentation"> | <div class="documentation"> | ||||
<aside class="documentation__sidebar"> | <aside class="documentation__sidebar"> | ||||
<ul> | <ul> | ||||
{% for subsection in section.subsections %} | |||||
{% for p in section.subsections %} | |||||
{% set subsection = get_section(path=p) %} | |||||
<li> | <li> | ||||
<span class="documentation__sidebar__title">{{ subsection.title }}</span> | <span class="documentation__sidebar__title">{{ subsection.title }}</span> | ||||
<ul> | <ul> | ||||
@@ -4,7 +4,8 @@ | |||||
{% for page in section.pages %} | {% for page in section.pages %} | ||||
{{page.title}} | {{page.title}} | ||||
{% endfor %} | {% endfor %} | ||||
{% for subsection in section.subsections %} | |||||
{% for sub in section.subsections %} | |||||
{% set subsection = get_section(path=sub) %} | |||||
{{subsection.title}} | {{subsection.title}} | ||||
Sub-pages: {{subsection.pages | length}} | Sub-pages: {{subsection.pages | length}} | ||||
{% endfor %} | {% endfor %} | ||||