123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246 |
- 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
- }
|