Repository: TomWright/dasel
Pull request #2 (2020-09-27 21:57:32)

Add toml support


Patch:
diff --git a/README.md b/README.md
index 6551d3e..9a01f76 100644
--- a/README.md
+++ b/README.md
@@ -121,8 +121,9 @@ $ dasel put string -f deployment.yaml -s "spec.template.spec.containers.(name=au
 ## Supported data types
 Dasel attempts to find the correct parser for the given file type, but if that fails you can choose which parser to use with the `-p` or `--parser` flag. 
 
-- YAML - `-p yaml`
 - JSON - `-p json`
+- TOML - `-p toml`
+- YAML - `-p yaml`
 
 ## Selectors
 
diff --git a/go.mod b/go.mod
index b9d00ef..0e3ab8b 100644
--- a/go.mod
+++ b/go.mod
@@ -3,7 +3,7 @@ module github.com/tomwright/dasel
 go 1.15
 
 require (
-	github.com/google/go-cmp v0.2.0
+	github.com/pelletier/go-toml v1.8.1
 	github.com/spf13/cobra v1.0.0
 	gopkg.in/yaml.v2 v2.2.2
 )
diff --git a/go.sum b/go.sum
index f574888..4ad6e91 100644
--- a/go.sum
+++ b/go.sum
@@ -14,6 +14,7 @@ github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee
 github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
 github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
 github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
+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/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
 github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
@@ -58,6 +59,8 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh
 github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
