Skip to content

Commit

Permalink
Get term fn (#1964)
Browse files Browse the repository at this point in the history
* Add new `get_taxonomy_term` global function

* Update per @Keats feedback to make `page_count` available all the time.
  • Loading branch information
scouten authored and Keats committed Feb 16, 2023
1 parent 7b562fc commit 5fb0867
Show file tree
Hide file tree
Showing 7 changed files with 212 additions and 3 deletions.
32 changes: 32 additions & 0 deletions components/content/src/taxonomies.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ pub struct SerializedTaxonomyTerm<'a> {
path: &'a str,
permalink: &'a str,
pages: Vec<SerializingPage<'a>>,
page_count: usize,
}

impl<'a> SerializedTaxonomyTerm<'a> {
Expand All @@ -39,6 +40,30 @@ impl<'a> SerializedTaxonomyTerm<'a> {
path: &item.path,
permalink: &item.permalink,
pages,
page_count: item.pages.len(),
}
}
}

#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct SerializedTaxonomyTermWithoutPages<'a> {
name: &'a str,
slug: &'a str,
path: &'a str,
permalink: &'a str,
pages: Vec<SerializingPage<'a>>,
page_count: usize,
}

impl<'a> SerializedTaxonomyTermWithoutPages<'a> {
pub fn from_item(item: &'a TaxonomyTerm, _library: &'a Library) -> Self {
SerializedTaxonomyTermWithoutPages {
name: &item.name,
slug: &item.slug,
path: &item.path,
permalink: &item.permalink,
pages: vec!(),
page_count: item.pages.len(),
}
}
}
Expand Down Expand Up @@ -82,6 +107,13 @@ impl TaxonomyTerm {
SerializedTaxonomyTerm::from_item(self, library)
}

pub fn serialize_without_pages<'a>(
&'a self,
library: &'a Library,
) -> SerializedTaxonomyTermWithoutPages<'a> {
SerializedTaxonomyTermWithoutPages::from_item(self, library)
}

pub fn merge(&mut self, other: Self) {
self.pages.extend(other.pages);
}
Expand Down
8 changes: 8 additions & 0 deletions components/site/src/tpls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,12 @@ pub fn register_tera_global_fns(site: &mut Site) {
site.library.clone(),
),
);
site.tera.register_function(
"get_taxonomy_term",
global_fns::GetTaxonomyTerm::new(
&site.config.default_language,
site.taxonomies.clone(),
site.library.clone(),
),
);
}
153 changes: 152 additions & 1 deletion components/templates/src/global_fns/content.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use content::{Library, Taxonomy};
use content::{Library, Taxonomy, TaxonomyTerm};
use libs::tera::{from_value, to_value, Function as TeraFn, Result, Value};
use std::collections::HashMap;
use std::path::PathBuf;
Expand Down Expand Up @@ -182,6 +182,93 @@ impl TeraFn for GetTaxonomy {
}
}

