diff --git a/ini.go b/ini.go index c343ded..d6d3ae3 100644 --- a/ini.go +++ b/ini.go @@ -37,7 +37,7 @@ const ( // Maximum allowed depth when recursively substituing variable names. _DEPTH_VALUES = 99 - _VERSION = "1.24.0" + _VERSION = "1.25.0" ) // Version returns current package version literal. @@ -176,6 +176,8 @@ type LoadOptions struct { // AllowBooleanKeys indicates whether to allow boolean type keys or treat as value is missing. // This type of keys are mostly used in my.cnf. AllowBooleanKeys bool + // AllowShadows indicates whether to keep track of keys with same name under same section. + AllowShadows bool // Some INI formats allow group blocks that store a block of raw content that doesn't otherwise // conform to key/value pairs. Specify the names of those blocks here. UnparseableSections []string diff --git a/key.go b/key.go index 9738c55..d108d3f 100644 --- a/key.go +++ b/key.go @@ -15,6 +15,7 @@ package ini import ( + "errors" "fmt" "strconv" "strings" @@ -29,9 +30,40 @@ type Key struct { isAutoIncrement bool isBooleanType bool + isShadow bool + shadows []*Key + Comment string } +// newKey simply return a key object with given values. +func newKey(s *Section, name, val string) *Key { + return &Key{ + s: s, + name: name, + value: val, + } +} + +func (k *Key) addShadow(val string) error { + if k.isShadow { + return errors.New("cannot add shadow to another shadow key") + } + + shadow := newKey(k.s, k.name, val) + shadow.isShadow = true + k.shadows = append(k.shadows, shadow) + return nil +} + +// AddShadow adds a new shadow key to itself. +func (k *Key) AddShadow(val string) error { + if !k.s.f.options.AllowShadows { + return errors.New("shadow key is not allowed") + } + return k.addShadow(val) +} + // ValueMapper represents a mapping function for values, e.g. os.ExpandEnv type ValueMapper func(string) string @@ -45,6 +77,19 @@ func (k *Key) Value() string { return k.value } +// ValueWithShadows returns raw values of key and its shadows if any. +func (k *Key) ValueWithShadows() []string { + if len(k.shadows) == 0 { + return []string{k.value} + } + vals := make([]string, len(k.shadows)+1) + vals[0] = k.value + for i := range k.shadows { + vals[i+1] = k.shadows[i].value + } + return vals +} + // String returns string representation of value. func (k *Key) String() string { val := k.value diff --git a/key_test.go b/key_test.go index 39c6210..f855c9a 100644 --- a/key_test.go +++ b/key_test.go @@ -439,6 +439,30 @@ key2= ; comment` }) } +const _CONF_GIT_CONFIG = ` +[remote "origin"] + url = https://github.com/Antergone/test1.git + url = https://github.com/Antergone/test2.git +` + +func Test_Key_Shadows(t *testing.T) { + Convey("Shadows keys", t, func() { + Convey("Disable shadows", func() { + cfg, err := Load([]byte(_CONF_GIT_CONFIG)) + So(err, ShouldBeNil) + So(cfg.Section(`remote "origin"`).Key("url").String(), ShouldEqual, "https://github.com/Antergone/test2.git") + }) + + Convey("Enable shadows", func() { + cfg, err := LoadSources(LoadOptions{AllowShadows: true}, []byte(_CONF_GIT_CONFIG)) + So(err, ShouldBeNil) + So(cfg.Section(`remote "origin"`).Key("url").String(), ShouldEqual, "https://github.com/Antergone/test1.git") + So(strings.Join(cfg.Section(`remote "origin"`).Key("url").ValueWithShadows(), " "), ShouldEqual, + "https://github.com/Antergone/test1.git https://github.com/Antergone/test2.git") + }) + }) +} + func newTestFile(block bool) *File { c, _ := Load([]byte(_CONF_DATA)) c.BlockMode = block diff --git a/parser.go b/parser.go index 01ff20a..673ef80 100644 --- a/parser.go +++ b/parser.go @@ -341,17 +341,16 @@ func (f *File) parse(reader io.Reader) (err error) { p.count++ } - key, err := section.NewKey(kname, "") - if err != nil { - return err - } - key.isAutoIncrement = isAutoIncr - value, err := p.readValue(line[offset:], f.options.IgnoreContinuation) if err != nil { return err } - key.SetValue(value) + + key, err := section.NewKey(kname, value) + if err != nil { + return err + } + key.isAutoIncrement = isAutoIncr key.Comment = strings.TrimSpace(p.comment.String()) p.comment.Reset() } diff --git a/section.go b/section.go index 806f149..c9fa27e 100644 --- a/section.go +++ b/section.go @@ -68,16 +68,18 @@ func (s *Section) NewKey(name, val string) (*Key, error) { } if inSlice(name, s.keyList) { - s.keys[name].value = val + if s.f.options.AllowShadows { + if err := s.keys[name].addShadow(val); err != nil { + return nil, err + } + } else { + s.keys[name].value = val + } return s.keys[name], nil } s.keyList = append(s.keyList, name) - s.keys[name] = &Key{ - s: s, - name: name, - value: val, - } + s.keys[name] = newKey(s, name, val) s.keysHash[name] = val return s.keys[name], nil }