1package core 2 3import ( 4 "errors" 5 "fmt" 6 "reflect" 7 "strconv" 8 "strings" 9) 10 11// the tag used to denote the name of the question 12const tagName = "survey" 13 14// add a few interfaces so users can configure how the prompt values are set 15type settable interface { 16 WriteAnswer(field string, value interface{}) error 17} 18 19func WriteAnswer(t interface{}, name string, v interface{}) (err error) { 20 // if the field is a custom type 21 if s, ok := t.(settable); ok { 22 // use the interface method 23 return s.WriteAnswer(name, v) 24 } 25 26 // the target to write to 27 target := reflect.ValueOf(t) 28 // the value to write from 29 value := reflect.ValueOf(v) 30 31 // make sure we are writing to a pointer 32 if target.Kind() != reflect.Ptr { 33 return errors.New("you must pass a pointer as the target of a Write operation") 34 } 35 // the object "inside" of the target pointer 36 elem := target.Elem() 37 38 // handle the special types 39 switch elem.Kind() { 40 // if we are writing to a struct 41 case reflect.Struct: 42 // get the name of the field that matches the string we were given 43 fieldIndex, err := findFieldIndex(elem, name) 44 // if something went wrong 45 if err != nil { 46 // bubble up 47 return err 48 } 49 field := elem.Field(fieldIndex) 50 // handle references to the settable interface aswell 51 if s, ok := field.Interface().(settable); ok { 52 // use the interface method 53 return s.WriteAnswer(name, v) 54 } 55 if field.CanAddr() { 56 if s, ok := field.Addr().Interface().(settable); ok { 57 // use the interface method 58 return s.WriteAnswer(name, v) 59 } 60 } 61 62 // copy the value over to the normal struct 63 return copy(field, value) 64 case reflect.Map: 65 mapType := reflect.TypeOf(t).Elem() 66 if mapType.Key().Kind() != reflect.String || mapType.Elem().Kind() != reflect.Interface { 67 return errors.New("answer maps must be of type map[string]interface") 68 } 69 mt := *t.(*map[string]interface{}) 70 mt[name] = value.Interface() 71 return nil 72 } 73 // otherwise just copy the value to the target 74 return copy(elem, value) 75} 76 77// BUG(AlecAivazis): the current implementation might cause weird conflicts if there are 78// two fields with same name that only differ by casing. 79func findFieldIndex(s reflect.Value, name string) (int, error) { 80 // the type of the value 81 sType := s.Type() 82 83 // first look for matching tags so we can overwrite matching field names 84 for i := 0; i < sType.NumField(); i++ { 85 // the field we are current scanning 86 field := sType.Field(i) 87 88 // the value of the survey tag 89 tag := field.Tag.Get(tagName) 90 // if the tag matches the name we are looking for 91 if tag != "" && tag == name { 92 // then we found our index 93 return i, nil 94 } 95 } 96 97 // then look for matching names 98 for i := 0; i < sType.NumField(); i++ { 99 // the field we are current scanning 100 field := sType.Field(i) 101 102 // if the name of the field matches what we're looking for 103 if strings.ToLower(field.Name) == strings.ToLower(name) { 104 return i, nil 105 } 106 } 107 108 // we didn't find the field 109 return -1, fmt.Errorf("could not find field matching %v", name) 110} 111 112// isList returns true if the element is something we can Len() 113func isList(v reflect.Value) bool { 114 switch v.Type().Kind() { 115 case reflect.Array, reflect.Slice: 116 return true 117 default: 118 return false 119 } 120} 121 122// Write takes a value and copies it to the target 123func copy(t reflect.Value, v reflect.Value) (err error) { 124 // if something ends up panicing we need to catch it in a deferred func 125 defer func() { 126 if r := recover(); r != nil { 127 // if we paniced with an error 128 if _, ok := r.(error); ok { 129 // cast the result to an error object 130 err = r.(error) 131 } else if _, ok := r.(string); ok { 132 // otherwise we could have paniced with a string so wrap it in an error 133 err = errors.New(r.(string)) 134 } 135 } 136 }() 137 138 // if we are copying from a string result to something else 139 if v.Kind() == reflect.String && v.Type() != t.Type() { 140 var castVal interface{} 141 var casterr error 142 vString := v.Interface().(string) 143 144 switch t.Kind() { 145 case reflect.Bool: 146 castVal, casterr = strconv.ParseBool(vString) 147 case reflect.Int: 148 castVal, casterr = strconv.Atoi(vString) 149 case reflect.Int8: 150 var val64 int64 151 val64, casterr = strconv.ParseInt(vString, 10, 8) 152 if casterr == nil { 153 castVal = int8(val64) 154 } 155 case reflect.Int16: 156 var val64 int64 157 val64, casterr = strconv.ParseInt(vString, 10, 16) 158 if casterr == nil { 159 castVal = int16(val64) 160 } 161 case reflect.Int32: 162 var val64 int64 163 val64, casterr = strconv.ParseInt(vString, 10, 32) 164 if casterr == nil { 165 castVal = int32(val64) 166 } 167 case reflect.Int64: 168 castVal, casterr = strconv.ParseInt(vString, 10, 64) 169 case reflect.Uint: 170 var val64 uint64 171 val64, casterr = strconv.ParseUint(vString, 10, 8) 172 if casterr == nil { 173 castVal = uint(val64) 174 } 175 case reflect.Uint8: 176 var val64 uint64 177 val64, casterr = strconv.ParseUint(vString, 10, 8) 178 if casterr == nil { 179 castVal = uint8(val64) 180 } 181 case reflect.Uint16: 182 var val64 uint64 183 val64, casterr = strconv.ParseUint(vString, 10, 16) 184 if casterr == nil { 185 castVal = uint16(val64) 186 } 187 case reflect.Uint32: 188 var val64 uint64 189 val64, casterr = strconv.ParseUint(vString, 10, 32) 190 if casterr == nil { 191 castVal = uint32(val64) 192 } 193 case reflect.Uint64: 194 castVal, casterr = strconv.ParseUint(vString, 10, 64) 195 case reflect.Float32: 196 var val64 float64 197 val64, casterr = strconv.ParseFloat(vString, 32) 198 if casterr == nil { 199 castVal = float32(val64) 200 } 201 case reflect.Float64: 202 castVal, casterr = strconv.ParseFloat(vString, 64) 203 default: 204 return fmt.Errorf("Unable to convert from string to type %s", t.Kind()) 205 } 206 207 if casterr != nil { 208 return casterr 209 } 210 211 t.Set(reflect.ValueOf(castVal)) 212 return 213 } 214 215 // if we are copying from one slice or array to another 216 if isList(v) && isList(t) { 217 // loop over every item in the desired value 218 for i := 0; i < v.Len(); i++ { 219 // write to the target given its kind 220 switch t.Kind() { 221 // if its a slice 222 case reflect.Slice: 223 // an object of the correct type 224 obj := reflect.Indirect(reflect.New(t.Type().Elem())) 225 226 // write the appropriate value to the obj and catch any errors 227 if err := copy(obj, v.Index(i)); err != nil { 228 return err 229 } 230 231 // just append the value to the end 232 t.Set(reflect.Append(t, obj)) 233 // otherwise it could be an array 234 case reflect.Array: 235 // set the index to the appropriate value 236 copy(t.Slice(i, i+1).Index(0), v.Index(i)) 237 } 238 } 239 } else { 240 // set the value to the target 241 t.Set(v) 242 } 243 244 // we're done 245 return 246} 247