src.elv.sh@v0.21.0-dev.0.20240515223629-06979efb9a2a/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/ui"
    14  )
    15  
    16  func completeGetopt(fm *eval.Frame, vArgs, vOpts, vArgHandlers any) error {
    17  	args, err := parseGetoptArgs(vArgs)
    18  	if err != nil {
    19  		return err
    20  	}
    21  	opts, err := parseGetoptOptSpecs(vOpts)
    22  	if err != nil {
    23  		return err
    24  	}
    25  	argHandlers, variadic, err := parseGetoptArgHandlers(vArgHandlers)
    26  	if err != nil {
    27  		return err
    28  	}
    29  
    30  	// TODO: Make the Config field configurable
    31  	_, parsedArgs, ctx := getopt.Complete(args, opts.opts, getopt.GNU)
    32  
    33  	out := fm.ValueOutput()
    34  	putShortOpt := func(opt *getopt.OptionSpec) error {
    35  		c := complexItem{Stem: "-" + string(opt.Short)}
    36  		if d, ok := opts.desc[opt]; ok {
    37  			if e, ok := opts.argDesc[opt]; ok {
    38  				c.Display = ui.T(c.Stem + " " + e + " (" + d + ")")
    39  			} else {
    40  				c.Display = ui.T(c.Stem + " (" + d + ")")
    41  			}
    42  		}
    43  		return out.Put(c)
    44  	}
    45  	putLongOpt := func(opt *getopt.OptionSpec) error {
    46  		c := complexItem{Stem: "--" + opt.Long}
    47  		if d, ok := opts.desc[opt]; ok {
    48  			if e, ok := opts.argDesc[opt]; ok {
    49  				c.Display = ui.T(c.Stem + " " + e + " (" + d + ")")
    50  			} else {
    51  				c.Display = ui.T(c.Stem + " (" + d + ")")
    52  			}
    53  		}
    54  		return out.Put(c)
    55  	}
    56  	call := func(fn eval.Callable, args ...any) error {
    57  		return fn.Call(fm, args, eval.NoOpts)
    58  	}
    59  
    60  	switch ctx.Type {
    61  	case getopt.OptionOrArgument, getopt.Argument:
    62  		// Find argument handler.
    63  		var argHandler eval.Callable
    64  		if len(parsedArgs) < len(argHandlers) {
    65  			argHandler = argHandlers[len(parsedArgs)]
    66  		} else if variadic {
    67  			argHandler = argHandlers[len(argHandlers)-1]
    68  		}
    69  		if argHandler != nil {
    70  			return call(argHandler, ctx.Text)
    71  		}
    72  		// TODO(xiaq): Notify that there is no suitable argument completer.
    73  	case getopt.AnyOption:
    74  		for _, opt := range opts.opts {
    75  			if opt.Short != 0 {
    76  				err := putShortOpt(opt)
    77  				if err != nil {
    78  					return err
    79  				}
    80  			}
    81  			if opt.Long != "" {
    82  				err := putLongOpt(opt)
    83  				if err != nil {
    84  					return err
    85  				}
    86  			}
    87  		}
    88  	case getopt.LongOption:
    89  		for _, opt := range opts.opts {
    90  			if opt.Long != "" && strings.HasPrefix(opt.Long, ctx.Text) {
    91  				err := putLongOpt(opt)
    92  				if err != nil {
    93  					return err
    94  				}
    95  			}
    96  		}
    97  	case getopt.ChainShortOption:
    98  		for _, opt := range opts.opts {
    99  			if opt.Short != 0 {
   100  				// TODO(xiaq): Loses chained options.
   101  				err := putShortOpt(opt)
   102  				if err != nil {
   103  					return err
   104  				}
   105  			}
   106  		}
   107  	case getopt.OptionArgument:
   108  		gen := opts.argGenerator[ctx.Option.Spec]
   109  		if gen != nil {
   110  			return call(gen, ctx.Option.Argument)
   111  		}
   112  	}
   113  	return nil
   114  }
   115  
   116  // TODO(xiaq): Simplify most of the parsing below with reflection.
   117  
   118  func parseGetoptArgs(v any) ([]string, error) {
   119  	var args []string
   120  	var err error
   121  	errIterate := vals.Iterate(v, func(v any) bool {
   122  		arg, ok := v.(string)
   123  		if !ok {
   124  			err = fmt.Errorf("arg should be string, got %s", vals.Kind(v))
   125  			return false
   126  		}
   127  		args = append(args, arg)
   128  		return true
   129  	})
   130  	if errIterate != nil {
   131  		err = errIterate
   132  	}
   133  	return args, err
   134  }
   135  
   136  type parsedOptSpecs struct {
   137  	opts         []*getopt.OptionSpec
   138  	desc         map[*getopt.OptionSpec]string
   139  	argDesc      map[*getopt.OptionSpec]string
   140  	argGenerator map[*getopt.OptionSpec]eval.Callable
   141  }
   142  
   143  func parseGetoptOptSpecs(v any) (parsedOptSpecs, error) {
   144  	result := parsedOptSpecs{
   145  		nil, map[*getopt.OptionSpec]string{},
   146  		map[*getopt.OptionSpec]string{}, map[*getopt.OptionSpec]eval.Callable{}}
   147  
   148  	var err error
   149  	errIterate := vals.Iterate(v, func(v any) bool {
   150  		m, ok := v.(vals.Map)
   151  		if !ok {
   152  			err = fmt.Errorf("opt should be map, got %s", vals.Kind(v))
   153  			return false
   154  		}
   155  
   156  		opt := &getopt.OptionSpec{}
   157  
   158  		getStringField := func(k string) (string, bool, error) {
   159  			v, ok := m.Index(k)
   160  			if !ok {
   161  				return "", false, nil
   162  			}
   163  			if vs, ok := v.(string); ok {
   164  				return vs, true, nil
   165  			}
   166  			return "", false,
   167  				fmt.Errorf("%s should be string, got %s", k, vals.Kind(v))
   168  		}
   169  		getCallableField := func(k string) (eval.Callable, bool, error) {
   170  			v, ok := m.Index(k)
   171  			if !ok {
   172  				return nil, false, nil
   173  			}
   174  			if vb, ok := v.(eval.Callable); ok {
   175  				return vb, true, nil
   176  			}
   177  			return nil, false,
   178  				fmt.Errorf("%s should be fn, got %s", k, vals.Kind(v))
   179  		}
   180  		getBoolField := func(k string) (bool, bool, error) {
   181  			v, ok := m.Index(k)
   182  			if !ok {
   183  				return false, false, nil
   184  			}
   185  			if vb, ok := v.(bool); ok {
   186  				return vb, true, nil
   187  			}
   188  			return false, false,
   189  				fmt.Errorf("%s should be bool, got %s", k, vals.Kind(v))
   190  		}
   191  
   192  		if s, ok, errGet := getStringField("short"); ok {
   193  			r, size := utf8.DecodeRuneInString(s)
   194  			if r == utf8.RuneError || size != len(s) {
   195  				err = fmt.Errorf(
   196  					"short should be exactly one rune, got %v", parse.Quote(s))
   197  				return false
   198  			}
   199  			opt.Short = r
   200  		} else if errGet != nil {
   201  			err = errGet
   202  			return false
   203  		}
   204  		if s, ok, errGet := getStringField("long"); ok {
   205  			opt.Long = s
   206  		} else if errGet != nil {
   207  			err = errGet
   208  			return false
   209  		}
   210  		if opt.Short == 0 && opt.Long == "" {
   211  			err = errors.New(
   212  				"opt should have at least one of short and long forms")
   213  			return false
   214  		}
   215  
   216  		argRequired, _, errGet := getBoolField("arg-required")
   217  		if errGet != nil {
   218  			err = errGet
   219  			return false
   220  		}
   221  		argOptional, _, errGet := getBoolField("arg-optional")
   222  		if errGet != nil {
   223  			err = errGet
   224  			return false
   225  		}
   226  		switch {
   227  		case argRequired && argOptional:
   228  			err = errors.New(
   229  				"opt cannot have both arg-required and arg-optional")
   230  			return false
   231  		case argRequired:
   232  			opt.Arity = getopt.RequiredArgument
   233  		case argOptional:
   234  			opt.Arity = getopt.OptionalArgument
   235  		}
   236  
   237  		if s, ok, errGet := getStringField("desc"); ok {
   238  			result.desc[opt] = s
   239  		} else if errGet != nil {
   240  			err = errGet
   241  			return false
   242  		}
   243  		if s, ok, errGet := getStringField("arg-desc"); ok {
   244  			result.argDesc[opt] = s
   245  		} else if errGet != nil {
   246  			err = errGet
   247  			return false
   248  		}
   249  		if f, ok, errGet := getCallableField("completer"); ok {
   250  			result.argGenerator[opt] = f
   251  		} else if errGet != nil {
   252  			err = errGet
   253  			return false
   254  		}
   255  
   256  		result.opts = append(result.opts, opt)
   257  		return true
   258  	})
   259  	if errIterate != nil {
   260  		err = errIterate
   261  	}
   262  	return result, err
   263  }
   264  
   265  func parseGetoptArgHandlers(v any) ([]eval.Callable, bool, error) {
   266  	var argHandlers []eval.Callable
   267  	var variadic bool
   268  	var err error
   269  	errIterate := vals.Iterate(v, func(v any) bool {
   270  		sv, ok := v.(string)
   271  		if ok {
   272  			if sv == "..." {
   273  				variadic = true
   274  				return true
   275  			}
   276  			err = fmt.Errorf(
   277  				"string except for ... not allowed as argument handler, got %s",
   278  				parse.Quote(sv))
   279  			return false
   280  		}
   281  		argHandler, ok := v.(eval.Callable)
   282  		if !ok {
   283  			err = fmt.Errorf(
   284  				"argument handler should be fn, got %s", vals.Kind(v))
   285  		}
   286  		argHandlers = append(argHandlers, argHandler)
   287  		return true
   288  	})
   289  	if errIterate != nil {
   290  		err = errIterate
   291  	}
   292  	return argHandlers, variadic, err
   293  }