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