From 7190e02a31f730c5997e27c29e1381dc22ebffd8 Mon Sep 17 00:00:00 2001 From: 42Atomys Date: Wed, 8 May 2024 23:59:37 +0200 Subject: [PATCH] =?UTF-8?q?refactor:=20complete=20slices=20category=20migr?= =?UTF-8?q?ation=20=F0=9F=8C=B1=E2=9C=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- NOTABLE_CHANGES_FROM_SPRIG_NOTES.md | 12 + alias.go | 1 + migrated_functions.go | 451 -------------- slices_functions.go | 917 ++++++++++++++++++++++++++++ slices_functions_test.go | 347 +++++++++++ sprout.go | 2 +- strings_functions.go | 2 +- to_migrate_test.go | 336 ---------- 8 files changed, 1279 insertions(+), 789 deletions(-) create mode 100644 slices_functions.go create mode 100644 slices_functions_test.go diff --git a/NOTABLE_CHANGES_FROM_SPRIG_NOTES.md b/NOTABLE_CHANGES_FROM_SPRIG_NOTES.md index eb783c7..f4f6465 100644 --- a/NOTABLE_CHANGES_FROM_SPRIG_NOTES.md +++ b/NOTABLE_CHANGES_FROM_SPRIG_NOTES.md @@ -54,3 +54,15 @@ In sprout this function support int32 and *time.Time and return the correct resu ### DateRound In sprig When we pass a negative value, it will return the correct duration but in positive value. In sprout When we pass a negative value, it will return the correct duration with in negative value. + +### Append, Prepend, Concat, Chunk, Uniq, Compact, Slice, Without, Rest, Initial, Reverse +In sprig all these functions cause internal panic. +In sprout all these functions return empty slice when an error occurs. + +### First, Last +In sprig all these functions cause internal panic. +In sprout all these functions return nil when an error occurs. + +### MustAppend, MustPrepend, MustConcat, MustChunk, MustUniq, MustCompact, MustSlice, MustWithout, MustRest, MustInitial, MustReverse +In sprig all these functions cause segfault when lsit are nil. +In sprout all these functions return nil and an error when an error occurs. diff --git a/alias.go b/alias.go index 101a69f..853c15d 100644 --- a/alias.go +++ b/alias.go @@ -39,6 +39,7 @@ var bc_registerSprigFuncs = FunctionAliasMap{ "pathIsAbs": {"isAbs"}, //! Deprecated: Should use pathIsAbs instead "expandEnv": {"expandenv"}, //! Deprecated: Should use expandEnv instead "dateAgo": {"ago"}, //! Deprecated: Should use dateAgo instead + "strSlice": {"toStrings"}, //! Deprecated: Should use strSlice instead } //\ BACKWARDS COMPATIBILITY diff --git a/migrated_functions.go b/migrated_functions.go index 92eb5e2..5291ade 100644 --- a/migrated_functions.go +++ b/migrated_functions.go @@ -25,13 +25,11 @@ import ( "fmt" "hash/adler32" "io" - "math" "math/big" mathrand "math/rand" "net" "net/url" "reflect" - "sort" "strconv" "strings" "time" @@ -44,11 +42,6 @@ import ( "golang.org/x/crypto/scrypt" ) -// ! FUTURE: Rename this function to be more explicit -func (fh *FunctionHandler) SplitList(sep string, str string) []string { - return strings.Split(str, sep) -} - func (fh *FunctionHandler) Strval(v any) string { switch v := v.(type) { case string: @@ -65,44 +58,6 @@ func (fh *FunctionHandler) Strval(v any) string { } } -func (fh *FunctionHandler) Strslice(v any) []string { - if v == nil { - return []string{} - } - - // Handle []string type efficiently without reflection. - if strs, ok := v.([]string); ok { - return strs - } - - // For slices of any, convert each element to a string, skipping nil values. - if interfaces, ok := v.([]any); ok { - var result []string - for _, s := range interfaces { - if s != nil { - result = append(result, fh.Strval(s)) - } - } - return result - } - - // Use reflection for other slice types to convert them to []string. - val := reflect.ValueOf(v) - if val.Kind() == reflect.Slice || val.Kind() == reflect.Array { - var result []string - for i := 0; i < val.Len(); i++ { - value := val.Index(i).Interface() - if value != nil { - result = append(result, fh.Strval(value)) - } - } - return result - } - - // If it's not a slice, array, or nil, return a slice with the string representation of v. - return []string{fh.Strval(v)} -} - func (fh *FunctionHandler) FillMapWithParts(parts []string) map[string]string { res := make(map[string]string, len(parts)) for i, v := range parts { @@ -208,328 +163,6 @@ func (fh *FunctionHandler) GetHostByName(name string) string { return addrs[mathrand.Intn(len(addrs))] } -func (fh *FunctionHandler) List(v ...any) []any { - return v -} - -func (fh *FunctionHandler) Append(list any, v any) []any { - l, err := fh.MustAppend(list, v) - if err != nil { - panic(err) - } - - return l -} - -func (fh *FunctionHandler) MustAppend(list any, v any) ([]any, error) { - tp := reflect.TypeOf(list).Kind() - switch tp { - case reflect.Slice, reflect.Array: - l2 := reflect.ValueOf(list) - - l := l2.Len() - nl := make([]any, l) - for i := 0; i < l; i++ { - nl[i] = l2.Index(i).Interface() - } - - return append(nl, v), nil - - default: - return nil, fmt.Errorf("Cannot push on type %s", tp) - } -} - -func (fh *FunctionHandler) Prepend(list any, v any) []any { - l, err := fh.MustPrepend(list, v) - if err != nil { - panic(err) - } - - return l -} - -func (fh *FunctionHandler) MustPrepend(list any, v any) ([]any, error) { - //return append([]any{v}, list...) - - tp := reflect.TypeOf(list).Kind() - switch tp { - case reflect.Slice, reflect.Array: - l2 := reflect.ValueOf(list) - - l := l2.Len() - nl := make([]any, l) - for i := 0; i < l; i++ { - nl[i] = l2.Index(i).Interface() - } - - return append([]any{v}, nl...), nil - - default: - return nil, fmt.Errorf("Cannot prepend on type %s", tp) - } -} - -func (fh *FunctionHandler) Chunk(size int, list any) [][]any { - l, err := fh.MustChunk(size, list) - if err != nil { - panic(err) - } - - return l -} - -func (fh *FunctionHandler) MustChunk(size int, list any) ([][]any, error) { - tp := reflect.TypeOf(list).Kind() - switch tp { - case reflect.Slice, reflect.Array: - l2 := reflect.ValueOf(list) - - l := l2.Len() - - cs := int(math.Floor(float64(l-1)/float64(size)) + 1) - nl := make([][]any, cs) - - for i := 0; i < cs; i++ { - clen := size - if i == cs-1 { - clen = int(math.Floor(math.Mod(float64(l), float64(size)))) - if clen == 0 { - clen = size - } - } - - nl[i] = make([]any, clen) - - for j := 0; j < clen; j++ { - ix := i*size + j - nl[i][j] = l2.Index(ix).Interface() - } - } - - return nl, nil - - default: - return nil, fmt.Errorf("Cannot chunk type %s", tp) - } -} - -func (fh *FunctionHandler) Last(list any) any { - l, err := fh.MustLast(list) - if err != nil { - panic(err) - } - - return l -} - -func (fh *FunctionHandler) MustLast(list any) (any, error) { - tp := reflect.TypeOf(list).Kind() - switch tp { - case reflect.Slice, reflect.Array: - l2 := reflect.ValueOf(list) - - l := l2.Len() - if l == 0 { - return nil, nil - } - - return l2.Index(l - 1).Interface(), nil - default: - return nil, fmt.Errorf("Cannot find last on type %s", tp) - } -} - -func (fh *FunctionHandler) First(list any) any { - l, err := fh.MustFirst(list) - if err != nil { - panic(err) - } - - return l -} - -func (fh *FunctionHandler) MustFirst(list any) (any, error) { - tp := reflect.TypeOf(list).Kind() - switch tp { - case reflect.Slice, reflect.Array: - l2 := reflect.ValueOf(list) - - l := l2.Len() - if l == 0 { - return nil, nil - } - - return l2.Index(0).Interface(), nil - default: - return nil, fmt.Errorf("Cannot find first on type %s", tp) - } -} - -func (fh *FunctionHandler) Rest(list any) []any { - l, err := fh.MustRest(list) - if err != nil { - panic(err) - } - - return l -} - -func (fh *FunctionHandler) MustRest(list any) ([]any, error) { - tp := reflect.TypeOf(list).Kind() - switch tp { - case reflect.Slice, reflect.Array: - l2 := reflect.ValueOf(list) - - l := l2.Len() - if l == 0 { - return nil, nil - } - - nl := make([]any, l-1) - for i := 1; i < l; i++ { - nl[i-1] = l2.Index(i).Interface() - } - - return nl, nil - default: - return nil, fmt.Errorf("Cannot find rest on type %s", tp) - } -} - -func (fh *FunctionHandler) Initial(list any) []any { - l, err := fh.MustInitial(list) - if err != nil { - panic(err) - } - - return l -} - -func (fh *FunctionHandler) MustInitial(list any) ([]any, error) { - tp := reflect.TypeOf(list).Kind() - switch tp { - case reflect.Slice, reflect.Array: - l2 := reflect.ValueOf(list) - - l := l2.Len() - if l == 0 { - return nil, nil - } - - nl := make([]any, l-1) - for i := 0; i < l-1; i++ { - nl[i] = l2.Index(i).Interface() - } - - return nl, nil - default: - return nil, fmt.Errorf("Cannot find initial on type %s", tp) - } -} - -func (fh *FunctionHandler) SortAlpha(list any) []string { - k := reflect.Indirect(reflect.ValueOf(list)).Kind() - switch k { - case reflect.Slice, reflect.Array: - a := fh.Strslice(list) - s := sort.StringSlice(a) - s.Sort() - return s - } - return []string{fh.Strval(list)} -} - -func (fh *FunctionHandler) Reverse(v any) []any { - l, err := fh.MustReverse(v) - if err != nil { - panic(err) - } - - return l -} - -func (fh *FunctionHandler) MustReverse(v any) ([]any, error) { - tp := reflect.TypeOf(v).Kind() - switch tp { - case reflect.Slice, reflect.Array: - l2 := reflect.ValueOf(v) - - l := l2.Len() - // We do not sort in place because the incoming array should not be altered. - nl := make([]any, l) - for i := 0; i < l; i++ { - nl[l-i-1] = l2.Index(i).Interface() - } - - return nl, nil - default: - return nil, fmt.Errorf("Cannot find reverse on type %s", tp) - } -} - -func (fh *FunctionHandler) Compact(list any) []any { - l, err := fh.MustCompact(list) - if err != nil { - panic(err) - } - - return l -} - -func (fh *FunctionHandler) MustCompact(list any) ([]any, error) { - tp := reflect.TypeOf(list).Kind() - switch tp { - case reflect.Slice, reflect.Array: - l2 := reflect.ValueOf(list) - - l := l2.Len() - nl := []any{} - var item any - for i := 0; i < l; i++ { - item = l2.Index(i).Interface() - if !fh.Empty(item) { - nl = append(nl, item) - } - } - - return nl, nil - default: - return nil, fmt.Errorf("Cannot compact on type %s", tp) - } -} - -func (fh *FunctionHandler) Uniq(list any) []any { - l, err := fh.MustUniq(list) - if err != nil { - panic(err) - } - - return l -} - -func (fh *FunctionHandler) MustUniq(list any) ([]any, error) { - tp := reflect.TypeOf(list).Kind() - switch tp { - case reflect.Slice, reflect.Array: - l2 := reflect.ValueOf(list) - - l := l2.Len() - dest := []any{} - var item any - for i := 0; i < l; i++ { - item = l2.Index(i).Interface() - if !fh.InList(dest, item) { - dest = append(dest, item) - } - } - - return dest, nil - default: - return nil, fmt.Errorf("Cannot find uniq on type %s", tp) - } -} - func (fh *FunctionHandler) InList(haystack []any, needle any) bool { for _, h := range haystack { if reflect.DeepEqual(needle, h) { @@ -539,37 +172,6 @@ func (fh *FunctionHandler) InList(haystack []any, needle any) bool { return false } -func (fh *FunctionHandler) Without(list any, omit ...any) []any { - l, err := fh.MustWithout(list, omit...) - if err != nil { - panic(err) - } - - return l -} - -func (fh *FunctionHandler) MustWithout(list any, omit ...any) ([]any, error) { - tp := reflect.TypeOf(list).Kind() - switch tp { - case reflect.Slice, reflect.Array: - l2 := reflect.ValueOf(list) - - l := l2.Len() - res := []any{} - var item any - for i := 0; i < l; i++ { - item = l2.Index(i).Interface() - if !fh.InList(omit, item) { - res = append(res, item) - } - } - - return res, nil - default: - return nil, fmt.Errorf("Cannot find without on type %s", tp) - } -} - func (fh *FunctionHandler) Has(needle any, haystack any) bool { l, err := fh.MustHas(needle, haystack) if err != nil { @@ -602,59 +204,6 @@ func (fh *FunctionHandler) MustHas(needle any, haystack any) (bool, error) { } } -func (fh *FunctionHandler) Slice(list any, indices ...any) any { - l, err := fh.MustSlice(list, indices...) - if err != nil { - panic(err) - } - - return l -} - -func (fh *FunctionHandler) MustSlice(list any, indices ...any) (any, error) { - tp := reflect.TypeOf(list).Kind() - switch tp { - case reflect.Slice, reflect.Array: - l2 := reflect.ValueOf(list) - - l := l2.Len() - if l == 0 { - return nil, nil - } - - var start, end int - if len(indices) > 0 { - start = fh.ToInt(indices[0]) - } - if len(indices) < 2 { - end = l - } else { - end = fh.ToInt(indices[1]) - } - - return l2.Slice(start, end).Interface(), nil - default: - return nil, fmt.Errorf("list should be type of slice or array but %s", tp) - } -} - -func (fh *FunctionHandler) Concat(lists ...any) any { - var res []any - for _, list := range lists { - tp := reflect.TypeOf(list).Kind() - switch tp { - case reflect.Slice, reflect.Array: - l2 := reflect.ValueOf(list) - for i := 0; i < l2.Len(); i++ { - res = append(res, l2.Index(i).Interface()) - } - default: - panic(fmt.Sprintf("Cannot concat type %s as list", tp)) - } - } - return res -} - func (fh *FunctionHandler) Get(d map[string]any, key string) any { if val, ok := d[key]; ok { return val diff --git a/slices_functions.go b/slices_functions.go new file mode 100644 index 0000000..e3cdb74 --- /dev/null +++ b/slices_functions.go @@ -0,0 +1,917 @@ +package sprout + +import ( + "fmt" + "math" + "reflect" + "sort" + "strings" +) + +// List creates a list from the provided elements. +// +// Parameters: +// +// values ...any - the elements to include in the list. +// +// Returns: +// +// []any - the created list containing the provided elements. +// +// Example: +// +// {{ 1, 2, 3 | list }} // Output: [1, 2, 3] +func (fh *FunctionHandler) List(values ...any) []any { + return values +} + +// Append adds an element to the end of the list. +// +// Parameters: +// +// list any - the original list to append to. +// v any - the element to append. +// +// Returns: +// +// []any - the new list with the element appended. +// +// Example: +// +// {{ append ["a", "b"], "c" }} // Output: ["a", "b", "c"] +func (fh *FunctionHandler) Append(list any, v any) []any { + result, err := fh.MustAppend(list, v) + if err != nil { + return []any{} + // panic(err) + } + + return result +} + +// Prepend adds an element to the beginning of the list. +// +// Parameters: +// +// list any - the original list to prepend to. +// v any - the element to prepend. +// +// Returns: +// +// []any - the new list with the element prepended. +// +// Example: +// +// {{ prepend ["b", "c"], "a" }} // Output: ["a", "b", "c"] +func (fh *FunctionHandler) Prepend(list any, v any) []any { + result, err := fh.MustPrepend(list, v) + if err != nil { + return []any{} + // panic(err) + } + + return result +} + +// Concat merges multiple lists into a single list. +// +// Parameters: +// +// lists ...any - the lists to concatenate. +// +// Returns: +// +// any - a single concatenated list containing elements from all provided lists. +// +// Example: +// +// {{ ["c", "d"] | concat ["a", "b"] }} // Output: ["a", "b", "c", "d"] +func (fh *FunctionHandler) Concat(lists ...any) any { + var res []any + for _, list := range lists { + if list == nil { + continue + } + + tp := reflect.TypeOf(list).Kind() + switch tp { + case reflect.Slice, reflect.Array: + valueOfList := reflect.ValueOf(list) + for i := 0; i < valueOfList.Len(); i++ { + res = append(res, valueOfList.Index(i).Interface()) + } + default: + continue + // panic(fmt.Sprintf("cannot concat type %s as list", tp)) + } + } + return res +} + +// Chunk divides a list into chunks of specified size. +// +// Parameters: +// +// size int - the size of each chunk. +// list any - the list to divide. +// +// Returns: +// +// [][]any - a list of chunks. +// +// Example: +// +// {{ chunk 2, ["a", "b", "c", "d"] }} // Output: [["a", "b"], ["c", "d"]] +func (fh *FunctionHandler) Chunk(size int, list any) [][]any { + result, err := fh.MustChunk(size, list) + if err != nil { + return [][]any{} + // panic(err) + } + + return result +} + +// Uniq removes duplicate elements from a list. +// +// Parameters: +// +// list any - the list from which to remove duplicates. +// +// Returns: +// +// []any - a list containing only unique elements. +// +// Example: +// +// {{ ["a", "b", "a", "c"] | uniq }} // Output: ["a", "b", "c"] +func (fh *FunctionHandler) Uniq(list any) []any { + result, err := fh.MustUniq(list) + if err != nil { + return []any{} + // panic(err) + } + + return result +} + +// Compact removes nil and zero-value elements from a list. +// +// Parameters: +// +// list any - the list to compact. +// +// Returns: +// +// []any - the list without nil or zero-value elements. +// +// Example: +// +// {{ [0, 1, nil, 2, "", 3] | compact }} // Output: [1, 2, 3] +func (fh *FunctionHandler) Compact(list any) []any { + result, err := fh.MustCompact(list) + if err != nil { + return []any{} + // panic(err) + } + + return result +} + +// Slice extracts a slice from a list between two indices. +// +// Parameters: +// +// list any - the list to slice. +// indices ...any - the start and optional end indices; if end is omitted, +// +// slices to the end. +// +// Returns: +// +// any - the sliced part of the list. +// +// Example: +// +// {{ slice [1, 2, 3, 4, 5], 1, 3 }} // Output: [2, 3] +func (fh *FunctionHandler) Slice(list any, indices ...any) any { + result, err := fh.MustSlice(list, indices...) + if err != nil { + return []any{} + // panic(err) + } + + return result +} + +// Without returns a new list excluding specified elements. +// +// Parameters: +// +// list any - the original list. +// omit ...any - elements to exclude from the new list. +// +// Returns: +// +// []any - the list excluding the specified elements. +// +// Example: +// +// {{ without [1, 2, 3, 4], 2, 4 }} // Output: [1, 3] +func (fh *FunctionHandler) Without(list any, omit ...any) []any { + result, err := fh.MustWithout(list, omit...) + if err != nil { + return []any{} + // panic(err) + } + + return result +} + +// Rest returns all elements of a list except the first. +// +// Parameters: +// +// list any - the list to process. +// +// Returns: +// +// []any - the list without the first element. +// +// Example: +// +// {{ [1, 2, 3, 4] | rest }} // Output: [2, 3, 4] +func (fh *FunctionHandler) Rest(list any) []any { + result, err := fh.MustRest(list) + if err != nil { + return []any{} + // panic(err) + } + + return result +} + +// Initial returns all elements of a list except the last. +// +// Parameters: +// +// list any - the list to process. +// +// Returns: +// +// []any - the list without the last element. +// +// Example: +// +// {{ [1, 2, 3, 4] | initial }} // Output: [1, 2, 3] +func (fh *FunctionHandler) Initial(list any) []any { + result, err := fh.MustInitial(list) + if err != nil { + return []any{} + // panic(err) + } + + return result +} + +// First returns the first element of a list. +// +// Parameters: +// +// list any - the list from which to take the first element. +// +// Returns: +// +// any - the first element of the list. +// +// Example: +// +// {{ [1, 2, 3, 4] | first }} // Output: 1 +func (fh *FunctionHandler) First(list any) any { + result, err := fh.MustFirst(list) + if err != nil { + return nil + // panic(err) + } + + return result +} + +// Last returns the last element of a list. +// +// Parameters: +// +// list any - the list from which to take the last element. +// +// Returns: +// +// any - the last element of the list. +// +// Example: +// +// {{ [1, 2, 3, 4] | last }} // Output: 4 +func (fh *FunctionHandler) Last(list any) any { + result, err := fh.MustLast(list) + if err != nil { + return nil + // panic(err) + } + + return result +} + +// Reverse returns a new list with the elements in reverse order. +// +// Parameters: +// +// list any - the list to reverse. +// +// Returns: +// +// []any - the list in reverse order. +// +// Example: +// +// {{ [1, 2, 3, 4] | reverse }} // Output: [4, 3, 2, 1] +func (fh *FunctionHandler) Reverse(list any) []any { + result, err := fh.MustReverse(list) + if err != nil { + return []any{} + // panic(err) + } + + return result +} + +// SortAlpha sorts a list of strings in alphabetical order. +// +// Parameters: +// +// list any - the list of strings to sort. +// +// Returns: +// +// []string - the sorted list. +// +// Example: +// +// {{ ["d", "b", "a", "c"] | sortAlpha }} // Output: ["a", "b", "c", "d"] +func (fh *FunctionHandler) SortAlpha(list any) []string { + kind := reflect.Indirect(reflect.ValueOf(list)).Kind() + switch kind { + case reflect.Slice, reflect.Array: + sortedList := sort.StringSlice(fh.StrSlice(list)) + sortedList.Sort() + return sortedList + } + return []string{fh.Strval(list)} +} + +// SplitList divides a string into a slice of substrings separated by the +// specified separator. +// +// ! FUTURE: Rename this function to be more explicit +// +// Parameters: +// +// sep string - the delimiter used to split the string. +// str string - the string to split. +// +// Returns: +// +// []string - a slice containing the substrings obtained from splitting the input string. +// +// Example: +// +// {{ ", ", "one, two, three" | splitList }} // Output: ["one", "two", "three"] +func (fh *FunctionHandler) SplitList(sep string, str string) []string { + return strings.Split(str, sep) +} + +func (fh *FunctionHandler) StrSlice(value any) []string { + if value == nil { + return []string{} + } + + // Handle []string type efficiently without reflection. + if strs, ok := value.([]string); ok { + return strs + } + + // For slices of any, convert each element to a string, skipping nil values. + if interfaces, ok := value.([]any); ok { + var result []string + for _, s := range interfaces { + if s != nil { + result = append(result, fh.Strval(s)) + } + } + return result + } + + // Use reflection for other slice types to convert them to []string. + reflectedValue := reflect.ValueOf(value) + if reflectedValue.Kind() == reflect.Slice || reflectedValue.Kind() == reflect.Array { + var result []string + for i := 0; i < reflectedValue.Len(); i++ { + value := reflectedValue.Index(i).Interface() + if value != nil { + result = append(result, fh.Strval(value)) + } + } + return result + } + + // If it's not a slice, array, or nil, return a slice with the string representation of v. + return []string{fh.Strval(value)} +} + +// MustAppend appends an element to a slice or array, returning an error if the +// operation isn't applicable. +// +// Parameters: +// +// list any - the original list to append to. +// v any - the element to append. +// +// Returns: +// +// []any - the new list with the element appended. +// error - error if the list is nil or not a slice/array. +// +// Example: +// +// {{ mustAppend ["a", "b"], "c" }} // Output: ["a", "b", "c"], nil +func (fh *FunctionHandler) MustAppend(list any, v any) ([]any, error) { + if list == nil { + return nil, fmt.Errorf("cannot append to nil") + } + + tp := reflect.TypeOf(list).Kind() + switch tp { + case reflect.Slice, reflect.Array: + valueOfList := reflect.ValueOf(list) + + length := valueOfList.Len() + result := make([]any, length) + for i := 0; i < length; i++ { + result[i] = valueOfList.Index(i).Interface() + } + + return append(result, v), nil + + default: + return nil, fmt.Errorf("cannot append on type %s", tp) + } +} + +// MustPrepend prepends an element to a slice or array, returning an error if +// the operation isn't applicable. +// +// Parameters: +// +// list any - the original list to prepend to. +// v any - the element to prepend. +// +// Returns: +// +// []any - the new list with the element prepended. +// error - error if the list is nil or not a slice/array. +// +// Example: +// +// {{ mustPrepend ["b", "c"], "a" }} // Output: ["a", "b", "c"], nil +func (fh *FunctionHandler) MustPrepend(list any, v any) ([]any, error) { + if list == nil { + return nil, fmt.Errorf("cannot prepend to nil") + } + + tp := reflect.TypeOf(list).Kind() + switch tp { + case reflect.Slice, reflect.Array: + valueOfList := reflect.ValueOf(list) + + length := valueOfList.Len() + result := make([]any, length) + for i := 0; i < length; i++ { + result[i] = valueOfList.Index(i).Interface() + } + + return append([]any{v}, result...), nil + + default: + return nil, fmt.Errorf("cannot prepend on type %s", tp) + } +} + +// MustChunk divides a list into chunks of specified size, returning an error +// if the list is nil or not a slice/array. +// +// Parameters: +// +// size int - the maximum size of each chunk. +// list any - the list to chunk. +// +// Returns: +// +// [][]any - a list of chunks. +// error - error if the list is nil or not a slice/array. +// +// Example: +// +// {{ ["a", "b", "c", "d"] | mustChunk 2 }} // Output: [["a", "b"], ["c", "d"]], nil +func (fh *FunctionHandler) MustChunk(size int, list any) ([][]any, error) { + if list == nil { + return nil, fmt.Errorf("cannot chunk nil") + } + + tp := reflect.TypeOf(list).Kind() + switch tp { + case reflect.Slice, reflect.Array: + valueOfList := reflect.ValueOf(list) + + length := valueOfList.Len() + + chunkCount := int(math.Floor(float64(length-1)/float64(size)) + 1) + result := make([][]any, chunkCount) + + for i := 0; i < chunkCount; i++ { + chunkLength := size + if i == chunkCount-1 { + chunkLength = int(math.Floor(math.Mod(float64(length), float64(size)))) + if chunkLength == 0 { + chunkLength = size + } + } + + result[i] = make([]any, chunkLength) + + for j := 0; j < chunkLength; j++ { + ix := i*size + j + result[i][j] = valueOfList.Index(ix).Interface() + } + } + + return result, nil + + default: + return nil, fmt.Errorf("cannot chunk type %s", tp) + } +} + +// MustUniq returns a new slice containing unique elements of the given list, +// preserving order. +// +// Parameters: +// +// list any - the list from which to remove duplicates. +// +// Returns: +// +// []any - a list containing only the unique elements. +// error - error if the list is nil or not a slice/array. +// +// Example: +// +// {{ ["a", "b", "a", "c"] | mustUniq }} // Output: ["a", "b", "c"], nil +func (fh *FunctionHandler) MustUniq(list any) ([]any, error) { + if list == nil { + return nil, fmt.Errorf("cannot uniq nil") + } + + tp := reflect.TypeOf(list).Kind() + switch tp { + case reflect.Slice, reflect.Array: + valueOfList := reflect.ValueOf(list) + + length := valueOfList.Len() + result := []any{} + var item any + for i := 0; i < length; i++ { + item = valueOfList.Index(i).Interface() + if !fh.InList(result, item) { + result = append(result, item) + } + } + + return result, nil + default: + return nil, fmt.Errorf("cannot find uniq on type %s", tp) + } +} + +// MustCompact removes nil or zero-value elements from a list. +// +// Parameters: +// +// list any - the list to compact. +// +// Returns: +// +// []any - the list without nil or zero-value elements. +// error - error if the list is nil or not a slice/array. +// +// Example: +// +// {{ [0, 1, nil, 2, "", 3] | mustCompact }} // Output: [1, 2, 3], nil +func (fh *FunctionHandler) MustCompact(list any) ([]any, error) { + if list == nil { + return nil, fmt.Errorf("cannot compact nil") + } + + tp := reflect.TypeOf(list).Kind() + switch tp { + case reflect.Slice, reflect.Array: + valueOfList := reflect.ValueOf(list) + + length := valueOfList.Len() + result := []any{} + var item any + for i := 0; i < length; i++ { + item = valueOfList.Index(i).Interface() + if !fh.Empty(item) { + result = append(result, item) + } + } + + return result, nil + default: + return nil, fmt.Errorf("cannot compact on type %s", tp) + } +} + +// MustSlice extracts a slice from a list between two indices. +// +// Parameters: +// +// list any - the list to slice. +// indices ...any - the start and optional end indices; if end is omitted, +// +// slices to the end. +// +// Returns: +// +// any - the sliced part of the list. +// error - error if the list is nil or not a slice/array. +// +// Example: +// +// {{ mustSlice [1, 2, 3, 4, 5], 1, 3 }} // Output: [2, 3], nil +func (fh *FunctionHandler) MustSlice(list any, indices ...any) (any, error) { + if list == nil { + return nil, fmt.Errorf("cannot slice nil") + } + + tp := reflect.TypeOf(list).Kind() + switch tp { + case reflect.Slice, reflect.Array: + valueOfList := reflect.ValueOf(list) + + length := valueOfList.Len() + if length == 0 { + return nil, nil + } + + var start, end int + if len(indices) > 0 { + start = fh.ToInt(indices[0]) + } + if len(indices) < 2 { + end = length + } else { + end = fh.ToInt(indices[1]) + } + + return valueOfList.Slice(start, end).Interface(), nil + default: + return nil, fmt.Errorf("list should be type of slice or array but %s", tp) + } +} + +// MustWithout returns a new list excluding specified elements. +// +// Parameters: +// +// list any - the original list. +// omit ...any - elements to exclude from the new list. +// +// Returns: +// +// []any - the list excluding the specified elements. +// error - error if the list is nil or not a slice/array. +// +// Example: +// +// {{ mustWithout [1, 2, 3, 4], 2, 4 }} // Output: [1, 3], nil +func (fh *FunctionHandler) MustWithout(list any, omit ...any) ([]any, error) { + if list == nil { + return nil, fmt.Errorf("cannot without nil") + } + + tp := reflect.TypeOf(list).Kind() + switch tp { + case reflect.Slice, reflect.Array: + valueOfList := reflect.ValueOf(list) + + length := valueOfList.Len() + result := []any{} + var item any + for i := 0; i < length; i++ { + item = valueOfList.Index(i).Interface() + if !fh.InList(omit, item) { + result = append(result, item) + } + } + + return result, nil + default: + return nil, fmt.Errorf("cannot find without on type %s", tp) + } +} + +// MustRest returns all elements of a list except the first. +// +// Parameters: +// +// list any - the list to process. +// +// Returns: +// +// []any - the list without the first element. +// error - error if the list is nil or not a slice/array. +// +// Example: +// +// {{ [1, 2, 3, 4] | mustRest }} // Output: [2, 3, 4], nil +func (fh *FunctionHandler) MustRest(list any) ([]any, error) { + if list == nil { + return nil, fmt.Errorf("cannot rest nil") + } + + tp := reflect.TypeOf(list).Kind() + switch tp { + case reflect.Slice, reflect.Array: + valueOfList := reflect.ValueOf(list) + + length := valueOfList.Len() + if length == 0 { + return nil, nil + } + + result := make([]any, length-1) + for i := 1; i < length; i++ { + result[i-1] = valueOfList.Index(i).Interface() + } + + return result, nil + default: + return nil, fmt.Errorf("cannot find rest on type %s", tp) + } +} + +// MustInitial returns all elements of a list except the last. +// +// Parameters: +// +// list any - the list to process. +// +// Returns: +// +// []any - the list without the last element. +// error - error if the list is nil or not a slice/array. +// +// Example: +// +// {{ [1, 2, 3, 4] | mustInitial }} // Output: [1, 2, 3], nil +func (fh *FunctionHandler) MustInitial(list any) ([]any, error) { + if list == nil { + return nil, fmt.Errorf("cannot initial nil") + } + + tp := reflect.TypeOf(list).Kind() + switch tp { + case reflect.Slice, reflect.Array: + valueOfList := reflect.ValueOf(list) + + length := valueOfList.Len() + if length == 0 { + return nil, nil + } + + result := make([]any, length-1) + for i := 0; i < length-1; i++ { + result[i] = valueOfList.Index(i).Interface() + } + + return result, nil + default: + return nil, fmt.Errorf("cannot find initial on type %s", tp) + } +} + +// MustFirst returns the first element of a list. +// +// Parameters: +// +// list any - the list from which to take the first element. +// +// Returns: +// +// any - the first element of the list. +// error - error if the list is nil, empty, or not a slice/array. +// +// Example: +// +// {{ [1, 2, 3, 4] | mustFirst }} // Output: 1, nil +func (fh *FunctionHandler) MustFirst(list any) (any, error) { + if list == nil { + return nil, fmt.Errorf("cannot first nil") + } + + tp := reflect.TypeOf(list).Kind() + switch tp { + case reflect.Slice, reflect.Array: + valueOfList := reflect.ValueOf(list) + + length := valueOfList.Len() + if length == 0 { + return nil, nil + } + + return valueOfList.Index(0).Interface(), nil + default: + return nil, fmt.Errorf("cannot find first on type %s", tp) + } +} + +// MustLast returns the last element of a list. +// +// Parameters: +// +// list any - the list from which to take the last element. +// +// Returns: +// +// any - the last element of the list. +// error - error if the list is nil, empty, or not a slice/array. +// +// Example: +// +// {{ [1, 2, 3, 4] | mustLast }} // Output: 4, nil +func (fh *FunctionHandler) MustLast(list any) (any, error) { + if list == nil { + return nil, fmt.Errorf("cannot last nil") + } + + tp := reflect.TypeOf(list).Kind() + switch tp { + case reflect.Slice, reflect.Array: + valueOfList := reflect.ValueOf(list) + + length := valueOfList.Len() + if length == 0 { + return nil, nil + } + + return valueOfList.Index(length - 1).Interface(), nil + default: + return nil, fmt.Errorf("cannot find last on type %s", tp) + } +} + +// MustReverse returns a new list with the elements in reverse order. +// +// Parameters: +// +// list any - the list to reverse. +// +// Returns: +// +// []any - the list in reverse order. +// error - error if the list is nil or not a slice/array. +// +// Example: +// +// {{ [1, 2, 3, 4] | mustReverse }} // Output: [4, 3, 2, 1], nil +func (fh *FunctionHandler) MustReverse(list any) ([]any, error) { + if list == nil { + return nil, fmt.Errorf("cannot reverse nil") + } + + tp := reflect.TypeOf(list).Kind() + switch tp { + case reflect.Slice, reflect.Array: + valueOfList := reflect.ValueOf(list) + + length := valueOfList.Len() + // We do not sort in place because the incoming array should not be altered. + nl := make([]any, length) + for i := 0; i < length; i++ { + nl[length-i-1] = valueOfList.Index(i).Interface() + } + + return nl, nil + default: + return nil, fmt.Errorf("cannot find reverse on type %s", tp) + } +} diff --git a/slices_functions_test.go b/slices_functions_test.go new file mode 100644 index 0000000..9b42ae3 --- /dev/null +++ b/slices_functions_test.go @@ -0,0 +1,347 @@ +package sprout + +import "testing" + +func TestList(t *testing.T) { + var tests = testCases{ + {"", `{{ list }}`, "[]", nil}, + {"", `{{ .V | list "ab" true 4 5 }}`, "[ab true 4 5 ]", map[string]any{"V": nil}}, + } + + runTestCases(t, tests) +} + +func TestAppend(t *testing.T) { + var tests = testCases{ + {"", `{{ append .V "a" }}`, "[a]", map[string]any{"V": []string{}}}, + {"", `{{ append .V "a" }}`, "[a]", map[string]any{"V": []string(nil)}}, + {"", `{{ append .V "a" }}`, "[]", map[string]any{"V": nil}}, + {"", `{{ append .V "a" }}`, "[x a]", map[string]any{"V": []string{"x"}}}, + } + + runTestCases(t, tests) +} + +func TestPrepend(t *testing.T) { + var tests = testCases{ + {"", `{{ prepend .V "a" }}`, "[a]", map[string]any{"V": []string{}}}, + {"", `{{ prepend .V "a" }}`, "[a]", map[string]any{"V": []string(nil)}}, + {"", `{{ prepend .V "a" }}`, "[]", map[string]any{"V": nil}}, + {"", `{{ prepend .V "a" }}`, "[a x]", map[string]any{"V": []string{"x"}}}, + } + + runTestCases(t, tests) +} + +func TestConcat(t *testing.T) { + var tests = testCases{ + {"", `{{ concat .V (list 1 2 3) }}`, "[a 1 2 3]", map[string]any{"V": []string{"a"}}}, + {"", `{{ list 4 5 | concat (list 1 2 3) }}`, "[1 2 3 4 5]", nil}, + {"", `{{ concat .V (list 1 2 3) }}`, "[1 2 3]", map[string]any{"V": nil}}, + {"", `{{ concat .V (list 1 2 3) }}`, "[1 2 3]", map[string]any{"V": "a"}}, + {"", `{{ concat .V (list 1 2 3) }}`, "[x 1 2 3]", map[string]any{"V": []string{"x"}}}, + {"", `{{ concat .V (list 1 2 3) }}`, "[[x] 1 2 3]", map[string]any{"V": [][]string{{"x"}}}}, + } + + runTestCases(t, tests) +} + +func TestChunk(t *testing.T) { + var tests = testCases{ + {"", `{{ chunk 2 .V }}`, "[[a b] [c d] [e]]", map[string]any{"V": []string{"a", "b", "c", "d", "e"}}}, + {"", `{{ chunk 2 .V }}`, "[[a b] [c d]]", map[string]any{"V": []string{"a", "b", "c", "d"}}}, + {"", `{{ chunk 2 .V }}`, "[[a b]]", map[string]any{"V": []string{"a", "b"}}}, + {"", `{{ chunk 2 .V }}`, "[]", map[string]any{"V": []string{}}}, + {"", `{{ chunk 2 .V }}`, "[]", map[string]any{"V": nil}}, + } + + runTestCases(t, tests) +} + +func TestUniq(t *testing.T) { + var tests = testCases{ + {"", `{{ uniq .V }}`, "[a b c]", map[string]any{"V": []string{"a", "b", "c", "a", "b", "c"}}}, + {"", `{{ uniq .V }}`, "[a b c]", map[string]any{"V": []string{"a", "b", "c"}}}, + {"", `{{ uniq .V }}`, "[a]", map[string]any{"V": []string{"a", "a", "a"}}}, + {"", `{{ uniq .V }}`, "[a]", map[string]any{"V": []string{"a"}}}, + {"", `{{ uniq .V }}`, "[]", map[string]any{"V": []string{}}}, + {"", `{{ uniq .V }}`, "[]", map[string]any{"V": nil}}, + } + + runTestCases(t, tests) +} + +func TestCompact(t *testing.T) { + var tests = testCases{ + {"", `{{ compact .V }}`, "[a b c]", map[string]any{"V": []string{"a", "", "b", "", "c"}}}, + {"", `{{ compact .V }}`, "[a a]", map[string]any{"V": []string{"a", "", "a"}}}, + {"", `{{ compact .V }}`, "[a]", map[string]any{"V": []string{"a"}}}, + {"", `{{ compact .V }}`, "[]", map[string]any{"V": []string{}}}, + {"", `{{ compact .V }}`, "[]", map[string]any{"V": nil}}, + {"", `{{ list 1 0 "" "hello" | compact }}`, "[1 hello]", nil}, + {"", `{{ list "" "" | compact }}`, "[]", nil}, + {"", `{{ list | compact }}`, "[]", nil}, + } + + runTestCases(t, tests) +} + +func TestSlice(t *testing.T) { + var tests = testCases{ + {"", `{{ slice .V }}`, "[a b c d e]", map[string]any{"V": []string{"a", "b", "c", "d", "e"}}}, + {"", `{{ slice .V 1 }}`, "[b c d e]", map[string]any{"V": []string{"a", "b", "c", "d", "e"}}}, + {"", `{{ slice .V 1 3 }}`, "[b c]", map[string]any{"V": []string{"a", "b", "c", "d", "e"}}}, + {"", `{{ slice .V 0 1 }}`, "[a]", map[string]any{"V": []string{"a", "b", "c", "d", "e"}}}, + {"", `{{ slice .V 0 1 }}`, "[a]", map[string]any{"V": []string{"a"}}}, + {"", `{{ slice .V 0 1 }}`, "", map[string]any{"V": []string{}}}, + {"", `{{ slice .V 0 1 }}`, "[]", map[string]any{"V": nil}}, + } + + runTestCases(t, tests) +} + +func TestWithout(t *testing.T) { + var tests = testCases{ + {"", `{{ without .V "a" }}`, "[b c d e]", map[string]any{"V": []string{"a", "b", "c", "d", "e"}}}, + {"", `{{ without .V "a" }}`, "[b c d e]", map[string]any{"V": []string{"b", "c", "d", "e"}}}, + {"", `{{ without .V "a" }}`, "[b c d e]", map[string]any{"V": []string{"b", "c", "d", "e", "a"}}}, + {"", `{{ without .V "a" }}`, "[]", map[string]any{"V": []string{"a"}}}, + {"", `{{ without .V "a" }}`, "[]", map[string]any{"V": []string{}}}, + {"", `{{ without .V "a" }}`, "[]", map[string]any{"V": nil}}, + } + + runTestCases(t, tests) +} + +func TestRest(t *testing.T) { + var tests = testCases{ + {"", `{{ rest .V }}`, "[b c d e]", map[string]any{"V": []string{"a", "b", "c", "d", "e"}}}, + {"", `{{ rest .V }}`, "[c d e]", map[string]any{"V": []string{"b", "c", "d", "e"}}}, + {"", `{{ rest .V }}`, "[c d e a]", map[string]any{"V": []string{"b", "c", "d", "e", "a"}}}, + {"", `{{ rest .V }}`, "[]", map[string]any{"V": []string{"a"}}}, + {"", `{{ rest .V }}`, "[]", map[string]any{"V": []string{}}}, + {"", `{{ rest .V }}`, "[]", map[string]any{"V": nil}}, + } + + runTestCases(t, tests) +} + +func TestInitial(t *testing.T) { + var tests = testCases{ + {"", `{{ initial .V }}`, "[a b c d]", map[string]any{"V": []string{"a", "b", "c", "d", "e"}}}, + {"", `{{ initial .V }}`, "[a b c]", map[string]any{"V": []string{"a", "b", "c", "d"}}}, + {"", `{{ initial .V }}`, "[]", map[string]any{"V": []string{"a"}}}, + {"", `{{ initial .V }}`, "[]", map[string]any{"V": []string{}}}, + {"", `{{ initial .V }}`, "[]", map[string]any{"V": nil}}, + } + + runTestCases(t, tests) +} + +func TestFirst(t *testing.T) { + var tests = testCases{ + {"", `{{ first .V }}`, "a", map[string]any{"V": []string{"a", "b", "c", "d", "e"}}}, + {"", `{{ first .V }}`, "", map[string]any{"V": []string{}}}, + {"", `{{ first .V }}`, "", map[string]any{"V": nil}}, + } + + runTestCases(t, tests) +} + +func TestLast(t *testing.T) { + var tests = testCases{ + {"", `{{ last .V }}`, "e", map[string]any{"V": []string{"a", "b", "c", "d", "e"}}}, + {"", `{{ last .V }}`, "", map[string]any{"V": []string{}}}, + {"", `{{ last .V }}`, "", map[string]any{"V": nil}}, + } + + runTestCases(t, tests) +} + +func TestReverse(t *testing.T) { + var tests = testCases{ + {"", `{{ reverse .V }}`, "[e d c b a]", map[string]any{"V": []string{"a", "b", "c", "d", "e"}}}, + {"", `{{ reverse .V }}`, "[a b c d e]", map[string]any{"V": []string{"e", "d", "c", "b", "a"}}}, + {"", `{{ reverse .V }}`, "[]", map[string]any{"V": []string{}}}, + {"", `{{ reverse .V }}`, "[]", map[string]any{"V": nil}}, + } + + runTestCases(t, tests) +} + +func TestSortAlpha(t *testing.T) { + var tests = testCases{ + {"", `{{ sortAlpha .V }}`, "[a b c d e]", map[string]any{"V": []string{"e", "d", "c", "b", "a"}}}, + {"", `{{ sortAlpha .V }}`, "[1 2 3 4 5 a]", map[string]any{"V": []any{5, 4, 3, 2, 1, "a"}}}, + {"", `{{ sortAlpha .V }}`, "[]", map[string]any{"V": []string{}}}, + {"", `{{ sortAlpha .V }}`, "[]", map[string]any{"V": nil}}, + } + + runTestCases(t, tests) +} + +func TestSplitList(t *testing.T) { + var tests = testCases{ + {"", `{{ .V | splitList "," }}`, "[a b c d e]", map[string]any{"V": "a,b,c,d,e"}}, + {"", `{{ .V | splitList "," }}`, "[a b c d e ]", map[string]any{"V": "a,b,c,d,e,"}}, + {"", `{{ .V | splitList "," }}`, "[ a b c d e]", map[string]any{"V": ",a,b,c,d,e"}}, + {"", `{{ .V | splitList "," }}`, "[ a b c d e ]", map[string]any{"V": ",a,b,c,d,e,"}}, + {"", `{{ .V | splitList "," }}`, "[]", map[string]any{"V": ""}}, + } + + runTestCases(t, tests) +} + +func TestStrSlice(t *testing.T) { + var tests = testCases{ + {"", `{{ strSlice .V }}`, "[a b c d e]", map[string]any{"V": []string{"a", "b", "c", "d", "e"}}}, + {"", `{{ strSlice .V }}`, "[5 4 3 2 1]", map[string]any{"V": []int{5, 4, 3, 2, 1}}}, + {"", `{{ strSlice .V }}`, "[5 a true 1]", map[string]any{"V": []any{5, "a", true, nil, 1}}}, + {"", `{{ strSlice .V }}`, "[]", map[string]any{"V": ""}}, + } + + runTestCases(t, tests) +} + +func TestMustAppend(t *testing.T) { + var tests = mustTestCases{ + {testCase{"", `{{ mustAppend .V "a" }}`, "[a]", map[string]any{"V": []string{}}}, ""}, + {testCase{"", `{{ mustAppend .V "a" }}`, "[a]", map[string]any{"V": []string(nil)}}, ""}, + {testCase{"", `{{ mustAppend .V "a" }}`, "[x a]", map[string]any{"V": []string{"x"}}}, ""}, + {testCase{"", `{{ mustAppend .V "a" }}`, "", map[string]any{"V": nil}}, "cannot append to nil"}, + {testCase{"", `{{ mustAppend .V "a" }}`, "", map[string]any{"V": 1}}, "cannot append on type int"}, + } + + runMustTestCases(t, tests) +} + +func TestMustPrepend(t *testing.T) { + var tests = mustTestCases{ + {testCase{"", `{{ mustPrepend .V "a" }}`, "[a]", map[string]any{"V": []string{}}}, ""}, + {testCase{"", `{{ mustPrepend .V "a" }}`, "[a]", map[string]any{"V": []string(nil)}}, ""}, + {testCase{"", `{{ mustPrepend .V "a" }}`, "[a x]", map[string]any{"V": []string{"x"}}}, ""}, + {testCase{"", `{{ mustPrepend .V "a" }}`, "", map[string]any{"V": nil}}, "cannot prepend to nil"}, + {testCase{"", `{{ mustPrepend .V "a" }}`, "", map[string]any{"V": 1}}, "cannot prepend on type int"}, + } + + runMustTestCases(t, tests) +} + +func TestMustChunk(t *testing.T) { + var tests = mustTestCases{ + {testCase{"", `{{ mustChunk 2 .V }}`, "[[a b] [c d] [e]]", map[string]any{"V": []string{"a", "b", "c", "d", "e"}}}, ""}, + {testCase{"", `{{ mustChunk 2 .V }}`, "[[a b] [c d]]", map[string]any{"V": []string{"a", "b", "c", "d"}}}, ""}, + {testCase{"", `{{ mustChunk 2 .V }}`, "[[a b]]", map[string]any{"V": []string{"a", "b"}}}, ""}, + {testCase{"", `{{ mustChunk 2 .V }}`, "", map[string]any{"V": nil}}, "cannot chunk nil"}, + {testCase{"", `{{ mustChunk 2 .V }}`, "", map[string]any{"V": 1}}, "cannot chunk type int"}, + } + + runMustTestCases(t, tests) +} + +func TestMustUniq(t *testing.T) { + var tests = mustTestCases{ + {testCase{"", `{{ mustUniq .V }}`, "[a b c]", map[string]any{"V": []string{"a", "b", "c", "a", "b", "c"}}}, ""}, + {testCase{"", `{{ mustUniq .V }}`, "[a b c]", map[string]any{"V": []string{"a", "b", "c"}}}, ""}, + {testCase{"", `{{ mustUniq .V }}`, "[a]", map[string]any{"V": []string{"a", "a", "a"}}}, ""}, + {testCase{"", `{{ mustUniq .V }}`, "[a]", map[string]any{"V": []string{"a"}}}, ""}, + {testCase{"", `{{ mustUniq .V }}`, "", map[string]any{"V": nil}}, "cannot uniq nil"}, + {testCase{"", `{{ mustUniq .V }}`, "", map[string]any{"V": 1}}, "cannot find uniq on type int"}, + } + + runMustTestCases(t, tests) +} + +func TestMustCompact(t *testing.T) { + var tests = mustTestCases{ + {testCase{"", `{{ mustCompact .V }}`, "[a b c]", map[string]any{"V": []string{"a", "", "b", "", "c"}}}, ""}, + {testCase{"", `{{ mustCompact .V }}`, "[a a]", map[string]any{"V": []string{"a", "", "a"}}}, ""}, + {testCase{"", `{{ mustCompact .V }}`, "[a]", map[string]any{"V": []string{"a"}}}, ""}, + {testCase{"", `{{ mustCompact .V }}`, "", map[string]any{"V": nil}}, "cannot compact nil"}, + {testCase{"", `{{ mustCompact .V }}`, "", map[string]any{"V": 1}}, "cannot compact on type int"}, + } + + runMustTestCases(t, tests) +} + +func TestMustSlice(t *testing.T) { + var tests = mustTestCases{ + {testCase{"", `{{ mustSlice .V }}`, "[a b c d e]", map[string]any{"V": []string{"a", "b", "c", "d", "e"}}}, ""}, + {testCase{"", `{{ mustSlice .V 1 }}`, "[b c d e]", map[string]any{"V": []string{"a", "b", "c", "d", "e"}}}, ""}, + {testCase{"", `{{ mustSlice .V 1 3 }}`, "[b c]", map[string]any{"V": []string{"a", "b", "c", "d", "e"}}}, ""}, + {testCase{"", `{{ mustSlice .V 0 1 }}`, "[a]", map[string]any{"V": []string{"a", "b", "c", "d", "e"}}}, ""}, + {testCase{"", `{{ mustSlice .V 0 1 }}`, "", map[string]any{"V": nil}}, "cannot slice nil"}, + {testCase{"", `{{ mustSlice .V 0 1 }}`, "", map[string]any{"V": 1}}, "list should be type of slice or array but int"}, + } + + runMustTestCases(t, tests) +} + +func TestMustWithout(t *testing.T) { + var tests = mustTestCases{ + {testCase{"", `{{ mustWithout .V "a" }}`, "[b c d e]", map[string]any{"V": []string{"a", "b", "c", "d", "e"}}}, ""}, + {testCase{"", `{{ mustWithout .V "a" }}`, "[b c d e]", map[string]any{"V": []string{"b", "c", "d", "e"}}}, ""}, + {testCase{"", `{{ mustWithout .V "a" }}`, "[b c d e]", map[string]any{"V": []string{"b", "c", "d", "e", "a"}}}, ""}, + {testCase{"", `{{ mustWithout .V "a" }}`, "[]", map[string]any{"V": []string{"a"}}}, ""}, + {testCase{"", `{{ mustWithout .V "a" }}`, "", map[string]any{"V": nil}}, "cannot without nil"}, + {testCase{"", `{{ mustWithout .V "a" }}`, "", map[string]any{"V": 1}}, "cannot find without on type int"}, + } + + runMustTestCases(t, tests) +} + +func TestMustRest(t *testing.T) { + var tests = mustTestCases{ + {testCase{"", `{{ mustRest .V }}`, "[b c d e]", map[string]any{"V": []string{"a", "b", "c", "d", "e"}}}, ""}, + {testCase{"", `{{ mustRest .V }}`, "[c d e]", map[string]any{"V": []string{"b", "c", "d", "e"}}}, ""}, + {testCase{"", `{{ mustRest .V }}`, "[c d e a]", map[string]any{"V": []string{"b", "c", "d", "e", "a"}}}, ""}, + {testCase{"", `{{ mustRest .V }}`, "[]", map[string]any{"V": []string{"a"}}}, ""}, + {testCase{"", `{{ mustRest .V }}`, "", map[string]any{"V": nil}}, "cannot rest nil"}, + {testCase{"", `{{ mustRest .V }}`, "", map[string]any{"V": 1}}, "cannot find rest on type int"}, + } + + runMustTestCases(t, tests) +} + +func TestMustInitial(t *testing.T) { + var tests = mustTestCases{ + {testCase{"", `{{ mustInitial .V }}`, "[a b c d]", map[string]any{"V": []string{"a", "b", "c", "d", "e"}}}, ""}, + {testCase{"", `{{ mustInitial .V }}`, "[a b c]", map[string]any{"V": []string{"a", "b", "c", "d"}}}, ""}, + {testCase{"", `{{ mustInitial .V }}`, "[]", map[string]any{"V": []string{"a"}}}, ""}, + {testCase{"", `{{ mustInitial .V }}`, "", map[string]any{"V": nil}}, "cannot initial nil"}, + {testCase{"", `{{ mustInitial .V }}`, "", map[string]any{"V": 1}}, "cannot find initial on type int"}, + } + + runMustTestCases(t, tests) +} + +func TestMustFirst(t *testing.T) { + var tests = mustTestCases{ + {testCase{"", `{{ mustFirst .V }}`, "a", map[string]any{"V": []string{"a", "b", "c", "d", "e"}}}, ""}, + {testCase{"", `{{ mustFirst .V }}`, "", map[string]any{"V": nil}}, "cannot first nil"}, + {testCase{"", `{{ mustFirst .V }}`, "", map[string]any{"V": 1}}, "cannot find first on type int"}, + } + + runMustTestCases(t, tests) +} + +func TestMustLast(t *testing.T) { + var tests = mustTestCases{ + {testCase{"", `{{ mustLast .V }}`, "e", map[string]any{"V": []string{"a", "b", "c", "d", "e"}}}, ""}, + {testCase{"", `{{ mustLast .V }}`, "", map[string]any{"V": nil}}, "cannot last nil"}, + {testCase{"", `{{ mustLast .V }}`, "", map[string]any{"V": 1}}, "cannot find last on type int"}, + } + + runMustTestCases(t, tests) +} + +func TestMustReverse(t *testing.T) { + var tests = mustTestCases{ + {testCase{"", `{{ mustReverse .V }}`, "[e d c b a]", map[string]any{"V": []string{"a", "b", "c", "d", "e"}}}, ""}, + {testCase{"", `{{ mustReverse .V }}`, "[a b c d e]", map[string]any{"V": []string{"e", "d", "c", "b", "a"}}}, ""}, + {testCase{"", `{{ mustReverse .V }}`, "", map[string]any{"V": nil}}, "cannot reverse nil"}, + {testCase{"", `{{ mustReverse .V }}`, "", map[string]any{"V": 1}}, "cannot find reverse on type int"}, + } + + runMustTestCases(t, tests) +} diff --git a/sprout.go b/sprout.go index aaac567..57f3e92 100644 --- a/sprout.go +++ b/sprout.go @@ -163,7 +163,7 @@ func FuncMap(opts ...FunctionHandlerOption) template.FuncMap { fnHandler.funcMap["split"] = fnHandler.Split fnHandler.funcMap["splitList"] = fnHandler.SplitList fnHandler.funcMap["splitn"] = fnHandler.Splitn - fnHandler.funcMap["toStrings"] = fnHandler.Strslice // fnHandler.ToStrings + fnHandler.funcMap["strSlice"] = fnHandler.StrSlice fnHandler.funcMap["until"] = fnHandler.Until fnHandler.funcMap["untilStep"] = fnHandler.UntilStep fnHandler.funcMap["add1"] = fnHandler.Add1 diff --git a/strings_functions.go b/strings_functions.go index 8fcca47..382bb73 100644 --- a/strings_functions.go +++ b/strings_functions.go @@ -297,7 +297,7 @@ func (fh *FunctionHandler) Repeat(count int, str string) string { // {{ $list := slice "apple" "banana" "cherry" }} // {{ $list | join ", " }} // Output: "apple, banana, cherry" func (fh *FunctionHandler) Join(sep string, v any) string { - return strings.Join(fh.Strslice(v), sep) + return strings.Join(fh.StrSlice(v), sep) } // Trunc truncates 's' to a maximum length 'count'. If 'count' is negative, it removes diff --git a/to_migrate_test.go b/to_migrate_test.go index 818ca5b..90a3a0b 100644 --- a/to_migrate_test.go +++ b/to_migrate_test.go @@ -115,16 +115,6 @@ func TestToStrings(t *testing.T) { } } -func TestSortAlpha(t *testing.T) { - // Named `append` in the function map - tests := map[string]string{ - `{{ list "c" "a" "b" | sortAlpha | join "" }}`: "abc", - `{{ list 2 1 4 3 | sortAlpha | join "" }}`: "1234", - } - for tpl, expect := range tests { - assert.NoError(t, runt(tpl, expect)) - } -} func TestBase64EncodeDecode(t *testing.T) { magicWord := "coffee" expect := base64.StdEncoding.EncodeToString([]byte(magicWord)) @@ -362,293 +352,6 @@ func TestTuple(t *testing.T) { } } -func TestList(t *testing.T) { - tpl := `{{$t := list 1 "a" "foo"}}{{index $t 2}}{{index $t 0 }}{{index $t 1}}` - if err := runt(tpl, "foo1a"); err != nil { - t.Error(err) - } -} - -func TestPush(t *testing.T) { - // Named `append` in the function map - tests := map[string]string{ - `{{ $t := tuple 1 2 3 }}{{ append $t 4 | len }}`: "4", - `{{ $t := tuple 1 2 3 4 }}{{ append $t 5 | join "-" }}`: "1-2-3-4-5", - `{{ $t := regexSplit "/" "foo/bar/baz" -1 }}{{ append $t "qux" | join "-" }}`: "foo-bar-baz-qux", - } - for tpl, expect := range tests { - assert.NoError(t, runt(tpl, expect)) - } -} - -func TestMustPush(t *testing.T) { - // Named `append` in the function map - tests := map[string]string{ - `{{ $t := tuple 1 2 3 }}{{ mustAppend $t 4 | len }}`: "4", - `{{ $t := tuple 1 2 3 4 }}{{ mustAppend $t 5 | join "-" }}`: "1-2-3-4-5", - `{{ $t := regexSplit "/" "foo/bar/baz" -1 }}{{ mustPush $t "qux" | join "-" }}`: "foo-bar-baz-qux", - } - for tpl, expect := range tests { - assert.NoError(t, runt(tpl, expect)) - } -} - -func TestChunk(t *testing.T) { - tests := map[string]string{ - `{{ tuple 1 2 3 4 5 6 7 | chunk 3 | len }}`: "3", - `{{ tuple | chunk 3 | len }}`: "0", - `{{ range ( tuple 1 2 3 4 5 6 7 8 9 | chunk 3 ) }}{{. | join "-"}}|{{end}}`: "1-2-3|4-5-6|7-8-9|", - `{{ range ( tuple 1 2 3 4 5 6 7 8 | chunk 3 ) }}{{. | join "-"}}|{{end}}`: "1-2-3|4-5-6|7-8|", - `{{ range ( tuple 1 2 | chunk 3 ) }}{{. | join "-"}}|{{end}}`: "1-2|", - } - for tpl, expect := range tests { - assert.NoError(t, runt(tpl, expect)) - } -} - -func TestMustChunk(t *testing.T) { - tests := map[string]string{ - `{{ tuple 1 2 3 4 5 6 7 | mustChunk 3 | len }}`: "3", - `{{ tuple | mustChunk 3 | len }}`: "0", - `{{ range ( tuple 1 2 3 4 5 6 7 8 9 | mustChunk 3 ) }}{{. | join "-"}}|{{end}}`: "1-2-3|4-5-6|7-8-9|", - `{{ range ( tuple 1 2 3 4 5 6 7 8 | mustChunk 3 ) }}{{. | join "-"}}|{{end}}`: "1-2-3|4-5-6|7-8|", - `{{ range ( tuple 1 2 | mustChunk 3 ) }}{{. | join "-"}}|{{end}}`: "1-2|", - } - for tpl, expect := range tests { - assert.NoError(t, runt(tpl, expect)) - } -} - -func TestPrepend(t *testing.T) { - tests := map[string]string{ - `{{ $t := tuple 1 2 3 }}{{ prepend $t 0 | len }}`: "4", - `{{ $t := tuple 1 2 3 4 }}{{ prepend $t 0 | join "-" }}`: "0-1-2-3-4", - `{{ $t := regexSplit "/" "foo/bar/baz" -1 }}{{ prepend $t "qux" | join "-" }}`: "qux-foo-bar-baz", - } - for tpl, expect := range tests { - assert.NoError(t, runt(tpl, expect)) - } -} - -func TestMustPrepend(t *testing.T) { - tests := map[string]string{ - `{{ $t := tuple 1 2 3 }}{{ mustPrepend $t 0 | len }}`: "4", - `{{ $t := tuple 1 2 3 4 }}{{ mustPrepend $t 0 | join "-" }}`: "0-1-2-3-4", - `{{ $t := regexSplit "/" "foo/bar/baz" -1 }}{{ mustPrepend $t "qux" | join "-" }}`: "qux-foo-bar-baz", - } - for tpl, expect := range tests { - assert.NoError(t, runt(tpl, expect)) - } -} - -func TestFirst(t *testing.T) { - tests := map[string]string{ - `{{ list 1 2 3 | first }}`: "1", - `{{ list | first }}`: "", - `{{ regexSplit "/src/" "foo/src/bar" -1 | first }}`: "foo", - } - for tpl, expect := range tests { - assert.NoError(t, runt(tpl, expect)) - } -} - -func TestMustFirst(t *testing.T) { - tests := map[string]string{ - `{{ list 1 2 3 | mustFirst }}`: "1", - `{{ list | mustFirst }}`: "", - `{{ regexSplit "/src/" "foo/src/bar" -1 | mustFirst }}`: "foo", - } - for tpl, expect := range tests { - assert.NoError(t, runt(tpl, expect)) - } -} - -func TestLast(t *testing.T) { - tests := map[string]string{ - `{{ list 1 2 3 | last }}`: "3", - `{{ list | last }}`: "", - `{{ regexSplit "/src/" "foo/src/bar" -1 | last }}`: "bar", - } - for tpl, expect := range tests { - assert.NoError(t, runt(tpl, expect)) - } -} - -func TestMustLast(t *testing.T) { - tests := map[string]string{ - `{{ list 1 2 3 | mustLast }}`: "3", - `{{ list | mustLast }}`: "", - `{{ regexSplit "/src/" "foo/src/bar" -1 | mustLast }}`: "bar", - } - for tpl, expect := range tests { - assert.NoError(t, runt(tpl, expect)) - } -} - -func TestInitial(t *testing.T) { - tests := map[string]string{ - `{{ list 1 2 3 | initial | len }}`: "2", - `{{ list 1 2 3 | initial | last }}`: "2", - `{{ list 1 2 3 | initial | first }}`: "1", - `{{ list | initial }}`: "[]", - `{{ regexSplit "/" "foo/bar/baz" -1 | initial }}`: "[foo bar]", - } - for tpl, expect := range tests { - assert.NoError(t, runt(tpl, expect)) - } -} - -func TestMustInitial(t *testing.T) { - tests := map[string]string{ - `{{ list 1 2 3 | mustInitial | len }}`: "2", - `{{ list 1 2 3 | mustInitial | last }}`: "2", - `{{ list 1 2 3 | mustInitial | first }}`: "1", - `{{ list | mustInitial }}`: "[]", - `{{ regexSplit "/" "foo/bar/baz" -1 | mustInitial }}`: "[foo bar]", - } - for tpl, expect := range tests { - assert.NoError(t, runt(tpl, expect)) - } -} - -func TestRest(t *testing.T) { - tests := map[string]string{ - `{{ list 1 2 3 | rest | len }}`: "2", - `{{ list 1 2 3 | rest | last }}`: "3", - `{{ list 1 2 3 | rest | first }}`: "2", - `{{ list | rest }}`: "[]", - `{{ regexSplit "/" "foo/bar/baz" -1 | rest }}`: "[bar baz]", - } - for tpl, expect := range tests { - assert.NoError(t, runt(tpl, expect)) - } -} - -func TestMustRest(t *testing.T) { - tests := map[string]string{ - `{{ list 1 2 3 | mustRest | len }}`: "2", - `{{ list 1 2 3 | mustRest | last }}`: "3", - `{{ list 1 2 3 | mustRest | first }}`: "2", - `{{ list | mustRest }}`: "[]", - `{{ regexSplit "/" "foo/bar/baz" -1 | mustRest }}`: "[bar baz]", - } - for tpl, expect := range tests { - assert.NoError(t, runt(tpl, expect)) - } -} - -func TestReverse(t *testing.T) { - tests := map[string]string{ - `{{ list 1 2 3 | reverse | first }}`: "3", - `{{ list 1 2 3 | reverse | rest | first }}`: "2", - `{{ list 1 2 3 | reverse | last }}`: "1", - `{{ list 1 2 3 4 | reverse }}`: "[4 3 2 1]", - `{{ list 1 | reverse }}`: "[1]", - `{{ list | reverse }}`: "[]", - `{{ regexSplit "/" "foo/bar/baz" -1 | reverse }}`: "[baz bar foo]", - } - for tpl, expect := range tests { - assert.NoError(t, runt(tpl, expect)) - } -} - -func TestMustReverse(t *testing.T) { - tests := map[string]string{ - `{{ list 1 2 3 | mustReverse | first }}`: "3", - `{{ list 1 2 3 | mustReverse | rest | first }}`: "2", - `{{ list 1 2 3 | mustReverse | last }}`: "1", - `{{ list 1 2 3 4 | mustReverse }}`: "[4 3 2 1]", - `{{ list 1 | mustReverse }}`: "[1]", - `{{ list | mustReverse }}`: "[]", - `{{ regexSplit "/" "foo/bar/baz" -1 | mustReverse }}`: "[baz bar foo]", - } - for tpl, expect := range tests { - assert.NoError(t, runt(tpl, expect)) - } -} - -func TestCompact(t *testing.T) { - tests := map[string]string{ - `{{ list 1 0 "" "hello" | compact }}`: `[1 hello]`, - `{{ list "" "" | compact }}`: `[]`, - `{{ list | compact }}`: `[]`, - `{{ regexSplit "/" "foo//bar" -1 | compact }}`: "[foo bar]", - } - for tpl, expect := range tests { - assert.NoError(t, runt(tpl, expect)) - } -} - -func TestMustCompact(t *testing.T) { - tests := map[string]string{ - `{{ list 1 0 "" "hello" | mustCompact }}`: `[1 hello]`, - `{{ list "" "" | mustCompact }}`: `[]`, - `{{ list | mustCompact }}`: `[]`, - `{{ regexSplit "/" "foo//bar" -1 | mustCompact }}`: "[foo bar]", - } - for tpl, expect := range tests { - assert.NoError(t, runt(tpl, expect)) - } -} - -func TestUniq(t *testing.T) { - tests := map[string]string{ - `{{ list 1 2 3 4 | uniq }}`: `[1 2 3 4]`, - `{{ list "a" "b" "c" "d" | uniq }}`: `[a b c d]`, - `{{ list 1 1 1 1 2 2 2 2 | uniq }}`: `[1 2]`, - `{{ list "foo" 1 1 1 1 "foo" "foo" | uniq }}`: `[foo 1]`, - `{{ list | uniq }}`: `[]`, - `{{ regexSplit "/" "foo/foo/bar" -1 | uniq }}`: "[foo bar]", - } - for tpl, expect := range tests { - assert.NoError(t, runt(tpl, expect)) - } -} - -func TestMustUniq(t *testing.T) { - tests := map[string]string{ - `{{ list 1 2 3 4 | mustUniq }}`: `[1 2 3 4]`, - `{{ list "a" "b" "c" "d" | mustUniq }}`: `[a b c d]`, - `{{ list 1 1 1 1 2 2 2 2 | mustUniq }}`: `[1 2]`, - `{{ list "foo" 1 1 1 1 "foo" "foo" | mustUniq }}`: `[foo 1]`, - `{{ list | mustUniq }}`: `[]`, - `{{ regexSplit "/" "foo/foo/bar" -1 | mustUniq }}`: "[foo bar]", - } - for tpl, expect := range tests { - assert.NoError(t, runt(tpl, expect)) - } -} - -func TestWithout(t *testing.T) { - tests := map[string]string{ - `{{ without (list 1 2 3 4) 1 }}`: `[2 3 4]`, - `{{ without (list "a" "b" "c" "d") "a" }}`: `[b c d]`, - `{{ without (list 1 1 1 1 2) 1 }}`: `[2]`, - `{{ without (list) 1 }}`: `[]`, - `{{ without (list 1 2 3) }}`: `[1 2 3]`, - `{{ without list }}`: `[]`, - `{{ without (regexSplit "/" "foo/bar/baz" -1 ) "foo" }}`: "[bar baz]", - } - for tpl, expect := range tests { - assert.NoError(t, runt(tpl, expect)) - } -} - -func TestMustWithout(t *testing.T) { - tests := map[string]string{ - `{{ mustWithout (list 1 2 3 4) 1 }}`: `[2 3 4]`, - `{{ mustWithout (list "a" "b" "c" "d") "a" }}`: `[b c d]`, - `{{ mustWithout (list 1 1 1 1 2) 1 }}`: `[2]`, - `{{ mustWithout (list) 1 }}`: `[]`, - `{{ mustWithout (list 1 2 3) }}`: `[1 2 3]`, - `{{ mustWithout list }}`: `[]`, - `{{ mustWithout (regexSplit "/" "foo/bar/baz" -1 ) "foo" }}`: "[bar baz]", - } - for tpl, expect := range tests { - assert.NoError(t, runt(tpl, expect)) - } -} - func TestHas(t *testing.T) { tests := map[string]string{ `{{ list 1 2 3 | has 1 }}`: `true`, @@ -673,45 +376,6 @@ func TestMustHas(t *testing.T) { } } -func TestSlice(t *testing.T) { - tests := map[string]string{ - `{{ slice (list 1 2 3) }}`: "[1 2 3]", - `{{ slice (list 1 2 3) 0 1 }}`: "[1]", - `{{ slice (list 1 2 3) 1 3 }}`: "[2 3]", - `{{ slice (list 1 2 3) 1 }}`: "[2 3]", - `{{ slice (regexSplit "/" "foo/bar/baz" -1) 1 2 }}`: "[bar]", - } - for tpl, expect := range tests { - assert.NoError(t, runt(tpl, expect)) - } -} - -func TestMustSlice(t *testing.T) { - tests := map[string]string{ - `{{ mustSlice (list 1 2 3) }}`: "[1 2 3]", - `{{ mustSlice (list 1 2 3) 0 1 }}`: "[1]", - `{{ mustSlice (list 1 2 3) 1 3 }}`: "[2 3]", - `{{ mustSlice (list 1 2 3) 1 }}`: "[2 3]", - `{{ mustSlice (regexSplit "/" "foo/bar/baz" -1) 1 2 }}`: "[bar]", - } - for tpl, expect := range tests { - assert.NoError(t, runt(tpl, expect)) - } -} - -func TestConcat(t *testing.T) { - tests := map[string]string{ - `{{ concat (list 1 2 3) }}`: "[1 2 3]", - `{{ concat (list 1 2 3) (list 4 5) }}`: "[1 2 3 4 5]", - `{{ concat (list 1 2 3) (list 4 5) (list) }}`: "[1 2 3 4 5]", - `{{ concat (list 1 2 3) (list 4 5) (list nil) }}`: "[1 2 3 4 5 ]", - `{{ concat (list 1 2 3) (list 4 5) (list ( list "foo" ) ) }}`: "[1 2 3 4 5 [foo]]", - } - for tpl, expect := range tests { - assert.NoError(t, runt(tpl, expect)) - } -} - func TestIssue188(t *testing.T) { tests := map[string]string{