go-sprout/sprout

View on GitHub
internal/helpers/helpers.go

Summary

Maintainability
A
35 mins
Test Coverage
package helpers

import (
    "fmt"
    "reflect"

    "github.com/spf13/cast"
)

// strSlice converts a value to a slice of strings, handling various types
// including []string, []any, and other slice types.
//
// Parameters:
//
//    value any - the value to convert to a slice of strings.
//
// Returns:
//
//    []string - a slice of strings representing the input value.
//
// Example:
//
//    strs := StrSlice([]any{"apple", "banana", "cherry"})
//    fmt.Println(strs) // Output: ["apple", "banana", "cherry"]
func 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, cast.ToString(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, cast.ToString(value))
            }
        }
        return result
    }

    // If it's not a slice, array, or nil, return a slice with the string representation of v.
    return []string{cast.ToString(value)}
}

// UntilStep generates a slice of integers from 'start' to 'stop' (exclusive),
// incrementing by 'step'. If 'step' is positive, the sequence increases; if
// negative, it decreases. The function returns an empty slice if the sequence
// does not make logical sense (e.g., positive step when start is greater than
// stop or vice versa).
//
// Parameters:
//
//    start int - the starting point of the sequence.
//    stop int - the endpoint (exclusive) of the sequence.
//    step int - the increment between elements in the sequence.
//
// Returns:
//
//    []int - a dynamically generated slice of integers based on the input
//            parameters, or an empty slice if the parameters are inconsistent
//            with the desired range and step.
//
// Example:
//
//    {{ 0, 10, 2 | untilStep }} // Output: [0 2 4 6 8]
//    {{ 10, 0, -2 | untilStep }} // Output: [10 8 6 4 2]
func UntilStep(start, stop, step int) []int {
    v := []int{}

    if stop < start {
        if step >= 0 {
            return v
        }
        for i := start; i > stop; i += step {
            v = append(v, i)
        }
        return v
    }

    if step <= 0 {
        return v
    }
    for i := start; i < stop; i += step {
        v = append(v, i)
    }
    return v
}

// ToString converts the input value to a string based on its type.
//
// Parameters:
//
//    given any - the value to be converted to a string.
//
// Returns:
//
//    string - the string representation of the input value.
func ToString(v any) string {
    switch v := v.(type) {
    case string:
        return v
    case []byte:
        return string(v)
    case error:
        return v.Error()
    case fmt.Stringer:
        return v.String()
    default:
        return fmt.Sprintf("%v", v)
    }
}

// Empty evaluates the emptiness of the provided value 'given'. It returns
// true if 'given' is considered empty based on its type. This method is
// essential for determining the presence or absence of meaningful value
// across various data types.
//
// Parameters:
//
//    given any - the value to be evaluated for emptiness.
//
// Returns:
//
//    bool - true if 'given' is empty, false otherwise.
//
// This method utilizes the reflect package to inspect the type and value of
// 'given'. Depending on the type, it checks for nil pointers, zero-length
// collections (arrays, slices, maps, and strings), zero values of numeric
// types (integers, floats, complex numbers, unsigned ints), and false for
// booleans.
//
// Example:
//
//    Empty(nil) // Output: true
//    Empty("") // Output: true
//    Empty(0) // Output: true
//    Empty(false) // Output: true
//    Empty(sruct{}{}) // Output: false
func Empty(given any) bool {
    g := reflect.ValueOf(given)
    if !g.IsValid() {
        return true
    }

    // Basically adapted from text/template.isTrue
    switch g.Kind() {
    case reflect.Array, reflect.Slice, reflect.Map, reflect.String:
        return g.Len() == 0
    case reflect.Bool:
        return !g.Bool()
    case reflect.Complex64, reflect.Complex128:
        return g.Complex() == 0
    case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
        return g.Int() == 0
    case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
        return g.Uint() == 0
    case reflect.Float32, reflect.Float64:
        return g.Float() == 0
    case reflect.Interface, reflect.Ptr:
        return g.IsNil()
    default:
        return false
    }
}