github.com/blend/go-sdk@v1.20220411.3/template/view_funcs.go (about)

     1  /*
     2  
     3  Copyright (c) 2022 - Present. Blend Labs, Inc. All rights reserved
     4  Use of this source code is governed by a MIT license that can be found in the LICENSE file.
     5  
     6  */
     7  
     8  package template
     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  	"time"
    29  
    30  	"gopkg.in/yaml.v3"
    31  
    32  	"github.com/blend/go-sdk/mathutil"
    33  	"github.com/blend/go-sdk/semver"
    34  	"github.com/blend/go-sdk/stringutil"
    35  	"github.com/blend/go-sdk/uuid"
    36  	"github.com/blend/go-sdk/webutil"
    37  )
    38  
    39  // DefaultViewFuncs is a singleton for viewfuncs.
    40  var (
    41  	Funcs ViewFuncs
    42  )
    43  
    44  // ViewFuncs is the type stub for view functions.
    45  type ViewFuncs struct{}
    46  
    47  // FuncMap returns the name => func mapping.
    48  func (vf ViewFuncs) FuncMap() map[string]interface{} {
    49  	return map[string]interface{}{
    50  		/* files */
    51  		"file_exists": vf.FileExists,
    52  		"read_file":   vf.ReadFile,
    53  		"process":     vf.Process,
    54  		/* conversion */
    55  		"as_string": vf.ToString,
    56  		"as_bytes":  vf.ToBytes,
    57  		/* parsing */
    58  		/* these are like to_ but can error */
    59  		"parse_bool":    vf.ParseBool,
    60  		"parse_int":     vf.ParseInt,
    61  		"parse_int64":   vf.ParseInt64,
    62  		"parse_float64": vf.ParseFloat64,
    63  		"parse_time":    vf.ParseTime,
    64  		"parse_unix":    vf.ParseUnix,
    65  		"parse_semver":  vf.ParseSemver,
    66  		"parse_url":     vf.ParseURL,
    67  		/* time */
    68  		"now":            vf.Now,
    69  		"now_utc":        vf.NowUTC,
    70  		"time_format":    vf.TimeFormat,
    71  		"time_is_zero":   vf.TimeIsZero,
    72  		"time_is_epoch":  vf.TimeIsEpoch,
    73  		"date_long":      vf.DateLong,
    74  		"date_short":     vf.DateShort,
    75  		"date_month_day": vf.DateMonthDay,
    76  		"date_short_rev": vf.DateShortRev,
    77  		"unix":           vf.Unix,
    78  		"unix_nano":      vf.UnixNano,
    79  		"rfc3339":        vf.RFC3339,
    80  		"time_short":     vf.TimeShort,
    81  		"time_medium":    vf.TimeMedium,
    82  		"time_kitchen":   vf.TimeKitchen,
    83  		"in_utc":         vf.TimeInUTC,
    84  		"in_loc":         vf.TimeInLocation,
    85  		"since":          vf.Since,
    86  		"since_utc":      vf.SinceUTC,
    87  		"time_sub":       vf.TimeSub,
    88  		"year":           vf.Year,
    89  		"month":          vf.Month,
    90  		"day":            vf.Day,
    91  		"hour":           vf.Hour,
    92  		"minute":         vf.Minute,
    93  		"second":         vf.Second,
    94  		"millisecond":    vf.Millisecond,
    95  		/* duration */
    96  		"to_duration":            vf.ToDuration,
    97  		"duration_round":         vf.DurationRound,
    98  		"duration_round_millis":  vf.DurationRoundMillis,
    99  		"duration_round_seconds": vf.DurationRoundSeconds,
   100  		/* numbers */
   101  		"format_money":    vf.FormatMoney,
   102  		"format_pct":      vf.FormatPct,
   103  		"format_filesize": vf.FormatFileSize,
   104  		"round":           vf.Round,
   105  		"ceil":            vf.Ceil,
   106  		"floor":           vf.Floor,
   107  		/* base64 */
   108  		"base64":       vf.Base64,
   109  		"base64decode": vf.Base64Decode,
   110  		/* uuid */
   111  		"parse_uuid": vf.ParseUUID,
   112  		"uuid":       vf.UUIDv4,
   113  		"uuidv4":     vf.UUIDv4,
   114  		/* strings */
   115  		"to_upper":                    vf.ToUpper,
   116  		"to_lower":                    vf.ToLower,
   117  		"to_title":                    vf.ToTitle,
   118  		"slugify":                     vf.Slugify,
   119  		"random_letters":              vf.RandomLetters,
   120  		"random_letters_with_numbers": vf.RandomLettersWithNumbers,
   121  		"trim_space":                  vf.TrimSpace,
   122  		"concat":                      vf.Concat,
   123  		"prefix":                      vf.Prefix,
   124  		"suffix":                      vf.Suffix,
   125  		"split":                       vf.Split,
   126  		"split_n":                     vf.SplitN,
   127  		"has_suffix":                  vf.HasSuffix,
   128  		"has_prefix":                  vf.HasPrefix,
   129  		"trim_suffix":                 vf.TrimSuffix,
   130  		"trim_prefix":                 vf.TrimPrefix,
   131  		"contains":                    vf.Contains,
   132  		"matches":                     vf.Matches,
   133  		"quote":                       vf.Quote,
   134  		"strip_quotes":                vf.StripQuotes,
   135  		/* arrays or maps */
   136  		"reverse":  vf.Reverse,
   137  		"slice":    vf.Slice,
   138  		"first":    vf.First,
   139  		"at_index": vf.AtIndex,
   140  		"last":     vf.Last,
   141  		"join":     vf.Join,
   142  		"csv":      vf.CSV,
   143  		"tsv":      vf.TSV,
   144  		/* urls */
   145  		"urlencode":          vf.URLEncode,
   146  		"url_scheme":         vf.URLScheme,
   147  		"with_url_scheme":    vf.WithURLScheme,
   148  		"url_host":           vf.URLHost,
   149  		"with_url_host":      vf.WithURLHost,
   150  		"url_port":           vf.URLPort,
   151  		"with_url_port":      vf.WithURLPort,
   152  		"url_path":           vf.URLPath,
   153  		"with_url_path":      vf.URLPath,
   154  		"url_raw_query":      vf.URLRawQuery,
   155  		"with_url_raw_query": vf.WithURLRawQuery,
   156  		"url_query":          vf.URLQuery,
   157  		"with_url_query":     vf.WithURLQuery,
   158  		/* cryptography */
   159  		"sha256": vf.SHA256,
   160  		"sha512": vf.SHA512,
   161  		"hmac":   vf.HMAC512,
   162  		/* semantic versions */
   163  		"semver_major":      vf.SemverMajor,
   164  		"semver_bump_major": vf.SemverBumpMajor,
   165  		"semver_minor":      vf.SemverMinor,
   166  		"semver_bump_minor": vf.SemverBumpMinor,
   167  		"semver_patch":      vf.SemverPatch,
   168  		"semver_bump_patch": vf.SemverBumpPatch,
   169  		/* generators */
   170  		"generate_ordinal_names": vf.GenerateOrdinalNames,
   171  		"generate_password":      vf.RandomLettersWithNumbersAndSymbols,
   172  		"generate_key":           vf.GenerateKey,
   173  		/* json + yaml */
   174  		"to_json":        vf.JSONEncode,
   175  		"to_json_pretty": vf.JSONEncodePretty,
   176  		"to_yaml":        vf.YAMLEncode,
   177  		"parse_json":     vf.ParseJSON,
   178  		"parse_yaml":     vf.ParseYAML,
   179  		/* indentation */
   180  		"indent_tabs":   vf.IndentTabs,
   181  		"indent_spaces": vf.IndentSpaces,
   182  		/* sequences */
   183  		"seq_int": vf.SequenceInts,
   184  		/* arithmatic */
   185  		"add":        vf.Add,
   186  		"mul":        vf.Multiply,
   187  		"div":        vf.Divide,
   188  		"sub":        vf.Subtract,
   189  		"to_float64": vf.ToFloat64,
   190  		"to_int":     vf.ToInt,
   191  	}
   192  }
   193  
   194  // FileExists returns if the file at a given path exists.
   195  func (vf ViewFuncs) FileExists(path string) bool {
   196  	_, err := os.Stat(path)
   197  	return err == nil
   198  }
   199  
   200  // ReadFile reads the contents of a file path as a string.
   201  func (vf ViewFuncs) ReadFile(path string) (string, error) {
   202  	contents, err := os.ReadFile(path)
   203  	return string(contents), err
   204  }
   205  
   206  // Process processes the given contents using a given template viewmodel
   207  func (vf ViewFuncs) Process(vm Viewmodel, contents string) (string, error) {
   208  	tmp := New().WithBody(contents).WithVars(vm.vars).WithEnvVars(vm.env)
   209  	buffer := new(bytes.Buffer)
   210  	if err := tmp.Process(buffer); err != nil {
   211  		return "", err
   212  	}
   213  	return buffer.String(), nil
   214  }
   215  
   216  // ToString attempts to return a string representation of a value.
   217  func (vf ViewFuncs) ToString(v interface{}) string {
   218  	switch c := v.(type) {
   219  	case []byte:
   220  		return string(c)
   221  	case string:
   222  		return c
   223  	default:
   224  		return fmt.Sprintf("%v", v)
   225  	}
   226  }
   227  
   228  // ToBytes attempts to return a bytes representation of a value.
   229  func (vf ViewFuncs) ToBytes(v interface{}) []byte {
   230  	return []byte(fmt.Sprintf("%v", v))
   231  }
   232  
   233  // ParseInt parses a value as an integer.
   234  func (vf ViewFuncs) ParseInt(v interface{}) (int, error) {
   235  	return strconv.Atoi(fmt.Sprintf("%v", v))
   236  }
   237  
   238  // ParseInt64 parses a value as an int64.
   239  func (vf ViewFuncs) ParseInt64(v interface{}) (int64, error) {
   240  	return strconv.ParseInt(fmt.Sprintf("%v", v), 10, 64)
   241  }
   242  
   243  // ParseFloat64 parses a value as a float64.
   244  func (vf ViewFuncs) ParseFloat64(v string) (float64, error) {
   245  	return strconv.ParseFloat(v, 64)
   246  }
   247  
   248  // Now returns the current time in the system timezone.
   249  func (vf ViewFuncs) Now() time.Time {
   250  	return time.Now()
   251  }
   252  
   253  // NowUTC returns the current time in the UTC timezone.
   254  func (vf ViewFuncs) NowUTC() time.Time {
   255  	return time.Now().UTC()
   256  }
   257  
   258  // Unix returns the unix format for a timestamp.
   259  func (vf ViewFuncs) Unix(t time.Time) int64 {
   260  	return t.Unix()
   261  }
   262  
   263  // UnixNano returns the timetamp as nanoseconds from 1970-01-01.
   264  func (vf ViewFuncs) UnixNano(t time.Time) int64 {
   265  	return t.UnixNano()
   266  }
   267  
   268  // RFC3339 returns the RFC3339 format for a timestamp.
   269  func (vf ViewFuncs) RFC3339(t time.Time) string {
   270  	return t.Format(time.RFC3339)
   271  }
   272  
   273  // TimeShort returns the short format for a timestamp.
   274  // The format string is "1/02/2006 3:04:05 PM".
   275  func (vf ViewFuncs) TimeShort(t time.Time) string {
   276  	return t.Format("1/02/2006 3:04:05 PM")
   277  }
   278  
   279  // TimeFormat returns the time with a given format string.
   280  func (vf ViewFuncs) TimeFormat(format string, t time.Time) string {
   281  	return t.Format(format)
   282  }
   283  
   284  // TimeIsZero returns if the time is set or not.
   285  func (vf ViewFuncs) TimeIsZero(t time.Time) bool {
   286  	return t.IsZero()
   287  }
   288  
   289  // TimeIsEpoch returns if the time is the unix epoch time or not.
   290  func (vf ViewFuncs) TimeIsEpoch(t time.Time) bool {
   291  	return t.Equal(time.Unix(0, 0))
   292  }
   293  
   294  // DateLong returns the short date for a timestamp.
   295  func (vf ViewFuncs) DateLong(t time.Time) string {
   296  	return t.Format("Jan _2, 2006")
   297  }
   298  
   299  // DateShort returns the short date for a timestamp.
   300  // The format string is "1/02/2006"
   301  func (vf ViewFuncs) DateShort(t time.Time) string {
   302  	return t.Format("1/02/2006")
   303  }
   304  
   305  // DateShortRev returns the short date for a timestamp in YYYY/mm/dd format.
   306  func (vf ViewFuncs) DateShortRev(t time.Time) string {
   307  	return t.Format("2006/1/02")
   308  }
   309  
   310  // TimeMedium returns the medium format for a timestamp.
   311  // The format string is "1/02/2006 3:04:05 PM".
   312  func (vf ViewFuncs) TimeMedium(t time.Time) string {
   313  	return t.Format("Jan 02, 2006 3:04:05 PM")
   314  }
   315  
   316  // TimeKitchen returns the kitchen format for a timestamp.
   317  // The format string is "3:04PM".
   318  func (vf ViewFuncs) TimeKitchen(t time.Time) string {
   319  	return t.Format(time.Kitchen)
   320  }
   321  
   322  // DateMonthDay returns the month dat format for a timestamp.
   323  // The format string is "1/2".
   324  func (vf ViewFuncs) DateMonthDay(t time.Time) string {
   325  	return t.Format("1/2")
   326  }
   327  
   328  // TimeInUTC returns the time in a given location by string.
   329  // If the location is invalid, this will error.
   330  func (vf ViewFuncs) TimeInUTC(t time.Time) time.Time {
   331  	return t.UTC()
   332  }
   333  
   334  // TimeInLocation returns the time in a given location by string.
   335  // If the location is invalid, this will error.
   336  func (vf ViewFuncs) TimeInLocation(loc string, t time.Time) (time.Time, error) {
   337  	location, err := time.LoadLocation(loc)
   338  	if err != nil {
   339  		return time.Time{}, err
   340  	}
   341  	return t.In(location), err
   342  }
   343  
   344  // ParseTime parses a time string with a given format.
   345  func (vf ViewFuncs) ParseTime(format, v string) (time.Time, error) {
   346  	return time.Parse(format, v)
   347  }
   348  
   349  // ParseUnix returns a timestamp from a unix format.
   350  func (vf ViewFuncs) ParseUnix(v int64) time.Time {
   351  	return time.Unix(v, 0)
   352  }
   353  
   354  // Year returns the year component of a timestamp.
   355  func (vf ViewFuncs) Year(t time.Time) int {
   356  	return t.Year()
   357  }
   358  
   359  // Month returns the month component of a timestamp.
   360  func (vf ViewFuncs) Month(t time.Time) int {
   361  	return int(t.Month())
   362  }
   363  
   364  // Day returns the day component of a timestamp.
   365  func (vf ViewFuncs) Day(t time.Time) int {
   366  	return t.Day()
   367  }
   368  
   369  // Hour returns the hour component of a timestamp.
   370  func (vf ViewFuncs) Hour(t time.Time) int {
   371  	return t.Hour()
   372  }
   373  
   374  // Minute returns the minute component of a timestamp.
   375  func (vf ViewFuncs) Minute(t time.Time) int {
   376  	return t.Minute()
   377  }
   378  
   379  // Second returns the seconds component of a timestamp.
   380  func (vf ViewFuncs) Second(t time.Time) int {
   381  	return t.Second()
   382  }
   383  
   384  // Millisecond returns the millisecond component of a timestamp.
   385  func (vf ViewFuncs) Millisecond(t time.Time) int {
   386  	return int(time.Duration(t.Nanosecond()) / time.Millisecond)
   387  }
   388  
   389  // ToDuration returns a given value as a duration.
   390  func (vf ViewFuncs) ToDuration(val interface{}) (typedVal time.Duration, err error) {
   391  	switch tv := val.(type) {
   392  	case time.Duration:
   393  		typedVal = tv
   394  	case uint8:
   395  		typedVal = time.Duration(tv)
   396  	case int8:
   397  		typedVal = time.Duration(tv)
   398  	case uint16:
   399  		typedVal = time.Duration(tv)
   400  	case int16:
   401  		typedVal = time.Duration(tv)
   402  	case uint32:
   403  		typedVal = time.Duration(tv)
   404  	case int32:
   405  		typedVal = time.Duration(tv)
   406  	case uint64:
   407  		typedVal = time.Duration(tv)
   408  	case int64:
   409  		typedVal = time.Duration(tv)
   410  	case int:
   411  		typedVal = time.Duration(tv)
   412  	case uint:
   413  		typedVal = time.Duration(tv)
   414  	case float32:
   415  		typedVal = time.Duration(tv)
   416  	case float64:
   417  		typedVal = time.Duration(tv)
   418  	default:
   419  		err = fmt.Errorf("invalid duration value %[1]T: %[1]v", val)
   420  	}
   421  	return
   422  }
   423  
   424  // Since returns the duration since a given timestamp.
   425  // It is relative, meaning the value returned can be negative.
   426  func (vf ViewFuncs) Since(t time.Time) time.Duration {
   427  	return time.Since(t)
   428  }
   429  
   430  // TimeSub the duration difference between two times.
   431  func (vf ViewFuncs) TimeSub(t1, t2 time.Time) time.Duration {
   432  	return t1.UTC().Sub(t2.UTC())
   433  }
   434  
   435  // SinceUTC returns the duration since a given timestamp in UTC.
   436  // It is relative, meaning the value returned can be negative.
   437  func (vf ViewFuncs) SinceUTC(t time.Time) time.Duration {
   438  	return time.Now().UTC().Sub(t.UTC())
   439  }
   440  
   441  // DurationRound rounds a duration value.
   442  func (vf ViewFuncs) DurationRound(d time.Duration, to time.Duration) time.Duration {
   443  	return d.Round(to)
   444  }
   445  
   446  // DurationRoundMillis rounds a duration value to milliseconds.
   447  func (vf ViewFuncs) DurationRoundMillis(d time.Duration) time.Duration {
   448  	return d.Round(time.Millisecond)
   449  }
   450  
   451  // DurationRoundSeconds rounds a duration value to seconds.
   452  func (vf ViewFuncs) DurationRoundSeconds(d time.Duration) time.Duration {
   453  	return d.Round(time.Millisecond)
   454  }
   455  
   456  // ParseBool attempts to parse a value as a bool.
   457  // "truthy" values include "true", "1", "yes".
   458  // "falsey" values include "false", "0", "no".
   459  func (vf ViewFuncs) ParseBool(raw interface{}) (bool, error) {
   460  	v := fmt.Sprintf("%v", raw)
   461  	if len(v) == 0 {
   462  		return false, nil
   463  	}
   464  	switch strings.ToLower(v) {
   465  	case "true", "1", "yes":
   466  		return true, nil
   467  	case "false", "0", "no":
   468  		return false, nil
   469  	default:
   470  		return false, fmt.Errorf("invalid boolean value `%s`", v)
   471  	}
   472  }
   473  
   474  // Round returns the value rounded to a given set of places.
   475  // It uses midpoint rounding.
   476  func (vf ViewFuncs) Round(places, d float64) float64 {
   477  	return mathutil.RoundPlaces(d, int(places))
   478  }
   479  
   480  // Ceil returns the value rounded up to the nearest integer.
   481  func (vf ViewFuncs) Ceil(d float64) float64 {
   482  	return math.Ceil(d)
   483  }
   484  
   485  // Floor returns the value rounded down to zero.
   486  func (vf ViewFuncs) Floor(d float64) float64 {
   487  	return math.Floor(d)
   488  }
   489  
   490  // FormatMoney returns a float as a formatted string rounded to two decimal places.
   491  func (vf ViewFuncs) FormatMoney(d float64) string {
   492  	return fmt.Sprintf("$%0.2f", mathutil.RoundPlaces(d, 2))
   493  }
   494  
   495  // FormatPct formats a float as a percentage (it is multiplied by 100,
   496  // then suffixed with '%')
   497  func (vf ViewFuncs) FormatPct(d float64) string {
   498  	return fmt.Sprintf("%0.2f%%", d*100)
   499  }
   500  
   501  // FormatFileSize formats an int as a file size.
   502  func (vf ViewFuncs) FormatFileSize(sizeBytes int) string {
   503  	return stringutil.FileSize(sizeBytes)
   504  }
   505  
   506  // Base64 encodes data as a string as a base6 string.
   507  func (vf ViewFuncs) Base64(v string) string {
   508  	return base64.StdEncoding.EncodeToString([]byte(v))
   509  }
   510  
   511  //Base64Decode decodes a base 64 string.
   512  func (vf ViewFuncs) Base64Decode(v string) (string, error) {
   513  	result, err := base64.StdEncoding.DecodeString(v)
   514  	if err != nil {
   515  		return "", err
   516  	}
   517  	return string(result), nil
   518  }
   519  
   520  // ParseUUID parses a uuid.
   521  func (vf ViewFuncs) ParseUUID(v string) (uuid.UUID, error) {
   522  	return uuid.Parse(v)
   523  }
   524  
   525  // UUIDv4 generates a uuid v4.
   526  func (vf ViewFuncs) UUIDv4() uuid.UUID {
   527  	return uuid.V4()
   528  }
   529  
   530  // ToUpper returns a string case shifted to upper case.
   531  func (vf ViewFuncs) ToUpper(v string) string {
   532  	return strings.ToUpper(v)
   533  }
   534  
   535  // ToLower returns a string case shifted to lower case.
   536  func (vf ViewFuncs) ToLower(v string) string {
   537  	return strings.ToLower(v)
   538  }
   539  
   540  // ToTitle returns a title cased string.
   541  func (vf ViewFuncs) ToTitle(v string) string {
   542  	return strings.ToTitle(v)
   543  }
   544  
   545  // Slugify returns a slug format string.
   546  // It replaces whitespace with `-`
   547  // It path escapes any other characters.
   548  func (vf ViewFuncs) Slugify(v string) string {
   549  	return stringutil.Slugify(v)
   550  }
   551  
   552  // TrimSpace trims whitespace from the beginning and end of a string.
   553  func (vf ViewFuncs) TrimSpace(v string) string {
   554  	return strings.TrimSpace(v)
   555  }
   556  
   557  // Prefix appends a given string to a prefix.
   558  func (vf ViewFuncs) Prefix(pref, v string) string {
   559  	return pref + v
   560  }
   561  
   562  // Concat concatenates a list of strings.
   563  func (vf ViewFuncs) Concat(strs ...string) string {
   564  	var output string
   565  	for index := 0; index < len(strs); index++ {
   566  		output = output + strs[index]
   567  	}
   568  	return output
   569  }
   570  
   571  // Suffix appends a given prefix to a string.
   572  func (vf ViewFuncs) Suffix(suf, v string) string {
   573  	return v + suf
   574  }
   575  
   576  // Split splits a string by a separator.
   577  func (vf ViewFuncs) Split(sep, v string) []string {
   578  	return strings.Split(v, sep)
   579  }
   580  
   581  // SplitN splits a string by a separator a given number of times.
   582  func (vf ViewFuncs) SplitN(sep string, n float64, v string) []string {
   583  	return strings.SplitN(v, sep, int(n))
   584  }
   585  
   586  // RandomLetters returns a string of random letters.
   587  func (vf ViewFuncs) RandomLetters(length int) string {
   588  	return stringutil.Random(stringutil.Letters, length)
   589  }
   590  
   591  // RandomLettersWithNumbers returns a string of random letters.
   592  func (vf ViewFuncs) RandomLettersWithNumbers(count int) string {
   593  	return stringutil.Random(stringutil.LettersAndNumbers, count)
   594  }
   595  
   596  // RandomLettersWithNumbersAndSymbols returns a string of random letters.
   597  func (vf ViewFuncs) RandomLettersWithNumbersAndSymbols(count int) string {
   598  	return stringutil.Random(stringutil.LettersNumbersAndSymbols, count)
   599  }
   600  
   601  //
   602  // array functions
   603  //
   604  
   605  // Reverse reverses an array.
   606  func (vf ViewFuncs) Reverse(collection interface{}) (interface{}, error) {
   607  	value := reflect.ValueOf(collection)
   608  
   609  	if value.Type().Kind() != reflect.Slice {
   610  		return nil, fmt.Errorf("input must be a slice")
   611  	}
   612  
   613  	output := make([]interface{}, value.Len())
   614  	for index := 0; index < value.Len(); index++ {
   615  		output[index] = value.Index((value.Len() - 1) - index).Interface()
   616  	}
   617  	return output, nil
   618  }
   619  
   620  // Slice returns a subrange of a collection.
   621  func (vf ViewFuncs) Slice(from, to int, collection interface{}) (interface{}, error) {
   622  	value := reflect.ValueOf(collection)
   623  
   624  	if value.Type().Kind() != reflect.Slice {
   625  		return nil, fmt.Errorf("input must be a slice")
   626  	}
   627  
   628  	return value.Slice(from, to).Interface(), nil
   629  }
   630  
   631  // First returns the first element of a collection.
   632  func (vf ViewFuncs) First(collection interface{}) (interface{}, error) {
   633  	value := reflect.ValueOf(collection)
   634  	kind := value.Type().Kind()
   635  	if kind != reflect.Slice && kind != reflect.Map && kind != reflect.Array {
   636  		return nil, fmt.Errorf("input must be a slice or map")
   637  	}
   638  	if value.Len() == 0 {
   639  		return nil, nil
   640  	}
   641  	switch kind {
   642  	case reflect.Slice, reflect.Array:
   643  		return value.Index(0).Interface(), nil
   644  	case reflect.Map:
   645  		iter := value.MapRange()
   646  		if iter.Next() {
   647  			return iter.Value().Interface(), nil
   648  		}
   649  	default:
   650  	}
   651  
   652  	return nil, nil
   653  }
   654  
   655  // AtIndex returns an element at a given index.
   656  func (vf ViewFuncs) AtIndex(index int, collection interface{}) (interface{}, error) {
   657  	value := reflect.ValueOf(collection)
   658  	if value.Type().Kind() != reflect.Slice {
   659  		return nil, fmt.Errorf("input must be a slice")
   660  	}
   661  	if value.Len() == 0 {
   662  		return nil, nil
   663  	}
   664  	return value.Index(index).Interface(), nil
   665  }
   666  
   667  // Last returns the last element of a collection.
   668  func (vf ViewFuncs) Last(collection interface{}) (interface{}, error) {
   669  	value := reflect.ValueOf(collection)
   670  	if value.Type().Kind() != reflect.Slice {
   671  		return nil, fmt.Errorf("input must be a slice")
   672  	}
   673  	if value.Len() == 0 {
   674  		return nil, nil
   675  	}
   676  	return value.Index(value.Len() - 1).Interface(), nil
   677  }
   678  
   679  // Join creates a string joined with a given separator.
   680  func (vf ViewFuncs) Join(sep string, collection interface{}) (string, error) {
   681  	value := reflect.ValueOf(collection)
   682  	if value.Type().Kind() != reflect.Slice {
   683  		return "", fmt.Errorf("input must be a slice")
   684  	}
   685  	if value.Len() == 0 {
   686  		return "", nil
   687  	}
   688  	values := make([]string, value.Len())
   689  	for i := 0; i < value.Len(); i++ {
   690  		values[i] = fmt.Sprintf("%v", value.Index(i).Interface())
   691  	}
   692  	return strings.Join(values, sep), nil
   693  }
   694  
   695  // CSV returns a csv of a given collection.
   696  func (vf ViewFuncs) CSV(collection interface{}) (string, error) {
   697  	return vf.Join(",", collection)
   698  }
   699  
   700  // TSV returns a tab separated values of a given collection.
   701  func (vf ViewFuncs) TSV(collection interface{}) (string, error) {
   702  	return vf.Join("\t", collection)
   703  }
   704  
   705  // HasSuffix returns if a string has a given suffix.
   706  func (vf ViewFuncs) HasSuffix(suffix, v string) bool {
   707  	return strings.HasSuffix(v, suffix)
   708  }
   709  
   710  // HasPrefix returns if a string has a given prefix.
   711  func (vf ViewFuncs) HasPrefix(prefix, v string) bool {
   712  	return strings.HasPrefix(v, prefix)
   713  }
   714  
   715  // TrimSuffix returns if a string has a given suffix.
   716  func (vf ViewFuncs) TrimSuffix(suffix, v string) string {
   717  	return strings.TrimSuffix(v, suffix)
   718  }
   719  
   720  // TrimPrefix returns if a string has a given prefix.
   721  func (vf ViewFuncs) TrimPrefix(prefix, v string) string {
   722  	return strings.TrimPrefix(v, prefix)
   723  }
   724  
   725  // Contains returns if a string contains a given substring.
   726  func (vf ViewFuncs) Contains(substr, v string) bool {
   727  	return strings.Contains(v, substr)
   728  }
   729  
   730  // Matches returns if a string matches a given regular expression.
   731  func (vf ViewFuncs) Matches(expr, v string) (bool, error) {
   732  	return regexp.MatchString(expr, v)
   733  }
   734  
   735  // Quote returns a string wrapped in " characters.
   736  // It will trim space before and after, and only add quotes
   737  // if they don't already exist.
   738  func (vf ViewFuncs) Quote(v string) string {
   739  	v = strings.TrimSpace(v)
   740  	if !strings.HasPrefix(v, "\"") {
   741  		v = "\"" + v
   742  	}
   743  	if !strings.HasSuffix(v, "\"") {
   744  		v = v + "\""
   745  	}
   746  	return v
   747  }
   748  
   749  // StripQuotes strips leading and trailing quotes.
   750  func (vf ViewFuncs) StripQuotes(v string) string {
   751  	v = strings.TrimSpace(v)
   752  	v = strings.TrimPrefix(v, "\"")
   753  	v = strings.TrimSuffix(v, "\"")
   754  	return v
   755  }
   756  
   757  // ParseURL parses a url.
   758  func (vf ViewFuncs) ParseURL(v string) (*url.URL, error) {
   759  	return url.Parse(v)
   760  }
   761  
   762  // URLEncode encodes a value as a url token.
   763  func (vf ViewFuncs) URLEncode(value string) string {
   764  	return url.QueryEscape(value)
   765  }
   766  
   767  // URLScheme returns the scheme of a url.
   768  func (vf ViewFuncs) URLScheme(v *url.URL) string {
   769  	return v.Scheme
   770  }
   771  
   772  // WithURLScheme returns the scheme of a url.
   773  func (vf ViewFuncs) WithURLScheme(scheme string, v *url.URL) *url.URL {
   774  	return webutil.URLWithScheme(v, scheme)
   775  }
   776  
   777  // URLHost returns the host of a url.
   778  func (vf ViewFuncs) URLHost(v *url.URL) string {
   779  	return v.Host
   780  }
   781  
   782  // WithURLHost returns the host of a url.
   783  func (vf ViewFuncs) WithURLHost(host string, v *url.URL) *url.URL {
   784  	return webutil.URLWithHost(v, host)
   785  }
   786  
   787  // URLPort returns the url port.
   788  // If none is explicitly specified, this will return empty string.
   789  func (vf ViewFuncs) URLPort(v *url.URL) string {
   790  	return v.Port()
   791  }
   792  
   793  // WithURLPort sets the url port.
   794  func (vf ViewFuncs) WithURLPort(port string, v *url.URL) *url.URL {
   795  	return webutil.URLWithPort(v, port)
   796  }
   797  
   798  // URLPath returns the url path.
   799  func (vf ViewFuncs) URLPath(v *url.URL) string {
   800  	return v.Path
   801  }
   802  
   803  // WithURLPath returns the url path.
   804  func (vf ViewFuncs) WithURLPath(path string, v *url.URL) *url.URL {
   805  	return webutil.URLWithPath(v, path)
   806  }
   807  
   808  // URLRawQuery returns the url raw query.
   809  func (vf ViewFuncs) URLRawQuery(v *url.URL) string {
   810  	return v.RawQuery
   811  }
   812  
   813  // WithURLRawQuery returns the url path.
   814  func (vf ViewFuncs) WithURLRawQuery(rawQuery string, v *url.URL) *url.URL {
   815  	return webutil.URLWithRawQuery(v, rawQuery)
   816  }
   817  
   818  // URLQuery returns a url query param.
   819  func (vf ViewFuncs) URLQuery(name string, v *url.URL) string {
   820  	return v.Query().Get(name)
   821  }
   822  
   823  // WithURLQuery returns a url query param.
   824  func (vf ViewFuncs) WithURLQuery(key, value string, v *url.URL) *url.URL {
   825  	return webutil.URLWithQuery(v, key, value)
   826  }
   827  
   828  // SHA256 returns the sha256 sum of a string.
   829  func (vf ViewFuncs) SHA256(v string) string {
   830  	h := sha256.New()
   831  	fmt.Fprint(h, v)
   832  	return hex.EncodeToString(h.Sum(nil))
   833  }
   834  
   835  // SHA512 returns the sha512 sum of a string.
   836  func (vf ViewFuncs) SHA512(v string) string {
   837  	h := sha512.New()
   838  	fmt.Fprint(h, v)
   839  	return hex.EncodeToString(h.Sum(nil))
   840  }
   841  
   842  // HMAC512 returns the hmac signed sha 512 sum of a string.
   843  func (vf ViewFuncs) HMAC512(key, v string) (string, error) {
   844  	keyBytes, err := base64.StdEncoding.DecodeString(key)
   845  	if err != nil {
   846  		return "", err
   847  	}
   848  	h := hmac.New(sha512.New, keyBytes)
   849  	fmt.Fprint(h, v)
   850  	return hex.EncodeToString(h.Sum(nil)), nil
   851  }
   852  
   853  // ParseSemver parses a semantic version string.
   854  func (vf ViewFuncs) ParseSemver(v string) (*semver.Version, error) {
   855  	return semver.NewVersion(v)
   856  }
   857  
   858  // SemverMajor returns the major component of a semver.
   859  func (vf ViewFuncs) SemverMajor(v *semver.Version) int {
   860  	return int(v.Major())
   861  }
   862  
   863  // SemverBumpMajor returns a semver with an incremented major version.
   864  func (vf ViewFuncs) SemverBumpMajor(v *semver.Version) *semver.Version {
   865  	v.BumpMajor()
   866  	return v
   867  }
   868  
   869  // SemverMinor returns the minor component of a semver.
   870  func (vf ViewFuncs) SemverMinor(v *semver.Version) int {
   871  	return int(v.Minor())
   872  }
   873  
   874  // SemverBumpMinor returns a semver with an incremented minor version.
   875  func (vf ViewFuncs) SemverBumpMinor(v *semver.Version) *semver.Version {
   876  	v.BumpMinor()
   877  	return v
   878  }
   879  
   880  // SemverPatch returns the patch component of a semver.
   881  func (vf ViewFuncs) SemverPatch(v *semver.Version) int {
   882  	return int(v.Patch())
   883  }
   884  
   885  // SemverBumpPatch returns a semver with an incremented patch version.
   886  func (vf ViewFuncs) SemverBumpPatch(v *semver.Version) *semver.Version {
   887  	v.BumpPatch()
   888  	return v
   889  }
   890  
   891  // IndentTabs indents a string with a given number of tabs.
   892  func (vf ViewFuncs) IndentTabs(tabCount int, v interface{}) string {
   893  	lines := strings.Split(fmt.Sprintf("%v", v), "\n")
   894  	outputLines := make([]string, len(lines))
   895  
   896  	var tabs string
   897  	for i := 0; i < tabCount; i++ {
   898  		tabs = tabs + "\t"
   899  	}
   900  
   901  	for i := 0; i < len(lines); i++ {
   902  		outputLines[i] = tabs + lines[i]
   903  	}
   904  	return strings.Join(outputLines, "\n")
   905  }
   906  
   907  // IndentSpaces indents a string by a given set of spaces.
   908  func (vf ViewFuncs) IndentSpaces(spaceCount int, v interface{}) string {
   909  	lines := strings.Split(fmt.Sprintf("%v", v), "\n")
   910  	outputLines := make([]string, len(lines))
   911  
   912  	var spaces string
   913  	for i := 0; i < spaceCount; i++ {
   914  		spaces = spaces + " "
   915  	}
   916  
   917  	for i := 0; i < len(lines); i++ {
   918  		outputLines[i] = spaces + lines[i]
   919  	}
   920  	return strings.Join(outputLines, "\n")
   921  }
   922  
   923  // GenerateOrdinalNames generates ordinal names by passing the index to a given formatter.
   924  // The formatter should be in Sprintf format (i.e. using a '%d' token for where the index should go).
   925  /*
   926  Example:
   927      {{ generate_ordinal_names "worker-%d" 3 }} // [worker-0 worker-1 worker-2]
   928  */
   929  func (vf ViewFuncs) GenerateOrdinalNames(format string, replicas int) []string {
   930  	output := make([]string, replicas)
   931  	for index := 0; index < replicas; index++ {
   932  		output[index] = fmt.Sprintf(format, index)
   933  	}
   934  	return output
   935  }
   936  
   937  // GenerateKey generates a key of a given size base 64 encoded.
   938  func (vf ViewFuncs) GenerateKey(keySize int) string {
   939  	key := make([]byte, keySize)
   940  	_, _ = io.ReadFull(rand.Reader, key)
   941  	return base64.StdEncoding.EncodeToString(key)
   942  }
   943  
   944  // YAMLEncode returns an object encoded as yaml.
   945  func (vf ViewFuncs) YAMLEncode(v interface{}) (string, error) {
   946  	data, err := yaml.Marshal(v)
   947  	return string(data), err
   948  }
   949  
   950  // JSONEncode returns an object encoded as json.
   951  func (vf ViewFuncs) JSONEncode(v interface{}) (string, error) {
   952  	data, err := json.Marshal(v)
   953  	return string(data), err
   954  }
   955  
   956  // JSONEncodePretty encodes an object as json with indentation.
   957  func (vf ViewFuncs) JSONEncodePretty(v interface{}) (string, error) {
   958  	buf := new(bytes.Buffer)
   959  	encoder := json.NewEncoder(buf)
   960  	encoder.SetIndent("", "\t")
   961  	err := encoder.Encode(v)
   962  	if err != nil {
   963  		return "", err
   964  	}
   965  	return buf.String(), nil
   966  }
   967  
   968  // ParseYAML decodes a corups as yaml.
   969  func (vf ViewFuncs) ParseYAML(v string) (interface{}, error) {
   970  	var data interface{}
   971  	err := yaml.Unmarshal([]byte(v), &data)
   972  	return data, err
   973  }
   974  
   975  // ParseJSON returns an object encoded as json.
   976  func (vf ViewFuncs) ParseJSON(v string) (interface{}, error) {
   977  	var data interface{}
   978  	err := json.Unmarshal([]byte(v), &data)
   979  	return data, err
   980  }
   981  
   982  // SequenceInts returns an array of ints from min to max, not including max.
   983  // Given (0,5) as inputs, it would return [0,1,2,3,4]
   984  func (vf ViewFuncs) SequenceInts(start, end int) []int {
   985  	if start == end {
   986  		return []int{}
   987  	}
   988  	if start > end {
   989  		output := make([]int, start-end)
   990  		for x := start; x > end; x-- {
   991  			output[start-x] = x
   992  		}
   993  		return output
   994  	}
   995  
   996  	output := make([]int, end-start)
   997  	for x := start; x < end; x++ {
   998  		output[x] = x
   999  	}
  1000  	return output
  1001  }
  1002  
  1003  // ToFloat64 returns a given value as a float64.
  1004  func (vf ViewFuncs) ToFloat64(val interface{}) (typedVal float64, err error) {
  1005  	switch tv := val.(type) {
  1006  	case uint8:
  1007  		typedVal = float64(tv)
  1008  	case int8:
  1009  		typedVal = float64(tv)
  1010  	case uint16:
  1011  		typedVal = float64(tv)
  1012  	case int16:
  1013  		typedVal = float64(tv)
  1014  	case uint32:
  1015  		typedVal = float64(tv)
  1016  	case int32:
  1017  		typedVal = float64(tv)
  1018  	case uint64:
  1019  		typedVal = float64(tv)
  1020  	case int64:
  1021  		typedVal = float64(tv)
  1022  	case int:
  1023  		typedVal = float64(tv)
  1024  	case float32:
  1025  		typedVal = float64(tv)
  1026  	case float64:
  1027  		typedVal = tv
  1028  	default:
  1029  		err = fmt.Errorf("invalid to_float value %[1]T: %[1]v", val)
  1030  	}
  1031  	return
  1032  }
  1033  
  1034  // ToInt returns a given value as a int64.
  1035  func (vf ViewFuncs) ToInt(val interface{}) (typedVal int, err error) {
  1036  	switch tv := val.(type) {
  1037  	case uint8:
  1038  		typedVal = int(tv)
  1039  	case int8:
  1040  		typedVal = int(tv)
  1041  	case uint16:
  1042  		typedVal = int(tv)
  1043  	case int16:
  1044  		typedVal = int(tv)
  1045  	case uint32:
  1046  		typedVal = int(tv)
  1047  	case int32:
  1048  		typedVal = int(tv)
  1049  	case uint64:
  1050  		typedVal = int(tv)
  1051  	case int64:
  1052  		typedVal = int(tv)
  1053  	case int:
  1054  		typedVal = tv
  1055  	case float32:
  1056  		typedVal = int(tv)
  1057  	case float64:
  1058  		typedVal = int(tv)
  1059  	default:
  1060  		err = fmt.Errorf("invalid to_int value %[1]T: %[1]v", val)
  1061  	}
  1062  	return
  1063  }
  1064  
  1065  // Add adds numbers together.
  1066  func (vf ViewFuncs) Add(values ...interface{}) (float64, error) {
  1067  	var output float64
  1068  	var typedVal float64
  1069  	var err error
  1070  	for index, val := range values {
  1071  		typedVal, err = vf.ToFloat64(val)
  1072  		if err != nil {
  1073  			return 0, err
  1074  		}
  1075  		if index == 0 {
  1076  			output = typedVal
  1077  		} else {
  1078  			output += typedVal
  1079  		}
  1080  	}
  1081  	return output, nil
  1082  }
  1083  
  1084  // Multiply multiplies numbers together.
  1085  func (vf ViewFuncs) Multiply(values ...interface{}) (float64, error) {
  1086  	var output float64
  1087  	var typedVal float64
  1088  	var err error
  1089  	for index, val := range values {
  1090  		typedVal, err = vf.ToFloat64(val)
  1091  		if err != nil {
  1092  			return 0, err
  1093  		}
  1094  		if index == 0 {
  1095  			output = typedVal
  1096  		} else {
  1097  			output *= typedVal
  1098  		}
  1099  	}
  1100  	return output, nil
  1101  }
  1102  
  1103  // Subtract divides numbers together.
  1104  func (vf ViewFuncs) Subtract(values ...interface{}) (float64, error) {
  1105  	var output float64
  1106  	var typedVal float64
  1107  	var err error
  1108  	for index, val := range values {
  1109  		typedVal, err = vf.ToFloat64(val)
  1110  		if err != nil {
  1111  			return 0, err
  1112  		}
  1113  		if index == 0 {
  1114  			output = typedVal
  1115  		} else {
  1116  			output -= typedVal
  1117  		}
  1118  	}
  1119  	return output, nil
  1120  }
  1121  
  1122  // Divide divides numbers together.
  1123  func (vf ViewFuncs) Divide(values ...interface{}) (float64, error) {
  1124  	var output float64
  1125  	var typedVal float64
  1126  	var err error
  1127  	for index, val := range values {
  1128  		typedVal, err = vf.ToFloat64(val)
  1129  		if err != nil {
  1130  			return 0, err
  1131  		}
  1132  		if index == 0 {
  1133  			output = typedVal
  1134  		} else {
  1135  			output /= typedVal
  1136  		}
  1137  	}
  1138  	return output, nil
  1139  }