paulgorman.org/technical

Golang Templates

Go provides two similar template packages, one for general text and another for HTML. The HTML package works essentially the same way at the text templates, but protects against some web-specific security concerns.

Execute a template by applying it to a data structure.

package main

import (
	"os"
	"text/template"
)

type Inventory struct {
	Material string
	Count    uint
}

func main() {
	planet := "Mars"
	tmpl, _ := template.New("launch").Parse("Launch the mission to {{.}}!\n")
	tmpl.Execute(os.Stdout, planet)

	sweaters := Inventory{"wool", 17}
	tmpl, _ = template.New("itemcount").Parse("{{.Count}} items are made of {{.Material}}.\n")
	tmpl.Execute(os.Stdout, sweaters)
}

Execution of the template walks the data structure. Annotations ({{foo}}) in the template refer to elements of the data structure. Execution of the template sets a cursor to the value at its current location as it walks the data structure. A dot ({{.}}) represents the cursor.

A template is UTF-8 text. All text is output verbatim, except for “actions” enclosed between {{ and }}. Well, not necessarily verbatim — {{.x -}} × {{- .y}} trims the leading and trailing whitespace around × in the output.

Actions include loops, conditionals, and variable assignment:

{{/* A comment. */}}

{{define "embeddedtemplate"}} Thing: {{.}} {{end}}

{{template "embeddedtemplate" .Foo}}

{{with .MyValue}} A thing. {{else}} Nothing {{end}}

<h1>{{.Title}}<h1>
<ul>
{{range .Todos}}
	{{if .Done}}
		<li class="done">{{.Title}}</li>
	{{else}}
		<li>{{.Title}}</li>
	{{end}}
{{end}}
</ul>

<ol>
{{range .Steps}}
	{{$s := .Name}}
	<ol>
	{{range .SubSteps}}
		<li><b>{{$s}}:</b> {{.}}</li>
	{{end}}
	</ol>
{{end}}
</ol>

Parse a template from a file:

tmpl := template.Must(template.ParseFiles("foo.html")

Use a template in an http request handler:

func(w http.ResponseWriter, r *http.Request) {
	tmpl.Execute(w, data)
}

Caching Templates

For efficiency, parse templates once only, then execute them as needed. Since ParseFiles is variadic, have it parse all the files in one go and save them to a slice.

var templates = template.Must(template.ParseGlob(filepath.Join("templates", "*.html")))

func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) {
    err := templates.ExecuteTemplate(w, tmpl+".html", p)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
    }
}