package core import ( "errors" "fmt" "reflect" "strconv" "strings" ) // the tag used to denote the name of the question const tagName = "survey" // add a few interfaces so users can configure how the prompt values are set type settable interface { WriteAnswer(field string, value interface{}) error } func WriteAnswer(t interface{}, name string, v interface{}) (err error) { // if the field is a custom type if s, ok := t.(settable); ok { // use the interface method return s.WriteAnswer(name, v) } // the target to write to target := reflect.ValueOf(t) // the value to write from value := reflect.ValueOf(v) // make sure we are writing to a pointer if target.Kind() != reflect.Ptr { return errors.New("you must pass a pointer as the target of a Write operation") } // the object "inside" of the target pointer elem := target.Elem() // handle the special types switch elem.Kind() { // if we are writing to a struct case reflect.Struct: // get the name of the field that matches the string we were given fieldIndex, err := findFieldIndex(elem, name) // if something went wrong if err != nil { // bubble up return err } field := elem.Field(fieldIndex) // handle references to the settable interface aswell if s, ok := field.Interface().(settable); ok { // use the interface method return s.WriteAnswer(name, v) } if field.CanAddr() { if s, ok := field.Addr().Interface().(settable); ok { // use the interface method return s.WriteAnswer(name, v) } } // copy the value over to the normal struct return copy(field, value) case reflect.Map: mapType := reflect.TypeOf(t).Elem() if mapType.Key().Kind() != reflect.String || mapType.Elem().Kind() != reflect.Interface { return errors.New("answer maps must be of type map[string]interface") } mt := *t.(*map[string]interface{}) mt[name] = value.Interface() return nil } // otherwise just copy the value to the target return copy(elem, value) } // BUG(AlecAivazis): the current implementation might cause weird conflicts if there are // two fields with same name that only differ by casing. func findFieldIndex(s reflect.Value, name string) (int, error) { // the type of the value sType := s.Type() // first look for matching tags so we can overwrite matching field names for i := 0; i < sType.NumField(); i++ { // the field we are current scanning field := sType.Field(i) // the value of the survey tag tag := field.Tag.Get(tagName) // if the tag matches the name we are looking for if tag != "" && tag == name { // then we found our index return i, nil } } // then look for matching names for i := 0; i < sType.NumField(); i++ { // the field we are current scanning field := sType.Field(i) // if the name of the field matches what we're looking for if strings.ToLower(field.Name) == strings.ToLower(name) { return i, nil } } // we didn't find the field return -1, fmt.Errorf("could not find field matching %v", name) } // isList returns true if the element is something we can Len() func isList(v reflect.Value) bool { switch v.Type().Kind() { case reflect.Array, reflect.Slice: return true default: return false } } // Write takes a value and copies it to the target func copy(t reflect.Value, v reflect.Value) (err error) { // if something ends up panicing we need to catch it in a deferred func defer func() { if r := recover(); r != nil { // if we paniced with an error if _, ok := r.(error); ok { // cast the result to an error object err = r.(error) } else if _, ok := r.(string); ok { // otherwise we could have paniced with a string so wrap it in an error err = errors.New(r.(string)) } } }() // if we are copying from a string result to something else if v.Kind() == reflect.String && v.Type() != t.Type() { var castVal interface{} var casterr error vString := v.Interface().(string) switch t.Kind() { case reflect.Bool: castVal, casterr = strconv.ParseBool(vString) case reflect.Int: castVal, casterr = strconv.Atoi(vString) case reflect.Int8: var val64 int64 val64, casterr = strconv.ParseInt(vString, 10, 8) if casterr == nil { castVal = int8(val64) } case reflect.Int16: var val64 int64 val64, casterr = strconv.ParseInt(vString, 10, 16) if casterr == nil { castVal = int16(val64) } case reflect.Int32: var val64 int64 val64, casterr = strconv.ParseInt(vString, 10, 32) if casterr == nil { castVal = int32(val64) } case reflect.Int64: castVal, casterr = strconv.ParseInt(vString, 10, 64) case reflect.Uint: var val64 uint64 val64, casterr = strconv.ParseUint(vString, 10, 8) if casterr == nil { castVal = uint(val64) } case reflect.Uint8: var val64 uint64 val64, casterr = strconv.ParseUint(vString, 10, 8) if casterr == nil { castVal = uint8(val64) } case reflect.Uint16: var val64 uint64 val64, casterr = strconv.ParseUint(vString, 10, 16) if casterr == nil { castVal = uint16(val64) } case reflect.Uint32: var val64 uint64 val64, casterr = strconv.ParseUint(vString, 10, 32) if casterr == nil { castVal = uint32(val64) } case reflect.Uint64: castVal, casterr = strconv.ParseUint(vString, 10, 64) case reflect.Float32: var val64 float64 val64, casterr = strconv.ParseFloat(vString, 32) if casterr == nil { castVal = float32(val64) } case reflect.Float64: castVal, casterr = strconv.ParseFloat(vString, 64) default: return fmt.Errorf("Unable to convert from string to type %s", t.Kind()) } if casterr != nil { return casterr } t.Set(reflect.ValueOf(castVal)) return } // if we are copying from one slice or array to another if isList(v) && isList(t) { // loop over every item in the desired value for i := 0; i < v.Len(); i++ { // write to the target given its kind switch t.Kind() { // if its a slice case reflect.Slice: // an object of the correct type obj := reflect.Indirect(reflect.New(t.Type().Elem())) // write the appropriate value to the obj and catch any errors if err := copy(obj, v.Index(i)); err != nil { return err } // just append the value to the end t.Set(reflect.Append(t, obj)) // otherwise it could be an array case reflect.Array: // set the index to the appropriate value copy(t.Slice(i, i+1).Index(0), v.Index(i)) } } } else { // set the value to the target t.Set(v) } // we're done return }