package param import ( "encoding" "fmt" "reflect" "strconv" "strings" ) var textUnmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem() // Generic parse dispatcher. This function's signature is the interface of all // parse functions. `key` is the entire key that is currently being parsed, such // as "foo[bar][]". `keytail` is the portion of the string that the current // parser is responsible for, for instance "[bar][]". `values` is the list of // values assigned to this key, and `target` is where the resulting typed value // should be Set() to. func parse(key, keytail string, values []string, target reflect.Value) { t := target.Type() if reflect.PtrTo(t).Implements(textUnmarshalerType) { parseTextUnmarshaler(key, keytail, values, target) return } switch k := target.Kind(); k { case reflect.Bool: parseBool(key, keytail, values, target) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: parseInt(key, keytail, values, target) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: parseUint(key, keytail, values, target) case reflect.Float32, reflect.Float64: parseFloat(key, keytail, values, target) case reflect.Map: parseMap(key, keytail, values, target) case reflect.Ptr: parsePtr(key, keytail, values, target) case reflect.Slice: parseSlice(key, keytail, values, target) case reflect.String: parseString(key, keytail, values, target) case reflect.Struct: parseStruct(key, keytail, values, target) default: pebkac("unsupported object of type %v and kind %v.", target.Type(), k) } } // We pass down both the full key ("foo[bar][]") and the part the current layer // is responsible for making sense of ("[bar][]"). This computes the other thing // you probably want to know, which is the path you took to get here ("foo"). func kpath(key, keytail string) string { l, t := len(key), len(keytail) return key[:l-t] } // Helper for validating that a value has been passed exactly once, and that the // user is not attempting to nest on the key. func primitive(tipe, key, keytail string, values []string) { if keytail != "" { perr("expected %s for key %q, got nested value", tipe, kpath(key, keytail)) } if len(values) != 1 { perr("expected %s for key %q, but key passed %v times", tipe, kpath(key, keytail), len(values)) } } func keyed(tipe reflect.Type, key, keytail string) (string, string) { idx := strings.IndexRune(keytail, ']') // Keys must be at least 1 rune wide: we refuse to use the empty string // as the key if len(keytail) < 3 || keytail[0] != '[' || idx < 2 { perr("expected a square bracket delimited index for %q "+ "(of type %v)", kpath(key, keytail), tipe) } return keytail[1:idx], keytail[idx+1:] } func parseTextUnmarshaler(key, keytail string, values []string, target reflect.Value) { primitive("encoding.TextUnmarshaler", key, keytail, values) tu := target.Addr().Interface().(encoding.TextUnmarshaler) err := tu.UnmarshalText([]byte(values[0])) if err != nil { perr("error while calling UnmarshalText on %v for key %q: %v", target.Type(), kpath(key, keytail), err) } } func parseBool(key, keytail string, values []string, target reflect.Value) { primitive("bool", key, keytail, values) switch values[0] { case "true", "1", "on": target.SetBool(true) case "false", "0", "": target.SetBool(false) default: perr("could not parse key %q as bool", kpath(key, keytail)) } } func parseInt(key, keytail string, values []string, target reflect.Value) { primitive("int", key, keytail, values) t := target.Type() i, err := strconv.ParseInt(values[0], 10, t.Bits()) if err != nil { perr("error parsing key %q as int: %v", kpath(key, keytail), err) } target.SetInt(i) } func parseUint(key, keytail string, values []string, target reflect.Value) { primitive("uint", key, keytail, values) t := target.Type() i, err := strconv.ParseUint(values[0], 10, t.Bits()) if err != nil { perr("error parsing key %q as uint: %v", kpath(key, keytail), err) } target.SetUint(i) } func parseFloat(key, keytail string, values []string, target reflect.Value) { primitive("float", key, keytail, values) t := target.Type() f, err := strconv.ParseFloat(values[0], t.Bits()) if err != nil { perr("error parsing key %q as float: %v", kpath(key, keytail), err) } target.SetFloat(f) } func parseString(key, keytail string, values []string, target reflect.Value) { primitive("string", key, keytail, values) target.SetString(values[0]) } func parseSlice(key, keytail string, values []string, target reflect.Value) { // BUG(carl): We currently do not handle slices of nested types. If // support is needed, the implementation probably could be fleshed out. if keytail != "[]" { perr("unexpected array nesting for key %q: %q", kpath(key, keytail), keytail) } t := target.Type() slice := reflect.MakeSlice(t, len(values), len(values)) kp := kpath(key, keytail) for i, _ := range values { // We actually cheat a little bit and modify the key so we can // generate better debugging messages later key := fmt.Sprintf("%s[%d]", kp, i) parse(key, "", values[i:i+1], slice.Index(i)) } target.Set(slice) } func parseMap(key, keytail string, values []string, target reflect.Value) { t := target.Type() mapkey, maptail := keyed(t, key, keytail) // BUG(carl): We don't support any map keys except strings, although // there's no reason we shouldn't be able to throw the value through our // unparsing stack. var mk reflect.Value if t.Key().Kind() == reflect.String { mk = reflect.ValueOf(mapkey).Convert(t.Key()) } else { pebkac("key for map %v isn't a string (it's a %v).", t, t.Key()) } if target.IsNil() { target.Set(reflect.MakeMap(t)) } val := target.MapIndex(mk) if !val.IsValid() || !val.CanSet() { // It's a teensy bit annoying that the value returned by // MapIndex isn't Set()table if the key exists. val = reflect.New(t.Elem()).Elem() } parse(key, maptail, values, val) target.SetMapIndex(mk, val) } func parseStruct(key, keytail string, values []string, target reflect.Value) { t := target.Type() sk, skt := keyed(t, key, keytail) cache := cacheStruct(t) parseStructField(cache, key, sk, skt, values, target) } func parsePtr(key, keytail string, values []string, target reflect.Value) { t := target.Type() if target.IsNil() { target.Set(reflect.New(t.Elem())) } parse(key, keytail, values, target.Elem()) }