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 }