WASAPhoto/vendor/github.com/ardanlabs/conf/fields.go

395 lines
8.7 KiB
Go

package conf
import (
"encoding"
"fmt"
"reflect"
"strconv"
"strings"
"time"
"unicode"
)
// Field maintains information about a field in the configuration struct.
type Field struct {
Name string
FlagKey []string
EnvKey []string
Field reflect.Value
Options FieldOptions
// Important for flag parsing or any other source where
// booleans might be treated specially.
BoolField bool
}
// FieldOptions maintain flag options for a given field.
type FieldOptions struct {
Help string
DefaultVal string
EnvName string
FlagName string
ShortFlagChar rune
Noprint bool
Required bool
Mask bool
}
// extractFields uses reflection to examine the struct and generate the keys.
func extractFields(prefix []string, target interface{}) ([]Field, error) {
if prefix == nil {
prefix = []string{}
}
s := reflect.ValueOf(target)
if s.Kind() != reflect.Ptr {
return nil, ErrInvalidStruct
}
s = s.Elem()
if s.Kind() != reflect.Struct {
return nil, ErrInvalidStruct
}
targetType := s.Type()
var fields []Field
for i := 0; i < s.NumField(); i++ {
f := s.Field(i)
structField := targetType.Field(i)
// Get the conf tags associated with this item (if any).
fieldTags := structField.Tag.Get("conf")
// If it's ignored or can't be set, move on.
if !f.CanSet() || fieldTags == "-" {
continue
}
fieldName := structField.Name
// Get and options. TODO: Need more.
fieldOpts, err := parseTag(fieldTags)
if err != nil {
return nil, fmt.Errorf("conf: error parsing tags for field %s: %s", fieldName, err)
}
// Generate the field key. This could be ignored.
fieldKey := append(prefix, camelSplit(fieldName)...)
// Drill down through pointers until we bottom out at type or nil.
for f.Kind() == reflect.Ptr {
if f.IsNil() {
// It's not a struct so leave it alone.
if f.Type().Elem().Kind() != reflect.Struct {
break
}
// It is a struct so zero it out.
f.Set(reflect.New(f.Type().Elem()))
}
f = f.Elem()
}
switch {
// If we've found a struct, drill down, appending fields as we go.
case f.Kind() == reflect.Struct:
// Skip if it can deserialize itself.
if setterFrom(f) == nil && textUnmarshaler(f) == nil && binaryUnmarshaler(f) == nil {
// Prefix for any subkeys is the fieldKey, unless it's
// anonymous, then it's just the prefix so far.
innerPrefix := fieldKey
if structField.Anonymous {
innerPrefix = prefix
}
embeddedPtr := f.Addr().Interface()
innerFields, err := extractFields(innerPrefix, embeddedPtr)
if err != nil {
return nil, err
}
fields = append(fields, innerFields...)
}
default:
envKey := fieldKey
if fieldOpts.EnvName != "" {
envKey = strings.Split(fieldOpts.EnvName, "_")
}
flagKey := fieldKey
if fieldOpts.FlagName != "" {
flagKey = strings.Split(fieldOpts.FlagName, "-")
}
fld := Field{
Name: fieldName,
EnvKey: envKey,
FlagKey: flagKey,
Field: f,
Options: fieldOpts,
BoolField: f.Kind() == reflect.Bool,
}
fields = append(fields, fld)
}
}
return fields, nil
}
func parseTag(tagStr string) (FieldOptions, error) {
var f FieldOptions
if tagStr == "" {
return f, nil
}
tagParts := strings.Split(tagStr, ",")
for _, tagPart := range tagParts {
vals := strings.SplitN(tagPart, ":", 2)
tagProp := vals[0]
switch len(vals) {
case 1:
switch tagProp {
case "noprint":
f.Noprint = true
case "required":
f.Required = true
case "mask":
f.Mask = true
}
case 2:
tagPropVal := strings.TrimSpace(vals[1])
if tagPropVal == "" {
return f, fmt.Errorf("tag %q missing a value", tagProp)
}
switch tagProp {
case "short":
if len([]rune(tagPropVal)) != 1 {
return f, fmt.Errorf("short value must be a single rune, got %q", tagPropVal)
}
f.ShortFlagChar = []rune(tagPropVal)[0]
case "default":
f.DefaultVal = tagPropVal
case "env":
f.EnvName = tagPropVal
case "flag":
f.FlagName = tagPropVal
case "help":
f.Help = tagPropVal
}
default:
// TODO: Do we check for integrity issues here?
}
}
// Perform a sanity check.
switch {
case f.Required && f.DefaultVal != "":
return f, fmt.Errorf("cannot set both `required` and `default`")
}
return f, nil
}
// camelSplit takes a string based on camel case and splits it.
func camelSplit(src string) []string {
if src == "" {
return []string{}
}
if len(src) < 2 {
return []string{src}
}
runes := []rune(src)
lastClass := charClass(runes[0])
lastIdx := 0
out := []string{}
// Split into fields based on class of unicode character.
for i, r := range runes {
class := charClass(r)
// If the class has transitioned.
if class != lastClass {
// If going from uppercase to lowercase, we want to retain the last
// uppercase letter for names like FOOBar, which should split to
// FOO Bar.
switch {
case lastClass == classUpper && class != classNumber:
if i-lastIdx > 1 {
out = append(out, string(runes[lastIdx:i-1]))
lastIdx = i - 1
}
default:
out = append(out, string(runes[lastIdx:i]))
lastIdx = i
}
}
if i == len(runes)-1 {
out = append(out, string(runes[lastIdx:]))
}
lastClass = class
}
return out
}
func processField(value string, field reflect.Value) error {
typ := field.Type()
// Look for a Set method.
setter := setterFrom(field)
if setter != nil {
return setter.Set(value)
}
if t := textUnmarshaler(field); t != nil {
return t.UnmarshalText([]byte(value))
}
if b := binaryUnmarshaler(field); b != nil {
return b.UnmarshalBinary([]byte(value))
}
if typ.Kind() == reflect.Ptr {
typ = typ.Elem()
if field.IsNil() {
field.Set(reflect.New(typ))
}
field = field.Elem()
}
switch typ.Kind() {
case reflect.String:
field.SetString(value)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
var (
val int64
err error
)
if field.Kind() == reflect.Int64 && typ.PkgPath() == "time" && typ.Name() == "Duration" {
var d time.Duration
d, err = time.ParseDuration(value)
val = int64(d)
} else {
val, err = strconv.ParseInt(value, 0, typ.Bits())
}
if err != nil {
return err
}
field.SetInt(val)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
val, err := strconv.ParseUint(value, 0, typ.Bits())
if err != nil {
return err
}
field.SetUint(val)
case reflect.Bool:
val, err := strconv.ParseBool(value)
if err != nil {
return err
}
field.SetBool(val)
case reflect.Float32, reflect.Float64:
val, err := strconv.ParseFloat(value, typ.Bits())
if err != nil {
return err
}
field.SetFloat(val)
case reflect.Slice:
vals := strings.Split(value, ";")
sl := reflect.MakeSlice(typ, len(vals), len(vals))
for i, val := range vals {
err := processField(val, sl.Index(i))
if err != nil {
return err
}
}
field.Set(sl)
case reflect.Map:
mp := reflect.MakeMap(typ)
if len(strings.TrimSpace(value)) != 0 {
pairs := strings.Split(value, ";")
for _, pair := range pairs {
kvpair := strings.Split(pair, ":")
if len(kvpair) != 2 {
return fmt.Errorf("invalid map item: %q", pair)
}
k := reflect.New(typ.Key()).Elem()
err := processField(kvpair[0], k)
if err != nil {
return err
}
v := reflect.New(typ.Elem()).Elem()
err = processField(kvpair[1], v)
if err != nil {
return err
}
mp.SetMapIndex(k, v)
}
}
field.Set(mp)
}
return nil
}
func interfaceFrom(field reflect.Value, fn func(interface{}, *bool)) {
// It may be impossible for a struct field to fail this check.
if !field.CanInterface() {
return
}
var ok bool
fn(field.Interface(), &ok)
if !ok && field.CanAddr() {
fn(field.Addr().Interface(), &ok)
}
}
// Setter is implemented by types can self-deserialize values.
// Any type that implements flag.Value also implements Setter.
type Setter interface {
Set(value string) error
}
func setterFrom(field reflect.Value) (s Setter) {
interfaceFrom(field, func(v interface{}, ok *bool) { s, *ok = v.(Setter) })
return s
}
func textUnmarshaler(field reflect.Value) (t encoding.TextUnmarshaler) {
interfaceFrom(field, func(v interface{}, ok *bool) { t, *ok = v.(encoding.TextUnmarshaler) })
return t
}
func binaryUnmarshaler(field reflect.Value) (b encoding.BinaryUnmarshaler) {
interfaceFrom(field, func(v interface{}, ok *bool) { b, *ok = v.(encoding.BinaryUnmarshaler) })
return b
}
const (
classLower int = iota
classUpper
classNumber
classOther
)
func charClass(r rune) int {
switch {
case unicode.IsLower(r):
return classLower
case unicode.IsUpper(r):
return classUpper
case unicode.IsDigit(r):
return classNumber
}
return classOther
}