1package flags
2
3import (
4	"fmt"
5	"path/filepath"
6	"reflect"
7	"sort"
8	"strings"
9	"unicode/utf8"
10)
11
12// Completion is a type containing information of a completion.
13type Completion struct {
14	// The completed item
15	Item string
16
17	// A description of the completed item (optional)
18	Description string
19}
20
21type completions []Completion
22
23func (c completions) Len() int {
24	return len(c)
25}
26
27func (c completions) Less(i, j int) bool {
28	return c[i].Item < c[j].Item
29}
30
31func (c completions) Swap(i, j int) {
32	c[i], c[j] = c[j], c[i]
33}
34
35// Completer is an interface which can be implemented by types
36// to provide custom command line argument completion.
37type Completer interface {
38	// Complete receives a prefix representing a (partial) value
39	// for its type and should provide a list of possible valid
40	// completions.
41	Complete(match string) []Completion
42}
43
44type completion struct {
45	parser *Parser
46}
47
48// Filename is a string alias which provides filename completion.
49type Filename string
50
51func completionsWithoutDescriptions(items []string) []Completion {
52	ret := make([]Completion, len(items))
53
54	for i, v := range items {
55		ret[i].Item = v
56	}
57
58	return ret
59}
60
61// Complete returns a list of existing files with the given
62// prefix.
63func (f *Filename) Complete(match string) []Completion {
64	ret, _ := filepath.Glob(match + "*")
65	return completionsWithoutDescriptions(ret)
66}
67
68func (c *completion) skipPositional(s *parseState, n int) {
69	if n >= len(s.positional) {
70		s.positional = nil
71	} else {
72		s.positional = s.positional[n:]
73	}
74}
75
76func (c *completion) completeOptionNames(s *parseState, prefix string, match string, short bool) []Completion {
77	if short && len(match) != 0 {
78		return []Completion{
79			Completion{
80				Item: prefix + match,
81			},
82		}
83	}
84
85	var results []Completion
86	repeats := map[string]bool{}
87
88	for name, opt := range s.lookup.longNames {
89		if strings.HasPrefix(name, match) && !opt.Hidden {
90			results = append(results, Completion{
91				Item:        defaultLongOptDelimiter + name,
92				Description: opt.Description,
93			})
94
95			if short {
96				repeats[string(opt.ShortName)] = true
97			}
98		}
99	}
100
101	if short {
102		for name, opt := range s.lookup.shortNames {
103			if _, exist := repeats[name]; !exist && strings.HasPrefix(name, match) && !opt.Hidden {
104				results = append(results, Completion{
105					Item:        string(defaultShortOptDelimiter) + name,
106					Description: opt.Description,
107				})
108			}
109		}
110	}
111
112	return results
113}
114
115func (c *completion) completeNamesForLongPrefix(s *parseState, prefix string, match string) []Completion {
116	return c.completeOptionNames(s, prefix, match, false)
117}
118
119func (c *completion) completeNamesForShortPrefix(s *parseState, prefix string, match string) []Completion {
120	return c.completeOptionNames(s, prefix, match, true)
121}
122
123func (c *completion) completeCommands(s *parseState, match string) []Completion {
124	n := make([]Completion, 0, len(s.command.commands))
125
126	for _, cmd := range s.command.commands {
127		if cmd.data != c && strings.HasPrefix(cmd.Name, match) {
128			n = append(n, Completion{
129				Item:        cmd.Name,
130				Description: cmd.ShortDescription,
131			})
132		}
133	}
134
135	return n
136}
137
138func (c *completion) completeValue(value reflect.Value, prefix string, match string) []Completion {
139	if value.Kind() == reflect.Slice {
140		value = reflect.New(value.Type().Elem())
141	}
142	i := value.Interface()
143
144	var ret []Completion
145
146	if cmp, ok := i.(Completer); ok {
147		ret = cmp.Complete(match)
148	} else if value.CanAddr() {
149		if cmp, ok = value.Addr().Interface().(Completer); ok {
150			ret = cmp.Complete(match)
151		}
152	}
153
154	for i, v := range ret {
155		ret[i].Item = prefix + v.Item
156	}
157
158	return ret
159}
160
161func (c *completion) complete(args []string) []Completion {
162	if len(args) == 0 {
163		args = []string{""}
164	}
165
166	s := &parseState{
167		args: args,
168	}
169
170	c.parser.fillParseState(s)
171
172	var opt *Option
173
174	for len(s.args) > 1 {
175		arg := s.pop()
176
177		if (c.parser.Options&PassDoubleDash) != None && arg == "--" {
178			opt = nil
179			c.skipPositional(s, len(s.args)-1)
180
181			break
182		}
183
184		if argumentIsOption(arg) {
185			prefix, optname, islong := stripOptionPrefix(arg)
186			optname, _, argument := splitOption(prefix, optname, islong)
187
188			if argument == nil {
189				var o *Option
190				canarg := true
191
192				if islong {
193					o = s.lookup.longNames[optname]
194				} else {
195					for i, r := range optname {
196						sname := string(r)
197						o = s.lookup.shortNames[sname]
198
199						if o == nil {
200							break
201						}
202
203						if i == 0 && o.canArgument() && len(optname) != len(sname) {
204							canarg = false
205							break
206						}
207					}
208				}
209
210				if o == nil && (c.parser.Options&PassAfterNonOption) != None {
211					opt = nil
212					c.skipPositional(s, len(s.args)-1)
213
214					break
215				} else if o != nil && o.canArgument() && !o.OptionalArgument && canarg {
216					if len(s.args) > 1 {
217						s.pop()
218					} else {
219						opt = o
220					}
221				}
222			}
223		} else {
224			if len(s.positional) > 0 {
225				if !s.positional[0].isRemaining() {
226					// Don't advance beyond a remaining positional arg (because
227					// it consumes all subsequent args).
228					s.positional = s.positional[1:]
229				}
230			} else if cmd, ok := s.lookup.commands[arg]; ok {
231				cmd.fillParseState(s)
232			}
233
234			opt = nil
235		}
236	}
237
238	lastarg := s.args[len(s.args)-1]
239	var ret []Completion
240
241	if opt != nil {
242		// Completion for the argument of 'opt'
243		ret = c.completeValue(opt.value, "", lastarg)
244	} else if argumentStartsOption(lastarg) {
245		// Complete the option
246		prefix, optname, islong := stripOptionPrefix(lastarg)
247		optname, split, argument := splitOption(prefix, optname, islong)
248
249		if argument == nil && !islong {
250			rname, n := utf8.DecodeRuneInString(optname)
251			sname := string(rname)
252
253			if opt := s.lookup.shortNames[sname]; opt != nil && opt.canArgument() {
254				ret = c.completeValue(opt.value, prefix+sname, optname[n:])
255			} else {
256				ret = c.completeNamesForShortPrefix(s, prefix, optname)
257			}
258		} else if argument != nil {
259			if islong {
260				opt = s.lookup.longNames[optname]
261			} else {
262				opt = s.lookup.shortNames[optname]
263			}
264
265			if opt != nil {
266				ret = c.completeValue(opt.value, prefix+optname+split, *argument)
267			}
268		} else if islong {
269			ret = c.completeNamesForLongPrefix(s, prefix, optname)
270		} else {
271			ret = c.completeNamesForShortPrefix(s, prefix, optname)
272		}
273	} else if len(s.positional) > 0 {
274		// Complete for positional argument
275		ret = c.completeValue(s.positional[0].value, "", lastarg)
276	} else if len(s.command.commands) > 0 {
277		// Complete for command
278		ret = c.completeCommands(s, lastarg)
279	}
280
281	sort.Sort(completions(ret))
282	return ret
283}
284
285func (c *completion) print(items []Completion, showDescriptions bool) {
286	if showDescriptions && len(items) > 1 {
287		maxl := 0
288
289		for _, v := range items {
290			if len(v.Item) > maxl {
291				maxl = len(v.Item)
292			}
293		}
294
295		for _, v := range items {
296			fmt.Printf("%s", v.Item)
297
298			if len(v.Description) > 0 {
299				fmt.Printf("%s  # %s", strings.Repeat(" ", maxl-len(v.Item)), v.Description)
300			}
301
302			fmt.Printf("\n")
303		}
304	} else {
305		for _, v := range items {
306			fmt.Println(v.Item)
307		}
308	}
309}
310