blog-static/content/blog/hugo_functions.md

3.9 KiB

title date tags
Approximating Custom Functions in Hugo 2021-01-17T18:44:53-08:00
Hugo

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 make hugo 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.