github.com/zntrio/harp/v2@v2.0.9/pkg/sdk/flags/strvals/parser.go (about) 1 // Licensed to Elasticsearch B.V. under one or more contributor 2 // license agreements. See the NOTICE file distributed with 3 // this work for additional information regarding copyright 4 // ownership. Elasticsearch B.V. licenses this file to you under 5 // the Apache License, Version 2.0 (the "License"); you may 6 // not use this file except in compliance with the License. 7 // You may obtain a copy of the License at 8 // 9 // http://www.apache.org/licenses/LICENSE-2.0 10 // 11 // Unless required by applicable law or agreed to in writing, 12 // software distributed under the License is distributed on an 13 // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 // KIND, either express or implied. See the License for the 15 // specific language governing permissions and limitations 16 // under the License. 17 18 package strvals 19 20 import ( 21 "bytes" 22 "errors" 23 "fmt" 24 "io" 25 "strconv" 26 "strings" 27 28 "sigs.k8s.io/yaml" 29 ) 30 31 // ErrNotList indicates that a non-list was treated as a list. 32 var ErrNotList = errors.New("not a list") 33 34 // ToYAML takes a string of arguments and converts to a YAML document. 35 func ToYAML(s string) (string, error) { 36 m, err := Parse(s) 37 if err != nil { 38 return "", fmt.Errorf("unable to parse input as YAML: %w", err) 39 } 40 d, err := yaml.Marshal(m) 41 if err != nil { 42 return "", fmt.Errorf("unable to marshal content as YAML: %w", err) 43 } 44 45 return strings.TrimSuffix(string(d), "\n"), nil 46 } 47 48 // Parse parses a set line. 49 // 50 // A set line is of the form name1=value1,name2=value2. 51 func Parse(s string) (map[string]interface{}, error) { 52 vals := map[string]interface{}{} 53 scanner := bytes.NewBufferString(s) 54 t := newParser(scanner, vals, false) 55 err := t.parse() 56 return vals, err 57 } 58 59 // ParseString parses a set line and forces a string value. 60 // 61 // A set line is of the form name1=value1,name2=value2. 62 func ParseString(s string) (map[string]interface{}, error) { 63 vals := map[string]interface{}{} 64 scanner := bytes.NewBufferString(s) 65 t := newParser(scanner, vals, true) 66 err := t.parse() 67 return vals, err 68 } 69 70 // ParseInto parses a strvals line and merges the result into dest. 71 // 72 // If the strval string has a key that exists in dest, it overwrites the 73 // dest version. 74 func ParseInto(s string, dest map[string]interface{}) error { 75 scanner := bytes.NewBufferString(s) 76 t := newParser(scanner, dest, false) 77 return t.parse() 78 } 79 80 // ParseFile parses a set line, but its final value is loaded from the file at the path specified by the original value. 81 // 82 // A set line is of the form name1=path1,name2=path2 83 // 84 // When the files at path1 and path2 contained "val1" and "val2" respectively, the set line is consumed as 85 // name1=val1,name2=val2. 86 func ParseFile(s string, reader RunesValueReader) (map[string]interface{}, error) { 87 vals := map[string]interface{}{} 88 scanner := bytes.NewBufferString(s) 89 t := newFileParser(scanner, vals, reader) 90 err := t.parse() 91 return vals, err 92 } 93 94 // ParseIntoString parses a strvals line and merges the result into dest. 95 // 96 // This method always returns a string as the value. 97 func ParseIntoString(s string, dest map[string]interface{}) error { 98 scanner := bytes.NewBufferString(s) 99 t := newParser(scanner, dest, true) 100 return t.parse() 101 } 102 103 // ParseIntoFile parses a filevals line and merges the result into dest. 104 // 105 // This method always returns a string as the value. 106 func ParseIntoFile(s string, dest map[string]interface{}, reader RunesValueReader) error { 107 scanner := bytes.NewBufferString(s) 108 t := newFileParser(scanner, dest, reader) 109 return t.parse() 110 } 111 112 // RunesValueReader is a function that takes the given value (a slice of runes) 113 // and returns the parsed value. 114 type RunesValueReader func([]rune) (interface{}, error) 115 116 // parser is a simple parser that takes a strvals line and parses it into a 117 // map representation. 118 // 119 // where sc is the source of the original data being parsed 120 // where data is the final parsed data from the parses with correct types. 121 type parser struct { 122 sc *bytes.Buffer 123 data map[string]interface{} 124 reader RunesValueReader 125 } 126 127 func newParser(sc *bytes.Buffer, data map[string]interface{}, stringBool bool) *parser { 128 stringConverter := func(rs []rune) (interface{}, error) { 129 return typedVal(rs, stringBool), nil 130 } 131 return &parser{sc: sc, data: data, reader: stringConverter} 132 } 133 134 func newFileParser(sc *bytes.Buffer, data map[string]interface{}, reader RunesValueReader) *parser { 135 return &parser{sc: sc, data: data, reader: reader} 136 } 137 138 func (t *parser) parse() error { 139 for { 140 err := t.key(t.data) 141 if err == nil { 142 continue 143 } 144 if errors.Is(err, io.EOF) { 145 return nil 146 } 147 return err 148 } 149 } 150 151 func runeSet(r []rune) map[rune]bool { 152 s := make(map[rune]bool, len(r)) 153 for _, rr := range r { 154 s[rr] = true 155 } 156 return s 157 } 158 159 // nolint // imported code 160 func (t *parser) key(data map[string]interface{}) error { 161 stop := runeSet([]rune{'=', '[', ',', '.'}) 162 for { 163 switch k, last, err := runesUntil(t.sc, stop); { 164 case err != nil: 165 if len(k) == 0 { 166 return err 167 } 168 return fmt.Errorf("key %q has no value", string(k)) 169 case last == '[': 170 // We are in a list index context, so we need to set an index. 171 i, err := t.keyIndex() 172 if err != nil { 173 return fmt.Errorf("error parsing index: %w", err) 174 } 175 kk := string(k) 176 // Find or create target list 177 list := []interface{}{} 178 if _, ok := data[kk]; ok { 179 list = data[kk].([]interface{}) 180 } 181 182 // Now we need to get the value after the ]. 183 list, err = t.listItem(list, i) 184 set(data, kk, list) 185 return err 186 case last == '=': 187 vl, e := t.valList() 188 switch e { 189 case nil: 190 set(data, string(k), vl) 191 return nil 192 case io.EOF: 193 set(data, string(k), "") 194 return e 195 case ErrNotList: 196 rs, e := t.val() 197 if e != nil && e != io.EOF { 198 return e 199 } 200 v, e := t.reader(rs) 201 set(data, string(k), v) 202 return e 203 default: 204 return e 205 } 206 207 case last == ',': 208 // No value given. Set the value to empty string. Return error. 209 set(data, string(k), "") 210 return fmt.Errorf("key %q has no value (cannot end with ,)", string(k)) 211 case last == '.': 212 // First, create or find the target map. 213 inner := map[string]interface{}{} 214 if _, ok := data[string(k)]; ok { 215 inner = data[string(k)].(map[string]interface{}) 216 } 217 218 // Recurse 219 e := t.key(inner) 220 if len(inner) == 0 { 221 return fmt.Errorf("key map %q has no value", string(k)) 222 } 223 set(data, string(k), inner) 224 return e 225 } 226 } 227 } 228 229 func set(data map[string]interface{}, key string, val interface{}) { 230 // If key is empty, don't set it. 231 if key == "" { 232 return 233 } 234 data[key] = val 235 } 236 237 func setIndex(list []interface{}, index int, val interface{}) []interface{} { 238 if len(list) <= index { 239 newlist := make([]interface{}, index+1) 240 copy(newlist, list) 241 list = newlist 242 } 243 list[index] = val 244 return list 245 } 246 247 func (t *parser) keyIndex() (int, error) { 248 // First, get the key. 249 stop := runeSet([]rune{']'}) 250 v, _, err := runesUntil(t.sc, stop) 251 if err != nil { 252 return 0, err 253 } 254 // v should be the index 255 return strconv.Atoi(string(v)) 256 } 257 258 // nolint // imported code 259 func (t *parser) listItem(list []interface{}, i int) ([]interface{}, error) { 260 stop := runeSet([]rune{'[', '.', '='}) 261 switch k, last, err := runesUntil(t.sc, stop); { 262 case len(k) > 0: 263 return list, fmt.Errorf("unexpected data at end of array index: %q", k) 264 case err != nil: 265 return list, err 266 case last == '=': 267 vl, e := t.valList() 268 switch e { 269 case nil: 270 return setIndex(list, i, vl), nil 271 case io.EOF: 272 return setIndex(list, i, ""), err 273 case ErrNotList: 274 rs, e := t.val() 275 if e != nil && e != io.EOF { 276 return list, e 277 } 278 v, e := t.reader(rs) 279 return setIndex(list, i, v), e 280 default: 281 return list, e 282 } 283 case last == '[': 284 // now we have a nested list. Read the index and handle. 285 i, err := t.keyIndex() 286 if err != nil { 287 return list, fmt.Errorf("error parsing index: %w", err) 288 } 289 // Now we need to get the value after the ]. 290 list2, err := t.listItem(list, i) 291 return setIndex(list, i, list2), err 292 case last == '.': 293 // We have a nested object. Send to t.key 294 inner := map[string]interface{}{} 295 if len(list) > i { 296 var ok bool 297 inner, ok = list[i].(map[string]interface{}) 298 if !ok { 299 // We have indices out of order. Initialize empty value. 300 list[i] = map[string]interface{}{} 301 inner = list[i].(map[string]interface{}) 302 } 303 } 304 305 // Recurse 306 e := t.key(inner) 307 return setIndex(list, i, inner), e 308 default: 309 return nil, fmt.Errorf("parse error: unexpected token %v", last) 310 } 311 } 312 313 func (t *parser) val() ([]rune, error) { 314 stop := runeSet([]rune{','}) 315 v, _, err := runesUntil(t.sc, stop) 316 return v, err 317 } 318 319 // nolint // imported code 320 func (t *parser) valList() ([]interface{}, error) { 321 r, _, e := t.sc.ReadRune() 322 if e != nil { 323 return []interface{}{}, e 324 } 325 326 if r != '{' { 327 t.sc.UnreadRune() 328 return []interface{}{}, ErrNotList 329 } 330 331 list := []interface{}{} 332 stop := runeSet([]rune{',', '}'}) 333 for { 334 switch rs, last, err := runesUntil(t.sc, stop); { 335 case err != nil: 336 if errors.Is(err, io.EOF) { 337 err = errors.New("list must terminate with '}'") 338 } 339 return list, err 340 case last == '}': 341 // If this is followed by ',', consume it. 342 if r, _, e := t.sc.ReadRune(); e == nil && r != ',' { 343 t.sc.UnreadRune() 344 } 345 v, e := t.reader(rs) 346 list = append(list, v) 347 return list, e 348 case last == ',': 349 v, e := t.reader(rs) 350 if e != nil { 351 return list, e 352 } 353 list = append(list, v) 354 } 355 } 356 } 357 358 func runesUntil(in io.RuneReader, stop map[rune]bool) (runeset []rune, last rune, err error) { 359 v := []rune{} 360 for { 361 switch r, _, e := in.ReadRune(); { 362 case e != nil: 363 return v, r, e 364 case inMap(r, stop): 365 return v, r, nil 366 case r == '\\': 367 next, _, e := in.ReadRune() 368 if e != nil { 369 return v, next, e 370 } 371 v = append(v, next) 372 default: 373 v = append(v, r) 374 } 375 } 376 } 377 378 func inMap(k rune, m map[rune]bool) bool { 379 _, ok := m[k] 380 return ok 381 } 382 383 func typedVal(v []rune, st bool) interface{} { 384 val := string(v) 385 386 if st { 387 return val 388 } 389 390 if strings.EqualFold(val, "true") { 391 return true 392 } 393 394 if strings.EqualFold(val, "false") { 395 return false 396 } 397 398 if strings.EqualFold(val, "null") { 399 return nil 400 } 401 402 if strings.EqualFold(val, "0") { 403 return int64(0) 404 } 405 406 // If this value does not start with zero, try parsing it to an int 407 if val != "" && val[0] != '0' { 408 if iv, err := strconv.ParseInt(val, 10, 64); err == nil { 409 return iv 410 } 411 } 412 413 return val 414 }