diff --git a/README.md b/README.md index 91ba0c64..d7412704 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ go get -u github.com/knadh/koanf/parsers/toml - [Reading raw bytes](#reading-raw-bytes) - [Reading from maps and structs](#reading-from-nested-maps) - [Unmarshalling and marshalling](#unmarshalling-and-marshalling) -- [Order of merge and key case senstivity](#order-of-merge-and-key-case-sensitivity) +- [Order of merge and key case sensitivity](#order-of-merge-and-key-case-sensitivity) - [Custom Providers and Parsers](#custom-providers-and-parsers) - [Custom merge strategies](#custom-merge-strategies) - [List of installable Providers and Parsers](#api) diff --git a/parsers/kdl/go.mod b/parsers/kdl/go.mod new file mode 100644 index 00000000..143f85b9 --- /dev/null +++ b/parsers/kdl/go.mod @@ -0,0 +1,13 @@ +module github.com/knadh/koanf/parsers/kdl + +go 1.18 + +require ( + github.com/pmezard/go-difflib v1.0.0 // i github.com/sblinch/kdl-go v0.0.0-20231112203113-9fa9a505b79a // indirect + github.com/stretchr/testify v1.8.1 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/sblinch/kdl-go v0.0.0-20231112203113-9fa9a505b79a // indirect +) diff --git a/parsers/kdl/go.sum b/parsers/kdl/go.sum new file mode 100644 index 00000000..8e31e657 --- /dev/null +++ b/parsers/kdl/go.sum @@ -0,0 +1,19 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sblinch/kdl-go v0.0.0-20231112203113-9fa9a505b79a h1:q2ywS3Q4C8vJKm0gBmUmub0yyLs6VAYivOKAvfhurJ4= +github.com/sblinch/kdl-go v0.0.0-20231112203113-9fa9a505b79a/go.mod h1:b3oNGuAKOQzhsCKmuLc/urEOPzgHj6fB8vl8bwTBh28= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/parsers/kdl/kdl.go b/parsers/kdl/kdl.go new file mode 100644 index 00000000..0f3ef398 --- /dev/null +++ b/parsers/kdl/kdl.go @@ -0,0 +1,26 @@ +// Package kdl implements a koanf.Parser that parses KDL bytes as conf maps. +package kdl + +import ( + kdl "github.com/sblinch/kdl-go" +) + +// KDL implements a KDL parser. +type KDL struct{} + +// Parser returns a KDL Parser. +func Parser() *KDL { + return &KDL{} +} + +// Unmarshal parses the given KDL bytes. +func (p *KDL) Unmarshal(b []byte) (map[string]interface{}, error) { + var o map[string]interface{} + err := kdl.Unmarshal(b, &o) + return o, err +} + +// Marshal marshals the given config map to KDL bytes. +func (p *KDL) Marshal(o map[string]interface{}) ([]byte, error) { + return kdl.Marshal(o) +} diff --git a/parsers/kdl/kdl_test.go b/parsers/kdl/kdl_test.go new file mode 100644 index 00000000..0419631a --- /dev/null +++ b/parsers/kdl/kdl_test.go @@ -0,0 +1,189 @@ +package kdl + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestKDL_Unmarshal(t *testing.T) { + testCases := []struct { + name string + input []byte + keys []string + values []interface{} + isErr bool + }{ + { + name: "Empty KDL", + input: []byte(``), + }, + { + name: "Valid KDL", + input: []byte(`key "val" ; name "test" ; number 2.0`), + keys: []string{"key", "name", "number"}, + values: []interface{}{"val", "test", 2.0}, + }, + { + name: "Invalid KDL - syntax error", + input: []byte(`node1 key="val`), + isErr: true, + }, + { + name: "Complex KDL - Different types", + input: []byte(` + array 1.0 2.0 3.0 + boolean true + color "gold" + "null" null + number 123 + object a="b" c="d" e=2.7 f=true + string "Hello World" + `), + keys: []string{"array", "boolean", "color", "null", "number", "object", "string"}, + values: []interface{}{[]interface{}{1.0, 2.0, 3.0}, + true, + "gold", + nil, + int64(123), + map[string]interface{}{"a": "b", "c": "d", "e": 2.7, "f": true}, + "Hello World"}, + }, + { + name: "Invalid KDL - missing value", + input: []byte(`node1 boolean=`), + isErr: true, + }, + { + name: "Complex KDL - Nested map", + input: []byte(`key "value" + "1" "skipped" + map key="skipped" key="value" + nested_map { + map key="value" 17 { + list "item1" "item2" "item3" + mixup "y"=1 2 3 4 + first "first"=1 2 3 4 + child "test"=1 2 3 4 { "y" 5 ; "d" 6 ; } + } + } + `), + keys: []string{"key", "1", "map", "nested_map"}, + values: []interface{}{ + "value", + "skipped", + map[string]interface{}{ + "key": "value", + }, + map[string]interface{}{ + "map": map[string]interface{}{ + "0": int64(17), + "key": "value", + "list": []interface{}{ + "item1", + "item2", + "item3", + }, + "mixup": map[string]interface{}{ + "y": int64(1), + "0": int64(2), + "1": int64(3), + "2": int64(4), + }, + "first": map[string]interface{}{ + "first": int64(1), + "0": int64(2), + "1": int64(3), + "2": int64(4), + }, + "child": map[string]interface{}{ + "test": int64(1), + "0": int64(2), + "1": int64(3), + "2": int64(4), + "y": int64(5), + "d": int64(6), + }, + }, + }, + }, + }, + } + + k := Parser() // Assuming Parser() is implemented for KDL + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + out, err := k.Unmarshal(tc.input) + if tc.isErr { + assert.NotNil(t, err) + } else { + assert.Nil(t, err) + for i, k := range tc.keys { + v := out[k] + assert.Equal(t, tc.values[i], v) + } + } + }) + } +} + +func TestKDL_Marshal(t *testing.T) { + testCases := []struct { + name string + input map[string]interface{} + stringifiedOutput string + isErr bool + }{ + { + name: "Empty KDL", + input: map[string]interface{}{}, + stringifiedOutput: ``, + }, + { + name: "Valid KDL", + input: map[string]interface{}{ + "key": "val", + "name": "test", + "number": 2.0, + }, + stringifiedOutput: `key "val" +name "test" +number 2.0 +`, + }, + { + name: "Complex KDL - Different types", + input: map[string]interface{}{ + "null": nil, + "boolean": true, + "color": "gold", + "number": int64(123), + "string": "Hello World", + // "array": []interface{}{1, 2, 3, 4, 5}, // https://github.com/sblinch/kdl-go/issues/3 + "object": map[string]interface{}{"a": "b", "c": "d"}, + }, + stringifiedOutput: `boolean true +color "gold" +number 123 +string "Hello World" +object a="b" c="d" +null null +`, + }, + } + + k := Parser() // Assuming Parser() is implemented for KDL + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + out, err := k.Marshal(tc.input) + if tc.isErr { + assert.NotNil(t, err) + } else { + assert.Nil(t, err) + assert.Equal(t, tc.stringifiedOutput, string(out)) + } + }) + } +}