mirror of
https://github.com/golang/go
synced 2024-11-12 00:30:22 -07:00
text/template: add a slice function to the predefined global functions
The new slice function returns the result of slicing its first argument by the following arguments. Thus {{slice x 1 3}} is, in Go syntax, x[1:3]. Each sliced item must be a string, slice, or array. Closed #30153 RELNOTE=yes Change-Id: I63188c422848cee3d383a64dc4d046e3a1767c63 Reviewed-on: https://go-review.googlesource.com/c/go/+/161762 Reviewed-by: Rob Pike <r@golang.org>
This commit is contained in:
parent
6f51082da7
commit
e2970a4591
@ -328,6 +328,11 @@ Predefined global functions are named as follows.
|
||||
Returns the result of indexing its first argument by the
|
||||
following arguments. Thus "index x 1 2 3" is, in Go syntax,
|
||||
x[1][2][3]. Each indexed item must be a map, slice, or array.
|
||||
slice
|
||||
slice returns the result of slicing its first argument by the
|
||||
remaining arguments. Thus "slice x 1 2" is, in Go syntax, x[1:2],
|
||||
while "slice x" is x[:], "slice x 1" is x[1:], and "slice x 1 2 3"
|
||||
is x[1:2:3]. The first argument must be a string, slice, or array.
|
||||
js
|
||||
Returns the escaped JavaScript equivalent of the textual
|
||||
representation of its arguments.
|
||||
|
@ -23,7 +23,7 @@ type T struct {
|
||||
True bool
|
||||
I int
|
||||
U16 uint16
|
||||
X string
|
||||
X, S string
|
||||
FloatZero float64
|
||||
ComplexZero complex128
|
||||
// Nested structs.
|
||||
@ -36,8 +36,11 @@ type T struct {
|
||||
W1, W2 *W
|
||||
// Slices
|
||||
SI []int
|
||||
SICap []int
|
||||
SIEmpty []int
|
||||
SB []bool
|
||||
// Arrays
|
||||
AI [3]int
|
||||
// Maps
|
||||
MSI map[string]int
|
||||
MSIone map[string]int // one element, for deterministic output
|
||||
@ -122,12 +125,15 @@ var tVal = &T{
|
||||
I: 17,
|
||||
U16: 16,
|
||||
X: "x",
|
||||
S: "xyz",
|
||||
U: &U{"v"},
|
||||
V0: V{6666},
|
||||
V1: &V{7777}, // leave V2 as nil
|
||||
W0: W{888},
|
||||
W1: &W{999}, // leave W2 as nil
|
||||
SI: []int{3, 4, 5},
|
||||
SICap: make([]int, 5, 10),
|
||||
AI: [3]int{3, 4, 5},
|
||||
SB: []bool{true, false},
|
||||
MSI: map[string]int{"one": 1, "two": 2, "three": 3},
|
||||
MSIone: map[string]int{"one": 1},
|
||||
@ -491,6 +497,31 @@ var execTests = []execTest{
|
||||
{"map MI8S", "{{index .MI8S 3}}", "i83", tVal, true},
|
||||
{"map MUI8S", "{{index .MUI8S 2}}", "u82", tVal, true},
|
||||
|
||||
// Slicing.
|
||||
{"slice[:]", "{{slice .SI}}", "[3 4 5]", tVal, true},
|
||||
{"slice[1:]", "{{slice .SI 1}}", "[4 5]", tVal, true},
|
||||
{"slice[1:2]", "{{slice .SI 1 2}}", "[4]", tVal, true},
|
||||
{"slice[-1:]", "{{slice .SI -1}}", "", tVal, false},
|
||||
{"slice[1:-2]", "{{slice .SI 1 -2}}", "", tVal, false},
|
||||
{"slice[1:2:-1]", "{{slice .SI 1 2 -1}}", "", tVal, false},
|
||||
{"slice[2:1]", "{{slice .SI 2 1}}", "", tVal, false},
|
||||
{"slice[2:2:1]", "{{slice .SI 2 2 1}}", "", tVal, false},
|
||||
{"out of range", "{{slice .SI 4 5}}", "", tVal, false},
|
||||
{"out of range", "{{slice .SI 2 2 5}}", "", tVal, false},
|
||||
{"len(s) < indexes < cap(s)", "{{slice .SICap 6 10}}", "[0 0 0 0]", tVal, true},
|
||||
{"len(s) < indexes < cap(s)", "{{slice .SICap 6 10 10}}", "[0 0 0 0]", tVal, true},
|
||||
{"indexes > cap(s)", "{{slice .SICap 10 11}}", "", tVal, false},
|
||||
{"indexes > cap(s)", "{{slice .SICap 6 10 11}}", "", tVal, false},
|
||||
{"array[:]", "{{slice .AI}}", "[3 4 5]", tVal, true},
|
||||
{"array[1:]", "{{slice .AI 1}}", "[4 5]", tVal, true},
|
||||
{"array[1:2]", "{{slice .AI 1 2}}", "[4]", tVal, true},
|
||||
{"string[:]", "{{slice .S}}", "xyz", tVal, true},
|
||||
{"string[0:1]", "{{slice .S 0 1}}", "x", tVal, true},
|
||||
{"string[1:]", "{{slice .S 1}}", "yz", tVal, true},
|
||||
{"string[1:2]", "{{slice .S 1 2}}", "y", tVal, true},
|
||||
{"out of range", "{{slice .S 1 5}}", "", tVal, false},
|
||||
{"3-index slice of string", "{{slice .S 1 2 2}}", "", tVal, false},
|
||||
|
||||
// Len.
|
||||
{"slice", "{{len .SI}}", "3", tVal, true},
|
||||
{"map", "{{len .MSI }}", "3", tVal, true},
|
||||
|
@ -34,6 +34,7 @@ var builtins = FuncMap{
|
||||
"call": call,
|
||||
"html": HTMLEscaper,
|
||||
"index": index,
|
||||
"slice": slice,
|
||||
"js": JSEscaper,
|
||||
"len": length,
|
||||
"not": not,
|
||||
@ -159,17 +160,36 @@ func intLike(typ reflect.Kind) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// indexArg checks if a reflect.Value can be used as an index, and converts it to int if possible.
|
||||
func indexArg(index reflect.Value, cap int) (int, error) {
|
||||
var x int64
|
||||
switch index.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
x = index.Int()
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
x = int64(index.Uint())
|
||||
case reflect.Invalid:
|
||||
return 0, fmt.Errorf("cannot index slice/array with nil")
|
||||
default:
|
||||
return 0, fmt.Errorf("cannot index slice/array with type %s", index.Type())
|
||||
}
|
||||
if x < 0 || int(x) < 0 || int(x) > cap {
|
||||
return 0, fmt.Errorf("index out of range: %d", x)
|
||||
}
|
||||
return int(x), nil
|
||||
}
|
||||
|
||||
// Indexing.
|
||||
|
||||
// index returns the result of indexing its first argument by the following
|
||||
// arguments. Thus "index x 1 2 3" is, in Go syntax, x[1][2][3]. Each
|
||||
// indexed item must be a map, slice, or array.
|
||||
func index(item reflect.Value, indices ...reflect.Value) (reflect.Value, error) {
|
||||
func index(item reflect.Value, indexes ...reflect.Value) (reflect.Value, error) {
|
||||
v := indirectInterface(item)
|
||||
if !v.IsValid() {
|
||||
return reflect.Value{}, fmt.Errorf("index of untyped nil")
|
||||
}
|
||||
for _, i := range indices {
|
||||
for _, i := range indexes {
|
||||
index := indirectInterface(i)
|
||||
var isNil bool
|
||||
if v, isNil = indirect(v); isNil {
|
||||
@ -177,21 +197,11 @@ func index(item reflect.Value, indices ...reflect.Value) (reflect.Value, error)
|
||||
}
|
||||
switch v.Kind() {
|
||||
case reflect.Array, reflect.Slice, reflect.String:
|
||||
var x int64
|
||||
switch index.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
x = index.Int()
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
x = int64(index.Uint())
|
||||
case reflect.Invalid:
|
||||
return reflect.Value{}, fmt.Errorf("cannot index slice/array with nil")
|
||||
default:
|
||||
return reflect.Value{}, fmt.Errorf("cannot index slice/array with type %s", index.Type())
|
||||
x, err := indexArg(index, v.Len())
|
||||
if err != nil {
|
||||
return reflect.Value{}, err
|
||||
}
|
||||
if x < 0 || x >= int64(v.Len()) {
|
||||
return reflect.Value{}, fmt.Errorf("index out of range: %d", x)
|
||||
}
|
||||
v = v.Index(int(x))
|
||||
v = v.Index(x)
|
||||
case reflect.Map:
|
||||
index, err := prepareArg(index, v.Type().Key())
|
||||
if err != nil {
|
||||
@ -212,6 +222,57 @@ func index(item reflect.Value, indices ...reflect.Value) (reflect.Value, error)
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// Slicing.
|
||||
|
||||
// slice returns the result of slicing its first argument by the remaining
|
||||
// arguments. Thus "slice x 1 2" is, in Go syntax, x[1:2], while "slice x"
|
||||
// is x[:], "slice x 1" is x[1:], and "slice x 1 2 3" is x[1:2:3]. The first
|
||||
// argument must be a string, slice, or array.
|
||||
func slice(item reflect.Value, indexes ...reflect.Value) (reflect.Value, error) {
|
||||
var (
|
||||
cap int
|
||||
v = indirectInterface(item)
|
||||
)
|
||||
if !v.IsValid() {
|
||||
return reflect.Value{}, fmt.Errorf("slice of untyped nil")
|
||||
}
|
||||
if len(indexes) > 3 {
|
||||
return reflect.Value{}, fmt.Errorf("too many slice indexes: %d", len(indexes))
|
||||
}
|
||||
switch v.Kind() {
|
||||
case reflect.String:
|
||||
if len(indexes) == 3 {
|
||||
return reflect.Value{}, fmt.Errorf("cannot 3-index slice a string")
|
||||
}
|
||||
cap = v.Len()
|
||||
case reflect.Array, reflect.Slice:
|
||||
cap = v.Cap()
|
||||
default:
|
||||
return reflect.Value{}, fmt.Errorf("can't slice item of type %s", v.Type())
|
||||
}
|
||||
// set default values for cases item[:], item[i:].
|
||||
idx := [3]int{0, v.Len()}
|
||||
for i, index := range indexes {
|
||||
x, err := indexArg(index, cap)
|
||||
if err != nil {
|
||||
return reflect.Value{}, err
|
||||
}
|
||||
idx[i] = x
|
||||
}
|
||||
// given item[i:j], make sure i <= j.
|
||||
if idx[0] > idx[1] {
|
||||
return reflect.Value{}, fmt.Errorf("invalid slice index: %d > %d", idx[0], idx[1])
|
||||
}
|
||||
if len(indexes) < 3 {
|
||||
return item.Slice(idx[0], idx[1]), nil
|
||||
}
|
||||
// given item[i:j:k], make sure i <= j <= k.
|
||||
if idx[1] > idx[2] {
|
||||
return reflect.Value{}, fmt.Errorf("invalid slice index: %d > %d", idx[1], idx[2])
|
||||
}
|
||||
return item.Slice3(idx[0], idx[1], idx[2]), nil
|
||||
}
|
||||
|
||||
// Length
|
||||
|
||||
// length returns the length of the item, with an error if it has no defined length.
|
||||
|
Loading…
Reference in New Issue
Block a user