github.com/markusbkk/elvish@v0.0.0-20231204143114-91dc52438621/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/markusbkk/elvish/pkg/eval"
    10  	"github.com/markusbkk/elvish/pkg/eval/vals"
    11  	"github.com/markusbkk/elvish/pkg/getopt"
    12  	"github.com/markusbkk/elvish/pkg/parse"
    13  	"github.com/markusbkk/elvish/pkg/ui"
    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  // @cf flag:parse-getopt
    96  
    97  func completeGetopt(fm *eval.Frame, vArgs, vOpts, vArgHandlers interface{}) error {
    98  	args, err := parseGetoptArgs(vArgs)
    99  	if err != nil {
   100  		return err
   101  	}
   102  	opts, err := parseGetoptOptSpecs(vOpts)
   103  	if err != nil {
   104  		return err
   105  	}
   106  	argHandlers, variadic, err := parseGetoptArgHandlers(vArgHandlers)
   107  	if err != nil {
   108  		return err
   109  	}
   110  
   111  	// TODO: Make the Config field configurable
   112  	_, parsedArgs, ctx := getopt.Complete(args, opts.opts, getopt.GNU)
   113  
   114  	out := fm.ValueOutput()
   115  	putShortOpt := func(opt *getopt.OptionSpec) error {
   116  		c := complexItem{Stem: "-" + string(opt.Short)}
   117  		if d, ok := opts.desc[opt]; ok {
   118  			if e, ok := opts.argDesc[opt]; ok {
   119  				c.Display = ui.T(c.Stem + " " + e + " (" + d + ")")
   120  			} else {
   121  				c.Display = ui.T(c.Stem + " (" + d + ")")
   122  			}
   123  		}
   124  		return out.Put(c)
   125  	}
   126  	putLongOpt := func(opt *getopt.OptionSpec) error {
   127  		c := complexItem{Stem: "--" + opt.Long}
   128  		if d, ok := opts.desc[opt]; ok {
   129  			if e, ok := opts.argDesc[opt]; ok {
   130  				c.Display = ui.T(c.Stem + " " + e + " (" + d + ")")
   131  			} else {
   132  				c.Display = ui.T(c.Stem + " (" + d + ")")
   133  			}
   134  		}
   135  		return out.Put(c)
   136  	}
   137  	call := func(fn eval.Callable, args ...interface{}) error {
   138  		return fn.Call(fm, args, eval.NoOpts)
   139  	}
   140  
   141  	switch ctx.Type {
   142  	case getopt.OptionOrArgument, getopt.Argument:
   143  		// Find argument handler.
   144  		var argHandler eval.Callable
   145  		if len(parsedArgs) < len(argHandlers) {
   146  			argHandler = argHandlers[len(parsedArgs)]
   147  		} else if variadic {
   148  			argHandler = argHandlers[len(argHandlers)-1]
   149  		}
   150  		if argHandler != nil {
   151  			return call(argHandler, ctx.Text)
   152  		}
   153  		// TODO(xiaq): Notify that there is no suitable argument completer.
   154  	case getopt.AnyOption:
   155  		for _, opt := range opts.opts {
   156  			if opt.Short != 0 {
   157  				err := putShortOpt(opt)
   158  				if err != nil {
   159  					return err
   160  				}
   161  			}
   162  			if opt.Long != "" {
   163  				err := putLongOpt(opt)
   164  				if err != nil {
   165  					return err
   166  				}
   167  			}
   168  		}
   169  	case getopt.LongOption:
   170  		for _, opt := range opts.opts {
   171  			if opt.Long != "" && strings.HasPrefix(opt.Long, ctx.Text) {
   172  				err := putLongOpt(opt)
   173  				if err != nil {
   174  					return err
   175  				}
   176  			}
   177  		}
   178  	case getopt.ChainShortOption:
   179  		for _, opt := range opts.opts {
   180  			if opt.Short != 0 {
   181  				// TODO(xiaq): Loses chained options.
   182  				err := putShortOpt(opt)
   183  				if err != nil {
   184  					return err
   185  				}
   186  			}
   187  		}
   188  	case getopt.OptionArgument:
   189  		gen := opts.argGenerator[ctx.Option.Spec]
   190  		if gen != nil {
   191  			return call(gen, ctx.Option.Argument)
   192  		}
   193  	}
   194  	return nil
   195  }
   196  
   197  // TODO(xiaq): Simplify most of the parsing below with reflection.
   198  
   199  func parseGetoptArgs(v interface{}) ([]string, error) {
   200  	var args []string
   201  	var err error
   202  	errIterate := vals.Iterate(v, func(v interface{}) bool {
   203  		arg, ok := v.(string)
   204  		if !ok {
   205  			err = fmt.Errorf("arg should be string, got %s", vals.Kind(v))
   206  			return false
   207  		}
   208  		args = append(args, arg)
   209  		return true
   210  	})
   211  	if errIterate != nil {
   212  		err = errIterate
   213  	}
   214  	return args, err
   215  }
   216  
   217  type parsedOptSpecs struct {
   218  	opts         []*getopt.OptionSpec
   219  	desc         map[*getopt.OptionSpec]string
   220  	argDesc      map[*getopt.OptionSpec]string
   221  	argGenerator map[*getopt.OptionSpec]eval.Callable
   222  }
   223  
   224  func parseGetoptOptSpecs(v interface{}) (parsedOptSpecs, error) {
   225  	result := parsedOptSpecs{
   226  		nil, map[*getopt.OptionSpec]string{},
   227  		map[*getopt.OptionSpec]string{}, map[*getopt.OptionSpec]eval.Callable{}}
   228  
   229  	var err error
   230  	errIterate := vals.Iterate(v, func(v interface{}) bool {
   231  		m, ok := v.(vals.Map)
   232  		if !ok {
   233  			err = fmt.Errorf("opt should be map, got %s", vals.Kind(v))
   234  			return false
   235  		}
   236  
   237  		opt := &getopt.OptionSpec{}
   238  
   239  		getStringField := func(k string) (string, bool, error) {
   240  			v, ok := m.Index(k)
   241  			if !ok {
   242  				return "", false, nil
   243  			}
   244  			if vs, ok := v.(string); ok {
   245  				return vs, true, nil
   246  			}
   247  			return "", false,
   248  				fmt.Errorf("%s should be string, got %s", k, vals.Kind(v))
   249  		}
   250  		getCallableField := func(k string) (eval.Callable, bool, error) {
   251  			v, ok := m.Index(k)
   252  			if !ok {
   253  				return nil, false, nil
   254  			}
   255  			if vb, ok := v.(eval.Callable); ok {
   256  				return vb, true, nil
   257  			}
   258  			return nil, false,
   259  				fmt.Errorf("%s should be fn, got %s", k, vals.Kind(v))
   260  		}
   261  		getBoolField := func(k string) (bool, bool, error) {
   262  			v, ok := m.Index(k)
   263  			if !ok {
   264  				return false, false, nil
   265  			}
   266  			if vb, ok := v.(bool); ok {
   267  				return vb, true, nil
   268  			}
   269  			return false, false,
   270  				fmt.Errorf("%s should be bool, got %s", k, vals.Kind(v))
   271  		}
   272  
   273  		if s, ok, errGet := getStringField("short"); ok {
   274  			r, size := utf8.DecodeRuneInString(s)
   275  			if r == utf8.RuneError || size != len(s) {
   276  				err = fmt.Errorf(
   277  					"short should be exactly one rune, got %v", parse.Quote(s))
   278  				return false
   279  			}
   280  			opt.Short = r
   281  		} else if errGet != nil {
   282  			err = errGet
   283  			return false
   284  		}
   285  		if s, ok, errGet := getStringField("long"); ok {
   286  			opt.Long = s
   287  		} else if errGet != nil {
   288  			err = errGet
   289  			return false
   290  		}
   291  		if opt.Short == 0 && opt.Long == "" {
   292  			err = errors.New(
   293  				"opt should have at least one of short and long forms")
   294  			return false
   295  		}
   296  
   297  		argRequired, _, errGet := getBoolField("arg-required")
   298  		if errGet != nil {
   299  			err = errGet
   300  			return false
   301  		}
   302  		argOptional, _, errGet := getBoolField("arg-optional")
   303  		if errGet != nil {
   304  			err = errGet
   305  			return false
   306  		}
   307  		switch {
   308  		case argRequired && argOptional:
   309  			err = errors.New(
   310  				"opt cannot have both arg-required and arg-optional")
   311  			return false
   312  		case argRequired:
   313  			opt.Arity = getopt.RequiredArgument
   314  		case argOptional:
   315  			opt.Arity = getopt.OptionalArgument
   316  		}
   317  
   318  		if s, ok, errGet := getStringField("desc"); ok {
   319  			result.desc[opt] = s
   320  		} else if errGet != nil {
   321  			err = errGet
   322  			return false
   323  		}
   324  		if s, ok, errGet := getStringField("arg-desc"); ok {
   325  			result.argDesc[opt] = s
   326  		} else if errGet != nil {
   327  			err = errGet
   328  			return false
   329  		}
   330  		if f, ok, errGet := getCallableField("completer"); ok {
   331  			result.argGenerator[opt] = f
   332  		} else if errGet != nil {
   333  			err = errGet
   334  			return false
   335  		}
   336  
   337  		result.opts = append(result.opts, opt)
   338  		return true
   339  	})
   340  	if errIterate != nil {
   341  		err = errIterate
   342  	}
   343  	return result, err
   344  }
   345  
   346  func parseGetoptArgHandlers(v interface{}) ([]eval.Callable, bool, error) {
   347  	var argHandlers []eval.Callable
   348  	var variadic bool
   349  	var err error
   350  	errIterate := vals.Iterate(v, func(v interface{}) bool {
   351  		sv, ok := v.(string)
   352  		if ok {
   353  			if sv == "..." {
   354  				variadic = true
   355  				return true
   356  			}
   357  			err = fmt.Errorf(
   358  				"string except for ... not allowed as argument handler, got %s",
   359  				parse.Quote(sv))
   360  			return false
   361  		}
   362  		argHandler, ok := v.(eval.Callable)
   363  		if !ok {
   364  			err = fmt.Errorf(
   365  				"argument handler should be fn, got %s", vals.Kind(v))
   366  		}
   367  		argHandlers = append(argHandlers, argHandler)
   368  		return true
   369  	})
   370  	if errIterate != nil {
   371  		err = errIterate
   372  	}
   373  	return argHandlers, variadic, err
   374  }