308 lines
7.2 KiB
Go
308 lines
7.2 KiB
Go
package kingpin
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
type flagGroup struct {
|
|
short map[string]*FlagClause
|
|
long map[string]*FlagClause
|
|
flagOrder []*FlagClause
|
|
}
|
|
|
|
func newFlagGroup() *flagGroup {
|
|
return &flagGroup{
|
|
short: map[string]*FlagClause{},
|
|
long: map[string]*FlagClause{},
|
|
}
|
|
}
|
|
|
|
// GetFlag gets a flag definition.
|
|
//
|
|
// This allows existing flags to be modified after definition but before parsing. Useful for
|
|
// modular applications.
|
|
func (f *flagGroup) GetFlag(name string) *FlagClause {
|
|
return f.long[name]
|
|
}
|
|
|
|
// Flag defines a new flag with the given long name and help.
|
|
func (f *flagGroup) Flag(name, help string) *FlagClause {
|
|
flag := newFlag(name, help)
|
|
f.long[name] = flag
|
|
f.flagOrder = append(f.flagOrder, flag)
|
|
return flag
|
|
}
|
|
|
|
func (f *flagGroup) init(defaultEnvarPrefix string) error {
|
|
if err := f.checkDuplicates(); err != nil {
|
|
return err
|
|
}
|
|
for _, flag := range f.long {
|
|
if defaultEnvarPrefix != "" && !flag.noEnvar && flag.envar == "" {
|
|
flag.envar = envarTransform(defaultEnvarPrefix + "_" + flag.name)
|
|
}
|
|
if err := flag.init(); err != nil {
|
|
return err
|
|
}
|
|
if flag.shorthand != 0 {
|
|
f.short[string(flag.shorthand)] = flag
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (f *flagGroup) checkDuplicates() error {
|
|
seenShort := map[rune]bool{}
|
|
seenLong := map[string]bool{}
|
|
for _, flag := range f.flagOrder {
|
|
if flag.shorthand != 0 {
|
|
if _, ok := seenShort[flag.shorthand]; ok {
|
|
return fmt.Errorf("duplicate short flag -%c", flag.shorthand)
|
|
}
|
|
seenShort[flag.shorthand] = true
|
|
}
|
|
if _, ok := seenLong[flag.name]; ok {
|
|
return fmt.Errorf("duplicate long flag --%s", flag.name)
|
|
}
|
|
seenLong[flag.name] = true
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (f *flagGroup) parse(context *ParseContext) (*FlagClause, error) {
|
|
var token *Token
|
|
|
|
loop:
|
|
for {
|
|
token = context.Peek()
|
|
switch token.Type {
|
|
case TokenEOL:
|
|
break loop
|
|
|
|
case TokenLong, TokenShort:
|
|
flagToken := token
|
|
defaultValue := ""
|
|
var flag *FlagClause
|
|
var ok bool
|
|
invert := false
|
|
|
|
name := token.Value
|
|
if token.Type == TokenLong {
|
|
flag, ok = f.long[name]
|
|
if !ok {
|
|
if strings.HasPrefix(name, "no-") {
|
|
name = name[3:]
|
|
invert = true
|
|
}
|
|
flag, ok = f.long[name]
|
|
}
|
|
if !ok {
|
|
return nil, fmt.Errorf("unknown long flag '%s'", flagToken)
|
|
}
|
|
} else {
|
|
flag, ok = f.short[name]
|
|
if !ok {
|
|
return nil, fmt.Errorf("unknown short flag '%s'", flagToken)
|
|
}
|
|
}
|
|
|
|
context.Next()
|
|
|
|
fb, ok := flag.value.(boolFlag)
|
|
if ok && fb.IsBoolFlag() {
|
|
if invert {
|
|
defaultValue = "false"
|
|
} else {
|
|
defaultValue = "true"
|
|
}
|
|
} else {
|
|
if invert {
|
|
context.Push(token)
|
|
return nil, fmt.Errorf("unknown long flag '%s'", flagToken)
|
|
}
|
|
token = context.Peek()
|
|
if token.Type != TokenArg {
|
|
context.Push(token)
|
|
return nil, fmt.Errorf("expected argument for flag '%s'", flagToken)
|
|
}
|
|
context.Next()
|
|
defaultValue = token.Value
|
|
}
|
|
|
|
context.matchedFlag(flag, defaultValue)
|
|
return flag, nil
|
|
|
|
default:
|
|
break loop
|
|
}
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
// FlagClause is a fluid interface used to build flags.
|
|
type FlagClause struct {
|
|
parserMixin
|
|
actionMixin
|
|
completionsMixin
|
|
envarMixin
|
|
name string
|
|
shorthand rune
|
|
help string
|
|
defaultValues []string
|
|
placeholder string
|
|
hidden bool
|
|
}
|
|
|
|
func newFlag(name, help string) *FlagClause {
|
|
f := &FlagClause{
|
|
name: name,
|
|
help: help,
|
|
}
|
|
return f
|
|
}
|
|
|
|
func (f *FlagClause) setDefault() error {
|
|
if f.HasEnvarValue() {
|
|
if v, ok := f.value.(repeatableFlag); !ok || !v.IsCumulative() {
|
|
// Use the value as-is
|
|
return f.value.Set(f.GetEnvarValue())
|
|
} else {
|
|
for _, value := range f.GetSplitEnvarValue() {
|
|
if err := f.value.Set(value); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
if len(f.defaultValues) > 0 {
|
|
for _, defaultValue := range f.defaultValues {
|
|
if err := f.value.Set(defaultValue); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (f *FlagClause) needsValue() bool {
|
|
haveDefault := len(f.defaultValues) > 0
|
|
return f.required && !(haveDefault || f.HasEnvarValue())
|
|
}
|
|
|
|
func (f *FlagClause) init() error {
|
|
if f.required && len(f.defaultValues) > 0 {
|
|
return fmt.Errorf("required flag '--%s' with default value that will never be used", f.name)
|
|
}
|
|
if f.value == nil {
|
|
return fmt.Errorf("no type defined for --%s (eg. .String())", f.name)
|
|
}
|
|
if v, ok := f.value.(repeatableFlag); (!ok || !v.IsCumulative()) && len(f.defaultValues) > 1 {
|
|
return fmt.Errorf("invalid default for '--%s', expecting single value", f.name)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Dispatch to the given function after the flag is parsed and validated.
|
|
func (f *FlagClause) Action(action Action) *FlagClause {
|
|
f.addAction(action)
|
|
return f
|
|
}
|
|
|
|
func (f *FlagClause) PreAction(action Action) *FlagClause {
|
|
f.addPreAction(action)
|
|
return f
|
|
}
|
|
|
|
// HintAction registers a HintAction (function) for the flag to provide completions
|
|
func (a *FlagClause) HintAction(action HintAction) *FlagClause {
|
|
a.addHintAction(action)
|
|
return a
|
|
}
|
|
|
|
// HintOptions registers any number of options for the flag to provide completions
|
|
func (a *FlagClause) HintOptions(options ...string) *FlagClause {
|
|
a.addHintAction(func() []string {
|
|
return options
|
|
})
|
|
return a
|
|
}
|
|
|
|
func (a *FlagClause) EnumVar(target *string, options ...string) {
|
|
a.parserMixin.EnumVar(target, options...)
|
|
a.addHintActionBuiltin(func() []string {
|
|
return options
|
|
})
|
|
}
|
|
|
|
func (a *FlagClause) Enum(options ...string) (target *string) {
|
|
a.addHintActionBuiltin(func() []string {
|
|
return options
|
|
})
|
|
return a.parserMixin.Enum(options...)
|
|
}
|
|
|
|
// Default values for this flag. They *must* be parseable by the value of the flag.
|
|
func (f *FlagClause) Default(values ...string) *FlagClause {
|
|
f.defaultValues = values
|
|
return f
|
|
}
|
|
|
|
// DEPRECATED: Use Envar(name) instead.
|
|
func (f *FlagClause) OverrideDefaultFromEnvar(envar string) *FlagClause {
|
|
return f.Envar(envar)
|
|
}
|
|
|
|
// Envar overrides the default value(s) for a flag from an environment variable,
|
|
// if it is set. Several default values can be provided by using new lines to
|
|
// separate them.
|
|
func (f *FlagClause) Envar(name string) *FlagClause {
|
|
f.envar = name
|
|
f.noEnvar = false
|
|
return f
|
|
}
|
|
|
|
// NoEnvar forces environment variable defaults to be disabled for this flag.
|
|
// Most useful in conjunction with app.DefaultEnvars().
|
|
func (f *FlagClause) NoEnvar() *FlagClause {
|
|
f.envar = ""
|
|
f.noEnvar = true
|
|
return f
|
|
}
|
|
|
|
// PlaceHolder sets the place-holder string used for flag values in the help. The
|
|
// default behaviour is to use the value provided by Default() if provided,
|
|
// then fall back on the capitalized flag name.
|
|
func (f *FlagClause) PlaceHolder(placeholder string) *FlagClause {
|
|
f.placeholder = placeholder
|
|
return f
|
|
}
|
|
|
|
// Hidden hides a flag from usage but still allows it to be used.
|
|
func (f *FlagClause) Hidden() *FlagClause {
|
|
f.hidden = true
|
|
return f
|
|
}
|
|
|
|
// Required makes the flag required. You can not provide a Default() value to a Required() flag.
|
|
func (f *FlagClause) Required() *FlagClause {
|
|
f.required = true
|
|
return f
|
|
}
|
|
|
|
// Short sets the short flag name.
|
|
func (f *FlagClause) Short(name rune) *FlagClause {
|
|
f.shorthand = name
|
|
return f
|
|
}
|
|
|
|
// Bool makes this flag a boolean flag.
|
|
func (f *FlagClause) Bool() (target *bool) {
|
|
target = new(bool)
|
|
f.SetValue(newBoolValue(target))
|
|
return
|
|
}
|