github.com/kolbycrouch/elvish@v0.14.1-0.20210614162631-215b9ac1c423/pkg/edit/complete_getopt.go (about)

     1  package edit
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"strings"
     7  	"unicode/utf8"
     8  
     9  	"src.elv.sh/pkg/eval"
    10  	"src.elv.sh/pkg/eval/vals"
    11  	"src.elv.sh/pkg/getopt"
    12  	"src.elv.sh/pkg/parse"
    13  	"src.elv.sh/pkg/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  }