Merge pull request #491 from ykanda/toml-support
Add TOML config file loader
This commit is contained in:
commit
a71fcd8f23
310
altsrc/toml_command_test.go
Normal file
310
altsrc/toml_command_test.go
Normal file
@ -0,0 +1,310 @@
|
||||
// Disabling building of toml support in cases where golang is 1.0 or 1.1
|
||||
// as the encoding library is not implemented or supported.
|
||||
|
||||
// +build go1.2
|
||||
|
||||
package altsrc
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
func TestCommandTomFileTest(t *testing.T) {
|
||||
app := cli.NewApp()
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
ioutil.WriteFile("current.toml", []byte("test = 15"), 0666)
|
||||
defer os.Remove("current.toml")
|
||||
test := []string{"test-cmd", "--load", "current.toml"}
|
||||
set.Parse(test)
|
||||
|
||||
c := cli.NewContext(app, set, nil)
|
||||
|
||||
command := &cli.Command{
|
||||
Name: "test-cmd",
|
||||
Aliases: []string{"tc"},
|
||||
Usage: "this is for testing",
|
||||
Description: "testing",
|
||||
Action: func(c *cli.Context) error {
|
||||
val := c.Int("test")
|
||||
expect(t, val, 15)
|
||||
return nil
|
||||
},
|
||||
Flags: []cli.Flag{
|
||||
NewIntFlag(cli.IntFlag{Name: "test"}),
|
||||
cli.StringFlag{Name: "load"}},
|
||||
}
|
||||
command.Before = InitInputSourceWithContext(command.Flags, NewTomlSourceFromFlagFunc("load"))
|
||||
err := command.Run(c)
|
||||
|
||||
expect(t, err, nil)
|
||||
}
|
||||
|
||||
func TestCommandTomlFileTestGlobalEnvVarWins(t *testing.T) {
|
||||
app := cli.NewApp()
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
ioutil.WriteFile("current.toml", []byte("test = 15"), 0666)
|
||||
defer os.Remove("current.toml")
|
||||
|
||||
os.Setenv("THE_TEST", "10")
|
||||
defer os.Setenv("THE_TEST", "")
|
||||
test := []string{"test-cmd", "--load", "current.toml"}
|
||||
set.Parse(test)
|
||||
|
||||
c := cli.NewContext(app, set, nil)
|
||||
|
||||
command := &cli.Command{
|
||||
Name: "test-cmd",
|
||||
Aliases: []string{"tc"},
|
||||
Usage: "this is for testing",
|
||||
Description: "testing",
|
||||
Action: func(c *cli.Context) error {
|
||||
val := c.Int("test")
|
||||
expect(t, val, 10)
|
||||
return nil
|
||||
},
|
||||
Flags: []cli.Flag{
|
||||
NewIntFlag(cli.IntFlag{Name: "test", EnvVar: "THE_TEST"}),
|
||||
cli.StringFlag{Name: "load"}},
|
||||
}
|
||||
command.Before = InitInputSourceWithContext(command.Flags, NewTomlSourceFromFlagFunc("load"))
|
||||
|
||||
err := command.Run(c)
|
||||
|
||||
expect(t, err, nil)
|
||||
}
|
||||
|
||||
func TestCommandTomlFileTestGlobalEnvVarWinsNested(t *testing.T) {
|
||||
app := cli.NewApp()
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
ioutil.WriteFile("current.toml", []byte("[top]\ntest = 15"), 0666)
|
||||
defer os.Remove("current.toml")
|
||||
|
||||
os.Setenv("THE_TEST", "10")
|
||||
defer os.Setenv("THE_TEST", "")
|
||||
test := []string{"test-cmd", "--load", "current.toml"}
|
||||
set.Parse(test)
|
||||
|
||||
c := cli.NewContext(app, set, nil)
|
||||
|
||||
command := &cli.Command{
|
||||
Name: "test-cmd",
|
||||
Aliases: []string{"tc"},
|
||||
Usage: "this is for testing",
|
||||
Description: "testing",
|
||||
Action: func(c *cli.Context) error {
|
||||
val := c.Int("top.test")
|
||||
expect(t, val, 10)
|
||||
return nil
|
||||
},
|
||||
Flags: []cli.Flag{
|
||||
NewIntFlag(cli.IntFlag{Name: "top.test", EnvVar: "THE_TEST"}),
|
||||
cli.StringFlag{Name: "load"}},
|
||||
}
|
||||
command.Before = InitInputSourceWithContext(command.Flags, NewTomlSourceFromFlagFunc("load"))
|
||||
|
||||
err := command.Run(c)
|
||||
|
||||
expect(t, err, nil)
|
||||
}
|
||||
|
||||
func TestCommandTomlFileTestSpecifiedFlagWins(t *testing.T) {
|
||||
app := cli.NewApp()
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
ioutil.WriteFile("current.toml", []byte("test = 15"), 0666)
|
||||
defer os.Remove("current.toml")
|
||||
|
||||
test := []string{"test-cmd", "--load", "current.toml", "--test", "7"}
|
||||
set.Parse(test)
|
||||
|
||||
c := cli.NewContext(app, set, nil)
|
||||
|
||||
command := &cli.Command{
|
||||
Name: "test-cmd",
|
||||
Aliases: []string{"tc"},
|
||||
Usage: "this is for testing",
|
||||
Description: "testing",
|
||||
Action: func(c *cli.Context) error {
|
||||
val := c.Int("test")
|
||||
expect(t, val, 7)
|
||||
return nil
|
||||
},
|
||||
Flags: []cli.Flag{
|
||||
NewIntFlag(cli.IntFlag{Name: "test"}),
|
||||
cli.StringFlag{Name: "load"}},
|
||||
}
|
||||
command.Before = InitInputSourceWithContext(command.Flags, NewTomlSourceFromFlagFunc("load"))
|
||||
|
||||
err := command.Run(c)
|
||||
|
||||
expect(t, err, nil)
|
||||
}
|
||||
|
||||
func TestCommandTomlFileTestSpecifiedFlagWinsNested(t *testing.T) {
|
||||
app := cli.NewApp()
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
ioutil.WriteFile("current.toml", []byte(`[top]
|
||||
test = 15`), 0666)
|
||||
defer os.Remove("current.toml")
|
||||
|
||||
test := []string{"test-cmd", "--load", "current.toml", "--top.test", "7"}
|
||||
set.Parse(test)
|
||||
|
||||
c := cli.NewContext(app, set, nil)
|
||||
|
||||
command := &cli.Command{
|
||||
Name: "test-cmd",
|
||||
Aliases: []string{"tc"},
|
||||
Usage: "this is for testing",
|
||||
Description: "testing",
|
||||
Action: func(c *cli.Context) error {
|
||||
val := c.Int("top.test")
|
||||
expect(t, val, 7)
|
||||
return nil
|
||||
},
|
||||
Flags: []cli.Flag{
|
||||
NewIntFlag(cli.IntFlag{Name: "top.test"}),
|
||||
cli.StringFlag{Name: "load"}},
|
||||
}
|
||||
command.Before = InitInputSourceWithContext(command.Flags, NewTomlSourceFromFlagFunc("load"))
|
||||
|
||||
err := command.Run(c)
|
||||
|
||||
expect(t, err, nil)
|
||||
}
|
||||
|
||||
func TestCommandTomlFileTestDefaultValueFileWins(t *testing.T) {
|
||||
app := cli.NewApp()
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
ioutil.WriteFile("current.toml", []byte("test = 15"), 0666)
|
||||
defer os.Remove("current.toml")
|
||||
|
||||
test := []string{"test-cmd", "--load", "current.toml"}
|
||||
set.Parse(test)
|
||||
|
||||
c := cli.NewContext(app, set, nil)
|
||||
|
||||
command := &cli.Command{
|
||||
Name: "test-cmd",
|
||||
Aliases: []string{"tc"},
|
||||
Usage: "this is for testing",
|
||||
Description: "testing",
|
||||
Action: func(c *cli.Context) error {
|
||||
val := c.Int("test")
|
||||
expect(t, val, 15)
|
||||
return nil
|
||||
},
|
||||
Flags: []cli.Flag{
|
||||
NewIntFlag(cli.IntFlag{Name: "test", Value: 7}),
|
||||
cli.StringFlag{Name: "load"}},
|
||||
}
|
||||
command.Before = InitInputSourceWithContext(command.Flags, NewTomlSourceFromFlagFunc("load"))
|
||||
|
||||
err := command.Run(c)
|
||||
|
||||
expect(t, err, nil)
|
||||
}
|
||||
|
||||
func TestCommandTomlFileTestDefaultValueFileWinsNested(t *testing.T) {
|
||||
app := cli.NewApp()
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
ioutil.WriteFile("current.toml", []byte("[top]\ntest = 15"), 0666)
|
||||
defer os.Remove("current.toml")
|
||||
|
||||
test := []string{"test-cmd", "--load", "current.toml"}
|
||||
set.Parse(test)
|
||||
|
||||
c := cli.NewContext(app, set, nil)
|
||||
|
||||
command := &cli.Command{
|
||||
Name: "test-cmd",
|
||||
Aliases: []string{"tc"},
|
||||
Usage: "this is for testing",
|
||||
Description: "testing",
|
||||
Action: func(c *cli.Context) error {
|
||||
val := c.Int("top.test")
|
||||
expect(t, val, 15)
|
||||
return nil
|
||||
},
|
||||
Flags: []cli.Flag{
|
||||
NewIntFlag(cli.IntFlag{Name: "top.test", Value: 7}),
|
||||
cli.StringFlag{Name: "load"}},
|
||||
}
|
||||
command.Before = InitInputSourceWithContext(command.Flags, NewTomlSourceFromFlagFunc("load"))
|
||||
|
||||
err := command.Run(c)
|
||||
|
||||
expect(t, err, nil)
|
||||
}
|
||||
|
||||
func TestCommandTomlFileFlagHasDefaultGlobalEnvTomlSetGlobalEnvWins(t *testing.T) {
|
||||
app := cli.NewApp()
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
ioutil.WriteFile("current.toml", []byte("test = 15"), 0666)
|
||||
defer os.Remove("current.toml")
|
||||
|
||||
os.Setenv("THE_TEST", "11")
|
||||
defer os.Setenv("THE_TEST", "")
|
||||
|
||||
test := []string{"test-cmd", "--load", "current.toml"}
|
||||
set.Parse(test)
|
||||
|
||||
c := cli.NewContext(app, set, nil)
|
||||
|
||||
command := &cli.Command{
|
||||
Name: "test-cmd",
|
||||
Aliases: []string{"tc"},
|
||||
Usage: "this is for testing",
|
||||
Description: "testing",
|
||||
Action: func(c *cli.Context) error {
|
||||
val := c.Int("test")
|
||||
expect(t, val, 11)
|
||||
return nil
|
||||
},
|
||||
Flags: []cli.Flag{
|
||||
NewIntFlag(cli.IntFlag{Name: "test", Value: 7, EnvVar: "THE_TEST"}),
|
||||
cli.StringFlag{Name: "load"}},
|
||||
}
|
||||
command.Before = InitInputSourceWithContext(command.Flags, NewTomlSourceFromFlagFunc("load"))
|
||||
err := command.Run(c)
|
||||
|
||||
expect(t, err, nil)
|
||||
}
|
||||
|
||||
func TestCommandTomlFileFlagHasDefaultGlobalEnvTomlSetGlobalEnvWinsNested(t *testing.T) {
|
||||
app := cli.NewApp()
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
ioutil.WriteFile("current.toml", []byte("[top]\ntest = 15"), 0666)
|
||||
defer os.Remove("current.toml")
|
||||
|
||||
os.Setenv("THE_TEST", "11")
|
||||
defer os.Setenv("THE_TEST", "")
|
||||
|
||||
test := []string{"test-cmd", "--load", "current.toml"}
|
||||
set.Parse(test)
|
||||
|
||||
c := cli.NewContext(app, set, nil)
|
||||
|
||||
command := &cli.Command{
|
||||
Name: "test-cmd",
|
||||
Aliases: []string{"tc"},
|
||||
Usage: "this is for testing",
|
||||
Description: "testing",
|
||||
Action: func(c *cli.Context) error {
|
||||
val := c.Int("top.test")
|
||||
expect(t, val, 11)
|
||||
return nil
|
||||
},
|
||||
Flags: []cli.Flag{
|
||||
NewIntFlag(cli.IntFlag{Name: "top.test", Value: 7, EnvVar: "THE_TEST"}),
|
||||
cli.StringFlag{Name: "load"}},
|
||||
}
|
||||
command.Before = InitInputSourceWithContext(command.Flags, NewTomlSourceFromFlagFunc("load"))
|
||||
err := command.Run(c)
|
||||
|
||||
expect(t, err, nil)
|
||||
}
|
113
altsrc/toml_file_loader.go
Normal file
113
altsrc/toml_file_loader.go
Normal file
@ -0,0 +1,113 @@
|
||||
// Disabling building of toml support in cases where golang is 1.0 or 1.1
|
||||
// as the encoding library is not implemented or supported.
|
||||
|
||||
// +build go1.2
|
||||
|
||||
package altsrc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
type tomlMap struct {
|
||||
Map map[interface{}]interface{}
|
||||
}
|
||||
|
||||
func unmarshalMap(i interface{}) (ret map[interface{}]interface{}, err error) {
|
||||
ret = make(map[interface{}]interface{})
|
||||
m := i.(map[string]interface{})
|
||||
for key, val := range m {
|
||||
v := reflect.ValueOf(val)
|
||||
switch v.Kind() {
|
||||
case reflect.Bool:
|
||||
ret[key] = val.(bool)
|
||||
case reflect.String:
|
||||
ret[key] = val.(string)
|
||||
case reflect.Int:
|
||||
ret[key] = int(val.(int))
|
||||
case reflect.Int8:
|
||||
ret[key] = int(val.(int8))
|
||||
case reflect.Int16:
|
||||
ret[key] = int(val.(int16))
|
||||
case reflect.Int32:
|
||||
ret[key] = int(val.(int32))
|
||||
case reflect.Int64:
|
||||
ret[key] = int(val.(int64))
|
||||
case reflect.Uint:
|
||||
ret[key] = int(val.(uint))
|
||||
case reflect.Uint8:
|
||||
ret[key] = int(val.(uint8))
|
||||
case reflect.Uint16:
|
||||
ret[key] = int(val.(uint16))
|
||||
case reflect.Uint32:
|
||||
ret[key] = int(val.(uint32))
|
||||
case reflect.Uint64:
|
||||
ret[key] = int(val.(uint64))
|
||||
case reflect.Float32:
|
||||
ret[key] = float64(val.(float32))
|
||||
case reflect.Float64:
|
||||
ret[key] = float64(val.(float64))
|
||||
case reflect.Map:
|
||||
if tmp, err := unmarshalMap(val); err == nil {
|
||||
ret[key] = tmp
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
case reflect.Array:
|
||||
fallthrough // [todo] - Support array type
|
||||
default:
|
||||
return nil, fmt.Errorf("Unsupported: type = %#v", v.Kind())
|
||||
}
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (self *tomlMap) UnmarshalTOML(i interface{}) error {
|
||||
if tmp, err := unmarshalMap(i); err == nil {
|
||||
self.Map = tmp
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type tomlSourceContext struct {
|
||||
FilePath string
|
||||
}
|
||||
|
||||
// NewTomlSourceFromFile creates a new TOML InputSourceContext from a filepath.
|
||||
func NewTomlSourceFromFile(file string) (InputSourceContext, error) {
|
||||
tsc := &tomlSourceContext{FilePath: file}
|
||||
var results tomlMap = tomlMap{}
|
||||
if err := readCommandToml(tsc.FilePath, &results); err != nil {
|
||||
return nil, fmt.Errorf("Unable to load TOML file '%s': inner error: \n'%v'", tsc.FilePath, err.Error())
|
||||
}
|
||||
return &MapInputSource{valueMap: results.Map}, nil
|
||||
}
|
||||
|
||||
// NewTomlSourceFromFlagFunc creates a new TOML InputSourceContext from a provided flag name and source context.
|
||||
func NewTomlSourceFromFlagFunc(flagFileName string) func(context *cli.Context) (InputSourceContext, error) {
|
||||
return func(context *cli.Context) (InputSourceContext, error) {
|
||||
filePath := context.String(flagFileName)
|
||||
return NewTomlSourceFromFile(filePath)
|
||||
}
|
||||
}
|
||||
|
||||
func readCommandToml(filePath string, container interface{}) (err error) {
|
||||
b, err := loadDataFrom(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = toml.Unmarshal(b, container)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = nil
|
||||
return
|
||||
}
|
Loading…
Reference in New Issue
Block a user