github.com/enmand/kubernetes@v1.2.0-alpha.0/third_party/golang/expansion/expand.go (about)

     1  package expansion
     2  
     3  import (
     4  	"bytes"
     5  )
     6  
     7  const (
     8  	operator        = '$'
     9  	referenceOpener = '('
    10  	referenceCloser = ')'
    11  )
    12  
    13  // syntaxWrap returns the input string wrapped the expansion syntax.
    14  func syntaxWrap(input string) string {
    15  	return string(operator) + string(referenceOpener) + input + string(referenceCloser)
    16  }
    17  
    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]
    26  			if ok {
    27  				return val
    28  			}
    29  		}
    30  
    31  		return syntaxWrap(input)
    32  	}
    33  }
    34  
    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 {
    39  	var buf bytes.Buffer
    40  	checkpoint := 0
    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])
    46  
    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:])
    50  
    51  			if isVar {
    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))
    56  			} else {
    57  				// Not a variable name; copy the read bytes into the buffer
    58  				buf.WriteString(read)
    59  			}
    60  
    61  			// Advance the cursor in the input string to account for
    62  			// bytes consumed to read the variable name expression
    63  			cursor += advance
    64  
    65  			// Advance the checkpoint in the input string
    66  			checkpoint = cursor + 1
    67  		}
    68  	}
    69  
    70  	// Return the buffer and any remaining unwritten bytes in the
    71  	// input string.
    72  	return buf.String() + input[checkpoint:]
    73  }
    74  
    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.
    79  //
    80  // The input string is assumed not to contain the initial operator.
    81  func tryReadVariableName(input string) (string, bool, int) {
    82  	switch input[0] {
    83  	case operator:
    84  		// Escaped operator; return it.
    85  		return input[0:1], false, 1
    86  	case referenceOpener:
    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
    91  			}
    92  		}
    93  
    94  		// Incomplete reference; return it.
    95  		return string(operator) + string(referenceOpener), false, 1
    96  	default:
    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
   101  	}
   102  }