go.charczuk.com@v0.0.0-20240327042549-bc490516bd1a/sdk/viewutil/funcs.go (about)

     1  /*
     2  
     3  Copyright (c) 2023 - Present. Will Charczuk. All rights reserved.
     4  Use of this source code is governed by a MIT license that can be found in the LICENSE file at the root of the repository.
     5  
     6  */
     7  
     8  package viewutil
     9  
    10  import (
    11  	"bytes"
    12  	"crypto/hmac"
    13  	"crypto/rand"
    14  	"crypto/sha256"
    15  	"crypto/sha512"
    16  	"encoding/base64"
    17  	"encoding/hex"
    18  	"encoding/json"
    19  	"fmt"
    20  	"io"
    21  	"math"
    22  	"net/url"
    23  	"os"
    24  	"reflect"
    25  	"regexp"
    26  	"strconv"
    27  	"strings"
    28  	"text/template"
    29  	"time"
    30  
    31  	"go.charczuk.com/sdk/mathutil"
    32  	"go.charczuk.com/sdk/stringutil"
    33  	"go.charczuk.com/sdk/uuid"
    34  )
    35  
    36  // Funcs is a singleton for viewfuncs.
    37  var (
    38  	Funcs template.FuncMap = map[string]any{
    39  		"as_bytes":                   AsBytes,
    40  		"as_duration":                AsDuration,
    41  		"as_string":                  AsString,
    42  		"at_index":                   AtIndex,
    43  		"base64":                     Base64,
    44  		"base64decode":               Base64Decode,
    45  		"ceil":                       Ceil,
    46  		"concat":                     Concat,
    47  		"contains":                   Contains,
    48  		"control_for":                ControlFor,
    49  		"csv":                        CSV,
    50  		"duration_round_millis":      DurationRoundMillis,
    51  		"duration_round_seconds":     DurationRoundSeconds,
    52  		"duration_round":             DurationRound,
    53  		"first":                      First,
    54  		"floor":                      Floor,
    55  		"format_filesize":            FormatFileSize,
    56  		"format_money":               FormatMoney,
    57  		"format_pct":                 FormatPct,
    58  		"form_for":                   FormFor,
    59  		"generate_ordinal_names":     GenerateOrdinalNames,
    60  		"has_prefix":                 HasPrefix,
    61  		"has_suffix":                 HasSuffix,
    62  		"indent_spaces":              IndentSpaces,
    63  		"indent_tabs":                IndentTabs,
    64  		"join":                       Join,
    65  		"last":                       Last,
    66  		"matches":                    Matches,
    67  		"parse_bool":                 ParseBool,
    68  		"parse_float64":              ParseFloat64,
    69  		"parse_int":                  ParseInt,
    70  		"parse_int64":                ParseInt64,
    71  		"parse_json":                 ParseJSON,
    72  		"parse_time_unix":            ParseTimeUnix,
    73  		"parse_time":                 ParseTime,
    74  		"parse_url":                  ParseURL,
    75  		"parse_uuid":                 ParseUUID,
    76  		"prefix":                     Prefix,
    77  		"quote":                      Quote,
    78  		"reverse":                    Reverse,
    79  		"round":                      Round,
    80  		"sequence_range":             SequenceRange,
    81  		"sha256":                     SHA256,
    82  		"sha512":                     SHA512,
    83  		"slice":                      Slice,
    84  		"slugify":                    Slugify,
    85  		"split_n":                    SplitN,
    86  		"split":                      Split,
    87  		"strip_quotes":               StripQuotes,
    88  		"suffix":                     Suffix,
    89  		"time_day":                   TimeDay,
    90  		"time_format_date_long":      TimeFormatDateLong,
    91  		"time_format_date_month_day": TimeFormatDateMonthDay,
    92  		"time_format_date_short_rev": TimeFormatDateShortRev,
    93  		"time_format_date_short":     TimeFormatDateShort,
    94  		"time_format_kitchen":        TimeFormatKitchen,
    95  		"time_format_medium":         TimeFormatMedium,
    96  		"time_format_rfc3339":        TimeFormatRFC3339,
    97  		"time_format_short":          TimeFormatShort,
    98  		"time_format":                TimeFormat,
    99  		"time_hour":                  TimeHour,
   100  		"time_in_loc":                TimeInLocation,
   101  		"time_in_utc":                TimeInUTC,
   102  		"time_is_epoch":              TimeIsEpoch,
   103  		"time_is_zero":               TimeIsZero,
   104  		"time_millisecond":           TimeMillisecond,
   105  		"time_minute":                TimeMinute,
   106  		"time_month":                 TimeMonth,
   107  		"time_now_utc":               TimeNowUTC,
   108  		"time_now":                   TimeNow,
   109  		"time_second":                TimeSecond,
   110  		"time_since_utc":             TimeSinceUTC,
   111  		"time_since":                 TimeSince,
   112  		"time_sub":                   TimeSub,
   113  		"time_unix_nano":             TimeUnixNano,
   114  		"time_unix":                  TimeUnix,
   115  		"time_year":                  TimeYear,
   116  		"to_json_pretty":             ToJSONPretty,
   117  		"to_json":                    ToJSON,
   118  		"to_lower":                   ToLower,
   119  		"to_title":                   ToTitle,
   120  		"to_upper":                   ToUpper,
   121  		"trim_prefix":                TrimPrefix,
   122  		"trim_space":                 TrimSpace,
   123  		"trim_suffix":                TrimSuffix,
   124  		"tsv":                        TSV,
   125  		"url_host":                   URLHost,
   126  		"url_path":                   URLPath,
   127  		"url_port":                   URLPort,
   128  		"url_query":                  URLQuery,
   129  		"url_raw_query":              URLRawQuery,
   130  		"url_scheme":                 URLScheme,
   131  		"urlencode":                  URLEncode,
   132  		"uuid":                       UUIDv4,
   133  		"uuidv4":                     UUIDv4,
   134  		"with_url_host":              WithURLHost,
   135  		"with_url_path":              URLPath,
   136  		"with_url_port":              WithURLPort,
   137  		"with_url_query":             WithURLQuery,
   138  		"with_url_raw_query":         WithURLRawQuery,
   139  		"with_url_scheme":            WithURLScheme,
   140  	}
   141  )
   142  
   143  // FileExists returns if the file at a given path exists.
   144  func FileExists(path string) bool {
   145  	_, err := os.Stat(path)
   146  	return err == nil
   147  }
   148  
   149  // ReadFile reads the contents of a file path as a string.
   150  func ReadFile(path string) (string, error) {
   151  	contents, err := os.ReadFile(path)
   152  	return string(contents), err
   153  }
   154  
   155  // AsString attempts to return a string representation of a value.
   156  func AsString(v any) string {
   157  	switch c := v.(type) {
   158  	case []byte:
   159  		return string(c)
   160  	case string:
   161  		return c
   162  	default:
   163  		return fmt.Sprintf("%v", v)
   164  	}
   165  }
   166  
   167  // AsBytes attempts to return a bytes representation of a value.
   168  func AsBytes(v interface{}) []byte {
   169  	return []byte(fmt.Sprintf("%v", v))
   170  }
   171  
   172  // ParseInt parses a value as an integer.
   173  func ParseInt(v interface{}) (int, error) {
   174  	return strconv.Atoi(fmt.Sprintf("%v", v))
   175  }
   176  
   177  // ParseInt64 parses a value as an int64.
   178  func ParseInt64(v interface{}) (int64, error) {
   179  	return strconv.ParseInt(fmt.Sprintf("%v", v), 10, 64)
   180  }
   181  
   182  // ParseFloat64 parses a value as a float64.
   183  func ParseFloat64(v string) (float64, error) {
   184  	return strconv.ParseFloat(v, 64)
   185  }
   186  
   187  // ParseBool attempts to parse a value as a bool.
   188  // "truthy" values include "true", "1", "yes".
   189  // "falsey" values include "false", "0", "no".
   190  func ParseBool(raw interface{}) (bool, error) {
   191  	v := fmt.Sprintf("%v", raw)
   192  	if len(v) == 0 {
   193  		return false, nil
   194  	}
   195  	switch strings.ToLower(v) {
   196  	case "true", "1", "yes":
   197  		return true, nil
   198  	case "false", "0", "no":
   199  		return false, nil
   200  	default:
   201  		return false, fmt.Errorf("invalid boolean value `%s`", v)
   202  	}
   203  }
   204  
   205  // ParseTime parses a time string with a given format.
   206  func ParseTime(format, v string) (time.Time, error) {
   207  	return time.Parse(format, v)
   208  }
   209  
   210  // ParseTimeUnix returns a timestamp from a unix format.
   211  func ParseTimeUnix(v int64) time.Time {
   212  	return time.Unix(v, 0)
   213  }
   214  
   215  // TimeNow returns the current time in the system timezone.
   216  func TimeNow() time.Time {
   217  	return time.Now()
   218  }
   219  
   220  // TimeNowUTC returns the current time in the UTC timezone.
   221  func TimeNowUTC() time.Time {
   222  	return time.Now().UTC()
   223  }
   224  
   225  // TimeUnix returns the unix format for a timestamp.
   226  func TimeUnix(t time.Time) int64 {
   227  	return t.Unix()
   228  }
   229  
   230  // TimeUnixNano returns the timetamp as nanoseconds from 1970-01-01.
   231  func TimeUnixNano(t time.Time) int64 {
   232  	return t.UnixNano()
   233  }
   234  
   235  // TimeFormatRFC3339 returns the RFC3339 format for a timestamp.
   236  func TimeFormatRFC3339(t time.Time) string {
   237  	return t.Format(time.RFC3339)
   238  }
   239  
   240  // TimeFormatShort returns the short format for a timestamp.
   241  // The format string is "1/02/2006 3:04:05 PM".
   242  func TimeFormatShort(t time.Time) string {
   243  	return t.Format("1/02/2006 3:04:05 PM")
   244  }
   245  
   246  // TimeFormat returns the time with a given format string.
   247  func TimeFormat(format string, t time.Time) string {
   248  	return t.Format(format)
   249  }
   250  
   251  // TimeIsZero returns if the time is set or not.
   252  func TimeIsZero(t time.Time) bool {
   253  	return t.IsZero()
   254  }
   255  
   256  // TimeIsEpoch returns if the time is the unix epoch time or not.
   257  func TimeIsEpoch(t time.Time) bool {
   258  	return t.Equal(time.Unix(0, 0))
   259  }
   260  
   261  // TimeFormatDateLong returns the short date for a timestamp.
   262  func TimeFormatDateLong(t time.Time) string {
   263  	return t.Format("Jan _2, 2006")
   264  }
   265  
   266  // TimeFormatDateShort returns the short date for a timestamp.
   267  // The format string is "1/02/2006"
   268  func TimeFormatDateShort(t time.Time) string {
   269  	return t.Format("1/02/2006")
   270  }
   271  
   272  // TimeFormatDateShortRev returns the short date for a timestamp in YYYY/mm/dd format.
   273  func TimeFormatDateShortRev(t time.Time) string {
   274  	return t.Format("2006/1/02")
   275  }
   276  
   277  // TimeFormatTimeMedium returns the medium format for a timestamp.
   278  // The format string is "1/02/2006 3:04:05 PM".
   279  func TimeFormatMedium(t time.Time) string {
   280  	return t.Format("Jan 02, 2006 3:04:05 PM")
   281  }
   282  
   283  // TimeFormatTimeKitchen returns the kitchen format for a timestamp.
   284  // The format string is "3:04PM".
   285  func TimeFormatKitchen(t time.Time) string {
   286  	return t.Format(time.Kitchen)
   287  }
   288  
   289  // TimeFormatDateMonthDay returns the month dat format for a timestamp.
   290  // The format string is "1/2".
   291  func TimeFormatDateMonthDay(t time.Time) string {
   292  	return t.Format("1/2")
   293  }
   294  
   295  // TimeInUTC returns the time in a given location by string.
   296  // If the location is invalid, this will error.
   297  func TimeInUTC(t time.Time) time.Time {
   298  	return t.UTC()
   299  }
   300  
   301  // TimeInLocation returns the time in a given location by string.
   302  // If the location is invalid, this will error.
   303  func TimeInLocation(loc string, t time.Time) (time.Time, error) {
   304  	location, err := time.LoadLocation(loc)
   305  	if err != nil {
   306  		return time.Time{}, err
   307  	}
   308  	return t.In(location), err
   309  }
   310  
   311  // TimeYear returns the year component of a timestamp.
   312  func TimeYear(t time.Time) int {
   313  	return t.Year()
   314  }
   315  
   316  // TimeMonth returns the month component of a timestamp.
   317  func TimeMonth(t time.Time) int {
   318  	return int(t.Month())
   319  }
   320  
   321  // TimeDay returns the day component of a timestamp.
   322  func TimeDay(t time.Time) int {
   323  	return t.Day()
   324  }
   325  
   326  // TimeHour returns the hour component of a timestamp.
   327  func TimeHour(t time.Time) int {
   328  	return t.Hour()
   329  }
   330  
   331  // TimeMinute returns the minute component of a timestamp.
   332  func TimeMinute(t time.Time) int {
   333  	return t.Minute()
   334  }
   335  
   336  // TimeSecond returns the seconds component of a timestamp.
   337  func TimeSecond(t time.Time) int {
   338  	return t.Second()
   339  }
   340  
   341  // TimeMillisecond returns the millisecond component of a timestamp.
   342  func TimeMillisecond(t time.Time) int {
   343  	return int(time.Duration(t.Nanosecond()) / time.Millisecond)
   344  }
   345  
   346  // AsDuration returns a given value as a duration.
   347  func AsDuration(val any) (typedVal time.Duration, err error) {
   348  	switch tv := val.(type) {
   349  	case time.Duration:
   350  		typedVal = tv
   351  	case uint8:
   352  		typedVal = time.Duration(tv)
   353  	case int8:
   354  		typedVal = time.Duration(tv)
   355  	case uint16:
   356  		typedVal = time.Duration(tv)
   357  	case int16:
   358  		typedVal = time.Duration(tv)
   359  	case uint32:
   360  		typedVal = time.Duration(tv)
   361  	case int32:
   362  		typedVal = time.Duration(tv)
   363  	case uint64:
   364  		typedVal = time.Duration(tv)
   365  	case int64:
   366  		typedVal = time.Duration(tv)
   367  	case int:
   368  		typedVal = time.Duration(tv)
   369  	case uint:
   370  		typedVal = time.Duration(tv)
   371  	case float32:
   372  		typedVal = time.Duration(tv)
   373  	case float64:
   374  		typedVal = time.Duration(tv)
   375  	default:
   376  		err = fmt.Errorf("invalid duration value %[1]T: %[1]v", val)
   377  	}
   378  	return
   379  }
   380  
   381  // TimeSince returns the duration since a given timestamp.
   382  // It is relative, meaning the value returned can be negative.
   383  func TimeSince(t time.Time) time.Duration {
   384  	return time.Since(t)
   385  }
   386  
   387  // TimeSub the duration difference between two times.
   388  func TimeSub(t1, t2 time.Time) time.Duration {
   389  	return t1.UTC().Sub(t2.UTC())
   390  }
   391  
   392  // TimeSinceUTC returns the duration since a given timestamp in UTC.
   393  // It is relative, meaning the value returned can be negative.
   394  func TimeSinceUTC(t time.Time) time.Duration {
   395  	return time.Now().UTC().Sub(t.UTC())
   396  }
   397  
   398  // DurationRound rounds a duration value.
   399  func DurationRound(d time.Duration, to time.Duration) time.Duration {
   400  	return d.Round(to)
   401  }
   402  
   403  // DurationRoundMillis rounds a duration value to milliseconds.
   404  func DurationRoundMillis(d time.Duration) time.Duration {
   405  	return d.Round(time.Millisecond)
   406  }
   407  
   408  // DurationRoundSeconds rounds a duration value to seconds.
   409  func DurationRoundSeconds(d time.Duration) time.Duration {
   410  	return d.Round(time.Millisecond)
   411  }
   412  
   413  // Round returns the value rounded to a given set of places.
   414  // It uses midpoint rounding.
   415  func Round(places, d float64) float64 {
   416  	return mathutil.RoundPlaces(d, int(places))
   417  }
   418  
   419  // Ceil returns the value rounded up to the nearest integer.
   420  func Ceil(d float64) float64 {
   421  	return math.Ceil(d)
   422  }
   423  
   424  // Floor returns the value rounded down to zero.
   425  func Floor(d float64) float64 {
   426  	return math.Floor(d)
   427  }
   428  
   429  // FormatMoney returns a float as a formatted string rounded to two decimal places.
   430  func FormatMoney(d float64) string {
   431  	return fmt.Sprintf("$%0.2f", mathutil.RoundPlaces(d, 2))
   432  }
   433  
   434  // FormatPct formats a float as a percentage (it is multiplied by 100,
   435  // then suffixed with '%')
   436  func FormatPct(d float64) string {
   437  	return fmt.Sprintf("%0.2f%%", d*100)
   438  }
   439  
   440  // FormatFileSize formats an int as a file size.
   441  func FormatFileSize(sizeBytes int) string {
   442  	if sizeBytes >= 1<<30 {
   443  		return fmt.Sprintf("%dgB", sizeBytes/(1<<30))
   444  	} else if sizeBytes >= 1<<20 {
   445  		return fmt.Sprintf("%dmB", sizeBytes/(1<<20))
   446  	} else if sizeBytes >= 1<<10 {
   447  		return fmt.Sprintf("%dkB", sizeBytes/(1<<10))
   448  	}
   449  	return fmt.Sprintf("%dB", sizeBytes)
   450  }
   451  
   452  // Base64 encodes data as a string as a base6 string.
   453  func Base64(v string) string {
   454  	return base64.StdEncoding.EncodeToString([]byte(v))
   455  }
   456  
   457  // Base64Decode decodes a base 64 string.
   458  func Base64Decode(v string) (string, error) {
   459  	result, err := base64.StdEncoding.DecodeString(v)
   460  	if err != nil {
   461  		return "", err
   462  	}
   463  	return string(result), nil
   464  }
   465  
   466  // ParseUUID parses a uuid.
   467  func ParseUUID(v string) (uuid.UUID, error) {
   468  	return uuid.Parse(v)
   469  }
   470  
   471  // UUIDv4 generates a uuid v4.
   472  func UUIDv4() uuid.UUID {
   473  	return uuid.V4()
   474  }
   475  
   476  // ToUpper returns a string case shifted to upper case.
   477  func ToUpper(v string) string {
   478  	return strings.ToUpper(v)
   479  }
   480  
   481  // ToLower returns a string case shifted to lower case.
   482  func ToLower(v string) string {
   483  	return strings.ToLower(v)
   484  }
   485  
   486  // ToTitle returns a title cased string.
   487  func ToTitle(v string) string {
   488  	return strings.ToTitle(v)
   489  }
   490  
   491  // Slugify returns a slug format string.
   492  // It replaces whitespace with `-`
   493  // It path escapes any other characters.
   494  func Slugify(v string) string {
   495  	return stringutil.Slugify(v)
   496  }
   497  
   498  // TrimSpace trims whitespace from the beginning and end of a string.
   499  func TrimSpace(v string) string {
   500  	return strings.TrimSpace(v)
   501  }
   502  
   503  // Prefix appends a given string to a prefix.
   504  func Prefix(pref, v string) string {
   505  	return pref + v
   506  }
   507  
   508  // Concat concatenates a list of strings.
   509  func Concat(strs ...string) string {
   510  	var output string
   511  	for index := 0; index < len(strs); index++ {
   512  		output = output + strs[index]
   513  	}
   514  	return output
   515  }
   516  
   517  // Suffix appends a given prefix to a string.
   518  func Suffix(suf, v string) string {
   519  	return v + suf
   520  }
   521  
   522  // Split splits a string by a separator.
   523  func Split(sep, v string) []string {
   524  	return strings.Split(v, sep)
   525  }
   526  
   527  // SplitN splits a string by a separator a given number of times.
   528  func SplitN(sep string, n float64, v string) []string {
   529  	return strings.SplitN(v, sep, int(n))
   530  }
   531  
   532  //
   533  // array functions
   534  //
   535  
   536  // Reverse reverses an array.
   537  func Reverse(collection interface{}) (interface{}, error) {
   538  	value := reflect.ValueOf(collection)
   539  
   540  	if value.Type().Kind() != reflect.Slice {
   541  		return nil, fmt.Errorf("input must be a slice")
   542  	}
   543  
   544  	output := make([]interface{}, value.Len())
   545  	for index := 0; index < value.Len(); index++ {
   546  		output[index] = value.Index((value.Len() - 1) - index).Interface()
   547  	}
   548  	return output, nil
   549  }
   550  
   551  // Slice returns a subrange of a collection.
   552  func Slice(from, to int, collection interface{}) (interface{}, error) {
   553  	value := reflect.ValueOf(collection)
   554  
   555  	if value.Type().Kind() != reflect.Slice {
   556  		return nil, fmt.Errorf("input must be a slice")
   557  	}
   558  
   559  	return value.Slice(from, to).Interface(), nil
   560  }
   561  
   562  // First returns the first element of a collection.
   563  func First(collection interface{}) (interface{}, error) {
   564  	value := reflect.ValueOf(collection)
   565  	kind := value.Type().Kind()
   566  	if kind != reflect.Slice && kind != reflect.Map && kind != reflect.Array {
   567  		return nil, fmt.Errorf("input must be a slice or map")
   568  	}
   569  	if value.Len() == 0 {
   570  		return nil, nil
   571  	}
   572  	switch kind {
   573  	case reflect.Slice, reflect.Array:
   574  		return value.Index(0).Interface(), nil
   575  	case reflect.Map:
   576  		iter := value.MapRange()
   577  		if iter.Next() {
   578  			return iter.Value().Interface(), nil
   579  		}
   580  	default:
   581  	}
   582  
   583  	return nil, nil
   584  }
   585  
   586  // AtIndex returns an element at a given index.
   587  func AtIndex(index int, collection interface{}) (interface{}, error) {
   588  	value := reflect.ValueOf(collection)
   589  	if value.Type().Kind() != reflect.Slice {
   590  		return nil, fmt.Errorf("input must be a slice")
   591  	}
   592  	if value.Len() == 0 {
   593  		return nil, nil
   594  	}
   595  	return value.Index(index).Interface(), nil
   596  }
   597  
   598  // Last returns the last element of a collection.
   599  func Last(collection interface{}) (interface{}, error) {
   600  	value := reflect.ValueOf(collection)
   601  	if value.Type().Kind() != reflect.Slice {
   602  		return nil, fmt.Errorf("input must be a slice")
   603  	}
   604  	if value.Len() == 0 {
   605  		return nil, nil
   606  	}
   607  	return value.Index(value.Len() - 1).Interface(), nil
   608  }
   609  
   610  // Join creates a string joined with a given separator.
   611  func Join(sep string, collection any) (string, error) {
   612  	value := reflect.ValueOf(collection)
   613  	if value.Type().Kind() != reflect.Slice {
   614  		return "", fmt.Errorf("input must be a slice")
   615  	}
   616  	if value.Len() == 0 {
   617  		return "", nil
   618  	}
   619  	values := make([]string, value.Len())
   620  	for i := 0; i < value.Len(); i++ {
   621  		values[i] = fmt.Sprintf("%v", value.Index(i).Interface())
   622  	}
   623  	return strings.Join(values, sep), nil
   624  }
   625  
   626  // CSV returns a csv of a given collection.
   627  func CSV(collection interface{}) (string, error) {
   628  	return Join(",", collection)
   629  }
   630  
   631  // TSV returns a tab separated values of a given collection.
   632  func TSV(collection interface{}) (string, error) {
   633  	return Join("\t", collection)
   634  }
   635  
   636  // HasSuffix returns if a string has a given suffix.
   637  func HasSuffix(suffix, v string) bool {
   638  	return strings.HasSuffix(v, suffix)
   639  }
   640  
   641  // HasPrefix returns if a string has a given prefix.
   642  func HasPrefix(prefix, v string) bool {
   643  	return strings.HasPrefix(v, prefix)
   644  }
   645  
   646  // TrimSuffix returns if a string has a given suffix.
   647  func TrimSuffix(suffix, v string) string {
   648  	return strings.TrimSuffix(v, suffix)
   649  }
   650  
   651  // TrimPrefix returns if a string has a given prefix.
   652  func TrimPrefix(prefix, v string) string {
   653  	return strings.TrimPrefix(v, prefix)
   654  }
   655  
   656  // Contains returns if a string contains a given substring.
   657  func Contains(substr, v string) bool {
   658  	return strings.Contains(v, substr)
   659  }
   660  
   661  // Matches returns if a string matches a given regular expression.
   662  func Matches(expr, v string) (bool, error) {
   663  	return regexp.MatchString(expr, v)
   664  }
   665  
   666  // Quote returns a string wrapped in " characters.
   667  // It will trim space before and after, and only add quotes
   668  // if they don't already exist.
   669  func Quote(v string) string {
   670  	v = strings.TrimSpace(v)
   671  	if !strings.HasPrefix(v, "\"") {
   672  		v = "\"" + v
   673  	}
   674  	if !strings.HasSuffix(v, "\"") {
   675  		v = v + "\""
   676  	}
   677  	return v
   678  }
   679  
   680  // StripQuotes strips leading and trailing quotes.
   681  func StripQuotes(v string) string {
   682  	v = strings.TrimSpace(v)
   683  	v = strings.TrimPrefix(v, "\"")
   684  	v = strings.TrimSuffix(v, "\"")
   685  	return v
   686  }
   687  
   688  // ParseURL parses a url.
   689  func ParseURL(v string) (*url.URL, error) {
   690  	return url.Parse(v)
   691  }
   692  
   693  // URLEncode encodes a value as a url token.
   694  func URLEncode(value string) string {
   695  	return url.QueryEscape(value)
   696  }
   697  
   698  // URLScheme returns the scheme of a url.
   699  func URLScheme(v *url.URL) string {
   700  	return v.Scheme
   701  }
   702  
   703  // WithURLScheme returns the scheme of a url.
   704  func WithURLScheme(scheme string, u *url.URL) *url.URL {
   705  	copy := *u
   706  	copy.Scheme = scheme
   707  	return &copy
   708  }
   709  
   710  // URLHost returns the host of a url.
   711  func URLHost(v *url.URL) string {
   712  	return v.Host
   713  }
   714  
   715  // WithURLHost returns the host of a url.
   716  func WithURLHost(host string, u *url.URL) *url.URL {
   717  	copy := *u
   718  	copy.Host = host
   719  	return &copy
   720  }
   721  
   722  // URLPort returns the url port.
   723  // If none is explicitly specified, this will return empty string.
   724  func URLPort(v *url.URL) string {
   725  	return v.Port()
   726  }
   727  
   728  // WithURLPort sets the url port.
   729  func WithURLPort(port string, u *url.URL) *url.URL {
   730  	copy := *u
   731  	host := copy.Host
   732  	if strings.Contains(host, ":") {
   733  		parts := strings.SplitN(host, ":", 2)
   734  		copy.Host = parts[0] + ":" + port
   735  	} else {
   736  		copy.Host = host + ":" + port
   737  	}
   738  	return &copy
   739  }
   740  
   741  // URLPath returns the url path.
   742  func URLPath(v *url.URL) string {
   743  	return v.Path
   744  }
   745  
   746  // WithURLPath returns the url path.
   747  func WithURLPath(path string, u *url.URL) *url.URL {
   748  	copy := *u
   749  	copy.Path = path
   750  	return &copy
   751  }
   752  
   753  // URLRawQuery returns the url raw query.
   754  func URLRawQuery(v *url.URL) string {
   755  	return v.RawQuery
   756  }
   757  
   758  // WithURLRawQuery returns the url path.
   759  func WithURLRawQuery(rawQuery string, u *url.URL) *url.URL {
   760  	copy := *u
   761  	copy.RawQuery = rawQuery
   762  	return &copy
   763  }
   764  
   765  // URLQuery returns a url query param.
   766  func URLQuery(name string, v *url.URL) string {
   767  	return v.Query().Get(name)
   768  }
   769  
   770  // WithURLQuery returns a url query param.
   771  func WithURLQuery(key, value string, u *url.URL) *url.URL {
   772  	copy := *u
   773  	queryValues := copy.Query()
   774  	queryValues.Add(key, value)
   775  	copy.RawQuery = queryValues.Encode()
   776  	return &copy
   777  }
   778  
   779  // SHA256 returns the sha256 sum of a string.
   780  func SHA256(v string) string {
   781  	h := sha256.New()
   782  	fmt.Fprint(h, v)
   783  	return hex.EncodeToString(h.Sum(nil))
   784  }
   785  
   786  // SHA512 returns the sha512 sum of a string.
   787  func SHA512(v string) string {
   788  	h := sha512.New()
   789  	fmt.Fprint(h, v)
   790  	return hex.EncodeToString(h.Sum(nil))
   791  }
   792  
   793  // HMAC512 returns the hmac signed sha 512 sum of a string.
   794  func HMAC512(key, v string) (string, error) {
   795  	keyBytes, err := base64.StdEncoding.DecodeString(key)
   796  	if err != nil {
   797  		return "", err
   798  	}
   799  	h := hmac.New(sha512.New, keyBytes)
   800  	fmt.Fprint(h, v)
   801  	return hex.EncodeToString(h.Sum(nil)), nil
   802  }
   803  
   804  // IndentTabs indents a string with a given number of tabs.
   805  func IndentTabs(tabCount int, v interface{}) string {
   806  	lines := strings.Split(fmt.Sprintf("%v", v), "\n")
   807  	outputLines := make([]string, len(lines))
   808  
   809  	var tabs string
   810  	for i := 0; i < tabCount; i++ {
   811  		tabs = tabs + "\t"
   812  	}
   813  
   814  	for i := 0; i < len(lines); i++ {
   815  		outputLines[i] = tabs + lines[i]
   816  	}
   817  	return strings.Join(outputLines, "\n")
   818  }
   819  
   820  // IndentSpaces indents a string by a given set of spaces.
   821  func IndentSpaces(spaceCount int, v interface{}) string {
   822  	lines := strings.Split(fmt.Sprintf("%v", v), "\n")
   823  	outputLines := make([]string, len(lines))
   824  
   825  	var spaces string
   826  	for i := 0; i < spaceCount; i++ {
   827  		spaces = spaces + " "
   828  	}
   829  
   830  	for i := 0; i < len(lines); i++ {
   831  		outputLines[i] = spaces + lines[i]
   832  	}
   833  	return strings.Join(outputLines, "\n")
   834  }
   835  
   836  // GenerateOrdinalNames generates ordinal names by passing the index to a given formatter.
   837  // The formatter should be in Sprintf format (i.e. using a '%d' token for where the index should go).
   838  /*
   839  Example:
   840      {{ generate_ordinal_names "worker-%d" 3 }} // [worker-0 worker-1 worker-2]
   841  */
   842  func GenerateOrdinalNames(format string, replicas int) []string {
   843  	output := make([]string, replicas)
   844  	for index := 0; index < replicas; index++ {
   845  		output[index] = fmt.Sprintf(format, index)
   846  	}
   847  	return output
   848  }
   849  
   850  // GenerateKey generates a key of a given size base 64 encoded.
   851  func GenerateKey(keySize int) string {
   852  	key := make([]byte, keySize)
   853  	_, _ = io.ReadFull(rand.Reader, key)
   854  	return base64.StdEncoding.EncodeToString(key)
   855  }
   856  
   857  // ToJSON returns an object encoded as json.
   858  func ToJSON(v any) (string, error) {
   859  	data, err := json.Marshal(v)
   860  	return string(data), err
   861  }
   862  
   863  // ToJSONPretty encodes an object as json with indentation.
   864  func ToJSONPretty(v any) (string, error) {
   865  	buf := new(bytes.Buffer)
   866  	encoder := json.NewEncoder(buf)
   867  	encoder.SetIndent("", "\t")
   868  	err := encoder.Encode(v)
   869  	if err != nil {
   870  		return "", err
   871  	}
   872  	return buf.String(), nil
   873  }
   874  
   875  // ParseJSON returns an object encoded as json.
   876  func ParseJSON(v string) (interface{}, error) {
   877  	var data interface{}
   878  	err := json.Unmarshal([]byte(v), &data)
   879  	return data, err
   880  }
   881  
   882  // SequenceRange returns an array of ints from min to max, not including max.
   883  // Given (0,5) as inputs, it would return [0,1,2,3,4]
   884  func SequenceRange(start, end int) []int {
   885  	if start == end {
   886  		return []int{}
   887  	}
   888  	if start > end {
   889  		output := make([]int, start-end)
   890  		for x := start; x > end; x-- {
   891  			output[start-x] = x
   892  		}
   893  		return output
   894  	}
   895  
   896  	output := make([]int, end-start)
   897  	for x := start; x < end; x++ {
   898  		output[x] = x
   899  	}
   900  	return output
   901  }
   902  
   903  // AsFloat64 returns a given value as a float64.
   904  func AsFloat64(val interface{}) (typedVal float64, err error) {
   905  	switch tv := val.(type) {
   906  	case uint8:
   907  		typedVal = float64(tv)
   908  	case int8:
   909  		typedVal = float64(tv)
   910  	case uint16:
   911  		typedVal = float64(tv)
   912  	case int16:
   913  		typedVal = float64(tv)
   914  	case uint32:
   915  		typedVal = float64(tv)
   916  	case int32:
   917  		typedVal = float64(tv)
   918  	case uint64:
   919  		typedVal = float64(tv)
   920  	case int64:
   921  		typedVal = float64(tv)
   922  	case int:
   923  		typedVal = float64(tv)
   924  	case float32:
   925  		typedVal = float64(tv)
   926  	case float64:
   927  		typedVal = tv
   928  	default:
   929  		err = fmt.Errorf("invalid to_float value %[1]T: %[1]v", val)
   930  	}
   931  	return
   932  }
   933  
   934  // AsInt returns a given value as a int64.
   935  func AsInt(val interface{}) (typedVal int, err error) {
   936  	switch tv := val.(type) {
   937  	case uint8:
   938  		typedVal = int(tv)
   939  	case int8:
   940  		typedVal = int(tv)
   941  	case uint16:
   942  		typedVal = int(tv)
   943  	case int16:
   944  		typedVal = int(tv)
   945  	case uint32:
   946  		typedVal = int(tv)
   947  	case int32:
   948  		typedVal = int(tv)
   949  	case uint64:
   950  		typedVal = int(tv)
   951  	case int64:
   952  		typedVal = int(tv)
   953  	case int:
   954  		typedVal = tv
   955  	case float32:
   956  		typedVal = int(tv)
   957  	case float64:
   958  		typedVal = int(tv)
   959  	default:
   960  		err = fmt.Errorf("invalid to_int value %[1]T: %[1]v", val)
   961  	}
   962  	return
   963  }