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