github.com/insionng/yougam@v0.0.0-20170714101924-2bc18d833463/libraries/cli/flag.go (about) 1 package cli 2 3 import ( 4 "flag" 5 "fmt" 6 "os" 7 "runtime" 8 "strconv" 9 "strings" 10 "time" 11 ) 12 13 // This flag enables bash-completion for all commands and subcommands 14 var BashCompletionFlag = BoolFlag{ 15 Name: "generate-bash-completion", 16 } 17 18 // This flag prints the version for the application 19 var VersionFlag = BoolFlag{ 20 Name: "version, v", 21 Usage: "print the version", 22 } 23 24 // This flag prints the help for all commands and subcommands 25 // Set to the zero value (BoolFlag{}) to disable flag -- keeps subcommand 26 // unless HideHelp is set to true) 27 var HelpFlag = BoolFlag{ 28 Name: "help, h", 29 Usage: "show help", 30 } 31 32 // Flag is a common interface related to parsing flags in cli. 33 // For more advanced flag parsing techniques, it is recommended that 34 // this interface be implemented. 35 type Flag interface { 36 fmt.Stringer 37 // Apply Flag settings to the given flag set 38 Apply(*flag.FlagSet) 39 GetName() string 40 } 41 42 func flagSet(name string, flags []Flag) *flag.FlagSet { 43 set := flag.NewFlagSet(name, flag.ContinueOnError) 44 45 for _, f := range flags { 46 f.Apply(set) 47 } 48 return set 49 } 50 51 func eachName(longName string, fn func(string)) { 52 parts := strings.Split(longName, ",") 53 for _, name := range parts { 54 name = strings.Trim(name, " ") 55 fn(name) 56 } 57 } 58 59 // Generic is a generic parseable type identified by a specific flag 60 type Generic interface { 61 Set(value string) error 62 String() string 63 } 64 65 // GenericFlag is the flag type for types implementing Generic 66 type GenericFlag struct { 67 Name string 68 Value Generic 69 Usage string 70 EnvVar string 71 } 72 73 // String returns the string representation of the generic flag to display the 74 // help text to the user (uses the String() method of the generic flag to show 75 // the value) 76 func (f GenericFlag) String() string { 77 return withEnvHint(f.EnvVar, fmt.Sprintf("%s %v\t%v", prefixedNames(f.Name), f.FormatValueHelp(), f.Usage)) 78 } 79 80 func (f GenericFlag) FormatValueHelp() string { 81 if f.Value == nil { 82 return "" 83 } 84 s := f.Value.String() 85 if len(s) == 0 { 86 return "" 87 } 88 return fmt.Sprintf("\"%s\"", s) 89 } 90 91 // Apply takes the flagset and calls Set on the generic flag with the value 92 // provided by the user for parsing by the flag 93 func (f GenericFlag) Apply(set *flag.FlagSet) { 94 val := f.Value 95 if f.EnvVar != "" { 96 for _, envVar := range strings.Split(f.EnvVar, ",") { 97 envVar = strings.TrimSpace(envVar) 98 if envVal := os.Getenv(envVar); envVal != "" { 99 val.Set(envVal) 100 break 101 } 102 } 103 } 104 105 eachName(f.Name, func(name string) { 106 set.Var(f.Value, name, f.Usage) 107 }) 108 } 109 110 func (f GenericFlag) GetName() string { 111 return f.Name 112 } 113 114 // StringSlice is an opaque type for []string to satisfy flag.Value 115 type StringSlice []string 116 117 // Set appends the string value to the list of values 118 func (f *StringSlice) Set(value string) error { 119 *f = append(*f, value) 120 return nil 121 } 122 123 // String returns a readable representation of this value (for usage defaults) 124 func (f *StringSlice) String() string { 125 return fmt.Sprintf("%s", *f) 126 } 127 128 // Value returns the slice of strings set by this flag 129 func (f *StringSlice) Value() []string { 130 return *f 131 } 132 133 // StringSlice is a string flag that can be specified multiple times on the 134 // command-line 135 type StringSliceFlag struct { 136 Name string 137 Value *StringSlice 138 Usage string 139 EnvVar string 140 } 141 142 // String returns the usage 143 func (f StringSliceFlag) String() string { 144 firstName := strings.Trim(strings.Split(f.Name, ",")[0], " ") 145 pref := prefixFor(firstName) 146 return withEnvHint(f.EnvVar, fmt.Sprintf("%s [%v]\t%v", prefixedNames(f.Name), pref+firstName+" option "+pref+firstName+" option", f.Usage)) 147 } 148 149 // Apply populates the flag given the flag set and environment 150 func (f StringSliceFlag) Apply(set *flag.FlagSet) { 151 if f.EnvVar != "" { 152 for _, envVar := range strings.Split(f.EnvVar, ",") { 153 envVar = strings.TrimSpace(envVar) 154 if envVal := os.Getenv(envVar); envVal != "" { 155 newVal := &StringSlice{} 156 for _, s := range strings.Split(envVal, ",") { 157 s = strings.TrimSpace(s) 158 newVal.Set(s) 159 } 160 f.Value = newVal 161 break 162 } 163 } 164 } 165 166 eachName(f.Name, func(name string) { 167 if f.Value == nil { 168 f.Value = &StringSlice{} 169 } 170 set.Var(f.Value, name, f.Usage) 171 }) 172 } 173 174 func (f StringSliceFlag) GetName() string { 175 return f.Name 176 } 177 178 // StringSlice is an opaque type for []int to satisfy flag.Value 179 type IntSlice []int 180 181 // Set parses the value into an integer and appends it to the list of values 182 func (f *IntSlice) Set(value string) error { 183 tmp, err := strconv.Atoi(value) 184 if err != nil { 185 return err 186 } else { 187 *f = append(*f, tmp) 188 } 189 return nil 190 } 191 192 // String returns a readable representation of this value (for usage defaults) 193 func (f *IntSlice) String() string { 194 return fmt.Sprintf("%d", *f) 195 } 196 197 // Value returns the slice of ints set by this flag 198 func (f *IntSlice) Value() []int { 199 return *f 200 } 201 202 // IntSliceFlag is an int flag that can be specified multiple times on the 203 // command-line 204 type IntSliceFlag struct { 205 Name string 206 Value *IntSlice 207 Usage string 208 EnvVar string 209 } 210 211 // String returns the usage 212 func (f IntSliceFlag) String() string { 213 firstName := strings.Trim(strings.Split(f.Name, ",")[0], " ") 214 pref := prefixFor(firstName) 215 return withEnvHint(f.EnvVar, fmt.Sprintf("%s [%v]\t%v", prefixedNames(f.Name), pref+firstName+" option "+pref+firstName+" option", f.Usage)) 216 } 217 218 // Apply populates the flag given the flag set and environment 219 func (f IntSliceFlag) Apply(set *flag.FlagSet) { 220 if f.EnvVar != "" { 221 for _, envVar := range strings.Split(f.EnvVar, ",") { 222 envVar = strings.TrimSpace(envVar) 223 if envVal := os.Getenv(envVar); envVal != "" { 224 newVal := &IntSlice{} 225 for _, s := range strings.Split(envVal, ",") { 226 s = strings.TrimSpace(s) 227 err := newVal.Set(s) 228 if err != nil { 229 fmt.Fprintf(os.Stderr, err.Error()) 230 } 231 } 232 f.Value = newVal 233 break 234 } 235 } 236 } 237 238 eachName(f.Name, func(name string) { 239 if f.Value == nil { 240 f.Value = &IntSlice{} 241 } 242 set.Var(f.Value, name, f.Usage) 243 }) 244 } 245 246 func (f IntSliceFlag) GetName() string { 247 return f.Name 248 } 249 250 // BoolFlag is a switch that defaults to false 251 type BoolFlag struct { 252 Name string 253 Usage string 254 EnvVar string 255 Destination *bool 256 } 257 258 // String returns a readable representation of this value (for usage defaults) 259 func (f BoolFlag) String() string { 260 return withEnvHint(f.EnvVar, fmt.Sprintf("%s\t%v", prefixedNames(f.Name), f.Usage)) 261 } 262 263 // Apply populates the flag given the flag set and environment 264 func (f BoolFlag) Apply(set *flag.FlagSet) { 265 val := false 266 if f.EnvVar != "" { 267 for _, envVar := range strings.Split(f.EnvVar, ",") { 268 envVar = strings.TrimSpace(envVar) 269 if envVal := os.Getenv(envVar); envVal != "" { 270 envValBool, err := strconv.ParseBool(envVal) 271 if err == nil { 272 val = envValBool 273 } 274 break 275 } 276 } 277 } 278 279 eachName(f.Name, func(name string) { 280 if f.Destination != nil { 281 set.BoolVar(f.Destination, name, val, f.Usage) 282 return 283 } 284 set.Bool(name, val, f.Usage) 285 }) 286 } 287 288 func (f BoolFlag) GetName() string { 289 return f.Name 290 } 291 292 // BoolTFlag this represents a boolean flag that is true by default, but can 293 // still be set to false by --some-flag=false 294 type BoolTFlag struct { 295 Name string 296 Usage string 297 EnvVar string 298 Destination *bool 299 } 300 301 // String returns a readable representation of this value (for usage defaults) 302 func (f BoolTFlag) String() string { 303 return withEnvHint(f.EnvVar, fmt.Sprintf("%s\t%v", prefixedNames(f.Name), f.Usage)) 304 } 305 306 // Apply populates the flag given the flag set and environment 307 func (f BoolTFlag) Apply(set *flag.FlagSet) { 308 val := true 309 if f.EnvVar != "" { 310 for _, envVar := range strings.Split(f.EnvVar, ",") { 311 envVar = strings.TrimSpace(envVar) 312 if envVal := os.Getenv(envVar); envVal != "" { 313 envValBool, err := strconv.ParseBool(envVal) 314 if err == nil { 315 val = envValBool 316 break 317 } 318 } 319 } 320 } 321 322 eachName(f.Name, func(name string) { 323 if f.Destination != nil { 324 set.BoolVar(f.Destination, name, val, f.Usage) 325 return 326 } 327 set.Bool(name, val, f.Usage) 328 }) 329 } 330 331 func (f BoolTFlag) GetName() string { 332 return f.Name 333 } 334 335 // StringFlag represents a flag that takes as string value 336 type StringFlag struct { 337 Name string 338 Value string 339 Usage string 340 EnvVar string 341 Destination *string 342 } 343 344 // String returns the usage 345 func (f StringFlag) String() string { 346 return withEnvHint(f.EnvVar, fmt.Sprintf("%s %v\t%v", prefixedNames(f.Name), f.FormatValueHelp(), f.Usage)) 347 } 348 349 func (f StringFlag) FormatValueHelp() string { 350 s := f.Value 351 if len(s) == 0 { 352 return "" 353 } 354 return fmt.Sprintf("\"%s\"", s) 355 } 356 357 // Apply populates the flag given the flag set and environment 358 func (f StringFlag) Apply(set *flag.FlagSet) { 359 if f.EnvVar != "" { 360 for _, envVar := range strings.Split(f.EnvVar, ",") { 361 envVar = strings.TrimSpace(envVar) 362 if envVal := os.Getenv(envVar); envVal != "" { 363 f.Value = envVal 364 break 365 } 366 } 367 } 368 369 eachName(f.Name, func(name string) { 370 if f.Destination != nil { 371 set.StringVar(f.Destination, name, f.Value, f.Usage) 372 return 373 } 374 set.String(name, f.Value, f.Usage) 375 }) 376 } 377 378 func (f StringFlag) GetName() string { 379 return f.Name 380 } 381 382 // IntFlag is a flag that takes an integer 383 // Errors if the value provided cannot be parsed 384 type IntFlag struct { 385 Name string 386 Value int 387 Usage string 388 EnvVar string 389 Destination *int 390 } 391 392 // String returns the usage 393 func (f IntFlag) String() string { 394 return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name), f.Value, f.Usage)) 395 } 396 397 // Apply populates the flag given the flag set and environment 398 func (f IntFlag) Apply(set *flag.FlagSet) { 399 if f.EnvVar != "" { 400 for _, envVar := range strings.Split(f.EnvVar, ",") { 401 envVar = strings.TrimSpace(envVar) 402 if envVal := os.Getenv(envVar); envVal != "" { 403 envValInt, err := strconv.ParseInt(envVal, 0, 64) 404 if err == nil { 405 f.Value = int(envValInt) 406 break 407 } 408 } 409 } 410 } 411 412 eachName(f.Name, func(name string) { 413 if f.Destination != nil { 414 set.IntVar(f.Destination, name, f.Value, f.Usage) 415 return 416 } 417 set.Int(name, f.Value, f.Usage) 418 }) 419 } 420 421 func (f IntFlag) GetName() string { 422 return f.Name 423 } 424 425 // DurationFlag is a flag that takes a duration specified in Go's duration 426 // format: https://yougam/libraries/pkg/time/#ParseDuration 427 type DurationFlag struct { 428 Name string 429 Value time.Duration 430 Usage string 431 EnvVar string 432 Destination *time.Duration 433 } 434 435 // String returns a readable representation of this value (for usage defaults) 436 func (f DurationFlag) String() string { 437 return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name), f.Value, f.Usage)) 438 } 439 440 // Apply populates the flag given the flag set and environment 441 func (f DurationFlag) Apply(set *flag.FlagSet) { 442 if f.EnvVar != "" { 443 for _, envVar := range strings.Split(f.EnvVar, ",") { 444 envVar = strings.TrimSpace(envVar) 445 if envVal := os.Getenv(envVar); envVal != "" { 446 envValDuration, err := time.ParseDuration(envVal) 447 if err == nil { 448 f.Value = envValDuration 449 break 450 } 451 } 452 } 453 } 454 455 eachName(f.Name, func(name string) { 456 if f.Destination != nil { 457 set.DurationVar(f.Destination, name, f.Value, f.Usage) 458 return 459 } 460 set.Duration(name, f.Value, f.Usage) 461 }) 462 } 463 464 func (f DurationFlag) GetName() string { 465 return f.Name 466 } 467 468 // Float64Flag is a flag that takes an float value 469 // Errors if the value provided cannot be parsed 470 type Float64Flag struct { 471 Name string 472 Value float64 473 Usage string 474 EnvVar string 475 Destination *float64 476 } 477 478 // String returns the usage 479 func (f Float64Flag) String() string { 480 return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name), f.Value, f.Usage)) 481 } 482 483 // Apply populates the flag given the flag set and environment 484 func (f Float64Flag) Apply(set *flag.FlagSet) { 485 if f.EnvVar != "" { 486 for _, envVar := range strings.Split(f.EnvVar, ",") { 487 envVar = strings.TrimSpace(envVar) 488 if envVal := os.Getenv(envVar); envVal != "" { 489 envValFloat, err := strconv.ParseFloat(envVal, 10) 490 if err == nil { 491 f.Value = float64(envValFloat) 492 } 493 } 494 } 495 } 496 497 eachName(f.Name, func(name string) { 498 if f.Destination != nil { 499 set.Float64Var(f.Destination, name, f.Value, f.Usage) 500 return 501 } 502 set.Float64(name, f.Value, f.Usage) 503 }) 504 } 505 506 func (f Float64Flag) GetName() string { 507 return f.Name 508 } 509 510 func prefixFor(name string) (prefix string) { 511 if len(name) == 1 { 512 prefix = "-" 513 } else { 514 prefix = "--" 515 } 516 517 return 518 } 519 520 func prefixedNames(fullName string) (prefixed string) { 521 parts := strings.Split(fullName, ",") 522 for i, name := range parts { 523 name = strings.Trim(name, " ") 524 prefixed += prefixFor(name) + name 525 if i < len(parts)-1 { 526 prefixed += ", " 527 } 528 } 529 return 530 } 531 532 func withEnvHint(envVar, str string) string { 533 envText := "" 534 if envVar != "" { 535 prefix := "$" 536 suffix := "" 537 sep := ", $" 538 if runtime.GOOS == "windows" { 539 prefix = "%" 540 suffix = "%" 541 sep = "%, %" 542 } 543 envText = fmt.Sprintf(" [%s%s%s]", prefix, strings.Join(strings.Split(envVar, ","), sep), suffix) 544 } 545 return str + envText 546 }