GoLang Templating Made Easy

Fernando Diaz
4 min readDec 12, 2018

--

Golang provides the package text/template for generating textual output from an object. It is used in many popular OpenSource projects like Kubernetes Ingress-NGIX and Hugo.

It allows us to avoid doing extra work when generating similar forms. We only change/process just what we need.

I will go over a few of the things I’ve learned while using it.

“person holding four photos” by Josh Hild on Unsplash

I have created some executable examples which can be seen here. Each example is built off of the previous. I will be going over the following topics:

  • Creating a Template from a Golang Object
  • Creating a Template Function
  • Creating a Sub-Template

The examples will consist of:

  • zoo.go: contains the logic for generating the template
  • zoo.tmpl: contains the actual template

Creating a Template from an Object

An object or range of objects can be used with a template to create a textual output.

Pretty much its as simple as a template recognizing the object being passed in and drawing attributes from the object to create wanted text. It is very useful for reports or configurations.

Let’s say we have the following structs:

type Conf struct { 
TimeGenerated string
[]Zoo
}
type Zoo struct {
Name string
Climate string
Animals []Animal
}
type Animal struct {
Name string
Climates []string
}
Alligator = Animal{Name: "Alligator", Climates: []string{"Tropical", "SubTropical"}}Puffin = Animal{Name: "Puffin", Climates: []string{"Arctic", "SubArctic"}}miamiZoo = Zoo{Name: "MiamiZoo", Climate: "SubTropical", Animals: []Animal{Alligator, Puffin}}conf := Conf{TimeGenerated: time.Now().UTC().String(), Zoos: []Zoo{miamiZoo}}

and the following template:

{{ $cfg := . }}
{{ $zoos := .Zoos }}
Official Zoo's Report
---------------------
Report Generation Time: {{ $cfg.TimeGenerated }}
ZooSoftware: ZooKeeper 0.1.1
{{ range $index, $zoo := $zoos }}
{{ $zoo.Name }}
---------------
{{ range $animalIndex, $animal := $zoo.Animals }}
Animal {{ $animalIndex }}: {{ $animal.Name }}{{end}}
{{ end }}

we can loop through each of the Animals and generate a Report containing those animals. When executing the template, we will get the following output:

Official Zoo's Report
---------------------
Report Generation Time: 2018-12-12 23:16:21.340949125 +0000 UTC
ZooSoftware: ZooKeeper 0.1.1
MiamiZoo
---------------
Animal 0: Alligator
Animal 1: Puffin

For a detailed example you can run, checkout example 1.

Creating a Template Function

Template functions are very useful for processing Objects passed within the template. With these functions we can run some logic before passing objects to the template.

Photo by Matthew Cabret on Unsplash

Suppose the zoo only wanted to allow animals which are suitable for the climate in the area of the zoo’s location. We can create a function to sort which animals belong in the zoo, so we can generate a template of the acceptable animals.

The template function would look as follows:

// Here we pass in an Animal type and a Climate(string)
// If the animal's climate is the same climate as the one that
// we passed, then the animal is acceptable
func getAcceptableAnimals(a interface{}, b string) []Animal { animals, ok := a.([]Animal)
if !ok {
err := errors.New(fmt.Sprintf("expected an '[]*Animal' type but %T was returned", animals))
panic(err)
}
acceptable := []Animal{}
for _, animal := range animals {
for _, climate := range animal.Climates {
if b == climate {
acceptable = append(acceptable, animal)
break
}
}
}
return acceptable
}

and it can be used in a template as follows:

{{ $cfg := . }}
{{ $zoos := .Zoos }}
Official Zoo's Report
---------------------
Report Generation Time: {{ $cfg.TimeGenerated }}
ZooSoftware: ZooKeeper 0.1.2
{{ range $index, $zoo := $zoos }}
{{ $zoo.Name }}
---------------
{{ $acceptableAnimals := getAcceptableAnimals $zoo.Animals $zoo.Climate }}
{{ range $animalIndex, $animal := $acceptableAnimals }}
Animal {{ $animalIndex }}: {{ $animal.Name }}{{end}}
{{ end }}

We can see that before ranging through the animals to print, we run the getAcceptableAnimals function, passing the all the animals within a zoo, and the zoo’s climate. The we pass the result of that function(an array of animals) to the loop instead of zoo.Animals.

this will give us which animals are acceptable in the zoo:

Official Zoo's Report
---------------------
Report Generation Time: 2018-12-12 23:17:35.330908345 +0000 UTC
ZooSoftware: ZooKeeper 0.1.1
MiamiZoo
---------------
Animal 0: Alligator

For a detailed example you can run, checkout example 2.

Creating a Sub-Template

A sub-template would be a template that is loaded within another template.

This is useful because, we can move all common code to another template and reduce duplication of code if the sub-template is used several times in the main template.

In the following template, you can see how the the “ANIMALS” sub-template is defined, and used twice in the main template with the template syntax. This allows use to avoid duplicating the traversal of animals.

{{ $cfg := . }}
{{ $zoos := .Zoos }}
Official Zoo's Report
---------------------
Report Generation Time: {{ $cfg.TimeGenerated }}
ZooSoftware: ZooKeeper 0.1.3
{{ range $index, $zoo := $zoos }}
{{ $zoo.Name }}
%%%%%%%%%%%%%%%%
{{ $acceptableAnimals := getAcceptableAnimals $zoo.Animals $zoo.Climate }}
{{ $unacceptableAnimals := getUnacceptableAnimals $zoo.Animals $zoo.Climate }}
Acceptable Animals
------------------
{{ template "ANIMALS" $acceptableAnimals }}
UnAcceptable Animals
--------------------
{{ template "ANIMALS" $unacceptableAnimals }}
{{ end }} {{ define "ANIMALS" }}
{{ $animals := . }}
{{ range $animalIndex, $animal := $animals }}
Animal {{ $animalIndex }}: {{ $animal.Name }}{{end}}
{{ end }}

Also, note that Sub-Templates can be in other .tmpl files. You will just need to pass those files into the ParseFiles() function seen in the example.

For a detailed example you can run, checkout example 3. Note, I have added different template functions to process which animals should and should not be in the zoo.

For more details on using templating you can checkout some of the following resources:

Thank you for reading and I hope you enjoyed 😃

--

--

Fernando Diaz
Fernando Diaz

Written by Fernando Diaz

Senior Technical Marketing @ GitLab 🦊, Developer 👨🏻‍💻, Alien 👽