From b4097abed28f25e23b38df4fca7fce5538838247 Mon Sep 17 00:00:00 2001 From: jolheiser Date: Wed, 5 Jun 2024 20:11:19 -0500 Subject: [PATCH] feat: refactor for library and add test Signed-off-by: jolheiser --- cfg.go | 94 ++++++++++++++++++++++++++++++++++++++ cfg_test.go | 67 +++++++++++++++++++++++++++ main.go => cmd/cfg/main.go | 49 ++++---------------- go.mod | 3 ++ go.sum | 8 +++- 5 files changed, 180 insertions(+), 41 deletions(-) create mode 100644 cfg.go create mode 100644 cfg_test.go rename main.go => cmd/cfg/main.go (60%) diff --git a/cfg.go b/cfg.go new file mode 100644 index 0000000..c3d9612 --- /dev/null +++ b/cfg.go @@ -0,0 +1,94 @@ +package cfg + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/pelletier/go-toml/v2" + "github.com/philandstuff/dhall-golang/v6" + "github.com/tailscale/hujson" + "go.jolheiser.com/nixfig" + "gopkg.in/yaml.v3" +) + +// Encoding is a type of configuration encoding +type Encoding string + +const ( + JSON Encoding = "json" + JSONC Encoding = "jsonc" + YAML Encoding = "yaml" + TOML Encoding = "toml" + NIX Encoding = "nix" + DHALL Encoding = "dhall" +) + +// Marshal takes data and encodes for an [Encoding] +func (e Encoding) Marshal(v any) ([]byte, error) { + return Marshal(e, v) +} + +// Unmarshal decodes data for an [Encoding] +func (e Encoding) Unmarshal(data []byte, v any) error { + return Unmarshal(e, data, v) +} + +// ParseEncoding parses a string into an [Encoding] +func ParseEncoding(s string) (Encoding, error) { + switch strings.ToLower(s) { + case "json": + return JSON, nil + case "jsonc": + return JSONC, nil + case "yaml": + return YAML, nil + case "toml": + return TOML, nil + case "nix": + return NIX, nil + case "dhall": + return DHALL, nil + default: + return "", fmt.Errorf("unknown encoding %q", s) + } +} + +// Marshal is a top-level helper for encoding data +func Marshal(e Encoding, v any) ([]byte, error) { + switch e { + case JSON, JSONC: + return json.MarshalIndent(v, "", "\t") + case YAML: + return yaml.Marshal(v) + case TOML: + return toml.Marshal(v) + case NIX: + return nixfig.Marshal(v) + default: + return nil, fmt.Errorf("unknown marshal format %q", e) + } +} + +// Unmarshal is a top-level helper for decoding data +func Unmarshal(e Encoding, data []byte, v any) error { + switch e { + case JSON, JSONC: + b, err := hujson.Standardize(data) + if err != nil { + return err + } + return json.Unmarshal(b, v) + case YAML: + return yaml.Unmarshal(data, v) + case TOML: + return toml.Unmarshal(data, v) + case NIX: + return nixfig.Unmarshal(data, v) + case DHALL: + return dhall.Unmarshal(data, v) + default: + return fmt.Errorf("unknown unmarshal format %q", e) + + } +} diff --git a/cfg_test.go b/cfg_test.go new file mode 100644 index 0000000..79323fa --- /dev/null +++ b/cfg_test.go @@ -0,0 +1,67 @@ +package cfg + +import ( + "testing" + + "github.com/matryer/is" + "go.jolheiser.com/nixfig" +) + +type TestData struct { + Foo string + Baz bool + Bux int + Qux TestSubData +} + +type TestSubData struct { + Honk string + Chonk int + Gonk bool +} + +func TestEncoding(t *testing.T) { + assert := is.New(t) + + // Starting with dhall since it can't be encoded + dhall := ` + { + Foo = "bar" + , Baz = True + , Bux = 10 + , Qux = { + Honk = "bonk" + , Chonk = 50 + , Gonk = False + } + } + ` + final := TestData{ + Foo: "bar", + Baz: true, + Bux: 10, + Qux: TestSubData{ + Honk: "bonk", + Chonk: 50, + Gonk: false, + }, + } + + encoders := []Encoding{JSON, JSONC, YAML, TOML} + // Only test nix if it's available + if nixfig.Nix != "" { + encoders = append(encoders, NIX) + } + + var data TestData + err := DHALL.Unmarshal([]byte(dhall), &data) + assert.NoErr(err) // Should be able to unmarshal dhall + + for _, e := range encoders { + out, err := e.Marshal(data) + assert.NoErr(err) // Should be able to marshal + err = e.Unmarshal(out, &data) + assert.NoErr(err) // Should be able to unmarshal + } + assert.Equal(data, final) // Final structs should be equal +} diff --git a/main.go b/cmd/cfg/main.go similarity index 60% rename from main.go rename to cmd/cfg/main.go index 213bddb..7162595 100644 --- a/main.go +++ b/cmd/cfg/main.go @@ -1,7 +1,6 @@ package main import ( - "encoding/json" "errors" "flag" "fmt" @@ -9,11 +8,7 @@ import ( "path/filepath" "strings" - "github.com/pelletier/go-toml/v2" - "github.com/philandstuff/dhall-golang/v6" - "github.com/tailscale/hujson" - "go.jolheiser.com/nixfig" - "gopkg.in/yaml.v3" + "go.jolheiser.com/cfg" ) func maine() error { @@ -22,45 +17,21 @@ func maine() error { fs := flag.NewFlagSet("cfg", flag.ExitOnError) fromFunc := func(s string) error { - switch strings.ToLower(s) { - case "json", "jsonc": - unmarshal = func(b []byte, a any) error { - b, err := hujson.Standardize(b) - if err != nil { - return err - } - return json.Unmarshal(b, a) - } - case "yaml": - unmarshal = yaml.Unmarshal - case "toml": - unmarshal = toml.Unmarshal - case "dhall": - unmarshal = dhall.Unmarshal - case "nix": - unmarshal = nixfig.Unmarshal - default: - return fmt.Errorf("unknown format %q", s) + e, err := cfg.ParseEncoding(s) + if err != nil { + return err } + unmarshal = e.Unmarshal return nil } fs.Func("from", "The format to convert from", fromFunc) fs.Func("f", "--from", fromFunc) toFunc := func(s string) error { - switch strings.ToLower(s) { - case "json", "jsonc": - marshal = func(a any) ([]byte, error) { - return json.MarshalIndent(a, "", "\t") - } - case "yaml": - marshal = yaml.Marshal - case "toml": - marshal = toml.Marshal - case "nix": - marshal = nixfig.Marshal - default: - return fmt.Errorf("unknown format %q", s) + e, err := cfg.ParseEncoding(s) + if err != nil { + return err } + marshal = e.Marshal return nil } fs.Func("to", "The format to convert to", toFunc) @@ -95,7 +66,7 @@ func maine() error { } } } else if marshal == nil { - marshal = json.Marshal + marshal = cfg.JSON.Marshal } var data any diff --git a/go.mod b/go.mod index 15cf553..bedd44d 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module go.jolheiser.com/cfg go 1.22.3 require ( + github.com/matryer/is v1.4.1 github.com/pelletier/go-toml/v2 v2.2.2 github.com/philandstuff/dhall-golang/v6 v6.0.2 github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a @@ -13,4 +14,6 @@ require ( require ( github.com/fxamacker/cbor/v2 v2.2.1-0.20200511212021-28e39be4a84f // indirect github.com/x448/float16 v0.8.4 // indirect + golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab // indirect + golang.org/x/text v0.3.7 // indirect ) diff --git a/go.sum b/go.sum index 5cc0287..2a702be 100644 --- a/go.sum +++ b/go.sum @@ -25,6 +25,8 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/leanovate/gopter v0.2.5-0.20190402064358-634a59d12406 h1:+OUpk+IVvmKU0jivOVFGtOzA6U5AWFs8HE4DRzWLOUE= github.com/leanovate/gopter v0.2.5-0.20190402064358-634a59d12406/go.mod h1:gNcbPWNEWRe4lm+bycKqxUYoH5uoVje5SkOJ3uoLer8= +github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ= +github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -64,10 +66,12 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=