13
// syntaxWrap returns the input string wrapped by the expansion syntax.
14
func syntaxWrap(input string) string {
15
return string(operator) + string(referenceOpener) + input + string(referenceCloser)
18
// MappingFuncFor returns a mapping function for use with Expand that
19
// implements the expansion semantics defined in the expansion spec; it
20
// returns the input string wrapped in the expansion syntax if no mapping
21
// for the input is found.
22
func MappingFuncFor(context ...map[string]string) func(string) string {
23
return func(input string) string {
24
for _, vars := range context {
25
val, ok := vars[input]
31
return syntaxWrap(input)
35
// Expand replaces variable references in the input string according to
36
// the expansion spec using the given mapping function to resolve the
37
// values of variables.
38
func Expand(input string, mapping func(string) string) string {
41
for cursor := 0; cursor < len(input); cursor++ {
42
if input[cursor] == operator && cursor+1 < len(input) {
43
// Copy the portion of the input string since the last
44
// checkpoint into the buffer
45
buf.WriteString(input[checkpoint:cursor])
47
// Attempt to read the variable name as defined by the
48
// syntax from the input string
49
read, isVar, advance := tryReadVariableName(input[cursor+1:])
52
// We were able to read a variable name correctly;
53
// apply the mapping to the variable name and copy the
54
// bytes into the buffer
55
buf.WriteString(mapping(read))
57
// Not a variable name; copy the read bytes into the buffer
61
// Advance the cursor in the input string to account for
62
// bytes consumed to read the variable name expression
65
// Advance the checkpoint in the input string
66
checkpoint = cursor + 1
70
// Return the buffer and any remaining unwritten bytes in the
72
return buf.String() + input[checkpoint:]
75
// tryReadVariableName attempts to read a variable name from the input
76
// string and returns the content read from the input, whether that content
77
// represents a variable name to perform mapping on, and the number of bytes
78
// consumed in the input string.
80
// The input string is assumed not to contain the initial operator.
81
func tryReadVariableName(input string) (string, bool, int) {
84
// Escaped operator; return it.
85
return input[0:1], false, 1
87
// Scan to expression closer
88
for i := 1; i < len(input); i++ {
89
if input[i] == referenceCloser {
90
return input[1:i], true, i + 1
94
// Incomplete reference; return it.
95
return string(operator) + string(referenceOpener), false, 1
97
// Not the beginning of an expression, ie, an operator
98
// that doesn't begin an expression. Return the operator
99
// and the first rune in the string.
100
return (string(operator) + string(input[0])), false, 1