@ -17,10 +17,13 @@ package strvals
import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"strconv"
"strings"
"unicode"
"github.com/pkg/errors"
"sigs.k8s.io/yaml"
@ -98,6 +101,18 @@ func ParseIntoString(s string, dest map[string]interface{}) error {
return t . parse ( )
}
// ParseJSON parses a string with format key1=val1, key2=val2, ...
// where values are json strings (null, or scalars, or arrays, or objects).
// An empty val is treated as null.
//
// If a key exists in dest, the new value overwrites the dest version.
//
func ParseJSON ( s string , dest map [ string ] interface { } ) error {
scanner := bytes . NewBufferString ( s )
t := newJSONParser ( scanner , dest )
return t . parse ( )
}
// ParseIntoFile parses a filevals line and merges the result into dest.
//
// This method always returns a string as the value.
@ -117,9 +132,10 @@ type RunesValueReader func([]rune) (interface{}, error)
// where sc is the source of the original data being parsed
// where data is the final parsed data from the parses with correct types
type parser struct {
sc * bytes . Buffer
data map [ string ] interface { }
reader RunesValueReader
sc * bytes . Buffer
data map [ string ] interface { }
reader RunesValueReader
isjsonval bool
}
func newParser ( sc * bytes . Buffer , data map [ string ] interface { } , stringBool bool ) * parser {
@ -129,6 +145,10 @@ func newParser(sc *bytes.Buffer, data map[string]interface{}, stringBool bool) *
return & parser { sc : sc , data : data , reader : stringConverter }
}
func newJSONParser ( sc * bytes . Buffer , data map [ string ] interface { } ) * parser {
return & parser { sc : sc , data : data , reader : nil , isjsonval : true }
}
func newFileParser ( sc * bytes . Buffer , data map [ string ] interface { } , reader RunesValueReader ) * parser {
return & parser { sc : sc , data : data , reader : reader }
}
@ -188,6 +208,33 @@ func (t *parser) key(data map[string]interface{}) (reterr error) {
set ( data , kk , list )
return err
case last == '=' :
if t . isjsonval {
empval , err := t . emptyVal ( )
if err != nil {
return err
}
if empval {
set ( data , string ( k ) , nil )
return nil
}
// parse jsonvals by using Go’ s JSON standard library
// Decode is preferred to Unmarshal in order to parse just the json parts of the list key1=jsonval1,key2=jsonval2,...
// Since Decode has its own buffer that consumes more characters (from underlying t.sc) than the ones actually decoded,
// we invoke Decode on a separate reader built with a copy of what is left in t.sc. After Decode is executed, we
// discard in t.sc the chars of the decoded json value (the number of those characters is returned by InputOffset).
var jsonval interface { }
dec := json . NewDecoder ( strings . NewReader ( t . sc . String ( ) ) )
if err = dec . Decode ( & jsonval ) ; err != nil {
return err
}
set ( data , string ( k ) , jsonval )
if _ , err = io . CopyN ( ioutil . Discard , t . sc , dec . InputOffset ( ) ) ; err != nil {
return err
}
// skip possible blanks and comma
_ , err = t . emptyVal ( )
return err
}
//End of key. Consume =, Get value.
// FIXME: Get value list first
vl , e := t . valList ( )
@ -209,7 +256,6 @@ func (t *parser) key(data map[string]interface{}) (reterr error) {
default :
return e
}
case last == ',' :
// No value given. Set the value to empty string. Return error.
set ( data , string ( k ) , "" )
@ -287,6 +333,34 @@ func (t *parser) listItem(list []interface{}, i int) ([]interface{}, error) {
case err != nil :
return list , err
case last == '=' :
if t . isjsonval {
empval , err := t . emptyVal ( )
if err != nil {
return list , err
}
if empval {
return setIndex ( list , i , nil )
}
// parse jsonvals by using Go’ s JSON standard library
// Decode is preferred to Unmarshal in order to parse just the json parts of the list key1=jsonval1,key2=jsonval2,...
// Since Decode has its own buffer that consumes more characters (from underlying t.sc) than the ones actually decoded,
// we invoke Decode on a separate reader built with a copy of what is left in t.sc. After Decode is executed, we
// discard in t.sc the chars of the decoded json value (the number of those characters is returned by InputOffset).
var jsonval interface { }
dec := json . NewDecoder ( strings . NewReader ( t . sc . String ( ) ) )
if err = dec . Decode ( & jsonval ) ; err != nil {
return list , err
}
if list , err = setIndex ( list , i , jsonval ) ; err != nil {
return list , err
}
if _ , err = io . CopyN ( ioutil . Discard , t . sc , dec . InputOffset ( ) ) ; err != nil {
return list , err
}
// skip possible blanks and comma
_ , err = t . emptyVal ( )
return list , err
}
vl , e := t . valList ( )
switch e {
case nil :
@ -350,6 +424,28 @@ func (t *parser) listItem(list []interface{}, i int) ([]interface{}, error) {
}
}
// check for an empty value
// read and consume optional spaces until comma or EOF (empty val) or any other char (not empty val)
// comma and spaces are consumed, while any other char is not cosumed
func ( t * parser ) emptyVal ( ) ( bool , error ) {
for {
r , _ , e := t . sc . ReadRune ( )
if e == io . EOF {
return true , nil
}
if e != nil {
return false , e
}
if r == ',' {
return true , nil
}
if ! unicode . IsSpace ( r ) {
t . sc . UnreadRune ( )
return false , nil
}
}
}
func ( t * parser ) val ( ) ( [ ] rune , error ) {
stop := runeSet ( [ ] rune { ',' } )
v , _ , err := runesUntil ( t . sc , stop )