Go Text Template Processing
Tags: go, template
Published: 2019-04-27 23:37:09

---
title: Go Text Template Processing
published: true
description: Demonstration of processing Go text templates
tags: go, template
---

I recently worked on a project where I used Go's template package for parsing and interpolating templates. After processing the templates, I needed to use the interpolated string result for saving new file content, and also naming directories and files. This post is a walkthrough of the functions I wrote to help process text templates, then afterwards each function is demonstrated.


## Go Project Setup
Since I am demonstrating these functions outside the project I created them for, I've created a simpler project for this post as follows:

```bash
go-template-processing/
├── main.go
├── src
│   └── demo
│       └── template
│           └── process.go
└── templates
    └── got.tmpl
```

### Create the Template File
Add the following text to the file `templates/got.tmpl`.  This file template will be processed later.

```bash
{{.Name}} of house {{.House}}

{{if .Traits -}}
Traits:
    {{- range .Traits}}
    * {{. -}}
    {{end}}
{{end}}
```

### Anatomy of the Template
The  annotations like `{{.Name}}` and `{{.House}}` will be replaced with their respective values, during template execution. The other annotations like `{{if}}` and `{{range}}`  know as [Actions](https://golang.org/pkg/text/template/#hdr-Actions), will also be evaluated. The hyphens `-` to either side of the `{{` and `}}` delimiters, are used to trim any space before and after them respectively. The dot `.` represents the current element in the range of Traits being iterated.


### Set the GOPATH
```bash
cd go-template-processing
export GOPATH=$PWD
```

## Interpolating the Parsed Template

To separate the concern of first parsing then interpolating a template file or string, I've created a function in the file `process.go` which does the interpolation first. 

> Go refers to the this interpolation as "executing the template", where the template is applied to a data structure.

```go
package template

import (
    "bytes"
    "text/template"
)

// process applies the data structure 'vars' onto an already
// parsed template 't', and returns the resulting string.
func process(t *template.Template, vars interface{}) string {
	var tmplBytes bytes.Buffer

	err := t.Execute(&tmplBytes, vars)
	if err != nil {
		panic(err)
	}
	return tmplBytes.String()
}
```

The `process` function should be easy to reason with given its comment, annotations and implementation.

The argument `vars` is typed to an [empty interface](https://tour.golang.org/methods/14), so that any type like `map` or `Struct` with fields corresponding to the template's annotations can be used. Any `{{.Annotation}}` without a corresponding field is replaced with `<no value>`, or panics if it fails evaluation.

> Writing the `process` function above is a pre-emptive [extract method](https://refactoring.guru/extract-method) refactoring done to avoid [DRY](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself) occurring later in the code.

## Processing a Template String

The next function added to the `process.go` file, will process a template string by creating a new [Template](https://golang.org/pkg/text/template/#Template.New) and then parsing the given string `str` argument. The new `Template` assigned to `tmpl` is then interpolated using the private `process` function created earlier.

```go
func ProcessString(str string, vars interface{}) string {
	tmpl, err := template.New("tmpl").Parse(str)

	if err != nil {
		panic(err)
	}
	return process(tmpl, vars)
}
```

This function will be demonstrated later.


## Processing a Template File

This function is very similar to `ProcessString` but it processes the file indicated by the `fileName` argument.

> The [ParseFiles](https://golang.org/pkg/text/template/#Template.ParseFiles) function can accept more than one file name argument. However, I only need to parse one file at a time, so I am using a single `fileName` argument.

```go
func ProcessFile(fileName string, vars interface{}) string {
	tmpl, err := template.ParseFiles(fileName)

	if err != nil {
		panic(err)
	}
	return process(tmpl, vars)
}
```

That's it, both functions can now be consumed by client code.


## Using the Process Functions

The `./main.go` file below demonstrates how both process functions are used.

```go
package main

import (
	"fmt"
	"demo/template"
)

func main() {
	// data structure the template will be applied to
	vars := make(map[string]interface{})
	vars["Name"] = "Brienne"
	vars["House"] = "Tarth"
	vars["Traits"] = []string{"Brave", "Loyal"}

	// process a template string
	resultA := template.ProcessString("{{.Name}} of house {{.House}}", vars)

	// process a template file
	resultB := template.ProcessFile("templates/got.tmpl", vars)

	fmt.Println(resultA, "\n")
	fmt.Println(resultB)
}
```

Running `go run main.go`  should output the following:
```
Brienne of house Tarth

Brienne of house Tarth

Traits:
    * Brave
    * Loyal
```

Both the template string and file have been successfully processed. The resulting string can be now used for an email body, the contents of a file, etc.


### Supplying Vars via a Struct
Once again, since `vars` is typed to an empty interface, a struct can be used here also. 

_I've added the following snippet just below the `fmt.Println(resultB)` statement in `main.go`._

```go
// using a Struct
type Westerosi struct {
	Name   string
	House  string
	Traits []string
}

jorah := Westerosi{"Ser Jorah", "Mormont", []string{"Brave", "Protective"}}
resultC := template.ProcessFile("templates/got.tmpl", jorah)

fmt.Println(resultC)
```

Running `go run main.go` again should now output the following:

```
Brienne of house Tarth

Brienne of house Tarth

Traits:
    * Brave
    * Loyal

Ser Jorah of house Mormont

Traits:
    * Brave
    * Protective
```

---

## Conclusion
Working with Go templates is pretty straight forward out of the box, making it easy to build your own project specific functions. While I have only covered a specific task in this post, you can read more about the Go template package at [https://golang.org/pkg/text/template](https://golang.org/pkg/text/template/).

The code used in the article is available on [Github](https://github.com/kirklewis/go-template-processing).
_Go version used at time of writing `go version go1.12.1`_

Thank you for reading.