Skip to content

Instantly share code, notes, and snippets.

@blockloop
Last active June 28, 2016 21:53
Show Gist options
  • Save blockloop/d49f21aaeb1a6c06958aef3d595fd024 to your computer and use it in GitHub Desktop.
Save blockloop/d49f21aaeb1a6c06958aef3d595fd024 to your computer and use it in GitHub Desktop.
package main
import (
"bufio"
"os"
"reflect"
"strconv"
"strings"
log "github.com/Sirupsen/logrus"
"github.com/pkg/errors"
)
const (
whiteSpaces = " \t"
)
// Config is a representation of the configuration for this application
type Config struct {
Port string `config:"PORT"`
Host string `config:"HOST"`
Debug bool `config:"DEBUG"`
TLSCert string `config:"TLSCERT"`
TLSKey string `config:"TLSKEY"`
DBConStr string `config:"DBCONSTR"`
LogstashHost string `config:"LOGSTASHHOST"`
DBMaxIdle int `config:"DBMAXIDLE"`
DBMaxOpen int `config:"DBMAXOPEN"`
}
func parseConfig(filePath string) (*Config, error) {
// start with Environment variables because they are lowest precedence
config := &Config{}
for _, kv := range os.Environ() {
for i, c := range kv {
if c == '=' {
if err := setConfig(config, kv[:i], kv[i+1:]); err != nil {
return nil, errors.Wrapf(err, "error with environment variable")
}
break
}
}
}
// read configuration file
file, err := os.Open(filePath)
if err != nil {
return nil, errors.Wrapf(err, "could not read configuration file")
}
defer file.Close()
scanner := bufio.NewScanner(file)
lineNo := 1
for scanner.Scan() {
line := strings.TrimLeft(scanner.Text(), whiteSpaces)
// skip commented lines and lines with 0 length
if len(line) == 0 || strings.HasPrefix(line, "#") {
continue
}
lineOk := false
Line:
for i, c := range line {
if c == '=' {
lineOk = true
if err := setConfig(config, line[:i], line[i+1:]); err != nil {
return nil, errors.Wrapf(err, "error with configuration value")
}
break Line
}
}
// lineOk is set to indicate that the line was in the correct format (i.e. key=value)
if !lineOk {
return nil, errors.Errorf("Line %d isn't in the correct format. Expected 'KEY=value', Got '%s'", lineNo, line)
}
lineNo++
}
if err := scanner.Err(); err != nil {
log.Fatal(err)
}
return config, nil
}
func setConfig(conf *Config, k string, v string) error {
val := reflect.ValueOf(conf).Elem()
for i := 0; i < val.NumField(); i++ {
valueField := val.Field(i)
typeField := val.Type().Field(i)
if k == typeField.Tag.Get("config") {
if !valueField.CanSet() {
return errors.Errorf("Can't set value of %s because CanSet() returned false. See https://golang.org/pkg/reflect/#Value.CanSet for details", typeField.Name)
}
// valueField.Set(v)
if valueField.Kind() == reflect.Int {
i, err := strconv.Atoi(v)
if err != nil {
return errors.Wrapf(err, "Expected int. Got '%s'", v)
}
x := int64(i)
if !valueField.OverflowInt(x) {
valueField.SetInt(x)
} else {
return errors.Errorf("Number would overflow struct. %s", k)
}
} else if valueField.Kind() == reflect.String {
valueField.SetString(v)
} else if valueField.Kind() == reflect.Bool {
b, err := strconv.ParseBool(v)
if err != nil {
return errors.Wrapf(err, "Expected bool. Got '%s'", v)
}
valueField.SetBool(b)
} else {
return errors.Errorf("Don't know how to set type '%s'", valueField.Kind())
}
break
}
}
return nil
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment