1package cli 2 3import ( 4 "flag" 5 "fmt" 6 "io/ioutil" 7 "reflect" 8 "runtime" 9 "strconv" 10 "strings" 11 "syscall" 12) 13 14const defaultPlaceholder = "value" 15 16// BashCompletionFlag enables bash-completion for all commands and subcommands 17var BashCompletionFlag Flag = BoolFlag{ 18 Name: "generate-bash-completion", 19 Hidden: true, 20} 21 22// VersionFlag prints the version for the application 23var VersionFlag Flag = BoolFlag{ 24 Name: "version, v", 25 Usage: "print the version", 26} 27 28// HelpFlag prints the help for all commands and subcommands 29// Set to the zero value (BoolFlag{}) to disable flag -- keeps subcommand 30// unless HideHelp is set to true) 31var HelpFlag Flag = BoolFlag{ 32 Name: "help, h", 33 Usage: "show help", 34} 35 36// FlagStringer converts a flag definition to a string. This is used by help 37// to display a flag. 38var FlagStringer FlagStringFunc = stringifyFlag 39 40// FlagNamePrefixer converts a full flag name and its placeholder into the help 41// message flag prefix. This is used by the default FlagStringer. 42var FlagNamePrefixer FlagNamePrefixFunc = prefixedNames 43 44// FlagEnvHinter annotates flag help message with the environment variable 45// details. This is used by the default FlagStringer. 46var FlagEnvHinter FlagEnvHintFunc = withEnvHint 47 48// FlagFileHinter annotates flag help message with the environment variable 49// details. This is used by the default FlagStringer. 50var FlagFileHinter FlagFileHintFunc = withFileHint 51 52// FlagsByName is a slice of Flag. 53type FlagsByName []Flag 54 55func (f FlagsByName) Len() int { 56 return len(f) 57} 58 59func (f FlagsByName) Less(i, j int) bool { 60 return lexicographicLess(f[i].GetName(), f[j].GetName()) 61} 62 63func (f FlagsByName) Swap(i, j int) { 64 f[i], f[j] = f[j], f[i] 65} 66 67// Flag is a common interface related to parsing flags in cli. 68// For more advanced flag parsing techniques, it is recommended that 69// this interface be implemented. 70type Flag interface { 71 fmt.Stringer 72 // Apply Flag settings to the given flag set 73 Apply(*flag.FlagSet) 74 GetName() string 75} 76 77// RequiredFlag is an interface that allows us to mark flags as required 78// it allows flags required flags to be backwards compatible with the Flag interface 79type RequiredFlag interface { 80 Flag 81 82 IsRequired() bool 83} 84 85// DocGenerationFlag is an interface that allows documentation generation for the flag 86type DocGenerationFlag interface { 87 Flag 88 89 // TakesValue returns true if the flag takes a value, otherwise false 90 TakesValue() bool 91 92 // GetUsage returns the usage string for the flag 93 GetUsage() string 94 95 // GetValue returns the flags value as string representation and an empty 96 // string if the flag takes no value at all. 97 GetValue() string 98} 99 100// errorableFlag is an interface that allows us to return errors during apply 101// it allows flags defined in this library to return errors in a fashion backwards compatible 102// TODO remove in v2 and modify the existing Flag interface to return errors 103type errorableFlag interface { 104 Flag 105 106 ApplyWithError(*flag.FlagSet) error 107} 108 109func flagSet(name string, flags []Flag) (*flag.FlagSet, error) { 110 set := flag.NewFlagSet(name, flag.ContinueOnError) 111 112 for _, f := range flags { 113 //TODO remove in v2 when errorableFlag is removed 114 if ef, ok := f.(errorableFlag); ok { 115 if err := ef.ApplyWithError(set); err != nil { 116 return nil, err 117 } 118 } else { 119 f.Apply(set) 120 } 121 } 122 set.SetOutput(ioutil.Discard) 123 return set, nil 124} 125 126func eachName(longName string, fn func(string)) { 127 parts := strings.Split(longName, ",") 128 for _, name := range parts { 129 name = strings.Trim(name, " ") 130 fn(name) 131 } 132} 133 134func visibleFlags(fl []Flag) []Flag { 135 var visible []Flag 136 for _, f := range fl { 137 field := flagValue(f).FieldByName("Hidden") 138 if !field.IsValid() || !field.Bool() { 139 visible = append(visible, f) 140 } 141 } 142 return visible 143} 144 145func prefixFor(name string) (prefix string) { 146 if len(name) == 1 { 147 prefix = "-" 148 } else { 149 prefix = "--" 150 } 151 152 return 153} 154 155// Returns the placeholder, if any, and the unquoted usage string. 156func unquoteUsage(usage string) (string, string) { 157 for i := 0; i < len(usage); i++ { 158 if usage[i] == '`' { 159 for j := i + 1; j < len(usage); j++ { 160 if usage[j] == '`' { 161 name := usage[i+1 : j] 162 usage = usage[:i] + name + usage[j+1:] 163 return name, usage 164 } 165 } 166 break 167 } 168 } 169 return "", usage 170} 171 172func prefixedNames(fullName, placeholder string) string { 173 var prefixed string 174 parts := strings.Split(fullName, ",") 175 for i, name := range parts { 176 name = strings.Trim(name, " ") 177 prefixed += prefixFor(name) + name 178 if placeholder != "" { 179 prefixed += " " + placeholder 180 } 181 if i < len(parts)-1 { 182 prefixed += ", " 183 } 184 } 185 return prefixed 186} 187 188func withEnvHint(envVar, str string) string { 189 envText := "" 190 if envVar != "" { 191 prefix := "$" 192 suffix := "" 193 sep := ", $" 194 if runtime.GOOS == "windows" { 195 prefix = "%" 196 suffix = "%" 197 sep = "%, %" 198 } 199 envText = " [" + prefix + strings.Join(strings.Split(envVar, ","), sep) + suffix + "]" 200 } 201 return str + envText 202} 203 204func withFileHint(filePath, str string) string { 205 fileText := "" 206 if filePath != "" { 207 fileText = fmt.Sprintf(" [%s]", filePath) 208 } 209 return str + fileText 210} 211 212func flagValue(f Flag) reflect.Value { 213 fv := reflect.ValueOf(f) 214 for fv.Kind() == reflect.Ptr { 215 fv = reflect.Indirect(fv) 216 } 217 return fv 218} 219 220func stringifyFlag(f Flag) string { 221 fv := flagValue(f) 222 223 switch f.(type) { 224 case IntSliceFlag: 225 return FlagFileHinter( 226 fv.FieldByName("FilePath").String(), 227 FlagEnvHinter( 228 fv.FieldByName("EnvVar").String(), 229 stringifyIntSliceFlag(f.(IntSliceFlag)), 230 ), 231 ) 232 case Int64SliceFlag: 233 return FlagFileHinter( 234 fv.FieldByName("FilePath").String(), 235 FlagEnvHinter( 236 fv.FieldByName("EnvVar").String(), 237 stringifyInt64SliceFlag(f.(Int64SliceFlag)), 238 ), 239 ) 240 case StringSliceFlag: 241 return FlagFileHinter( 242 fv.FieldByName("FilePath").String(), 243 FlagEnvHinter( 244 fv.FieldByName("EnvVar").String(), 245 stringifyStringSliceFlag(f.(StringSliceFlag)), 246 ), 247 ) 248 } 249 250 placeholder, usage := unquoteUsage(fv.FieldByName("Usage").String()) 251 252 needsPlaceholder := false 253 defaultValueString := "" 254 255 if val := fv.FieldByName("Value"); val.IsValid() { 256 needsPlaceholder = true 257 defaultValueString = fmt.Sprintf(" (default: %v)", val.Interface()) 258 259 if val.Kind() == reflect.String && val.String() != "" { 260 defaultValueString = fmt.Sprintf(" (default: %q)", val.String()) 261 } 262 } 263 264 if defaultValueString == " (default: )" { 265 defaultValueString = "" 266 } 267 268 if needsPlaceholder && placeholder == "" { 269 placeholder = defaultPlaceholder 270 } 271 272 usageWithDefault := strings.TrimSpace(usage + defaultValueString) 273 274 return FlagFileHinter( 275 fv.FieldByName("FilePath").String(), 276 FlagEnvHinter( 277 fv.FieldByName("EnvVar").String(), 278 FlagNamePrefixer(fv.FieldByName("Name").String(), placeholder)+"\t"+usageWithDefault, 279 ), 280 ) 281} 282 283func stringifyIntSliceFlag(f IntSliceFlag) string { 284 var defaultVals []string 285 if f.Value != nil && len(f.Value.Value()) > 0 { 286 for _, i := range f.Value.Value() { 287 defaultVals = append(defaultVals, strconv.Itoa(i)) 288 } 289 } 290 291 return stringifySliceFlag(f.Usage, f.Name, defaultVals) 292} 293 294func stringifyInt64SliceFlag(f Int64SliceFlag) string { 295 var defaultVals []string 296 if f.Value != nil && len(f.Value.Value()) > 0 { 297 for _, i := range f.Value.Value() { 298 defaultVals = append(defaultVals, strconv.FormatInt(i, 10)) 299 } 300 } 301 302 return stringifySliceFlag(f.Usage, f.Name, defaultVals) 303} 304 305func stringifyStringSliceFlag(f StringSliceFlag) string { 306 var defaultVals []string 307 if f.Value != nil && len(f.Value.Value()) > 0 { 308 for _, s := range f.Value.Value() { 309 if len(s) > 0 { 310 defaultVals = append(defaultVals, strconv.Quote(s)) 311 } 312 } 313 } 314 315 return stringifySliceFlag(f.Usage, f.Name, defaultVals) 316} 317 318func stringifySliceFlag(usage, name string, defaultVals []string) string { 319 placeholder, usage := unquoteUsage(usage) 320 if placeholder == "" { 321 placeholder = defaultPlaceholder 322 } 323 324 defaultVal := "" 325 if len(defaultVals) > 0 { 326 defaultVal = fmt.Sprintf(" (default: %s)", strings.Join(defaultVals, ", ")) 327 } 328 329 usageWithDefault := strings.TrimSpace(usage + defaultVal) 330 return FlagNamePrefixer(name, placeholder) + "\t" + usageWithDefault 331} 332 333func flagFromFileEnv(filePath, envName string) (val string, ok bool) { 334 for _, envVar := range strings.Split(envName, ",") { 335 envVar = strings.TrimSpace(envVar) 336 if envVal, ok := syscall.Getenv(envVar); ok { 337 return envVal, true 338 } 339 } 340 for _, fileVar := range strings.Split(filePath, ",") { 341 if data, err := ioutil.ReadFile(fileVar); err == nil { 342 return string(data), true 343 } 344 } 345 return "", false 346} 347