github.com/elves/elvish@v0.15.0/pkg/edit/complete_getopt.go (about) 1 package edit 2 3 import ( 4 "errors" 5 "fmt" 6 "strings" 7 "unicode/utf8" 8 9 "github.com/elves/elvish/pkg/eval" 10 "github.com/elves/elvish/pkg/eval/vals" 11 "github.com/elves/elvish/pkg/getopt" 12 "github.com/elves/elvish/pkg/parse" 13 "github.com/xiaq/persistent/hashmap" 14 ) 15 16 //elvdoc:fn complete-getopt 17 // 18 // ```elvish 19 // edit:complete-getopt $args $opt-specs $arg-handlers 20 // ``` 21 // Produces completions according to a specification of accepted command-line 22 // options (both short and long options are handled), positional handler 23 // functions for each command position, and the current arguments in the command 24 // line. The arguments are as follows: 25 // 26 // * `$args` is an array containing the current arguments in the command line 27 // (without the command itself). These are the arguments as passed to the 28 // [Argument Completer](#argument-completer) function. 29 // 30 // * `$opt-specs` is an array of maps, each one containing the definition of 31 // one possible command-line option. Matching options will be provided as 32 // completions when the last element of `$args` starts with a dash, but not 33 // otherwise. Each map can contain the following keys (at least one of `short` 34 // or `long` needs to be specified): 35 // 36 // - `short` contains the one-letter short option, if any, without the dash. 37 // 38 // - `long` contains the long option name, if any, without the initial two 39 // dashes. 40 // 41 // - `arg-optional`, if set to `$true`, specifies that the option receives an 42 // optional argument. 43 // 44 // - `arg-required`, if set to `$true`, specifies that the option receives a 45 // mandatory argument. Only one of `arg-optional` or `arg-required` can be 46 // set to `$true`. 47 // 48 // - `desc` can be set to a human-readable description of the option which 49 // will be displayed in the completion menu. 50 // 51 // - `completer` can be set to a function to generate possible completions for 52 // the option argument. The function receives as argument the element at 53 // that position and return zero or more candidates. 54 // 55 // * `$arg-handlers` is an array of functions, each one returning the possible 56 // completions for that position in the arguments. Each function receives 57 // as argument the last element of `$args`, and should return zero or more 58 // possible values for the completions at that point. The returned values can 59 // be plain strings or the output of `edit:complex-candidate`. If the last 60 // element of the list is the string `...`, then the last handler is reused 61 // for all following arguments. 62 // 63 // Example: 64 // 65 // ```elvish-transcript 66 // ~> fn complete [@args]{ 67 // opt-specs = [ [&short=a &long=all &desc="Show all"] 68 // [&short=n &desc="Set name" &arg-required=$true 69 // &completer= [_]{ put name1 name2 }] ] 70 // arg-handlers = [ [_]{ put first1 first2 } 71 // [_]{ put second1 second2 } ... ] 72 // edit:complete-getopt $args $opt-specs $arg-handlers 73 // } 74 // ~> complete '' 75 // ▶ first1 76 // ▶ first2 77 // ~> complete '-' 78 // ▶ (edit:complex-candidate -a &display='-a (Show all)') 79 // ▶ (edit:complex-candidate --all &display='--all (Show all)') 80 // ▶ (edit:complex-candidate -n &display='-n (Set name)') 81 // ~> complete -n '' 82 // ▶ name1 83 // ▶ name2 84 // ~> complete -a '' 85 // ▶ first1 86 // ▶ first2 87 // ~> complete arg1 '' 88 // ▶ second1 89 // ▶ second2 90 // ~> complete arg1 arg2 '' 91 // ▶ second1 92 // ▶ second2 93 // ``` 94 95 func completeGetopt(fm *eval.Frame, vArgs, vOpts, vArgHandlers interface{}) error { 96 args, err := parseGetoptArgs(vArgs) 97 if err != nil { 98 return err 99 } 100 opts, err := parseGetoptOptSpecs(vOpts) 101 if err != nil { 102 return err 103 } 104 argHandlers, variadic, err := parseGetoptArgHandlers(vArgHandlers) 105 if err != nil { 106 return err 107 } 108 109 // TODO(xiaq): Make the Config field configurable 110 g := getopt.Getopt{Options: opts.opts, Config: getopt.GNUGetoptLong} 111 _, parsedArgs, ctx := g.Parse(args) 112 113 out := fm.OutputChan() 114 putShortOpt := func(opt *getopt.Option) { 115 c := complexItem{Stem: "-" + string(opt.Short)} 116 if d, ok := opts.desc[opt]; ok { 117 if e, ok := opts.argDesc[opt]; ok { 118 c.Display = c.Stem + " " + e + " (" + d + ")" 119 } else { 120 c.Display = c.Stem + " (" + d + ")" 121 } 122 } 123 out <- c 124 } 125 putLongOpt := func(opt *getopt.Option) { 126 c := complexItem{Stem: "--" + opt.Long} 127 if d, ok := opts.desc[opt]; ok { 128 if e, ok := opts.argDesc[opt]; ok { 129 c.Display = c.Stem + " " + e + " (" + d + ")" 130 } else { 131 c.Display = c.Stem + " (" + d + ")" 132 } 133 } 134 out <- c 135 } 136 call := func(fn eval.Callable, args ...interface{}) { 137 fn.Call(fm, args, eval.NoOpts) 138 } 139 140 switch ctx.Type { 141 case getopt.NewOptionOrArgument, getopt.Argument: 142 // Find argument handler. 143 var argHandler eval.Callable 144 if len(parsedArgs) < len(argHandlers) { 145 argHandler = argHandlers[len(parsedArgs)] 146 } else if variadic { 147 argHandler = argHandlers[len(argHandlers)-1] 148 } 149 if argHandler != nil { 150 call(argHandler, ctx.Text) 151 } else { 152 // TODO(xiaq): Notify that there is no suitable argument completer. 153 } 154 case getopt.NewOption: 155 for _, opt := range opts.opts { 156 if opt.Short != 0 { 157 putShortOpt(opt) 158 } 159 if opt.Long != "" { 160 putLongOpt(opt) 161 } 162 } 163 case getopt.NewLongOption: 164 for _, opt := range opts.opts { 165 if opt.Long != "" { 166 putLongOpt(opt) 167 } 168 } 169 case getopt.LongOption: 170 for _, opt := range opts.opts { 171 if strings.HasPrefix(opt.Long, ctx.Text) { 172 putLongOpt(opt) 173 } 174 } 175 case getopt.ChainShortOption: 176 for _, opt := range opts.opts { 177 if opt.Short != 0 { 178 // TODO(xiaq): Loses chained options. 179 putShortOpt(opt) 180 } 181 } 182 case getopt.OptionArgument: 183 gen := opts.argGenerator[ctx.Option.Option] 184 if gen != nil { 185 call(gen, ctx.Option.Argument) 186 } 187 } 188 return nil 189 } 190 191 // TODO(xiaq): Simplify most of the parsing below with reflection. 192 193 func parseGetoptArgs(v interface{}) ([]string, error) { 194 var args []string 195 var err error 196 errIterate := vals.Iterate(v, func(v interface{}) bool { 197 arg, ok := v.(string) 198 if !ok { 199 err = fmt.Errorf("arg should be string, got %s", vals.Kind(v)) 200 return false 201 } 202 args = append(args, arg) 203 return true 204 }) 205 if errIterate != nil { 206 err = errIterate 207 } 208 return args, err 209 } 210 211 type parsedOptSpecs struct { 212 opts []*getopt.Option 213 desc map[*getopt.Option]string 214 argDesc map[*getopt.Option]string 215 argGenerator map[*getopt.Option]eval.Callable 216 } 217 218 func parseGetoptOptSpecs(v interface{}) (parsedOptSpecs, error) { 219 result := parsedOptSpecs{ 220 nil, map[*getopt.Option]string{}, 221 map[*getopt.Option]string{}, map[*getopt.Option]eval.Callable{}} 222 223 var err error 224 errIterate := vals.Iterate(v, func(v interface{}) bool { 225 m, ok := v.(hashmap.Map) 226 if !ok { 227 err = fmt.Errorf("opt should be map, got %s", vals.Kind(v)) 228 return false 229 } 230 231 opt := &getopt.Option{} 232 233 getStringField := func(k string) (string, bool, error) { 234 v, ok := m.Index(k) 235 if !ok { 236 return "", false, nil 237 } 238 if vs, ok := v.(string); ok { 239 return vs, true, nil 240 } 241 return "", false, 242 fmt.Errorf("%s should be string, got %s", k, vals.Kind(v)) 243 } 244 getCallableField := func(k string) (eval.Callable, bool, error) { 245 v, ok := m.Index(k) 246 if !ok { 247 return nil, false, nil 248 } 249 if vb, ok := v.(eval.Callable); ok { 250 return vb, true, nil 251 } 252 return nil, false, 253 fmt.Errorf("%s should be fn, got %s", k, vals.Kind(v)) 254 } 255 getBoolField := func(k string) (bool, bool, error) { 256 v, ok := m.Index(k) 257 if !ok { 258 return false, false, nil 259 } 260 if vb, ok := v.(bool); ok { 261 return vb, true, nil 262 } 263 return false, false, 264 fmt.Errorf("%s should be bool, got %s", k, vals.Kind(v)) 265 } 266 267 if s, ok, errGet := getStringField("short"); ok { 268 r, size := utf8.DecodeRuneInString(s) 269 if r == utf8.RuneError || size != len(s) { 270 err = fmt.Errorf( 271 "short option should be exactly one rune, got %v", 272 parse.Quote(s)) 273 return false 274 } 275 opt.Short = r 276 } else if errGet != nil { 277 err = errGet 278 return false 279 } 280 if s, ok, errGet := getStringField("long"); ok { 281 opt.Long = s 282 } else if errGet != nil { 283 err = errGet 284 return false 285 } 286 if opt.Short == 0 && opt.Long == "" { 287 err = errors.New( 288 "opt should have at least one of short and long forms") 289 return false 290 } 291 292 argRequired, _, errGet := getBoolField("arg-required") 293 if errGet != nil { 294 err = errGet 295 return false 296 } 297 argOptional, _, errGet := getBoolField("arg-optional") 298 if errGet != nil { 299 err = errGet 300 return false 301 } 302 switch { 303 case argRequired && argOptional: 304 err = errors.New( 305 "opt cannot have both arg-required and arg-optional") 306 return false 307 case argRequired: 308 opt.HasArg = getopt.RequiredArgument 309 case argOptional: 310 opt.HasArg = getopt.OptionalArgument 311 } 312 313 if s, ok, errGet := getStringField("desc"); ok { 314 result.desc[opt] = s 315 } else if errGet != nil { 316 err = errGet 317 return false 318 } 319 if s, ok, errGet := getStringField("arg-desc"); ok { 320 result.argDesc[opt] = s 321 } else if errGet != nil { 322 err = errGet 323 return false 324 } 325 if f, ok, errGet := getCallableField("completer"); ok { 326 result.argGenerator[opt] = f 327 } else if errGet != nil { 328 err = errGet 329 return false 330 } 331 332 result.opts = append(result.opts, opt) 333 return true 334 }) 335 if errIterate != nil { 336 err = errIterate 337 } 338 return result, err 339 } 340 341 func parseGetoptArgHandlers(v interface{}) ([]eval.Callable, bool, error) { 342 var argHandlers []eval.Callable 343 var variadic bool 344 var err error 345 errIterate := vals.Iterate(v, func(v interface{}) bool { 346 sv, ok := v.(string) 347 if ok { 348 if sv == "..." { 349 variadic = true 350 return true 351 } 352 err = fmt.Errorf( 353 "string except for ... not allowed as argument handler, got %s", 354 parse.Quote(sv)) 355 return false 356 } 357 argHandler, ok := v.(eval.Callable) 358 if !ok { 359 err = fmt.Errorf( 360 "argument handler should be fn, got %s", vals.Kind(v)) 361 } 362 argHandlers = append(argHandlers, argHandler) 363 return true 364 }) 365 if errIterate != nil { 366 err = errIterate 367 } 368 return argHandlers, variadic, err 369 }