I recently went through How To Use Templates in Go to refresh my memory on Golang templates. I was reminded how great they are and learned several things along the way.

I learned that the below syntaxes are equivalent:

  • {{ . | len }}
  • {{ (len .) }}

Here is the program I wrote and tweaked along the way, to refresh my memory in the future:

package main

import (
    "html/template"
    "os"
    "strings"
)

type Pet struct {
    Name   string
    Sex    string
    Intact bool
    Age    string
    Breed  []string
}

var dogs = []Pet{
    {
        // This is why you should use html/template and not text/template when
        // rendering HTML. Especially if you don't trust the source of your
        // data!
        //
        // You don't want the template you render to do (potentially) malicious
        // things in your user's webbrowser.
        Name:   "<script>alert(\"Gotcha!\");</script>Jujube",
        Sex:    "Female",
        Intact: false,
        Age:    "10 months",
        Breed: []string{
            "German Shepherd",
            "Pitbull",
        },
    },
    {
        Name:   "Zephyr",
        Sex:    "Male",
        Intact: true,
        Age:    "13 years, 3 months",
        Breed: []string{
            "German Shepherd",
            "Border Collie",
        },
    },
    {
        Name:   "Roger",
        Sex:    "Male",
        Intact: false,
        Age:    "9 years, 7 months",
        Breed: []string{
            "German Shepherd",
            "Border Collie",
        },
    },
    {
        Name:   "Missi",
        Sex:    "Female",
        Intact: false,
        Age:    "99 years, 2 months",
        Breed: []string{
            "Teacup Poodle",
        },
    },
}

// This is how you add functions to your template. You can add functions from
// other packages or define them inline here.
//
// See petsHtml.tmpl for an example of using "join".
var funcMap = template.FuncMap{
    "dec":     func(i int) int { return i - 1 }, // Inline!
    "replace": strings.ReplaceAll,               // Other package!
    "join":    strings.Join,
}

func main() {
    // Create Template, add functions, and load template files.
    tmpl, err := template.New("").Funcs(funcMap).ParseGlob("*.tmpl")
    if err != nil {
        panic(err)
    }

    // Create a file.
    var f *os.File
    f, err = os.Create("pets.html")
    if err != nil {
        panic(err)
    }
    defer f.Close()

    // Execute a template by name with data "dogs" and write to writer "f".
    err = tmpl.ExecuteTemplate(f, "petsHtml.tmpl", dogs)
    if err != nil {
        panic(err)
    }
}

And here are the files I wrote and tweaked along the way:

pets.tmpl:

Number of dogs: {{ . | len }}

{{- range . }}
---
Name: {{ .Name }}
Sex: {{ .Sex }} ({{ if .Intact }}intact{{ else }}{{ if (eq .Sex "Female") }}spayed{{ else }}neutered{{ end }}{{ end }})
Age: {{ .Age }}
Breed: {{ join .Breed " & "}} ({{ if (len .Breed | eq 1) }}purebread{{ else }}mixed breed{{ end }})
{{ end }}

lastPet.tmpl:

{{- range (len . | dec | slice . ) }}
---
Name:  {{ .Name }}

Sex:   {{ .Sex }} ({{ if .Intact }}intact{{ else }}{{ if ("Female" | eq .Sex) }}spayed{{ else }}neutered{{ end }}{{ end }})

Age:   {{ .Age }}

Breed: {{ replace .Breed "/" " & " }}
{{ end -}}

petsHtml.tmpl:

<p><strong>Pets:</strong> {{ . | len }}</p>
{{ range . }}
<hr />
<dl>
    <dt>Name</dt>
    <dd>{{ .Name }}</dd>
    <dt>Sex</dt>
    <dd>{{ .Sex }} ({{ if .Intact }}intact{{ else }}{{ if (eq .Sex "Female") }}spayed{{ else }}neutered{{ end }}{{ end }})</dd>
    <dt>Age</dt>
    <dd>{{ .Age }}</dd>
    <dt>Breed</dt>
    <dd>{{ join .Breed " & "}} ({{ if (len .Breed | eq 1) }}purebread{{ else }}mixed breed{{ end }})
</dl>
{{ end }}