diff --git a/tpl/template_funcs.go b/tpl/template_funcs.go index ff6b4a23a..6cecd2348 100644 --- a/tpl/template_funcs.go +++ b/tpl/template_funcs.go @@ -177,7 +177,7 @@ func Substr(a interface{}, nums ...interface{}) (string, error) { } var start, length int - toInt := func (v interface{}, message string) (int, error) { + toInt := func(v interface{}, message string) (int, error) { switch i := v.(type) { case int: return i, nil @@ -388,6 +388,42 @@ func First(limit interface{}, seq interface{}) (interface{}, error) { return seqv.Slice(0, limitv).Interface(), nil } +// After is exposed to templates, to iterate over all the items after N in a +// rangeable list. It's meant to accompany First +func After(limit interface{}, seq interface{}) (interface{}, error) { + + if limit == nil || seq == nil { + return nil, errors.New("both limit and seq must be provided") + } + + limitv, err := cast.ToIntE(limit) + + if err != nil { + return nil, err + } + + if limitv < 1 { + return nil, errors.New("can't return negative/empty count of items from sequence") + } + + seqv := reflect.ValueOf(seq) + seqv, isNil := indirect(seqv) + if isNil { + return nil, errors.New("can't iterate over a nil value") + } + + switch seqv.Kind() { + case reflect.Array, reflect.Slice, reflect.String: + // okay + default: + return nil, errors.New("can't iterate over " + reflect.ValueOf(seq).Type().String()) + } + if limitv >= seqv.Len() { + return nil, errors.New("no items left") + } + return seqv.Slice(limitv, seqv.Len()).Interface(), nil +} + var ( zero reflect.Value errorType = reflect.TypeOf((*error)(nil)).Elem() @@ -1252,6 +1288,7 @@ func init() { "relURL": func(a string) template.HTML { return template.HTML(helpers.RelURL(a)) }, "markdownify": Markdownify, "first": First, + "after": After, "where": Where, "delimit": Delimit, "sort": Sort, diff --git a/tpl/template_funcs_test.go b/tpl/template_funcs_test.go index c8ed00272..2df82457c 100644 --- a/tpl/template_funcs_test.go +++ b/tpl/template_funcs_test.go @@ -4,13 +4,14 @@ import ( "bytes" "errors" "fmt" - "github.com/stretchr/testify/assert" "html/template" "path" "reflect" "runtime" "testing" "time" + + "github.com/stretchr/testify/assert" ) type tstNoStringer struct { @@ -252,6 +253,40 @@ func TestFirst(t *testing.T) { } } +func TestAfter(t *testing.T) { + for i, this := range []struct { + count interface{} + sequence interface{} + expect interface{} + }{ + {int(2), []string{"a", "b", "c", "d"}, []string{"c", "d"}}, + {int32(3), []string{"a", "b"}, false}, + {int64(2), []int{100, 200, 300}, []int{300}}, + {100, []int{100, 200}, false}, + {"1", []int{100, 200, 300}, []int{200, 300}}, + {int64(-1), []int{100, 200, 300}, false}, + {"noint", []int{100, 200, 300}, false}, + {1, nil, false}, + {nil, []int{100}, false}, + {1, t, false}, + } { + results, err := After(this.count, this.sequence) + if b, ok := this.expect.(bool); ok && !b { + if err == nil { + t.Errorf("[%d] First didn't return an expected error", i) + } + } else { + if err != nil { + t.Errorf("[%d] failed: %s", i, err) + continue + } + if !reflect.DeepEqual(results, this.expect) { + t.Errorf("[%d] First %d items, got %v but expected %v", i, this.count, results, this.expect) + } + } + } +} + func TestIn(t *testing.T) { for i, this := range []struct { v1 interface{}