Skip to content

Commit

Permalink
add toml and yaml loader
Browse files Browse the repository at this point in the history
  • Loading branch information
fzerorubigd committed Jul 25, 2015
1 parent bc38f9c commit 291e3c2
Show file tree
Hide file tree
Showing 7 changed files with 255 additions and 8 deletions.
1 change: 0 additions & 1 deletion file_layer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,5 @@ func TestFileLayer(t *testing.T) {
err = o.AddLayer(fl)
So(err, ShouldNotBeNil)
})

})
}
36 changes: 31 additions & 5 deletions onion.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func (o Onion) Get(key string) (interface{}, bool) {
path := strings.Split(strings.ToLower(key), o.GetDelimiter())
for i := len(o.ll) - 1; i >= 0; i-- {
l := o.layers[o.ll[i]]
res, found := searchMap(path, l)
res, found := searchStringMap(path, l)
if found {
return res, found
}
Expand All @@ -69,7 +69,12 @@ func (o Onion) Get(key string) (interface{}, bool) {
return nil, false
}

func searchMap(path []string, m map[string]interface{}) (interface{}, bool) {
// The folowing two function is identical. but converting between map[string] and
// map[interface{}] is not easy, and there is no Generic, so I decide to create
// two almost identical function instead of writing a convertor each time
// Some of the loaders like yaml, load inner keys in map[interface{}]interface{}
// some othr like json do it in map[string]interface{} so we should suppport both
func searchStringMap(path []string, m map[string]interface{}) (interface{}, bool) {
v, ok := m[path[0]]
if !ok {
return nil, false
Expand All @@ -81,7 +86,28 @@ func searchMap(path []string, m map[string]interface{}) (interface{}, bool) {

switch v.(type) {
case map[string]interface{}:
return searchMap(path[1:], v.(map[string]interface{}))
return searchStringMap(path[1:], v.(map[string]interface{}))
case map[interface{}]interface{}:
return searchInterfaceMap(path[1:], v.(map[interface{}]interface{}))
}
return nil, false
}

func searchInterfaceMap(path []string, m map[interface{}]interface{}) (interface{}, bool) {
v, ok := m[path[0]]
if !ok {
return nil, false
}

if len(path) == 1 {
return v, true
}

switch v.(type) {
case map[string]interface{}:
return searchStringMap(path[1:], v.(map[string]interface{}))
case map[interface{}]interface{}:
return searchInterfaceMap(path[1:], v.(map[interface{}]interface{}))
}
return nil, false
}
Expand Down Expand Up @@ -177,8 +203,8 @@ func (o Onion) GetBool(key string, def bool) bool {
}

// GetStruct fill an structure base on the config nested set
func (o Onion) GetStruct(s interface{}) {
iterateConfig(o, s, "")
func (o Onion) GetStruct(prefix string, s interface{}) {
iterateConfig(o, s, prefix)
}

func iterateConfig(o Onion, c interface{}, op string) {
Expand Down
21 changes: 19 additions & 2 deletions onion_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@ func TestOnion(t *testing.T) {
lm := &layerMock{}
lm.data = getMap("key", 42, "universe", "answer", true, float32(20.88), float64(200), int64(100))
lm.data["nested"] = getMap("n", "a", 99, true)
t1 := make(map[interface{}]interface{})
t1["str1"] = 1
t1["str2"] = "hi"
t1["nested"] = t1
t1["other"] = struct{}{}
t1["what"] = getMap("n", "a")
lm.data["yes"] = t1

o := New()
So(o.AddLayer(lm), ShouldBeNil)
Expand Down Expand Up @@ -94,6 +101,12 @@ func TestOnion(t *testing.T) {
So(o.GetInt64("nested.n1", 0), ShouldEqual, 99)
So(o.GetInt("nested.n1", 0), ShouldEqual, 99)
So(o.GetBool("nested.n2", false), ShouldEqual, true)

So(o.GetInt("yes.str1", 0), ShouldEqual, 1)
So(o.GetString("yes.str2", ""), ShouldEqual, "hi")

So(o.GetString("yes.nested.str2", ""), ShouldEqual, "hi")
So(o.GetString("yes.what.n0", ""), ShouldEqual, "a")
})

Convey("Get nested default variable", func() {
Expand All @@ -102,6 +115,10 @@ func TestOnion(t *testing.T) {
So(o.GetInt64("nested.n11", 0), ShouldEqual, 0)
So(o.GetInt("nested.n11", 0), ShouldEqual, 0)
So(o.GetBool("nested.n21", false), ShouldEqual, false)

So(o.GetString("yes.nested.no", "def"), ShouldEqual, "def")
So(o.GetString("yes.nested.other.key", "def"), ShouldEqual, "def")
So(o.GetString("yes.what.no", "def"), ShouldEqual, "def")
})

Convey("change delimiter", func() {
Expand All @@ -126,7 +143,7 @@ func TestOnion(t *testing.T) {

Convey("delegate to structure", func() {
s := structExample{}
o.GetStruct(&s)
o.GetStruct("", &s)
ex := structExample{
Key0: 42,
Universe: "universe",
Expand All @@ -150,7 +167,7 @@ func TestOnion(t *testing.T) {
}
So(reflect.DeepEqual(s, ex), ShouldBeTrue)
var tmp []string
o.GetStruct(tmp)
o.GetStruct("", tmp)
So(tmp, ShouldBeNil)
})

Expand Down
32 changes: 32 additions & 0 deletions tomlloader/loader.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package tomlloader

import (
"io"
"io/ioutil"

"github.com/BurntSushi/toml"

"github.com/fzerorubigd/onion"
)

type tomlLoader struct {
}

func (tl tomlLoader) SupportedEXT() []string {
return []string{".toml"}
}

func (tl tomlLoader) Convert(r io.Reader) (map[string]interface{}, error) {
data, _ := ioutil.ReadAll(r)
ret := make(map[string]interface{})
err := toml.Unmarshal(data, &ret)
if err != nil {
return nil, err
}

return ret, nil
}

func init() {
onion.RegisterLoader(&tomlLoader{})
}
68 changes: 68 additions & 0 deletions tomlloader/loader_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package tomlloader

import (
"bytes"
"io"
"os"
"testing"

. "github.com/fzerorubigd/onion"
. "github.com/smartystreets/goconvey/convey"
)

func TestYamlLoader(t *testing.T) {
Convey("Load a yaml structure into a json", t, func() {
// Make sure there is two ile available in the tmp file system
tmp := os.TempDir()
dir := tmp + "/onion_f"
So(os.MkdirAll(dir, 0744), ShouldBeNil)
path := dir + "/test.toml"
path2 := dir + "/invalid.toml"

buf := bytes.NewBufferString(`
str = "string_data"
bool = true
integer = 10
[nested]
key1 = "string"
key2 = 100
`)
bufInvalid := bytes.NewBufferString(`invalid toml file`)
f, err := os.Create(path)
So(err, ShouldBeNil)
_, err = io.Copy(f, buf)
So(err, ShouldBeNil)

f2, err := os.Create(path2)
So(err, ShouldBeNil)
_, err = io.Copy(f2, bufInvalid)
So(err, ShouldBeNil)

defer func() {
_ = os.Remove(path)
}()
So(f.Close(), ShouldBeNil)
So(f2.Close(), ShouldBeNil)

Convey("Check if the file is loaded correctly ", func() {
fl := NewFileLayer(path)
o := New()
err := o.AddLayer(fl)
So(err, ShouldBeNil)
So(o.GetString("str", ""), ShouldEqual, "string_data")
So(o.GetString("nested.key1", ""), ShouldEqual, "string")
So(o.GetInt("nested.key2", 0), ShouldEqual, 100)
So(o.GetBool("bool", false), ShouldBeTrue)

a := New() // Just for test load again
So(a.AddLayer(fl), ShouldBeNil)
})

Convey("Check for the invalid file content", func() {
fl := NewFileLayer(path2)
o := New()
err = o.AddLayer(fl)
So(err, ShouldNotBeNil)
})
})
}
32 changes: 32 additions & 0 deletions yamlloader/loader.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package yamlloader

import (
"io"
"io/ioutil"

"gopkg.in/yaml.v2"

"github.com/fzerorubigd/onion"
)

type yamlLoader struct {
}

func (yl yamlLoader) SupportedEXT() []string {
return []string{".yaml", ".yml"}
}

func (yl yamlLoader) Convert(r io.Reader) (map[string]interface{}, error) {
data, _ := ioutil.ReadAll(r)
ret := make(map[string]interface{})
err := yaml.Unmarshal(data, &ret)
if err != nil {
return nil, err
}

return ret, nil
}

func init() {
onion.RegisterLoader(&yamlLoader{})
}
73 changes: 73 additions & 0 deletions yamlloader/loader_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package yamlloader

import (
"bytes"
"io"
"os"
"testing"

. "github.com/fzerorubigd/onion"
. "github.com/smartystreets/goconvey/convey"
)

func TestYamlLoader(t *testing.T) {
Convey("Load a yaml structure into a json", t, func() {
// Make sure there is two ile available in the tmp file system
tmp := os.TempDir()
dir := tmp + "/onion_f"
So(os.MkdirAll(dir, 0744), ShouldBeNil)
path := dir + "/test.yaml"
path2 := dir + "/invalid.yml"

buf := bytes.NewBufferString(`---
str: "string_data"
bool: true
integer: 10
nested:
key1: "string"
key2: 100
`)
bufInvalid := bytes.NewBufferString(`---
str: - inv
lid
s
ALALA`)
f, err := os.Create(path)
So(err, ShouldBeNil)
_, err = io.Copy(f, buf)
So(err, ShouldBeNil)

f2, err := os.Create(path2)
So(err, ShouldBeNil)
_, err = io.Copy(f2, bufInvalid)
So(err, ShouldBeNil)

defer func() {
_ = os.Remove(path)
_ = os.Remove(path2)
}()
So(f.Close(), ShouldBeNil)
So(f2.Close(), ShouldBeNil)

Convey("Check if the file is loaded correctly ", func() {
fl := NewFileLayer(path)
o := New()
err := o.AddLayer(fl)
So(err, ShouldBeNil)
So(o.GetString("str", ""), ShouldEqual, "string_data")
So(o.GetString("nested.key1", ""), ShouldEqual, "string")
So(o.GetInt("nested.key2", 0), ShouldEqual, 100)
So(o.GetBool("bool", false), ShouldBeTrue)

a := New() // Just for test load again
So(a.AddLayer(fl), ShouldBeNil)
})

Convey("Check for the invalid file content", func() {
fl := NewFileLayer(path2)
o := New()
err = o.AddLayer(fl)
So(err, ShouldNotBeNil)
})
})
}

0 comments on commit 291e3c2

Please sign in to comment.