|
- package flags
-
- import (
- "fmt"
- "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 + "*")
- 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{
- 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 && 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)
- }
- }
- }
|