update cfg/common interface to add Parse()->Read() and Write() methods | add 'ini' format + tests
This commit is contained in:
parent
ea4dfef5e9
commit
cafd4063a0
|
@ -6,8 +6,11 @@ import (
|
||||||
|
|
||||||
// ConfigurationFormat is the common interface for all configuration parser
|
// ConfigurationFormat is the common interface for all configuration parser
|
||||||
type ConfigurationFormat interface {
|
type ConfigurationFormat interface {
|
||||||
// Parse the given file
|
// Read the given file
|
||||||
Parse(io.Reader) error
|
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 the value of a field if it exists
|
||||||
Get(string) (string, bool)
|
Get(string) (string, bool)
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -11,8 +11,8 @@ type Json struct {
|
||||||
parsed bool
|
parsed bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse extract a reader as its JSON representation
|
// Read extract a reader as its JSON representation
|
||||||
func (d *Json) Parse(_reader io.Reader) error {
|
func (d *Json) Read(_reader io.Reader) error {
|
||||||
|
|
||||||
// 1. get json decoder
|
// 1. get json decoder
|
||||||
decoder := json.NewDecoder(_reader)
|
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)
|
// 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) {
|
func (d *Json) browse(_path string) ([]interface{}, bool) {
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGet(t *testing.T) {
|
func TestJsonGet(t *testing.T) {
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
raw string
|
raw string
|
||||||
|
@ -23,7 +23,7 @@ func TestGet(t *testing.T) {
|
||||||
reader := bytes.NewBufferString(test.raw)
|
reader := bytes.NewBufferString(test.raw)
|
||||||
|
|
||||||
// try to extract value
|
// try to extract value
|
||||||
err := parser.Parse(reader)
|
err := parser.Read(reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("parse error: %s", err)
|
t.Errorf("parse error: %s", err)
|
||||||
continue
|
continue
|
||||||
|
@ -45,7 +45,7 @@ func TestGet(t *testing.T) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetNotString(t *testing.T) {
|
func TestJsonGetNotString(t *testing.T) {
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
raw string
|
raw string
|
||||||
|
@ -63,7 +63,7 @@ func TestGetNotString(t *testing.T) {
|
||||||
reader := bytes.NewBufferString(test.raw)
|
reader := bytes.NewBufferString(test.raw)
|
||||||
|
|
||||||
// try to extract value
|
// try to extract value
|
||||||
err := parser.Parse(reader)
|
err := parser.Read(reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("parse error: %s", err)
|
t.Errorf("parse error: %s", err)
|
||||||
continue
|
continue
|
||||||
|
@ -80,7 +80,7 @@ func TestGetNotString(t *testing.T) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSetPathExistsAndIsString(t *testing.T) {
|
func TestJsonSetPathExistsAndIsString(t *testing.T) {
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
raw string
|
raw string
|
||||||
|
@ -99,7 +99,7 @@ func TestSetPathExistsAndIsString(t *testing.T) {
|
||||||
reader := bytes.NewBufferString(test.raw)
|
reader := bytes.NewBufferString(test.raw)
|
||||||
|
|
||||||
// try to extract value
|
// try to extract value
|
||||||
err := parser.Parse(reader)
|
err := parser.Read(reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("parse error: %s", err)
|
t.Errorf("parse error: %s", err)
|
||||||
continue
|
continue
|
||||||
|
@ -127,7 +127,7 @@ func TestSetPathExistsAndIsString(t *testing.T) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSetCreatePath(t *testing.T) {
|
func TestJsonSetCreatePath(t *testing.T) {
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
raw string
|
raw string
|
||||||
|
@ -150,7 +150,7 @@ func TestSetCreatePath(t *testing.T) {
|
||||||
reader := bytes.NewBufferString(test.raw)
|
reader := bytes.NewBufferString(test.raw)
|
||||||
|
|
||||||
// try to extract value
|
// try to extract value
|
||||||
err := parser.Parse(reader)
|
err := parser.Read(reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("[%d] parse error: %s", i, err)
|
t.Errorf("[%d] parse error: %s", i, err)
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -37,7 +37,7 @@ func Load(path string) (ConfigurationFormat, error) {
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
// parse
|
// parse
|
||||||
err = confFormat.Parse(file)
|
err = confFormat.Read(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot parse file as '%s' | %s", extension, err)
|
return nil, fmt.Errorf("cannot parse file as '%s' | %s", extension, err)
|
||||||
}
|
}
|
||||||
|
@ -68,6 +68,8 @@ func loadFromExtension(ext string) ConfigurationFormat {
|
||||||
switch ext {
|
switch ext {
|
||||||
case "json":
|
case "json":
|
||||||
return new(Json)
|
return new(Json)
|
||||||
|
case "ini":
|
||||||
|
return new(Ini)
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,6 +52,8 @@ func (d *config) Build(_args string) error {
|
||||||
|
|
||||||
func (d config) Exec(ctx ExecutionContext) ([]byte, error) {
|
func (d config) Exec(ctx ExecutionContext) ([]byte, error) {
|
||||||
|
|
||||||
|
fmt.Printf("path is '%v'\n", d.Path)
|
||||||
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue