mf

Text Generation With Go Templates

Overview

Text is all around us as software developers. Code is text, HTML is text, XNL/JSON/YAML/TOML is text, Markdown is text, CSV is text. All these text formats are designed to cater to both humans and machines. Humans should be able to read and edit textual formats with plain text editors. 

But there are many cases where you need to generate text in a certain format. You may convert from one format to another, create your own DSL, generate some helper code automatically, or just customize an email with user-specific information. Whatever the need is, Go is more than able to assist you along the way with its powerful templates. 

In this tutorial, you’ll learn about the ins and outs of Go templates and how to use them for powerful text generation.

What Are Go Templates?

Go templates are objects that manage some text with special placeholders called actions, which are enclosed by double curly braces: {{ some action }}. When you execute the template, you provide it with a Go struct that has the data the placeholders need. 

Here’s a quick example that generates knock knock jokes. A knock knock joke has a very strict format. The only things that change are the identity of the knocker and the punchline.

Understanding Template Actions

The template syntax is very powerful, and it supports actions such as data accessors, functions, pipelines, variables, conditionals, and loops.

Data Accessors

Data accessors are very simple. They just pull data out of the struct starting. They can drill into nested structs too:

If the data is not a struct, you can use just {{.}} to access the value directly:

We will see later how to deal with arrays, slices, and maps.

Functions

Functions really elevate what you can do with templates. There are many global functions, and you can even add template-specific functions. The complete list of global functions is available on the Go website.

Here is an example of how to use the printf function in a template:

Pipelines

Pipelines let you apply multiple functions to the current value. Combining different functions significantly expands the ways in which you can slice and dice your values. 

In the following code, I chain three functions. First, the call function executes the function pass to Execute(). Then the len function returns the length of the result of the input function, which is 3 in this case. Finally, the printf function prints the number of items.

Variables

Sometimes you want to reuse the result of a complex pipeline multiple times. With Go templates, you can define a variable and reuse it as many times as you want. The following example extracts the first and last name from the input struct, quotes them, and stores them in the variables $F and $L. Then it renders them in normal and reverse order. 

Another neat trick here is that I pass an anonymous struct to the template to make the code more concise and avoid cluttering it with types that are used only in one place.

Conditionals

But let’s not stop here. You can even have conditions in your templates. There is an if-end action and if-else-end action. The if clause is displayed if the output of the conditional pipeline is not empty:

Note that the else clause causes a new line, and the “No data available” text is significantly indented.

Loops

Go templates have loops too. This is super useful when your data contains slices, maps, or other iterables. The data object for a loop can be any iterable Go object like array, slice, map, or channel. The range function allows you to iterate over the data object and create an output for each element. Let’s see how to iterate over a map:

As you can see, the leading whitespace is still a problem. I wasn’t able to find a decent way to address it within the template syntax. It will require post-processing. In theory, you can place a dash to trim whitespace preceding or following actions, but it doesn’t work in the presence of range.

Text Templates

Text templates are implemented in the text/template package. In addition to everything we’ve seen so far, this package can also load templates from files and compose multiple templates using the template action. The Template object itself has many methods to support such advanced use cases:

  • ParseFiles()
  • ParseGlob()
  • AddParseTree()
  • Clone()
  • DefinedTemplates()
  • Delims()
  • ExecuteTemplate()
  • Funcs()
  • Lookup()
  • Option()
  • Templates()

Due to space limitations, I will not go into further detail (maybe in another tutorial).

HTML Templates 

HTML templates are defined in the html/template package. It has exactly the same interface as the text template package, but it is designed to generate HTML that is safe from code injection. This is done by carefully sanitizing the data before embedding it in the template. The working assumption is that template authors are trusted, but the data provided to the template can’t be trusted. 

This is important. If you automatically apply templates you receive from untrusted sources then the html/template package will not protect you. It is your responsibility to check the templates.

Let’s see the difference between the output of text/template and html/template. When using the text/template, it’s easy to inject JavaScript code into the generated output.

But importing the html/template instead of text/template prevents this attack, by escaping the script tags and the parentheses:

Dealing With Errors

There are two types of errors: parsing errors and execution errors. The Parse() function parses the template text and returns an error, which I ignored in the code samples, but in production code you want to catch these errors early and address them. 

If you want a quick and dirty exit then the Must() method takes the output of a method that returns (*Template, error)—like Clone(), Parse(), or ParseFiles()—and panics if the error is not nil. Here is how you check for an explicit parsing error:

Using Must() just panics if something is wrong with the template:

The other kind of error is an execution error if the provided data doesn’t match the template. Again, you can check explicitly or use Must() to panic. I recommend in this case that you check and have a recovery mechanism in place. 

Usually, there is no need to bring the whole system down just because an input doesn’t meet the requirements. In the following example, the template expects a field called Name on the data struct, but I provide a struct with a field called FullName.

Conclusion

Go has a powerful and sophisticated templating system. It is used to great effect in many large projects like Kubernetes and Hugo. The html/template package provides a secure, industrial-strength facility to sanitize the output of web-based systems. In this tutorial, we covered all the basics and some intermediate use cases. 

There are still more advanced features in the template packages waiting to be unlocked. Play with templates and incorporate them into your programs. You will be pleasantly surprised how concise and readable your text-generation code looks.

Powered by WPeMatico

Leave a Comment

Scroll to Top