aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShulhan <ms@kilabit.info>2019-05-26 19:22:22 +0700
committerShulhan <ms@kilabit.info>2019-05-26 19:22:22 +0700
commitdcd484c21de6e2b16470987e591f098b1d47e091 (patch)
treefd13a22dfc4cb0fd5e9af9bc06e99ba6dea2b2af
parentc6f112bdc61a64acb7b5ff28ccc1c0d9ccc4c262 (diff)
downloadpakakeh.go-dcd484c21de6e2b16470987e591f098b1d47e091.tar.xz
ini: add methods to support templating
The following methods are added to support templating using this package, * Subs(): a method that return all non-empty subsections * Val(): a method that return the last variable's value using key's path as combination of section-name ":" sub-section-name ":" key. * Vals(): a method that return all variable values as slice of string This changes cause the section type to be exported back, again.
-rw-r--r--lib/ini/doc.go6
-rw-r--r--lib/ini/ini.go109
-rw-r--r--lib/ini/ini_example_test.go101
-rw-r--r--lib/ini/ini_test.go6
-rw-r--r--lib/ini/reader.go6
-rw-r--r--lib/ini/section.go63
-rw-r--r--lib/ini/section_test.go62
7 files changed, 281 insertions, 72 deletions
diff --git a/lib/ini/doc.go b/lib/ini/doc.go
index dfdf45bb..132cc276 100644
--- a/lib/ini/doc.go
+++ b/lib/ini/doc.go
@@ -6,11 +6,13 @@
// Package ini implement reading and writing INI configuration as defined by
// Git configuration file syntax.
//
-// Feature Promises
+// Features
//
-// Reading and writing on the same file should not change the content of
+// * Reading and writing on the same file should not change the content of
// file (including comment).
//
+// * Template friendly, through Val(), Vals(), and Subs().
+//
// Unsupported Features
//
// Git `include` and `includeIf` directives.
diff --git a/lib/ini/ini.go b/lib/ini/ini.go
index 1670718c..253d1e22 100644
--- a/lib/ini/ini.go
+++ b/lib/ini/ini.go
@@ -18,7 +18,7 @@ import (
// Ini contains the parsed file.
//
type Ini struct {
- secs []*section
+ secs []*Section
}
//
@@ -133,7 +133,7 @@ func (in *Ini) Unset(secName, subName, key string) bool {
//
// addSection append the new section to the list.
//
-func (in *Ini) addSection(sec *section) {
+func (in *Ini) addSection(sec *Section) {
if sec == nil {
return
}
@@ -201,20 +201,20 @@ func (in *Ini) AsMap() (out map[string][]string) {
// If key found it will return its value and true; otherwise it will return
// default value in def and false.
//
-func (in *Ini) Get(section, subsection, key, def string) (val string, ok bool) {
- if len(in.secs) == 0 || len(section) == 0 || len(key) == 0 {
+func (in *Ini) Get(secName, subName, key, def string) (val string, ok bool) {
+ if len(in.secs) == 0 || len(secName) == 0 || len(key) == 0 {
return def, false
}
x := len(in.secs) - 1
- sec := strings.ToLower(section)
+ sec := strings.ToLower(secName)
for ; x >= 0; x-- {
if in.secs[x].nameLower != sec {
continue
}
- if in.secs[x].sub != subsection {
+ if in.secs[x].sub != subName {
continue
}
@@ -231,8 +231,8 @@ func (in *Ini) Get(section, subsection, key, def string) (val string, ok bool) {
// GetBool return key's value as boolean. If no key found it will return
// default value.
//
-func (in *Ini) GetBool(section, subsection, key string, def bool) bool {
- out, ok := in.Get(section, subsection, key, "false")
+func (in *Ini) GetBool(secName, subName, key string, def bool) bool {
+ out, ok := in.Get(secName, subName, key, "false")
if !ok {
return def
}
@@ -243,17 +243,17 @@ func (in *Ini) GetBool(section, subsection, key string, def bool) bool {
//
// Gets key's values as slice of string in the same section and subsection.
//
-func (in *Ini) Gets(section, subsection, key string) (out []string) {
- section = strings.ToLower(section)
+func (in *Ini) Gets(secName, subName, key string) (out []string) {
+ secName = strings.ToLower(secName)
for _, sec := range in.secs {
if sec.mode&lineModeSection == 0 {
continue
}
- if sec.nameLower != section {
+ if sec.nameLower != secName {
continue
}
- if sec.sub != subsection {
+ if sec.sub != subName {
continue
}
vals, ok := sec.gets(key, nil)
@@ -271,13 +271,13 @@ func (in *Ini) Gets(section, subsection, key string) (out []string) {
// subsection with the same name into one group.
//
func (in *Ini) Prune() {
- newSecs := make([]*section, 0, len(in.secs))
+ newSecs := make([]*Section, 0, len(in.secs))
for _, sec := range in.secs {
if sec.mode == lineModeEmpty {
continue
}
- newSec := &section{
+ newSec := &Section{
mode: lineModeSection,
name: sec.name,
nameLower: sec.nameLower,
@@ -307,7 +307,7 @@ func (in *Ini) Prune() {
//
// mergeSection merge a section (and subsection) into slice.
//
-func mergeSection(secs []*section, newSec *section) []*section {
+func mergeSection(secs []*Section, newSec *Section) []*Section {
for x := 0; x < len(secs); x++ {
if secs[x].nameLower != newSec.nameLower {
continue
@@ -355,6 +355,83 @@ func (in *Ini) Save(filename string) (err error) {
}
//
+// Subs return all non empty subsections (and its variable) that have the same
+// section name.
+//
+// This function is shortcut to be used in templating.
+//
+func (in *Ini) Subs(secName string) (subs []*Section) {
+ if len(secName) == 0 {
+ return
+ }
+
+ secName = strings.ToLower(secName)
+
+ for x := 0; x < len(in.secs); x++ {
+ if in.secs[x].mode == lineModeEmpty || in.secs[x].mode == lineModeComment {
+ continue
+ }
+ if len(in.secs[x].sub) == 0 {
+ continue
+ }
+ if in.secs[x].nameLower != secName {
+ continue
+ }
+
+ subs = mergeSection(subs, in.secs[x])
+ }
+
+ return subs
+}
+
+//
+// Val return the last variable value using a string as combination of
+// section, subsection, and key with ":" as separator. If key not found, it
+// will return empty string.
+//
+// For example, to get the value of key "k" in section "s" and subsection
+// "sub", call
+//
+// V("s:sub:k")
+//
+// This function is shortcut to be used in templating.
+//
+func (in *Ini) Val(keyPath string) (val string) {
+ keys := strings.Split(keyPath, ":")
+ if len(keys) != 3 {
+ return
+ }
+
+ val, _ = in.Get(keys[0], keys[1], keys[2], "")
+
+ return
+}
+
+//
+// Vals return all values as slice of string.
+// The keyPath is combination of section, subsection, and key using colon ":"
+// as separator.
+// If key not found, it will return an empty slice.
+//
+// For example, to get all values of key "k" in section "s" and subsection
+// "sub", call
+//
+// Vals("s:sub:k")
+//
+// This function is shortcut to be used in templating.
+//
+func (in *Ini) Vals(keyPath string) (vals []string) {
+ keys := strings.Split(keyPath, ":")
+ if len(keys) != 3 {
+ return
+ }
+
+ vals = in.Gets(keys[0], keys[1], keys[2])
+
+ return
+}
+
+//
// Write the current parsed Ini into writer `w`.
//
func (in *Ini) Write(w io.Writer) (err error) {
@@ -374,7 +451,7 @@ func (in *Ini) Write(w io.Writer) (err error) {
// subsection's name.
// Section's name MUST have in lowercase.
//
-func (in *Ini) getSection(secName, subName string) *section {
+func (in *Ini) getSection(secName, subName string) *Section {
x := len(in.secs) - 1
for ; x >= 0; x-- {
if in.secs[x].mode == lineModeEmpty || in.secs[x].mode == lineModeComment {
diff --git a/lib/ini/ini_example_test.go b/lib/ini/ini_example_test.go
index 06985788..0fd4c31e 100644
--- a/lib/ini/ini_example_test.go
+++ b/lib/ini/ini_example_test.go
@@ -243,6 +243,38 @@ key=value1
//key=value3
}
+func ExampleIni_Subs() {
+ input := []byte(`
+[section]
+key=value1 # comment
+key2= ; another comment
+
+[section "sub"]
+key=value1
+
+[section] ; here is comment on section
+key=value2
+key2=false
+
+[section "sub"]
+key=value2
+key=value1
+`)
+
+ ini, err := Parse(input)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ subs := ini.Subs("section")
+
+ for _, sub := range subs {
+ fmt.Println(sub.SubName(), sub.Vals("key"))
+ }
+ // Output:
+ // sub [value2 value1]
+}
+
func ExampleIni_Unset() {
input := []byte(`
[section]
@@ -294,3 +326,72 @@ key=value1
//[section "sub"]
//key=value2
}
+
+func ExampleIni_Val() {
+ input := `
+[section]
+key=value1
+key2=
+
+[section "sub"]
+key=value1
+
+[section]
+key=value2
+key2=false
+
+[section "sub"]
+key=value2
+key=value3
+`
+
+ ini, err := Parse([]byte(input))
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ fmt.Println(ini.Val("section:sub:key"))
+ fmt.Println(ini.Val("section:sub:key2"))
+ fmt.Println(ini.Val("section::key"))
+ fmt.Println(ini.Val("section:key"))
+ // Output:
+ // value3
+ //
+ // value2
+ //
+}
+
+func ExampleIni_Vals() {
+ input := `
+[section]
+key=value1
+key2=
+
+[section "sub"]
+key=value1
+key=value2
+
+[section]
+key=value2
+key2=false
+
+[section "sub"]
+key=value2
+key=value3
+`
+
+ ini, err := Parse([]byte(input))
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ fmt.Println(ini.Vals("section:key"))
+ fmt.Println(ini.Vals("section::key"))
+ fmt.Println(ini.Vals("section:sub:key2"))
+ fmt.Println(ini.Vals("section:sub:key"))
+ // Output:
+ // []
+ // [value1 value2]
+ // []
+ // [value1 value2 value3]
+}
diff --git a/lib/ini/ini_test.go b/lib/ini/ini_test.go
index 859b63cc..5901c011 100644
--- a/lib/ini/ini_test.go
+++ b/lib/ini/ini_test.go
@@ -91,20 +91,20 @@ func TestAddSection(t *testing.T) {
cases := []struct {
desc string
- sec *section
+ sec *Section
expIni *Ini
}{{
desc: "With nil section",
expIni: &Ini{},
}, {
desc: "With valid section",
- sec: &section{
+ sec: &Section{
mode: lineModeSection,
name: "Test",
nameLower: "test",
},
expIni: &Ini{
- secs: []*section{{
+ secs: []*Section{{
mode: lineModeSection,
name: "Test",
nameLower: "test",
diff --git a/lib/ini/reader.go b/lib/ini/reader.go
index f1769d48..d95a7fcb 100644
--- a/lib/ini/reader.go
+++ b/lib/ini/reader.go
@@ -49,7 +49,7 @@ type reader struct {
lineNum int
filename string
_var *variable
- sec *section
+ sec *Section
buf bytes.Buffer
bufComment bytes.Buffer
bufFormat bytes.Buffer
@@ -79,7 +79,7 @@ func (reader *reader) reset(src []byte) {
reader._var = &variable{
mode: lineModeEmpty,
}
- reader.sec = &section{
+ reader.sec = &Section{
mode: lineModeEmpty,
}
reader.buf.Reset()
@@ -147,7 +147,7 @@ func (reader *reader) Parse(src []byte) (in *Ini, err error) {
in.addSection(reader.sec)
- reader.sec = &section{
+ reader.sec = &Section{
mode: reader._var.mode,
lineNum: reader._var.lineNum,
name: reader._var.secName,
diff --git a/lib/ini/section.go b/lib/ini/section.go
index 898393c9..d30a6e3d 100644
--- a/lib/ini/section.go
+++ b/lib/ini/section.go
@@ -11,12 +11,11 @@ import (
)
//
-// section represent section header in INI file format and their variables.
+// Section represent section header in INI file format and their variables.
//
-// Remember that section's name is case insensitive. When trying to
-// compare section name use the NameLower field.
+// Remember that section's name is case insensitive.
//
-type section struct {
+type Section struct {
mode lineMode
lineNum int
name string
@@ -32,12 +31,12 @@ type section struct {
// `subName`.
// If section `name` is empty, it will return nil.
//
-func newSection(name, subName string) (sec *section) {
+func newSection(name, subName string) (sec *Section) {
if len(name) == 0 {
return
}
- sec = &section{
+ sec = &Section{
mode: lineModeSection,
name: name,
}
@@ -53,9 +52,23 @@ func newSection(name, subName string) (sec *section) {
}
//
+// Name return the section's name.
+//
+func (sec *Section) Name() string {
+ return sec.name
+}
+
+//
+// SubName return subsection's name.
+//
+func (sec *Section) SubName() string {
+ return sec.sub
+}
+
+//
// String return formatted INI section header.
//
-func (sec *section) String() string {
+func (sec *Section) String() string {
var buf bytes.Buffer
switch sec.mode {
@@ -89,6 +102,22 @@ func (sec *section) String() string {
}
//
+// Val return the last defined variable key in section.
+//
+func (sec *Section) Val(key string) string {
+ val, _ := sec.get(key, "")
+ return val
+}
+
+//
+// Vals return all variables in section as slice of string.
+//
+func (sec *Section) Vals(key string) []string {
+ vals, _ := sec.gets(key, nil)
+ return vals
+}
+
+//
// add append variable with `key` and `value` to current section.
//
// If key is empty, no variable will be appended.
@@ -99,7 +128,7 @@ func (sec *section) String() string {
// It will return true if new variable is appended, otherwise it will return
// false.
//
-func (sec *section) add(key, value string) bool {
+func (sec *Section) add(key, value string) bool {
if len(key) == 0 {
return false
}
@@ -139,7 +168,7 @@ func (sec *section) add(key, value string) bool {
// to end of list, to make the last declared variable still at the end of
// list.
//
-func (sec *section) addUniqValue(key, value string) {
+func (sec *Section) addUniqValue(key, value string) {
keyLower := strings.ToLower(key)
for x := 0; x < len(sec.vars); x++ {
if sec.vars[x].keyLower == keyLower {
@@ -160,7 +189,7 @@ func (sec *section) addUniqValue(key, value string) {
sec.vars = append(sec.vars, v)
}
-func (sec *section) addVariable(v *variable) {
+func (sec *Section) addVariable(v *variable) {
if v == nil {
return
}
@@ -181,7 +210,7 @@ func (sec *section) addVariable(v *variable) {
// get will return the last variable value based on key.
// If no key found it will return default value and false.
//
-func (sec *section) get(key, def string) (val string, ok bool) {
+func (sec *Section) get(key, def string) (val string, ok bool) {
val = def
if len(sec.vars) == 0 || len(key) == 0 {
return
@@ -207,7 +236,7 @@ func (sec *section) get(key, def string) (val string, ok bool) {
// getVariable return the last variable that have the same key.
// The key MUST have been converted to lowercase.
//
-func (sec *section) getVariable(key string) (idx int, v *variable) {
+func (sec *Section) getVariable(key string) (idx int, v *variable) {
idx = len(sec.vars) - 1
for ; idx >= 0; idx-- {
if !isLineModeVar(sec.vars[idx].mode) {
@@ -227,7 +256,7 @@ func (sec *section) getVariable(key string) (idx int, v *variable) {
// bottom.
// If no key found it will return default values and false.
//
-func (sec *section) gets(key string, defs []string) (vals []string, ok bool) {
+func (sec *Section) gets(key string, defs []string) (vals []string, ok bool) {
if len(sec.vars) == 0 || len(key) == 0 {
return defs, false
}
@@ -253,7 +282,7 @@ func (sec *section) gets(key string, defs []string) (vals []string, ok bool) {
// If section contains duplicate keys, all duplicate keys will be
// removed, and replaced with one key only.
//
-func (sec *section) replaceAll(key, value string) {
+func (sec *Section) replaceAll(key, value string) {
sec.unsetAll(key)
sec.add(key, value)
}
@@ -263,7 +292,7 @@ func (sec *section) replaceAll(key, value string) {
// The key MUST be not empty and has been converted to lowercase.
// If value is empty, it will be set to true.
//
-func (sec *section) set(key, value string) bool {
+func (sec *Section) set(key, value string) bool {
if len(sec.vars) == 0 || len(key) == 0 {
return false
}
@@ -289,7 +318,7 @@ func (sec *section) set(key, value string) bool {
// On success, where a variable removed or one variable is removed, it will
// return true, otherwise it will be removed.
//
-func (sec *section) unset(key string) bool {
+func (sec *Section) unset(key string) bool {
if len(key) == 0 {
return false
}
@@ -311,7 +340,7 @@ func (sec *section) unset(key string) bool {
//
// unsetAll remove all variables with `key`.
//
-func (sec *section) unsetAll(key string) {
+func (sec *Section) unsetAll(key string) {
if len(key) == 0 {
return
}
diff --git a/lib/ini/section_test.go b/lib/ini/section_test.go
index 3e7df78e..01f5c942 100644
--- a/lib/ini/section_test.go
+++ b/lib/ini/section_test.go
@@ -15,7 +15,7 @@ func TestNewSection(t *testing.T) {
desc string
name string
sub string
- expSec *section
+ expSec *Section
}{{
desc: "With empty name",
}, {
@@ -24,7 +24,7 @@ func TestNewSection(t *testing.T) {
}, {
desc: "With name only",
name: "Section",
- expSec: &section{
+ expSec: &Section{
mode: lineModeSection,
name: "Section",
nameLower: "section",
@@ -33,7 +33,7 @@ func TestNewSection(t *testing.T) {
desc: "With name and subname",
name: "Section",
sub: "Subsection",
- expSec: &section{
+ expSec: &Section{
mode: lineModeSection | lineModeSubsection,
name: "Section",
nameLower: "section",
@@ -51,7 +51,7 @@ func TestNewSection(t *testing.T) {
}
func TestSectionSet(t *testing.T) {
- sec := &section{
+ sec := &Section{
mode: lineModeSection,
name: "section",
nameLower: "section",
@@ -73,12 +73,12 @@ func TestSectionSet(t *testing.T) {
k string
v string
expOK bool
- expSec *section
+ expSec *Section
}{{
desc: "With empty value",
k: "k",
expOK: true,
- expSec: &section{
+ expSec: &Section{
mode: sec.mode,
name: sec.name,
nameLower: sec.nameLower,
@@ -99,7 +99,7 @@ func TestSectionSet(t *testing.T) {
k: "k",
v: "false",
expOK: true,
- expSec: &section{
+ expSec: &Section{
mode: sec.mode,
name: sec.name,
nameLower: sec.nameLower,
@@ -128,7 +128,7 @@ func TestSectionSet(t *testing.T) {
}
func TestSectionAdd(t *testing.T) {
- sec := &section{
+ sec := &Section{
mode: lineModeSection,
name: "section",
nameLower: "section",
@@ -149,10 +149,10 @@ func TestSectionAdd(t *testing.T) {
desc string
k string
v string
- expSec *section
+ expSec *Section
}{{
desc: "With empty key",
- expSec: &section{
+ expSec: &Section{
mode: sec.mode,
name: sec.name,
nameLower: sec.nameLower,
@@ -171,7 +171,7 @@ func TestSectionAdd(t *testing.T) {
}, {
desc: "With no value",
k: "k",
- expSec: &section{
+ expSec: &Section{
mode: sec.mode,
name: sec.name,
nameLower: sec.nameLower,
@@ -196,7 +196,7 @@ func TestSectionAdd(t *testing.T) {
desc: "Duplicate key and value",
k: "k",
v: "v1",
- expSec: &section{
+ expSec: &Section{
mode: sec.mode,
name: sec.name,
nameLower: sec.nameLower,
@@ -229,7 +229,7 @@ func TestSectionAdd(t *testing.T) {
}
func TestSectionUnset(t *testing.T) {
- sec := &section{
+ sec := &Section{
mode: lineModeSection,
name: "section",
nameLower: "section",
@@ -250,11 +250,11 @@ func TestSectionUnset(t *testing.T) {
desc string
k string
expOK bool
- expSec *section
+ expSec *Section
}{{
desc: "With empty key",
expOK: false,
- expSec: &section{
+ expSec: &Section{
mode: sec.mode,
name: sec.name,
nameLower: sec.nameLower,
@@ -274,7 +274,7 @@ func TestSectionUnset(t *testing.T) {
desc: "With duplicate key",
k: "k",
expOK: true,
- expSec: &section{
+ expSec: &Section{
mode: sec.mode,
name: sec.name,
nameLower: sec.nameLower,
@@ -288,7 +288,7 @@ func TestSectionUnset(t *testing.T) {
}, {
desc: "With invalid key",
k: "key-2",
- expSec: &section{
+ expSec: &Section{
mode: sec.mode,
name: sec.name,
nameLower: sec.nameLower,
@@ -303,7 +303,7 @@ func TestSectionUnset(t *testing.T) {
desc: "With valid key (again)",
k: "k",
expOK: true,
- expSec: &section{
+ expSec: &Section{
mode: sec.mode,
name: sec.name,
nameLower: sec.nameLower,
@@ -322,7 +322,7 @@ func TestSectionUnset(t *testing.T) {
}
func TestSectionUnsetAll(t *testing.T) {
- sec := &section{
+ sec := &Section{
mode: lineModeSection,
name: "section",
nameLower: "section",
@@ -342,10 +342,10 @@ func TestSectionUnsetAll(t *testing.T) {
cases := []struct {
desc string
k string
- expSec *section
+ expSec *Section
}{{
desc: "With empty key",
- expSec: &section{
+ expSec: &Section{
mode: sec.mode,
name: sec.name,
nameLower: sec.nameLower,
@@ -364,7 +364,7 @@ func TestSectionUnsetAll(t *testing.T) {
}, {
desc: "With unmatch key",
k: "unmatch",
- expSec: &section{
+ expSec: &Section{
mode: sec.mode,
name: sec.name,
nameLower: sec.nameLower,
@@ -383,7 +383,7 @@ func TestSectionUnsetAll(t *testing.T) {
}, {
desc: "With valid k",
k: "K",
- expSec: &section{
+ expSec: &Section{
mode: sec.mode,
name: sec.name,
nameLower: sec.nameLower,
@@ -391,7 +391,7 @@ func TestSectionUnsetAll(t *testing.T) {
}, {
desc: "With valid key (again)",
k: "K",
- expSec: &section{
+ expSec: &Section{
mode: sec.mode,
name: sec.name,
nameLower: sec.nameLower,
@@ -408,7 +408,7 @@ func TestSectionUnsetAll(t *testing.T) {
}
func TestSectionReplaceAll(t *testing.T) {
- sec := &section{
+ sec := &Section{
mode: lineModeSection,
name: "section",
nameLower: "section",
@@ -423,10 +423,10 @@ func TestSectionReplaceAll(t *testing.T) {
desc string
k string
v string
- expSec *section
+ expSec *Section
}{{
desc: "With empty key",
- expSec: &section{
+ expSec: &Section{
mode: sec.mode,
name: sec.name,
nameLower: sec.nameLower,
@@ -456,7 +456,7 @@ func TestSectionReplaceAll(t *testing.T) {
desc: "With invalid key",
k: "KEY-4",
v: "4",
- expSec: &section{
+ expSec: &Section{
mode: sec.mode,
name: sec.name,
nameLower: sec.nameLower,
@@ -491,7 +491,7 @@ func TestSectionReplaceAll(t *testing.T) {
desc: "With valid key",
k: "KEY-3",
v: "replaced",
- expSec: &section{
+ expSec: &Section{
mode: sec.mode,
name: sec.name,
nameLower: sec.nameLower,
@@ -519,7 +519,7 @@ func TestSectionReplaceAll(t *testing.T) {
}
func TestSectionGet(t *testing.T) {
- sec := &section{
+ sec := &Section{
mode: lineModeSection,
name: "section",
nameLower: "section",
@@ -566,7 +566,7 @@ func TestSectionGet(t *testing.T) {
}
func TestSectionGets(t *testing.T) {
- sec := &section{
+ sec := &Section{
mode: lineModeSection,
name: "section",
nameLower: "section",