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  }