lib.go 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. package templatelib
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "os"
  6. "reflect"
  7. "strings"
  8. "text/template"
  9. )
  10. func swapStringsFuncBoolArgsOrder(a func(string, string) bool) func(string, string) bool {
  11. return func(str1 string, str2 string) bool {
  12. return a(str2, str1)
  13. }
  14. }
  15. func thingsActionFactory(name string, actOnFirst bool, action func([]interface{}, interface{}) interface{}) func(args ...interface{}) interface{} {
  16. return func(args ...interface{}) interface{} {
  17. if len(args) < 1 {
  18. panic(fmt.Sprintf(`%q requires at least one argument`, name))
  19. }
  20. actArgs := []interface{}{}
  21. for _, val := range args {
  22. v := reflect.ValueOf(val)
  23. switch v.Kind() {
  24. case reflect.Slice, reflect.Array:
  25. for i := 0; i < v.Len(); i++ {
  26. actArgs = append(actArgs, v.Index(i).Interface())
  27. }
  28. default:
  29. actArgs = append(actArgs, v.Interface())
  30. }
  31. }
  32. var arg interface{}
  33. if actOnFirst {
  34. arg = actArgs[0]
  35. actArgs = actArgs[1:]
  36. } else {
  37. arg = actArgs[len(actArgs)-1]
  38. actArgs = actArgs[:len(actArgs)-1]
  39. }
  40. return action(actArgs, arg)
  41. }
  42. }
  43. func stringsActionFactory(name string, actOnFirst bool, action func([]string, string) string) func(args ...interface{}) interface{} {
  44. return thingsActionFactory(name, actOnFirst, func(args []interface{}, arg interface{}) interface{} {
  45. str := arg.(string)
  46. strs := []string{}
  47. for _, val := range args {
  48. strs = append(strs, val.(string))
  49. }
  50. return action(strs, str)
  51. })
  52. }
  53. func stringsModifierActionFactory(a func(string, string) string) func([]string, string) string {
  54. return func(strs []string, str string) string {
  55. for _, mod := range strs {
  56. str = a(str, mod)
  57. }
  58. return str
  59. }
  60. }
  61. var FuncMap = template.FuncMap{
  62. // {{- $isGitHub := hasPrefix "https://github.com/" $url -}}
  63. // {{- $isHtml := hasSuffix ".html" $url -}}
  64. "hasPrefix": swapStringsFuncBoolArgsOrder(strings.HasPrefix),
  65. "hasSuffix": swapStringsFuncBoolArgsOrder(strings.HasSuffix),
  66. // {{- $hugeIfTrue := .SomeValue | ternary "HUGE" "not so huge" -}}
  67. // if .SomeValue is truthy, $hugeIfTrue will be "HUGE"
  68. // (otherwise, "not so huge")
  69. "ternary": func(truthy interface{}, falsey interface{}, val interface{}) interface{} {
  70. if t, ok := template.IsTrue(val); !ok {
  71. panic(fmt.Sprintf(`template.IsTrue(%+v) says things are NOT OK`, val))
  72. } else if t {
  73. return truthy
  74. } else {
  75. return falsey
  76. }
  77. },
  78. // First Tag: {{- .Tags | first -}}
  79. // Last Tag: {{- .Tags | last -}}
  80. "first": thingsActionFactory("first", true, func(args []interface{}, arg interface{}) interface{} { return arg }),
  81. "last": thingsActionFactory("last", false, func(args []interface{}, arg interface{}) interface{} { return arg }),
  82. // JSON data dump: {{ json . }}
  83. // (especially nice for taking data and piping it to "jq")
  84. // (ie "some-tool inspect --format '{{ json . }}' some-things | jq .")
  85. "json": func(v interface{}) (string, error) {
  86. j, err := json.Marshal(v)
  87. return string(j), err
  88. },
  89. // Everybody: {{- join ", " .Names -}}
  90. // Concat: {{- join "/" "https://github.com" "jsmith" "some-repo" -}}
  91. "join": stringsActionFactory("join", true, strings.Join),
  92. // {{- $mungedUrl := $url | replace "git://" "https://" | trimSuffixes ".git" -}}
  93. // turns: git://github.com/jsmith/some-repo.git
  94. // into: https://github.com/jsmith/some-repo
  95. "trimPrefixes": stringsActionFactory("trimPrefixes", false, stringsModifierActionFactory(strings.TrimPrefix)),
  96. "trimSuffixes": stringsActionFactory("trimSuffixes", false, stringsModifierActionFactory(strings.TrimSuffix)),
  97. "replace": stringsActionFactory("replace", false, func(strs []string, str string) string {
  98. return strings.NewReplacer(strs...).Replace(str)
  99. }),
  100. // {{- getenv "PATH" -}}
  101. // {{- getenv "HOME" "no HOME set" -}}
  102. // {{- getenv "HOME" "is set" "is NOT set (or is empty)" -}}
  103. "getenv": thingsActionFactory("getenv", true, func(args []interface{}, arg interface{}) interface{} {
  104. var (
  105. val = os.Getenv(arg.(string))
  106. setVal interface{} = val
  107. unsetVal interface{} = ""
  108. )
  109. if len(args) == 2 {
  110. setVal, unsetVal = args[0], args[1]
  111. } else if len(args) == 1 {
  112. unsetVal = args[0]
  113. } else if len(args) != 0 {
  114. panic(fmt.Sprintf(`expected between 1 and 3 arguments to "getenv", got %d`, len(args)+1))
  115. }
  116. if val != "" {
  117. return setVal
  118. } else {
  119. return unsetVal
  120. }
  121. }),
  122. }