Using text/templates in Go

Save template execution output to a string variable

In the example below, varOutputTemplate execution output is be saved to varTemplateOutput variable of type string.

import (
	"bytes"
	"fmt"
	"log"
	"text/template"
)

func main() {

	var buff bytes.Buffer
	varValue := "variable value"

	outputTemplate := "var: {{.}}"
	varOutputTemplate := template.Must(template.New("").Parse(outputTemplate))

	if err := varOutputTemplate.Execute(&buff, varValue); err != nil {
		log.Fatal("error executing variable output template")
	}

	varTemplateOutput := buff.String()

	fmt.Printf("varTemplateOutput: %q\n", varTemplateOutput)

}

Explanation of the example:

  • var buff bytes.Buffer - declaration of a buff variable to store the template execution output,
  • varValue := "variable value" - varValue varibale with value variable value,
  • outputTemplate := "var: {{.}}" - template contains var: followed by a supplied value,
  • varOutputTemplate := template.Must(template.New("").Parse(outputTemplate)) - creation of a template,
  • varOutputTemplate.Execute(&buff, varValue) - executing the varOutputTemplate template and writing the output to buff,
  • varTemplateOutput := buff.String() - assigning buffer output as string to varTemplateOutput value.

Produce table-formatted text using templates

Using Go templates, we can produce nicely formatted table-like output. The key is to use text/tabwriter instead of os.Stdout when executing the template.

The output of current example execution is the following:

ID             Grade        Name
50ec2179fa7e   AAAAAAA      Some item1
9d84711a928b   AAAAAA       Some long item2 name
cea2215158e2   BBBBBBBBBB   Item3
25346d215c6e   AAA          Some long item4 name

The template to produce this output is this:

const tmpl = "ID\tGrade\tName\n{{range .}}{{.ID}}\t{{.Grade}}\t{{.Name}}\n{{end}}"

Here we use \t to identify the columns, \n for new lines, and {{range .}} with corresponding {{end}} to iterate over the items.

The items are stored in a slice of Items type structs.

type Item struct {
	ID    string
	Grade string
	Name  string
}

items := []Item{
    {ID: "50ec2179fa7e", Grade: "AAAAAAA", Name: "Some item1"},
    {ID: "9d84711a928b", Grade: "AAAAAA", Name: "Some long item2 name"},
    {ID: "cea2215158e2", Grade: "BBBBBBBBBB", Name: "Item3"},
    {ID: "25346d215c6e", Grade: "AAA", Name: "Some long item4 name"},
}

As I have mentioned before, template execution is not the same as in the previous example. Template execution is written to a tabwriter which formats the output before writing it to os.Stdout. 5 stands for minimal column width, 3 with space indicates that we want three spaces between the columns.

itemsTmpl := template.Must(template.New("").Parse(tmpl))
w := tabwriter.NewWriter(os.Stdout, 5, 0, 3, ' ', 0)
if err := itemsTmpl.Execute(w, items); err != nil {
    log.Fatal("error executing items template")
}
w.Flush()

Example complete code:

import (
	"log"
	"os"
	"text/tabwriter"
	"text/template"
)

type Item struct {
	ID    string
	Grade string
	Name  string
}

const tmpl = "ID\tGrade\tName\n{{range .}}{{.ID}}\t{{.Grade}}\t{{.Name}}\n{{end}}"

func main() {

	items := []Item{
		{ID: "50ec2179fa7e", Grade: "AAAAAAA", Name: "Some item1"},
		{ID: "9d84711a928b", Grade: "AAAAAA", Name: "Some long item2 name"},
		{ID: "cea2215158e2", Grade: "BBBBBBBBBB", Name: "Item3"},
		{ID: "25346d215c6e", Grade: "AAA", Name: "Some long item4 name"},
	}

	itemsTmpl := template.Must(template.New("").Parse(tmpl))
	w := tabwriter.NewWriter(os.Stdout, 5, 0, 3, ' ', 0)
	if err := itemsTmpl.Execute(w, items); err != nil {
		log.Fatal("error executing items template")
	}
	w.Flush()

}

Output variable value only if it exists

We will start from a simple text template, which we will use to print out map values.

const tmpl1 = "ATTR1: {{.attr1}}, ATTR2: {{.attr2}}, ATTR3: {{.attr3}}\n"

In the maps, we will have entries with keys attr1, attr2, and attr3. The first map will have only the first attribute, the second - the first two, and the thrird - all three of them. For convenience, the three maps will be put into an array.

items := []map[string]string{
    {"attr1": "item1_attr1"},
    {"attr1": "item2_attr1", "attr2": "item2_attr2"},
    {"attr1": "item3_attr1", "attr2": "item3_attr2", "attr3": "item3_attr3"},
}

Executing tmpl1 with items array values supplied to it as a parameter, will output <no value> for missing attributes in the first two maps.

itemTmpl := template.Must(template.New("").Parse(tmpl1))
for _, item := range items {
    if err := itemTmpl.Execute(os.Stdout, item); err != nil {
        log.Fatal("error executing item template")
    }
}

The output will be:

ATTR1: item1_attr1, ATTR2: <no value>, ATTR3: <no value>
ATTR1: item2_attr1, ATTR2: item2_attr2, ATTR3: <no value>
ATTR1: item3_attr1, ATTR2: item3_attr2, ATTR3: item3_attr3

Now we will modify the template in order for it to output attr2 and attr3 values only if they exist in map.

const tmpl2 = "ATTR1: {{.attr1}}{{if .attr2}}, ATTR2: {{.attr2}}{{end}}{{if .attr3}}, ATTR3: {{.attr3}}{{end}}\n"

Executing this template produces the required output.

ATTR1: item1_attr1
ATTR1: item2_attr1, ATTR2: item2_attr2
ATTR1: item3_attr1, ATTR2: item3_attr2, ATTR3: item3_attr3

Complete listing of the example:

import (
	"log"
	"os"
	"text/template"
)

const tmpl1 = "ATTR1: {{.attr1}}, ATTR2: {{.attr2}}, ATTR3: {{.attr3}}\n"
const tmpl2 = "ATTR1: {{.attr1}}{{if .attr2}}, ATTR2: {{.attr2}}{{end}}{{if .attr3}}, ATTR3: {{.attr3}}{{end}}\n"

var items = []map[string]string{
	{"attr1": "item1_attr1"},
	{"attr1": "item2_attr1", "attr2": "item2_attr2"},
	{"attr1": "item3_attr1", "attr2": "item3_attr2", "attr3": "item3_attr3"},
}

func executeTmpl(tmpl string) {
	itemTmpl := template.Must(template.New("").Parse(tmpl))
	for _, item := range items {
		if err := itemTmpl.Execute(os.Stdout, item); err != nil {
			log.Fatal("error executing item template")
		}
	}
}

func main() {

	executeTmpl(tmpl1)
	executeTmpl(tmpl2)

}