mirror of
https://github.com/Luzifer/rconfig.git
synced 2024-12-21 03:31:19 +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