update cfg/common interface to add Parse()->Read() and Write() methods | add 'ini' format + tests

This commit is contained in:
xdrm-brackets 2018-11-10 17:33:27 +01:00
parent ea4dfef5e9
commit cafd4063a0
7 changed files with 282 additions and 13 deletions

View File

@ -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)

104
internal/cnf/ini.go Normal file
View File

@ -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
}

152
internal/cnf/ini_test.go Normal file
View File

@ -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
}
}
}

View File

@ -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) {

View File

@ -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

View File

@ -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
}

View File

@ -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
}