123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103 |
- package expansion
- import (
- "bytes"
- )
- const (
- operator = '$'
- referenceOpener = '('
- referenceCloser = ')'
- )
- // syntaxWrap returns the input string wrapped by the expansion syntax.
- func syntaxWrap(input string) string {
- return string(operator) + string(referenceOpener) + input + string(referenceCloser)
- }
- // MappingFuncFor returns a mapping function for use with Expand that
- // implements the expansion semantics defined in the expansion spec; it
- // returns the input string wrapped in the expansion syntax if no mapping
- // for the input is found.
- func MappingFuncFor(context ...map[string]string) func(string) string {
- return func(input string) string {
- for _, vars := range context {
- val, ok := vars[input]
- if ok {
- return val
- }
- }
- return syntaxWrap(input)
- }
- }
- // Expand replaces variable references in the input string according to
- // the expansion spec using the given mapping function to resolve the
- // values of variables.
- func Expand(input string, mapping func(string) string) string {
- var buf bytes.Buffer
- checkpoint := 0
- for cursor := 0; cursor < len(input); cursor++ {
- if input[cursor] == operator && cursor+1 < len(input) {
- // Copy the portion of the input string since the last
- // checkpoint into the buffer
- buf.WriteString(input[checkpoint:cursor])
- // Attempt to read the variable name as defined by the
- // syntax from the input string
- read, isVar, advance := tryReadVariableName(input[cursor+1:])
- if isVar {
- // We were able to read a variable name correctly;
- // apply the mapping to the variable name and copy the
- // bytes into the buffer
- buf.WriteString(mapping(read))
- } else {
- // Not a variable name; copy the read bytes into the buffer
- buf.WriteString(read)
- }
- // Advance the cursor in the input string to account for
- // bytes consumed to read the variable name expression
- cursor += advance
- // Advance the checkpoint in the input string
- checkpoint = cursor + 1
- }
- }
- // Return the buffer and any remaining unwritten bytes in the
- // input string.
- return buf.String() + input[checkpoint:]
- }
- // tryReadVariableName attempts to read a variable name from the input
- // string and returns the content read from the input, whether that content
- // represents a variable name to perform mapping on, and the number of bytes
- // consumed in the input string.
- //
- // The input string is assumed not to contain the initial operator.
- func tryReadVariableName(input string) (string, bool, int) {
- switch input[0] {
- case operator:
- // Escaped operator; return it.
- return input[0:1], false, 1
- case referenceOpener:
- // Scan to expression closer
- for i := 1; i < len(input); i++ {
- if input[i] == referenceCloser {
- return input[1:i], true, i + 1
- }
- }
- // Incomplete reference; return it.
- return string(operator) + string(referenceOpener), false, 1
- default:
- // Not the beginning of an expression, ie, an operator
- // that doesn't begin an expression. Return the operator
- // and the first rune in the string.
- return (string(operator) + string(input[0])), false, 1
- }
- }
|