Less is more, if
you know more
if
is one of the most fundamental functions in any coding or scripting language, often being the easiest concept to understand; “if this, do that”, however, there are many cases where, ironically, using the if
function is not the simplest solution. Take the following example:
There are a number of problems with this example:
- The
<meta>
line is duplicated for each variable. - The variables themselves are duplicated, by being declared in the
if
statement and inside thecontent
value. - The
markdownify | plainify | safeHTML
sanitation functions are duplicated too (see therange
of choices post for what this does).
When constructing fallbacks for variables like this, it’s usually better to use a different function: default
(see the default
function documentation for more information). With this function, we can simplify our previous example:
Syntax for
default
function:default .FallbackVar .Var
or.Var | default .FallbackVar
.
Looking at slices
Working with string
variables is relatively simple, but a slice
can cause templates to explode in complexity because of functions like range
, delimit
, and, as we touched on before, duplicate lines within if
statements. Here’s an example:
For extra complexity, let’s say we also want .Params.series and .Params.tags to be listed in the content
value together, with .Site.Params.keywords being the fallback in case both the series and tags variables are unset. Already, this will grow into a iffy nest of nested if
statements.
So first, let’s involve the default
function:
Instead of trying to handle .Params.series and .Params.tags separately, with each being delimited next to each other to “combine” them inside of the content
value, we can actually just combine the slices together with the union
function:
And finally, since delimiting a slice
retains the order its items were specified in, such that ["Tag 1", "Tag 2"]
and ["Tag 2", "Tag 1"]
are different, we’ll pipe everything through the sort
function for a deterministic list:
Right, I should probably talk about the sort
function more, you’ll see why…
Let’s sort
this
The sort
function is deceptively difficult to use, mainly because its limitation is poorly explained in the sort
function documentation (hence why this blog series exists), and requires you to always do one thing: The input must be a slice
.
…Well, yeah, of course it does; it should be as simple as doing sort .Params.tags
and-NOPE. Do that, and Hugo will slap you with a vague failed to render pages: sequence must be provided error message. Here’s the sneaky detail: .Params.tags is only a slice
when set. So, unless .Params.tags is set on every single page, you’ll get this error message.
Thankfully, the fix is really simple: sort (default slice .Var)
. The only reason this fix wasn’t needed in the previous examples is because .Site.Params.keywords is set globally, and therefore, the sort
function always gets a slice
(but, if you can’t assume that variable is always set, you’ll need this fix).
Legitimate construction
Lucky URLs
If you’re manually constructing URLs or paths, do not use generic printf
or replace
functions to insert or remove slashes and other characters, and instead, use the various path
and urls
functions. These functions can deal with extraneous or missing slash characters in paths, extracting specific sections from URLs as desired, and simplify your template overall while being robust.
To handle slashes in paths:
path.Join "/" $taxonomy $term
->/tags/hugo
- Notice that the leading slash before “tags” won’t be inserted unless “/” or
$taxonomy := "/tags"
is given to thepath.Join
function. - If you are setting $term by parsing .Params.tags, make sure to pass the names through the
urlize
function (e.g.path.Join "/" $taxonomy (urlize $term)
).
- Notice that the leading slash before “tags” won’t be inserted unless “/” or
path.Clean "/tags/hugo/"
->/tags/hugo
- This is particularly useful for the
.GetPage
function, which can’t fetch the paths of pages with a trailing slash (e.g..GetPage "/tags/hugo/"
won’t fetch anything).
- This is particularly useful for the
To parse URLs:
(urls.Parse "/blog/post/#fragment").Path
->/blog/post/
- This is also useful for the
.GetPage
function for similar reasons as above.
- This is also useful for the
Lackluster comparisons
Generally speaking, you should never use .Title and similar user-content variables to match pages, such as using an if
statement in a range .Pages
function to check if the current page is in the list, as the sanitation steps are both error-prone and unnecessary. Instead, try to use more stable and appropriate variables like .Permalink or .RelPermalink, which don’t require sanitation when compared. So instead of:
You should instead do:
Last note
And yes, trying to make every heading start with “L” did make writing take much longer than it should of, BUT WAS IT WORTH IT!?
No, not really.