From 444ddf609c2e999c0835019e881a494f4ac79cbb Mon Sep 17 00:00:00 2001 From: sumdonkus Date: Tue, 9 Jan 2024 22:12:16 -0700 Subject: [PATCH 1/8] adding optional `lang` arugment to `get_section` global function --- .../templates/src/global_fns/content.rs | 100 +++++++++++++++++- .../documentation/templates/overview.md | 6 ++ 2 files changed, 103 insertions(+), 3 deletions(-) diff --git a/components/templates/src/global_fns/content.rs b/components/templates/src/global_fns/content.rs index 84c155fd71..ef9be43a56 100644 --- a/components/templates/src/global_fns/content.rs +++ b/components/templates/src/global_fns/content.rs @@ -106,6 +106,21 @@ impl GetSection { pub fn new(base_path: PathBuf, library: Arc>) -> Self { Self { base_path: base_path.join("content"), library } } + + fn add_lang_to_path(path: &str, lang: &str) -> Result { + match path.rfind('.') { + Some(period_offset) => { + let prefix = path.get(0..period_offset); + let suffix = path.get(period_offset..); + if prefix.is_none() || suffix.is_none() { + Err(format!("Error adding language code to {}", path).into()) + } else { + Ok(format!("{}.{}{}", prefix.unwrap(), lang, suffix.unwrap())) + } + } + None => Ok(format!("{}.{}", path, lang)), + } + } } impl TeraFn for GetSection { fn call(&self, args: &HashMap) -> Result { @@ -119,7 +134,14 @@ impl TeraFn for GetSection { .get("metadata_only") .map_or(false, |c| from_value::(c.clone()).unwrap_or(false)); - let full_path = self.base_path.join(&path); + let lang = + optional_arg!(String, args.get("lang"), "`get_section`: `lang` must be a string"); + + let calculated_path = lang.as_ref().map_or_else( + || Ok(path.clone()), + |lang_code| Self::add_lang_to_path(&path, lang_code), + )?; + let full_path = self.base_path.join(calculated_path); let library = self.library.read().unwrap(); match library.sections.get(&full_path) { @@ -130,7 +152,13 @@ impl TeraFn for GetSection { Ok(to_value(s.serialize(&library)).unwrap()) } } - None => Err(format!("Section `{}` not found.", path).into()), + None => match lang { + Some(lang_code) => { + Err(format!("Section `{}` not found for language `{}`.", path, lang_code) + .into()) + } + None => Err(format!("Section `{}` not found.", path).into()), + }, } } } @@ -273,7 +301,73 @@ impl TeraFn for GetTaxonomyTerm { mod tests { use super::*; use config::{Config, TaxonomyConfig}; - use content::TaxonomyTerm; + use content::{FileInfo, Library, Section, SortBy, TaxonomyTerm}; + use std::path::Path; + use std::sync::{Arc, RwLock}; + + fn create_section(title: &str, file_path: &str, lang: &str) -> Section { + let mut section = Section{ + lang: lang.to_owned(), + ..Section::default() + }; + section.file = FileInfo::new_section( + Path::new(format!("/test/base/path/{}", file_path).as_str()), + &PathBuf::new(), + ); + section.meta.title = Some(title.to_string()); + section.meta.weight = 1; + section.meta.transparent = false; + section.meta.sort_by = SortBy::None; + section.meta.page_template = Some("new_page.html".to_owned()); + section.file.find_language("en", &["fr"]).unwrap(); + section + } + + #[test] + fn can_get_section() { + let mut library = Library::default(); + let sections = vec![ + ("Homepage", "content/_index.md", "en"), + ("Page D'Accueil", "content/_index.fr.md", "fr"), + ("Blog", "content/blog/_index.md", "en"), + ("Wiki", "content/wiki/_index.md", "en"), + ("Wiki", "content/wiki/_index.fr.md", "fr"), + ("Recipes", "content/wiki/recipes/_index.md", "en"), + ("Recettes", "content/wiki/recipes/_index.fr.md", "fr"), + ("Programming", "content/wiki/programming/_index.md", "en"), + ("La Programmation", "content/wiki/programming/_index.fr.md", "fr"), + ("Novels", "content/novels/_index.md", "en"), + ("Des Romans", "content/novels/_index.fr.md", "fr"), + ]; + for (t, f, l) in sections.clone() { + library.insert_section(create_section(t, f, l)); + } + let base_path = "/test/base/path".into(); + + let static_fn = GetSection::new(base_path, Arc::new(RwLock::new(library))); + + // Find with lang argument + let mut args = HashMap::new(); + args.insert("path".to_string(), to_value("wiki/recipes/_index.md").unwrap()); + args.insert("lang".to_string(), to_value("fr").unwrap()); + let res = static_fn.call(&args).unwrap(); + let res_obj = res.as_object().unwrap(); + assert_eq!(res_obj["title"], to_value("Recettes").unwrap()); + + // Find with lang in path for legacy support + args = HashMap::new(); + args.insert("path".to_string(), to_value("wiki/recipes/_index.fr.md").unwrap()); + let res = static_fn.call(&args).unwrap(); + let res_obj = res.as_object().unwrap(); + assert_eq!(res_obj["title"], to_value("Recettes").unwrap()); + + // Find with default lang + args = HashMap::new(); + args.insert("path".to_string(), to_value("wiki/recipes/_index.md").unwrap()); + let res = static_fn.call(&args).unwrap(); + let res_obj = res.as_object().unwrap(); + assert_eq!(res_obj["title"], to_value("Recipes").unwrap()); + } #[test] fn can_get_taxonomy() { diff --git a/docs/content/documentation/templates/overview.md b/docs/content/documentation/templates/overview.md index b5f1a50947..e04632a6d3 100644 --- a/docs/content/documentation/templates/overview.md +++ b/docs/content/documentation/templates/overview.md @@ -154,6 +154,12 @@ If you only need the metadata of the section, you can pass `metadata_only=true` {% set section = get_section(path="blog/_index.md", metadata_only=true) %} ``` +If selecting a specific language for the section, you can pass `lang` with the language code to the function: + +```jinja2 +{% set section = get_section(path="blog/_index.md", lang="fr") %} +``` + ### `get_taxonomy_url` Gets the permalink for the taxonomy item found. From 4814e3c32b4573a97e20ee0414eb67e03b3fb05d Mon Sep 17 00:00:00 2001 From: sumdonkus Date: Tue, 9 Jan 2024 22:33:06 -0700 Subject: [PATCH 2/8] Add handling of default language passed in `lang` argument of `get_section` --- components/site/src/tpls.rs | 2 +- .../templates/src/global_fns/content.rs | 20 +++++++++++++++---- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/components/site/src/tpls.rs b/components/site/src/tpls.rs index d1e2eacd5a..c7edf878ed 100644 --- a/components/site/src/tpls.rs +++ b/components/site/src/tpls.rs @@ -80,7 +80,7 @@ pub fn register_tera_global_fns(site: &mut Site) { ); site.tera.register_function( "get_section", - global_fns::GetSection::new(site.base_path.clone(), site.library.clone()), + global_fns::GetSection::new(site.base_path.clone(), &site.config.default_language, site.library.clone()), ); site.tera.register_function( "get_taxonomy", diff --git a/components/templates/src/global_fns/content.rs b/components/templates/src/global_fns/content.rs index ef9be43a56..4a8debb32c 100644 --- a/components/templates/src/global_fns/content.rs +++ b/components/templates/src/global_fns/content.rs @@ -100,11 +100,12 @@ impl TeraFn for GetPage { #[derive(Debug)] pub struct GetSection { base_path: PathBuf, + default_lang: String, library: Arc>, } impl GetSection { - pub fn new(base_path: PathBuf, library: Arc>) -> Self { - Self { base_path: base_path.join("content"), library } + pub fn new(base_path: PathBuf, default_lang: &str, library: Arc>) -> Self { + Self { base_path: base_path.join("content"), default_lang: default_lang.to_string(), library } } fn add_lang_to_path(path: &str, lang: &str) -> Result { @@ -139,7 +140,10 @@ impl TeraFn for GetSection { let calculated_path = lang.as_ref().map_or_else( || Ok(path.clone()), - |lang_code| Self::add_lang_to_path(&path, lang_code), + |lang_code| match self.default_lang.as_str() == lang_code { + true => Ok(path.clone()), + false => Self::add_lang_to_path(&path, lang_code), + }, )?; let full_path = self.base_path.join(calculated_path); let library = self.library.read().unwrap(); @@ -344,7 +348,7 @@ mod tests { } let base_path = "/test/base/path".into(); - let static_fn = GetSection::new(base_path, Arc::new(RwLock::new(library))); + let static_fn = GetSection::new(base_path, "en", Arc::new(RwLock::new(library))); // Find with lang argument let mut args = HashMap::new(); @@ -367,6 +371,14 @@ mod tests { let res = static_fn.call(&args).unwrap(); let res_obj = res.as_object().unwrap(); assert_eq!(res_obj["title"], to_value("Recipes").unwrap()); + + // Find with default lang when default lang passed + args = HashMap::new(); + args.insert("path".to_string(), to_value("wiki/recipes/_index.md").unwrap()); + args.insert("lang".to_string(), to_value("en").unwrap()); + let res = static_fn.call(&args).unwrap(); + let res_obj = res.as_object().unwrap(); + assert_eq!(res_obj["title"], to_value("Recipes").unwrap()); } #[test] From 4bedd1417f8e888688cc8689a778999043212dd9 Mon Sep 17 00:00:00 2001 From: sumdonkus Date: Sat, 13 Jan 2024 04:28:21 -0700 Subject: [PATCH 3/8] Remove clones for path. Change "?" to an explicit check for error --- .../templates/src/global_fns/content.rs | 52 +++++++++++-------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/components/templates/src/global_fns/content.rs b/components/templates/src/global_fns/content.rs index 4a8debb32c..f83e630d36 100644 --- a/components/templates/src/global_fns/content.rs +++ b/components/templates/src/global_fns/content.rs @@ -1,5 +1,6 @@ use content::{Library, Taxonomy, TaxonomyTerm}; use libs::tera::{from_value, to_value, Function as TeraFn, Result, Value}; +use std::borrow::Cow; use std::collections::HashMap; use std::path::PathBuf; use std::sync::{Arc, RwLock}; @@ -108,7 +109,7 @@ impl GetSection { Self { base_path: base_path.join("content"), default_lang: default_lang.to_string(), library } } - fn add_lang_to_path(path: &str, lang: &str) -> Result { + fn add_lang_to_path<'a>(path: &str, lang: &str) -> Result> { match path.rfind('.') { Some(period_offset) => { let prefix = path.get(0..period_offset); @@ -116,10 +117,10 @@ impl GetSection { if prefix.is_none() || suffix.is_none() { Err(format!("Error adding language code to {}", path).into()) } else { - Ok(format!("{}.{}{}", prefix.unwrap(), lang, suffix.unwrap())) + Ok(Cow::Owned(format!("{}.{}{}", prefix.unwrap(), lang, suffix.unwrap()))) } } - None => Ok(format!("{}.{}", path, lang)), + None => Ok(Cow::Owned(format!("{}.{}", path, lang))), } } } @@ -138,31 +139,36 @@ impl TeraFn for GetSection { let lang = optional_arg!(String, args.get("lang"), "`get_section`: `lang` must be a string"); - let calculated_path = lang.as_ref().map_or_else( - || Ok(path.clone()), + let calculated_path_result = lang.as_ref().map_or_else( + || Ok(Cow::Borrowed(&path)), |lang_code| match self.default_lang.as_str() == lang_code { - true => Ok(path.clone()), + true => Ok(Cow::Borrowed(&path)), false => Self::add_lang_to_path(&path, lang_code), }, - )?; - let full_path = self.base_path.join(calculated_path); - let library = self.library.read().unwrap(); - - match library.sections.get(&full_path) { - Some(s) => { - if metadata_only { - Ok(to_value(s.serialize_basic(&library)).unwrap()) - } else { - Ok(to_value(s.serialize(&library)).unwrap()) - } - } - None => match lang { - Some(lang_code) => { - Err(format!("Section `{}` not found for language `{}`.", path, lang_code) - .into()) + ); + match calculated_path_result { + Ok(calculated_path) => { + let full_path = self.base_path.join(calculated_path.as_ref()); + let library = self.library.read().unwrap(); + + match library.sections.get(&full_path) { + Some(s) => { + if metadata_only { + Ok(to_value(s.serialize_basic(&library)).unwrap()) + } else { + Ok(to_value(s.serialize(&library)).unwrap()) + } + } + None => match lang { + Some(lang_code) => { + Err(format!("Section `{}` not found for language `{}`.", path, lang_code) + .into()) + } + None => Err(format!("Section `{}` not found.", path).into()), + }, } - None => Err(format!("Section `{}` not found.", path).into()), }, + Err(e) => Err(e), } } } From 7ea1376df54f1ced82faf02d36508aa327d85fbb Mon Sep 17 00:00:00 2001 From: sumdonkus Date: Sat, 13 Jan 2024 05:07:50 -0700 Subject: [PATCH 4/8] lint changes --- components/site/src/tpls.rs | 6 ++++- .../templates/src/global_fns/content.rs | 22 ++++++++++--------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/components/site/src/tpls.rs b/components/site/src/tpls.rs index c7edf878ed..b181c0a07e 100644 --- a/components/site/src/tpls.rs +++ b/components/site/src/tpls.rs @@ -80,7 +80,11 @@ pub fn register_tera_global_fns(site: &mut Site) { ); site.tera.register_function( "get_section", - global_fns::GetSection::new(site.base_path.clone(), &site.config.default_language, site.library.clone()), + global_fns::GetSection::new( + site.base_path.clone(), + &site.config.default_language, + site.library.clone(), + ), ); site.tera.register_function( "get_taxonomy", diff --git a/components/templates/src/global_fns/content.rs b/components/templates/src/global_fns/content.rs index f83e630d36..f740f9720f 100644 --- a/components/templates/src/global_fns/content.rs +++ b/components/templates/src/global_fns/content.rs @@ -106,7 +106,11 @@ pub struct GetSection { } impl GetSection { pub fn new(base_path: PathBuf, default_lang: &str, library: Arc>) -> Self { - Self { base_path: base_path.join("content"), default_lang: default_lang.to_string(), library } + Self { + base_path: base_path.join("content"), + default_lang: default_lang.to_string(), + library, + } } fn add_lang_to_path<'a>(path: &str, lang: &str) -> Result> { @@ -160,14 +164,15 @@ impl TeraFn for GetSection { } } None => match lang { - Some(lang_code) => { - Err(format!("Section `{}` not found for language `{}`.", path, lang_code) - .into()) - } + Some(lang_code) => Err(format!( + "Section `{}` not found for language `{}`.", + path, lang_code + ) + .into()), None => Err(format!("Section `{}` not found.", path).into()), }, } - }, + } Err(e) => Err(e), } } @@ -316,10 +321,7 @@ mod tests { use std::sync::{Arc, RwLock}; fn create_section(title: &str, file_path: &str, lang: &str) -> Section { - let mut section = Section{ - lang: lang.to_owned(), - ..Section::default() - }; + let mut section = Section { lang: lang.to_owned(), ..Section::default() }; section.file = FileInfo::new_section( Path::new(format!("/test/base/path/{}", file_path).as_str()), &PathBuf::new(), From 113553b9a694ab4a04f15fc8e45708b39e96d68d Mon Sep 17 00:00:00 2001 From: sumdonkus Date: Sat, 13 Jan 2024 16:52:40 -0700 Subject: [PATCH 5/8] Clean up error handling for add_lang_to_path call --- .../templates/src/global_fns/content.rs | 44 +++++++++---------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/components/templates/src/global_fns/content.rs b/components/templates/src/global_fns/content.rs index f740f9720f..cbdbb5efa7 100644 --- a/components/templates/src/global_fns/content.rs +++ b/components/templates/src/global_fns/content.rs @@ -143,38 +143,34 @@ impl TeraFn for GetSection { let lang = optional_arg!(String, args.get("lang"), "`get_section`: `lang` must be a string"); - let calculated_path_result = lang.as_ref().map_or_else( + lang.as_ref().map_or_else( || Ok(Cow::Borrowed(&path)), |lang_code| match self.default_lang.as_str() == lang_code { true => Ok(Cow::Borrowed(&path)), false => Self::add_lang_to_path(&path, lang_code), }, - ); - match calculated_path_result { - Ok(calculated_path) => { - let full_path = self.base_path.join(calculated_path.as_ref()); - let library = self.library.read().unwrap(); - - match library.sections.get(&full_path) { - Some(s) => { - if metadata_only { - Ok(to_value(s.serialize_basic(&library)).unwrap()) - } else { - Ok(to_value(s.serialize(&library)).unwrap()) - } + ).and_then(|calculated_path| { + let full_path = self.base_path.join(calculated_path.as_ref()); + let library = self.library.read().unwrap(); + + match library.sections.get(&full_path) { + Some(s) => { + if metadata_only { + Ok(to_value(s.serialize_basic(&library)).unwrap()) + } else { + Ok(to_value(s.serialize(&library)).unwrap()) } - None => match lang { - Some(lang_code) => Err(format!( - "Section `{}` not found for language `{}`.", - path, lang_code - ) - .into()), - None => Err(format!("Section `{}` not found.", path).into()), - }, } + None => match lang { + Some(lang_code) => Err(format!( + "Section `{}` not found for language `{}`.", + path, lang_code + ) + .into()), + None => Err(format!("Section `{}` not found.", path).into()), + }, } - Err(e) => Err(e), - } + }) } } From 68b3c29ed2ba8b3606ad7dcc0d77b38f9f65d4cf Mon Sep 17 00:00:00 2001 From: sumdonkus Date: Sun, 14 Jan 2024 03:52:18 -0700 Subject: [PATCH 6/8] fix format --- .../templates/src/global_fns/content.rs | 54 ++++++++++--------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/components/templates/src/global_fns/content.rs b/components/templates/src/global_fns/content.rs index cbdbb5efa7..c57f26b90d 100644 --- a/components/templates/src/global_fns/content.rs +++ b/components/templates/src/global_fns/content.rs @@ -143,34 +143,36 @@ impl TeraFn for GetSection { let lang = optional_arg!(String, args.get("lang"), "`get_section`: `lang` must be a string"); - lang.as_ref().map_or_else( - || Ok(Cow::Borrowed(&path)), - |lang_code| match self.default_lang.as_str() == lang_code { - true => Ok(Cow::Borrowed(&path)), - false => Self::add_lang_to_path(&path, lang_code), - }, - ).and_then(|calculated_path| { - let full_path = self.base_path.join(calculated_path.as_ref()); - let library = self.library.read().unwrap(); - - match library.sections.get(&full_path) { - Some(s) => { - if metadata_only { - Ok(to_value(s.serialize_basic(&library)).unwrap()) - } else { - Ok(to_value(s.serialize(&library)).unwrap()) + lang.as_ref() + .map_or_else( + || Ok(Cow::Borrowed(&path)), + |lang_code| match self.default_lang.as_str() == lang_code { + true => Ok(Cow::Borrowed(&path)), + false => Self::add_lang_to_path(&path, lang_code), + }, + ) + .and_then(|calculated_path| { + let full_path = self.base_path.join(calculated_path.as_ref()); + let library = self.library.read().unwrap(); + + match library.sections.get(&full_path) { + Some(s) => { + if metadata_only { + Ok(to_value(s.serialize_basic(&library)).unwrap()) + } else { + Ok(to_value(s.serialize(&library)).unwrap()) + } } + None => match lang { + Some(lang_code) => Err(format!( + "Section `{}` not found for language `{}`.", + path, lang_code + ) + .into()), + None => Err(format!("Section `{}` not found.", path).into()), + }, } - None => match lang { - Some(lang_code) => Err(format!( - "Section `{}` not found for language `{}`.", - path, lang_code - ) - .into()), - None => Err(format!("Section `{}` not found.", path).into()), - }, - } - }) + }) } } From 86ea4df86f0b992af84033035a911e6578dd2f9e Mon Sep 17 00:00:00 2001 From: sumdonkus Date: Sat, 20 Jan 2024 09:03:48 -0700 Subject: [PATCH 7/8] Add optional parameter "lang" to get_page template function. Add check for language available in config. --- components/site/src/tpls.rs | 11 +- .../templates/src/global_fns/content.rs | 184 ++++++++++++++---- .../documentation/templates/overview.md | 6 + 3 files changed, 166 insertions(+), 35 deletions(-) diff --git a/components/site/src/tpls.rs b/components/site/src/tpls.rs index b181c0a07e..c51d0de175 100644 --- a/components/site/src/tpls.rs +++ b/components/site/src/tpls.rs @@ -1,5 +1,6 @@ use crate::Site; use libs::tera::Result as TeraResult; +use std::sync::Arc; use templates::{filters, global_fns}; /// Adds global fns that are to be available to shortcodes while rendering markdown @@ -74,15 +75,23 @@ pub fn register_early_global_fns(site: &mut Site) -> TeraResult<()> { /// Functions filled once we have parsed all the pages/sections only, so not available in shortcodes pub fn register_tera_global_fns(site: &mut Site) { + let language_list: Arc> = + Arc::new(site.config.languages.keys().map(|s| s.to_string()).collect()); site.tera.register_function( "get_page", - global_fns::GetPage::new(site.base_path.clone(), site.library.clone()), + global_fns::GetPage::new( + site.base_path.clone(), + &site.config.default_language, + Arc::clone(&language_list), + site.library.clone(), + ), ); site.tera.register_function( "get_section", global_fns::GetSection::new( site.base_path.clone(), &site.config.default_language, + Arc::clone(&language_list), site.library.clone(), ), ); diff --git a/components/templates/src/global_fns/content.rs b/components/templates/src/global_fns/content.rs index c57f26b90d..ded57b039d 100644 --- a/components/templates/src/global_fns/content.rs +++ b/components/templates/src/global_fns/content.rs @@ -72,14 +72,60 @@ impl TeraFn for GetTaxonomyUrl { } } +fn add_lang_to_path<'a>(path: &str, lang: &str) -> Result> { + match path.rfind('.') { + Some(period_offset) => { + let prefix = path.get(0..period_offset); + let suffix = path.get(period_offset..); + if prefix.is_none() || suffix.is_none() { + Err(format!("Error adding language code to {}", path).into()) + } else { + Ok(Cow::Owned(format!("{}.{}{}", prefix.unwrap(), lang, suffix.unwrap()))) + } + } + None => Ok(Cow::Owned(format!("{}.{}", path, lang))), + } +} + +fn calculate_path<'a>( + path: &'a String, + lang: &Option, + default_lang: &str, + supported_languages: &[String], +) -> Result> { + if supported_languages.contains(&default_lang.to_string()) { + lang.as_ref().map_or_else( + || Ok(Cow::Borrowed(path)), + |lang_code| match default_lang == lang_code { + true => Ok(Cow::Borrowed(path)), + false => add_lang_to_path(path, lang_code), + }, + ) + } else { + Err(format!("Unsupported language {}", default_lang).into()) + } +} + #[derive(Debug)] pub struct GetPage { base_path: PathBuf, + default_lang: String, + supported_languages: Arc>, library: Arc>, } impl GetPage { - pub fn new(base_path: PathBuf, library: Arc>) -> Self { - Self { base_path: base_path.join("content"), library } + pub fn new( + base_path: PathBuf, + default_lang: &str, + supported_languages: Arc>, + library: Arc>, + ) -> Self { + Self { + base_path: base_path.join("content"), + default_lang: default_lang.to_string(), + supported_languages, + library, + } } } impl TeraFn for GetPage { @@ -89,12 +135,27 @@ impl TeraFn for GetPage { args.get("path"), "`get_page` requires a `path` argument with a string value" ); - let full_path = self.base_path.join(&path); - let library = self.library.read().unwrap(); - match library.pages.get(&full_path) { - Some(p) => Ok(to_value(p.serialize(&library)).unwrap()), - None => Err(format!("Page `{}` not found.", path).into()), - } + + let lang = + optional_arg!(String, args.get("lang"), "`get_section`: `lang` must be a string"); + + calculate_path(&path, &lang, &self.default_lang, &self.supported_languages).and_then( + |calculated_path| { + let full_path = self.base_path.join(calculated_path.as_ref()); + let library = self.library.read().unwrap(); + + match library.pages.get(&full_path) { + Some(p) => Ok(to_value(p.serialize(&library)).unwrap()), + None => match lang { + Some(lang_code) => { + Err(format!("Page `{}` not found for language `{}`.", path, lang_code) + .into()) + } + None => Err(format!("Page `{}` not found.", path).into()), + }, + } + }, + ) } } @@ -102,31 +163,23 @@ impl TeraFn for GetPage { pub struct GetSection { base_path: PathBuf, default_lang: String, + supported_languages: Arc>, library: Arc>, } impl GetSection { - pub fn new(base_path: PathBuf, default_lang: &str, library: Arc>) -> Self { + pub fn new( + base_path: PathBuf, + default_lang: &str, + supported_languages: Arc>, + library: Arc>, + ) -> Self { Self { base_path: base_path.join("content"), default_lang: default_lang.to_string(), + supported_languages, library, } } - - fn add_lang_to_path<'a>(path: &str, lang: &str) -> Result> { - match path.rfind('.') { - Some(period_offset) => { - let prefix = path.get(0..period_offset); - let suffix = path.get(period_offset..); - if prefix.is_none() || suffix.is_none() { - Err(format!("Error adding language code to {}", path).into()) - } else { - Ok(Cow::Owned(format!("{}.{}{}", prefix.unwrap(), lang, suffix.unwrap()))) - } - } - None => Ok(Cow::Owned(format!("{}.{}", path, lang))), - } - } } impl TeraFn for GetSection { fn call(&self, args: &HashMap) -> Result { @@ -143,14 +196,7 @@ impl TeraFn for GetSection { let lang = optional_arg!(String, args.get("lang"), "`get_section`: `lang` must be a string"); - lang.as_ref() - .map_or_else( - || Ok(Cow::Borrowed(&path)), - |lang_code| match self.default_lang.as_str() == lang_code { - true => Ok(Cow::Borrowed(&path)), - false => Self::add_lang_to_path(&path, lang_code), - }, - ) + calculate_path(&path, &lang, self.default_lang.as_str(), &self.supported_languages) .and_then(|calculated_path| { let full_path = self.base_path.join(calculated_path.as_ref()); let library = self.library.read().unwrap(); @@ -314,10 +360,78 @@ impl TeraFn for GetTaxonomyTerm { mod tests { use super::*; use config::{Config, TaxonomyConfig}; - use content::{FileInfo, Library, Section, SortBy, TaxonomyTerm}; + use content::{FileInfo, Library, Page, Section, SortBy, TaxonomyTerm}; use std::path::Path; use std::sync::{Arc, RwLock}; + fn create_page(title: &str, file_path: &str, lang: &str) -> Page { + let mut page = Page { lang: lang.to_owned(), ..Page::default() }; + page.file = FileInfo::new_page( + Path::new(format!("/test/base/path/{}", file_path).as_str()), + &PathBuf::new(), + ); + page.meta.title = Some(title.to_string()); + page.meta.weight = Some(1); + page.file.find_language("en", &["fr"]).unwrap(); + page + } + + #[test] + fn can_get_page() { + let mut library = Library::default(); + let pages = vec![ + ("Homepage", "content/homepage.md", "en"), + ("Page D'Accueil", "content/homepage.fr.md", "fr"), + ("Blog", "content/blog.md", "en"), + ("Wiki", "content/wiki.md", "en"), + ("Wiki", "content/wiki.fr.md", "fr"), + ("Recipes", "content/wiki/recipes.md", "en"), + ("Recettes", "content/wiki/recipes.fr.md", "fr"), + ("Programming", "content/wiki/programming.md", "en"), + ("La Programmation", "content/wiki/programming.fr.md", "fr"), + ("Novels", "content/novels.md", "en"), + ("Des Romans", "content/novels.fr.md", "fr"), + ]; + for (t, f, l) in pages.clone() { + library.insert_page(create_page(t, f, l)); + } + let base_path = "/test/base/path".into(); + let lang_list = vec!["en".to_string(), "fr".to_string()]; + + let static_fn = + GetPage::new(base_path, "en", Arc::new(lang_list), Arc::new(RwLock::new(library))); + + // Find with lang argument + let mut args = HashMap::new(); + args.insert("path".to_string(), to_value("wiki/recipes.md").unwrap()); + args.insert("lang".to_string(), to_value("fr").unwrap()); + let res = static_fn.call(&args).unwrap(); + let res_obj = res.as_object().unwrap(); + assert_eq!(res_obj["title"], to_value("Recettes").unwrap()); + + // Find with lang in path for legacy support + args = HashMap::new(); + args.insert("path".to_string(), to_value("wiki/recipes.fr.md").unwrap()); + let res = static_fn.call(&args).unwrap(); + let res_obj = res.as_object().unwrap(); + assert_eq!(res_obj["title"], to_value("Recettes").unwrap()); + + // Find with default lang + args = HashMap::new(); + args.insert("path".to_string(), to_value("wiki/recipes.md").unwrap()); + let res = static_fn.call(&args).unwrap(); + let res_obj = res.as_object().unwrap(); + assert_eq!(res_obj["title"], to_value("Recipes").unwrap()); + + // Find with default lang when default lang passed + args = HashMap::new(); + args.insert("path".to_string(), to_value("wiki/recipes.md").unwrap()); + args.insert("lang".to_string(), to_value("en").unwrap()); + let res = static_fn.call(&args).unwrap(); + let res_obj = res.as_object().unwrap(); + assert_eq!(res_obj["title"], to_value("Recipes").unwrap()); + } + fn create_section(title: &str, file_path: &str, lang: &str) -> Section { let mut section = Section { lang: lang.to_owned(), ..Section::default() }; section.file = FileInfo::new_section( @@ -353,8 +467,10 @@ mod tests { library.insert_section(create_section(t, f, l)); } let base_path = "/test/base/path".into(); + let lang_list = vec!["en".to_string(), "fr".to_string()]; - let static_fn = GetSection::new(base_path, "en", Arc::new(RwLock::new(library))); + let static_fn = + GetSection::new(base_path, "en", Arc::new(lang_list), Arc::new(RwLock::new(library))); // Find with lang argument let mut args = HashMap::new(); diff --git a/docs/content/documentation/templates/overview.md b/docs/content/documentation/templates/overview.md index e04632a6d3..026ddb6e99 100644 --- a/docs/content/documentation/templates/overview.md +++ b/docs/content/documentation/templates/overview.md @@ -141,6 +141,12 @@ Takes a path to an `.md` file and returns the associated page. The base path is {% set page = get_page(path="blog/page2.md") %} ``` +If selecting a specific language for the page, you can pass `lang` with the language code to the function: + +```jinja2 +{% set page = get_page(path="blog/page2.md", lang="fr") %} +``` + ### `get_section` Takes a path to an `_index.md` file and returns the associated section. The base path is the `content` directory. From 5ef7e4b62f8a090c2336156674f19f86c9978938 Mon Sep 17 00:00:00 2001 From: sumdonkus Date: Mon, 22 Jan 2024 20:24:32 -0700 Subject: [PATCH 8/8] Modify helper function name from calculate_path to get_path_with_lang. Modify documentation for get_section and get_page to include equivalent calls without using lang argument to demostrate how lang argument effects pathing. --- components/templates/src/global_fns/content.rs | 14 +++++++------- docs/content/documentation/templates/overview.md | 12 ++++++++++++ 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/components/templates/src/global_fns/content.rs b/components/templates/src/global_fns/content.rs index ded57b039d..17183bb639 100644 --- a/components/templates/src/global_fns/content.rs +++ b/components/templates/src/global_fns/content.rs @@ -87,7 +87,7 @@ fn add_lang_to_path<'a>(path: &str, lang: &str) -> Result> { } } -fn calculate_path<'a>( +fn get_path_with_lang<'a>( path: &'a String, lang: &Option, default_lang: &str, @@ -139,9 +139,9 @@ impl TeraFn for GetPage { let lang = optional_arg!(String, args.get("lang"), "`get_section`: `lang` must be a string"); - calculate_path(&path, &lang, &self.default_lang, &self.supported_languages).and_then( - |calculated_path| { - let full_path = self.base_path.join(calculated_path.as_ref()); + get_path_with_lang(&path, &lang, &self.default_lang, &self.supported_languages).and_then( + |path_with_lang| { + let full_path = self.base_path.join(path_with_lang.as_ref()); let library = self.library.read().unwrap(); match library.pages.get(&full_path) { @@ -196,9 +196,9 @@ impl TeraFn for GetSection { let lang = optional_arg!(String, args.get("lang"), "`get_section`: `lang` must be a string"); - calculate_path(&path, &lang, self.default_lang.as_str(), &self.supported_languages) - .and_then(|calculated_path| { - let full_path = self.base_path.join(calculated_path.as_ref()); + get_path_with_lang(&path, &lang, self.default_lang.as_str(), &self.supported_languages) + .and_then(|path_with_lang| { + let full_path = self.base_path.join(path_with_lang.as_ref()); let library = self.library.read().unwrap(); match library.sections.get(&full_path) { diff --git a/docs/content/documentation/templates/overview.md b/docs/content/documentation/templates/overview.md index 026ddb6e99..c94cf86b93 100644 --- a/docs/content/documentation/templates/overview.md +++ b/docs/content/documentation/templates/overview.md @@ -145,6 +145,12 @@ If selecting a specific language for the page, you can pass `lang` with the lang ```jinja2 {% set page = get_page(path="blog/page2.md", lang="fr") %} + +{# If "fr" is the default language, this is equivalent to #} +{% set page = get_page(path="blog/page2.md") %} + +{# If "fr" is not the default language, this is equivalent to #} +{% set page = get_page(path="blog/page2.fr.md") %} ``` ### `get_section` @@ -164,6 +170,12 @@ If selecting a specific language for the section, you can pass `lang` with the l ```jinja2 {% set section = get_section(path="blog/_index.md", lang="fr") %} + +{# If "fr" is the default language, this is equivalent to #} +{% set section = get_section(path="blog/_index.md") %} + +{# If "fr" is not the default language, this is equivalent to #} +{% set section = get_section(path="blog/_index.fr.md") %} ``` ### `get_taxonomy_url`