Skip to content

Commit

Permalink
Make sure quoted keys with dots work well (#333)
Browse files Browse the repository at this point in the history
Using strings.Join(key, ".") is problematic as these:

  "foo.bar" = 42
  foo.bar   = 42

Will have the same string value, but are not identical!

Always use maybeQuotedAll() in String(); I don't know when you would
ever *not* want to use quoting.
  • Loading branch information
arp242 committed Nov 25, 2021
1 parent ff0a3f8 commit 7d0236f
Show file tree
Hide file tree
Showing 6 changed files with 156 additions and 156 deletions.
2 changes: 1 addition & 1 deletion decode.go
Expand Up @@ -320,13 +320,13 @@ func (md *MetaData) unifyMap(mapping interface{}, rv reflect.Value) error {
md.decoded[md.context.add(k).String()] = struct{}{}
md.context = append(md.context, k)

rvkey := indirect(reflect.New(rv.Type().Key()))
rvval := reflect.Indirect(reflect.New(rv.Type().Elem()))
if err := md.unify(v, rvval); err != nil {
return err
}
md.context = md.context[0 : len(md.context)-1]

rvkey := indirect(reflect.New(rv.Type().Key()))
rvkey.SetString(k)
rv.SetMapIndex(rvkey, rvval)
}
Expand Down
8 changes: 3 additions & 5 deletions decode_test.go
Expand Up @@ -720,25 +720,23 @@ func TestDecodeDatetime(t *testing.T) {
}

func TestMetaDotConflict(t *testing.T) {
// See comment in the metaTest map in toml_test.go
t.Skip()

var m map[string]interface{}
meta, err := Decode(`
"a.b" = "str"
a.b = 1
"" = 2
`, &m)
if err != nil {
t.Fatal(err)
}

want := "a.b=String; a|b=Integer"
want := `"a.b"=String; a.b=Integer; ""=Integer`
have := ""
for i, k := range meta.Keys() {
if i > 0 {
have += "; "
}
have += strings.Join(k, "|") + "=" + meta.Type(k...)
have += k.String() + "=" + meta.Type(k...)
}
if have != want {
t.Errorf("\nhave: %s\nwant: %s", have, want)
Expand Down
4 changes: 2 additions & 2 deletions encode.go
Expand Up @@ -299,7 +299,7 @@ func (enc *Encoder) eArrayOfTables(key Key, rv reflect.Value) {
continue
}
enc.newline()
enc.wf("%s[[%s]]", enc.indentStr(key), key.maybeQuotedAll())
enc.wf("%s[[%s]]", enc.indentStr(key), key)
enc.newline()
enc.eMapOrStruct(key, trv, false)
}
Expand All @@ -312,7 +312,7 @@ func (enc *Encoder) eTable(key Key, rv reflect.Value) {
enc.newline()
}
if len(key) > 0 {
enc.wf("%s[%s]", enc.indentStr(key), key.maybeQuotedAll())
enc.wf("%s[%s]", enc.indentStr(key), key)
enc.newline()
}
enc.eMapOrStruct(key, rv, false)
Expand Down
21 changes: 7 additions & 14 deletions meta.go
Expand Up @@ -10,11 +10,12 @@ import (
// It allows checking if a key is defined in the TOML data, whether any keys
// were undecoded, and the TOML type of a key.
type MetaData struct {
context Key // Used only during decoding.

mapping map[string]interface{}
types map[string]tomlType
keys []Key
decoded map[string]struct{}
context Key // Used only during decoding.
}

// IsDefined reports if the key exists in the TOML data.
Expand Down Expand Up @@ -49,8 +50,7 @@ func (md *MetaData) IsDefined(key ...string) bool {
// Type will return the empty string if given an empty key or a key that does
// not exist. Keys are case sensitive.
func (md *MetaData) Type(key ...string) string {
fullkey := strings.Join(key, ".")
if typ, ok := md.types[fullkey]; ok {
if typ, ok := md.types[Key(key).String()]; ok {
return typ.typeString()
}
return ""
Expand Down Expand Up @@ -92,12 +92,10 @@ func (md *MetaData) Undecoded() []Key {
// values of this type.
type Key []string

func (k Key) String() string { return strings.Join(k, ".") }

func (k Key) maybeQuotedAll() string {
var ss []string
func (k Key) String() string {
ss := make([]string, len(k))
for i := range k {
ss = append(ss, k.maybeQuoted(i))
ss[i] = k.maybeQuoted(i)
}
return strings.Join(ss, ".")
}
Expand All @@ -106,16 +104,11 @@ func (k Key) maybeQuoted(i int) string {
if k[i] == "" {
return `""`
}
quote := false
for _, c := range k[i] {
if !isBareKeyChar(c) {
quote = true
break
return `"` + dblQuotedReplacer.Replace(k[i]) + `"`
}
}
if quote {
return `"` + dblQuotedReplacer.Replace(k[i]) + `"`
}
return k[i]
}

Expand Down
16 changes: 11 additions & 5 deletions parse.go
Expand Up @@ -530,8 +530,8 @@ func (p *parser) addContext(key Key, array bool) {

// set calls setValue and setType.
func (p *parser) set(key string, val interface{}, typ tomlType) {
p.setValue(p.currentKey, val)
p.setType(p.currentKey, typ)
p.setValue(key, val)
p.setType(key, typ)
}

// setValue sets the given key to the given value in the current context.
Expand Down Expand Up @@ -590,8 +590,8 @@ func (p *parser) setValue(key string, value interface{}) {
hash[key] = value
}

// setType sets the type of a particular value at a given key.
// It should be called immediately AFTER setValue.
// setType sets the type of a particular value at a given key. It should be
// called immediately AFTER setValue.
//
// Note that if `key` is empty, then the type given will be applied to the
// current context (which is either a table or an array of tables).
Expand All @@ -601,6 +601,12 @@ func (p *parser) setType(key string, typ tomlType) {
if len(key) > 0 { // allow type setting for hashes
keyContext = append(keyContext, key)
}
// Special case to make empty keys ("" = 1) work.
// Without it it will set "" rather than `""`.
// TODO: why is this needed? And why is this only needed here?
if len(keyContext) == 0 {
keyContext = Key{""}
}
p.types[keyContext.String()] = typ
}

Expand Down Expand Up @@ -678,7 +684,7 @@ func stripEscapedNewlines(s string) string {
}

func (p *parser) replaceEscapes(it item, str string) string {
var replaced []rune
replaced := make([]rune, 0, len(str))
s := []byte(str)
r := 0
for r < len(s) {
Expand Down

0 comments on commit 7d0236f

Please sign in to comment.