1package cli 2 3import ( 4 "errors" 5 "flag" 6 "reflect" 7 "strings" 8 "syscall" 9) 10 11// Context is a type that is passed through to 12// each Handler action in a cli application. Context 13// can be used to retrieve context-specific Args and 14// parsed command-line options. 15type Context struct { 16 App *App 17 Command Command 18 shellComplete bool 19 flagSet *flag.FlagSet 20 setFlags map[string]bool 21 parentContext *Context 22} 23 24// NewContext creates a new context. For use in when invoking an App or Command action. 25func NewContext(app *App, set *flag.FlagSet, parentCtx *Context) *Context { 26 c := &Context{App: app, flagSet: set, parentContext: parentCtx} 27 28 if parentCtx != nil { 29 c.shellComplete = parentCtx.shellComplete 30 } 31 32 return c 33} 34 35// NumFlags returns the number of flags set 36func (c *Context) NumFlags() int { 37 return c.flagSet.NFlag() 38} 39 40// Set sets a context flag to a value. 41func (c *Context) Set(name, value string) error { 42 c.setFlags = nil 43 return c.flagSet.Set(name, value) 44} 45 46// GlobalSet sets a context flag to a value on the global flagset 47func (c *Context) GlobalSet(name, value string) error { 48 globalContext(c).setFlags = nil 49 return globalContext(c).flagSet.Set(name, value) 50} 51 52// IsSet determines if the flag was actually set 53func (c *Context) IsSet(name string) bool { 54 if c.setFlags == nil { 55 c.setFlags = make(map[string]bool) 56 57 c.flagSet.Visit(func(f *flag.Flag) { 58 c.setFlags[f.Name] = true 59 }) 60 61 c.flagSet.VisitAll(func(f *flag.Flag) { 62 if _, ok := c.setFlags[f.Name]; ok { 63 return 64 } 65 c.setFlags[f.Name] = false 66 }) 67 68 // XXX hack to support IsSet for flags with EnvVar 69 // 70 // There isn't an easy way to do this with the current implementation since 71 // whether a flag was set via an environment variable is very difficult to 72 // determine here. Instead, we intend to introduce a backwards incompatible 73 // change in version 2 to add `IsSet` to the Flag interface to push the 74 // responsibility closer to where the information required to determine 75 // whether a flag is set by non-standard means such as environment 76 // variables is avaliable. 77 // 78 // See https://github.com/urfave/cli/issues/294 for additional discussion 79 flags := c.Command.Flags 80 if c.Command.Name == "" { // cannot == Command{} since it contains slice types 81 if c.App != nil { 82 flags = c.App.Flags 83 } 84 } 85 for _, f := range flags { 86 eachName(f.GetName(), func(name string) { 87 if isSet, ok := c.setFlags[name]; isSet || !ok { 88 return 89 } 90 91 val := reflect.ValueOf(f) 92 if val.Kind() == reflect.Ptr { 93 val = val.Elem() 94 } 95 96 envVarValue := val.FieldByName("EnvVar") 97 if !envVarValue.IsValid() { 98 return 99 } 100 101 eachName(envVarValue.String(), func(envVar string) { 102 envVar = strings.TrimSpace(envVar) 103 if _, ok := syscall.Getenv(envVar); ok { 104 c.setFlags[name] = true 105 return 106 } 107 }) 108 }) 109 } 110 } 111 112 return c.setFlags[name] 113} 114 115// GlobalIsSet determines if the global flag was actually set 116func (c *Context) GlobalIsSet(name string) bool { 117 ctx := c 118 if ctx.parentContext != nil { 119 ctx = ctx.parentContext 120 } 121 122 for ; ctx != nil; ctx = ctx.parentContext { 123 if ctx.IsSet(name) { 124 return true 125 } 126 } 127 return false 128} 129 130// FlagNames returns a slice of flag names used in this context. 131func (c *Context) FlagNames() (names []string) { 132 for _, flag := range c.Command.Flags { 133 name := strings.Split(flag.GetName(), ",")[0] 134 if name == "help" { 135 continue 136 } 137 names = append(names, name) 138 } 139 return 140} 141 142// GlobalFlagNames returns a slice of global flag names used by the app. 143func (c *Context) GlobalFlagNames() (names []string) { 144 for _, flag := range c.App.Flags { 145 name := strings.Split(flag.GetName(), ",")[0] 146 if name == "help" || name == "version" { 147 continue 148 } 149 names = append(names, name) 150 } 151 return 152} 153 154// Parent returns the parent context, if any 155func (c *Context) Parent() *Context { 156 return c.parentContext 157} 158 159// value returns the value of the flag coressponding to `name` 160func (c *Context) value(name string) interface{} { 161 return c.flagSet.Lookup(name).Value.(flag.Getter).Get() 162} 163 164// Args contains apps console arguments 165type Args []string 166 167// Args returns the command line arguments associated with the context. 168func (c *Context) Args() Args { 169 args := Args(c.flagSet.Args()) 170 return args 171} 172 173// NArg returns the number of the command line arguments. 174func (c *Context) NArg() int { 175 return len(c.Args()) 176} 177 178// Get returns the nth argument, or else a blank string 179func (a Args) Get(n int) string { 180 if len(a) > n { 181 return a[n] 182 } 183 return "" 184} 185 186// First returns the first argument, or else a blank string 187func (a Args) First() string { 188 return a.Get(0) 189} 190 191// Tail returns the rest of the arguments (not the first one) 192// or else an empty string slice 193func (a Args) Tail() []string { 194 if len(a) >= 2 { 195 return []string(a)[1:] 196 } 197 return []string{} 198} 199 200// Present checks if there are any arguments present 201func (a Args) Present() bool { 202 return len(a) != 0 203} 204 205// Swap swaps arguments at the given indexes 206func (a Args) Swap(from, to int) error { 207 if from >= len(a) || to >= len(a) { 208 return errors.New("index out of range") 209 } 210 a[from], a[to] = a[to], a[from] 211 return nil 212} 213 214func globalContext(ctx *Context) *Context { 215 if ctx == nil { 216 return nil 217 } 218 219 for { 220 if ctx.parentContext == nil { 221 return ctx 222 } 223 ctx = ctx.parentContext 224 } 225} 226 227func lookupGlobalFlagSet(name string, ctx *Context) *flag.FlagSet { 228 if ctx.parentContext != nil { 229 ctx = ctx.parentContext 230 } 231 for ; ctx != nil; ctx = ctx.parentContext { 232 if f := ctx.flagSet.Lookup(name); f != nil { 233 return ctx.flagSet 234 } 235 } 236 return nil 237} 238 239func copyFlag(name string, ff *flag.Flag, set *flag.FlagSet) { 240 switch ff.Value.(type) { 241 case *StringSlice: 242 default: 243 set.Set(name, ff.Value.String()) 244 } 245} 246 247func normalizeFlags(flags []Flag, set *flag.FlagSet) error { 248 visited := make(map[string]bool) 249 set.Visit(func(f *flag.Flag) { 250 visited[f.Name] = true 251 }) 252 for _, f := range flags { 253 parts := strings.Split(f.GetName(), ",") 254 if len(parts) == 1 { 255 continue 256 } 257 var ff *flag.Flag 258 for _, name := range parts { 259 name = strings.Trim(name, " ") 260 if visited[name] { 261 if ff != nil { 262 return errors.New("Cannot use two forms of the same flag: " + name + " " + ff.Name) 263 } 264 ff = set.Lookup(name) 265 } 266 } 267 if ff == nil { 268 continue 269 } 270 for _, name := range parts { 271 name = strings.Trim(name, " ") 272 if !visited[name] { 273 copyFlag(name, ff, set) 274 } 275 } 276 } 277 return nil 278} 279