package kingpin import ( "fmt" "os" "regexp" "strings" ) var ( envVarValuesSeparator = "\r?\n" envVarValuesTrimmer = regexp.MustCompile(envVarValuesSeparator + "$") envVarValuesSplitter = regexp.MustCompile(envVarValuesSeparator) ) 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[byte]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 { 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 } func (f *flagGroup) visibleFlags() int { count := 0 for _, flag := range f.long { if !flag.hidden { count++ } } return count } // FlagClause is a fluid interface used to build flags. type FlagClause struct { parserMixin actionMixin completionsMixin name string shorthand byte help string envar string noEnvar bool 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.noEnvar && f.envar != "" { if envarValue := os.Getenv(f.envar); envarValue != "" { if v, ok := f.value.(repeatableFlag); !ok || !v.IsCumulative() { // Use the value as-is return f.value.Set(envarValue) } else { // Split by new line to extract multiple values, if any. trimmed := envVarValuesTrimmer.ReplaceAllString(envarValue, "") for _, value := range envVarValuesSplitter.Split(trimmed, -1) { 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 haveEnvar := !f.noEnvar && f.envar != "" && os.Getenv(f.envar) != "" return f.required && !(haveDefault || haveEnvar) } 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 byte) *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 }