diff --git a/content/blog/hugo_functions.md b/content/blog/hugo_functions.md new file mode 100644 index 0000000..30e1881 --- /dev/null +++ b/content/blog/hugo_functions.md @@ -0,0 +1,79 @@ +--- +title: "Approximating Custom Functions in Hugo" +date: 2021-01-17T18:44:53-08:00 +tags: ["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](https://discourse.gohugo.io/t/adding-custom-functions/14164). 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](https://gohugo.io/functions/scratch/) +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.