4.2 KiB
title | date | draft | categories | tags | |||
---|---|---|---|---|---|---|---|
Hugo Dictionary API | 2022-10-13T10:19:02-07:00 | true |
|
|
Hugo's templating system has support for dictionaries. Unfortunately the API for working with them is, frankly, awful. While working on developing some new templates for this site, I had to figure out how to build up dictionary data structures and it took me a long time to figure out how to do some basic operations with them.
Here's a quick summary of what I found.
Creating Dictionaries
The function to create a dictionary is called dict
and it takes a
variable number of arguments that alternate between keys and values. Keys must
be strings (or string slices) and values can be anything. So this:
{{< figures/code >}}
{{ $d := dict "a" 1 "b" 2 "c" 3 }}
{{< /figures/code >}}
creates a structure that looks like this JSON object:
{{< figures/code >}}
{ "a": 1, "b": 2, "c": 3 }
{{< /figures/code >}}
For completeness, you can also create an empty dictionary by calling dict
with
no arguments.
{{< figures/code >}}
{{ $d := dict }}
{{< /figures/code >}}
Accessing Keys and Values
Statically, you can get a single item in a dictionary with dot syntax. Below,
$item
will get the value 1.
{{< figures/code >}}
{{ $item := (dict "a" 1 "b" 2 "c" 3).a }}
{{< /figures/code >}}
If you want to get a value with a key you get at render time, you can use the
index
function. In the snippet below, $item
will get the value of
"b"
, which is 2.
{{< figures/code >}}
{{ $item := index "b" (dict "a" 1 "b" 2 "c" 3) }}
{{< /figures/code >}}
index
doesn't make much sense to me as a verb for accessing values in a
dictionary. It sounds more like an array function. I would like to see another
function with a more dictionary-sounding name, like get
or value
or item
,
even if it were just an alias for index
underneath.
Adding Items to a Dictionary
This is a bit complex because, as far as I can tell, dictionaries are immutable.
So, if you want to update a dictionary, you need to combine two dictionaries and
then save it back to the original variable. The merge
function does
that. Here's a snippet:
{{< figures/code >}}
{{ $d := dict "a" 1 "b" 2 "c" 3 }}
{{ $d = merge $d (dict "b" 4) }}
{{ $item = index "b" $d }}
{{< /figures/code >}}
merge
takes a variable number of arguments, and merges dictionaries left to
right. So, items in dictionaries later in the argument list will override items
in dictionaries earlier in the list.
Just to underscore, you have to set the update dictionary back to the original
variable to complete the update, hence the $d = ...
.
All that is to say: at the end of that snippet, $item
will get the value 4.
A Complex Example: A Dictionary of Arrays
For the previously mentioned template changes I was making, I was updating the
terms
template for my category taxonomy. For each category, I wanted to show
one section per tag, and a list of all the posts with that tag underneath.
My categories are high level groups like "Tech," "Music," and "Travel." Tags are more specific topics for the post like "Web" or "Compositions." By my own policy, pages should only ever have one category but they can have multiple tags.
A terms
template lets you access an array of terms, and the pages associated
with those terms. You can access the tags attached to a page with the
.GetTerms
function. Here's what I did, and then I'll talk through it:
{{< figures/code >}}
{{- $pagesByTag := dict -}}
{{- range $page := .Pages }}
{{- range $tag := .GetTerms "tags" -}}
{{- $tagName := $tag.Name -}}
{{- if not (in $pagesByTag $tagName) -}}
{{- $pagesByTag = merge $pagesByTag
(dict $tagName (slice $page)) -}}
{{- else -}}
{{- $pagesForTag := index $pagesByTag $tagName -}}
{{- $pagesForTag = $pagesForTag | append $page -}}
{{- $pagesByTag = merge $pagesByTag
(dict $tagName $pagesForTag) -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{< /figures/code >}}