github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/common/expand.go (about)

     1  /*
     2  Copyright (C) 2022-2023 ApeCloud Co., Ltd
     3  
     4  This file is part of KubeBlocks project
     5  
     6  This program is free software: you can redistribute it and/or modify
     7  it under the terms of the GNU Affero General Public License as published by
     8  the Free Software Foundation, either version 3 of the License, or
     9  (at your option) any later version.
    10  
    11  This program is distributed in the hope that it will be useful
    12  but WITHOUT ANY WARRANTY; without even the implied warranty of
    13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    14  GNU Affero General Public License for more details.
    15  
    16  You should have received a copy of the GNU Affero General Public License
    17  along with this program.  If not, see <http://www.gnu.org/licenses/>.
    18  */
    19  
    20  package common
    21  
    22  import (
    23  	"bytes"
    24  )
    25  
    26  // forked from k8s.io/kubernetes/third_party/forked/golang/expansion
    27  const (
    28  	operator        = '$'
    29  	referenceOpener = '('
    30  	referenceCloser = ')'
    31  )
    32  
    33  // syntaxWrap returns the input string wrapped by the expansion syntax.
    34  func syntaxWrap(input string) string {
    35  	return string(operator) + string(referenceOpener) + input + string(referenceCloser)
    36  }
    37  
    38  // MappingFuncFor returns a mapping function for use with Expand that
    39  // implements the expansion semantics defined in the expansion spec; it
    40  // returns the input string wrapped in the expansion syntax if no mapping
    41  // for the input is found.
    42  func MappingFuncFor(context ...map[string]string) func(string) string {
    43  	return func(input string) string {
    44  		for _, vars := range context {
    45  			val, ok := vars[input]
    46  			if ok {
    47  				return val
    48  			}
    49  		}
    50  
    51  		return syntaxWrap(input)
    52  	}
    53  }
    54  
    55  // Expand replaces variable references in the input string according to
    56  // the expansion spec using the given mapping function to resolve the
    57  // values of variables.
    58  func Expand(input string, mapping func(string) string) string {
    59  	var buf bytes.Buffer
    60  	checkpoint := 0
    61  	for cursor := 0; cursor < len(input); cursor++ {
    62  		if input[cursor] == operator && cursor+1 < len(input) {
    63  			// Copy the portion of the input string since the last
    64  			// checkpoint into the buffer
    65  			buf.WriteString(input[checkpoint:cursor])
    66  
    67  			// Attempt to read the variable name as defined by the
    68  			// syntax from the input string
    69  			read, isVar, advance := tryReadVariableName(input[cursor+1:])
    70  
    71  			if isVar {
    72  				// We were able to read a variable name correctly;
    73  				// apply the mapping to the variable name and copy the
    74  				// bytes into the buffer
    75  				buf.WriteString(mapping(read))
    76  			} else {
    77  				// Not a variable name; copy the read bytes into the buffer
    78  				buf.WriteString(read)
    79  			}
    80  
    81  			// Advance the cursor in the input string to account for
    82  			// bytes consumed to read the variable name expression
    83  			cursor += advance
    84  
    85  			// Advance the checkpoint in the input string
    86  			checkpoint = cursor + 1
    87  		}
    88  	}
    89  
    90  	// Return the buffer and any remaining unwritten bytes in the
    91  	// input string.
    92  	return buf.String() + input[checkpoint:]
    93  }
    94  
    95  // tryReadVariableName attempts to read a variable name from the input
    96  // string and returns the content read from the input, whether that content
    97  // represents a variable name to perform mapping on, and the number of bytes
    98  // consumed in the input string.
    99  //
   100  // The input string is assumed not to contain the initial operator.
   101  func tryReadVariableName(input string) (string, bool, int) {
   102  	switch input[0] {
   103  	case operator:
   104  		// Escaped operator; return it.
   105  		return input[0:1], false, 1
   106  	case referenceOpener:
   107  		// Scan to expression closer
   108  		for i := 1; i < len(input); i++ {
   109  			if input[i] == referenceCloser {
   110  				return input[1:i], true, i + 1
   111  			}
   112  		}
   113  
   114  		// Incomplete reference; return it.
   115  		return string(operator) + string(referenceOpener), false, 1
   116  	default:
   117  		// Not the beginning of an expression, ie, an operator
   118  		// that doesn't begin an expression.  Return the operator
   119  		// and the first rune in the string.
   120  		return (string(operator) + string(input[0])), false, 1
   121  	}
   122  }