1package commands
2
3import (
4	"fmt"
5	"sort"
6	"strings"
7	"unicode"
8
9	"github.com/chzyer/readline"
10	"github.com/wallix/awless/aws/services"
11	"github.com/wallix/awless/cloud"
12	"github.com/wallix/awless/graph"
13	"github.com/wallix/awless/template"
14)
15
16func enumCompletionFunc(enum []string) readline.AutoCompleter {
17	if len(enum) == 1 && enum[0] == "" {
18		return readline.NewPrefixCompleter()
19	}
20	var items []readline.PrefixCompleterInterface
21	for _, e := range enum {
22		items = append(items, readline.PcItem(e))
23	}
24	return readline.NewPrefixCompleter(items...)
25}
26
27func typedParamCompletionFunc(g cloud.GraphAPI, resourceType, propName string) readline.AutoCompleter {
28	var items []readline.PrefixCompleterInterface
29	resources, _ := g.Find(cloud.NewQuery(resourceType))
30	for _, res := range resources {
31		if val, ok := res.Properties()[propName]; ok {
32			switch vv := val.(type) {
33			case []string:
34				for _, s := range vv {
35					items = append(items, readline.PcItem(s))
36				}
37			default:
38				items = append(items, readline.PcItem(fmt.Sprint(val)))
39			}
40		}
41	}
42
43	return readline.NewPrefixCompleter(items...)
44}
45func holeAutoCompletion(g cloud.GraphAPI, paramPaths []string) readline.AutoCompleter {
46	type typesProp struct {
47		types []string
48		prop  string
49	}
50
51	var entities []typesProp
52
53	for _, paramPath := range paramPaths {
54		splits := strings.Split(paramPath, ".")
55		if len(splits) != 3 {
56			continue
57		}
58
59		if entityTypes, entityProp := guessEntityTypeFromHoleQuestion(splits[1] + "." + splits[2]); len(entityTypes) > 0 {
60			entities = append(entities, typesProp{types: entityTypes, prop: entityProp})
61		}
62	}
63
64	var possibleSuggests []string
65	for _, entityProp := range entities {
66		resources, err := g.Find(cloud.NewQuery(entityProp.types...))
67		exitOn(err)
68		if len(resources) == 0 {
69			continue
70		}
71
72		var validPropName string
73		if entityProp.prop != "" {
74			for _, r := range resources {
75				for propName := range r.Properties() {
76					if keyCorrespondsToProperty(entityProp.prop, propName) {
77						validPropName = propName
78					}
79				}
80			}
81		}
82		if validPropName == "" {
83			for _, r := range resources {
84				possibleSuggests = append(possibleSuggests, r.Id())
85				possibleSuggests = appendWithNameAliases(possibleSuggests, r)
86			}
87			continue
88		}
89
90		for _, r := range resources {
91			if v, ok := r.Property(validPropName); ok {
92				switch prop := v.(type) {
93				case string, float64, int, bool:
94					possibleSuggests = append(possibleSuggests, fmt.Sprint(prop))
95					if validPropName == "ID" {
96						possibleSuggests = appendWithNameAliases(possibleSuggests, r)
97					}
98				case []string:
99					for _, str := range prop {
100						possibleSuggests = append(possibleSuggests, str)
101					}
102				case []*graph.KeyValue:
103					for _, kv := range prop {
104						possibleSuggests = append(possibleSuggests, fmt.Sprintf("%s:%s", kv.KeyName, kv.Value))
105					}
106				}
107			}
108		}
109	}
110
111	completeFunc := func(s string) (suggest []string) {
112		s = splitKeepLast(s, ",")
113		s = strings.TrimLeft(s, "'@\"")
114		for _, possible := range possibleSuggests {
115			suggest = appendIfContains(suggest, possible, s)
116		}
117		suggest = quotedSortedSet(suggest)
118		return
119	}
120
121	return &prefixCompleter{callback: completeFunc, splitChar: ","}
122}
123
124type prefixCompleter struct {
125	callback  readline.DynamicCompleteFunc
126	splitChar string
127}
128
129func (p *prefixCompleter) Do(line []rune, pos int) (newLine [][]rune, offset int) {
130	var lines []string
131	lines, offset = doInternal(p, string(line), pos, line)
132	for _, l := range lines {
133		newLine = append(newLine, []rune(l))
134	}
135	return
136}
137
138func doInternal(p *prefixCompleter, line string, pos int, origLine []rune) (newLine []string, offset int) {
139	strings.TrimLeftFunc(line[:pos], func(r rune) bool {
140		return unicode.IsSpace(r)
141	})
142	if p.splitChar != "" {
143		line = splitKeepLast(line, p.splitChar)
144	}
145	for _, suggest := range p.callback(line) {
146		line = strings.TrimLeft(line, "[")
147		if len(line) >= len(suggest) {
148			if strings.HasPrefix(line, suggest) {
149				if len(line) != len(suggest) {
150					newLine = append(newLine, suggest)
151				}
152				offset = len(suggest)
153			}
154		} else {
155			if strings.HasPrefix(suggest, line) {
156				newLine = append(newLine, suggest[len(line):])
157				offset = len(line)
158			}
159		}
160	}
161	return
162}
163
164func splitKeepLast(s, sep string) (last string) {
165	if !strings.Contains(s, sep) {
166		last = s
167		return
168	}
169	offset := strings.LastIndex(s, sep)
170	if offset+1 < len(s) {
171		last = s[offset+1:]
172	}
173	return
174}
175
176func quotedSortedSet(list []string) (out []string) {
177	unique := make(map[string]bool)
178	for _, l := range list {
179		unique[l] = true
180	}
181
182	for k := range unique {
183		if !template.MatchStringParamValue(k) {
184			k = "'" + k + "'"
185		}
186
187		out = append(out, k)
188	}
189
190	sort.Strings(out)
191	return
192}
193
194// Return potential resource types and a prop
195// according to given holes questions.
196// See corresponding unit test for logic
197func guessEntityTypeFromHoleQuestion(hole string) (resolved []string, prop string) {
198	tokens := strings.Split(strings.TrimSpace(hole), ".")
199	if len(tokens) == 0 {
200		return
201	}
202
203	var types []string
204	for _, t := range tokens {
205		for _, r := range resourcesTypesWithPlural {
206			if t == r {
207				types = append(types, cloud.SingularizeResource(r))
208				break
209			}
210		}
211	}
212
213	if l := len(types); l > 0 {
214		if len(tokens) == 2 {
215			prop = tokens[1]
216		}
217		if l > 1 {
218			prop = ""
219		}
220		resolved = []string{types[l-1]}
221	} else if len(tokens) > 1 {
222		for i := len(tokens) - 1; i >= 0; i-- {
223			if len(tokens[i]) < 4 {
224				continue
225			}
226			for _, r := range awsservices.ResourceTypes {
227				if strings.Contains(r, tokens[i]) {
228					resolved = append(resolved, r)
229				}
230			}
231			if len(resolved) > 0 {
232				return
233			}
234		}
235	}
236	return
237}
238
239func keyCorrespondsToProperty(holekey, prop string) bool {
240	holekey = strings.ToLower(holekey)
241	prop = strings.ToLower(prop)
242	if holekey == prop {
243		return true
244	}
245	if strings.Replace(holekey, "-", "", -1) == prop {
246		return true
247	}
248	return false
249}
250
251func appendIfContains(slice []string, value, subst string) []string {
252	subst = strings.TrimLeft(subst, "[")
253
254	if strings.Contains(value, subst) && value != "" {
255		return append(slice, value)
256	}
257	return slice
258}
259
260func appendWithNameAliases(slice []string, res cloud.Resource) []string {
261	if val, ok := res.Properties()["Name"]; ok {
262		switch val.(type) {
263		case string:
264			name := val.(string)
265			if name != "" {
266				slice = append(slice, fmt.Sprintf("@%s", name))
267			}
268		}
269	}
270	return slice
271}
272
273var resourcesTypesWithPlural []string
274
275func init() {
276	for _, r := range awsservices.ResourceTypes {
277		resourcesTypesWithPlural = append(resourcesTypesWithPlural, r, cloud.PluralizeResource(r))
278	}
279}
280