Browse Source

Merge pull request #484 from Keats/subsections

section.subsections is now an array of paths
index-subcmd
Vincent Prouillet GitHub 5 years ago
parent
commit
ad6e443ffa
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 189 additions and 56 deletions
  1. +4
    -0
      CHANGELOG.md
  2. +27
    -13
      components/library/src/content/page.rs
  3. +24
    -5
      components/library/src/content/section.rs
  4. +60
    -16
      components/library/src/library.rs
  5. +3
    -3
      components/library/src/pagination/mod.rs
  6. +1
    -1
      components/library/src/taxonomies/mod.rs
  7. +1
    -0
      components/rebuild/src/lib.rs
  8. +1
    -1
      components/site/benches/site.rs
  9. +3
    -5
      components/site/src/lib.rs
  10. +21
    -4
      components/site/tests/site.rs
  11. +20
    -2
      components/templates/src/global_fns.rs
  12. +9
    -3
      docs/content/documentation/templates/overview.md
  13. +11
    -1
      docs/content/documentation/templates/pages-sections.md
  14. +2
    -1
      docs/templates/documentation.html
  15. +2
    -1
      test_site/templates/section.html

+ 4
- 0
CHANGELOG.md View File

@@ -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)




+ 27
- 13
components/library/src/content/page.rs View File

@@ -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(),


+ 24
- 5
components/library/src/content/section.rs View File

@@ -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 &section.pages { for k in &section.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 &section.subsections { for k in &section.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: &section.content, content: &section.content,
permalink: &section.permalink, permalink: &section.permalink,
title: &section.meta.title, title: &section.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: &section.content, content: &section.content,
permalink: &section.permalink, permalink: &section.permalink,
title: &section.meta.title, title: &section.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(),


+ 60
- 16
components/library/src/library.rs View File

@@ -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 &section.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(&section.file.parent) {
section.subsections = paths
.iter()
.map(|p| sections[p])
.collect::<Vec<_>>();
section.subsections
.sort_by(|a, b| sections_weight[a].cmp(&sections_weight[b]));
if let Some(ref children) = subsections.get(&section.file.path) {
let mut children: Vec<_> = children.iter().map(|p| sections[p]).collect();
children.sort_by(|a, b| sections_weight[a].cmp(&sections_weight[b]));
section.subsections = children;
} }
section.ancestors = ancestors.get(&section.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


+ 3
- 3
components/library/src/pagination/mod.rs View File

@@ -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) => {


+ 1
- 1
components/library/src/taxonomies/mod.rs View File

@@ -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 {


+ 1
- 0
components/rebuild/src/lib.rs View File

@@ -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


+ 1
- 1
components/site/benches/site.rs View File

@@ -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(&section, site.library.pages());
let paginator = Paginator::from_section(&section, &site.library);


b.iter(|| site.render_paginated(public, &paginator)); b.iter(|| site.render_paginated(public, &paginator));
} }


+ 3
- 5
components/site/src/lib.rs View File

@@ -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 {


+ 21
- 4
components/site/tests/site.rs View File

@@ -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);


+ 20
- 2
components/templates/src/global_fns.rs View File

@@ -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(&section.file.path).unwrap().to_serialized(library)).unwrap(), to_value(library.get_section(&section.file.path).unwrap().to_serialized(library)).unwrap(),
); );

sections_basic.insert(
section.file.relative.clone(),
to_value(library.get_section(&section.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 {
&sections_basic
} else {
&sections
};

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())
} }


+ 9
- 3
docs/content/documentation/templates/overview.md View File

@@ -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


+ 11
- 1
docs/content/documentation/templates/pages-sections.md View File

@@ -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


+ 2
- 1
docs/templates/documentation.html View File

@@ -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>


+ 2
- 1
test_site/templates/section.html View File

@@ -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 %}


Loading…
Cancel
Save