Variables inside templates in golang
In go1.11 text/template and hence html/template became able to set the value of existing variables, which means that the original code can be made to work with one very small modification.
Change
{{$prev_year:=$year}}
To
{{$prev_year = $year}}
Playground
Edit: See https://stackoverflow.com/a/52925780/1685538 for a more up-to-date answer.
Original answer:
https://golang.org/pkg/text/template/#hdr-Variables:
A variable's scope extends to the "end" action of the control structure ("if", "with", or "range") in which it is declared, or to the end of the template if there is no such control structure.
So the $prev_year
you define with {{$prev_year:=$year}}
only lives until.. the next line ({{end}}
).
It seems there is no way of going around that.
The "right" way to do this is to take that logic out of your template, and do the grouping in your Go code.
Here is a working example : https://play.golang.org/p/DZoSXo9WQR
package main
import (
"fmt"
"os"
"text/template"
"time"
)
type Tournament struct {
Place string
Date time.Time
}
type TournamentGroup struct {
Year int
Tournaments []Tournament
}
func groupTournamentsByYear(tournaments []Tournament) []TournamentGroup {
if len(tournaments) == 0 {
return nil
}
result := []TournamentGroup{
{
Year: tournaments[0].Date.Year(),
Tournaments: make([]Tournament, 0, 1),
},
}
i := 0
for _, tournament := range tournaments {
year := tournament.Date.Year()
if result[i].Year == year {
// Add to existing group
result[i].Tournaments = append(result[i].Tournaments, tournament)
} else {
// New group
result = append(result, TournamentGroup{
Year: year,
Tournaments: []Tournament{
tournament,
},
})
i++
}
}
return result
}
func main() {
tournaments := []Tournament{
// for clarity - date is sorted, we don't need sort it again
{"Town1", time.Date(2015, time.November, 10, 23, 0, 0, 0, time.Local)},
{"Town2", time.Date(2015, time.October, 10, 23, 0, 0, 0, time.Local)},
{"Town3", time.Date(2014, time.November, 10, 23, 0, 0, 0, time.Local)},
}
t, err := template.New("").Parse(`
{{$prev_year:=0}}
{{range .}}
Actions in year {{.Year}}:
{{range .Tournaments}}
{{.Place}}, {{.Date}}
{{end}}
{{end}}
`)
if err != nil {
panic(err)
}
err = t.Execute(os.Stdout, groupTournamentsByYear(tournaments))
if err != nil {
fmt.Println("executing template:", err)
}
}
As mentioned by this answer, the scope of that variable "re-assignment" ends with the {{end}}
block. Therefore using standard variables only there's no way around the problem and it should be solved inside the Go program executing the template.
In some frameworks however this is not that easy (e.g. protoc-gen-gotemplate).
The Sprig library adds additional functionality to the standard template language. One of them are mutable maps that can be used in the following way:
// init the dictionary (you can init it without initial key/values as well)
{{$myVar := dict "key" "value"}}
// getting the "key" from the dictionary (returns array) and then fetching the first element from that array
{{pluck "key" $myVar | first}}
// conditional update block
{{if eq "some" "some"}}
// the $_ seems necessary because Go template functions need to return something
{{$_ := set $myVar "key" "newValue"}}
{{end}}
// print out the updated value
{{pluck "key" $myVar | first}}
This little example prints out:
value
newValue
A pragmatic approach would be to use a single dictionary for all mutable variables and store them under their corresponding variable name as key.
Reference:
- http://masterminds.github.io/sprig/dicts.html
- https://github.com/Masterminds/sprig