Scott Baker | 105df15 | 2020-04-13 15:55:14 -0700 | [diff] [blame] | 1 | package flags |
| 2 | |
| 3 | import ( |
| 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. |
| 13 | type Completion struct { |
| 14 | // The completed item |
| 15 | Item string |
| 16 | |
| 17 | // A description of the completed item (optional) |
| 18 | Description string |
| 19 | } |
| 20 | |
| 21 | type completions []Completion |
| 22 | |
| 23 | func (c completions) Len() int { |
| 24 | return len(c) |
| 25 | } |
| 26 | |
| 27 | func (c completions) Less(i, j int) bool { |
| 28 | return c[i].Item < c[j].Item |
| 29 | } |
| 30 | |
| 31 | func (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. |
| 37 | type 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 | |
| 44 | type completion struct { |
| 45 | parser *Parser |
| 46 | } |
| 47 | |
| 48 | // Filename is a string alias which provides filename completion. |
| 49 | type Filename string |
| 50 | |
| 51 | func 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. |
| 63 | func (f *Filename) Complete(match string) []Completion { |
| 64 | ret, _ := filepath.Glob(match + "*") |
| 65 | return completionsWithoutDescriptions(ret) |
| 66 | } |
| 67 | |
| 68 | func (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 | |
| 76 | func (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 | |
| 115 | func (c *completion) completeNamesForLongPrefix(s *parseState, prefix string, match string) []Completion { |
| 116 | return c.completeOptionNames(s, prefix, match, false) |
| 117 | } |
| 118 | |
| 119 | func (c *completion) completeNamesForShortPrefix(s *parseState, prefix string, match string) []Completion { |
| 120 | return c.completeOptionNames(s, prefix, match, true) |
| 121 | } |
| 122 | |
| 123 | func (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 | |
| 138 | func (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 | |
| 161 | func (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 | |
| 285 | func (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 | } |