mirror of
https://github.com/Luzifer/rconfig.git
synced 2024-11-09 16:30:04 +00:00
Initial version of rconfig
This commit is contained in:
commit
0c78105a26
5 changed files with 664 additions and 0 deletions
13
LICENSE
Normal file
13
LICENSE
Normal file
|
@ -0,0 +1,13 @@
|
|||
Copyright 2015 Knut Ahlers <knut@ahlers.me>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
58
README.md
Normal file
58
README.md
Normal file
|
@ -0,0 +1,58 @@
|
|||
[![Circle CI](https://circleci.com/gh/Luzifer/rconfig.svg?style=svg)](https://circleci.com/gh/Luzifer/rconfig)
|
||||
[![License: Apache v2.0](https://badge.luzifer.io/v1/badge?color=5d79b5&title=license&text=Apache+v2.0)](http://www.apache.org/licenses/LICENSE-2.0)
|
||||
[![Documentation](https://badge.luzifer.io/v1/badge?title=godoc&text=reference)](https://godoc.org/github.com/Luzifer/rconfig)
|
||||
|
||||
## Description
|
||||
|
||||
> Package rconfig implements a CLI configuration reader with struct-embedded defaults, environment variables and posix compatible flag parsing using the [pflag](https://github.com/spf13/pflag) library.
|
||||
|
||||
## Installation
|
||||
|
||||
Install by running:
|
||||
|
||||
```
|
||||
go get -u github.com/Luzifer/rconfig
|
||||
```
|
||||
|
||||
Run tests by running:
|
||||
|
||||
```
|
||||
go test -v -race -cover github.com/Luzifer/rconfig
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
As a first step define a struct holding your configuration:
|
||||
|
||||
```go
|
||||
type config struct {
|
||||
Username string `default:"unknown" flag:"user" description:"Your name"`
|
||||
Details struct {
|
||||
Age int `default:"25" flag:"age" env:"age" description:"Your age"`
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Next create an instance of that struct and let `rconfig` fill that config:
|
||||
|
||||
```go
|
||||
var cfg config
|
||||
func init() {
|
||||
cfg = config{}
|
||||
rconfig.Parse(&cfg)
|
||||
}
|
||||
```
|
||||
|
||||
You're ready to access your configuration:
|
||||
|
||||
```go
|
||||
func main() {
|
||||
fmt.Printf("Hello %s, happy birthday for your %dth birthday.",
|
||||
cfg.Username,
|
||||
cfg.Details.Age)
|
||||
}
|
||||
```
|
||||
|
||||
## More info
|
||||
|
||||
You can see the full reference documentation of the rconfig package [at godoc.org](https://godoc.org/github.com/Luzifer/rconfig), or through go's standard documentation system by running `godoc -http=:6060` and browsing to [http://localhost:6060/pkg/github.com/Luzifer/rconfig](http://localhost:6060/pkg/github.com/Luzifer/rconfig) after installation.
|
282
config.go
Normal file
282
config.go
Normal file
|
@ -0,0 +1,282 @@
|
|||
// Package rconfig implements a CLI configuration reader with struct-embedded
|
||||
// defaults, environment variables and posix compatible flag parsing using
|
||||
// the pflag library.
|
||||
package rconfig //import "github.com/Luzifer/rconfig"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
var fs *pflag.FlagSet
|
||||
|
||||
// Parse takes the pointer to a struct filled with variables which should be read
|
||||
// from ENV, default or flag. The precedence in this is flag > ENV > default. So
|
||||
// if a flag is specified on the CLI it will overwrite the ENV and otherwise ENV
|
||||
// overwrites the default specified.
|
||||
//
|
||||
// For your configuration struct you can use the following struct-tags to control
|
||||
// the behavior of rconfig:
|
||||
//
|
||||
// default: Set a default value
|
||||
// env: Read the value from this environment variable
|
||||
// flag: Flag to read in format "long,short" (for example "listen,l")
|
||||
// description: A help text for Usage output to guide your users
|
||||
//
|
||||
// The format you need to specify those values you can see in the example to this
|
||||
// function.
|
||||
//
|
||||
func Parse(config interface{}) error {
|
||||
return parse(config, nil)
|
||||
}
|
||||
|
||||
// Usage prints a basic usage with the corresponding defaults for the flags to
|
||||
// os.Stdout. The defaults are derived from the `default` struct-tag and the ENV.
|
||||
func Usage() {
|
||||
if fs != nil && fs.Parsed() {
|
||||
fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
|
||||
fs.PrintDefaults()
|
||||
}
|
||||
}
|
||||
|
||||
func parse(in interface{}, args []string) error {
|
||||
if args == nil {
|
||||
args = os.Args
|
||||
}
|
||||
|
||||
fs = pflag.NewFlagSet(os.Args[0], pflag.ExitOnError)
|
||||
if err := execTags(in, fs); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return fs.Parse(args)
|
||||
}
|
||||
|
||||
func execTags(in interface{}, fs *pflag.FlagSet) error {
|
||||
if reflect.TypeOf(in).Kind() != reflect.Ptr {
|
||||
return errors.New("Calling parser with non-pointer")
|
||||
}
|
||||
|
||||
if reflect.ValueOf(in).Elem().Kind() != reflect.Struct {
|
||||
return errors.New("Calling parser with pointer to non-struct")
|
||||
}
|
||||
|
||||
st := reflect.ValueOf(in).Elem()
|
||||
for i := 0; i < st.NumField(); i++ {
|
||||
valField := st.Field(i)
|
||||
typeField := st.Type().Field(i)
|
||||
|
||||
if typeField.Tag.Get("default") == "" && typeField.Tag.Get("env") == "" && typeField.Tag.Get("flag") == "" && typeField.Type.Kind() != reflect.Struct {
|
||||
// None of our supported tags is present and it's not a sub-struct
|
||||
continue
|
||||
}
|
||||
|
||||
value := envDefault(typeField.Tag.Get("env"), typeField.Tag.Get("default"))
|
||||
parts := strings.Split(typeField.Tag.Get("flag"), ",")
|
||||
|
||||
switch typeField.Type.Kind() {
|
||||
case reflect.String:
|
||||
if typeField.Tag.Get("flag") != "" {
|
||||
if len(parts) == 1 {
|
||||
fs.StringVar(valField.Addr().Interface().(*string), parts[0], value, typeField.Tag.Get("description"))
|
||||
} else {
|
||||
fs.StringVarP(valField.Addr().Interface().(*string), parts[0], parts[1], value, typeField.Tag.Get("description"))
|
||||
}
|
||||
} else {
|
||||
valField.SetString(value)
|
||||
}
|
||||
|
||||
case reflect.Bool:
|
||||
v := value == "true"
|
||||
if typeField.Tag.Get("flag") != "" {
|
||||
if len(parts) == 1 {
|
||||
fs.BoolVar(valField.Addr().Interface().(*bool), parts[0], v, typeField.Tag.Get("description"))
|
||||
} else {
|
||||
fs.BoolVarP(valField.Addr().Interface().(*bool), parts[0], parts[1], v, typeField.Tag.Get("description"))
|
||||
}
|
||||
} else {
|
||||
valField.SetBool(v)
|
||||
}
|
||||
|
||||
case reflect.Int, reflect.Int8, reflect.Int32, reflect.Int64:
|
||||
vt, err := strconv.ParseInt(value, 10, 64)
|
||||
if err != nil {
|
||||
if value == "" {
|
||||
vt = 0
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if typeField.Tag.Get("flag") != "" {
|
||||
registerFlagInt(typeField.Type.Kind(), fs, valField.Addr().Interface(), parts, vt, typeField.Tag.Get("description"))
|
||||
} else {
|
||||
valField.SetInt(vt)
|
||||
}
|
||||
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
vt, err := strconv.ParseUint(value, 10, 64)
|
||||
if err != nil {
|
||||
if value == "" {
|
||||
vt = 0
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if typeField.Tag.Get("flag") != "" {
|
||||
registerFlagUint(typeField.Type.Kind(), fs, valField.Addr().Interface(), parts, vt, typeField.Tag.Get("description"))
|
||||
} else {
|
||||
valField.SetUint(vt)
|
||||
}
|
||||
|
||||
case reflect.Float32, reflect.Float64:
|
||||
vt, err := strconv.ParseFloat(value, 64)
|
||||
if err != nil {
|
||||
if value == "" {
|
||||
vt = 0.0
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if typeField.Tag.Get("flag") != "" {
|
||||
registerFlagFloat(typeField.Type.Kind(), fs, valField.Addr().Interface(), parts, vt, typeField.Tag.Get("description"))
|
||||
} else {
|
||||
valField.SetFloat(vt)
|
||||
}
|
||||
|
||||
case reflect.Struct:
|
||||
if err := execTags(valField.Addr().Interface(), fs); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case reflect.Slice:
|
||||
switch typeField.Type.Elem().Kind() {
|
||||
case reflect.Int:
|
||||
def := []int{}
|
||||
for _, v := range strings.Split(value, ",") {
|
||||
it, err := strconv.ParseInt(strings.TrimSpace(v), 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
def = append(def, int(it))
|
||||
}
|
||||
if len(parts) == 1 {
|
||||
fs.IntSliceVar(valField.Addr().Interface().(*[]int), parts[0], def, typeField.Tag.Get("description"))
|
||||
} else {
|
||||
fs.IntSliceVarP(valField.Addr().Interface().(*[]int), parts[0], parts[1], def, typeField.Tag.Get("description"))
|
||||
}
|
||||
case reflect.String:
|
||||
del := typeField.Tag.Get("delimiter")
|
||||
if len(del) == 0 {
|
||||
del = ","
|
||||
}
|
||||
def := strings.Split(value, del)
|
||||
if len(parts) == 1 {
|
||||
fs.StringSliceVar(valField.Addr().Interface().(*[]string), parts[0], def, typeField.Tag.Get("description"))
|
||||
} else {
|
||||
fs.StringSliceVarP(valField.Addr().Interface().(*[]string), parts[0], parts[1], def, typeField.Tag.Get("description"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func registerFlagFloat(t reflect.Kind, fs *pflag.FlagSet, field interface{}, parts []string, vt float64, desc string) {
|
||||
switch t {
|
||||
case reflect.Float32:
|
||||
if len(parts) == 1 {
|
||||
fs.Float32Var(field.(*float32), parts[0], float32(vt), desc)
|
||||
} else {
|
||||
fs.Float32VarP(field.(*float32), parts[0], parts[1], float32(vt), desc)
|
||||
}
|
||||
case reflect.Float64:
|
||||
if len(parts) == 1 {
|
||||
fs.Float64Var(field.(*float64), parts[0], float64(vt), desc)
|
||||
} else {
|
||||
fs.Float64VarP(field.(*float64), parts[0], parts[1], float64(vt), desc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func registerFlagInt(t reflect.Kind, fs *pflag.FlagSet, field interface{}, parts []string, vt int64, desc string) {
|
||||
switch t {
|
||||
case reflect.Int:
|
||||
if len(parts) == 1 {
|
||||
fs.IntVar(field.(*int), parts[0], int(vt), desc)
|
||||
} else {
|
||||
fs.IntVarP(field.(*int), parts[0], parts[1], int(vt), desc)
|
||||
}
|
||||
case reflect.Int8:
|
||||
if len(parts) == 1 {
|
||||
fs.Int8Var(field.(*int8), parts[0], int8(vt), desc)
|
||||
} else {
|
||||
fs.Int8VarP(field.(*int8), parts[0], parts[1], int8(vt), desc)
|
||||
}
|
||||
case reflect.Int32:
|
||||
if len(parts) == 1 {
|
||||
fs.Int32Var(field.(*int32), parts[0], int32(vt), desc)
|
||||
} else {
|
||||
fs.Int32VarP(field.(*int32), parts[0], parts[1], int32(vt), desc)
|
||||
}
|
||||
case reflect.Int64:
|
||||
if len(parts) == 1 {
|
||||
fs.Int64Var(field.(*int64), parts[0], int64(vt), desc)
|
||||
} else {
|
||||
fs.Int64VarP(field.(*int64), parts[0], parts[1], int64(vt), desc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func registerFlagUint(t reflect.Kind, fs *pflag.FlagSet, field interface{}, parts []string, vt uint64, desc string) {
|
||||
switch t {
|
||||
case reflect.Uint:
|
||||
if len(parts) == 1 {
|
||||
fs.UintVar(field.(*uint), parts[0], uint(vt), desc)
|
||||
} else {
|
||||
fs.UintVarP(field.(*uint), parts[0], parts[1], uint(vt), desc)
|
||||
}
|
||||
case reflect.Uint8:
|
||||
if len(parts) == 1 {
|
||||
fs.Uint8Var(field.(*uint8), parts[0], uint8(vt), desc)
|
||||
} else {
|
||||
fs.Uint8VarP(field.(*uint8), parts[0], parts[1], uint8(vt), desc)
|
||||
}
|
||||
case reflect.Uint16:
|
||||
if len(parts) == 1 {
|
||||
fs.Uint16Var(field.(*uint16), parts[0], uint16(vt), desc)
|
||||
} else {
|
||||
fs.Uint16VarP(field.(*uint16), parts[0], parts[1], uint16(vt), desc)
|
||||
}
|
||||
case reflect.Uint32:
|
||||
if len(parts) == 1 {
|
||||
fs.Uint32Var(field.(*uint32), parts[0], uint32(vt), desc)
|
||||
} else {
|
||||
fs.Uint32VarP(field.(*uint32), parts[0], parts[1], uint32(vt), desc)
|
||||
}
|
||||
case reflect.Uint64:
|
||||
if len(parts) == 1 {
|
||||
fs.Uint64Var(field.(*uint64), parts[0], uint64(vt), desc)
|
||||
} else {
|
||||
fs.Uint64VarP(field.(*uint64), parts[0], parts[1], uint64(vt), desc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func envDefault(env, def string) string {
|
||||
value := def
|
||||
|
||||
if env != "" {
|
||||
if e := os.Getenv(env); e != "" {
|
||||
value = e
|
||||
}
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
274
config_test.go
Normal file
274
config_test.go
Normal file
|
@ -0,0 +1,274 @@
|
|||
package rconfig
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGeneralMechanics(t *testing.T) {
|
||||
cfg := struct {
|
||||
Test string `default:"foo" env:"shell" flag:"shell" description:"Test"`
|
||||
Test2 string `default:"blub" env:"testvar" flag:"testvar,t" description:"Test"`
|
||||
DefaultFlag string `default:"goo"`
|
||||
SadFlag string
|
||||
}{}
|
||||
|
||||
parse(&cfg, []string{
|
||||
"--shell=test23",
|
||||
"-t", "bla",
|
||||
})
|
||||
|
||||
if cfg.Test != "test23" {
|
||||
t.Errorf("Test should be 'test23', is '%s'", cfg.Test)
|
||||
}
|
||||
|
||||
if cfg.Test2 != "bla" {
|
||||
t.Errorf("Test2 should be 'bla', is '%s'", cfg.Test2)
|
||||
}
|
||||
|
||||
if cfg.SadFlag != "" {
|
||||
t.Errorf("SadFlag should be '', is '%s'", cfg.SadFlag)
|
||||
}
|
||||
|
||||
if cfg.DefaultFlag != "goo" {
|
||||
t.Errorf("DefaultFlag should be 'goo', is '%s'", cfg.DefaultFlag)
|
||||
}
|
||||
|
||||
parse(&cfg, []string{})
|
||||
|
||||
if cfg.Test != "foo" {
|
||||
t.Errorf("Test should be 'foo', is '%s'", cfg.Test)
|
||||
}
|
||||
|
||||
os.Setenv("shell", "test546")
|
||||
parse(&cfg, []string{})
|
||||
|
||||
if cfg.Test != "test546" {
|
||||
t.Errorf("Test should be 'test546', is '%s'", cfg.Test)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBool(t *testing.T) {
|
||||
cfg := struct {
|
||||
Test1 bool `default:"true"`
|
||||
Test2 bool `default:"false" flag:"test2"`
|
||||
Test3 bool `default:"true" flag:"test3,t"`
|
||||
Test4 bool `flag:"test4"`
|
||||
}{}
|
||||
|
||||
parse(&cfg, []string{
|
||||
"--test2",
|
||||
"-t",
|
||||
})
|
||||
|
||||
if !cfg.Test1 {
|
||||
t.Errorf("Test1 should be 'true', is '%+v'", cfg.Test1)
|
||||
}
|
||||
if !cfg.Test2 {
|
||||
t.Errorf("Test1 should be 'true', is '%+v'", cfg.Test2)
|
||||
}
|
||||
if !cfg.Test3 {
|
||||
t.Errorf("Test1 should be 'true', is '%+v'", cfg.Test3)
|
||||
}
|
||||
if cfg.Test4 {
|
||||
t.Errorf("Test1 should be 'false', is '%+v'", cfg.Test3)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInt(t *testing.T) {
|
||||
cfg := struct {
|
||||
Test int `flag:"int"`
|
||||
TestP int `flag:"intp,i"`
|
||||
Test8 int8 `flag:"int8"`
|
||||
Test8P int8 `flag:"int8p,8"`
|
||||
Test32 int32 `flag:"int32"`
|
||||
Test32P int32 `flag:"int32p,3"`
|
||||
Test64 int64 `flag:"int64"`
|
||||
Test64P int64 `flag:"int64p,6"`
|
||||
TestDef int8 `default:"66"`
|
||||
}{}
|
||||
|
||||
parse(&cfg, []string{
|
||||
"--int=1", "-i", "2",
|
||||
"--int8=3", "-8", "4",
|
||||
"--int32=5", "-3", "6",
|
||||
"--int64=7", "-6", "8",
|
||||
})
|
||||
|
||||
if cfg.Test != 1 || cfg.TestP != 2 || cfg.Test8 != 3 || cfg.Test8P != 4 || cfg.Test32 != 5 || cfg.Test32P != 6 || cfg.Test64 != 7 || cfg.Test64P != 8 {
|
||||
t.Errorf("One of the int tests failed.")
|
||||
}
|
||||
|
||||
if cfg.TestDef != 66 {
|
||||
t.Errorf("TestDef should be '66', is '%d'", cfg.TestDef)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUint(t *testing.T) {
|
||||
cfg := struct {
|
||||
Test uint `flag:"int"`
|
||||
TestP uint `flag:"intp,i"`
|
||||
Test8 uint8 `flag:"int8"`
|
||||
Test8P uint8 `flag:"int8p,8"`
|
||||
Test16 uint16 `flag:"int16"`
|
||||
Test16P uint16 `flag:"int16p,1"`
|
||||
Test32 uint32 `flag:"int32"`
|
||||
Test32P uint32 `flag:"int32p,3"`
|
||||
Test64 uint64 `flag:"int64"`
|
||||
Test64P uint64 `flag:"int64p,6"`
|
||||
TestDef uint8 `default:"66"`
|
||||
}{}
|
||||
|
||||
parse(&cfg, []string{
|
||||
"--int=1", "-i", "2",
|
||||
"--int8=3", "-8", "4",
|
||||
"--int32=5", "-3", "6",
|
||||
"--int64=7", "-6", "8",
|
||||
"--int16=9", "-1", "10",
|
||||
})
|
||||
|
||||
if cfg.Test != 1 || cfg.TestP != 2 || cfg.Test8 != 3 || cfg.Test8P != 4 || cfg.Test32 != 5 || cfg.Test32P != 6 || cfg.Test64 != 7 || cfg.Test64P != 8 || cfg.Test16 != 9 || cfg.Test16P != 10 {
|
||||
t.Errorf("One of the uint tests failed.")
|
||||
}
|
||||
|
||||
if cfg.TestDef != 66 {
|
||||
t.Errorf("TestDef should be '66', is '%d'", cfg.TestDef)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFloat(t *testing.T) {
|
||||
cfg := struct {
|
||||
Test32 float32 `flag:"float32"`
|
||||
Test32P float32 `flag:"float32p,3"`
|
||||
Test64 float64 `flag:"float64"`
|
||||
Test64P float64 `flag:"float64p,6"`
|
||||
TestDef float32 `default:"66.256"`
|
||||
}{}
|
||||
|
||||
parse(&cfg, []string{
|
||||
"--float32=5.5", "-3", "6.6",
|
||||
"--float64=7.7", "-6", "8.8",
|
||||
})
|
||||
|
||||
if cfg.Test32 != 5.5 || cfg.Test32P != 6.6 || cfg.Test64 != 7.7 || cfg.Test64P != 8.8 {
|
||||
t.Errorf("One of the int tests failed.")
|
||||
}
|
||||
|
||||
if cfg.TestDef != 66.256 {
|
||||
t.Errorf("TestDef should be '66.256', is '%.3f'", cfg.TestDef)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubStruct(t *testing.T) {
|
||||
cfg := struct {
|
||||
Test string `default:"blubb"`
|
||||
Sub struct {
|
||||
Test string `default:"Hallo"`
|
||||
}
|
||||
}{}
|
||||
|
||||
if err := parse(&cfg, []string{}); err != nil {
|
||||
t.Errorf("Test errored: %s", err)
|
||||
}
|
||||
|
||||
if cfg.Test != "blubb" {
|
||||
t.Errorf("Test should be 'blubb', is '%s'", cfg.Test)
|
||||
}
|
||||
|
||||
if cfg.Sub.Test != "Hallo" {
|
||||
t.Errorf("Sub.Test should be 'Hallo', is '%s'", cfg.Sub.Test)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSlice(t *testing.T) {
|
||||
cfg := struct {
|
||||
Int []int `default:"1,2,3" flag:"int"`
|
||||
String []string `default:"a,b,c" flag:"string"`
|
||||
IntP []int `default:"1,2,3" flag:"intp,i"`
|
||||
StringP []string `default:"a,b,c" flag:"stringp,s"`
|
||||
}{}
|
||||
|
||||
if err := parse(&cfg, []string{
|
||||
"--int=4,5", "-s", "hallo,welt",
|
||||
}); err != nil {
|
||||
t.Errorf("Test errored: %s", err)
|
||||
}
|
||||
|
||||
if len(cfg.Int) != 2 || cfg.Int[0] != 4 || cfg.Int[1] != 5 {
|
||||
t.Errorf("Int should be '4,5', is '%+v'", cfg.Int)
|
||||
}
|
||||
|
||||
if len(cfg.String) != 3 || cfg.String[0] != "a" || cfg.String[1] != "b" {
|
||||
t.Errorf("String should be 'a,b,c', is '%+v'", cfg.String)
|
||||
}
|
||||
|
||||
if len(cfg.StringP) != 2 || cfg.StringP[0] != "hallo" || cfg.StringP[1] != "welt" {
|
||||
t.Errorf("StringP should be 'hallo,welt', is '%+v'", cfg.StringP)
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrors(t *testing.T) {
|
||||
if err := parse(&struct {
|
||||
A int `default:"a"`
|
||||
}{}, []string{}); err == nil {
|
||||
t.Errorf("Test should have errored")
|
||||
}
|
||||
|
||||
if err := parse(&struct {
|
||||
A float32 `default:"a"`
|
||||
}{}, []string{}); err == nil {
|
||||
t.Errorf("Test should have errored")
|
||||
}
|
||||
|
||||
if err := parse(&struct {
|
||||
A uint `default:"a"`
|
||||
}{}, []string{}); err == nil {
|
||||
t.Errorf("Test should have errored")
|
||||
}
|
||||
|
||||
if err := parse(&struct {
|
||||
B struct {
|
||||
A uint `default:"a"`
|
||||
}
|
||||
}{}, []string{}); err == nil {
|
||||
t.Errorf("Test should have errored")
|
||||
}
|
||||
|
||||
if err := parse(&struct {
|
||||
A []int `default:"a,bn"`
|
||||
}{}, []string{}); err == nil {
|
||||
t.Errorf("Test should have errored")
|
||||
}
|
||||
}
|
||||
|
||||
func TestOSArgs(t *testing.T) {
|
||||
os.Args = []string{"--a=bar"}
|
||||
|
||||
cfg := struct {
|
||||
A string `default:"a" flag:"a"`
|
||||
}{}
|
||||
|
||||
Parse(&cfg)
|
||||
|
||||
if cfg.A != "bar" {
|
||||
t.Errorf("A should be 'bar', is '%s'", cfg.A)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNonPointer(t *testing.T) {
|
||||
cfg := struct {
|
||||
A string `default:"a"`
|
||||
}{}
|
||||
|
||||
if err := parse(cfg, []string{}); err == nil {
|
||||
t.Errorf("Test should have errored")
|
||||
}
|
||||
}
|
||||
|
||||
func TestOtherType(t *testing.T) {
|
||||
cfg := "test"
|
||||
|
||||
if err := parse(&cfg, []string{}); err == nil {
|
||||
t.Errorf("Test should have errored")
|
||||
}
|
||||
}
|
37
example_test.go
Normal file
37
example_test.go
Normal file
|
@ -0,0 +1,37 @@
|
|||
package rconfig
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
func ExampleParse() {
|
||||
// We're building an example configuration with a sub-struct to be filled
|
||||
// by the Parse command.
|
||||
config := struct {
|
||||
Username string `default:"unknown" flag:"user,u" description:"Your name"`
|
||||
Details struct {
|
||||
Age int `default:"25" flag:"age" description:"Your age"`
|
||||
}
|
||||
}{}
|
||||
|
||||
// To have more relieable results we're setting os.Args to a known value.
|
||||
// In real-life use cases you wouldn't do this but parse the original
|
||||
// commandline arguments.
|
||||
os.Args = []string{
|
||||
"example",
|
||||
"--user=Luzifer",
|
||||
}
|
||||
|
||||
Parse(&config)
|
||||
|
||||
fmt.Printf("Hello %s, happy birthday for your %dth birthday.",
|
||||
config.Username,
|
||||
config.Details.Age)
|
||||
|
||||
// You can also show an usage message for your user
|
||||
Usage()
|
||||
|
||||
// Output:
|
||||
// Hello Luzifer, happy birthday for your 25th birthday.
|
||||
}
|
Loading…
Reference in a new issue