#[derive(Debug)]
pub struct GetTaxonomyTerm {
library: Arc<RwLock<Library>>,
taxonomies: HashMap<String, Taxonomy>,
default_lang: String,
}
impl GetTaxonomyTerm {
pub fn new(
default_lang: &str,
all_taxonomies: Vec<Taxonomy>,
library: Arc<RwLock<Library>>,
) -> Self {
let mut taxonomies = HashMap::new();
for taxo in all_taxonomies {
taxonomies.insert(format!("{}-{}", taxo.kind.name, taxo.lang), taxo);
}
Self { taxonomies, library, default_lang: default_lang.to_string() }
}
}
impl TeraFn for GetTaxonomyTerm {
fn call(&self, args: &HashMap<String, Value>) -> Result<Value> {
let kind = required_arg!(
String,
args.get("kind"),
"`get_taxonomy_term` requires a `kind` argument with a string value"
);
let term = required_arg!(
String,
args.get("term"),
"`get_taxonomy_term` requires a `term` argument with a string value"
);
let include_pages = optional_arg!(
bool,
args.get("include_pages"),
"`get_taxonomy_term`: `include_pages` must be a boolean (true or false)"
)
.unwrap_or(true);
let required = optional_arg!(
bool,
args.get("required"),
"`get_taxonomy_term`: `required` must be a boolean (true or false)"
)
.unwrap_or(true);

let lang = optional_arg!(
String,
args.get("lang"),
"`get_taxonomy_term_by_name`: `lang` must be a string"
)
.unwrap_or_else(|| self.default_lang.clone());

let tax: &Taxonomy = match (self.taxonomies.get(&format!("{}-{}", kind, lang)), required) {
(Some(t), _) => t,
(None, false) => {
return Ok(Value::Null);
}
(None, true) => {
return Err(format!(
"`get_taxonomy_term_by_name` received an unknown taxonomy as kind: {}",
kind
)
.into());
}
};

let term: &TaxonomyTerm = match (tax.items.iter().find(|i| i.name == term), required) {
(Some(t), _) => t,
(None, false) => {
return Ok(Value::Null);
}
(None, true) => {
return Err(format!(
"`get_taxonomy_term_by_name` received an unknown taxonomy as kind: {}",
kind
)
.into());
}
};

if include_pages {
Ok(to_value(term.serialize(&self.library.read().unwrap())).unwrap())
} else {
Ok(to_value(term.serialize_without_pages(&self.library.read().unwrap())).unwrap())
}
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -325,4 +412,68 @@ mod tests {
args.insert("name".to_string(), to_value("random").unwrap());
assert!(static_fn.call(&args).is_err());
}

#[test]
fn can_get_taxonomy_term() {
let mut config = Config::default_for_test();
config.slugify.taxonomies = SlugifyStrategy::On;
let taxo_config = TaxonomyConfig { name: "tags".to_string(), ..TaxonomyConfig::default() };
let taxo_config_fr =
TaxonomyConfig { name: "tags".to_string(), ..TaxonomyConfig::default() };
config.slugify_taxonomies();
let library = Arc::new(RwLock::new(Library::new(&config)));
let tag = TaxonomyTerm::new("Programming", &config.default_language, "tags", &[], &config);
let tag_fr = TaxonomyTerm::new("Programmation", "fr", "tags", &[], &config);
let tags = Taxonomy {
kind: taxo_config,
lang: config.default_language.clone(),
slug: "tags".to_string(),
path: "/tags/".to_string(),
permalink: "https://vincent.is/tags/".to_string(),
items: vec![tag],
};
let tags_fr = Taxonomy {
kind: taxo_config_fr,
lang: "fr".to_owned(),
slug: "tags".to_string(),
path: "/fr/tags/".to_string(),
permalink: "https://vincent.is/fr/tags/".to_string(),
items: vec![tag_fr],
};

let taxonomies = vec![tags.clone(), tags_fr.clone()];
let static_fn = GetTaxonomyTerm::new(&config.default_language, taxonomies, library);
// can find it correctly
let mut args = HashMap::new();
args.insert("kind".to_string(), to_value("tags").unwrap());
args.insert("term".to_string(), to_value("Programming").unwrap());
let res = static_fn.call(&args).unwrap();
let res_obj = res.as_object().unwrap();
assert_eq!(res_obj["name"], Value::String("Programming".to_string()));
assert_eq!(res_obj["slug"], Value::String("programming".to_string()));
assert_eq!(
res_obj["permalink"],
Value::String("http://a-website.com/tags/programming/".to_string())
);
assert_eq!(res_obj["pages"], Value::Array(vec![]));
// Works with other languages as well
let mut args = HashMap::new();
args.insert("kind".to_string(), to_value("tags").unwrap());
args.insert("term".to_string(), to_value("Programmation").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["name"], Value::String("Programmation".to_string()));

// and errors if it can't find either taxonomy or term
let mut args = HashMap::new();
args.insert("kind".to_string(), to_value("something-else").unwrap());
args.insert("term".to_string(), to_value("Programming").unwrap());
assert!(static_fn.call(&args).is_err());

let mut args = HashMap::new();
args.insert("kind".to_string(), to_value("tags").unwrap());
args.insert("kind".to_string(), to_value("something-else").unwrap());
assert!(static_fn.call(&args).is_err());
}
}
2 changes: 1 addition & 1 deletion components/templates/src/global_fns/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ mod i18n;
mod images;
mod load_data;

pub use self::content::{GetPage, GetSection, GetTaxonomy, GetTaxonomyUrl};
pub use self::content::{GetPage, GetSection, GetTaxonomy, GetTaxonomyTerm, GetTaxonomyUrl};
pub use self::files::{GetFileHash, GetUrl};
pub use self::i18n::Trans;
pub use self::images::{GetImageMetadata, ResizeImage};
Expand Down
2 changes: 1 addition & 1 deletion docs/content/documentation/content/shortcodes.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ This will create a shortcode `books` with the argument `path` pointing to a `.to
titles and descriptions. They will flow with the rest of the document in which `books` is called.

Shortcodes are rendered before the page's Markdown is parsed so they don't have access to the page's table of contents.
Because of that, you also cannot use the [`get_page`](@/documentation/templates/overview.md#get-page)/[`get_section`](@/documentation/templates/overview.md#get-section)/[`get_taxonomy`](@/documentation/templates/overview.md#get-taxonomy) global functions. It might work while
Because of that, you also cannot use the [`get_page`](@/documentation/templates/overview.md#get-page)/[`get_section`](@/documentation/templates/overview.md#get-section)/[`get_taxonomy`](@/documentation/templates/overview.md#get-taxonomy)/[`get_taxonomy_term`](@/documentation/templates/overview.md#get-term) global functions. It might work while
running `zola serve` because it has been loaded but it will fail during `zola build`.

## Using shortcodes
Expand Down
17 changes: 17 additions & 0 deletions docs/content/documentation/templates/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,23 @@ items: Array<TaxonomyTerm>;

See the [Taxonomies documentation](@/documentation/templates/taxonomies.md) for a full documentation of those types.

### `get_taxonomy_term`
Gets a single term from a taxonomy of a specific kind.

```jinja2
{% set categories = get_taxonomy_term(kind="categories", term="term_name") %}
```

The type of the output is a single `TaxonomyTerm` item.

`lang` (optional) default to `config.default_language` in config.toml

`include_pages` (optional) default to true. If false, the `pages` item in the `TaxonomyTerm` will be empty, regardless of what pages may actually exist for this term. `page_count` will correctly reflect the number of pages for this term in both cases.

`required` (optional) if a taxonomy or term is not found`.

See the [Taxonomies documentation](@/documentation/templates/taxonomies.md) for a full documentation of those types.

### `get_url`
Gets the permalink for the given path.
If the path starts with `@/`, it will be treated as an internal link like the ones used in Markdown,
Expand Down
1 change: 1 addition & 0 deletions docs/content/documentation/templates/taxonomies.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ slug: String;
path: String;
permalink: String;
pages: Array<Page>;
page_count: Number;
```

and `TaxonomyConfig` has the following fields:
Expand Down

0 comments on commit 5fb0867

Please sign in to comment.