| package cli |
| |
| import ( |
| "context" |
| "flag" |
| "fmt" |
| "strings" |
| ) |
| |
| // Context is a type that is passed through to |
| // each Handler action in a cli application. Context |
| // can be used to retrieve context-specific args and |
| // parsed command-line options. |
| type Context struct { |
| context.Context |
| App *App |
| Command *Command |
| shellComplete bool |
| flagSet *flag.FlagSet |
| parentContext *Context |
| } |
| |
| // NewContext creates a new context. For use in when invoking an App or Command action. |
| func NewContext(app *App, set *flag.FlagSet, parentCtx *Context) *Context { |
| c := &Context{App: app, flagSet: set, parentContext: parentCtx} |
| if parentCtx != nil { |
| c.Context = parentCtx.Context |
| c.shellComplete = parentCtx.shellComplete |
| if parentCtx.flagSet == nil { |
| parentCtx.flagSet = &flag.FlagSet{} |
| } |
| } |
| |
| c.Command = &Command{} |
| |
| if c.Context == nil { |
| c.Context = context.Background() |
| } |
| |
| return c |
| } |
| |
| // NumFlags returns the number of flags set |
| func (cCtx *Context) NumFlags() int { |
| return cCtx.flagSet.NFlag() |
| } |
| |
| // Set sets a context flag to a value. |
| func (cCtx *Context) Set(name, value string) error { |
| if fs := cCtx.lookupFlagSet(name); fs != nil { |
| return fs.Set(name, value) |
| } |
| |
| return fmt.Errorf("no such flag -%s", name) |
| } |
| |
| // IsSet determines if the flag was actually set |
| func (cCtx *Context) IsSet(name string) bool { |
| |
| if fs := cCtx.lookupFlagSet(name); fs != nil { |
| isSet := false |
| fs.Visit(func(f *flag.Flag) { |
| if f.Name == name { |
| isSet = true |
| } |
| }) |
| if isSet { |
| return true |
| } |
| |
| f := cCtx.lookupFlag(name) |
| if f == nil { |
| return false |
| } |
| |
| if f.IsSet() { |
| return true |
| } |
| |
| // now redo flagset search on aliases |
| aliases := f.Names() |
| fs.Visit(func(f *flag.Flag) { |
| for _, alias := range aliases { |
| if f.Name == alias { |
| isSet = true |
| } |
| } |
| }) |
| |
| if isSet { |
| return true |
| } |
| } |
| |
| return false |
| } |
| |
| // LocalFlagNames returns a slice of flag names used in this context. |
| func (cCtx *Context) LocalFlagNames() []string { |
| var names []string |
| cCtx.flagSet.Visit(makeFlagNameVisitor(&names)) |
| // Check the flags which have been set via env or file |
| if cCtx.Command != nil && cCtx.Command.Flags != nil { |
| for _, f := range cCtx.Command.Flags { |
| if f.IsSet() { |
| names = append(names, f.Names()...) |
| } |
| } |
| } |
| |
| // Sort out the duplicates since flag could be set via multiple |
| // paths |
| m := map[string]struct{}{} |
| var unames []string |
| for _, name := range names { |
| if _, ok := m[name]; !ok { |
| m[name] = struct{}{} |
| unames = append(unames, name) |
| } |
| } |
| |
| return unames |
| } |
| |
| // FlagNames returns a slice of flag names used by the this context and all of |
| // its parent contexts. |
| func (cCtx *Context) FlagNames() []string { |
| var names []string |
| for _, pCtx := range cCtx.Lineage() { |
| names = append(names, pCtx.LocalFlagNames()...) |
| } |
| return names |
| } |
| |
| // Lineage returns *this* context and all of its ancestor contexts in order from |
| // child to parent |
| func (cCtx *Context) Lineage() []*Context { |
| var lineage []*Context |
| |
| for cur := cCtx; cur != nil; cur = cur.parentContext { |
| lineage = append(lineage, cur) |
| } |
| |
| return lineage |
| } |
| |
| // Count returns the num of occurrences of this flag |
| func (cCtx *Context) Count(name string) int { |
| if fs := cCtx.lookupFlagSet(name); fs != nil { |
| if cf, ok := fs.Lookup(name).Value.(Countable); ok { |
| return cf.Count() |
| } |
| } |
| return 0 |
| } |
| |
| // Value returns the value of the flag corresponding to `name` |
| func (cCtx *Context) Value(name string) interface{} { |
| if fs := cCtx.lookupFlagSet(name); fs != nil { |
| return fs.Lookup(name).Value.(flag.Getter).Get() |
| } |
| return nil |
| } |
| |
| // Args returns the command line arguments associated with the context. |
| func (cCtx *Context) Args() Args { |
| ret := args(cCtx.flagSet.Args()) |
| return &ret |
| } |
| |
| // NArg returns the number of the command line arguments. |
| func (cCtx *Context) NArg() int { |
| return cCtx.Args().Len() |
| } |
| |
| func (cCtx *Context) lookupFlag(name string) Flag { |
| for _, c := range cCtx.Lineage() { |
| if c.Command == nil { |
| continue |
| } |
| |
| for _, f := range c.Command.Flags { |
| for _, n := range f.Names() { |
| if n == name { |
| return f |
| } |
| } |
| } |
| } |
| |
| if cCtx.App != nil { |
| for _, f := range cCtx.App.Flags { |
| for _, n := range f.Names() { |
| if n == name { |
| return f |
| } |
| } |
| } |
| } |
| |
| return nil |
| } |
| |
| func (cCtx *Context) lookupFlagSet(name string) *flag.FlagSet { |
| for _, c := range cCtx.Lineage() { |
| if c.flagSet == nil { |
| continue |
| } |
| if f := c.flagSet.Lookup(name); f != nil { |
| return c.flagSet |
| } |
| } |
| cCtx.onInvalidFlag(name) |
| return nil |
| } |
| |
| func (cCtx *Context) checkRequiredFlags(flags []Flag) requiredFlagsErr { |
| var missingFlags []string |
| for _, f := range flags { |
| if rf, ok := f.(RequiredFlag); ok && rf.IsRequired() { |
| var flagPresent bool |
| var flagName string |
| |
| flagNames := f.Names() |
| flagName = flagNames[0] |
| |
| for _, key := range flagNames { |
| if cCtx.IsSet(strings.TrimSpace(key)) { |
| flagPresent = true |
| } |
| } |
| |
| if !flagPresent && flagName != "" { |
| missingFlags = append(missingFlags, flagName) |
| } |
| } |
| } |
| |
| if len(missingFlags) != 0 { |
| return &errRequiredFlags{missingFlags: missingFlags} |
| } |
| |
| return nil |
| } |
| |
| func (cCtx *Context) onInvalidFlag(name string) { |
| for cCtx != nil { |
| if cCtx.App != nil && cCtx.App.InvalidFlagAccessHandler != nil { |
| cCtx.App.InvalidFlagAccessHandler(cCtx, name) |
| break |
| } |
| cCtx = cCtx.parentContext |
| } |
| } |
| |
| func makeFlagNameVisitor(names *[]string) func(*flag.Flag) { |
| return func(f *flag.Flag) { |
| nameParts := strings.Split(f.Name, ",") |
| name := strings.TrimSpace(nameParts[0]) |
| |
| for _, part := range nameParts { |
| part = strings.TrimSpace(part) |
| if len(part) > len(name) { |
| name = part |
| } |
| } |
| |
| if name != "" { |
| *names = append(*names, name) |
| } |
| } |
| } |