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  }