Skip to content

Commit

Permalink
Feature/add support arrays (#16)
Browse files Browse the repository at this point in the history
* Support arrays in JSON, ENV, ARGS
* Add cover and maintainability badge
* Add some asserts functions
* Add tests to increase coverage up to 94%
* Add ci for go 1.11
  • Loading branch information
fulldump authored Feb 5, 2019
1 parent 3484678 commit f290e1b
Show file tree
Hide file tree
Showing 13 changed files with 464 additions and 29 deletions.
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ go:
- 1.8
- 1.9
- "1.10"
- "1.11"

script:
- make setup && make test
4 changes: 3 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ setup:
ln -s ../../.. src/$(PROJECT)

test:
$(GOCMD) test $(PROJECT) -cover
$(GOCMD) version
$(GOCMD) env
$(GOCMD) test -v $(PROJECT)

example:
$(GOCMD) install $(PROJECT)/example
Expand Down
13 changes: 8 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,16 @@

<p align="center">
<a href="https://travis-ci.org/fulldump/goconfig"><img src="https://travis-ci.org/fulldump/goconfig.svg?branch=master"></a>
<a href="https://cover.run/go?tag=golang-1.10&repo=github.com%2Ffulldump%2Fgoconfig"><img src="/go/github.com/fulldump/goconfig.svg?style=flat&amp;tag=golang-1.10&amp;d=1549396252922"></a>
<a href="https://goreportcard.com/report/fulldump/goconfig"><img src="http://goreportcard.com/badge/fulldump/goconfig"></a>
<a href="https://godoc.org/github.com/fulldump/goconfig"><img src="https://godoc.org/github.com/fulldump/goconfig?status.svg" alt="GoDoc"></a>
<a href="https://codeclimate.com/github/fulldump/goconfig/maintainability"><img src="https://api.codeclimate.com/v1/badges/d3f50778ac8598d4438f/maintainability"></a>
</p>

Goconfig is an extremely simple configuration library for your Go programs.
Goconfig is an extremely simple and powerful configuration library for your Go
programs that read values from environment vars, command line arguments and
configuration file in JSON.

Make your configuration flags compact and easy to read.

Arguments are parsed from command line with the standard `flag` library.
Expand Down Expand Up @@ -100,11 +105,10 @@ Mainly almost all types from `flag` library are supported:
* uint64
* uint
* struct (hyerarchical keys)
* array (any type)

For the moment `duration` type is not supported.

Type `slice` or `array` is also being considered.


# Builtin flags

Expand Down Expand Up @@ -132,6 +136,7 @@ configuration above, this is a sample config.json file:
Configuration precedence is as follows (higher to lower):
* Arg command line
* Json config file
* Environment variable
* Default value


Expand All @@ -147,8 +152,6 @@ or email me for new features, issues or whatever.

This command will pass all tests.

No tests are expected for the moment.

```sh
make
```
Expand Down
74 changes: 74 additions & 0 deletions assertions_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package goconfig

import (
"reflect"
"runtime/debug"
"testing"
)

// AssertNil checks whether the obtained field is equal to nil,
// failing in other case.
func AssertNil(t *testing.T, obtained interface{}) {

if nil == obtained {
return
}

if reflect.ValueOf(obtained).IsNil() {
return
}

line := GetStackLine(2)
t.Errorf("\nExpected: nil \nObtained:%#v\nat %s\n", obtained, line)
}

// AssertNotNil checks whether the obtained field is distinct to nil,
// failing in other case.
func AssertNotNil(t *testing.T, obtained interface{}) {

line := GetStackLine(2)
if nil == obtained {
t.Errorf("\nExpected: not nil \nObtained:%#v\nat %s\n", obtained, line)
return
}

if reflect.ValueOf(obtained).IsNil() {
t.Errorf("\nExpected: not nil \nObtained:%#v\nat %s\n", obtained, line)
return
}

}

// AssertEqual checks whether the obtained and expected fields are equal
// failing in other case.
func AssertEqual(t *testing.T, obtained, expected interface{}) bool {
if reflect.DeepEqual(expected, obtained) {
return true
}

line := GetStackLine(2)
t.Errorf("\nExpected: %#v\nObtained: %#v\nat %s\n", expected, obtained, line)

return false
}

// GetStackLine accesses the stack trace to get some lines
// so they can be showed by the tests in case of error.
func GetStackLine(linesToSkip int) string {

stack := debug.Stack()
lines := make([]string, 0)
index := 0
for i := 0; i < len(stack); i++ {
if stack[i] == []byte("\n")[0] {
lines = append(lines, string(stack[index:i-1]))
index = i + 1
}
}

if linesToSkip >= len(lines) {
return ""
}

return lines[linesToSkip]
}
45 changes: 39 additions & 6 deletions fill_args.go
Original file line number Diff line number Diff line change
@@ -1,23 +1,33 @@
package goconfig

import (
"bytes"
"encoding/json"
"errors"
"flag"
"fmt"
"os"
"reflect"
"strings"
"fmt"
)

var values = map[string]interface{}{}

func FillArgs(c interface{}, args []string) {
type postFillArgs struct {
item
Raw *string
}

func FillArgs(c interface{}, args []string) error {
var f = flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
f.Usage = func() {}
f.SetOutput(os.Stdout)

// Default config flag
f.String("config", "", "Configuration JSON file")

post := []postFillArgs{}

traverse(c, func(i item) {
name_path := strings.ToLower(strings.Join(i.Path, "."))

Expand All @@ -43,7 +53,16 @@ func FillArgs(c interface{}, args []string) {
f.UintVar(i.Ptr.(*uint), name_path, i.Value.Interface().(uint), i.Usage)

} else if reflect.Slice == i.Kind {
panic("Slice is not supported by goconfig at this moment.")

b, _ := json.Marshal(i.Value.Interface())

value := ""
f.StringVar(&value, name_path, string(b), i.Usage)

post = append(post, postFillArgs{
Raw: &value,
item: i,
})

} else {
panic("Kind `" + i.Kind.String() +
Expand All @@ -53,9 +72,23 @@ func FillArgs(c interface{}, args []string) {
})

if err := f.Parse(args); err != nil && err == flag.ErrHelp {
f.SetOutput(os.Stderr)
fmt.Fprint(os.Stderr, "Usage of goconfig:\n\n")
m := bytes.NewBufferString("Usage of goconfig:\n\n")
f.SetOutput(m)
f.PrintDefaults()
os.Exit(1)
return errors.New(m.String())
}

// Postprocess flags: unsupported flags needs to be declared as string
// and parsed later. Here is the place.
for _, p := range post {
err := json.Unmarshal([]byte(*p.Raw), p.Ptr)
if err != nil {
return errors.New(fmt.Sprintf(
"'%s' should be a JSON array: %s",
p.FieldName, err.Error(),
))
}
}

return nil
}
156 changes: 154 additions & 2 deletions fill_args_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package goconfig

import "testing"
import (
"testing"
)

func TestFillArgs(t *testing.T) {
c := struct {
Expand Down Expand Up @@ -29,7 +31,8 @@ func TestFillArgs(t *testing.T) {
"-mystruct.myitem", "nested",
}

FillArgs(&c, args)
err := FillArgs(&c, args)
AssertNil(t, err)

if c.MyBoolTrue != true {
t.Error("MyBoolTrue should be true")
Expand Down Expand Up @@ -68,3 +71,152 @@ func TestFillArgs(t *testing.T) {
}

}

func TestFillArgsWithArrayDefinedUndefined(t *testing.T) {

c := struct {
Defined []string
Undefined []string
}{
Defined: []string{"default", "values"},
}

args := []string{
`-defined=["one","two","three"]`,
`-undefined=["a", "b", "c"]`,
}

err := FillArgs(&c, args)
AssertNil(t, err)

AssertEqual(t, c.Defined, []string{"one", "two", "three"})
AssertEqual(t, c.Undefined, []string{"a", "b", "c"})

}

func TestFillArgsWithArrayPointers(t *testing.T) {

c := struct {
Pointers []*string
}{}

args := []string{
`-pointers=["one","two","three"]`,
}

err := FillArgs(&c, args)
AssertNil(t, err)

one := "one"
two := "two"
three := "three"

AssertEqual(t, c.Pointers, []*string{&one, &two, &three})

}

func TestFillArgsWithArrayScalars(t *testing.T) {

c := struct {
// Bool
MyBool []bool

// String
MyString []string

// Numbers
MyFloat64 []float64
MyInt64 []int64
MyInt []int
MyUint64 []uint64
MyUint []uint
}{}

args := []string{
// Bool
"-mybool", "[true, false, true]",

// String
"-mystring", `["one", "two", "three"]`,

// Numbers
"-myfloat64", "[1.23, 1.24]",
"-myint64", "[123, 124]",
"-myint", "[8888, 9999]",
"-myuint64", "[64, 65]",
"-myuint", "[4444, 5555]",
}

err := FillArgs(&c, args)
AssertNil(t, err)

// Bool
AssertEqual(t, c.MyBool, []bool{true, false, true})

// String
AssertEqual(t, c.MyString, []string{"one", "two", "three"})

// Numbers
AssertEqual(t, c.MyFloat64, []float64{1.23, 1.24})
AssertEqual(t, c.MyInt64, []int64{123, 124})
AssertEqual(t, c.MyInt, []int{8888, 9999})
AssertEqual(t, c.MyUint64, []uint64{64, 65})
AssertEqual(t, c.MyUint, []uint{4444, 5555})

}

func TestFillArgsWithArrayStructs(t *testing.T) {

type mystruct struct {
Name string
Age int
}

c := struct {
MyStruct []mystruct
}{}

args := []string{
"-mystruct", `[{"name":"Fulanez", "age": 33}, {"name":"Menganez", "age": 22}]`,
}

err := FillArgs(&c, args)
AssertNil(t, err)

AssertEqual(t, c.MyStruct, []mystruct{
{Name: "Fulanez", Age: 33},
{Name: "Menganez", Age: 22},
})

}

func TestFillArgsWithArrayMalformed(t *testing.T) {

c := struct {
MyArray []string
}{}

args := []string{
"-myarray", `[1,2,3]`,
}

err := FillArgs(&c, args)
AssertNotNil(t, err)

AssertEqual(t, err.Error(), "'MyArray' should be a JSON "+
"array: json: cannot unmarshal number into Go value of type string")

}

func TestFillArgsParseFail(t *testing.T) {

c := struct{}{}

args := []string{
"-help",
}

err := FillArgs(&c, args)
AssertNotNil(t, err)

}
Loading

0 comments on commit f290e1b

Please sign in to comment.