506 lines
14 KiB
Go
506 lines
14 KiB
Go
package sprig
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"reflect"
|
|
"sort"
|
|
)
|
|
|
|
// Reflection is used in these functions so that slices and arrays of strings,
|
|
// ints, and other types not implementing []any can be worked with.
|
|
// For example, this is useful if you need to work on the output of regexs.
|
|
|
|
// list creates a new list (slice) containing the provided arguments.
|
|
// It accepts any number of arguments of any type and returns them as a slice.
|
|
func list(v ...any) []any {
|
|
return v
|
|
}
|
|
|
|
// push appends an element to the end of a list (slice or array).
|
|
// It takes a list and a value, and returns a new list with the value appended.
|
|
// This function will panic if the first argument is not a slice or array.
|
|
func push(list any, v any) []any {
|
|
l, err := mustPush(list, v)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return l
|
|
}
|
|
|
|
// mustPush is the implementation of push that returns an error instead of panicking.
|
|
// It converts the input list to a slice of any type, then appends the value.
|
|
func mustPush(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)
|
|
}
|
|
}
|
|
|
|
// prepend adds an element to the beginning of a list (slice or array).
|
|
// It takes a list and a value, and returns a new list with the value at the start.
|
|
// This function will panic if the first argument is not a slice or array.
|
|
func prepend(list any, v any) []any {
|
|
l, err := mustPrepend(list, v)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return l
|
|
}
|
|
|
|
// mustPrepend is the implementation of prepend that returns an error instead of panicking.
|
|
// It converts the input list to a slice of any type, then prepends the value.
|
|
func mustPrepend(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([]any{v}, nl...), nil
|
|
default:
|
|
return nil, fmt.Errorf("cannot prepend on type %s", tp)
|
|
}
|
|
}
|
|
|
|
// chunk divides a list into sub-lists of the specified size.
|
|
// It takes a size and a list, and returns a list of lists, each containing
|
|
// up to 'size' elements from the original list.
|
|
// This function will panic if the second argument is not a slice or array.
|
|
func chunk(size int, list any) [][]any {
|
|
l, err := mustChunk(size, list)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return l
|
|
}
|
|
|
|
// mustChunk is the implementation of chunk that returns an error instead of panicking.
|
|
// It divides the input list into chunks of the specified size.
|
|
func 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()
|
|
numChunks := int(math.Floor(float64(l-1)/float64(size)) + 1)
|
|
if numChunks > sliceSizeLimit {
|
|
return nil, fmt.Errorf("number of chunks %d exceeds maximum limit of %d", numChunks, sliceSizeLimit)
|
|
}
|
|
result := make([][]any, numChunks)
|
|
for i := 0; i < numChunks; i++ {
|
|
clen := size
|
|
// Handle the last chunk which might be smaller
|
|
if i == numChunks-1 {
|
|
clen = int(math.Floor(math.Mod(float64(l), float64(size))))
|
|
if clen == 0 {
|
|
clen = size
|
|
}
|
|
}
|
|
result[i] = make([]any, clen)
|
|
for j := 0; j < clen; j++ {
|
|
ix := i*size + j
|
|
result[i][j] = l2.Index(ix).Interface()
|
|
}
|
|
}
|
|
return result, nil
|
|
|
|
default:
|
|
return nil, fmt.Errorf("cannot chunk type %s", tp)
|
|
}
|
|
}
|
|
|
|
// last returns the last element of a list (slice or array).
|
|
// If the list is empty, it returns nil.
|
|
// This function will panic if the argument is not a slice or array.
|
|
func last(list any) any {
|
|
l, err := mustLast(list)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return l
|
|
}
|
|
|
|
// mustLast is the implementation of last that returns an error instead of panicking.
|
|
// It returns the last element of the list or nil if the list is empty.
|
|
func 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)
|
|
}
|
|
}
|
|
|
|
// first returns the first element of a list (slice or array).
|
|
// If the list is empty, it returns nil.
|
|
// This function will panic if the argument is not a slice or array.
|
|
func first(list any) any {
|
|
l, err := mustFirst(list)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return l
|
|
}
|
|
|
|
// mustFirst is the implementation of first that returns an error instead of panicking.
|
|
// It returns the first element of the list or nil if the list is empty.
|
|
func 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)
|
|
}
|
|
}
|
|
|
|
// rest returns all elements of a list except the first one.
|
|
// If the list is empty, it returns nil.
|
|
// This function will panic if the argument is not a slice or array.
|
|
func rest(list any) []any {
|
|
l, err := mustRest(list)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return l
|
|
}
|
|
|
|
// mustRest is the implementation of rest that returns an error instead of panicking.
|
|
// It returns all elements of the list except the first one, or nil if the list is empty.
|
|
func 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)
|
|
}
|
|
}
|
|
|
|
// initial returns all elements of a list except the last one.
|
|
// If the list is empty, it returns nil.
|
|
// This function will panic if the argument is not a slice or array.
|
|
func initial(list any) []any {
|
|
l, err := mustInitial(list)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return l
|
|
}
|
|
|
|
// mustInitial is the implementation of initial that returns an error instead of panicking.
|
|
// It returns all elements of the list except the last one, or nil if the list is empty.
|
|
func 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)
|
|
}
|
|
}
|
|
|
|
// sortAlpha sorts a list of strings alphabetically.
|
|
// If the input is not a slice or array, it returns a single-element slice
|
|
// containing the string representation of the input.
|
|
func sortAlpha(list any) []string {
|
|
k := reflect.Indirect(reflect.ValueOf(list)).Kind()
|
|
switch k {
|
|
case reflect.Slice, reflect.Array:
|
|
a := strslice(list)
|
|
s := sort.StringSlice(a)
|
|
s.Sort()
|
|
return s
|
|
}
|
|
return []string{strval(list)}
|
|
}
|
|
|
|
// reverse returns a new list with the elements in reverse order.
|
|
// This function will panic if the argument is not a slice or array.
|
|
func reverse(v any) []any {
|
|
l, err := mustReverse(v)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return l
|
|
}
|
|
|
|
// mustReverse is the implementation of reverse that returns an error instead of panicking.
|
|
// It returns a new list with the elements in reverse order.
|
|
func 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)
|
|
}
|
|
}
|
|
|
|
// compact returns a new list with all "empty" elements removed.
|
|
// An element is considered empty if it's nil, zero, an empty string, or an empty collection.
|
|
// This function will panic if the argument is not a slice or array.
|
|
func compact(list any) []any {
|
|
l, err := mustCompact(list)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return l
|
|
}
|
|
|
|
// mustCompact is the implementation of compact that returns an error instead of panicking.
|
|
// It returns a new list with all "empty" elements removed.
|
|
func mustCompact(list any) ([]any, error) {
|
|
tp := reflect.TypeOf(list).Kind()
|
|
switch tp {
|
|
case reflect.Slice, reflect.Array:
|
|
l2 := reflect.ValueOf(list)
|
|
l := l2.Len()
|
|
var nl []any
|
|
var item any
|
|
for i := 0; i < l; i++ {
|
|
item = l2.Index(i).Interface()
|
|
if !empty(item) {
|
|
nl = append(nl, item)
|
|
}
|
|
}
|
|
return nl, nil
|
|
default:
|
|
return nil, fmt.Errorf("cannot compact on type %s", tp)
|
|
}
|
|
}
|
|
|
|
// uniq returns a new list with duplicate elements removed.
|
|
// The first occurrence of each element is kept.
|
|
// This function will panic if the argument is not a slice or array.
|
|
func uniq(list any) []any {
|
|
l, err := mustUniq(list)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return l
|
|
}
|
|
|
|
// mustUniq is the implementation of uniq that returns an error instead of panicking.
|
|
// It returns a new list with duplicate elements removed.
|
|
func mustUniq(list any) ([]any, error) {
|
|
tp := reflect.TypeOf(list).Kind()
|
|
switch tp {
|
|
case reflect.Slice, reflect.Array:
|
|
l2 := reflect.ValueOf(list)
|
|
l := l2.Len()
|
|
var dest []any
|
|
var item any
|
|
for i := 0; i < l; i++ {
|
|
item = l2.Index(i).Interface()
|
|
if !inList(dest, item) {
|
|
dest = append(dest, item)
|
|
}
|
|
}
|
|
return dest, nil
|
|
default:
|
|
return nil, fmt.Errorf("cannot find uniq on type %s", tp)
|
|
}
|
|
}
|
|
|
|
// inList checks if a value is present in a list.
|
|
// It uses deep equality comparison to check for matches.
|
|
// Returns true if the value is found, false otherwise.
|
|
func inList(haystack []any, needle any) bool {
|
|
for _, h := range haystack {
|
|
if reflect.DeepEqual(needle, h) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// without returns a new list with all occurrences of the specified values removed.
|
|
// This function will panic if the first argument is not a slice or array.
|
|
func without(list any, omit ...any) []any {
|
|
l, err := mustWithout(list, omit...)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return l
|
|
}
|
|
|
|
// mustWithout is the implementation of without that returns an error instead of panicking.
|
|
// It returns a new list with all occurrences of the specified values removed.
|
|
func 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 !inList(omit, item) {
|
|
res = append(res, item)
|
|
}
|
|
}
|
|
return res, nil
|
|
default:
|
|
return nil, fmt.Errorf("cannot find without on type %s", tp)
|
|
}
|
|
}
|
|
|
|
// has checks if a value is present in a list.
|
|
// Returns true if the value is found, false otherwise.
|
|
// This function will panic if the second argument is not a slice or array.
|
|
func has(needle any, haystack any) bool {
|
|
l, err := mustHas(needle, haystack)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return l
|
|
}
|
|
|
|
// mustHas is the implementation of has that returns an error instead of panicking.
|
|
// It checks if a value is present in a list.
|
|
func mustHas(needle any, haystack any) (bool, error) {
|
|
if haystack == nil {
|
|
return false, nil
|
|
}
|
|
tp := reflect.TypeOf(haystack).Kind()
|
|
switch tp {
|
|
case reflect.Slice, reflect.Array:
|
|
l2 := reflect.ValueOf(haystack)
|
|
var item any
|
|
l := l2.Len()
|
|
for i := 0; i < l; i++ {
|
|
item = l2.Index(i).Interface()
|
|
if reflect.DeepEqual(needle, item) {
|
|
return true, nil
|
|
}
|
|
}
|
|
return false, nil
|
|
default:
|
|
return false, fmt.Errorf("cannot find has on type %s", tp)
|
|
}
|
|
}
|
|
|
|
// slice extracts a portion of a list based on the provided indices.
|
|
// Usage examples:
|
|
// $list := [1, 2, 3, 4, 5]
|
|
// slice $list -> list[0:5] = list[:]
|
|
// slice $list 0 3 -> list[0:3] = list[:3]
|
|
// slice $list 3 5 -> list[3:5]
|
|
// slice $list 3 -> list[3:5] = list[3:]
|
|
//
|
|
// This function will panic if the first argument is not a slice or array.
|
|
func slice(list any, indices ...any) any {
|
|
l, err := mustSlice(list, indices...)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return l
|
|
}
|
|
|
|
// mustSlice is the implementation of slice that returns an error instead of panicking.
|
|
// It extracts a portion of a list based on the provided indices.
|
|
func 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
|
|
}
|
|
// Determine start and end indices
|
|
var start, end int
|
|
if len(indices) > 0 {
|
|
start = toInt(indices[0])
|
|
}
|
|
if len(indices) < 2 {
|
|
end = l
|
|
} else {
|
|
end = 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)
|
|
}
|
|
}
|
|
|
|
// concat combines multiple lists into a single list.
|
|
// It takes any number of lists and returns a new list containing all elements.
|
|
// This function will panic if any argument is not a slice or array.
|
|
func 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
|
|
}
|