template.go 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. package template
  2. import (
  3. "fmt"
  4. "regexp"
  5. "strings"
  6. )
  7. var delimiter = "\\$"
  8. var substitution = "[_a-z][_a-z0-9]*(?::?-[^}]+)?"
  9. var patternString = fmt.Sprintf(
  10. "%s(?i:(?P<escaped>%s)|(?P<named>%s)|{(?P<braced>%s)}|(?P<invalid>))",
  11. delimiter, delimiter, substitution, substitution,
  12. )
  13. var pattern = regexp.MustCompile(patternString)
  14. // InvalidTemplateError is returned when a variable template is not in a valid
  15. // format
  16. type InvalidTemplateError struct {
  17. Template string
  18. }
  19. func (e InvalidTemplateError) Error() string {
  20. return fmt.Sprintf("Invalid template: %#v", e.Template)
  21. }
  22. // Mapping is a user-supplied function which maps from variable names to values.
  23. // Returns the value as a string and a bool indicating whether
  24. // the value is present, to distinguish between an empty string
  25. // and the absence of a value.
  26. type Mapping func(string) (string, bool)
  27. // Substitute variables in the string with their values
  28. func Substitute(template string, mapping Mapping) (result string, err *InvalidTemplateError) {
  29. result = pattern.ReplaceAllStringFunc(template, func(substring string) string {
  30. matches := pattern.FindStringSubmatch(substring)
  31. groups := make(map[string]string)
  32. for i, name := range pattern.SubexpNames() {
  33. if i != 0 {
  34. groups[name] = matches[i]
  35. }
  36. }
  37. substitution := groups["named"]
  38. if substitution == "" {
  39. substitution = groups["braced"]
  40. }
  41. if substitution != "" {
  42. // Soft default (fall back if unset or empty)
  43. if strings.Contains(substitution, ":-") {
  44. name, defaultValue := partition(substitution, ":-")
  45. value, ok := mapping(name)
  46. if !ok || value == "" {
  47. return defaultValue
  48. }
  49. return value
  50. }
  51. // Hard default (fall back if-and-only-if empty)
  52. if strings.Contains(substitution, "-") {
  53. name, defaultValue := partition(substitution, "-")
  54. value, ok := mapping(name)
  55. if !ok {
  56. return defaultValue
  57. }
  58. return value
  59. }
  60. // No default (fall back to empty string)
  61. value, ok := mapping(substitution)
  62. if !ok {
  63. return ""
  64. }
  65. return value
  66. }
  67. if escaped := groups["escaped"]; escaped != "" {
  68. return escaped
  69. }
  70. err = &InvalidTemplateError{Template: template}
  71. return ""
  72. })
  73. return result, err
  74. }
  75. // Split the string at the first occurrence of sep, and return the part before the separator,
  76. // and the part after the separator.
  77. //
  78. // If the separator is not found, return the string itself, followed by an empty string.
  79. func partition(s, sep string) (string, string) {
  80. if strings.Contains(s, sep) {
  81. parts := strings.SplitN(s, sep, 2)
  82. return parts[0], parts[1]
  83. }
  84. return s, ""
  85. }