github.com/10XDev/rclone@v1.52.3-0.20200626220027-16af9ab76b2a/fs/config/configstruct/configstruct.go (about) 1 // Package configstruct parses unstructured maps into structures 2 package configstruct 3 4 import ( 5 "fmt" 6 "reflect" 7 "regexp" 8 "strings" 9 10 "github.com/pkg/errors" 11 "github.com/rclone/rclone/fs/config/configmap" 12 ) 13 14 var matchUpper = regexp.MustCompile("([A-Z]+)") 15 16 // camelToSnake converts CamelCase to snake_case 17 func camelToSnake(in string) string { 18 out := matchUpper.ReplaceAllString(in, "_$1") 19 out = strings.ToLower(out) 20 out = strings.Trim(out, "_") 21 return out 22 } 23 24 // StringToInterface turns in into an interface{} the same type as def 25 func StringToInterface(def interface{}, in string) (newValue interface{}, err error) { 26 typ := reflect.TypeOf(def) 27 switch typ.Kind() { 28 case reflect.String: 29 // Pass strings unmodified 30 return in, nil 31 } 32 // Otherwise parse with Sscanln 33 // 34 // This means any types we use here must implement fmt.Scanner 35 o := reflect.New(typ) 36 n, err := fmt.Sscanln(in, o.Interface()) 37 if err != nil { 38 return newValue, errors.Wrapf(err, "parsing %q as %T failed", in, def) 39 } 40 if n != 1 { 41 return newValue, errors.New("no items parsed") 42 } 43 return o.Elem().Interface(), nil 44 } 45 46 // Item describes a single entry in the options structure 47 type Item struct { 48 Name string // snake_case 49 Field string // CamelCase 50 Num int // number of the field in the struct 51 Value interface{} 52 } 53 54 // Items parses the opt struct and returns a slice of Item objects. 55 // 56 // opt must be a pointer to a struct. The struct should have entirely 57 // public fields. 58 // 59 // The config_name is looked up in a struct tag called "config" or if 60 // not found is the field name converted from CamelCase to snake_case. 61 func Items(opt interface{}) (items []Item, err error) { 62 def := reflect.ValueOf(opt) 63 if def.Kind() != reflect.Ptr { 64 return nil, errors.New("argument must be a pointer") 65 } 66 def = def.Elem() // indirect the pointer 67 if def.Kind() != reflect.Struct { 68 return nil, errors.New("argument must be a pointer to a struct") 69 } 70 defType := def.Type() 71 for i := 0; i < def.NumField(); i++ { 72 field := defType.Field(i) 73 fieldName := field.Name 74 configName, ok := field.Tag.Lookup("config") 75 if !ok { 76 configName = camelToSnake(fieldName) 77 } 78 defaultItem := Item{ 79 Name: configName, 80 Field: fieldName, 81 Num: i, 82 Value: def.Field(i).Interface(), 83 } 84 items = append(items, defaultItem) 85 } 86 return items, nil 87 } 88 89 // Set interprets the field names in defaults and looks up config 90 // values in the config passed in. Any values found in config will be 91 // set in the opt structure. 92 // 93 // opt must be a pointer to a struct. The struct should have entirely 94 // public fields. The field names are converted from CamelCase to 95 // snake_case and looked up in the config supplied or a 96 // `config:"field_name"` is looked up. 97 // 98 // If items are found then they are converted from string to native 99 // types and set in opt. 100 // 101 // All the field types in the struct must implement fmt.Scanner. 102 func Set(config configmap.Getter, opt interface{}) (err error) { 103 defaultItems, err := Items(opt) 104 if err != nil { 105 return err 106 } 107 defStruct := reflect.ValueOf(opt).Elem() 108 for _, defaultItem := range defaultItems { 109 newValue := defaultItem.Value 110 if configValue, ok := config.Get(defaultItem.Name); ok { 111 var newNewValue interface{} 112 newNewValue, err = StringToInterface(newValue, configValue) 113 if err != nil { 114 // Mask errors if setting an empty string as 115 // it isn't valid for all types. This makes 116 // empty string be the equivalent of unset. 117 if configValue != "" { 118 return errors.Wrapf(err, "couldn't parse config item %q = %q as %T", defaultItem.Name, configValue, defaultItem.Value) 119 } 120 } else { 121 newValue = newNewValue 122 } 123 } 124 defStruct.Field(defaultItem.Num).Set(reflect.ValueOf(newValue)) 125 } 126 return nil 127 }