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 }