expand.go 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. package expansion
  2. import (
  3. "bytes"
  4. )
  5. const (
  6. operator = '$'
  7. referenceOpener = '('
  8. referenceCloser = ')'
  9. )
  10. // syntaxWrap returns the input string wrapped by the expansion syntax.
  11. func syntaxWrap(input string) string {
  12. return string(operator) + string(referenceOpener) + input + string(referenceCloser)
  13. }
  14. // MappingFuncFor returns a mapping function for use with Expand that
  15. // implements the expansion semantics defined in the expansion spec; it
  16. // returns the input string wrapped in the expansion syntax if no mapping
  17. // for the input is found.
  18. func MappingFuncFor(context ...map[string]string) func(string) string {
  19. return func(input string) string {
  20. for _, vars := range context {
  21. val, ok := vars[input]
  22. if ok {
  23. return val
  24. }
  25. }
  26. return syntaxWrap(input)
  27. }
  28. }
  29. // Expand replaces variable references in the input string according to
  30. // the expansion spec using the given mapping function to resolve the
  31. // values of variables.
  32. func Expand(input string, mapping func(string) string) string {
  33. var buf bytes.Buffer
  34. checkpoint := 0
  35. for cursor := 0; cursor < len(input); cursor++ {
  36. if input[cursor] == operator && cursor+1 < len(input) {
  37. // Copy the portion of the input string since the last
  38. // checkpoint into the buffer
  39. buf.WriteString(input[checkpoint:cursor])
  40. // Attempt to read the variable name as defined by the
  41. // syntax from the input string
  42. read, isVar, advance := tryReadVariableName(input[cursor+1:])
  43. if isVar {
  44. // We were able to read a variable name correctly;
  45. // apply the mapping to the variable name and copy the
  46. // bytes into the buffer
  47. buf.WriteString(mapping(read))
  48. } else {
  49. // Not a variable name; copy the read bytes into the buffer
  50. buf.WriteString(read)
  51. }
  52. // Advance the cursor in the input string to account for
  53. // bytes consumed to read the variable name expression
  54. cursor += advance
  55. // Advance the checkpoint in the input string
  56. checkpoint = cursor + 1
  57. }
  58. }
  59. // Return the buffer and any remaining unwritten bytes in the
  60. // input string.
  61. return buf.String() + input[checkpoint:]
  62. }
  63. // tryReadVariableName attempts to read a variable name from the input
  64. // string and returns the content read from the input, whether that content
  65. // represents a variable name to perform mapping on, and the number of bytes
  66. // consumed in the input string.
  67. //
  68. // The input string is assumed not to contain the initial operator.
  69. func tryReadVariableName(input string) (string, bool, int) {
  70. switch input[0] {
  71. case operator:
  72. // Escaped operator; return it.
  73. return input[0:1], false, 1
  74. case referenceOpener:
  75. // Scan to expression closer
  76. for i := 1; i < len(input); i++ {
  77. if input[i] == referenceCloser {
  78. return input[1:i], true, i + 1
  79. }
  80. }
  81. // Incomplete reference; return it.
  82. return string(operator) + string(referenceOpener), false, 1
  83. default:
  84. // Not the beginning of an expression, ie, an operator
  85. // that doesn't begin an expression. Return the operator
  86. // and the first rune in the string.
  87. return (string(operator) + string(input[0])), false, 1
  88. }
  89. }