|  | // Copyright 2017 Frank Schroeder. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style | 
|  | // license that can be found in the LICENSE file. | 
|  |  | 
|  | package properties | 
|  |  | 
|  | import ( | 
|  | "fmt" | 
|  | "reflect" | 
|  | "strconv" | 
|  | "strings" | 
|  | "time" | 
|  | ) | 
|  |  | 
|  | // Decode assigns property values to exported fields of a struct. | 
|  | // | 
|  | // Decode traverses v recursively and returns an error if a value cannot be | 
|  | // converted to the field type or a required value is missing for a field. | 
|  | // | 
|  | // The following type dependent decodings are used: | 
|  | // | 
|  | // String, boolean, numeric fields have the value of the property key assigned. | 
|  | // The property key name is the name of the field. A different key and a default | 
|  | // value can be set in the field's tag. Fields without default value are | 
|  | // required. If the value cannot be converted to the field type an error is | 
|  | // returned. | 
|  | // | 
|  | // time.Duration fields have the result of time.ParseDuration() assigned. | 
|  | // | 
|  | // time.Time fields have the vaule of time.Parse() assigned. The default layout | 
|  | // is time.RFC3339 but can be set in the field's tag. | 
|  | // | 
|  | // Arrays and slices of string, boolean, numeric, time.Duration and time.Time | 
|  | // fields have the value interpreted as a comma separated list of values. The | 
|  | // individual values are trimmed of whitespace and empty values are ignored. A | 
|  | // default value can be provided as a semicolon separated list in the field's | 
|  | // tag. | 
|  | // | 
|  | // Struct fields are decoded recursively using the field name plus "." as | 
|  | // prefix. The prefix (without dot) can be overridden in the field's tag. | 
|  | // Default values are not supported in the field's tag. Specify them on the | 
|  | // fields of the inner struct instead. | 
|  | // | 
|  | // Map fields must have a key of type string and are decoded recursively by | 
|  | // using the field's name plus ".' as prefix and the next element of the key | 
|  | // name as map key. The prefix (without dot) can be overridden in the field's | 
|  | // tag. Default values are not supported. | 
|  | // | 
|  | // Examples: | 
|  | // | 
|  | //     // Field is ignored. | 
|  | //     Field int `properties:"-"` | 
|  | // | 
|  | //     // Field is assigned value of 'Field'. | 
|  | //     Field int | 
|  | // | 
|  | //     // Field is assigned value of 'myName'. | 
|  | //     Field int `properties:"myName"` | 
|  | // | 
|  | //     // Field is assigned value of key 'myName' and has a default | 
|  | //     // value 15 if the key does not exist. | 
|  | //     Field int `properties:"myName,default=15"` | 
|  | // | 
|  | //     // Field is assigned value of key 'Field' and has a default | 
|  | //     // value 15 if the key does not exist. | 
|  | //     Field int `properties:",default=15"` | 
|  | // | 
|  | //     // Field is assigned value of key 'date' and the date | 
|  | //     // is in format 2006-01-02 | 
|  | //     Field time.Time `properties:"date,layout=2006-01-02"` | 
|  | // | 
|  | //     // Field is assigned the non-empty and whitespace trimmed | 
|  | //     // values of key 'Field' split by commas. | 
|  | //     Field []string | 
|  | // | 
|  | //     // Field is assigned the non-empty and whitespace trimmed | 
|  | //     // values of key 'Field' split by commas and has a default | 
|  | //     // value ["a", "b", "c"] if the key does not exist. | 
|  | //     Field []string `properties:",default=a;b;c"` | 
|  | // | 
|  | //     // Field is decoded recursively with "Field." as key prefix. | 
|  | //     Field SomeStruct | 
|  | // | 
|  | //     // Field is decoded recursively with "myName." as key prefix. | 
|  | //     Field SomeStruct `properties:"myName"` | 
|  | // | 
|  | //     // Field is decoded recursively with "Field." as key prefix | 
|  | //     // and the next dotted element of the key as map key. | 
|  | //     Field map[string]string | 
|  | // | 
|  | //     // Field is decoded recursively with "myName." as key prefix | 
|  | //     // and the next dotted element of the key as map key. | 
|  | //     Field map[string]string `properties:"myName"` | 
|  | func (p *Properties) Decode(x interface{}) error { | 
|  | t, v := reflect.TypeOf(x), reflect.ValueOf(x) | 
|  | if t.Kind() != reflect.Ptr || v.Elem().Type().Kind() != reflect.Struct { | 
|  | return fmt.Errorf("not a pointer to struct: %s", t) | 
|  | } | 
|  | if err := dec(p, "", nil, nil, v); err != nil { | 
|  | return err | 
|  | } | 
|  | return nil | 
|  | } | 
|  |  | 
|  | func dec(p *Properties, key string, def *string, opts map[string]string, v reflect.Value) error { | 
|  | t := v.Type() | 
|  |  | 
|  | // value returns the property value for key or the default if provided. | 
|  | value := func() (string, error) { | 
|  | if val, ok := p.Get(key); ok { | 
|  | return val, nil | 
|  | } | 
|  | if def != nil { | 
|  | return *def, nil | 
|  | } | 
|  | return "", fmt.Errorf("missing required key %s", key) | 
|  | } | 
|  |  | 
|  | // conv converts a string to a value of the given type. | 
|  | conv := func(s string, t reflect.Type) (val reflect.Value, err error) { | 
|  | var v interface{} | 
|  |  | 
|  | switch { | 
|  | case isDuration(t): | 
|  | v, err = time.ParseDuration(s) | 
|  |  | 
|  | case isTime(t): | 
|  | layout := opts["layout"] | 
|  | if layout == "" { | 
|  | layout = time.RFC3339 | 
|  | } | 
|  | v, err = time.Parse(layout, s) | 
|  |  | 
|  | case isBool(t): | 
|  | v, err = boolVal(s), nil | 
|  |  | 
|  | case isString(t): | 
|  | v, err = s, nil | 
|  |  | 
|  | case isFloat(t): | 
|  | v, err = strconv.ParseFloat(s, 64) | 
|  |  | 
|  | case isInt(t): | 
|  | v, err = strconv.ParseInt(s, 10, 64) | 
|  |  | 
|  | case isUint(t): | 
|  | v, err = strconv.ParseUint(s, 10, 64) | 
|  |  | 
|  | default: | 
|  | return reflect.Zero(t), fmt.Errorf("unsupported type %s", t) | 
|  | } | 
|  | if err != nil { | 
|  | return reflect.Zero(t), err | 
|  | } | 
|  | return reflect.ValueOf(v).Convert(t), nil | 
|  | } | 
|  |  | 
|  | // keydef returns the property key and the default value based on the | 
|  | // name of the struct field and the options in the tag. | 
|  | keydef := func(f reflect.StructField) (string, *string, map[string]string) { | 
|  | _key, _opts := parseTag(f.Tag.Get("properties")) | 
|  |  | 
|  | var _def *string | 
|  | if d, ok := _opts["default"]; ok { | 
|  | _def = &d | 
|  | } | 
|  | if _key != "" { | 
|  | return _key, _def, _opts | 
|  | } | 
|  | return f.Name, _def, _opts | 
|  | } | 
|  |  | 
|  | switch { | 
|  | case isDuration(t) || isTime(t) || isBool(t) || isString(t) || isFloat(t) || isInt(t) || isUint(t): | 
|  | s, err := value() | 
|  | if err != nil { | 
|  | return err | 
|  | } | 
|  | val, err := conv(s, t) | 
|  | if err != nil { | 
|  | return err | 
|  | } | 
|  | v.Set(val) | 
|  |  | 
|  | case isPtr(t): | 
|  | return dec(p, key, def, opts, v.Elem()) | 
|  |  | 
|  | case isStruct(t): | 
|  | for i := 0; i < v.NumField(); i++ { | 
|  | fv := v.Field(i) | 
|  | fk, def, opts := keydef(t.Field(i)) | 
|  | if !fv.CanSet() { | 
|  | return fmt.Errorf("cannot set %s", t.Field(i).Name) | 
|  | } | 
|  | if fk == "-" { | 
|  | continue | 
|  | } | 
|  | if key != "" { | 
|  | fk = key + "." + fk | 
|  | } | 
|  | if err := dec(p, fk, def, opts, fv); err != nil { | 
|  | return err | 
|  | } | 
|  | } | 
|  | return nil | 
|  |  | 
|  | case isArray(t): | 
|  | val, err := value() | 
|  | if err != nil { | 
|  | return err | 
|  | } | 
|  | vals := split(val, ";") | 
|  | a := reflect.MakeSlice(t, 0, len(vals)) | 
|  | for _, s := range vals { | 
|  | val, err := conv(s, t.Elem()) | 
|  | if err != nil { | 
|  | return err | 
|  | } | 
|  | a = reflect.Append(a, val) | 
|  | } | 
|  | v.Set(a) | 
|  |  | 
|  | case isMap(t): | 
|  | valT := t.Elem() | 
|  | m := reflect.MakeMap(t) | 
|  | for postfix := range p.FilterStripPrefix(key + ".").m { | 
|  | pp := strings.SplitN(postfix, ".", 2) | 
|  | mk, mv := pp[0], reflect.New(valT) | 
|  | if err := dec(p, key+"."+mk, nil, nil, mv); err != nil { | 
|  | return err | 
|  | } | 
|  | m.SetMapIndex(reflect.ValueOf(mk), mv.Elem()) | 
|  | } | 
|  | v.Set(m) | 
|  |  | 
|  | default: | 
|  | return fmt.Errorf("unsupported type %s", t) | 
|  | } | 
|  | return nil | 
|  | } | 
|  |  | 
|  | // split splits a string on sep, trims whitespace of elements | 
|  | // and omits empty elements | 
|  | func split(s string, sep string) []string { | 
|  | var a []string | 
|  | for _, v := range strings.Split(s, sep) { | 
|  | if v = strings.TrimSpace(v); v != "" { | 
|  | a = append(a, v) | 
|  | } | 
|  | } | 
|  | return a | 
|  | } | 
|  |  | 
|  | // parseTag parses a "key,k=v,k=v,..." | 
|  | func parseTag(tag string) (key string, opts map[string]string) { | 
|  | opts = map[string]string{} | 
|  | for i, s := range strings.Split(tag, ",") { | 
|  | if i == 0 { | 
|  | key = s | 
|  | continue | 
|  | } | 
|  |  | 
|  | pp := strings.SplitN(s, "=", 2) | 
|  | if len(pp) == 1 { | 
|  | opts[pp[0]] = "" | 
|  | } else { | 
|  | opts[pp[0]] = pp[1] | 
|  | } | 
|  | } | 
|  | return key, opts | 
|  | } | 
|  |  | 
|  | func isArray(t reflect.Type) bool    { return t.Kind() == reflect.Array || t.Kind() == reflect.Slice } | 
|  | func isBool(t reflect.Type) bool     { return t.Kind() == reflect.Bool } | 
|  | func isDuration(t reflect.Type) bool { return t == reflect.TypeOf(time.Second) } | 
|  | func isMap(t reflect.Type) bool      { return t.Kind() == reflect.Map } | 
|  | func isPtr(t reflect.Type) bool      { return t.Kind() == reflect.Ptr } | 
|  | func isString(t reflect.Type) bool   { return t.Kind() == reflect.String } | 
|  | func isStruct(t reflect.Type) bool   { return t.Kind() == reflect.Struct } | 
|  | func isTime(t reflect.Type) bool     { return t == reflect.TypeOf(time.Time{}) } | 
|  | func isFloat(t reflect.Type) bool { | 
|  | return t.Kind() == reflect.Float32 || t.Kind() == reflect.Float64 | 
|  | } | 
|  | func isInt(t reflect.Type) bool { | 
|  | return t.Kind() == reflect.Int || t.Kind() == reflect.Int8 || t.Kind() == reflect.Int16 || t.Kind() == reflect.Int32 || t.Kind() == reflect.Int64 | 
|  | } | 
|  | func isUint(t reflect.Type) bool { | 
|  | return t.Kind() == reflect.Uint || t.Kind() == reflect.Uint8 || t.Kind() == reflect.Uint16 || t.Kind() == reflect.Uint32 || t.Kind() == reflect.Uint64 | 
|  | } |