+github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM=
+github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
diff --git a/internal/storage/parser.go b/internal/storage/parser.go
index ef6fa78..dc841c4 100644
--- a/internal/storage/parser.go
+++ b/internal/storage/parser.go
@@ -33,6 +33,8 @@ func NewParserFromFilename(filename string) (Parser, error) {
 	switch ext {
 	case ".yaml", ".yml":
 		return &YAMLParser{}, nil
+	case ".toml":
+		return &TOMLParser{}, nil
 	case ".json":
 		return &JSONParser{}, nil
 	default:
@@ -47,6 +49,8 @@ func NewParserFromString(parser string) (Parser, error) {
 		return &YAMLParser{}, nil
 	case "json":
 		return &JSONParser{}, nil
+	case "toml":
+		return &TOMLParser{}, nil
 	default:
 		return nil, &UnknownParserErr{Parser: parser}
 	}
diff --git a/internal/storage/toml.go b/internal/storage/toml.go
new file mode 100644
index 0000000..11d8d02
--- /dev/null
+++ b/internal/storage/toml.go
@@ -0,0 +1,24 @@
+package storage
+
+import (
+	"fmt"
+	"github.com/pelletier/go-toml"
+)
+
+// TOMLParser is a Parser implementation to handle yaml files.
+type TOMLParser struct {
+}
+
+// FromBytes returns some Data that is represented by the given bytes.
+func (p *TOMLParser) FromBytes(byteData []byte) (interface{}, error) {
+	var data interface{}
+	if err := toml.Unmarshal(byteData, &data); err != nil {
+		return data, fmt.Errorf("could not unmarshal config data: %w", err)
+	}
+	return data, nil
+}
+
+// ToBytes returns a slice of bytes that represents the given value.
+func (p *TOMLParser) ToBytes(value interface{}) ([]byte, error) {
+	return toml.Marshal(value)
+}


Test patch:
diff --git a/internal/command/put_internal_test.go b/internal/command/put_internal_test.go
index 1bf3ee2..1ef0453 100644
--- a/internal/command/put_internal_test.go
+++ b/internal/command/put_internal_test.go
@@ -575,4 +575,181 @@ numbers:
 `, nil))
 
 	})
+
+	t.Run("TOML", func(t *testing.T) {
+		t.Run("SingleProperty", putTest(`
+id = "1111"
+
+[details]
+  age = 27
+  name = "Tom"
+`, "toml", ".id", "2222", "string", `
+id = "2222"
+
+[details]
+  age = 27
+  name = "Tom"
+`, nil))
+
+		t.Run("ObjectPropertyString", putTest(`
+id = "1111"
+
+[details]
+  age = 27
+  name = "Tom"
+`, "toml", ".details.name", "Frank", "string", `
+id = "1111"
+
+[details]
+  age = 27
+  name = "Frank"
+`, nil))
+
+		t.Run("ObjectPropertyInt", putTest(`
+id = "1111"
+
+[details]
+  age = 20
+  name = "Tom"
+`, "toml", ".details.age", "27", "int", `
+id = "1111"
+
+[details]
+  age = 27
+  name = "Tom"
+`, nil))
+
+		t.Run("IndexString", putTest(`
+numbers = ["one", "two", "three"]
+`, "toml", ".numbers.[1]", "four", "string", `
+numbers = ["one", "four", "three"]
+`, nil))
+
+		t.Run("DynamicString", putTest(`
+numbers = ["one", "two", "three"]
+`, "toml", ".numbers.(value=three)", "four", "string", `
+numbers = ["one", "two", "four"]
+`, nil))
+
+		t.Run("DynamicInt", putTest(`
+numbers = [1, 2, 3]
+`, "toml", ".numbers.(value=3)", "4", "int", `
+numbers = [1, 2, 4]
+`, nil))
+
+		t.Run("IndexInt", putTest(`
+numbers = [1, 2, 3]
+`, "toml", ".numbers.[1]", "4", "int", `
+numbers = [1, 4, 3]
+`, nil))
+
+		t.Run("DynamicString", putTest(`
+[[numbers]]
+  number = "one"
+  rank = 1
+
+[[numbers]]
+  number = "two"
+  rank = 2
+
+[[numbers]]
+  number = "three"
+  rank = 3
+`, "toml", ".numbers.(number=two).number", "four", "string", `
+[[numbers]]
+  number = "one"
+  rank = 1
+
+[[numbers]]
+  number = "four"
+  rank = 2
+
+[[numbers]]
+  number = "three"
+  rank = 3
+`, nil))
+
+		t.Run("DynamicInt", putTest(`
+[[numbers]]
+  number = "one"
+  rank = 1
+
+[[numbers]]
+  number = "two"
+  rank = 2
+
+[[numbers]]
+  number = "three"
+  rank = 3
+`, "toml", ".numbers.(rank=2).rank", "4", "int", `
+[[numbers]]
+  number = "one"
+  rank = 1
+
+[[numbers]]
+  number = "two"
+  rank = 4
+
+[[numbers]]
+  number = "three"
+  rank = 3
+`, nil))
+
+		t.Run("OverwriteObject", putObjectTest(`
+[[numbers]]
+  number = "one"
+  rank = 1
+
+[[numbers]]
+  number = "two"
+  rank = 2
+
+[[numbers]]
+  number = "three"
+  rank = 3
+`, "toml", ".numbers.[0]", []string{"number=five", "rank=5"}, []string{"string", "int"}, `
+[[numbers]]
+  number = "five"
+  rank = 5
+
+[[numbers]]
+  number = "two"
+  rank = 2
+
+[[numbers]]
+  number = "three"
+  rank = 3
+`, nil))
+
+		t.Run("AppendObject", putObjectTest(`
+[[numbers]]
+  number = "one"
+  rank = 1
+
+[[numbers]]
+  number = "two"
+  rank = 2
+
+[[numbers]]
+  number = "three"
+  rank = 3
+`, "toml", ".numbers.[]", []string{"number=five", "rank=5"}, []string{"string", "int"}, `
+[[numbers]]
+  number = "one"
+  rank = 1
+
+[[numbers]]
+  number = "two"
+  rank = 2
+
+[[numbers]]
+  number = "three"
+  rank = 3
+
+[[numbers]]
+  number = "five"
+  rank = 5
+`, nil))
+
+	})
 }
diff --git a/internal/command/select_internal_test.go b/internal/command/select_internal_test.go
index 71ce997..5b41e91 100644
--- a/internal/command/select_internal_test.go
+++ b/internal/command/select_internal_test.go
@@ -47,6 +47,23 @@ details:
     postcode: YYY YYY
 `
 
+var tomlData = `id = "1111"
+[details]
+  name = "Tom"
+  age = 27
+  [[details.addresses]]
+    street =  "101 Some Street"
+    town = "Some Town"
+    county = "Some County"
+    postcode = "XXX XXX"
+    primary = true
+  [[details.addresses]]
+    street = "34 Another Street"
+    town = "Another Town"
+    county = "Another County"
+    postcode = "YYY YYY"
+`
+
 func selectTest(in string, parser string, selector string, out string, expErr error) func(t *testing.T) {
 	return func(t *testing.T) {
 		outputBuffer := bytes.NewBuffer([]byte{})
@@ -97,4 +114,5 @@ func selectTestForParser(parser string, data string) func(t *testing.T) {
 func TestSelect(t *testing.T) {
 	t.Run("JSON", selectTestForParser("json", jsonData))
 	t.Run("YAML", selectTestForParser("yaml", yamlData))
+	t.Run("TOML", selectTestForParser("toml", tomlData))
 }
diff --git a/internal/storage/parser_test.go b/internal/storage/parser_test.go
index 7cb5d5e..9d9a94c 100644
--- a/internal/storage/parser_test.go
+++ b/internal/storage/parser_test.go
@@ -19,6 +19,7 @@ func TestNewParserFromString(t *testing.T) {
 	}{
 		{In: "json", Out: &storage.JSONParser{}},
 		{In: "yaml", Out: &storage.YAMLParser{}},
+		{In: "toml", Out: &storage.TOMLParser{}},
 		{In: "bad", Out: nil, Err: &storage.UnknownParserErr{Parser: "bad"}},
 	}
 
@@ -54,6 +55,7 @@ func TestNewParserFromFilename(t *testing.T) {
 		{In: "a.json", Out: &storage.JSONParser{}},
 		{In: "a.yaml", Out: &storage.YAMLParser{}},
 		{In: "a.yml", Out: &storage.YAMLParser{}},
+		{In: "a.toml", Out: &storage.TOMLParser{}},
 		{In: "a.txt", Out: nil, Err: &storage.UnknownParserErr{Parser: ".txt"}},
 	}
 
diff --git a/internal/storage/toml_test.go b/internal/storage/toml_test.go
new file mode 100644
index 0000000..e8f82c8
--- /dev/null
+++ b/internal/storage/toml_test.go
@@ -0,0 +1,41 @@
+package storage_test
+
+import (
+	"github.com/tomwright/dasel/internal/storage"
+	"reflect"
+	"testing"
+)
+
+var tomlBytes = []byte(`names = ["John", "Frank"]
+
+[person]
+  name = "Tom"
+`)
+var tomlMap = map[string]interface{}{
+	"person": map[string]interface{}{
+		"name": "Tom",
+	},
+	"names": []interface{}{"John", "Frank"},
+}
+
+func TestTOMLParser_FromBytes(t *testing.T) {
+	got, err := (&storage.TOMLParser{}).FromBytes(tomlBytes)
+	if err != nil {
+		t.Errorf("unexpected error: %s", err)
+		return
+	}
+	if !reflect.DeepEqual(tomlMap, got) {
+		t.Errorf("expected %v, got %v", tomlMap, got)
+	}
+}
+
+func TestTOMLParser_ToBytes(t *testing.T) {
+	got, err := (&storage.TOMLParser{}).ToBytes(tomlMap)
+	if err != nil {
+		t.Errorf("unexpected error: %s", err)
+		return
+	}
+	if string(tomlBytes) != string(got) {
+		t.Errorf("expected:\n%s\ngot:\n%s", tomlBytes, got)
+	}
+}