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