]> Cypherpunks repositories - gostls13.git/commitdiff
text/template: add a slice function to the predefined global functions
authorAriel Mashraki <ariel@mashraki.co.il>
Sun, 10 Feb 2019 20:44:03 +0000 (22:44 +0200)
committerRob Pike <r@golang.org>
Thu, 23 May 2019 08:01:24 +0000 (08:01 +0000)
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>
src/text/template/doc.go
src/text/template/exec_test.go
src/text/template/funcs.go

index 0179dec5c33de6ec9714a8311324d5dd5ca23f86..dbffaa4958001de16712cff31dc7a909f3181483 100644 (file)
@@ -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.
index 63ccd5c3c001b1f29621e300491c53ea851d5754..81f9e044764710b45c03535081ed5ad59174ebb6 100644 (file)
@@ -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},
index a626247c2cc12bc2a11bd2188d422716909306a5..248dbcf22ed99c098a7ad7081e7fc3f9f33fabe8 100644 (file)
@@ -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())
-                       }
-                       if x < 0 || x >= int64(v.Len()) {
-                               return reflect.Value{}, fmt.Errorf("index out of range: %d", x)
+                       x, err := indexArg(index, v.Len())
+                       if err != nil {
+                               return reflect.Value{}, err
                        }
-                       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.