feat: ffdhall

Signed-off-by: jolheiser <john.olheiser@gmail.com>
main
jolheiser 2024-06-03 12:19:40 -05:00
commit 1bcb2c430f
Signed by: jolheiser
GPG Key ID: B853ADA5DA7BBF7A
9 changed files with 267 additions and 0 deletions

64
ff_map.go 100644
View File

@ -0,0 +1,64 @@
// This is taken from https://github.com/peterbourgon/ff/blob/e267c41b1d149b5151fb637d65f53b6e833448fd/internal/traverse_map.go
package ffdhall
import (
"encoding/json"
"fmt"
"strconv"
)
// traverseMap recursively walks the given map, calling set for each value. If the
// value is a slice, set is called for each element of the slice. The keys of
// nested maps are joined with the given delimiter.
func traverseMap(m map[string]any, delimiter string, set func(name, value string) error) error {
return traverse("", m, delimiter, set)
}
func traverse(key string, val any, delimiter string, set func(name, value string) error) error {
switch v := val.(type) {
case string:
return set(key, v)
case json.Number:
return set(key, v.String())
case uint64:
return set(key, strconv.FormatUint(v, 10))
case int:
return set(key, strconv.Itoa(v))
case int64:
return set(key, strconv.FormatInt(v, 10))
case float64:
return set(key, strconv.FormatFloat(v, 'g', -1, 64))
case bool:
return set(key, strconv.FormatBool(v))
case nil:
return set(key, "")
case []any:
for _, v := range v {
if err := traverse(key, v, delimiter, set); err != nil {
return err
}
}
case map[string]any:
for k, v := range v {
if key != "" {
k = key + delimiter + k
}
if err := traverse(k, v, delimiter, set); err != nil {
return err
}
}
case map[any]any:
for k, v := range v {
ks := fmt.Sprint(k)
if key != "" {
ks = key + delimiter + ks
}
if err := traverse(ks, v, delimiter, set); err != nil {
return err
}
}
default:
return fmt.Errorf("couldn't convert %q (type %T) to string", val, val)
}
return nil
}

72
ffdhall.go 100644
View File

@ -0,0 +1,72 @@
package ffdhall
import (
"errors"
"fmt"
"io"
"strings"
"github.com/philandstuff/dhall-golang/v6"
)
// DhallParser is a helper function that uses a default DhallParseConfig.
func DhallParser(r io.Reader, set func(name, value string) error) error {
return (&DhallParseConfig{}).Parse(r, set)
}
// DhallParseConfig collects parameters for the Dhall config file parser.
type DhallParseConfig struct {
// Delimiter is used when concatenating nested node keys into a flag name.
// The default delimiter is ".".
Delimiter string
}
// Parse a Dhall document from the provided io.Reader, using the provided set
// function to set flag values. Flag names are derived from the node names and
// their key/value pairs.
func (pc *DhallParseConfig) Parse(r io.Reader, set func(name, value string) error) error {
if pc.Delimiter == "" {
pc.Delimiter = "."
}
data, err := io.ReadAll(r)
if err != nil {
return err
}
// Compatibility with other parsers
if strings.TrimSpace(string(data)) == "" {
return nil
}
var m interface{}
if err := dhall.Unmarshal(data, &m); err != nil {
return DhallParseError{Inner: err}
}
mm, ok := m.(map[string]interface{})
if !ok {
return errors.New("could not unmarshal to map[string]interface{}")
}
if err := traverseMap(mm, pc.Delimiter, set); err != nil {
return DhallParseError{Inner: err}
}
return nil
}
// DhallParseError wraps all errors originating from the DhallParser.
type DhallParseError struct {
Inner error
}
// Error implenents the error interface.
func (e DhallParseError) Error() string {
return fmt.Sprintf("could not parse Dhall config: %v", e.Inner)
}
// Unwrap implements the errors.Wrapper interface, allowing errors.Is and
// errors.As to work with DhallParseErrors.
func (e DhallParseError) Unwrap() error {
return e.Inner
}

56
ffdhall_test.go 100644
View File

@ -0,0 +1,56 @@
package ffdhall
import (
"fmt"
"testing"
"time"
"github.com/peterbourgon/ff/v3"
"github.com/peterbourgon/ff/v3/fftest"
)
func TestDhallParser(t *testing.T) {
t.Parallel()
for _, testcase := range []struct {
name string
args []string
file string
want fftest.Vars
}{
{
name: "empty input",
args: []string{},
file: "testdata/empty.dhall",
want: fftest.Vars{},
},
{
name: "basic KV pairs",
args: []string{},
file: "testdata/basic.dhall",
want: fftest.Vars{S: "s", I: 10, B: true, D: 5 * time.Second},
},
{
name: "value arrays",
args: []string{},
file: "testdata/value_arrays.dhall",
want: fftest.Vars{S: "bb", I: 12, B: true, D: 5 * time.Second, X: []string{"a", "B", "👍"}},
},
{
name: "bad Dhall file",
args: []string{},
file: "testdata/bad.dhall",
want: fftest.Vars{WantParseErrorString: "no match found"},
},
} {
t.Run(testcase.name, func(t *testing.T) {
fs, vars := fftest.Pair()
vars.ParseError = ff.Parse(fs, testcase.args,
ff.WithConfigFile(testcase.file),
ff.WithConfigFileParser(DhallParser),
)
fmt.Println(vars.ParseError)
fftest.Compare(t, &testcase.want, vars)
})
}
}

13
go.mod 100644
View File

@ -0,0 +1,13 @@
module go.jolheiser.com/ffdhall
go 1.22.3
require (
github.com/peterbourgon/ff/v3 v3.4.0
github.com/philandstuff/dhall-golang/v6 v6.0.2
)
require (
github.com/fxamacker/cbor/v2 v2.2.1-0.20200511212021-28e39be4a84f // indirect
github.com/x448/float16 v0.8.4 // indirect
)

53
go.sum 100644
View File

@ -0,0 +1,53 @@
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fxamacker/cbor/v2 v2.2.1-0.20200511212021-28e39be4a84f h1:lvGFo/tDOSQ4FKu0d2694s8XyOfAL6FLR9DCD5BIUW4=
github.com/fxamacker/cbor/v2 v2.2.1-0.20200511212021-28e39be4a84f/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
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/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=
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/peterbourgon/ff/v3 v3.4.0 h1:QBvM/rizZM1cB0p0lGMdmR7HxZeI/ZrBWB4DqLkMUBc=
github.com/peterbourgon/ff/v3 v3.4.0/go.mod h1:zjJVUhx+twciwfDl0zBcFzl4dW8axCRyXE/eKY9RztQ=
github.com/philandstuff/dhall-golang/v6 v6.0.2 h1:jv8fi4ZYiFe6uGrprx6dY7L3xPcgmEqWZo3s8ABCzkw=
github.com/philandstuff/dhall-golang/v6 v6.0.2/go.mod h1:XRoxjsqZM2y7KPFhjV7CSVdWpV5CwuTzGjAY/v+1SUU=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=

1
testdata/bad.dhall vendored 100644
View File

@ -0,0 +1 @@
{

1
testdata/basic.dhall vendored 100644
View File

@ -0,0 +1 @@
{ s = "s", i = 10, b = True, d = "5s" }

1
testdata/empty.dhall vendored 100644
View File

@ -0,0 +1 @@

6
testdata/value_arrays.dhall vendored 100644
View File

@ -0,0 +1,6 @@
{ s = [ "a", "bb" ]
, i = [ "10", "11", "12" ]
, b = [ False, True ]
, d = [ "10m", "5s" ]
, x = [ "a", "B", "👍" ]
}