package flags import ( "fmt" "os" "path/filepath" "reflect" "sort" "strings" "unicode/utf8" ) // Completion is a type containing information of a completion. type Completion struct { // The completed item Item string // A description of the completed item (optional) Description string } type completions []Completion func (c completions) Len() int { return len(c) } func (c completions) Less(i, j int) bool { return c[i].Item < c[j].Item } func (c completions) Swap(i, j int) { c[i], c[j] = c[j], c[i] } // Completer is an interface which can be implemented by types // to provide custom command line argument completion. type Completer interface { // Complete receives a prefix representing a (partial) value // for its type and should provide a list of possible valid // completions. Complete(match string) []Completion } type completion struct { parser *Parser } // Filename is a string alias which provides filename completion. type Filename string func completionsWithoutDescriptions(items []string) []Completion { ret := make([]Completion, len(items)) for i, v := range items { ret[i].Item = v } return ret } // Complete returns a list of existing files with the given // prefix. func (f *Filename) Complete(match string) []Completion { ret, _ := filepath.Glob(match + "*") if len(ret) == 1 { if info, err := os.Stat(ret[0]); err == nil && info.IsDir() { ret[0] = ret[0] + "/" } } return completionsWithoutDescriptions(ret) } func (c *completion) skipPositional(s *parseState, n int) { if n >= len(s.positional) { s.positional = nil } else { s.positional = s.positional[n:] } } func (c *completion) completeOptionNames(s *parseState, prefix string, match string, short bool) []Completion { if short && len(match) != 0 { return []Completion{ { Item: prefix + match, }, } } var results []Completion repeats := map[string]bool{} for name, opt := range s.lookup.longNames { if strings.HasPrefix(name, match) && !opt.Hidden { results = append(results, Completion{ Item: defaultLongOptDelimiter + name, Description: opt.Description, }) if short { repeats[string(opt.ShortName)] = true } } } if short { for name, opt := range s.lookup.shortNames { if _, exist := repeats[name]; !exist && strings.HasPrefix(name, match) && !opt.Hidden { results = append(results, Completion{ Item: string(defaultShortOptDelimiter) + name, Description: opt.Description, }) } } } return results } func (c *completion) completeNamesForLongPrefix(s *parseState, prefix string, match string) []Completion { return c.completeOptionNames(s, prefix, match, false) } func (c *completion) completeNamesForShortPrefix(s *parseState, prefix string, match string) []Completion { return c.completeOptionNames(s, prefix, match, true) } func (c *completion) completeCommands(s *parseState, match string) []Completion { n := make([]Completion, 0, len(s.command.commands)) for _, cmd := range s.command.commands { if cmd.data != c && !cmd.Hidden && strings.HasPrefix(cmd.Name, match) { n = append(n, Completion{ Item: cmd.Name, Description: cmd.ShortDescription, }) } } return n } func (c *completion) completeValue(value reflect.Value, prefix string, match string) []Completion { if value.Kind() == reflect.Slice { value = reflect.New(value.Type().Elem()) } i := value.Interface() var ret []Completion if cmp, ok := i.(Completer); ok { ret = cmp.Complete(match) } else if value.CanAddr() { if cmp, ok = value.Addr().Interface().(Completer); ok { ret = cmp.Complete(match) } } for i, v := range ret { ret[i].Item = prefix + v.Item } return ret } func (c *completion) complete(args []string) []Completion { if len(args) == 0 { args = []string{""} } s := &parseState{ args: args, } c.parser.fillParseState(s) var opt *Option for len(s.args) > 1 { arg := s.pop() if (c.parser.Options&PassDoubleDash) != None && arg == "--" { opt = nil c.skipPositional(s, len(s.args)-1) break } if argumentIsOption(arg) { prefix, optname, islong := stripOptionPrefix(arg) optname, _, argument := splitOption(prefix, optname, islong) if argument == nil { var o *Option canarg := true if islong { o = s.lookup.longNames[optname] } else { for i, r := range optname { sname := string(r) o = s.lookup.shortNames[sname] if o == nil { break } if i == 0 && o.canArgument() && len(optname) != len(sname) { canarg = false break } } } if o == nil && (c.parser.Options&PassAfterNonOption) != None { opt = nil c.skipPositional(s, len(s.args)-1) break } else if o != nil && o.canArgument() && !o.OptionalArgument && canarg { if len(s.args) > 1 { s.pop() } else { opt = o } } } } else { if len(s.positional) > 0 { if !s.positional[0].isRemaining() { // Don't advance beyond a remaining positional arg (because // it consumes all subsequent args). s.positional = s.positional[1:] } } else if cmd, ok := s.lookup.commands[arg]; ok { cmd.fillParseState(s) } opt = nil } } lastarg := s.args[len(s.args)-1] var ret []Completion if opt != nil { // Completion for the argument of 'opt' ret = c.completeValue(opt.value, "", lastarg) } else if argumentStartsOption(lastarg) { // Complete the option prefix, optname, islong := stripOptionPrefix(lastarg) optname, split, argument := splitOption(prefix, optname, islong) if argument == nil && !islong { rname, n := utf8.DecodeRuneInString(optname) sname := string(rname) if opt := s.lookup.shortNames[sname]; opt != nil && opt.canArgument() { ret = c.completeValue(opt.value, prefix+sname, optname[n:]) } else { ret = c.completeNamesForShortPrefix(s, prefix, optname) } } else if argument != nil { if islong { opt = s.lookup.longNames[optname] } else { opt = s.lookup.shortNames[optname] } if opt != nil { ret = c.completeValue(opt.value, prefix+optname+split, *argument) } } else if islong { ret = c.completeNamesForLongPrefix(s, prefix, optname) } else { ret = c.completeNamesForShortPrefix(s, prefix, optname) } } else if len(s.positional) > 0 { // Complete for positional argument ret = c.completeValue(s.positional[0].value, "", lastarg) } else if len(s.command.commands) > 0 { // Complete for command ret = c.completeCommands(s, lastarg) } sort.Sort(completions(ret)) return ret } func (c *completion) print(items []Completion, showDescriptions bool) { if showDescriptions && len(items) > 1 { maxl := 0 for _, v := range items { if len(v.Item) > maxl { maxl = len(v.Item) } } for _, v := range items { fmt.Printf("%s", v.Item) if len(v.Description) > 0 { fmt.Printf("%s # %s", strings.Repeat(" ", maxl-len(v.Item)), v.Description) } fmt.Printf("\n") } } else { for _, v := range items { fmt.Println(v.Item) } } }