package opts import ( "fmt" "reflect" "strconv" "strings" ) type fieldSpec struct { name string inline bool required bool positive bool defaultValue string } type Opts map[string]string func Parse(s string) (string, Opts) { v := strings.Split(s, ",") m := map[string]string{} for _, t := range v[1:] { kv := strings.SplitN(t, "=", 2) if len(kv) < 2 { m[kv[0]] = "" } else { m[kv[0]] = kv[1] } } return v[0], m } func (opts Opts) Bool(key string) bool { _, ok := opts[key] return ok } func (opts Opts) Configure(v interface{}) error { rv := reflect.ValueOf(v) switch { case rv.Kind() != reflect.Ptr: return fmt.Errorf("opts: configure failed: non-pointer %s", rv.Type().String()) case rv.IsNil(): return fmt.Errorf("opts: configure failed: nil %s", rv.Type().String()) case rv.Elem().Kind() != reflect.Struct: return fmt.Errorf("opts: configure failed: non-struct %s", rv.Elem().Type().String()) } return opts.configure(rv.Elem()) } func getFieldSpec(field reflect.StructField) (fs fieldSpec) { fs.name = strings.ToLower(field.Name) for _, s := range strings.Split(field.Tag.Get("opts"), ",") { switch { case s == "inline": fs.inline = true case s == "required": fs.required = true case s == "positive": fs.positive = true case strings.HasPrefix(s, "default:"): fs.defaultValue = s[8:] } } return } func (opts Opts) setValue(v reflect.Value, fs fieldSpec) error { s, ok := opts[fs.name] kind := v.Kind() if kind == reflect.Bool { if s != "" { return fmt.Errorf("%s: boolean option does not need a value", fs.name) } if ok { v.SetBool(true) } return nil } if s == "" { if fs.required { return fmt.Errorf("%s: expect value", fs.name) } s = fs.defaultValue } if s == "" { return nil } switch kind { case reflect.String: v.SetString(s) case reflect.Int: n, err := strconv.Atoi(s) if err != nil { return fmt.Errorf("%s: %w", fs.name, err) } if n <= 0 { return fmt.Errorf("%s: expect positive", fs.name) } v.SetInt(int64(n)) default: return fmt.Errorf("%s: unsupported type '%s'", fs.name, v.Type().String()) } return nil } func (opts Opts) configure(v reflect.Value) error { m := map[string]bool{} for s := range opts { m[s] = true } var visit func(v reflect.Value) error visit = func(v reflect.Value) error { t := v.Type() for n := 0; n < v.NumField(); n++ { fs := getFieldSpec(t.Field(n)) fv := v.Field(n) if fs.inline { if err := visit(fv); err != nil { return err } } else if fv.CanSet() { if err := opts.setValue(v.Field(n), fs); err != nil { return err } delete(m, fs.name) } } return nil } if err := visit(v); err != nil { return err } if len(m) > 0 { var ss []string for s := range m { ss = append(ss, s) } return fmt.Errorf("unknown options: %s", strings.Join(ss, ",")) } return nil }