From 2c5dc77f0a2b8a6decff88c8741e254a8a1f769f Mon Sep 17 00:00:00 2001
From: Kevin Schmittle
Date: Sat, 8 Aug 2020 22:11:55 -0600
Subject: [PATCH] Allow alternative delimiter for child sections
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 f95606f..33b328c 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 2961543..c4a1bd3 100644
--- a/ini.go
+++ b/ini.go
@@ -109,6 +109,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 e95aa01..a957a13 100644
--- a/ini_test.go
+++ b/ini_test.go
@@ -1253,6 +1253,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 6ba5ac2..da983fd 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) {