github.com/alecthomas/kong@v0.9.1-0.20240410131203-2ab5733f1179/kong.go (about)

     1  package kong
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"path/filepath"
     9  	"reflect"
    10  	"regexp"
    11  	"strings"
    12  )
    13  
    14  var (
    15  	callbackReturnSignature = reflect.TypeOf((*error)(nil)).Elem()
    16  )
    17  
    18  func failField(parent reflect.Value, field reflect.StructField, format string, args ...interface{}) error {
    19  	name := parent.Type().Name()
    20  	if name == "" {
    21  		name = "<anonymous struct>"
    22  	}
    23  	return fmt.Errorf("%s.%s: %s", name, field.Name, fmt.Sprintf(format, args...))
    24  }
    25  
    26  // Must creates a new Parser or panics if there is an error.
    27  func Must(ast interface{}, options ...Option) *Kong {
    28  	k, err := New(ast, options...)
    29  	if err != nil {
    30  		panic(err)
    31  	}
    32  	return k
    33  }
    34  
    35  type usageOnError int
    36  
    37  const (
    38  	shortUsage usageOnError = iota + 1
    39  	fullUsage
    40  )
    41  
    42  // Kong is the main parser type.
    43  type Kong struct {
    44  	// Grammar model.
    45  	Model *Application
    46  
    47  	// Termination function (defaults to os.Exit)
    48  	Exit func(int)
    49  
    50  	Stdout io.Writer
    51  	Stderr io.Writer
    52  
    53  	bindings     bindings
    54  	loader       ConfigurationLoader
    55  	resolvers    []Resolver
    56  	registry     *Registry
    57  	ignoreFields []*regexp.Regexp
    58  
    59  	noDefaultHelp bool
    60  	usageOnError  usageOnError
    61  	help          HelpPrinter
    62  	shortHelp     HelpPrinter
    63  	helpFormatter HelpValueFormatter
    64  	helpOptions   HelpOptions
    65  	helpFlag      *Flag
    66  	groups        []Group
    67  	vars          Vars
    68  	flagNamer     func(string) string
    69  
    70  	// Set temporarily by Options. These are applied after build().
    71  	postBuildOptions []Option
    72  	embedded         []embedded
    73  	dynamicCommands  []*dynamicCommand
    74  }
    75  
    76  // New creates a new Kong parser on grammar.
    77  //
    78  // See the README (https://github.com/alecthomas/kong) for usage instructions.
    79  func New(grammar interface{}, options ...Option) (*Kong, error) {
    80  	k := &Kong{
    81  		Exit:          os.Exit,
    82  		Stdout:        os.Stdout,
    83  		Stderr:        os.Stderr,
    84  		registry:      NewRegistry().RegisterDefaults(),
    85  		vars:          Vars{},
    86  		bindings:      bindings{},
    87  		helpFormatter: DefaultHelpValueFormatter,
    88  		ignoreFields:  make([]*regexp.Regexp, 0),
    89  		flagNamer: func(s string) string {
    90  			return strings.ToLower(dashedString(s))
    91  		},
    92  	}
    93  
    94  	options = append(options, Bind(k))
    95  
    96  	for _, option := range options {
    97  		if err := option.Apply(k); err != nil {
    98  			return nil, err
    99  		}
   100  	}
   101  
   102  	if k.help == nil {
   103  		k.help = DefaultHelpPrinter
   104  	}
   105  
   106  	if k.shortHelp == nil {
   107  		k.shortHelp = DefaultShortHelpPrinter
   108  	}
   109  
   110  	model, err := build(k, grammar)
   111  	if err != nil {
   112  		return k, err
   113  	}
   114  	model.Name = filepath.Base(os.Args[0])
   115  	k.Model = model
   116  	k.Model.HelpFlag = k.helpFlag
   117  
   118  	// Embed any embedded structs.
   119  	for _, embed := range k.embedded {
   120  		tag, err := parseTagString(strings.Join(embed.tags, " ")) //nolint:govet
   121  		if err != nil {
   122  			return nil, err
   123  		}
   124  		tag.Embed = true
   125  		v := reflect.Indirect(reflect.ValueOf(embed.strct))
   126  		node, err := buildNode(k, v, CommandNode, tag, map[string]bool{})
   127  		if err != nil {
   128  			return nil, err
   129  		}
   130  		for _, child := range node.Children {
   131  			child.Parent = k.Model.Node
   132  			k.Model.Children = append(k.Model.Children, child)
   133  		}
   134  		k.Model.Flags = append(k.Model.Flags, node.Flags...)
   135  	}
   136  
   137  	// Synthesise command nodes.
   138  	for _, dcmd := range k.dynamicCommands {
   139  		tag, terr := parseTagString(strings.Join(dcmd.tags, " "))
   140  		if terr != nil {
   141  			return nil, terr
   142  		}
   143  		tag.Name = dcmd.name
   144  		tag.Help = dcmd.help
   145  		tag.Group = dcmd.group
   146  		tag.Cmd = true
   147  		v := reflect.Indirect(reflect.ValueOf(dcmd.cmd))
   148  		err = buildChild(k, k.Model.Node, CommandNode, reflect.Value{}, reflect.StructField{
   149  			Name: dcmd.name,
   150  			Type: v.Type(),
   151  		}, v, tag, dcmd.name, map[string]bool{})
   152  		if err != nil {
   153  			return nil, err
   154  		}
   155  	}
   156  
   157  	for _, option := range k.postBuildOptions {
   158  		if err = option.Apply(k); err != nil {
   159  			return nil, err
   160  		}
   161  	}
   162  	k.postBuildOptions = nil
   163  
   164  	if err = k.interpolate(k.Model.Node); err != nil {
   165  		return nil, err
   166  	}
   167  
   168  	k.bindings.add(k.vars)
   169  
   170  	return k, nil
   171  }
   172  
   173  type varStack []Vars
   174  
   175  func (v *varStack) head() Vars { return (*v)[len(*v)-1] }
   176  func (v *varStack) pop()       { *v = (*v)[:len(*v)-1] }
   177  func (v *varStack) push(vars Vars) Vars {
   178  	if len(*v) != 0 {
   179  		vars = (*v)[len(*v)-1].CloneWith(vars)
   180  	}
   181  	*v = append(*v, vars)
   182  	return vars
   183  }
   184  
   185  // Interpolate variables into model.
   186  func (k *Kong) interpolate(node *Node) (err error) {
   187  	stack := varStack{}
   188  	return Visit(node, func(node Visitable, next Next) error {
   189  		switch node := node.(type) {
   190  		case *Node:
   191  			vars := stack.push(node.Vars())
   192  			node.Help, err = interpolate(node.Help, vars, nil)
   193  			if err != nil {
   194  				return fmt.Errorf("help for %s: %s", node.Path(), err)
   195  			}
   196  			err = next(nil)
   197  			stack.pop()
   198  			return err
   199  
   200  		case *Value:
   201  			return next(k.interpolateValue(node, stack.head()))
   202  		}
   203  		return next(nil)
   204  	})
   205  }
   206  
   207  func (k *Kong) interpolateValue(value *Value, vars Vars) (err error) {
   208  	if len(value.Tag.Vars) > 0 {
   209  		vars = vars.CloneWith(value.Tag.Vars)
   210  	}
   211  	if varsContributor, ok := value.Mapper.(VarsContributor); ok {
   212  		vars = vars.CloneWith(varsContributor.Vars(value))
   213  	}
   214  
   215  	if value.Enum, err = interpolate(value.Enum, vars, nil); err != nil {
   216  		return fmt.Errorf("enum for %s: %s", value.Summary(), err)
   217  	}
   218  
   219  	updatedVars := map[string]string{
   220  		"default": value.Default,
   221  		"enum":    value.Enum,
   222  	}
   223  	if value.Default, err = interpolate(value.Default, vars, nil); err != nil {
   224  		return fmt.Errorf("default value for %s: %s", value.Summary(), err)
   225  	}
   226  	if value.Enum, err = interpolate(value.Enum, vars, nil); err != nil {
   227  		return fmt.Errorf("enum value for %s: %s", value.Summary(), err)
   228  	}
   229  	if value.Flag != nil {
   230  		for i, env := range value.Flag.Envs {
   231  			if value.Flag.Envs[i], err = interpolate(env, vars, nil); err != nil {
   232  				return fmt.Errorf("env value for %s: %s", value.Summary(), err)
   233  			}
   234  		}
   235  		value.Tag.Envs = value.Flag.Envs
   236  		updatedVars["env"] = ""
   237  		if len(value.Flag.Envs) != 0 {
   238  			updatedVars["env"] = value.Flag.Envs[0]
   239  		}
   240  	}
   241  	value.Help, err = interpolate(value.Help, vars, updatedVars)
   242  	if err != nil {
   243  		return fmt.Errorf("help for %s: %s", value.Summary(), err)
   244  	}
   245  	return nil
   246  }
   247  
   248  // Provide additional builtin flags, if any.
   249  func (k *Kong) extraFlags() []*Flag {
   250  	if k.noDefaultHelp {
   251  		return nil
   252  	}
   253  	var helpTarget helpValue
   254  	value := reflect.ValueOf(&helpTarget).Elem()
   255  	helpFlag := &Flag{
   256  		Short: 'h',
   257  		Value: &Value{
   258  			Name:         "help",
   259  			Help:         "Show context-sensitive help.",
   260  			OrigHelp:     "Show context-sensitive help.",
   261  			Target:       value,
   262  			Tag:          &Tag{},
   263  			Mapper:       k.registry.ForValue(value),
   264  			DefaultValue: reflect.ValueOf(false),
   265  		},
   266  	}
   267  	helpFlag.Flag = helpFlag
   268  	k.helpFlag = helpFlag
   269  	return []*Flag{helpFlag}
   270  }
   271  
   272  // Parse arguments into target.
   273  //
   274  // The return Context can be used to further inspect the parsed command-line, to format help, to find the
   275  // selected command, to run command Run() methods, and so on. See Context and README for more information.
   276  //
   277  // Will return a ParseError if a *semantically* invalid command-line is encountered (as opposed to a syntactically
   278  // invalid one, which will report a normal error).
   279  func (k *Kong) Parse(args []string) (ctx *Context, err error) {
   280  	ctx, err = Trace(k, args)
   281  	if err != nil {
   282  		return nil, err
   283  	}
   284  	if ctx.Error != nil {
   285  		return nil, &ParseError{error: ctx.Error, Context: ctx}
   286  	}
   287  	if err = k.applyHook(ctx, "BeforeReset"); err != nil {
   288  		return nil, &ParseError{error: err, Context: ctx}
   289  	}
   290  	if err = ctx.Reset(); err != nil {
   291  		return nil, &ParseError{error: err, Context: ctx}
   292  	}
   293  	if err = k.applyHook(ctx, "BeforeResolve"); err != nil {
   294  		return nil, &ParseError{error: err, Context: ctx}
   295  	}
   296  	if err = ctx.Resolve(); err != nil {
   297  		return nil, &ParseError{error: err, Context: ctx}
   298  	}
   299  	if err = k.applyHook(ctx, "BeforeApply"); err != nil {
   300  		return nil, &ParseError{error: err, Context: ctx}
   301  	}
   302  	if _, err = ctx.Apply(); err != nil {
   303  		return nil, &ParseError{error: err, Context: ctx}
   304  	}
   305  	if err = ctx.Validate(); err != nil {
   306  		return nil, &ParseError{error: err, Context: ctx}
   307  	}
   308  	if err = k.applyHook(ctx, "AfterApply"); err != nil {
   309  		return nil, &ParseError{error: err, Context: ctx}
   310  	}
   311  	return ctx, nil
   312  }
   313  
   314  func (k *Kong) applyHook(ctx *Context, name string) error {
   315  	for _, trace := range ctx.Path {
   316  		var value reflect.Value
   317  		switch {
   318  		case trace.App != nil:
   319  			value = trace.App.Target
   320  		case trace.Argument != nil:
   321  			value = trace.Argument.Target
   322  		case trace.Command != nil:
   323  			value = trace.Command.Target
   324  		case trace.Positional != nil:
   325  			value = trace.Positional.Target
   326  		case trace.Flag != nil:
   327  			value = trace.Flag.Value.Target
   328  		default:
   329  			panic("unsupported Path")
   330  		}
   331  		method := getMethod(value, name)
   332  		if !method.IsValid() {
   333  			continue
   334  		}
   335  		binds := k.bindings.clone()
   336  		binds.add(ctx, trace)
   337  		binds.add(trace.Node().Vars().CloneWith(k.vars))
   338  		binds.merge(ctx.bindings)
   339  		if err := callFunction(method, binds); err != nil {
   340  			return err
   341  		}
   342  	}
   343  	// Path[0] will always be the app root.
   344  	return k.applyHookToDefaultFlags(ctx, ctx.Path[0].Node(), name)
   345  }
   346  
   347  // Call hook on any unset flags with default values.
   348  func (k *Kong) applyHookToDefaultFlags(ctx *Context, node *Node, name string) error {
   349  	if node == nil {
   350  		return nil
   351  	}
   352  	return Visit(node, func(n Visitable, next Next) error {
   353  		node, ok := n.(*Node)
   354  		if !ok {
   355  			return next(nil)
   356  		}
   357  		binds := k.bindings.clone().add(ctx).add(node.Vars().CloneWith(k.vars))
   358  		for _, flag := range node.Flags {
   359  			if !flag.HasDefault || ctx.values[flag.Value].IsValid() || !flag.Target.IsValid() {
   360  				continue
   361  			}
   362  			method := getMethod(flag.Target, name)
   363  			if !method.IsValid() {
   364  				continue
   365  			}
   366  			path := &Path{Flag: flag}
   367  			if err := callFunction(method, binds.clone().add(path)); err != nil {
   368  				return next(err)
   369  			}
   370  		}
   371  		return next(nil)
   372  	})
   373  }
   374  
   375  func formatMultilineMessage(w io.Writer, leaders []string, format string, args ...interface{}) {
   376  	lines := strings.Split(fmt.Sprintf(format, args...), "\n")
   377  	leader := ""
   378  	for _, l := range leaders {
   379  		if l == "" {
   380  			continue
   381  		}
   382  		leader += l + ": "
   383  	}
   384  	fmt.Fprintf(w, "%s%s\n", leader, lines[0])
   385  	for _, line := range lines[1:] {
   386  		fmt.Fprintf(w, "%*s%s\n", len(leader), " ", line)
   387  	}
   388  }
   389  
   390  // Printf writes a message to Kong.Stdout with the application name prefixed.
   391  func (k *Kong) Printf(format string, args ...interface{}) *Kong {
   392  	formatMultilineMessage(k.Stdout, []string{k.Model.Name}, format, args...)
   393  	return k
   394  }
   395  
   396  // Errorf writes a message to Kong.Stderr with the application name prefixed.
   397  func (k *Kong) Errorf(format string, args ...interface{}) *Kong {
   398  	formatMultilineMessage(k.Stderr, []string{k.Model.Name, "error"}, format, args...)
   399  	return k
   400  }
   401  
   402  // Fatalf writes a message to Kong.Stderr with the application name prefixed then exits with a non-zero status.
   403  func (k *Kong) Fatalf(format string, args ...interface{}) {
   404  	k.Errorf(format, args...)
   405  	k.Exit(1)
   406  }
   407  
   408  // FatalIfErrorf terminates with an error message if err != nil.
   409  func (k *Kong) FatalIfErrorf(err error, args ...interface{}) {
   410  	if err == nil {
   411  		return
   412  	}
   413  	msg := err.Error()
   414  	if len(args) > 0 {
   415  		msg = fmt.Sprintf(args[0].(string), args[1:]...) + ": " + err.Error() //nolint
   416  	}
   417  	// Maybe display usage information.
   418  	var parseErr *ParseError
   419  	if errors.As(err, &parseErr) {
   420  		switch k.usageOnError {
   421  		case fullUsage:
   422  			_ = k.help(k.helpOptions, parseErr.Context)
   423  			fmt.Fprintln(k.Stdout)
   424  		case shortUsage:
   425  			_ = k.shortHelp(k.helpOptions, parseErr.Context)
   426  			fmt.Fprintln(k.Stdout)
   427  		}
   428  	}
   429  	k.Fatalf("%s", msg)
   430  }
   431  
   432  // LoadConfig from path using the loader configured via Configuration(loader).
   433  //
   434  // "path" will have ~ and any variables expanded.
   435  func (k *Kong) LoadConfig(path string) (Resolver, error) {
   436  	var err error
   437  	path = ExpandPath(path)
   438  	path, err = interpolate(path, k.vars, nil)
   439  	if err != nil {
   440  		return nil, err
   441  	}
   442  	r, err := os.Open(path) //nolint: gas
   443  	if err != nil {
   444  		return nil, err
   445  	}
   446  	defer r.Close()
   447  
   448  	return k.loader(r)
   449  }