istio.io/istio@v0.0.0-20240520182934-d79c90f27776/operator/pkg/util/path.go (about) 1 // Copyright Istio Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package util 16 17 import ( 18 "fmt" 19 "path/filepath" 20 "regexp" 21 "strconv" 22 "strings" 23 ) 24 25 const ( 26 // PathSeparator is the separator between path elements. 27 PathSeparator = "." 28 kvSeparatorRune = ':' 29 30 // InsertIndex is the index that means "insert" when setting values 31 InsertIndex = -1 32 33 // PathSeparatorRune is the separator between path elements, as a rune. 34 pathSeparatorRune = '.' 35 // EscapedPathSeparator is what to use when the path shouldn't separate 36 EscapedPathSeparator = "\\" + PathSeparator 37 ) 38 39 // ValidKeyRegex is a regex for a valid path key element. 40 var ValidKeyRegex = regexp.MustCompile("^[a-zA-Z0-9_-]*$") 41 42 // Path is a path in slice form. 43 type Path []string 44 45 // PathFromString converts a string path of form a.b.c to a string slice representation. 46 func PathFromString(path string) Path { 47 path = filepath.Clean(path) 48 path = strings.TrimPrefix(path, PathSeparator) 49 path = strings.TrimSuffix(path, PathSeparator) 50 pv := splitEscaped(path, pathSeparatorRune) 51 var r []string 52 for _, str := range pv { 53 if str != "" { 54 str = strings.ReplaceAll(str, EscapedPathSeparator, PathSeparator) 55 // Is str of the form node[expr], convert to "node", "[expr]"? 56 nBracket := strings.IndexRune(str, '[') 57 if nBracket > 0 { 58 r = append(r, str[:nBracket], str[nBracket:]) 59 } else { 60 // str is "[expr]" or "node" 61 r = append(r, str) 62 } 63 } 64 } 65 return r 66 } 67 68 // String converts a string slice path representation of form ["a", "b", "c"] to a string representation like "a.b.c". 69 func (p Path) String() string { 70 return strings.Join(p, PathSeparator) 71 } 72 73 func (p Path) Equals(p2 Path) bool { 74 if len(p) != len(p2) { 75 return false 76 } 77 for i, pp := range p { 78 if pp != p2[i] { 79 return false 80 } 81 } 82 return true 83 } 84 85 // ToYAMLPath converts a path string to path such that the first letter of each path element is lower case. 86 func ToYAMLPath(path string) Path { 87 p := PathFromString(path) 88 for i := range p { 89 p[i] = firstCharToLowerCase(p[i]) 90 } 91 return p 92 } 93 94 // ToYAMLPathString converts a path string such that the first letter of each path element is lower case. 95 func ToYAMLPathString(path string) string { 96 return ToYAMLPath(path).String() 97 } 98 99 // IsValidPathElement reports whether pe is a valid path element. 100 func IsValidPathElement(pe string) bool { 101 return ValidKeyRegex.MatchString(pe) 102 } 103 104 // IsKVPathElement report whether pe is a key/value path element. 105 func IsKVPathElement(pe string) bool { 106 pe, ok := RemoveBrackets(pe) 107 if !ok { 108 return false 109 } 110 111 kv := splitEscaped(pe, kvSeparatorRune) 112 if len(kv) != 2 || len(kv[0]) == 0 || len(kv[1]) == 0 { 113 return false 114 } 115 return IsValidPathElement(kv[0]) 116 } 117 118 // IsVPathElement report whether pe is a value path element. 119 func IsVPathElement(pe string) bool { 120 pe, ok := RemoveBrackets(pe) 121 if !ok { 122 return false 123 } 124 125 return len(pe) > 1 && pe[0] == ':' 126 } 127 128 // IsNPathElement report whether pe is an index path element. 129 func IsNPathElement(pe string) bool { 130 pe, ok := RemoveBrackets(pe) 131 if !ok { 132 return false 133 } 134 135 n, err := strconv.Atoi(pe) 136 return err == nil && n >= InsertIndex 137 } 138 139 // PathKV returns the key and value string parts of the entire key/value path element. 140 // It returns an error if pe is not a key/value path element. 141 func PathKV(pe string) (k, v string, err error) { 142 if !IsKVPathElement(pe) { 143 return "", "", fmt.Errorf("%s is not a valid key:value path element", pe) 144 } 145 pe, _ = RemoveBrackets(pe) 146 kv := splitEscaped(pe, kvSeparatorRune) 147 return kv[0], kv[1], nil 148 } 149 150 // PathV returns the value string part of the entire value path element. 151 // It returns an error if pe is not a value path element. 152 func PathV(pe string) (string, error) { 153 // For :val, return the value only 154 if IsVPathElement(pe) { 155 v, _ := RemoveBrackets(pe) 156 return v[1:], nil 157 } 158 159 // For key:val, return the whole thing 160 v, _ := RemoveBrackets(pe) 161 if len(v) > 0 { 162 return v, nil 163 } 164 return "", fmt.Errorf("%s is not a valid value path element", pe) 165 } 166 167 // PathN returns the index part of the entire value path element. 168 // It returns an error if pe is not an index path element. 169 func PathN(pe string) (int, error) { 170 if !IsNPathElement(pe) { 171 return -1, fmt.Errorf("%s is not a valid index path element", pe) 172 } 173 v, _ := RemoveBrackets(pe) 174 return strconv.Atoi(v) 175 } 176 177 // RemoveBrackets removes the [] around pe and returns the resulting string. It returns false if pe is not surrounded 178 // by []. 179 func RemoveBrackets(pe string) (string, bool) { 180 if !strings.HasPrefix(pe, "[") || !strings.HasSuffix(pe, "]") { 181 return "", false 182 } 183 return pe[1 : len(pe)-1], true 184 } 185 186 // splitEscaped splits a string using the rune r as a separator. It does not split on r if it's prefixed by \. 187 func splitEscaped(s string, r rune) []string { 188 var prev rune 189 if len(s) == 0 { 190 return []string{} 191 } 192 prevIdx := 0 193 var out []string 194 for i, c := range s { 195 if c == r && (i == 0 || (i > 0 && prev != '\\')) { 196 out = append(out, s[prevIdx:i]) 197 prevIdx = i + 1 198 } 199 prev = c 200 } 201 out = append(out, s[prevIdx:]) 202 return out 203 } 204 205 func firstCharToLowerCase(s string) string { 206 return strings.ToLower(s[0:1]) + s[1:] 207 }