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 }) } // WithHyphenPrefixedParameters enables or disables hyphen-prefixed parameters. // // These are disabled by default. func WithHyphenPrefixedParameters(enable bool) Option { return OptionFunc(func(k *Kong) error { k.allowHyphenated = enable 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 any } // 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 :"". func DynamicCommand(name, help, group string, cmd any, tags ...string) Option { return OptionFunc(func(k *Kong) error { 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 }) } // WithBeforeReset registers a hook to run before fields values are reset to their defaults // (as specified in the grammar) or to zero values. func WithBeforeReset(fn any) Option { return withHook("BeforeReset", fn) } // WithBeforeResolve registers a hook to run before resolvers are applied. func WithBeforeResolve(fn any) Option { return withHook("BeforeResolve", fn) } // WithBeforeApply registers a hook to run before command line arguments are applied to the grammar. func WithBeforeApply(fn any) Option { return withHook("BeforeApply", fn) } // WithAfterApply registers a hook to run after values are applied to the grammar and validated. func WithAfterApply(fn any) Option { return withHook("AfterApply", fn) } // withHook registers a named hook. func withHook(name string, fn any) Option { value := reflect.ValueOf(fn) if value.Kind() != reflect.Func { panic(fmt.Errorf("expected function, got %s", value.Type())) } return OptionFunc(func(k *Kong) error { k.hooks[name] = append(k.hooks[name], value) 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 any, 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 ...any) 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 any) 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 one of the following signatures: // // func(...) (T, error) // func(...) T // // Where arguments to the function are injected by Kong. // // 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 any) Option { return OptionFunc(func(k *Kong) error { return k.bindings.addProvider(provider, false /* singleton */) }) } // BindSingletonProvider binds an injected value to a provider function. // The provider function must have the signature: // // func(...) (T, error) // func(...) T // // Unlike [BindToProvider], the provider function will only be called // at most once, and the result will be cached and reused // across multiple recipients of the injected value. func BindSingletonProvider(provider any) Option { return OptionFunc(func(k *Kong) error { return k.bindings.addProvider(provider, true /* singleton */) }) } // 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 }) }