diff options
| author | Shulhan <ms@kilabit.info> | 2019-05-26 19:22:22 +0700 |
|---|---|---|
| committer | Shulhan <ms@kilabit.info> | 2019-05-26 19:22:22 +0700 |
| commit | dcd484c21de6e2b16470987e591f098b1d47e091 (patch) | |
| tree | fd13a22dfc4cb0fd5e9af9bc06e99ba6dea2b2af | |
| parent | c6f112bdc61a64acb7b5ff28ccc1c0d9ccc4c262 (diff) | |
| download | pakakeh.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.go | 6 | ||||
| -rw-r--r-- | lib/ini/ini.go | 109 | ||||
| -rw-r--r-- | lib/ini/ini_example_test.go | 101 | ||||
| -rw-r--r-- | lib/ini/ini_test.go | 6 | ||||
| -rw-r--r-- | lib/ini/reader.go | 6 | ||||
| -rw-r--r-- | lib/ini/section.go | 63 | ||||
| -rw-r--r-- | lib/ini/section_test.go | 62 |
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 := §ion{ + 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: §ion{ + 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 = §ion{ + 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 = §ion{ + 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 = §ion{ + 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: §ion{ + 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: §ion{ + expSec: &Section{ mode: lineModeSection | lineModeSubsection, name: "Section", nameLower: "section", @@ -51,7 +51,7 @@ func TestNewSection(t *testing.T) { } func TestSectionSet(t *testing.T) { - sec := §ion{ + 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: §ion{ + 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: §ion{ + 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 := §ion{ + 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: §ion{ + 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: §ion{ + 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: §ion{ + 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 := §ion{ + 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: §ion{ + 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: §ion{ + 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: §ion{ + 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: §ion{ + 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 := §ion{ + 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: §ion{ + 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: §ion{ + 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: §ion{ + 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: §ion{ + 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 := §ion{ + 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: §ion{ + 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: §ion{ + 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: §ion{ + 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 := §ion{ + sec := &Section{ mode: lineModeSection, name: "section", nameLower: "section", @@ -566,7 +566,7 @@ func TestSectionGet(t *testing.T) { } func TestSectionGets(t *testing.T) { - sec := §ion{ + sec := &Section{ mode: lineModeSection, name: "section", nameLower: "section", |
