diff --git a/helpers/general.go b/helpers/general.go index 83854389eb5..ff389ed4b80 100644 --- a/helpers/general.go +++ b/helpers/general.go @@ -19,6 +19,7 @@ import ( "encoding/hex" "errors" "fmt" + "github.com/spf13/cast" bp "github.com/spf13/hugo/bufferpool" "github.com/spf13/viper" "io" @@ -125,6 +126,74 @@ func Md5String(f string) string { return hex.EncodeToString(h.Sum([]byte{})) } +// Seq creates a sequence of integers. +// It's named and used as GNU's seq. +// Examples: +// 3 => 1, 2, 3 +// 1 2 4 => 1, 3 +// -3 => -1, -2, -3 +// 1 4 => 1, 2, 3, 4 +// 1 -2 => 1, 0, -1, -2 +func Seq(args ...interface{}) ([]int, error) { + if len(args) < 1 || len(args) > 3 { + return nil, errors.New("Seq, invalid number of args: 'first' 'increment' (optional) 'last' (optional)") + } + + intArgs := cast.ToIntSlice(args) + + var inc int = 1 + var last int + var first = intArgs[0] + + if len(intArgs) == 1 { + last = first + if last == 0 { + return []int{}, nil + } else if last > 0 { + first = 1 + } else { + first = -1 + inc = -1 + } + } else if len(intArgs) == 2 { + last = intArgs[1] + if last < first { + inc = -1 + } + } else { + inc = intArgs[1] + last = intArgs[2] + if inc == 0 { + return nil, errors.New("'increment' must not be 0") + } + if first < last && inc < 0 { + return nil, errors.New("'increment' must be > 0") + } + if first > last && inc > 0 { + return nil, errors.New("'increment' must be < 0") + } + } + + size := int(((last - first) / inc) + 1) + + // sanity check + if size > 2000 { + return nil, errors.New("size of result exeeds limit") + } + + seq := make([]int, size) + val := first + for i := 0; ; i++ { + seq[i] = val + val += inc + if (inc < 0 && val < last) || (inc > 0 && val > last) { + break + } + } + + return seq, nil +} + // DoArithmetic performs arithmetic operations (+,-,*,/) using reflection to // determine the type of the two terms. func DoArithmetic(a, b interface{}, op rune) (interface{}, error) { diff --git a/helpers/general_test.go b/helpers/general_test.go index 527ba6facdf..36baf3934fc 100644 --- a/helpers/general_test.go +++ b/helpers/general_test.go @@ -130,6 +130,49 @@ func TestMd5StringEmpty(t *testing.T) { } } +func TestSeq(t *testing.T) { + for i, this := range []struct { + in []interface{} + expect interface{} + }{ + {[]interface{}{-2, 5}, []int{-2, -1, 0, 1, 2, 3, 4, 5}}, + {[]interface{}{1, 2, 4}, []int{1, 3}}, + {[]interface{}{1}, []int{1}}, + {[]interface{}{3}, []int{1, 2, 3}}, + {[]interface{}{3.2}, []int{1, 2, 3}}, + {[]interface{}{0}, []int{}}, + {[]interface{}{-1}, []int{-1}}, + {[]interface{}{-3}, []int{-1, -2, -3}}, + {[]interface{}{3, -2}, []int{3, 2, 1, 0, -1, -2}}, + {[]interface{}{6, -2, 2}, []int{6, 4, 2}}, + {[]interface{}{1, 0, 2}, false}, + {[]interface{}{1, -1, 2}, false}, + {[]interface{}{2, 1, 1}, false}, + {[]interface{}{2, 1, 1, 1}, false}, + {[]interface{}{2001}, false}, + {[]interface{}{}, false}, + {[]interface{}{t}, []int{}}, + {nil, false}, + } { + + result, err := Seq(this.in...) + + if b, ok := this.expect.(bool); ok && !b { + if err == nil { + t.Errorf("[%d] TestSeq didn't return an expected error %s", i) + } + } else { + if err != nil { + t.Errorf("[%d] failed: %s", i, err) + continue + } + if !reflect.DeepEqual(result, this.expect) { + t.Errorf("[%d] TestSeq got %v but expected %v", i, result, this.expect) + } + } + } +} + func TestDoArithmetic(t *testing.T) { for i, this := range []struct { a interface{} diff --git a/tpl/template.go b/tpl/template.go index 08360350002..1753778d62b 100644 --- a/tpl/template.go +++ b/tpl/template.go @@ -1314,6 +1314,7 @@ func init() { "dateFormat": DateFormat, "getJson": GetJson, "getCsv": GetCsv, + "seq": helpers.Seq, } }