310 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			310 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			Go
		
	
	
	
| 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)
 | |
| 		}
 | |
| 	}
 | |
| }
 |