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 © 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 © 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 © 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 © 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 © 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 © 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 }