Home
Writing
3D Stuff
/

Mini guide to code generation with Go `text/templates` package

2021-11-15

Learned a couple of things while writing gomakeme - a code generator / boilerpate reducer for Fiber REST APIs.

General concept

The general concept of generating code with the text/templates package is quite simple.

  1. Create the data that you will pass to a template
  2. Create a template to which the data will be passed to
  3. Execute a function that will map the data and the template to create output.

And that's the gist of it.

Example 01. Generate a text file from a struct.

This example holds one of the most basic examples of file / code generation. We define a struct, create a variable based on the struct. Then we get the template and pass down the defined values to it.


If you want to see all of the data that is passed down to the template, you can write

{{ . }}

inside the template.

Example 02. Variables inside the templates.

Let's say we pass down the Day field from the MyData struct and mention it inside the template 20 times. What happens when we want to rename the field of the struct? We also need to change all of those instances of the data inside the template... or we can just assign the passed down data to a variable inside the template and change only the value of the variable.


Assigning variables inside the template could look like this.

{{ $day := .Day }}

To use the variable inside the template, we can just write

Today is {{ $day }}

To have some structure, it's easier to define all of the variables at the top of the template.

Note that when you execute the template in this case, the line in which you assign the data to the variable will be seen as an empty line in the executed template. If you don't want them to register as empty lines, you need to add - before the ending curly braces, like this

{{ $day := .Day -}}

You can also concatenate multiple strings using printf to create new values

{{ $day_concat  := printf "%s%s" "Concatenated" $day -}}

Example 03. Functions inside templates.

So now we can pass down data to the template and assign it to the variables. What if we want to convert the passed down data to have a specific form? Like converting Friday to be all uppercase? For that we can use functions.


To use them inside the template, we need to import them. This is done by modifying the function that parses the template a bit.

tmpl, err := template.New("my_template.tpl").Funcs(template.FuncMap{
    "functionThatConvertsInput": strings.ToUpper,
}).ParseFiles("./templates/my_template.tpl")

to use the functionThatConvertsInput inside the template, we can just put the mapped function before the variable.

{{functionThatConvertsInput $day_concat}}

the output of the tempate would capitalize the variable.

Notes

  • If you execute the template and an empty file is created, then that most probably means that you used a variable inside the template that does not exist.

  • You can pass down nested structs and still access those values inside the template, like

{{ .SecondStruct.SomeValue }}
  • The name of the template in the template.New() should be the same as the one that you mention in .ParseFiles()

  • You can also use conditionals and for loops inside the templates.

  • Name you files with the .tpl file extension and install the Smarty Template Support extension in VSCode to get highlights

  • Instead of running

go run main.go

every time you want to create new output, you can add a comment that starts with go:generate before the main function that will trigger the script

//go:generate go run main.go

func main() {
    // do some stuff
}

and just run

go generate