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