github.com/hairyhenderson/gomplate/v4@v4.0.0-pre-2.0.20240520121557-362f058f0c93/conv/conv.go (about)

     1  // Package conv contains functions that help converting data between different types
     2  package conv
     3  
     4  import (
     5  	"fmt"
     6  	"math"
     7  	"reflect"
     8  	"strconv"
     9  	"strings"
    10  
    11  	iconv "github.com/hairyhenderson/gomplate/v4/internal/conv"
    12  )
    13  
    14  // Bool converts a string to a boolean value, using strconv.ParseBool under the covers.
    15  // Possible true values are: 1, t, T, TRUE, true, True
    16  // All other values are considered false.
    17  //
    18  // See ToBool also for a more flexible version.
    19  //
    20  // Deprecated: use ToBool instead
    21  func Bool(in string) bool {
    22  	if b, err := strconv.ParseBool(in); err == nil {
    23  		return b
    24  	}
    25  	return false
    26  }
    27  
    28  // ToBool converts an arbitrary input into a boolean.
    29  // Possible non-boolean true values are: 1 or the strings "t", "true", or "yes"
    30  // (any capitalizations)
    31  // All other values are considered false.
    32  func ToBool(in interface{}) bool {
    33  	if b, ok := in.(bool); ok {
    34  		return b
    35  	}
    36  
    37  	if str, ok := in.(string); ok {
    38  		str = strings.ToLower(str)
    39  		switch str {
    40  		case "1", "t", "true", "yes":
    41  			return true
    42  		default:
    43  			return strToFloat64(str) == 1
    44  		}
    45  	}
    46  
    47  	val := reflect.Indirect(reflect.ValueOf(in))
    48  	switch val.Kind() {
    49  	case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
    50  		return val.Int() == 1
    51  	case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
    52  		return val.Uint() == 1
    53  	case reflect.Float32, reflect.Float64:
    54  		return val.Float() == 1
    55  	default:
    56  		return false
    57  	}
    58  }
    59  
    60  // ToBools -
    61  func ToBools(in ...interface{}) []bool {
    62  	out := make([]bool, len(in))
    63  	for i, v := range in {
    64  		out[i] = ToBool(v)
    65  	}
    66  	return out
    67  }
    68  
    69  // Slice creates a slice from a bunch of arguments
    70  //
    71  // Deprecated: use [github.com/hairyhenderson/gomplate/v4/coll.Slice] instead
    72  func Slice(args ...interface{}) []interface{} {
    73  	return args
    74  }
    75  
    76  // Join concatenates the elements of a to create a single string.
    77  // The separator string sep is placed between elements in the resulting string.
    78  //
    79  // This is functionally identical to strings.Join, except that each element is
    80  // coerced to a string first
    81  func Join(in interface{}, sep string) (out string, err error) {
    82  	s, ok := in.([]string)
    83  	if ok {
    84  		return strings.Join(s, sep), nil
    85  	}
    86  
    87  	var a []interface{}
    88  	a, ok = in.([]interface{})
    89  	if !ok {
    90  		a, err = iconv.InterfaceSlice(in)
    91  		if err != nil {
    92  			return "", fmt.Errorf("input to Join must be an array: %w", err)
    93  		}
    94  		ok = true
    95  	}
    96  	if ok {
    97  		b := make([]string, len(a))
    98  		for i := range a {
    99  			b[i] = ToString(a[i])
   100  		}
   101  		return strings.Join(b, sep), nil
   102  	}
   103  
   104  	return "", fmt.Errorf("input to Join must be an array")
   105  }
   106  
   107  // Has determines whether or not a given object has a property with the given key
   108  func Has(in interface{}, key interface{}) bool {
   109  	av := reflect.ValueOf(in)
   110  
   111  	switch av.Kind() {
   112  	case reflect.Map:
   113  		kv := reflect.ValueOf(key)
   114  		return av.MapIndex(kv).IsValid()
   115  	case reflect.Slice, reflect.Array:
   116  		l := av.Len()
   117  		for i := 0; i < l; i++ {
   118  			v := av.Index(i).Interface()
   119  			if reflect.DeepEqual(v, key) {
   120  				return true
   121  			}
   122  		}
   123  	}
   124  
   125  	return false
   126  }
   127  
   128  // ToString -
   129  func ToString(in interface{}) string {
   130  	if in == nil {
   131  		return "nil"
   132  	}
   133  	if s, ok := in.(string); ok {
   134  		return s
   135  	}
   136  	if s, ok := in.(fmt.Stringer); ok {
   137  		return s.String()
   138  	}
   139  	if s, ok := in.([]byte); ok {
   140  		return string(s)
   141  	}
   142  
   143  	v, ok := printableValue(reflect.ValueOf(in))
   144  	if ok {
   145  		in = v
   146  	}
   147  	return fmt.Sprint(in)
   148  }
   149  
   150  // ToStrings -
   151  func ToStrings(in ...interface{}) []string {
   152  	out := make([]string, len(in))
   153  	for i, v := range in {
   154  		out[i] = ToString(v)
   155  	}
   156  	return out
   157  }
   158  
   159  // MustParseInt - wrapper for strconv.ParseInt that returns 0 in the case of error
   160  func MustParseInt(s string, base, bitSize int) int64 {
   161  	i, _ := strconv.ParseInt(s, base, bitSize)
   162  	return i
   163  }
   164  
   165  // MustParseFloat - wrapper for strconv.ParseFloat that returns 0 in the case of error
   166  func MustParseFloat(s string, bitSize int) float64 {
   167  	i, _ := strconv.ParseFloat(s, bitSize)
   168  	return i
   169  }
   170  
   171  // MustParseUint - wrapper for strconv.ParseUint that returns 0 in the case of error
   172  func MustParseUint(s string, base, bitSize int) uint64 {
   173  	i, _ := strconv.ParseUint(s, base, bitSize)
   174  	return i
   175  }
   176  
   177  // MustAtoi - wrapper for strconv.Atoi that returns 0 in the case of error
   178  func MustAtoi(s string) int {
   179  	i, _ := strconv.Atoi(s)
   180  	return i
   181  }
   182  
   183  // ToInt64 - convert input to an int64, if convertible. Otherwise, returns 0.
   184  func ToInt64(v interface{}) int64 {
   185  	if str, ok := v.(string); ok {
   186  		return strToInt64(str)
   187  	}
   188  
   189  	val := reflect.Indirect(reflect.ValueOf(v))
   190  	switch val.Kind() {
   191  	case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
   192  		return val.Int()
   193  	case reflect.Uint8, reflect.Uint16, reflect.Uint32:
   194  		return int64(val.Uint())
   195  	case reflect.Uint, reflect.Uint64:
   196  		tv := val.Uint()
   197  		// this can overflow and give -1, but IMO this is better than
   198  		// returning maxint64
   199  		return int64(tv)
   200  	case reflect.Float32, reflect.Float64:
   201  		return int64(val.Float())
   202  	case reflect.Bool:
   203  		if val.Bool() {
   204  			return 1
   205  		}
   206  		return 0
   207  	default:
   208  		return 0
   209  	}
   210  }
   211  
   212  // ToInt -
   213  func ToInt(in interface{}) int {
   214  	// Protect against CWE-190 and CWE-681
   215  	// https://cwe.mitre.org/data/definitions/190.html
   216  	// https://cwe.mitre.org/data/definitions/681.html
   217  	if i := ToInt64(in); i <= math.MaxInt || i >= math.MinInt {
   218  		return int(i)
   219  	}
   220  
   221  	// we're probably on a 32-bit system, so we can't represent this number
   222  	return -1
   223  }
   224  
   225  // ToInt64s -
   226  func ToInt64s(in ...interface{}) []int64 {
   227  	out := make([]int64, len(in))
   228  	for i, v := range in {
   229  		out[i] = ToInt64(v)
   230  	}
   231  	return out
   232  }
   233  
   234  // ToInts -
   235  func ToInts(in ...interface{}) []int {
   236  	out := make([]int, len(in))
   237  	for i, v := range in {
   238  		out[i] = ToInt(v)
   239  	}
   240  	return out
   241  }
   242  
   243  // ToFloat64 - convert input to a float64, if convertible. Otherwise, returns 0.
   244  func ToFloat64(v interface{}) float64 {
   245  	if str, ok := v.(string); ok {
   246  		return strToFloat64(str)
   247  	}
   248  
   249  	val := reflect.Indirect(reflect.ValueOf(v))
   250  	switch val.Kind() {
   251  	case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
   252  		return float64(val.Int())
   253  	case reflect.Uint8, reflect.Uint16, reflect.Uint32:
   254  		return float64(val.Uint())
   255  	case reflect.Uint, reflect.Uint64:
   256  		return float64(val.Uint())
   257  	case reflect.Float32, reflect.Float64:
   258  		return val.Float()
   259  	case reflect.Bool:
   260  		if val.Bool() {
   261  			return 1
   262  		}
   263  		return 0
   264  	default:
   265  		return 0
   266  	}
   267  }
   268  
   269  func strToInt64(str string) int64 {
   270  	if strings.Contains(str, ",") {
   271  		str = strings.ReplaceAll(str, ",", "")
   272  	}
   273  	iv, err := strconv.ParseInt(str, 0, 64)
   274  	if err != nil {
   275  		// maybe it's a float?
   276  		var fv float64
   277  		fv, err = strconv.ParseFloat(str, 64)
   278  		if err != nil {
   279  			return 0
   280  		}
   281  		return ToInt64(fv)
   282  	}
   283  	return iv
   284  }
   285  
   286  func strToFloat64(str string) float64 {
   287  	if strings.Contains(str, ",") {
   288  		str = strings.ReplaceAll(str, ",", "")
   289  	}
   290  	// this is inefficient, but it's the only way I can think of to
   291  	// properly convert octal integers to floats
   292  	iv, err := strconv.ParseInt(str, 0, 64)
   293  	if err != nil {
   294  		// ok maybe it's a float?
   295  		var fv float64
   296  		fv, err = strconv.ParseFloat(str, 64)
   297  		if err != nil {
   298  			return 0
   299  		}
   300  		return fv
   301  	}
   302  	return float64(iv)
   303  }
   304  
   305  // ToFloat64s -
   306  func ToFloat64s(in ...interface{}) []float64 {
   307  	out := make([]float64, len(in))
   308  	for i, v := range in {
   309  		out[i] = ToFloat64(v)
   310  	}
   311  	return out
   312  }
   313  
   314  // Dict is a convenience function that creates a map with string keys.
   315  // Provide arguments as key/value pairs. If an odd number of arguments
   316  // is provided, the last is used as the key, and an empty string is
   317  // set as the value.
   318  // All keys are converted to strings, regardless of input type.
   319  func Dict(v ...interface{}) (map[string]interface{}, error) {
   320  	dict := map[string]interface{}{}
   321  	lenv := len(v)
   322  	for i := 0; i < lenv; i += 2 {
   323  		key := ToString(v[i])
   324  		if i+1 >= lenv {
   325  			dict[key] = ""
   326  			continue
   327  		}
   328  		dict[key] = v[i+1]
   329  	}
   330  	return dict, nil
   331  }