mirror of
https://github.com/crazy-max/diun.git
synced 2024-12-22 19:38:28 +00:00
9a00af98bc
Bumps [github.com/alecthomas/kong](https://github.com/alecthomas/kong) from 0.9.0 to 1.6.0. - [Commits](https://github.com/alecthomas/kong/compare/v0.9.0...v1.6.0) --- updated-dependencies: - dependency-name: github.com/alecthomas/kong dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com>
502 lines
13 KiB
Go
502 lines
13 KiB
Go
package kong
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"os/user"
|
|
"path/filepath"
|
|
"reflect"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
// An Option applies optional changes to the Kong application.
|
|
type Option interface {
|
|
Apply(k *Kong) error
|
|
}
|
|
|
|
// OptionFunc is function that adheres to the Option interface.
|
|
type OptionFunc func(k *Kong) error
|
|
|
|
func (o OptionFunc) Apply(k *Kong) error { return o(k) } //nolint: revive
|
|
|
|
// Vars sets the variables to use for interpolation into help strings and default values.
|
|
//
|
|
// See README for details.
|
|
type Vars map[string]string
|
|
|
|
// Apply lets Vars act as an Option.
|
|
func (v Vars) Apply(k *Kong) error {
|
|
for key, value := range v {
|
|
k.vars[key] = value
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// CloneWith clones the current Vars and merges "vars" onto the clone.
|
|
func (v Vars) CloneWith(vars Vars) Vars {
|
|
out := make(Vars, len(v)+len(vars))
|
|
for key, value := range v {
|
|
out[key] = value
|
|
}
|
|
for key, value := range vars {
|
|
out[key] = value
|
|
}
|
|
return out
|
|
}
|
|
|
|
// Exit overrides the function used to terminate. This is useful for testing or interactive use.
|
|
func Exit(exit func(int)) Option {
|
|
return OptionFunc(func(k *Kong) error {
|
|
k.Exit = exit
|
|
return nil
|
|
})
|
|
}
|
|
|
|
type embedded struct {
|
|
strct any
|
|
tags []string
|
|
}
|
|
|
|
// Embed a struct into the root of the CLI.
|
|
//
|
|
// "strct" must be a pointer to a structure.
|
|
func Embed(strct any, tags ...string) Option {
|
|
t := reflect.TypeOf(strct)
|
|
if t.Kind() != reflect.Ptr || t.Elem().Kind() != reflect.Struct {
|
|
panic("kong: Embed() must be called with a pointer to a struct")
|
|
}
|
|
return OptionFunc(func(k *Kong) error {
|
|
k.embedded = append(k.embedded, embedded{strct, tags})
|
|
return nil
|
|
})
|
|
}
|
|
|
|
type dynamicCommand struct {
|
|
name string
|
|
help string
|
|
group string
|
|
tags []string
|
|
cmd interface{}
|
|
}
|
|
|
|
// DynamicCommand registers a dynamically constructed command with the root of the CLI.
|
|
//
|
|
// This is useful for command-line structures that are extensible via user-provided plugins.
|
|
//
|
|
// "tags" is a list of extra tag strings to parse, in the form <key>:"<value>".
|
|
func DynamicCommand(name, help, group string, cmd interface{}, tags ...string) Option {
|
|
return OptionFunc(func(k *Kong) error {
|
|
if run := getMethod(reflect.Indirect(reflect.ValueOf(cmd)), "Run"); !run.IsValid() {
|
|
return fmt.Errorf("kong: DynamicCommand %q must be a type with a 'Run' method; got %T", name, cmd)
|
|
}
|
|
|
|
k.dynamicCommands = append(k.dynamicCommands, &dynamicCommand{
|
|
name: name,
|
|
help: help,
|
|
group: group,
|
|
cmd: cmd,
|
|
tags: tags,
|
|
})
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// NoDefaultHelp disables the default help flags.
|
|
func NoDefaultHelp() Option {
|
|
return OptionFunc(func(k *Kong) error {
|
|
k.noDefaultHelp = true
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// PostBuild provides read/write access to kong.Kong after initial construction of the model is complete but before
|
|
// parsing occurs.
|
|
//
|
|
// This is useful for, e.g., adding short options to flags, updating help, etc.
|
|
func PostBuild(fn func(*Kong) error) Option {
|
|
return OptionFunc(func(k *Kong) error {
|
|
k.postBuildOptions = append(k.postBuildOptions, OptionFunc(fn))
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// Name overrides the application name.
|
|
func Name(name string) Option {
|
|
return PostBuild(func(k *Kong) error {
|
|
k.Model.Name = name
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// Description sets the application description.
|
|
func Description(description string) Option {
|
|
return PostBuild(func(k *Kong) error {
|
|
k.Model.Help = description
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// TypeMapper registers a mapper to a type.
|
|
func TypeMapper(typ reflect.Type, mapper Mapper) Option {
|
|
return OptionFunc(func(k *Kong) error {
|
|
k.registry.RegisterType(typ, mapper)
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// KindMapper registers a mapper to a kind.
|
|
func KindMapper(kind reflect.Kind, mapper Mapper) Option {
|
|
return OptionFunc(func(k *Kong) error {
|
|
k.registry.RegisterKind(kind, mapper)
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// ValueMapper registers a mapper to a field value.
|
|
func ValueMapper(ptr interface{}, mapper Mapper) Option {
|
|
return OptionFunc(func(k *Kong) error {
|
|
k.registry.RegisterValue(ptr, mapper)
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// NamedMapper registers a mapper to a name.
|
|
func NamedMapper(name string, mapper Mapper) Option {
|
|
return OptionFunc(func(k *Kong) error {
|
|
k.registry.RegisterName(name, mapper)
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// Writers overrides the default writers. Useful for testing or interactive use.
|
|
func Writers(stdout, stderr io.Writer) Option {
|
|
return OptionFunc(func(k *Kong) error {
|
|
k.Stdout = stdout
|
|
k.Stderr = stderr
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// Bind binds values for hooks and Run() function arguments.
|
|
//
|
|
// Any arguments passed will be available to the receiving hook functions, but may be omitted. Additionally, *Kong and
|
|
// the current *Context will also be made available.
|
|
//
|
|
// There are two hook points:
|
|
//
|
|
// BeforeApply(...) error
|
|
// AfterApply(...) error
|
|
//
|
|
// Called before validation/assignment, and immediately after validation/assignment, respectively.
|
|
func Bind(args ...interface{}) Option {
|
|
return OptionFunc(func(k *Kong) error {
|
|
k.bindings.add(args...)
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// BindTo allows binding of implementations to interfaces.
|
|
//
|
|
// BindTo(impl, (*iface)(nil))
|
|
func BindTo(impl, iface interface{}) Option {
|
|
return OptionFunc(func(k *Kong) error {
|
|
k.bindings.addTo(impl, iface)
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// BindToProvider binds an injected value to a provider function.
|
|
//
|
|
// The provider function must have the signature:
|
|
//
|
|
// func() (interface{}, error)
|
|
//
|
|
// This is useful when the Run() function of different commands require different values that may
|
|
// not all be initialisable from the main() function.
|
|
func BindToProvider(provider interface{}) Option {
|
|
return OptionFunc(func(k *Kong) error {
|
|
return k.bindings.addProvider(provider)
|
|
})
|
|
}
|
|
|
|
// Help printer to use.
|
|
func Help(help HelpPrinter) Option {
|
|
return OptionFunc(func(k *Kong) error {
|
|
k.help = help
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// ShortHelp configures the short usage message.
|
|
//
|
|
// It should be used together with kong.ShortUsageOnError() to display a
|
|
// custom short usage message on errors.
|
|
func ShortHelp(shortHelp HelpPrinter) Option {
|
|
return OptionFunc(func(k *Kong) error {
|
|
k.shortHelp = shortHelp
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// HelpFormatter configures how the help text is formatted.
|
|
//
|
|
// Deprecated: Use ValueFormatter() instead.
|
|
func HelpFormatter(helpFormatter HelpValueFormatter) Option {
|
|
return OptionFunc(func(k *Kong) error {
|
|
k.helpFormatter = helpFormatter
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// ValueFormatter configures how the help text is formatted.
|
|
func ValueFormatter(helpFormatter HelpValueFormatter) Option {
|
|
return OptionFunc(func(k *Kong) error {
|
|
k.helpFormatter = helpFormatter
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// ConfigureHelp sets the HelpOptions to use for printing help.
|
|
func ConfigureHelp(options HelpOptions) Option {
|
|
return OptionFunc(func(k *Kong) error {
|
|
k.helpOptions = options
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// AutoGroup automatically assigns groups to flags.
|
|
func AutoGroup(format func(parent Visitable, flag *Flag) *Group) Option {
|
|
return PostBuild(func(kong *Kong) error {
|
|
parents := []Visitable{kong.Model}
|
|
return Visit(kong.Model, func(node Visitable, next Next) error {
|
|
if flag, ok := node.(*Flag); ok && flag.Group == nil {
|
|
flag.Group = format(parents[len(parents)-1], flag)
|
|
}
|
|
parents = append(parents, node)
|
|
defer func() { parents = parents[:len(parents)-1] }()
|
|
return next(nil)
|
|
})
|
|
})
|
|
}
|
|
|
|
// Groups associates `group` field tags with group metadata.
|
|
//
|
|
// This option is used to simplify Kong tags while providing
|
|
// rich group information such as title and optional description.
|
|
//
|
|
// Each key in the "groups" map corresponds to the value of a
|
|
// `group` Kong tag, while the first line of the value will be
|
|
// the title, and subsequent lines if any will be the description of
|
|
// the group.
|
|
//
|
|
// See also ExplicitGroups for a more structured alternative.
|
|
type Groups map[string]string
|
|
|
|
func (g Groups) Apply(k *Kong) error { //nolint: revive
|
|
for key, info := range g {
|
|
lines := strings.Split(info, "\n")
|
|
title := strings.TrimSpace(lines[0])
|
|
description := ""
|
|
if len(lines) > 1 {
|
|
description = strings.TrimSpace(strings.Join(lines[1:], "\n"))
|
|
}
|
|
k.groups = append(k.groups, Group{
|
|
Key: key,
|
|
Title: title,
|
|
Description: description,
|
|
})
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ExplicitGroups associates `group` field tags with their metadata.
|
|
//
|
|
// It can be used to provide a title or header to a command or flag group.
|
|
func ExplicitGroups(groups []Group) Option {
|
|
return OptionFunc(func(k *Kong) error {
|
|
k.groups = groups
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// UsageOnError configures Kong to display context-sensitive usage if FatalIfErrorf is called with an error.
|
|
func UsageOnError() Option {
|
|
return OptionFunc(func(k *Kong) error {
|
|
k.usageOnError = fullUsage
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// ShortUsageOnError configures Kong to display context-sensitive short
|
|
// usage if FatalIfErrorf is called with an error. The default short
|
|
// usage message can be overridden with kong.ShortHelp(...).
|
|
func ShortUsageOnError() Option {
|
|
return OptionFunc(func(k *Kong) error {
|
|
k.usageOnError = shortUsage
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// ClearResolvers clears all existing resolvers.
|
|
func ClearResolvers() Option {
|
|
return OptionFunc(func(k *Kong) error {
|
|
k.resolvers = nil
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// Resolvers registers flag resolvers.
|
|
func Resolvers(resolvers ...Resolver) Option {
|
|
return OptionFunc(func(k *Kong) error {
|
|
k.resolvers = append(k.resolvers, resolvers...)
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// IgnoreFields will cause kong.New() to skip field names that match any
|
|
// of the provided regex patterns. This is useful if you are not able to add a
|
|
// kong="-" struct tag to a struct/element before the call to New.
|
|
//
|
|
// Example: When referencing protoc generated structs, you will likely want to
|
|
// ignore/skip XXX_* fields.
|
|
func IgnoreFields(regexes ...string) Option {
|
|
return OptionFunc(func(k *Kong) error {
|
|
for _, r := range regexes {
|
|
if r == "" {
|
|
return errors.New("regex input cannot be empty")
|
|
}
|
|
|
|
re, err := regexp.Compile(r)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to compile regex: %v", err)
|
|
}
|
|
|
|
k.ignoreFields = append(k.ignoreFields, re)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// ConfigurationLoader is a function that builds a resolver from a file.
|
|
type ConfigurationLoader func(r io.Reader) (Resolver, error)
|
|
|
|
// Configuration provides Kong with support for loading defaults from a set of configuration files.
|
|
//
|
|
// Paths will be opened in order, and "loader" will be used to provide a Resolver which is registered with Kong.
|
|
//
|
|
// Note: The JSON function is a ConfigurationLoader.
|
|
//
|
|
// ~ and variable expansion will occur on the provided paths.
|
|
func Configuration(loader ConfigurationLoader, paths ...string) Option {
|
|
return OptionFunc(func(k *Kong) error {
|
|
k.loader = loader
|
|
for _, path := range paths {
|
|
f, err := os.Open(ExpandPath(path))
|
|
if err != nil {
|
|
if os.IsNotExist(err) || os.IsPermission(err) {
|
|
continue
|
|
}
|
|
|
|
return err
|
|
}
|
|
f.Close()
|
|
|
|
resolver, err := k.LoadConfig(path)
|
|
if err != nil {
|
|
return fmt.Errorf("%s: %v", path, err)
|
|
}
|
|
if resolver != nil {
|
|
k.resolvers = append(k.resolvers, resolver)
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// ExpandPath is a helper function to expand a relative or home-relative path to an absolute path.
|
|
//
|
|
// eg. ~/.someconf -> /home/alec/.someconf
|
|
func ExpandPath(path string) string {
|
|
if filepath.IsAbs(path) {
|
|
return path
|
|
}
|
|
if strings.HasPrefix(path, "~/") {
|
|
user, err := user.Current()
|
|
if err != nil {
|
|
return path
|
|
}
|
|
return filepath.Join(user.HomeDir, path[2:])
|
|
}
|
|
abspath, err := filepath.Abs(path)
|
|
if err != nil {
|
|
return path
|
|
}
|
|
return abspath
|
|
}
|
|
|
|
func siftStrings(ss []string, filter func(s string) bool) []string {
|
|
i := 0
|
|
ss = append([]string(nil), ss...)
|
|
for _, s := range ss {
|
|
if filter(s) {
|
|
ss[i] = s
|
|
i++
|
|
}
|
|
}
|
|
return ss[0:i]
|
|
}
|
|
|
|
// DefaultEnvars option inits environment names for flags.
|
|
// The name will not generate if tag "env" is "-".
|
|
// Predefined environment variables are skipped.
|
|
//
|
|
// For example:
|
|
//
|
|
// --some.value -> PREFIX_SOME_VALUE
|
|
func DefaultEnvars(prefix string) Option {
|
|
processFlag := func(flag *Flag) {
|
|
switch env := flag.Envs; {
|
|
case flag.Name == "help":
|
|
return
|
|
case len(env) == 1 && env[0] == "-":
|
|
flag.Envs = nil
|
|
return
|
|
case len(env) > 0:
|
|
return
|
|
}
|
|
replacer := strings.NewReplacer("-", "_", ".", "_")
|
|
names := append([]string{prefix}, camelCase(replacer.Replace(flag.Name))...)
|
|
names = siftStrings(names, func(s string) bool { return !(s == "_" || strings.TrimSpace(s) == "") })
|
|
name := strings.ToUpper(strings.Join(names, "_"))
|
|
flag.Envs = append(flag.Envs, name)
|
|
flag.Value.Tag.Envs = append(flag.Value.Tag.Envs, name)
|
|
}
|
|
|
|
var processNode func(node *Node)
|
|
processNode = func(node *Node) {
|
|
for _, flag := range node.Flags {
|
|
processFlag(flag)
|
|
}
|
|
for _, node := range node.Children {
|
|
processNode(node)
|
|
}
|
|
}
|
|
|
|
return PostBuild(func(k *Kong) error {
|
|
processNode(k.Model.Node)
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// FlagNamer allows you to override the default kebab-case automated flag name generation.
|
|
func FlagNamer(namer func(fieldName string) string) Option {
|
|
return OptionFunc(func(k *Kong) error {
|
|
k.flagNamer = namer
|
|
return nil
|
|
})
|
|
}
|