1package cli 2 3import ( 4 "flag" 5 "fmt" 6 "reflect" 7 "runtime" 8 "strconv" 9 "strings" 10 "syscall" 11 "time" 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// FlagsByName is a slice of Flag. 41type FlagsByName []Flag 42 43func (f FlagsByName) Len() int { 44 return len(f) 45} 46 47func (f FlagsByName) Less(i, j int) bool { 48 return f[i].GetName() < f[j].GetName() 49} 50 51func (f FlagsByName) Swap(i, j int) { 52 f[i], f[j] = f[j], f[i] 53} 54 55// Flag is a common interface related to parsing flags in cli. 56// For more advanced flag parsing techniques, it is recommended that 57// this interface be implemented. 58type Flag interface { 59 fmt.Stringer 60 // Apply Flag settings to the given flag set 61 Apply(*flag.FlagSet) 62 GetName() string 63} 64 65// errorableFlag is an interface that allows us to return errors during apply 66// it allows flags defined in this library to return errors in a fashion backwards compatible 67// TODO remove in v2 and modify the existing Flag interface to return errors 68type errorableFlag interface { 69 Flag 70 71 ApplyWithError(*flag.FlagSet) error 72} 73 74func flagSet(name string, flags []Flag) (*flag.FlagSet, error) { 75 set := flag.NewFlagSet(name, flag.ContinueOnError) 76 77 for _, f := range flags { 78 //TODO remove in v2 when errorableFlag is removed 79 if ef, ok := f.(errorableFlag); ok { 80 if err := ef.ApplyWithError(set); err != nil { 81 return nil, err 82 } 83 } else { 84 f.Apply(set) 85 } 86 } 87 return set, nil 88} 89 90func eachName(longName string, fn func(string)) { 91 parts := strings.Split(longName, ",") 92 for _, name := range parts { 93 name = strings.Trim(name, " ") 94 fn(name) 95 } 96} 97 98// Generic is a generic parseable type identified by a specific flag 99type Generic interface { 100 Set(value string) error 101 String() string 102} 103 104// Apply takes the flagset and calls Set on the generic flag with the value 105// provided by the user for parsing by the flag 106// Ignores parsing errors 107func (f GenericFlag) Apply(set *flag.FlagSet) { 108 f.ApplyWithError(set) 109} 110 111// ApplyWithError takes the flagset and calls Set on the generic flag with the value 112// provided by the user for parsing by the flag 113func (f GenericFlag) ApplyWithError(set *flag.FlagSet) error { 114 val := f.Value 115 if f.EnvVar != "" { 116 for _, envVar := range strings.Split(f.EnvVar, ",") { 117 envVar = strings.TrimSpace(envVar) 118 if envVal, ok := syscall.Getenv(envVar); ok { 119 if err := val.Set(envVal); err != nil { 120 return fmt.Errorf("could not parse %s as value for flag %s: %s", envVal, f.Name, err) 121 } 122 break 123 } 124 } 125 } 126 127 eachName(f.Name, func(name string) { 128 set.Var(f.Value, name, f.Usage) 129 }) 130 131 return nil 132} 133 134// StringSlice is an opaque type for []string to satisfy flag.Value and flag.Getter 135type StringSlice []string 136 137// Set appends the string value to the list of values 138func (f *StringSlice) Set(value string) error { 139 *f = append(*f, value) 140 return nil 141} 142 143// String returns a readable representation of this value (for usage defaults) 144func (f *StringSlice) String() string { 145 return fmt.Sprintf("%s", *f) 146} 147 148// Value returns the slice of strings set by this flag 149func (f *StringSlice) Value() []string { 150 return *f 151} 152 153// Get returns the slice of strings set by this flag 154func (f *StringSlice) Get() interface{} { 155 return *f 156} 157 158// Apply populates the flag given the flag set and environment 159// Ignores errors 160func (f StringSliceFlag) Apply(set *flag.FlagSet) { 161 f.ApplyWithError(set) 162} 163 164// ApplyWithError populates the flag given the flag set and environment 165func (f StringSliceFlag) ApplyWithError(set *flag.FlagSet) error { 166 if f.EnvVar != "" { 167 for _, envVar := range strings.Split(f.EnvVar, ",") { 168 envVar = strings.TrimSpace(envVar) 169 if envVal, ok := syscall.Getenv(envVar); ok { 170 newVal := &StringSlice{} 171 for _, s := range strings.Split(envVal, ",") { 172 s = strings.TrimSpace(s) 173 if err := newVal.Set(s); err != nil { 174 return fmt.Errorf("could not parse %s as string value for flag %s: %s", envVal, f.Name, err) 175 } 176 } 177 f.Value = newVal 178 break 179 } 180 } 181 } 182 183 eachName(f.Name, func(name string) { 184 if f.Value == nil { 185 f.Value = &StringSlice{} 186 } 187 set.Var(f.Value, name, f.Usage) 188 }) 189 190 return nil 191} 192 193// IntSlice is an opaque type for []int to satisfy flag.Value and flag.Getter 194type IntSlice []int 195 196// Set parses the value into an integer and appends it to the list of values 197func (f *IntSlice) Set(value string) error { 198 tmp, err := strconv.Atoi(value) 199 if err != nil { 200 return err 201 } 202 *f = append(*f, tmp) 203 return nil 204} 205 206// String returns a readable representation of this value (for usage defaults) 207func (f *IntSlice) String() string { 208 return fmt.Sprintf("%#v", *f) 209} 210 211// Value returns the slice of ints set by this flag 212func (f *IntSlice) Value() []int { 213 return *f 214} 215 216// Get returns the slice of ints set by this flag 217func (f *IntSlice) Get() interface{} { 218 return *f 219} 220 221// Apply populates the flag given the flag set and environment 222// Ignores errors 223func (f IntSliceFlag) Apply(set *flag.FlagSet) { 224 f.ApplyWithError(set) 225} 226 227// ApplyWithError populates the flag given the flag set and environment 228func (f IntSliceFlag) ApplyWithError(set *flag.FlagSet) error { 229 if f.EnvVar != "" { 230 for _, envVar := range strings.Split(f.EnvVar, ",") { 231 envVar = strings.TrimSpace(envVar) 232 if envVal, ok := syscall.Getenv(envVar); ok { 233 newVal := &IntSlice{} 234 for _, s := range strings.Split(envVal, ",") { 235 s = strings.TrimSpace(s) 236 if err := newVal.Set(s); err != nil { 237 return fmt.Errorf("could not parse %s as int slice value for flag %s: %s", envVal, f.Name, err) 238 } 239 } 240 f.Value = newVal 241 break 242 } 243 } 244 } 245 246 eachName(f.Name, func(name string) { 247 if f.Value == nil { 248 f.Value = &IntSlice{} 249 } 250 set.Var(f.Value, name, f.Usage) 251 }) 252 253 return nil 254} 255 256// Int64Slice is an opaque type for []int to satisfy flag.Value and flag.Getter 257type Int64Slice []int64 258 259// Set parses the value into an integer and appends it to the list of values 260func (f *Int64Slice) Set(value string) error { 261 tmp, err := strconv.ParseInt(value, 10, 64) 262 if err != nil { 263 return err 264 } 265 *f = append(*f, tmp) 266 return nil 267} 268 269// String returns a readable representation of this value (for usage defaults) 270func (f *Int64Slice) String() string { 271 return fmt.Sprintf("%#v", *f) 272} 273 274// Value returns the slice of ints set by this flag 275func (f *Int64Slice) Value() []int64 { 276 return *f 277} 278 279// Get returns the slice of ints set by this flag 280func (f *Int64Slice) Get() interface{} { 281 return *f 282} 283 284// Apply populates the flag given the flag set and environment 285// Ignores errors 286func (f Int64SliceFlag) Apply(set *flag.FlagSet) { 287 f.ApplyWithError(set) 288} 289 290// ApplyWithError populates the flag given the flag set and environment 291func (f Int64SliceFlag) ApplyWithError(set *flag.FlagSet) error { 292 if f.EnvVar != "" { 293 for _, envVar := range strings.Split(f.EnvVar, ",") { 294 envVar = strings.TrimSpace(envVar) 295 if envVal, ok := syscall.Getenv(envVar); ok { 296 newVal := &Int64Slice{} 297 for _, s := range strings.Split(envVal, ",") { 298 s = strings.TrimSpace(s) 299 if err := newVal.Set(s); err != nil { 300 return fmt.Errorf("could not parse %s as int64 slice value for flag %s: %s", envVal, f.Name, err) 301 } 302 } 303 f.Value = newVal 304 break 305 } 306 } 307 } 308 309 eachName(f.Name, func(name string) { 310 if f.Value == nil { 311 f.Value = &Int64Slice{} 312 } 313 set.Var(f.Value, name, f.Usage) 314 }) 315 return nil 316} 317 318// Apply populates the flag given the flag set and environment 319// Ignores errors 320func (f BoolFlag) Apply(set *flag.FlagSet) { 321 f.ApplyWithError(set) 322} 323 324// ApplyWithError populates the flag given the flag set and environment 325func (f BoolFlag) ApplyWithError(set *flag.FlagSet) error { 326 val := false 327 if f.EnvVar != "" { 328 for _, envVar := range strings.Split(f.EnvVar, ",") { 329 envVar = strings.TrimSpace(envVar) 330 if envVal, ok := syscall.Getenv(envVar); ok { 331 if envVal == "" { 332 val = false 333 break 334 } 335 336 envValBool, err := strconv.ParseBool(envVal) 337 if err != nil { 338 return fmt.Errorf("could not parse %s as bool value for flag %s: %s", envVal, f.Name, err) 339 } 340 341 val = envValBool 342 break 343 } 344 } 345 } 346 347 eachName(f.Name, func(name string) { 348 if f.Destination != nil { 349 set.BoolVar(f.Destination, name, val, f.Usage) 350 return 351 } 352 set.Bool(name, val, f.Usage) 353 }) 354 355 return nil 356} 357 358// Apply populates the flag given the flag set and environment 359// Ignores errors 360func (f BoolTFlag) Apply(set *flag.FlagSet) { 361 f.ApplyWithError(set) 362} 363 364// ApplyWithError populates the flag given the flag set and environment 365func (f BoolTFlag) ApplyWithError(set *flag.FlagSet) error { 366 val := true 367 if f.EnvVar != "" { 368 for _, envVar := range strings.Split(f.EnvVar, ",") { 369 envVar = strings.TrimSpace(envVar) 370 if envVal, ok := syscall.Getenv(envVar); ok { 371 if envVal == "" { 372 val = false 373 break 374 } 375 376 envValBool, err := strconv.ParseBool(envVal) 377 if err != nil { 378 return fmt.Errorf("could not parse %s as bool value for flag %s: %s", envVal, f.Name, err) 379 } 380 381 val = envValBool 382 break 383 } 384 } 385 } 386 387 eachName(f.Name, func(name string) { 388 if f.Destination != nil { 389 set.BoolVar(f.Destination, name, val, f.Usage) 390 return 391 } 392 set.Bool(name, val, f.Usage) 393 }) 394 395 return nil 396} 397 398// Apply populates the flag given the flag set and environment 399// Ignores errors 400func (f StringFlag) Apply(set *flag.FlagSet) { 401 f.ApplyWithError(set) 402} 403 404// ApplyWithError populates the flag given the flag set and environment 405func (f StringFlag) ApplyWithError(set *flag.FlagSet) error { 406 if f.EnvVar != "" { 407 for _, envVar := range strings.Split(f.EnvVar, ",") { 408 envVar = strings.TrimSpace(envVar) 409 if envVal, ok := syscall.Getenv(envVar); ok { 410 f.Value = envVal 411 break 412 } 413 } 414 } 415 416 eachName(f.Name, func(name string) { 417 if f.Destination != nil { 418 set.StringVar(f.Destination, name, f.Value, f.Usage) 419 return 420 } 421 set.String(name, f.Value, f.Usage) 422 }) 423 424 return nil 425} 426 427// Apply populates the flag given the flag set and environment 428// Ignores errors 429func (f IntFlag) Apply(set *flag.FlagSet) { 430 f.ApplyWithError(set) 431} 432 433// ApplyWithError populates the flag given the flag set and environment 434func (f IntFlag) ApplyWithError(set *flag.FlagSet) error { 435 if f.EnvVar != "" { 436 for _, envVar := range strings.Split(f.EnvVar, ",") { 437 envVar = strings.TrimSpace(envVar) 438 if envVal, ok := syscall.Getenv(envVar); ok { 439 envValInt, err := strconv.ParseInt(envVal, 0, 64) 440 if err != nil { 441 return fmt.Errorf("could not parse %s as int value for flag %s: %s", envVal, f.Name, err) 442 } 443 f.Value = int(envValInt) 444 break 445 } 446 } 447 } 448 449 eachName(f.Name, func(name string) { 450 if f.Destination != nil { 451 set.IntVar(f.Destination, name, f.Value, f.Usage) 452 return 453 } 454 set.Int(name, f.Value, f.Usage) 455 }) 456 457 return nil 458} 459 460// Apply populates the flag given the flag set and environment 461// Ignores errors 462func (f Int64Flag) Apply(set *flag.FlagSet) { 463 f.ApplyWithError(set) 464} 465 466// ApplyWithError populates the flag given the flag set and environment 467func (f Int64Flag) ApplyWithError(set *flag.FlagSet) error { 468 if f.EnvVar != "" { 469 for _, envVar := range strings.Split(f.EnvVar, ",") { 470 envVar = strings.TrimSpace(envVar) 471 if envVal, ok := syscall.Getenv(envVar); ok { 472 envValInt, err := strconv.ParseInt(envVal, 0, 64) 473 if err != nil { 474 return fmt.Errorf("could not parse %s as int value for flag %s: %s", envVal, f.Name, err) 475 } 476 477 f.Value = envValInt 478 break 479 } 480 } 481 } 482 483 eachName(f.Name, func(name string) { 484 if f.Destination != nil { 485 set.Int64Var(f.Destination, name, f.Value, f.Usage) 486 return 487 } 488 set.Int64(name, f.Value, f.Usage) 489 }) 490 491 return nil 492} 493 494// Apply populates the flag given the flag set and environment 495// Ignores errors 496func (f UintFlag) Apply(set *flag.FlagSet) { 497 f.ApplyWithError(set) 498} 499 500// ApplyWithError populates the flag given the flag set and environment 501func (f UintFlag) ApplyWithError(set *flag.FlagSet) error { 502 if f.EnvVar != "" { 503 for _, envVar := range strings.Split(f.EnvVar, ",") { 504 envVar = strings.TrimSpace(envVar) 505 if envVal, ok := syscall.Getenv(envVar); ok { 506 envValInt, err := strconv.ParseUint(envVal, 0, 64) 507 if err != nil { 508 return fmt.Errorf("could not parse %s as uint value for flag %s: %s", envVal, f.Name, err) 509 } 510 511 f.Value = uint(envValInt) 512 break 513 } 514 } 515 } 516 517 eachName(f.Name, func(name string) { 518 if f.Destination != nil { 519 set.UintVar(f.Destination, name, f.Value, f.Usage) 520 return 521 } 522 set.Uint(name, f.Value, f.Usage) 523 }) 524 525 return nil 526} 527 528// Apply populates the flag given the flag set and environment 529// Ignores errors 530func (f Uint64Flag) Apply(set *flag.FlagSet) { 531 f.ApplyWithError(set) 532} 533 534// ApplyWithError populates the flag given the flag set and environment 535func (f Uint64Flag) ApplyWithError(set *flag.FlagSet) error { 536 if f.EnvVar != "" { 537 for _, envVar := range strings.Split(f.EnvVar, ",") { 538 envVar = strings.TrimSpace(envVar) 539 if envVal, ok := syscall.Getenv(envVar); ok { 540 envValInt, err := strconv.ParseUint(envVal, 0, 64) 541 if err != nil { 542 return fmt.Errorf("could not parse %s as uint64 value for flag %s: %s", envVal, f.Name, err) 543 } 544 545 f.Value = uint64(envValInt) 546 break 547 } 548 } 549 } 550 551 eachName(f.Name, func(name string) { 552 if f.Destination != nil { 553 set.Uint64Var(f.Destination, name, f.Value, f.Usage) 554 return 555 } 556 set.Uint64(name, f.Value, f.Usage) 557 }) 558 559 return nil 560} 561 562// Apply populates the flag given the flag set and environment 563// Ignores errors 564func (f DurationFlag) Apply(set *flag.FlagSet) { 565 f.ApplyWithError(set) 566} 567 568// ApplyWithError populates the flag given the flag set and environment 569func (f DurationFlag) ApplyWithError(set *flag.FlagSet) error { 570 if f.EnvVar != "" { 571 for _, envVar := range strings.Split(f.EnvVar, ",") { 572 envVar = strings.TrimSpace(envVar) 573 if envVal, ok := syscall.Getenv(envVar); ok { 574 envValDuration, err := time.ParseDuration(envVal) 575 if err != nil { 576 return fmt.Errorf("could not parse %s as duration for flag %s: %s", envVal, f.Name, err) 577 } 578 579 f.Value = envValDuration 580 break 581 } 582 } 583 } 584 585 eachName(f.Name, func(name string) { 586 if f.Destination != nil { 587 set.DurationVar(f.Destination, name, f.Value, f.Usage) 588 return 589 } 590 set.Duration(name, f.Value, f.Usage) 591 }) 592 593 return nil 594} 595 596// Apply populates the flag given the flag set and environment 597// Ignores errors 598func (f Float64Flag) Apply(set *flag.FlagSet) { 599 f.ApplyWithError(set) 600} 601 602// ApplyWithError populates the flag given the flag set and environment 603func (f Float64Flag) ApplyWithError(set *flag.FlagSet) error { 604 if f.EnvVar != "" { 605 for _, envVar := range strings.Split(f.EnvVar, ",") { 606 envVar = strings.TrimSpace(envVar) 607 if envVal, ok := syscall.Getenv(envVar); ok { 608 envValFloat, err := strconv.ParseFloat(envVal, 10) 609 if err != nil { 610 return fmt.Errorf("could not parse %s as float64 value for flag %s: %s", envVal, f.Name, err) 611 } 612 613 f.Value = float64(envValFloat) 614 break 615 } 616 } 617 } 618 619 eachName(f.Name, func(name string) { 620 if f.Destination != nil { 621 set.Float64Var(f.Destination, name, f.Value, f.Usage) 622 return 623 } 624 set.Float64(name, f.Value, f.Usage) 625 }) 626 627 return nil 628} 629 630func visibleFlags(fl []Flag) []Flag { 631 visible := []Flag{} 632 for _, flag := range fl { 633 field := flagValue(flag).FieldByName("Hidden") 634 if !field.IsValid() || !field.Bool() { 635 visible = append(visible, flag) 636 } 637 } 638 return visible 639} 640 641func prefixFor(name string) (prefix string) { 642 if len(name) == 1 { 643 prefix = "-" 644 } else { 645 prefix = "--" 646 } 647 648 return 649} 650 651// Returns the placeholder, if any, and the unquoted usage string. 652func unquoteUsage(usage string) (string, string) { 653 for i := 0; i < len(usage); i++ { 654 if usage[i] == '`' { 655 for j := i + 1; j < len(usage); j++ { 656 if usage[j] == '`' { 657 name := usage[i+1 : j] 658 usage = usage[:i] + name + usage[j+1:] 659 return name, usage 660 } 661 } 662 break 663 } 664 } 665 return "", usage 666} 667 668func prefixedNames(fullName, placeholder string) string { 669 var prefixed string 670 parts := strings.Split(fullName, ",") 671 for i, name := range parts { 672 name = strings.Trim(name, " ") 673 prefixed += prefixFor(name) + name 674 if placeholder != "" { 675 prefixed += " " + placeholder 676 } 677 if i < len(parts)-1 { 678 prefixed += ", " 679 } 680 } 681 return prefixed 682} 683 684func withEnvHint(envVar, str string) string { 685 envText := "" 686 if envVar != "" { 687 prefix := "$" 688 suffix := "" 689 sep := ", $" 690 if runtime.GOOS == "windows" { 691 prefix = "%" 692 suffix = "%" 693 sep = "%, %" 694 } 695 envText = fmt.Sprintf(" [%s%s%s]", prefix, strings.Join(strings.Split(envVar, ","), sep), suffix) 696 } 697 return str + envText 698} 699 700func flagValue(f Flag) reflect.Value { 701 fv := reflect.ValueOf(f) 702 for fv.Kind() == reflect.Ptr { 703 fv = reflect.Indirect(fv) 704 } 705 return fv 706} 707 708func stringifyFlag(f Flag) string { 709 fv := flagValue(f) 710 711 switch f.(type) { 712 case IntSliceFlag: 713 return withEnvHint(fv.FieldByName("EnvVar").String(), 714 stringifyIntSliceFlag(f.(IntSliceFlag))) 715 case Int64SliceFlag: 716 return withEnvHint(fv.FieldByName("EnvVar").String(), 717 stringifyInt64SliceFlag(f.(Int64SliceFlag))) 718 case StringSliceFlag: 719 return withEnvHint(fv.FieldByName("EnvVar").String(), 720 stringifyStringSliceFlag(f.(StringSliceFlag))) 721 } 722 723 placeholder, usage := unquoteUsage(fv.FieldByName("Usage").String()) 724 725 needsPlaceholder := false 726 defaultValueString := "" 727 728 if val := fv.FieldByName("Value"); val.IsValid() { 729 needsPlaceholder = true 730 defaultValueString = fmt.Sprintf(" (default: %v)", val.Interface()) 731 732 if val.Kind() == reflect.String && val.String() != "" { 733 defaultValueString = fmt.Sprintf(" (default: %q)", val.String()) 734 } 735 } 736 737 if defaultValueString == " (default: )" { 738 defaultValueString = "" 739 } 740 741 if needsPlaceholder && placeholder == "" { 742 placeholder = defaultPlaceholder 743 } 744 745 usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultValueString)) 746 747 return withEnvHint(fv.FieldByName("EnvVar").String(), 748 fmt.Sprintf("%s\t%s", prefixedNames(fv.FieldByName("Name").String(), placeholder), usageWithDefault)) 749} 750 751func stringifyIntSliceFlag(f IntSliceFlag) string { 752 defaultVals := []string{} 753 if f.Value != nil && len(f.Value.Value()) > 0 { 754 for _, i := range f.Value.Value() { 755 defaultVals = append(defaultVals, fmt.Sprintf("%d", i)) 756 } 757 } 758 759 return stringifySliceFlag(f.Usage, f.Name, defaultVals) 760} 761 762func stringifyInt64SliceFlag(f Int64SliceFlag) string { 763 defaultVals := []string{} 764 if f.Value != nil && len(f.Value.Value()) > 0 { 765 for _, i := range f.Value.Value() { 766 defaultVals = append(defaultVals, fmt.Sprintf("%d", i)) 767 } 768 } 769 770 return stringifySliceFlag(f.Usage, f.Name, defaultVals) 771} 772 773func stringifyStringSliceFlag(f StringSliceFlag) string { 774 defaultVals := []string{} 775 if f.Value != nil && len(f.Value.Value()) > 0 { 776 for _, s := range f.Value.Value() { 777 if len(s) > 0 { 778 defaultVals = append(defaultVals, fmt.Sprintf("%q", s)) 779 } 780 } 781 } 782 783 return stringifySliceFlag(f.Usage, f.Name, defaultVals) 784} 785 786func stringifySliceFlag(usage, name string, defaultVals []string) string { 787 placeholder, usage := unquoteUsage(usage) 788 if placeholder == "" { 789 placeholder = defaultPlaceholder 790 } 791 792 defaultVal := "" 793 if len(defaultVals) > 0 { 794 defaultVal = fmt.Sprintf(" (default: %s)", strings.Join(defaultVals, ", ")) 795 } 796 797 usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultVal)) 798 return fmt.Sprintf("%s\t%s", prefixedNames(name, placeholder), usageWithDefault) 799} 800