3.9 KiB
title | date | tags | |
---|---|---|---|
Approximating Custom Functions in Hugo | 2021-01-17T18:44:53-08:00 |
|
This will be an uncharacteristically short post. Recently, I wrote about my experience with [including code from local files]({{< relref "codelines" >}}). After I wrote that post, I decided to expand upon my setup. In particular, I wanted to display links to the files I'm referring to, in three different cases: when I'm referring to an entire code file, to an entire raw (non-highlighted) file, or to a portion of a code file.
The problem was that in all three cases, I needed to determine the
correct file URL to link to. The process for doing so is identical: it
really only depends on the path to the file I'm including. However,
many other aspects of each case are different. In the "entire code file"
case, I simply need to read in a file. In the "portion of a code file"
case, I have to perform some processing to extract the specific lines I want to include.
Whenever I include a code file -- entirely or partially -- I need to invoke the highlight
function to perform syntax highlighting; however, I don't want to do that when including a raw file.
It would be difficult to write a single shortcode or partial to handle all of these different cases.
However, computing the target URL is a simple transformation of a path and a list of submodules into a link. More plainly, it is a function. Hugo doesn't really have support for custom functions, at least according to this Discourse post. The only approach to add a real function to Hugo is to edit the Go-based source code, and recompile the thing. However, your own custom functions would then not be included in normal Hugo distributions, and any websites using these functions would not be portable. Really adding your own functions is not viable.
However, we can approximate functions using Hugo's
scratchpad feature
By feeding a
{{< sidenote "right" "mutable-note" "scratchpad" >}}
In reality, any mutable container will work. The scratchpad
just seems like the perfect tool for this purpose.
{{< /sidenote >}}
to a partial, and expecting the partial to modify the
scratchpad in some way, we can effectively recover data.
For instance, in my geturl
partial, I have something like
the following:
{{ .scratch.Set "bestUrl" theUrl }}
Once this partial executes, and the rendering engine is back to its call site,
the scratchpad will contain bestUrl
. To call this partial while providing inputs
(like the file path, for example), we can use Hugo's dict
function. An (abridged)
example:
{{ partial "geturl.html" (dict "scratch" .Scratch "path" filePath) }}
Now, from inside the partial, we'll be able to access the two variable using .scratch
and .path
.
Once we've called our partial, we simply extract the returned data from the scratchpad and use it:
{{ partial "geturl.html" (dict "scratch" .Scratch "path" filePath) }}
{{ $bestUrl := .Scratch.Get "bestUrl" }}
{{ ... do stuff with $bestUrl ... }}
Thus, although it's a little bit tedious, we're able to use geturl
like a function,
thereby refraining from duplicating its definition everywhere the same logic is needed. A few
closing thoughts:
- Why not just use a real language? It's true that I wrote a Ruby script to
do some of the work involved with linking submodules. However, generating the same
information for all calls to
codelines
would complicate the process of rendering the blog, and make live preview impossible. In general, by limiting the use of external scripts, it's easier to makehugo
the only "build tool" for the site. - Is there an easier way? I think that calls to
partial
return a string. If you simply wanted to return a string, you could probably do without using a scratchpad. However, if you wanted to do something more complicated (say, return a map or list), you'd probably want the scratchpad method after all.