166 lines
4.6 KiB
Go
166 lines
4.6 KiB
Go
// Package conf lets you manage configuration files in the easiest way possible, without the unnecessary pain.
|
|
package conf
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// The only custom errors this package will return.
|
|
var (
|
|
ErrNoFile = errors.New("conf: the configuration file doesn't exist")
|
|
ErrNotAStruct = errors.New("conf: the passed into/from variable is not a pointer to a struct")
|
|
)
|
|
|
|
// Load unmarshals a file into the struct passed as the argument "into".
|
|
func Load(into interface{}, filename string) error {
|
|
intoValue := reflect.ValueOf(into)
|
|
if intoValue.Kind() != reflect.Ptr || intoValue.Elem().Kind() != reflect.Struct {
|
|
return ErrNotAStruct
|
|
}
|
|
intoValue = intoValue.Elem()
|
|
f, err := ioutil.ReadFile(filename)
|
|
if os.IsNotExist(err) {
|
|
return ErrNoFile
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return loadRaw(intoValue, f)
|
|
}
|
|
|
|
// LoadRaw allows you to load into a struct some raw data bytes.
|
|
func LoadRaw(into interface{}, data []byte) error {
|
|
intoValue := reflect.ValueOf(into)
|
|
if intoValue.Kind() != reflect.Ptr || intoValue.Elem().Kind() != reflect.Struct {
|
|
return ErrNotAStruct
|
|
}
|
|
intoValue = intoValue.Elem()
|
|
return loadRaw(intoValue, data)
|
|
}
|
|
|
|
func loadRaw(intoValue reflect.Value, data []byte) error {
|
|
fvs := Parse(data)
|
|
for _, v := range fvs {
|
|
for i := 0; i < intoValue.Type().NumField(); i++ {
|
|
field := intoValue.Type().Field(i)
|
|
if !intoValue.Field(i).CanSet() {
|
|
continue
|
|
}
|
|
if field.Name == v.Field {
|
|
switch field.Type.Kind() {
|
|
case reflect.String:
|
|
intoValue.Field(i).SetString(v.Value)
|
|
case reflect.Bool:
|
|
boolVal, err := strconv.ParseBool(v.Value)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
intoValue.Field(i).SetBool(boolVal)
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
intVal, err := strconv.ParseInt(v.Value, 10, 64)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
intoValue.Field(i).SetInt(intVal)
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
uintVal, err := strconv.ParseUint(v.Value, 10, 64)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
intoValue.Field(i).SetUint(uintVal)
|
|
case reflect.Float32, reflect.Float64:
|
|
floatVal, err := strconv.ParseFloat(v.Value, 64)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
intoValue.Field(i).SetFloat(floatVal)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// MustLoad has the same behaviour as Load, but panics if it returns an error.
|
|
func MustLoad(into interface{}, filename string) {
|
|
if err := Load(into, filename); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
// MustLoadRaw has the same behaviour as LoadRaw, but panics if it returns an error.
|
|
func MustLoadRaw(into interface{}, data []byte) {
|
|
if err := LoadRaw(into, data); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
// Export uses ExportRaw to put the data into a file, specified with its name.
|
|
func Export(from interface{}, filename string) error {
|
|
data, err := ExportRaw(from)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return ioutil.WriteFile(filename, data, 0644)
|
|
}
|
|
|
|
// ExportRaw can create a []byte that can then be loaded back by LoadRaw to get a struct's original form back.
|
|
// I suck at explaining stuff.
|
|
func ExportRaw(from interface{}) ([]byte, error) {
|
|
fromValue := reflect.ValueOf(from)
|
|
if fromValue.Kind() == reflect.Ptr {
|
|
return ExportRaw(fromValue.Elem().Interface())
|
|
}
|
|
if fromValue.Kind() != reflect.Struct {
|
|
return []byte{}, ErrNotAStruct
|
|
}
|
|
return exportRaw(fromValue), nil
|
|
}
|
|
|
|
func exportRaw(fromValue reflect.Value) []byte {
|
|
var ret []byte
|
|
for i := 0; i < fromValue.Type().NumField(); i++ {
|
|
curfield := fromValue.Field(i)
|
|
curfieldType := fromValue.Type().Field(i)
|
|
|
|
// Dirty hack to ignore that field if we don't support that type.
|
|
if !((curfield.Kind() >= reflect.Bool && curfield.Kind() <= reflect.Uint64) ||
|
|
curfield.Kind() == reflect.String || curfield.Kind() == reflect.Float32 ||
|
|
curfield.Kind() == reflect.Float64) {
|
|
continue
|
|
}
|
|
|
|
/* guten */ tag := curfieldType.Tag.Get("description")
|
|
if tag != "" {
|
|
tag = strings.Replace(tag, "\n", "\n; ", -1)
|
|
ret = append(ret, []byte("; "+tag+"\n")...)
|
|
}
|
|
ret = append(ret, []byte(Escape(curfieldType.Name)+"="+Escape(fmt.Sprint(curfield.Interface()))+"\n")...)
|
|
}
|
|
return ret
|
|
}
|
|
|
|
// MustExport panics if Export returns an error, removing error checking from your code. For the lazy.
|
|
func MustExport(from interface{}, filename string) {
|
|
if err := Export(from, filename); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
// MustExportRaw panics if ExportRaw returns an error, removing error checking from your code. For the lazy.
|
|
func MustExportRaw(from interface{}) []byte {
|
|
data, err := ExportRaw(from)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return data
|
|
}
|