Skip to content

Commit

Permalink
Merge pull request #6 from elri/wip
Browse files Browse the repository at this point in the history
Default config file no longer required
  • Loading branch information
elri committed Apr 14, 2023
2 parents fa86c43 + 42aa5f6 commit 674a161
Show file tree
Hide file tree
Showing 13 changed files with 264 additions and 73 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Defaults : {
"port": 99,
}
Config : {
Given config file : {
"user": "root"
"secret": "confsecret"
}
Expand Down Expand Up @@ -54,6 +54,7 @@ The config files may be of the following types:
- yml
- json


## Keep in mind
- There is no case sensitivty, i.e. "pim", "Pim" and "PIM" are all considered the same
- The names of the environmental variables must match that of the struct. It is possible to set a prefix, so that i.e. if "MYVAR_" is set as a prefix, "MYVAR_PIM" will map to the property "pim"/"Pim"/"PIM".
Expand Down
112 changes: 82 additions & 30 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,26 @@ import (
"github.com/pkg/errors"
)

//Aliasing flag.Errorhandling for clarity and easy of use.
type ErrorHandling flag.ErrorHandling

var osExit = os.Exit //to enable testing

var (
envs map[string]interface{}
envPrefix string

writedefconf bool
printconf bool

configErrorHandling flag.ErrorHandling
configErrorHandling ErrorHandling
)

var osExit = os.Exit //to enable testing
var ErrNotAPointer = errors.New("cfg should be pointer")
const (
ContinueOnError = ErrorHandling(flag.ContinueOnError) // Return a descriptive error.
ExitOnError = ErrorHandling(flag.ExitOnError) // Call os.Exit(2) or for -h/-help Exit(0).
PanicOnError = ErrorHandling(flag.PanicOnError) // Call panic with a descriptive error.
)

func init() {
envs = make(map[string]interface{})
Expand All @@ -36,34 +44,56 @@ func init() {
flagSet.Usage = Usage
}

// Init sets the error handling property.
// The error handling for the config package is the same as that
// for the std flag package
func Init(errorHandling flag.ErrorHandling) {
/*
Init sets the global error handling property, as well as the error handling property for the flagset.
The error handling for the config package is similar to that of the standard flag package;
there are three modes: Continue, Panic and Exit.
The default mode is Continue.
*/
func Init(errorHandling ErrorHandling) {
configErrorHandling = errorHandling
flagSet.Init("config", errorHandling)
flagSet.Init("elri/config", flag.ErrorHandling(errorHandling))
}

func handleError(err error) {
switch configErrorHandling {
case flag.ContinueOnError:
case ContinueOnError:
return
case flag.ExitOnError:
case ExitOnError:
osExit(2)
case flag.PanicOnError:
case PanicOnError:
panic(err)
}
}

/*
Set a prefix to use for all environmental variables.
For example to different between what is used in testing and otherwise, the prefix "TEST_" could be used.
The environmental variables TEST_timeout and TEST_angle would then map to the properties 'timeout' and 'angle'.
*/
func SetEnvPrefix(prefix string) {
envPrefix = prefix
}

/*
Set a list of environmental variable names to check when filling out the configuration struct.
The list can consist of variables both containing a set env prefix and not, but the environmental variable that is looked for will be that with the prefix.
That is, if the prefix is set as TEST_ and the list envVarNames is ["timeout", "TEST_angle"], the environmental variables that will be looked for are ["TEST_timeout", "TEST_angle"].
If the environmental variable(s) cannot be find, SetEnvsToParse will return an error containing all the names of the non-existant variables. Note that the error will only be return if
the error handling mode is set to ContinueOnError, else the function will Panic or Exit depending on the mode.
*/
func SetEnvsToParse(envVarNames []string) (err error) {
for _, e := range envVarNames {
eFull := e
if envPrefix != "" {
eFull = envPrefix + e
if !strings.HasPrefix(eFull, envPrefix) {
eFull = envPrefix + e
}
}
envVar, ok := os.LookupEnv(eFull)
if ok {
Expand All @@ -78,18 +108,33 @@ func SetEnvsToParse(envVarNames []string) (err error) {
return
}

/*
Parse all the sources (flags, env vars, default config file) and store the result in the value pointer to by cfg.
If cfg is not a pointer, SetUpConfiguration returns an ErrNotAPointer.
*/
func SetUpConfiguration(cfg interface{}) (err error) {
return setup(cfg, "")
}

/*
Parse all the sources (flags, env vars, given config file, default config file) and store the result in the value pointer to by cfg.
If cfg is not a pointer, SetUpConfigurationWithConfigFile returns an ErrNotAPointer.
The 'filename' must either be an absolute path to the config file, exist in the current working directory, or in one of the directories given as 'dirs'. If the given file cannot be found, the other sources will still be parsed, but an ErrNoConfigFileToParse will be returned.
*/
func SetUpConfigurationWithConfigFile(cfg interface{}, filename string, dirs ...string) (err error) {
return setup(cfg, filename)
}

func setup(cfg interface{}, filename string, dirs ...string) (err error) {
//Check that cfg is pointer
if reflect.ValueOf(cfg).Kind() != reflect.Ptr {
return fmt.Errorf("invalid argument: "+ErrNotAPointer.Error()+"but is %s", reflect.ValueOf(cfg).Kind())
err = fmt.Errorf("[setup]: %w ", ErrNotAPointer)
return
}

// DEFAULT CONFIG FILE
Expand All @@ -102,10 +147,7 @@ func setup(cfg interface{}, filename string, dirs ...string) (err error) {

// GIVEN CONFIG FILE
if filename != "" {
cf_err := ParseConfigFile(cfg, filename, dirs...)
if cf_err != nil {
err = addErr(err, cf_err)
}
err = ParseConfigFile(cfg, filename, dirs...)
}

// ENVIRONMENTAL VARIABLES
Expand Down Expand Up @@ -143,6 +185,10 @@ func setup(cfg interface{}, filename string, dirs ...string) (err error) {
osExit(0)
}

if err != nil {
handleError(err)
}

return
}

Expand Down Expand Up @@ -259,23 +305,29 @@ func setFieldString(toInsert interface{}, fieldName string, fieldVal reflect.Val
return
}

// Creates a string given a ptr to a struct
// E.g.
// type Person struct {
// Name string
// Age int
// }
//
// mio := &Person{Name: "Mio", Age: 9}
//
// String(mio):
// name: Mio
// age: 9
/*
Creates a string given a ptr to a struct.
Example:
type Person struct {
Name string
Age int
}
func printMio() {
mio := &Person{Name: "Mio", Age: 9}
fmt.Println(String(mio))
}
output:
name: Mio
age: 9
*/
func String(c interface{}) string {
return createString(c, true)
}

// Same as String, except ignores zero values e.g. empty strings and zeroes
// Same as String(), except ignores zero values e.g. empty strings and zeroes
func StringIgnoreZeroValues(c interface{}) string {
return createString(c, false)
}
Expand Down
7 changes: 5 additions & 2 deletions config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -411,14 +411,17 @@ func Test_ConfigFail(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
SetDefaultFile(tt.defaultFile)
err = SetDefaultFile(tt.defaultFile)
if err != nil {
err = ErrNoDefaultConfig
}

if tt.cfg == nil {
cfg = new(TestConfig)
} else {
cfg = tt.cfg
}
err = SetUpConfigurationWithConfigFile(cfg, tt.configFile)
err = addErr(err, SetUpConfigurationWithConfigFile(cfg, tt.configFile))
assert.NotNil(t, err)
for _, expectedErr := range tt.expectedErrors {
assert.Contains(t, err.Error(), expectedErr.Error())
Expand Down
18 changes: 12 additions & 6 deletions examples/complexstructs/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,20 @@ type Configuration struct {
Bottles []Bottle `yaml:"bottles" toml:"bottles"`
}

func main() {
config.SetDefaultFile("default_conf.yml")
func createConfig() *Configuration {
config.Init(config.PanicOnError)

//The default file path must be absolut path, since config is set to PanicOnError, the setup will panic if run outside exmaples/complexstructs/
config.SetDefaultFile("default_conf.toml")

cfg := new(Configuration)
err := config.SetUpConfiguration(cfg)
if err != nil {
panic(err)
}
config.SetUpConfiguration(cfg) //disregards error here since error handling mode is set to panic

return cfg
}

func main() {
cfg := createConfig()

fmt.Println(cfg.Welcome)
fmt.Println("Now printing", cfg.Owner.Name+"'s bottles of "+cfg.Type)
Expand Down
12 changes: 12 additions & 0 deletions examples/complexstructs/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package main

import (
"testing"

"github.com/stretchr/testify/assert"
)

func Test_main(t *testing.T) {
main()
assert.True(t, true)
}
29 changes: 29 additions & 0 deletions examples/nodefaultfile/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package main

import (
"flag"
"fmt"

"github.com/elri/config"
)

// -- Configuration struct
type Configuration struct {
Nothing string `yaml:"nothing" toml:"nothing"`
}

var (
_ = flag.String("nothing", "Hello World", "does nothing")
)

func main() {
config.ParseFlags()

cfg := new(Configuration)
err := config.SetUpConfiguration(cfg)
if err != nil {
panic(err)
}
fmt.Println(cfg.Nothing)

}
12 changes: 12 additions & 0 deletions examples/nodefaultfile/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package main

import (
"testing"

"github.com/stretchr/testify/assert"
)

func Test_main(t *testing.T) {
main()
assert.True(t, true)
}
Binary file added examples/nodefaultfile/nodefaultfile
Binary file not shown.
12 changes: 12 additions & 0 deletions examples/simple/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package main

import (
"testing"

"github.com/stretchr/testify/assert"
)

func Test_main(t *testing.T) {
main()
assert.True(t, true)
}

0 comments on commit 674a161

Please sign in to comment.