From 35359fb312bc9e56d78513e362354a612832fa56 Mon Sep 17 00:00:00 2001 From: BezPowell <41268328+BezPowell@users.noreply.github.com> Date: Mon, 29 Nov 2021 08:54:16 +0000 Subject: [PATCH] Add generic template fallbacks to taxonomies (#1651) * Split checking for theme template off into separate function Allows to check for fallbacks elsewhere in the code, without attempting to actually render the template. * Add template fallback checking to taxonomy pages. * Add template fallback checking to paginated taxonomies Requires passing additional arguments to Paginator::from_taxonomy, which may not be desirable. * Update documentation to reflect taxonomy template fallbacks. * Update generic taxonomy template names. * Make check_template_fallbacks() return &str. * Add tests for check_template_fallbacks --- components/library/src/pagination/mod.rs | 20 ++++-- components/library/src/taxonomies/mod.rs | 30 +++++--- components/site/src/lib.rs | 2 +- components/utils/src/templates.rs | 70 ++++++++++++++----- .../documentation/templates/taxonomies.md | 6 +- 5 files changed, 95 insertions(+), 33 deletions(-) diff --git a/components/library/src/pagination/mod.rs b/components/library/src/pagination/mod.rs index 26bc8a3e..899b20bb 100644 --- a/components/library/src/pagination/mod.rs +++ b/components/library/src/pagination/mod.rs @@ -6,7 +6,7 @@ use tera::{to_value, Context, Tera, Value}; use config::Config; use errors::{Error, Result}; -use utils::templates::render_template; +use utils::templates::{check_template_fallbacks, render_template}; use crate::content::{Section, SerializingPage, SerializingSection}; use crate::library::Library; @@ -94,8 +94,16 @@ impl<'a> Paginator<'a> { taxonomy: &'a Taxonomy, item: &'a TaxonomyItem, library: &'a Library, + tera: &Tera, + theme: &Option, ) -> Paginator<'a> { let paginate_by = taxonomy.kind.paginate_by.unwrap(); + // Check for taxon-specific template, or use generic as fallback. + let specific_template = format!("{}/single.html", taxonomy.kind.name); + let template = match check_template_fallbacks(&specific_template, tera, theme) { + Some(template) => template, + None => "taxonomy_single.html", + }; let mut paginator = Paginator { all_pages: Cow::Borrowed(&item.pages), pagers: Vec::with_capacity(item.pages.len() / paginate_by), @@ -110,7 +118,7 @@ impl<'a> Paginator<'a> { .clone() .unwrap_or_else(|| "page".to_string()), is_index: false, - template: format!("{}/single.html", taxonomy.kind.name), + template: template.to_string(), }; // taxonomy paginators have no sorting so we won't have to reverse @@ -249,7 +257,7 @@ impl<'a> Paginator<'a> { #[cfg(test)] mod tests { use std::path::PathBuf; - use tera::to_value; + use tera::{to_value, Tera}; use crate::content::{Page, Section}; use crate::library::Library; @@ -408,6 +416,7 @@ mod tests { #[test] fn test_can_create_paginator_for_taxonomy() { let (_, library) = create_library(false, 3, false); + let tera = Tera::default(); let taxonomy_def = TaxonomyConfig { name: "tags".to_string(), paginate_by: Some(2), @@ -427,7 +436,7 @@ mod tests { permalink: "/tags/".to_string(), items: vec![taxonomy_item.clone()], }; - let paginator = Paginator::from_taxonomy(&taxonomy, &taxonomy_item, &library); + let paginator = Paginator::from_taxonomy(&taxonomy, &taxonomy_item, &library, &tera, &None); assert_eq!(paginator.pagers.len(), 2); assert_eq!(paginator.pagers[0].index, 1); @@ -444,6 +453,7 @@ mod tests { #[test] fn test_can_create_paginator_for_slugified_taxonomy() { let (_, library) = create_library(false, 3, false); + let tera = Tera::default(); let taxonomy_def = TaxonomyConfig { name: "some tags".to_string(), paginate_by: Some(2), @@ -463,7 +473,7 @@ mod tests { permalink: "/some-tags/".to_string(), items: vec![taxonomy_item.clone()], }; - let paginator = Paginator::from_taxonomy(&taxonomy, &taxonomy_item, &library); + let paginator = Paginator::from_taxonomy(&taxonomy, &taxonomy_item, &library, &tera, &None); assert_eq!(paginator.pagers.len(), 2); assert_eq!(paginator.pagers[0].index, 1); diff --git a/components/library/src/taxonomies/mod.rs b/components/library/src/taxonomies/mod.rs index f2d96ad1..99ed14c5 100644 --- a/components/library/src/taxonomies/mod.rs +++ b/components/library/src/taxonomies/mod.rs @@ -7,7 +7,7 @@ use tera::{Context, Tera}; use config::{Config, Taxonomy as TaxonomyConfig}; use errors::{bail, Error, Result}; -use utils::templates::render_template; +use utils::templates::{check_template_fallbacks, render_template}; use crate::content::SerializingPage; use crate::library::Library; @@ -202,10 +202,16 @@ impl Taxonomy { ); context.insert("current_path", &format!("/{}/{}/", self.kind.name, item.slug)); - render_template(&format!("{}/single.html", self.kind.name), tera, context, &config.theme) - .map_err(|e| { - Error::chain(format!("Failed to render single term {} page.", self.kind.name), e) - }) + // Check for taxon-specific template, or use generic as fallback. + let specific_template = format!("{}/single.html", self.kind.name); + let template = match check_template_fallbacks(&specific_template, tera, &config.theme) { + Some(template) => template, + None => "taxonomy_single.html", + }; + + render_template(&template, tera, context, &config.theme).map_err(|e| { + Error::chain(format!("Failed to render single term {} page.", self.kind.name), e) + }) } pub fn render_all_terms( @@ -224,10 +230,16 @@ impl Taxonomy { context.insert("current_url", &config.make_permalink(&self.kind.name)); context.insert("current_path", &format!("/{}/", self.kind.name)); - render_template(&format!("{}/list.html", self.kind.name), tera, context, &config.theme) - .map_err(|e| { - Error::chain(format!("Failed to render a list of {} page.", self.kind.name), e) - }) + // Check for taxon-specific template, or use generic as fallback. + let specific_template = format!("{}/list.html", self.kind.name); + let template = match check_template_fallbacks(&specific_template, tera, &config.theme) { + Some(template) => template, + None => "taxonomy_list.html", + }; + + render_template(&template, tera, context, &config.theme).map_err(|e| { + Error::chain(format!("Failed to render a list of {} page.", self.kind.name), e) + }) } pub fn to_serialized<'a>(&'a self, library: &'a Library) -> SerializedTaxonomy<'a> { diff --git a/components/site/src/lib.rs b/components/site/src/lib.rs index 0b938035..23427f50 100644 --- a/components/site/src/lib.rs +++ b/components/site/src/lib.rs @@ -847,7 +847,7 @@ impl Site { if taxonomy.kind.is_paginated() { self.render_paginated( comp.clone(), - &Paginator::from_taxonomy(taxonomy, item, &library), + &Paginator::from_taxonomy(taxonomy, item, &library, &self.tera, &self.config.theme), )?; } else { let single_output = diff --git a/components/utils/src/templates.rs b/components/utils/src/templates.rs index 5c0e8d41..814e59bc 100644 --- a/components/utils/src/templates.rs +++ b/components/utils/src/templates.rs @@ -77,23 +77,8 @@ pub fn render_template( context: Context, theme: &Option, ) -> Result { - // check if it is in the templates - if tera.templates.contains_key(name) { - return tera.render(name, &context).map_err(std::convert::Into::into); - } - - // check if it is part of a theme - if let Some(ref t) = *theme { - let theme_template_name = format!("{}/templates/{}", t, name); - if tera.templates.contains_key(&theme_template_name) { - return tera.render(&theme_template_name, &context).map_err(std::convert::Into::into); - } - } - - // check if it is part of ZOLA_TERA defaults - let default_name = format!("__zola_builtins/{}", name); - if tera.templates.contains_key(&default_name) { - return tera.render(&default_name, &context).map_err(std::convert::Into::into); + if let Some(template) = check_template_fallbacks(name, tera, theme) { + return tera.render(&template, &context).map_err(std::convert::Into::into); } // maybe it's a default one? @@ -130,8 +115,40 @@ pub fn rewrite_theme_paths(tera_theme: &mut Tera, theme: &str) { tera_theme.templates.extend(new_templates); } +/// Checks for the presence of a given template. If none is found, also looks for a +/// fallback in theme and default templates. Returns the path of the most specific +/// template found, or none if none are present. +pub fn check_template_fallbacks<'a>( + name: &'a str, + tera: &'a Tera, + theme: &Option, +) -> Option<&'a str> { + // check if it is in the templates + if tera.templates.contains_key(name) { + return Some(name); + } + + // check if it is part of a theme + if let Some(ref t) = *theme { + let theme_template_name = format!("{}/templates/{}", t, name); + if let Some((key, _)) = tera.templates.get_key_value(&theme_template_name) { + return Some(key); + } + } + + // check if it is part of ZOLA_TERA defaults + let default_name = format!("__zola_builtins/{}", name); + if let Some((key, _)) = tera.templates.get_key_value(&default_name) { + return Some(key); + } + + None +} + #[cfg(test)] mod tests { + use crate::templates::check_template_fallbacks; + use super::rewrite_theme_paths; use tera::Tera; @@ -157,4 +174,23 @@ mod tests { Some("index.html".to_string()) ); } + + #[test] + fn template_fallback_is_successful() { + let mut tera = Tera::parse("test-templates/*.html").unwrap(); + tera.add_raw_template(&"hyde/templates/index.html", "Hello").unwrap(); + tera.add_raw_template(&"hyde/templates/theme-only.html", "Hello").unwrap(); + + // Check finding existing template + assert_eq!(check_template_fallbacks("index.html", &tera, &None), Some("index.html")); + + // Check trying to find non-existant template + assert_eq!(check_template_fallbacks("not-here.html", &tera, &None), None); + + // Check theme fallback + assert_eq!( + check_template_fallbacks("theme-only.html", &tera, &Some("hyde".to_string())), + Some("hyde/templates/theme-only.html") + ); + } } diff --git a/docs/content/documentation/templates/taxonomies.md b/docs/content/documentation/templates/taxonomies.md index c8302877..1cc8220d 100644 --- a/docs/content/documentation/templates/taxonomies.md +++ b/docs/content/documentation/templates/taxonomies.md @@ -3,11 +3,15 @@ title = "Taxonomies" weight = 40 +++ -Zola will look up the following files in the `templates` directory: +Zola will look up the following, taxon-specific files in the `templates` directory: - `$TAXONOMY_NAME/single.html` - `$TAXONOMY_NAME/list.html` +if they are not found, it will attempt to fall back on the following generic template files: +- `taxonomy_single.html` +- `taxonomy_list.html` + First, `TaxonomyTerm` has the following fields: ```ts