| package gcfg |
| |
| import ( |
| "fmt" |
| "io" |
| "reflect" |
| "strings" |
| ) |
| |
| const ( |
| // Default value string in case a value for a variable isn't provided. |
| defaultValue = "true" |
| ) |
| |
| func fieldFold(v reflect.Value, name string) reflect.Value { |
| n := strings.Replace(name, "-", "_", -1) |
| return v.FieldByNameFunc(func(fieldName string) bool { |
| return strings.EqualFold(n, fieldName) |
| }) |
| } |
| |
| func set(cfg interface{}, sect, sub, name, value string) error { |
| vPCfg := reflect.ValueOf(cfg) |
| if vPCfg.Kind() != reflect.Ptr || vPCfg.Elem().Kind() != reflect.Struct { |
| panic(fmt.Errorf("config must be a pointer to a struct")) |
| } |
| vCfg := vPCfg.Elem() |
| vSect := fieldFold(vCfg, sect) |
| if !vSect.IsValid() { |
| return fmt.Errorf("invalid section: section %q", sect) |
| } |
| if vSect.Kind() == reflect.Map { |
| vst := vSect.Type() |
| if vst.Key().Kind() != reflect.String || |
| vst.Elem().Kind() != reflect.Ptr || |
| vst.Elem().Elem().Kind() != reflect.Struct { |
| panic(fmt.Errorf("map field for section must have string keys and "+ |
| " pointer-to-struct values: section %q", sect)) |
| } |
| if vSect.IsNil() { |
| vSect.Set(reflect.MakeMap(vst)) |
| } |
| k := reflect.ValueOf(sub) |
| pv := vSect.MapIndex(k) |
| if !pv.IsValid() { |
| vType := vSect.Type().Elem().Elem() |
| pv = reflect.New(vType) |
| vSect.SetMapIndex(k, pv) |
| } |
| vSect = pv.Elem() |
| } else if vSect.Kind() != reflect.Struct { |
| panic(fmt.Errorf("field for section must be a map or a struct: "+ |
| "section %q", sect)) |
| } else if sub != "" { |
| return fmt.Errorf("invalid subsection: "+ |
| "section %q subsection %q", sect, sub) |
| } |
| vName := fieldFold(vSect, name) |
| if !vName.IsValid() { |
| return fmt.Errorf("invalid variable: "+ |
| "section %q subsection %q variable %q", sect, sub, name) |
| } |
| vAddr := vName.Addr().Interface() |
| switch v := vAddr.(type) { |
| case *string: |
| *v = value |
| return nil |
| case *bool: |
| vAddr = (*gbool)(v) |
| } |
| // attempt to read an extra rune to make sure the value is consumed |
| var r rune |
| n, err := fmt.Sscanf(value, "%v%c", vAddr, &r) |
| switch { |
| case n < 1 || n == 1 && err != io.EOF: |
| return fmt.Errorf("failed to parse %q as %v: %v", value, vName.Type(), |
| err) |
| case n > 1: |
| return fmt.Errorf("failed to parse %q as %v: extra characters", value, |
| vName.Type()) |
| case n == 1 && err == io.EOF: |
| return nil |
| } |
| panic("never reached") |
| } |