290 lines
7.4 KiB
Go
290 lines
7.4 KiB
Go
|
package kong
|
||
|
|
||
|
import (
|
||
|
"io"
|
||
|
"os"
|
||
|
"os/user"
|
||
|
"path/filepath"
|
||
|
"reflect"
|
||
|
"strings"
|
||
|
|
||
|
"github.com/pkg/errors"
|
||
|
)
|
||
|
|
||
|
// 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: golint
|
||
|
|
||
|
// 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
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// 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 (k *Kong) 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 OptionFunc(func(k *Kong) error {
|
||
|
k.postBuildOptions = append(k.postBuildOptions, OptionFunc(func(k *Kong) error {
|
||
|
k.Model.Name = name
|
||
|
return nil
|
||
|
}))
|
||
|
return nil
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// Description sets the application description.
|
||
|
func Description(description string) Option {
|
||
|
return OptionFunc(func(k *Kong) error {
|
||
|
k.postBuildOptions = append(k.postBuildOptions, OptionFunc(func(k *Kong) error {
|
||
|
k.Model.Help = description
|
||
|
return nil
|
||
|
}))
|
||
|
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 {
|
||
|
valueOf := reflect.ValueOf(impl)
|
||
|
k.bindings[reflect.TypeOf(iface).Elem()] = func() (reflect.Value, error) { return valueOf, nil }
|
||
|
return nil
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// BindToProvider allows binding of provider functions.
|
||
|
//
|
||
|
// 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 {
|
||
|
pv := reflect.ValueOf(provider)
|
||
|
t := pv.Type()
|
||
|
if t.Kind() != reflect.Func || t.NumIn() != 0 || t.NumOut() != 2 || t.Out(1) != reflect.TypeOf((*error)(nil)).Elem() {
|
||
|
return errors.Errorf("%T must be a function with the signature func()(T, error)", provider)
|
||
|
}
|
||
|
rt := pv.Type().Out(0)
|
||
|
k.bindings[rt] = func() (reflect.Value, error) {
|
||
|
out := pv.Call(nil)
|
||
|
errv := out[1]
|
||
|
var err error
|
||
|
if !errv.IsNil() {
|
||
|
err = errv.Interface().(error)
|
||
|
}
|
||
|
return out[0], err
|
||
|
}
|
||
|
return nil
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// Help printer to use.
|
||
|
func Help(help HelpPrinter) Option {
|
||
|
return OptionFunc(func(k *Kong) error {
|
||
|
k.help = help
|
||
|
return nil
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// HelpFormatter configures how the help text is formatted.
|
||
|
func HelpFormatter(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
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// 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 = true
|
||
|
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
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// 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 {
|
||
|
if _, err := os.Stat(ExpandPath(path)); os.IsNotExist(err) {
|
||
|
continue
|
||
|
}
|
||
|
resolver, err := k.LoadConfig(path)
|
||
|
if err != nil {
|
||
|
return errors.Wrap(err, path)
|
||
|
}
|
||
|
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
|
||
|
}
|