1
0
mirror of https://github.com/golang/go synced 2024-11-25 01:08:02 -07:00

template: for range on a map, sort the keys if feasible.

Fixes #2696.

R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/5543055
This commit is contained in:
Rob Pike 2012-01-13 14:09:13 -08:00
parent 92c8df46c6
commit a5950df89e
3 changed files with 48 additions and 17 deletions

View File

@ -50,7 +50,9 @@ data, defined in detail below.
The value of the pipeline must be an array, slice, or map. If The value of the pipeline must be an array, slice, or map. If
the value of the pipeline has length zero, nothing is output; the value of the pipeline has length zero, nothing is output;
otherwise, dot is set to the successive elements of the array, otherwise, dot is set to the successive elements of the array,
slice, or map and T1 is executed. slice, or map and T1 is executed. If the value is a map and the
keys are of basic type with a defined order ("comparable"), the
elements will be visited in sorted key order.
{{range pipeline}} T1 {{else}} T0 {{end}} {{range pipeline}} T1 {{else}} T0 {{end}}
The value of the pipeline must be an array, slice, or map. If The value of the pipeline must be an array, slice, or map. If

View File

@ -9,6 +9,7 @@ import (
"io" "io"
"reflect" "reflect"
"runtime" "runtime"
"sort"
"strings" "strings"
"text/template/parse" "text/template/parse"
) )
@ -234,7 +235,7 @@ func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) {
if val.Len() == 0 { if val.Len() == 0 {
break break
} }
for _, key := range val.MapKeys() { for _, key := range sortKeys(val.MapKeys()) {
oneIteration(key, val.MapIndex(key)) oneIteration(key, val.MapIndex(key))
} }
return return
@ -676,3 +677,44 @@ func (s *state) printValue(n parse.Node, v reflect.Value) {
} }
fmt.Fprint(s.wr, v.Interface()) fmt.Fprint(s.wr, v.Interface())
} }
// Types to help sort the keys in a map for reproducible output.
type rvs []reflect.Value
func (x rvs) Len() int { return len(x) }
func (x rvs) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
type rvInts struct{ rvs }
func (x rvInts) Less(i, j int) bool { return x.rvs[i].Int() < x.rvs[j].Int() }
type rvUints struct{ rvs }
func (x rvUints) Less(i, j int) bool { return x.rvs[i].Uint() < x.rvs[j].Uint() }
type rvFloats struct{ rvs }
func (x rvFloats) Less(i, j int) bool { return x.rvs[i].Float() < x.rvs[j].Float() }
type rvStrings struct{ rvs }
func (x rvStrings) Less(i, j int) bool { return x.rvs[i].String() < x.rvs[j].String() }
// sortKeys sorts (if it can) the slice of reflect.Values, which is a slice of map keys.
func sortKeys(v []reflect.Value) []reflect.Value {
if len(v) <= 1 {
return v
}
switch v[0].Kind() {
case reflect.Float32, reflect.Float64:
sort.Sort(rvFloats{v})
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
sort.Sort(rvInts{v})
case reflect.String:
sort.Sort(rvStrings{v})
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
sort.Sort(rvUints{v})
}
return v
}

View File

@ -11,7 +11,6 @@ import (
"fmt" "fmt"
"os" "os"
"reflect" "reflect"
"sort"
"strings" "strings"
"testing" "testing"
) )
@ -169,18 +168,6 @@ func (t *T) MAdd(a int, b []int) []int {
return v return v
} }
// MSort is used to sort map keys for stable output. (Nice trick!)
func (t *T) MSort(m map[string]int) []string {
keys := make([]string, len(m))
i := 0
for k := range m {
keys[i] = k
i++
}
sort.Strings(keys)
return keys
}
// EPERM returns a value and an error according to its argument. // EPERM returns a value and an error according to its argument.
func (t *T) EPERM(error bool) (bool, error) { func (t *T) EPERM(error bool) (bool, error) {
if error { if error {
@ -410,9 +397,9 @@ var execTests = []execTest{
{"range empty else", "{{range .SIEmpty}}-{{.}}-{{else}}EMPTY{{end}}", "EMPTY", tVal, true}, {"range empty else", "{{range .SIEmpty}}-{{.}}-{{else}}EMPTY{{end}}", "EMPTY", tVal, true},
{"range []bool", "{{range .SB}}-{{.}}-{{end}}", "-true--false-", tVal, true}, {"range []bool", "{{range .SB}}-{{.}}-{{end}}", "-true--false-", tVal, true},
{"range []int method", "{{range .SI | .MAdd .I}}-{{.}}-{{end}}", "-20--21--22-", tVal, true}, {"range []int method", "{{range .SI | .MAdd .I}}-{{.}}-{{end}}", "-20--21--22-", tVal, true},
{"range map", "{{range .MSI | .MSort}}-{{.}}-{{end}}", "-one--three--two-", tVal, true}, {"range map", "{{range .MSI}}-{{.}}-{{end}}", "-1--3--2-", tVal, true},
{"range empty map no else", "{{range .MSIEmpty}}-{{.}}-{{end}}", "", tVal, true}, {"range empty map no else", "{{range .MSIEmpty}}-{{.}}-{{end}}", "", tVal, true},
{"range map else", "{{range .MSI | .MSort}}-{{.}}-{{else}}EMPTY{{end}}", "-one--three--two-", tVal, true}, {"range map else", "{{range .MSI}}-{{.}}-{{else}}EMPTY{{end}}", "-1--3--2-", tVal, true},
{"range empty map else", "{{range .MSIEmpty}}-{{.}}-{{else}}EMPTY{{end}}", "EMPTY", tVal, true}, {"range empty map else", "{{range .MSIEmpty}}-{{.}}-{{else}}EMPTY{{end}}", "EMPTY", tVal, true},
{"range empty interface", "{{range .Empty3}}-{{.}}-{{else}}EMPTY{{end}}", "-7--8-", tVal, true}, {"range empty interface", "{{range .Empty3}}-{{.}}-{{else}}EMPTY{{end}}", "-7--8-", tVal, true},
{"range empty nil", "{{range .Empty0}}-{{.}}-{{end}}", "", tVal, true}, {"range empty nil", "{{range .Empty0}}-{{.}}-{{end}}", "", tVal, true},