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 abuff
variable to store the template execution output,varValue := "variable value"
-varValue
varibale with valuevariable value
,outputTemplate := "var: {{.}}"
- template containsvar:
followed by a supplied value,varOutputTemplate := template.Must(template.New("").Parse(outputTemplate))
- creation of a template,varOutputTemplate.Execute(&buff, varValue)
- executing thevarOutputTemplate
template and writing the output tobuff
,varTemplateOutput := buff.String()
- assigning buffer output asstring
tovarTemplateOutput
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)
}