From cafd4063a06d52ea1ad34bbcf00079dd6b40b1c2 Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Sat, 10 Nov 2018 17:33:27 +0100 Subject: [PATCH] update cfg/common interface to add Parse()->Read() and Write() methods | add 'ini' format + tests --- internal/cnf/common.go | 7 +- internal/cnf/ini.go | 104 ++++++++++++++++++++++++ internal/cnf/ini_test.go | 152 ++++++++++++++++++++++++++++++++++++ internal/cnf/json.go | 10 ++- internal/cnf/json_test.go | 16 ++-- internal/cnf/loader.go | 4 +- internal/instruction/cnf.go | 2 + 7 files changed, 282 insertions(+), 13 deletions(-) create mode 100644 internal/cnf/ini.go create mode 100644 internal/cnf/ini_test.go diff --git a/internal/cnf/common.go b/internal/cnf/common.go index d62e128..e11fcfb 100644 --- a/internal/cnf/common.go +++ b/internal/cnf/common.go @@ -6,8 +6,11 @@ import ( // ConfigurationFormat is the common interface for all configuration parser type ConfigurationFormat interface { - // Parse the given file - Parse(io.Reader) error + // Read the given file + Read(io.Reader) error + + // Write the given update to the file + Write(io.Writer) error // Get the value of a field if it exists Get(string) (string, bool) diff --git a/internal/cnf/ini.go b/internal/cnf/ini.go new file mode 100644 index 0000000..b671b7d --- /dev/null +++ b/internal/cnf/ini.go @@ -0,0 +1,104 @@ +package cnf + +import ( + "github.com/go-ini/ini" + "io" + "io/ioutil" + "strings" +) + +type Ini struct { + sections []string + data *ini.File + parsed bool +} + +// Read extract a reader as its JSON representation +func (d *Ini) Read(_reader io.Reader) error { + + // 1. get json decoder + file, err := ini.Load(ioutil.NopCloser(_reader)) + if err != nil { + return err + } + + d.data = file + d.sections = file.SectionStrings() + d.parsed = true + return nil + +} + +// Write the INI representation to a writer +func (d *Ini) Write(_writer io.Writer) error { + _, err := d.data.WriteTo(_writer) + return err +} + +// Get returns the value of a dot-separated path, and if it exists +// the maximum depth is 2 : Section.Field +func (d *Ini) Get(_path string) (string, bool) { + + // 1. split path + path := strings.Split(_path, ".") + + // 2. fail if too long + if len(path) > 2 { + return "", false + } + + // 3. direct field (no section) + if len(path) == 1 { + // unknown field + if !d.data.Section("").Haskey(path[0]) { + return "", false + } + + // found + return d.data.Section("").Key(path[0]).String(), true + } + + // 4. fail on missing section + found := false + for _, sec := range d.sections { + if sec == path[0] { + found = true + break + } + } + if !found { + return "", false + } + + // 5. Fail on unknown field + if !d.data.Section(path[0]).HasKey(path[1]) { + return "", false + } + + // 6. return value + return d.data.Section(path[0]).Key(path[1]).String(), true + +} + +// Set the value of a dot-separated path, and creates it if not found +func (d *Ini) Set(_path, _value string) bool { + + // 1. split path + path := strings.Split(_path, ".") + + // 2. fail if too long + if len(path) > 2 { + return false + } + + // 3. direct field (no section) + section, field := "", path[0] + if len(path) > 1 { + section, field = path[0], path[1] + } + + // 4. Set field value + d.data.Section(section).Key(field).SetValue(_value) + return true + +} diff --git a/internal/cnf/ini_test.go b/internal/cnf/ini_test.go new file mode 100644 index 0000000..a22a0f6 --- /dev/null +++ b/internal/cnf/ini_test.go @@ -0,0 +1,152 @@ +package cnf + +import ( + "bytes" + "testing" +) + +func TestIniGet(t *testing.T) { + + tests := []struct { + raw string + key string + }{ + {"key = value", "key"}, + {"[section]\nkey = value", "section.key"}, + {"[ignoresec]\nignore = xxx\n[section]\nkey = value", "section.key"}, + } + + for _, test := range tests { + + parser := new(Ini) + reader := bytes.NewBufferString(test.raw) + + // try to extract value + err := parser.Read(reader) + if err != nil { + t.Errorf("parse error: %s", err) + continue + } + + // extract value + value, found := parser.Get(test.key) + if !found { + t.Errorf("expected a result, got none") + continue + } + + // check value + if value != "value" { + t.Errorf("expected 'value' got '%s'", value) + } + + } + +} + +func TestIniSetPathExists(t *testing.T) { + + tests := []struct { + raw string + key string + value string + }{ + {"key = value", "key", "newvalue"}, + {"[section]\nkey = value", "section.key", "newvalue"}, + {"[ignoresec]\nignore = xxx\n[section]\nkey = value", "section.key", "newvalue"}, + } + + for _, test := range tests { + + parser := new(Ini) + reader := bytes.NewBufferString(test.raw) + + // try to extract value + err := parser.Read(reader) + if err != nil { + t.Errorf("parse error: %s", err) + continue + } + + // update value + if !parser.Set(test.key, test.value) { + t.Errorf("cannot set '%s' to '%s'", test.key, test.value) + continue + } + + // check new value + value, found := parser.Get(test.key) + if !found { + t.Errorf("expected a result, got none") + continue + } + + // check value + if value != test.value { + t.Errorf("expected '%s' got '%s'", test.value, value) + } + + } + +} + +func TestIniSetCreatePath(t *testing.T) { + + tests := []struct { + raw string + key string + ignore string // path to field that must be present after transformation + value string + }{ + {"ignore = xxx", "key", "ignore", "newvalue"}, + {"ignore = xxx\n[section]\nkey = value", "section.key", "ignore", "newvalue"}, + {"[section]\nkey = value\nignore = xxx", "section.key", "section.ignore", "newvalue"}, + {"[ignoresec]\nignore = xxx\n[section]\nkey = value", "section.key", "ignoresec.ignore", "newvalue"}, + } + + for i, test := range tests { + + parser := new(Ini) + reader := bytes.NewBufferString(test.raw) + + // try to extract value + err := parser.Read(reader) + if err != nil { + t.Errorf("[%d] parse error: %s", i, err) + continue + } + + // update value + if !parser.Set(test.key, test.value) { + t.Errorf("[%d] cannot set '%s' to '%s'", i, test.key, test.value) + continue + } + + // check new value + value, found := parser.Get(test.key) + if !found { + t.Errorf("[%d] expected a result, got none", i) + continue + } + + // check value + if value != test.value { + t.Errorf("[%d] expected '%s' got '%s'", i, test.value, value) + continue + } + + // check that ignore field is still there + value, found = parser.Get(test.ignore) + if !found { + t.Errorf("[%d] expected ignore field, got none", i) + continue + } + + // check value + if value != "xxx" { + t.Errorf("[%d] expected ignore value to be '%s' got '%s'", i, "xxx", value) + continue + } + } + +} diff --git a/internal/cnf/json.go b/internal/cnf/json.go index d617d3a..0244ac9 100644 --- a/internal/cnf/json.go +++ b/internal/cnf/json.go @@ -11,8 +11,8 @@ type Json struct { parsed bool } -// Parse extract a reader as its JSON representation -func (d *Json) Parse(_reader io.Reader) error { +// Read extract a reader as its JSON representation +func (d *Json) Read(_reader io.Reader) error { // 1. get json decoder decoder := json.NewDecoder(_reader) @@ -26,6 +26,12 @@ func (d *Json) Parse(_reader io.Reader) error { } +// Write the JSON representation to a writer +func (d *Json) Write(_writer io.Writer) error { + encoder := json.NewEncoder(_writer) + return encoder.Encode(&d.data) +} + // browse returns the target of a dot-separated path (as an interface{} chain where the last is the target if found) func (d *Json) browse(_path string) ([]interface{}, bool) { diff --git a/internal/cnf/json_test.go b/internal/cnf/json_test.go index 3204385..c15a7d5 100644 --- a/internal/cnf/json_test.go +++ b/internal/cnf/json_test.go @@ -5,7 +5,7 @@ import ( "testing" ) -func TestGet(t *testing.T) { +func TestJsonGet(t *testing.T) { tests := []struct { raw string @@ -23,7 +23,7 @@ func TestGet(t *testing.T) { reader := bytes.NewBufferString(test.raw) // try to extract value - err := parser.Parse(reader) + err := parser.Read(reader) if err != nil { t.Errorf("parse error: %s", err) continue @@ -45,7 +45,7 @@ func TestGet(t *testing.T) { } -func TestGetNotString(t *testing.T) { +func TestJsonGetNotString(t *testing.T) { tests := []struct { raw string @@ -63,7 +63,7 @@ func TestGetNotString(t *testing.T) { reader := bytes.NewBufferString(test.raw) // try to extract value - err := parser.Parse(reader) + err := parser.Read(reader) if err != nil { t.Errorf("parse error: %s", err) continue @@ -80,7 +80,7 @@ func TestGetNotString(t *testing.T) { } -func TestSetPathExistsAndIsString(t *testing.T) { +func TestJsonSetPathExistsAndIsString(t *testing.T) { tests := []struct { raw string @@ -99,7 +99,7 @@ func TestSetPathExistsAndIsString(t *testing.T) { reader := bytes.NewBufferString(test.raw) // try to extract value - err := parser.Parse(reader) + err := parser.Read(reader) if err != nil { t.Errorf("parse error: %s", err) continue @@ -127,7 +127,7 @@ func TestSetPathExistsAndIsString(t *testing.T) { } -func TestSetCreatePath(t *testing.T) { +func TestJsonSetCreatePath(t *testing.T) { tests := []struct { raw string @@ -150,7 +150,7 @@ func TestSetCreatePath(t *testing.T) { reader := bytes.NewBufferString(test.raw) // try to extract value - err := parser.Parse(reader) + err := parser.Read(reader) if err != nil { t.Errorf("[%d] parse error: %s", i, err) continue diff --git a/internal/cnf/loader.go b/internal/cnf/loader.go index 64319bf..7878951 100644 --- a/internal/cnf/loader.go +++ b/internal/cnf/loader.go @@ -37,7 +37,7 @@ func Load(path string) (ConfigurationFormat, error) { defer file.Close() // parse - err = confFormat.Parse(file) + err = confFormat.Read(file) if err != nil { return nil, fmt.Errorf("cannot parse file as '%s' | %s", extension, err) } @@ -68,6 +68,8 @@ func loadFromExtension(ext string) ConfigurationFormat { switch ext { case "json": return new(Json) + case "ini": + return new(Ini) default: return nil } diff --git a/internal/instruction/cnf.go b/internal/instruction/cnf.go index cdcc236..1626525 100644 --- a/internal/instruction/cnf.go +++ b/internal/instruction/cnf.go @@ -52,6 +52,8 @@ func (d *config) Build(_args string) error { func (d config) Exec(ctx ExecutionContext) ([]byte, error) { + fmt.Printf("path is '%v'\n", d.Path) + return nil, nil }