Commit e2970a45 authored by Ariel Mashraki's avatar Ariel Mashraki Committed by Rob Pike

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/+/161762Reviewed-by: default avatarRob Pike <r@golang.org>
parent 6f51082d
...@@ -328,6 +328,11 @@ Predefined global functions are named as follows. ...@@ -328,6 +328,11 @@ Predefined global functions are named as follows.
Returns the result of indexing its first argument by the Returns the result of indexing its first argument by the
following arguments. Thus "index x 1 2 3" is, in Go syntax, 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. 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 js
Returns the escaped JavaScript equivalent of the textual Returns the escaped JavaScript equivalent of the textual
representation of its arguments. representation of its arguments.
......
...@@ -23,7 +23,7 @@ type T struct { ...@@ -23,7 +23,7 @@ type T struct {
True bool True bool
I int I int
U16 uint16 U16 uint16
X string X, S string
FloatZero float64 FloatZero float64
ComplexZero complex128 ComplexZero complex128
// Nested structs. // Nested structs.
...@@ -36,8 +36,11 @@ type T struct { ...@@ -36,8 +36,11 @@ type T struct {
W1, W2 *W W1, W2 *W
// Slices // Slices
SI []int SI []int
SICap []int
SIEmpty []int SIEmpty []int
SB []bool SB []bool
// Arrays
AI [3]int
// Maps // Maps
MSI map[string]int MSI map[string]int
MSIone map[string]int // one element, for deterministic output MSIone map[string]int // one element, for deterministic output
...@@ -122,12 +125,15 @@ var tVal = &T{ ...@@ -122,12 +125,15 @@ var tVal = &T{
I: 17, I: 17,
U16: 16, U16: 16,
X: "x", X: "x",
S: "xyz",
U: &U{"v"}, U: &U{"v"},
V0: V{6666}, V0: V{6666},
V1: &V{7777}, // leave V2 as nil V1: &V{7777}, // leave V2 as nil
W0: W{888}, W0: W{888},
W1: &W{999}, // leave W2 as nil W1: &W{999}, // leave W2 as nil
SI: []int{3, 4, 5}, SI: []int{3, 4, 5},
SICap: make([]int, 5, 10),
AI: [3]int{3, 4, 5},
SB: []bool{true, false}, SB: []bool{true, false},
MSI: map[string]int{"one": 1, "two": 2, "three": 3}, MSI: map[string]int{"one": 1, "two": 2, "three": 3},
MSIone: map[string]int{"one": 1}, MSIone: map[string]int{"one": 1},
...@@ -491,6 +497,31 @@ var execTests = []execTest{ ...@@ -491,6 +497,31 @@ var execTests = []execTest{
{"map MI8S", "{{index .MI8S 3}}", "i83", tVal, true}, {"map MI8S", "{{index .MI8S 3}}", "i83", tVal, true},
{"map MUI8S", "{{index .MUI8S 2}}", "u82", 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. // Len.
{"slice", "{{len .SI}}", "3", tVal, true}, {"slice", "{{len .SI}}", "3", tVal, true},
{"map", "{{len .MSI }}", "3", tVal, true}, {"map", "{{len .MSI }}", "3", tVal, true},
......
...@@ -34,6 +34,7 @@ var builtins = FuncMap{ ...@@ -34,6 +34,7 @@ var builtins = FuncMap{
"call": call, "call": call,
"html": HTMLEscaper, "html": HTMLEscaper,
"index": index, "index": index,
"slice": slice,
"js": JSEscaper, "js": JSEscaper,
"len": length, "len": length,
"not": not, "not": not,
...@@ -159,17 +160,36 @@ func intLike(typ reflect.Kind) bool { ...@@ -159,17 +160,36 @@ func intLike(typ reflect.Kind) bool {
return false 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. // Indexing.
// index returns the result of indexing its first argument by the following // 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 // 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. // 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) v := indirectInterface(item)
if !v.IsValid() { if !v.IsValid() {
return reflect.Value{}, fmt.Errorf("index of untyped nil") return reflect.Value{}, fmt.Errorf("index of untyped nil")
} }
for _, i := range indices { for _, i := range indexes {
index := indirectInterface(i) index := indirectInterface(i)
var isNil bool var isNil bool
if v, isNil = indirect(v); isNil { if v, isNil = indirect(v); isNil {
...@@ -177,21 +197,11 @@ func index(item reflect.Value, indices ...reflect.Value) (reflect.Value, error) ...@@ -177,21 +197,11 @@ func index(item reflect.Value, indices ...reflect.Value) (reflect.Value, error)
} }
switch v.Kind() { switch v.Kind() {
case reflect.Array, reflect.Slice, reflect.String: case reflect.Array, reflect.Slice, reflect.String:
var x int64 x, err := indexArg(index, v.Len())
switch index.Kind() { if err != nil {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return reflect.Value{}, err
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())
}
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: case reflect.Map:
index, err := prepareArg(index, v.Type().Key()) index, err := prepareArg(index, v.Type().Key())
if err != nil { if err != nil {
...@@ -212,6 +222,57 @@ func index(item reflect.Value, indices ...reflect.Value) (reflect.Value, error) ...@@ -212,6 +222,57 @@ func index(item reflect.Value, indices ...reflect.Value) (reflect.Value, error)
return v, nil 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
// length returns the length of the item, with an error if it has no defined length. // length returns the length of the item, with an error if it has no defined length.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment