From 29f972a20e4e7ae884c8f71ed63005698d93affa Mon Sep 17 00:00:00 2001 From: Kevin Schmittle Date: Sun, 16 Aug 2020 05:21:38 -0600 Subject: [PATCH] Allow alternative delimiter for child sections (#255) ini: added option for ChildSectionDelimiter file: added default for ChildSectionDelimiter (".") to maintain current behavior by default section: - append ChildSectionDelimiter instead of "." when checking if each section name begins with prefix - use ChildSectionDelimiter instead of "." when checking for LastIndex of delimiter --- file.go | 3 +++ ini.go | 2 ++ ini_test.go | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++++ section.go | 6 +++--- 4 files changed, 70 insertions(+), 3 deletions(-) diff --git a/file.go b/file.go index c022584..2fcd8de 100644 --- a/file.go +++ b/file.go @@ -55,6 +55,9 @@ func newFile(dataSources []dataSource, opts LoadOptions) *File { if len(opts.KeyValueDelimiterOnWrite) == 0 { opts.KeyValueDelimiterOnWrite = "=" } + if len(opts.ChildSectionDelimiter) == 0 { + opts.ChildSectionDelimiter = "." + } return &File{ BlockMode: true, diff --git a/ini.go b/ini.go index 39057a2..80ebf3a 100644 --- a/ini.go +++ b/ini.go @@ -113,6 +113,8 @@ type LoadOptions struct { KeyValueDelimiters string // KeyValueDelimiters is the delimiter that are used to separate key and value output. By default, it is "=". KeyValueDelimiterOnWrite string + // ChildSectionDelimiter is the delimiter that is used to separate child sections. By default, it is ".". + ChildSectionDelimiter string // PreserveSurroundedQuote indicates whether to preserve surrounded quote (single and double quotes). PreserveSurroundedQuote bool // DebugFunc is called to collect debug information (currently only useful to debug parsing Python-style multiline values). diff --git a/ini_test.go b/ini_test.go index 55a0a56..94012a9 100644 --- a/ini_test.go +++ b/ini_test.go @@ -1305,6 +1305,68 @@ GITHUB = U;n;k;n;w;o;n }) }) }) + + Convey("with `ChildSectionDelimiter` ':'", func() { + Convey("Get all keys of parent sections", func() { + f := ini.Empty(ini.LoadOptions{ChildSectionDelimiter: ":"}) + So(f, ShouldNotBeNil) + + k, err := f.Section("package").NewKey("NAME", "ini") + So(err, ShouldBeNil) + So(k, ShouldNotBeNil) + k, err = f.Section("package").NewKey("VERSION", "v1") + So(err, ShouldBeNil) + So(k, ShouldNotBeNil) + k, err = f.Section("package").NewKey("IMPORT_PATH", "gopkg.in/ini.v1") + So(err, ShouldBeNil) + So(k, ShouldNotBeNil) + + keys := f.Section("package:sub:sub2").ParentKeys() + names := []string{"NAME", "VERSION", "IMPORT_PATH"} + So(len(keys), ShouldEqual, len(names)) + for i, name := range names { + So(keys[i].Name(), ShouldEqual, name) + } + }) + + Convey("Getting and setting values", func() { + f, err := ini.LoadSources(ini.LoadOptions{ChildSectionDelimiter: ":"}, fullConf) + So(err, ShouldBeNil) + So(f, ShouldNotBeNil) + + Convey("Get parent-keys that are available to the child section", func() { + parentKeys := f.Section("package:sub").ParentKeys() + So(parentKeys, ShouldNotBeNil) + for _, k := range parentKeys { + So(k.Name(), ShouldEqual, "CLONE_URL") + } + }) + + Convey("Get parent section value", func() { + So(f.Section("package:sub").Key("CLONE_URL").String(), ShouldEqual, "https://gopkg.in/ini.v1") + So(f.Section("package:fake:sub").Key("CLONE_URL").String(), ShouldEqual, "https://gopkg.in/ini.v1") + }) + }) + + Convey("Get child sections by parent name", func() { + f, err := ini.LoadSources(ini.LoadOptions{ChildSectionDelimiter: ":"}, []byte(` +[node] +[node:biz1] +[node:biz2] +[node.biz3] +[node.bizN] +`)) + So(err, ShouldBeNil) + So(f, ShouldNotBeNil) + + children := f.ChildSections("node") + names := []string{"node:biz1", "node:biz2"} + So(len(children), ShouldEqual, len(names)) + for i, name := range names { + So(children[i].Name(), ShouldEqual, name) + } + }) + }) }) } diff --git a/section.go b/section.go index e08774c..afaa97c 100644 --- a/section.go +++ b/section.go @@ -121,7 +121,7 @@ func (s *Section) GetKey(name string) (*Key, error) { // Check if it is a child-section. sname := s.name for { - if i := strings.LastIndex(sname, "."); i > -1 { + if i := strings.LastIndex(sname, s.f.options.ChildSectionDelimiter); i > -1 { sname = sname[:i] sec, err := s.f.GetSection(sname) if err != nil { @@ -188,7 +188,7 @@ func (s *Section) ParentKeys() []*Key { var parentKeys []*Key sname := s.name for { - if i := strings.LastIndex(sname, "."); i > -1 { + if i := strings.LastIndex(sname, s.f.options.ChildSectionDelimiter); i > -1 { sname = sname[:i] sec, err := s.f.GetSection(sname) if err != nil { @@ -245,7 +245,7 @@ func (s *Section) DeleteKey(name string) { // For example, "[parent.child1]" and "[parent.child12]" are child sections // of section "[parent]". func (s *Section) ChildSections() []*Section { - prefix := s.name + "." + prefix := s.name + s.f.options.ChildSectionDelimiter children := make([]*Section, 0, 3) for _, name := range s.f.sectionList { if strings.HasPrefix(name, prefix) {