How to include code examples from file with Hugo

Source code on the left with the resulting code block on the right.

One limitation with Markdown is that you can’t include content from another file. For code examples, that means that you need to write the source code in your development environment and then copy it to the Markdown file.

And if you need to make a change to the code example, you’d need to copy it back over to the code editor, compile and run it, and then copy it back again. This tedious process makes it tempting to edit the code directly in the Markdown file.

Editing code directly in the Markdown file means you can’t run the code to verify that it works—leading to broken code examples. By keeping code examples in separate files instead, you’ll get several benefits:

  • Single source of truth: The code is stored in a file, which can be tested and run independently and reused in multiple contexts.
  • Easier to maintain: You can edit the code using your favorite editor. You can confidently fix linting errors, refactor, and run tests.

In this tutorial, you’ll learn how to create a Hugo shortcode that reads a code snippet from a file and displays it with syntax highlighting.

Read file and display with syntax highlighting

In this step, we’ll create a Hugo shortcode that reads the contents of a file and displays it with syntax highlighting in a code block.

Create a new file at layout/shortcodes/code.html with the following content:

layout/shortcodes/code.html
{{ $language := .Get "language" }}
{{ $source := .Get "source" }}
{{ $options := .Get "options" }}

{{ with $source | readFile }}
  {{ highlight (trim . "\n\r") $language $options }}
{{ end }}

The shortcode accepts three parameters, language, source, and options.

  • The source parameter is passed to the readFile.
  • The language and options parameters are forwarded as-is to the built-in highlight template function, which renders the code with a syntax highlighter.

To use it, add the following to your Markdown file:

{{< code language="go" source="/examples/main.go" >}}

The shortcode will expand the example above into the following code block:

package main

import "fmt"

func main() {
	fmt.Println("Hello, World!")
}
Code examples in page bundles

For this blog, I put code examples in the page bundle. Mainly because I want to keep the code examples close to the content, and I typically don’t reuse examples across blog posts.

If this is the case for you as well, use the following code instead:

{{ with .Page.Resources.GetMatch $source }}
  {{ highlight (trim .Content "\n\r") $language $options }}
{{ end }}

Include snippets from a file

The initial shortcode reads the entire file and displays it. While it may work for smaller, self-contained code examples, more complex examples will likely include boilerplate code irrelevant to the example. To avoid displaying the entire file, let’s add support for only showing a snippet of the referenced file.

First, we’ll need a way to specify the start and end of the snippet. To support different comment formats, we’ll check if a line ends with a specific tag. This should work whether you use // or # for comments.

To support multiple snippets in a single file, we’ll also add a suffix to each tag that’ll let us refer to a specific snippet.

main.go
package main

import "fmt"

func main() {
	fmt.Println(add(2, 1))
	fmt.Println(subtract(2, 1))
}

// START add
func add(a, b int) int {
	return a + b
}
// END add

// START subtract
func subtract(a, b int) int {
	return a - b
}
// END subtract

Let’s modify the shortcode to accept an id parameter that specifies the snippet to display, such as add or subtract.

The following is a modified version of the shortcode that now allows you to strip out everything except the part between the START <id> and END <id> tags.

layout/shortcodes/code.html
{{ $language := .Get "language" }}
{{ $source := .Get "source" }}
{{ $options := .Get "options" }}
{{ $id := .Get "id" }}

{{ with $source | readFile }}
  {{ $snippet := . }}

  {{ if $id }}
    {{ $lines := split $snippet "\n" }}

    {{ $startTag := printf "START %s" $id }}
    {{ $endTag := printf "END %s" $id }}

    {{ $startl := -1 }}
    {{ $endl := -1 }}

    {{/* Find the lines that ends with the start and end tags. */}}
    {{ range $index, $line := $lines }}
      {{ if hasSuffix $line $startTag }}
        {{ $startl = $index }}
      {{ else if hasSuffix $line $endTag }}
        {{ $endl = $index }}
      {{ end }}
    {{ end }}

    {{/* Let's add some basic assertions. */}}
    {{ if lt $startl 0 }}
      {{ errorf "Named snippet is missing START tag" }}
    {{ end }}

    {{ if lt $endl 0 }}
      {{ errorf "Named snippet is missing END tag" }}
    {{ end }}

    {{/* Size of the snippet in number of lines. */}}
    {{ $snippetLen := sub (sub $endl $startl) 2 }}

    {{/* Create slice with only the lines between the tags. */}}
    {{ $includedLines := first $snippetLen (after (add $startl 1) $lines) }}

    {{/* Join the lines into the final snippet. */}}
    {{ $snippet = delimit $includedLines "\n" }}
{{ end }}

We can now display the add function from the main.go file.

{{< code language="go" source="examples/main.go" id="add" >}}

The shortcode will expand the example above into the following code block:

func add(a, b int) int {
	return a + b
}

Using this approach, you can even maintain a single source code file for multiple code examples, making it easier to keep the examples up-to-date. Now, any time you modify any of the examples, you can run the main.go file to verify that the code works as intended.

Next steps

In this article, we created a Hugo shortcode that reads a code snippet from a file and displays it with syntax highlighting. We also added support for only showing a part of the file.

You can extend the shortcode to support additional features, such as displaying the file name.

Other documentation platforms allow you to instead use ranges of line numbers to select the code to display. I prefer the START <id> and END <id> approach, as it’s more robust and less likely to break if you change the code. Let me know if this is a feature you’d like to see in a future article!

If you want to learn more about writing educational content for developers, see my post on How to write tutorials for learning.