...

Source file src/sigs.k8s.io/kustomize/api/filters/refvar/expand.go

Documentation: sigs.k8s.io/kustomize/api/filters/refvar

     1  // Copyright 2019 The Kubernetes Authors.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package refvar
     5  
     6  import (
     7  	"fmt"
     8  	"log"
     9  	"strings"
    10  )
    11  
    12  const (
    13  	operator        = '$'
    14  	referenceOpener = '('
    15  	referenceCloser = ')'
    16  )
    17  
    18  // syntaxWrap returns the input string wrapped by the expansion syntax.
    19  func syntaxWrap(input string) string {
    20  	var sb strings.Builder
    21  	sb.WriteByte(operator)
    22  	sb.WriteByte(referenceOpener)
    23  	sb.WriteString(input)
    24  	sb.WriteByte(referenceCloser)
    25  	return sb.String()
    26  }
    27  
    28  // MappingFunc maps a string to anything.
    29  type MappingFunc func(string) interface{}
    30  
    31  // MakePrimitiveReplacer returns a MappingFunc that uses a map to do
    32  // replacements, and a histogram to count map hits.
    33  //
    34  // Func behavior:
    35  //
    36  // If the input key is NOT found in the map, the key is wrapped up as
    37  // as a variable declaration string and returned, e.g. key FOO becomes $(FOO).
    38  // This string is presumably put back where it was found, and might get replaced
    39  // later.
    40  //
    41  // If the key is found in the map, the value is returned if it is a primitive
    42  // type (string, bool, number), and the hit is counted.
    43  //
    44  // If it's not a primitive type (e.g. a map, struct, func, etc.) then this
    45  // function doesn't know what to do with it and it returns the key wrapped up
    46  // again as if it had not been replaced.  This should probably be an error.
    47  func MakePrimitiveReplacer(
    48  	counts map[string]int, someMap map[string]interface{}) MappingFunc {
    49  	return func(key string) interface{} {
    50  		if value, ok := someMap[key]; ok {
    51  			switch typedV := value.(type) {
    52  			case string, int, int32, int64, float32, float64, bool:
    53  				counts[key]++
    54  				return typedV
    55  			default:
    56  				// If the value is some complicated type (e.g. a map or struct),
    57  				// this function doesn't know how to jam it into a string,
    58  				// so just pretend it was a cache miss.
    59  				// Likely this should be an error instead of a silent failure,
    60  				// since the programmer passed an impossible value.
    61  				log.Printf(
    62  					"MakePrimitiveReplacer: bad replacement type=%T val=%v",
    63  					typedV, typedV)
    64  				return syntaxWrap(key)
    65  			}
    66  		}
    67  		// If unable to return the mapped variable, return it
    68  		// as it was found, and a later mapping might be able to
    69  		// replace it.
    70  		return syntaxWrap(key)
    71  	}
    72  }
    73  
    74  // DoReplacements replaces variable references in the input string
    75  // using the mapping function.
    76  func DoReplacements(input string, mapping MappingFunc) interface{} {
    77  	var buf strings.Builder
    78  	checkpoint := 0
    79  	for cursor := 0; cursor < len(input); cursor++ {
    80  		if input[cursor] == operator && cursor+1 < len(input) {
    81  			// Copy the portion of the input string since the last
    82  			// checkpoint into the buffer
    83  			buf.WriteString(input[checkpoint:cursor])
    84  
    85  			// Attempt to read the variable name as defined by the
    86  			// syntax from the input string
    87  			read, isVar, advance := tryReadVariableName(input[cursor+1:])
    88  
    89  			if isVar {
    90  				// We were able to read a variable name correctly;
    91  				// apply the mapping to the variable name and copy the
    92  				// bytes into the buffer
    93  				mapped := mapping(read)
    94  				if input == syntaxWrap(read) {
    95  					// Preserve the type of variable
    96  					return mapped
    97  				}
    98  
    99  				// Variable is used in a middle of a string
   100  				buf.WriteString(fmt.Sprintf("%v", mapped))
   101  			} else {
   102  				// Not a variable name; copy the read bytes into the buffer
   103  				buf.WriteString(read)
   104  			}
   105  
   106  			// Advance the cursor in the input string to account for
   107  			// bytes consumed to read the variable name expression
   108  			cursor += advance
   109  
   110  			// Advance the checkpoint in the input string
   111  			checkpoint = cursor + 1
   112  		}
   113  	}
   114  
   115  	// Return the buffer and any remaining unwritten bytes in the
   116  	// input string.
   117  	return buf.String() + input[checkpoint:]
   118  }
   119  
   120  // tryReadVariableName attempts to read a variable name from the input
   121  // string and returns the content read from the input, whether that content
   122  // represents a variable name to perform mapping on, and the number of bytes
   123  // consumed in the input string.
   124  //
   125  // The input string is assumed not to contain the initial operator.
   126  func tryReadVariableName(input string) (string, bool, int) {
   127  	switch input[0] {
   128  	case operator:
   129  		// Escaped operator; return it.
   130  		return input[0:1], false, 1
   131  	case referenceOpener:
   132  		// Scan to expression closer
   133  		for i := 1; i < len(input); i++ {
   134  			if input[i] == referenceCloser {
   135  				return input[1:i], true, i + 1
   136  			}
   137  		}
   138  
   139  		// Incomplete reference; return it.
   140  		return string(operator) + string(referenceOpener), false, 1
   141  	default:
   142  		// Not the beginning of an expression, ie, an operator
   143  		// that doesn't begin an expression.  Return the operator
   144  		// and the first rune in the string.
   145  		return string(operator) + string(input[0]), false, 1
   146  	}
   147  }
   148  

View as plain text