1cli v2 manual 2=== 3 4<!-- toc --> 5 6- [Migrating From Older Releases](#migrating-from-older-releases) 7- [Getting Started](#getting-started) 8- [Examples](#examples) 9 * [Arguments](#arguments) 10 * [Flags](#flags) 11 + [Placeholder Values](#placeholder-values) 12 + [Alternate Names](#alternate-names) 13 + [Ordering](#ordering) 14 + [Values from the Environment](#values-from-the-environment) 15 + [Values from files](#values-from-files) 16 + [Values from alternate input sources (YAML, TOML, and others)](#values-from-alternate-input-sources-yaml-toml-and-others) 17 + [Required Flags](#required-flags) 18 + [Default Values for help output](#default-values-for-help-output) 19 + [Precedence](#precedence) 20 * [Subcommands](#subcommands) 21 * [Subcommands categories](#subcommands-categories) 22 * [Exit code](#exit-code) 23 * [Combining short options](#combining-short-options) 24 * [Bash Completion](#bash-completion) 25 + [Default auto-completion](#default-auto-completion) 26 + [Custom auto-completion](#custom-auto-completion) 27 + [Enabling](#enabling) 28 + [Distribution and Persistent Autocompletion](#distribution-and-persistent-autocompletion) 29 + [Customization](#customization) 30 + [ZSH Support](#zsh-support) 31 + [ZSH default auto-complete example](#zsh-default-auto-complete-example) 32 + [ZSH custom auto-complete example](#zsh-custom-auto-complete-example) 33 * [Generated Help Text](#generated-help-text) 34 + [Customization](#customization-1) 35 * [Version Flag](#version-flag) 36 + [Customization](#customization-2) 37 * [Timestamp Flag](#timestamp-flag) 38 * [Full API Example](#full-api-example) 39 40<!-- tocstop --> 41 42## Migrating From Older Releases 43 44There are a small set of breaking changes between v1 and v2. 45Converting is relatively straightforward and typically takes less than 46an hour. Specific steps are included in 47[Migration Guide: v1 to v2](../migrate-v1-to-v2.md). 48 49## Getting Started 50 51One of the philosophies behind cli is that an API should be playful and full of 52discovery. So a cli app can be as little as one line of code in `main()`. 53 54<!-- { 55 "args": ["--help"], 56 "output": "A new cli application" 57} --> 58``` go 59package main 60 61import ( 62 "os" 63 64 "github.com/urfave/cli/v2" 65) 66 67func main() { 68 (&cli.App{}).Run(os.Args) 69} 70``` 71 72This app will run and show help text, but is not very useful. Let's give an 73action to execute and some help documentation: 74 75<!-- { 76 "output": "boom! I say!" 77} --> 78``` go 79package main 80 81import ( 82 "fmt" 83 "log" 84 "os" 85 86 "github.com/urfave/cli/v2" 87) 88 89func main() { 90 app := &cli.App{ 91 Name: "boom", 92 Usage: "make an explosive entrance", 93 Action: func(c *cli.Context) error { 94 fmt.Println("boom! I say!") 95 return nil 96 }, 97 } 98 99 err := app.Run(os.Args) 100 if err != nil { 101 log.Fatal(err) 102 } 103} 104``` 105 106Running this already gives you a ton of functionality, plus support for things 107like subcommands and flags, which are covered below. 108 109## Examples 110 111Being a programmer can be a lonely job. Thankfully by the power of automation 112that is not the case! Let's create a greeter app to fend off our demons of 113loneliness! 114 115Start by creating a directory named `greet`, and within it, add a file, 116`greet.go` with the following code in it: 117 118<!-- { 119 "output": "Hello friend!" 120} --> 121``` go 122package main 123 124import ( 125 "fmt" 126 "log" 127 "os" 128 129 "github.com/urfave/cli/v2" 130) 131 132func main() { 133 app := &cli.App{ 134 Name: "greet", 135 Usage: "fight the loneliness!", 136 Action: func(c *cli.Context) error { 137 fmt.Println("Hello friend!") 138 return nil 139 }, 140 } 141 142 err := app.Run(os.Args) 143 if err != nil { 144 log.Fatal(err) 145 } 146} 147``` 148 149Install our command to the `$GOPATH/bin` directory: 150 151``` 152$ go install 153``` 154 155Finally run our new command: 156 157``` 158$ greet 159Hello friend! 160``` 161 162cli also generates neat help text: 163 164``` 165$ greet help 166NAME: 167 greet - fight the loneliness! 168 169USAGE: 170 greet [global options] command [command options] [arguments...] 171 172COMMANDS: 173 help, h Shows a list of commands or help for one command 174 175GLOBAL OPTIONS 176 --help, -h show help (default: false) 177``` 178 179### Arguments 180 181You can lookup arguments by calling the `Args` function on `cli.Context`, e.g.: 182 183<!-- { 184 "output": "Hello \"" 185} --> 186``` go 187package main 188 189import ( 190 "fmt" 191 "log" 192 "os" 193 194 "github.com/urfave/cli/v2" 195) 196 197func main() { 198 app := &cli.App{ 199 Action: func(c *cli.Context) error { 200 fmt.Printf("Hello %q", c.Args().Get(0)) 201 return nil 202 }, 203 } 204 205 err := app.Run(os.Args) 206 if err != nil { 207 log.Fatal(err) 208 } 209} 210``` 211 212### Flags 213 214Setting and querying flags is simple. 215 216<!-- { 217 "output": "Hello Nefertiti" 218} --> 219``` go 220package main 221 222import ( 223 "fmt" 224 "log" 225 "os" 226 227 "github.com/urfave/cli/v2" 228) 229 230func main() { 231 app := &cli.App{ 232 Flags: []cli.Flag { 233 &cli.StringFlag{ 234 Name: "lang", 235 Value: "english", 236 Usage: "language for the greeting", 237 }, 238 }, 239 Action: func(c *cli.Context) error { 240 name := "Nefertiti" 241 if c.NArg() > 0 { 242 name = c.Args().Get(0) 243 } 244 if c.String("lang") == "spanish" { 245 fmt.Println("Hola", name) 246 } else { 247 fmt.Println("Hello", name) 248 } 249 return nil 250 }, 251 } 252 253 err := app.Run(os.Args) 254 if err != nil { 255 log.Fatal(err) 256 } 257} 258``` 259 260You can also set a destination variable for a flag, to which the content will be 261scanned. 262 263<!-- { 264 "output": "Hello someone" 265} --> 266``` go 267package main 268 269import ( 270 "log" 271 "os" 272 "fmt" 273 274 "github.com/urfave/cli/v2" 275) 276 277func main() { 278 var language string 279 280 app := &cli.App{ 281 Flags: []cli.Flag { 282 &cli.StringFlag{ 283 Name: "lang", 284 Value: "english", 285 Usage: "language for the greeting", 286 Destination: &language, 287 }, 288 }, 289 Action: func(c *cli.Context) error { 290 name := "someone" 291 if c.NArg() > 0 { 292 name = c.Args().Get(0) 293 } 294 if language == "spanish" { 295 fmt.Println("Hola", name) 296 } else { 297 fmt.Println("Hello", name) 298 } 299 return nil 300 }, 301 } 302 303 err := app.Run(os.Args) 304 if err != nil { 305 log.Fatal(err) 306 } 307} 308``` 309 310See full list of flags at https://pkg.go.dev/github.com/urfave/cli/v2 311 312#### Placeholder Values 313 314Sometimes it's useful to specify a flag's value within the usage string itself. 315Such placeholders are indicated with back quotes. 316 317For example this: 318 319<!-- { 320 "args": ["--help"], 321 "output": "--config FILE, -c FILE" 322} --> 323```go 324package main 325 326import ( 327 "log" 328 "os" 329 330 "github.com/urfave/cli/v2" 331) 332 333func main() { 334 app := &cli.App{ 335 Flags: []cli.Flag{ 336 &cli.StringFlag{ 337 Name: "config", 338 Aliases: []string{"c"}, 339 Usage: "Load configuration from `FILE`", 340 }, 341 }, 342 } 343 344 err := app.Run(os.Args) 345 if err != nil { 346 log.Fatal(err) 347 } 348} 349``` 350 351Will result in help output like: 352 353``` 354--config FILE, -c FILE Load configuration from FILE 355``` 356 357Note that only the first placeholder is used. Subsequent back-quoted words will 358be left as-is. 359 360#### Alternate Names 361 362You can set alternate (or short) names for flags by providing a comma-delimited 363list for the `Name`. e.g. 364 365<!-- { 366 "args": ["--help"], 367 "output": "--lang value, -l value.*language for the greeting.*default: \"english\"" 368} --> 369``` go 370package main 371 372import ( 373 "log" 374 "os" 375 376 "github.com/urfave/cli/v2" 377) 378 379func main() { 380 app := &cli.App{ 381 Flags: []cli.Flag { 382 &cli.StringFlag{ 383 Name: "lang", 384 Aliases: []string{"l"}, 385 Value: "english", 386 Usage: "language for the greeting", 387 }, 388 }, 389 } 390 391 err := app.Run(os.Args) 392 if err != nil { 393 log.Fatal(err) 394 } 395} 396``` 397 398That flag can then be set with `--lang spanish` or `-l spanish`. Note that 399giving two different forms of the same flag in the same command invocation is an 400error. 401 402#### Ordering 403 404Flags for the application and commands are shown in the order they are defined. 405However, it's possible to sort them from outside this library by using `FlagsByName` 406or `CommandsByName` with `sort`. 407 408For example this: 409 410<!-- { 411 "args": ["--help"], 412 "output": "add a task to the list\n.*complete a task on the list\n.*\n\n.*\n.*Load configuration from FILE\n.*Language for the greeting.*" 413} --> 414``` go 415package main 416 417import ( 418 "log" 419 "os" 420 "sort" 421 422 "github.com/urfave/cli/v2" 423) 424 425func main() { 426 app := &cli.App{ 427 Flags: []cli.Flag{ 428 &cli.StringFlag{ 429 Name: "lang, l", 430 Value: "english", 431 Usage: "Language for the greeting", 432 }, 433 &cli.StringFlag{ 434 Name: "config, c", 435 Usage: "Load configuration from `FILE`", 436 }, 437 }, 438 Commands: []*cli.Command{ 439 { 440 Name: "complete", 441 Aliases: []string{"c"}, 442 Usage: "complete a task on the list", 443 Action: func(c *cli.Context) error { 444 return nil 445 }, 446 }, 447 { 448 Name: "add", 449 Aliases: []string{"a"}, 450 Usage: "add a task to the list", 451 Action: func(c *cli.Context) error { 452 return nil 453 }, 454 }, 455 }, 456 } 457 458 sort.Sort(cli.FlagsByName(app.Flags)) 459 sort.Sort(cli.CommandsByName(app.Commands)) 460 461 err := app.Run(os.Args) 462 if err != nil { 463 log.Fatal(err) 464 } 465} 466``` 467 468Will result in help output like: 469 470``` 471--config FILE, -c FILE Load configuration from FILE 472--lang value, -l value Language for the greeting (default: "english") 473``` 474 475#### Values from the Environment 476 477You can also have the default value set from the environment via `EnvVars`. e.g. 478 479<!-- { 480 "args": ["--help"], 481 "output": "language for the greeting.*APP_LANG" 482} --> 483``` go 484package main 485 486import ( 487 "log" 488 "os" 489 490 "github.com/urfave/cli/v2" 491) 492 493func main() { 494 app := &cli.App{ 495 Flags: []cli.Flag { 496 &cli.StringFlag{ 497 Name: "lang", 498 Aliases: []string{"l"}, 499 Value: "english", 500 Usage: "language for the greeting", 501 EnvVars: []string{"APP_LANG"}, 502 }, 503 }, 504 } 505 506 err := app.Run(os.Args) 507 if err != nil { 508 log.Fatal(err) 509 } 510} 511``` 512 513If `EnvVars` contains more than one string, the first environment variable that 514resolves is used as the default. 515 516<!-- { 517 "args": ["--help"], 518 "output": "language for the greeting.*LEGACY_COMPAT_LANG.*APP_LANG.*LANG" 519} --> 520``` go 521package main 522 523import ( 524 "log" 525 "os" 526 527 "github.com/urfave/cli/v2" 528) 529 530func main() { 531 app := &cli.App{ 532 Flags: []cli.Flag{ 533 &cli.StringFlag{ 534 Name: "lang", 535 Aliases: []string{"l"}, 536 Value: "english", 537 Usage: "language for the greeting", 538 EnvVars: []string{"LEGACY_COMPAT_LANG", "APP_LANG", "LANG"}, 539 }, 540 }, 541 } 542 543 err := app.Run(os.Args) 544 if err != nil { 545 log.Fatal(err) 546 } 547} 548``` 549 550#### Values from files 551 552You can also have the default value set from file via `FilePath`. e.g. 553 554<!-- { 555 "args": ["--help"], 556 "output": "password for the mysql database" 557} --> 558``` go 559package main 560 561import ( 562 "log" 563 "os" 564 565 "github.com/urfave/cli/v2" 566) 567 568func main() { 569 app := cli.NewApp() 570 571 app.Flags = []cli.Flag { 572 &cli.StringFlag{ 573 Name: "password, p", 574 Usage: "password for the mysql database", 575 FilePath: "/etc/mysql/password", 576 }, 577 } 578 579 err := app.Run(os.Args) 580 if err != nil { 581 log.Fatal(err) 582 } 583} 584``` 585 586Note that default values set from file (e.g. `FilePath`) take precedence over 587default values set from the environment (e.g. `EnvVar`). 588 589#### Values from alternate input sources (YAML, TOML, and others) 590 591There is a separate package altsrc that adds support for getting flag values 592from other file input sources. 593 594Currently supported input source formats: 595* YAML 596* JSON 597* TOML 598 599In order to get values for a flag from an alternate input source the following 600code would be added to wrap an existing cli.Flag like below: 601 602``` go 603 altsrc.NewIntFlag(&cli.IntFlag{Name: "test"}) 604``` 605 606Initialization must also occur for these flags. Below is an example initializing 607getting data from a yaml file below. 608 609``` go 610 command.Before = altsrc.InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) 611``` 612 613The code above will use the "load" string as a flag name to get the file name of 614a yaml file from the cli.Context. It will then use that file name to initialize 615the yaml input source for any flags that are defined on that command. As a note 616the "load" flag used would also have to be defined on the command flags in order 617for this code snippet to work. 618 619Currently only YAML, JSON, and TOML files are supported but developers can add support 620for other input sources by implementing the altsrc.InputSourceContext for their 621given sources. 622 623Here is a more complete sample of a command using YAML support: 624 625<!-- { 626 "args": ["test-cmd", "--help"], 627 "output": "--test value.*default: 0" 628} --> 629``` go 630package main 631 632import ( 633 "fmt" 634 "os" 635 636 "github.com/urfave/cli/v2" 637 "github.com/urfave/cli/v2/altsrc" 638) 639 640func main() { 641 flags := []cli.Flag{ 642 altsrc.NewIntFlag(&cli.IntFlag{Name: "test"}), 643 &cli.StringFlag{Name: "load"}, 644 } 645 646 app := &cli.App{ 647 Action: func(c *cli.Context) error { 648 fmt.Println("yaml ist rad") 649 return nil 650 }, 651 Before: altsrc.InitInputSourceWithContext(flags, altsrc.NewYamlSourceFromFlagFunc("load")), 652 Flags: flags, 653 } 654 655 app.Run(os.Args) 656} 657``` 658 659#### Required Flags 660 661You can make a flag required by setting the `Required` field to `true`. If a user 662does not provide a required flag, they will be shown an error message. 663 664Take for example this app that requires the `lang` flag: 665 666<!-- { 667 "error": "Required flag \"lang\" not set" 668} --> 669```go 670package main 671 672import ( 673 "log" 674 "os" 675 "github.com/urfave/cli/v2" 676) 677 678func main() { 679 app := cli.NewApp() 680 681 app.Flags = []cli.Flag { 682 &cli.StringFlag{ 683 Name: "lang", 684 Value: "english", 685 Usage: "language for the greeting", 686 Required: true, 687 }, 688 } 689 690 app.Action = func(c *cli.Context) error { 691 var output string 692 if c.String("lang") == "spanish" { 693 output = "Hola" 694 } else { 695 output = "Hello" 696 } 697 fmt.Println(output) 698 return nil 699 } 700 701 err := app.Run(os.Args) 702 if err != nil { 703 log.Fatal(err) 704 } 705} 706``` 707 708If the app is run without the `lang` flag, the user will see the following message 709 710``` 711Required flag "lang" not set 712``` 713 714#### Default Values for help output 715 716Sometimes it's useful to specify a flag's default help-text value within the flag declaration. This can be useful if the default value for a flag is a computed value. The default value can be set via the `DefaultText` struct field. 717 718For example this: 719 720<!-- { 721 "args": ["--help"], 722 "output": "--port value" 723} --> 724```go 725package main 726 727import ( 728 "log" 729 "os" 730 731 "github.com/urfave/cli/v2" 732) 733 734func main() { 735 app := &cli.App{ 736 Flags: []cli.Flag{ 737 &cli.IntFlag{ 738 Name: "port", 739 Usage: "Use a randomized port", 740 Value: 0, 741 DefaultText: "random", 742 }, 743 }, 744 } 745 746 err := app.Run(os.Args) 747 if err != nil { 748 log.Fatal(err) 749 } 750} 751``` 752 753Will result in help output like: 754 755``` 756--port value Use a randomized port (default: random) 757``` 758 759#### Precedence 760 761The precedence for flag value sources is as follows (highest to lowest): 762 7630. Command line flag value from user 7640. Environment variable (if specified) 7650. Configuration file (if specified) 7660. Default defined on the flag 767 768### Subcommands 769 770Subcommands can be defined for a more git-like command line app. 771 772<!-- { 773 "args": ["template", "add"], 774 "output": "new task template: .+" 775} --> 776```go 777package main 778 779import ( 780 "fmt" 781 "log" 782 "os" 783 784 "github.com/urfave/cli/v2" 785) 786 787func main() { 788 app := &cli.App{ 789 Commands: []*cli.Command{ 790 { 791 Name: "add", 792 Aliases: []string{"a"}, 793 Usage: "add a task to the list", 794 Action: func(c *cli.Context) error { 795 fmt.Println("added task: ", c.Args().First()) 796 return nil 797 }, 798 }, 799 { 800 Name: "complete", 801 Aliases: []string{"c"}, 802 Usage: "complete a task on the list", 803 Action: func(c *cli.Context) error { 804 fmt.Println("completed task: ", c.Args().First()) 805 return nil 806 }, 807 }, 808 { 809 Name: "template", 810 Aliases: []string{"t"}, 811 Usage: "options for task templates", 812 Subcommands: []*cli.Command{ 813 { 814 Name: "add", 815 Usage: "add a new template", 816 Action: func(c *cli.Context) error { 817 fmt.Println("new task template: ", c.Args().First()) 818 return nil 819 }, 820 }, 821 { 822 Name: "remove", 823 Usage: "remove an existing template", 824 Action: func(c *cli.Context) error { 825 fmt.Println("removed task template: ", c.Args().First()) 826 return nil 827 }, 828 }, 829 }, 830 }, 831 }, 832 } 833 834 err := app.Run(os.Args) 835 if err != nil { 836 log.Fatal(err) 837 } 838} 839``` 840 841### Subcommands categories 842 843For additional organization in apps that have many subcommands, you can 844associate a category for each command to group them together in the help 845output. 846 847E.g. 848 849```go 850package main 851 852import ( 853 "log" 854 "os" 855 856 "github.com/urfave/cli/v2" 857) 858 859func main() { 860 app := &cli.App{ 861 Commands: []*cli.Command{ 862 { 863 Name: "noop", 864 }, 865 { 866 Name: "add", 867 Category: "template", 868 }, 869 { 870 Name: "remove", 871 Category: "template", 872 }, 873 }, 874 } 875 876 err := app.Run(os.Args) 877 if err != nil { 878 log.Fatal(err) 879 } 880} 881``` 882 883Will include: 884 885``` 886COMMANDS: 887 noop 888 889 Template actions: 890 add 891 remove 892``` 893 894### Exit code 895 896Calling `App.Run` will not automatically call `os.Exit`, which means that by 897default the exit code will "fall through" to being `0`. An explicit exit code 898may be set by returning a non-nil error that fulfills `cli.ExitCoder`, *or* a 899`cli.MultiError` that includes an error that fulfills `cli.ExitCoder`, e.g.: 900<!-- { 901 "error": "Ginger croutons are not in the soup" 902} --> 903``` go 904package main 905 906import ( 907 "log" 908 "os" 909 910 "github.com/urfave/cli/v2" 911) 912 913func main() { 914 app := &cli.App{ 915 Flags: []cli.Flag{ 916 &cli.BoolFlag{ 917 Name: "ginger-crouton", 918 Usage: "is it in the soup?", 919 }, 920 }, 921 Action: func(ctx *cli.Context) error { 922 if !ctx.Bool("ginger-crouton") { 923 return cli.Exit("Ginger croutons are not in the soup", 86) 924 } 925 return nil 926 }, 927 } 928 929 err := app.Run(os.Args) 930 if err != nil { 931 log.Fatal(err) 932 } 933} 934``` 935 936### Combining short options 937 938Traditional use of options using their shortnames look like this: 939 940``` 941$ cmd -s -o -m "Some message" 942``` 943 944Suppose you want users to be able to combine options with their shortnames. This 945can be done using the `UseShortOptionHandling` bool in your app configuration, 946or for individual commands by attaching it to the command configuration. For 947example: 948 949<!-- { 950 "args": ["short", "-som", "Some message"], 951 "output": "serve: true\noption: true\nmessage: Some message\n" 952} --> 953``` go 954package main 955 956import ( 957 "fmt" 958 "log" 959 "os" 960 961 "github.com/urfave/cli/v2" 962) 963 964func main() { 965 app := &cli.App{} 966 app.UseShortOptionHandling = true 967 app.Commands = []*cli.Command{ 968 { 969 Name: "short", 970 Usage: "complete a task on the list", 971 Flags: []cli.Flag{ 972 &cli.BoolFlag{Name: "serve", Aliases: []string{"s"}}, 973 &cli.BoolFlag{Name: "option", Aliases: []string{"o"}}, 974 &cli.StringFlag{Name: "message", Aliases: []string{"m"}}, 975 }, 976 Action: func(c *cli.Context) error { 977 fmt.Println("serve:", c.Bool("serve")) 978 fmt.Println("option:", c.Bool("option")) 979 fmt.Println("message:", c.String("message")) 980 return nil 981 }, 982 }, 983 } 984 985 err := app.Run(os.Args) 986 if err != nil { 987 log.Fatal(err) 988 } 989} 990``` 991 992If your program has any number of bool flags such as `serve` and `option`, and 993optionally one non-bool flag `message`, with the short options of `-s`, `-o`, 994and `-m` respectively, setting `UseShortOptionHandling` will also support the 995following syntax: 996 997``` 998$ cmd -som "Some message" 999``` 1000 1001If you enable `UseShortOptionHandling`, then you must not use any flags that 1002have a single leading `-` or this will result in failures. For example, 1003`-option` can no longer be used. Flags with two leading dashes (such as 1004`--options`) are still valid. 1005 1006### Bash Completion 1007 1008You can enable completion commands by setting the `EnableBashCompletion` 1009flag on the `App` object to `true`. By default, this setting will allow auto-completion 1010for an app's subcommands, but you can write your own completion methods for 1011the App or its subcommands as well. 1012 1013#### Default auto-completion 1014 1015```go 1016package main 1017import ( 1018 "fmt" 1019 "log" 1020 "os" 1021 "github.com/urfave/cli/v2" 1022) 1023func main() { 1024 app := cli.NewApp() 1025 app.EnableBashCompletion = true 1026 app.Commands = []*cli.Command{ 1027 { 1028 Name: "add", 1029 Aliases: []string{"a"}, 1030 Usage: "add a task to the list", 1031 Action: func(c *cli.Context) error { 1032 fmt.Println("added task: ", c.Args().First()) 1033 return nil 1034 }, 1035 }, 1036 { 1037 Name: "complete", 1038 Aliases: []string{"c"}, 1039 Usage: "complete a task on the list", 1040 Action: func(c *cli.Context) error { 1041 fmt.Println("completed task: ", c.Args().First()) 1042 return nil 1043 }, 1044 }, 1045 { 1046 Name: "template", 1047 Aliases: []string{"t"}, 1048 Usage: "options for task templates", 1049 Subcommands: []*cli.Command{ 1050 { 1051 Name: "add", 1052 Usage: "add a new template", 1053 Action: func(c *cli.Context) error { 1054 fmt.Println("new task template: ", c.Args().First()) 1055 return nil 1056 }, 1057 }, 1058 { 1059 Name: "remove", 1060 Usage: "remove an existing template", 1061 Action: func(c *cli.Context) error { 1062 fmt.Println("removed task template: ", c.Args().First()) 1063 return nil 1064 }, 1065 }, 1066 }, 1067 }, 1068 } 1069 err := app.Run(os.Args) 1070 if err != nil { 1071 log.Fatal(err) 1072 } 1073} 1074``` 1075![](/docs/v2/images/default-bash-autocomplete.gif) 1076 1077#### Custom auto-completion 1078<!-- { 1079 "args": ["complete", "--generate-bash-completion"], 1080 "output": "laundry" 1081} --> 1082``` go 1083package main 1084 1085import ( 1086 "fmt" 1087 "log" 1088 "os" 1089 1090 "github.com/urfave/cli/v2" 1091) 1092 1093func main() { 1094 tasks := []string{"cook", "clean", "laundry", "eat", "sleep", "code"} 1095 1096 app := &cli.App{ 1097 EnableBashCompletion: true, 1098 Commands: []*cli.Command{ 1099 { 1100 Name: "complete", 1101 Aliases: []string{"c"}, 1102 Usage: "complete a task on the list", 1103 Action: func(c *cli.Context) error { 1104 fmt.Println("completed task: ", c.Args().First()) 1105 return nil 1106 }, 1107 BashComplete: func(c *cli.Context) { 1108 // This will complete if no args are passed 1109 if c.NArg() > 0 { 1110 return 1111 } 1112 for _, t := range tasks { 1113 fmt.Println(t) 1114 } 1115 }, 1116 }, 1117 }, 1118 } 1119 1120 err := app.Run(os.Args) 1121 if err != nil { 1122 log.Fatal(err) 1123 } 1124} 1125``` 1126![](/docs/v2/images/custom-bash-autocomplete.gif) 1127 1128#### Enabling 1129 1130To enable auto-completion for the current shell session, a bash script, 1131`autocomplete/bash_autocomplete` is included in this repo. 1132 1133To use `autocomplete/bash_autocomplete` set an environment variable named `PROG` to 1134the name of your program and then `source` the `autocomplete/bash_autocomplete` file. 1135 1136For example, if your cli program is called `myprogram`: 1137 1138`PROG=myprogram source path/to/cli/autocomplete/bash_autocomplete` 1139 1140Auto-completion is now enabled for the current shell, but will not persist into a new shell. 1141 1142#### Distribution and Persistent Autocompletion 1143 1144Copy `autocomplete/bash_autocomplete` into `/etc/bash_completion.d/` and rename 1145it to the name of the program you wish to add autocomplete support for (or 1146automatically install it there if you are distributing a package). Don't forget 1147to source the file or restart your shell to activate the auto-completion. 1148 1149``` 1150sudo cp path/to/autocomplete/bash_autocomplete /etc/bash_completion.d/<myprogram> 1151source /etc/bash_completion.d/<myprogram> 1152``` 1153 1154Alternatively, you can just document that users should `source` the generic 1155`autocomplete/bash_autocomplete` and set `$PROG` within their bash configuration 1156file, adding these lines: 1157 1158``` 1159PROG=<myprogram> 1160source path/to/cli/autocomplete/bash_autocomplete 1161``` 1162Keep in mind that if they are enabling auto-completion for more than one program, 1163they will need to set `PROG` and source `autocomplete/bash_autocomplete` for each 1164program, like so: 1165 1166``` 1167PROG=<program1> 1168source path/to/cli/autocomplete/bash_autocomplete 1169PROG=<program2> 1170source path/to/cli/autocomplete/bash_autocomplete 1171``` 1172 1173#### Customization 1174 1175The default shell completion flag (`--generate-bash-completion`) is defined as 1176`cli.EnableBashCompletion`, and may be redefined if desired, e.g.: 1177 1178<!-- { 1179 "args": ["--generate-bash-completion"], 1180 "output": "wat\nhelp\nh" 1181} --> 1182``` go 1183package main 1184 1185import ( 1186 "log" 1187 "os" 1188 1189 "github.com/urfave/cli/v2" 1190) 1191 1192func main() { 1193 app := &cli.App{ 1194 EnableBashCompletion: true, 1195 Commands: []*cli.Command{ 1196 { 1197 Name: "wat", 1198 }, 1199 }, 1200 } 1201 err := app.Run(os.Args) 1202 if err != nil { 1203 log.Fatal(err) 1204 } 1205} 1206``` 1207 1208#### ZSH Support 1209Auto-completion for ZSH is also supported using the `autocomplete/zsh_autocomplete` 1210file included in this repo. Two environment variables are used, `PROG` and `_CLI_ZSH_AUTOCOMPLETE_HACK`. 1211Set `PROG` to the program name as before, set `_CLI_ZSH_AUTOCOMPLETE_HACK` to `1`, and 1212then `source path/to/autocomplete/zsh_autocomplete`. Adding the following lines to your ZSH 1213configuration file (usually `.zshrc`) will allow the auto-completion to persist across new shells: 1214 1215``` 1216PROG=<myprogram> 1217_CLI_ZSH_AUTOCOMPLETE_HACK=1 1218source path/to/autocomplete/zsh_autocomplete 1219``` 1220#### ZSH default auto-complete example 1221![](/docs/v2/images/default-zsh-autocomplete.gif) 1222#### ZSH custom auto-complete example 1223![](/docs/v2/images/custom-zsh-autocomplete.gif) 1224 1225### Generated Help Text 1226 1227The default help flag (`-h/--help`) is defined as `cli.HelpFlag` and is checked 1228by the cli internals in order to print generated help text for the app, command, 1229or subcommand, and break execution. 1230 1231#### Customization 1232 1233All of the help text generation may be customized, and at multiple levels. The 1234templates are exposed as variables `AppHelpTemplate`, `CommandHelpTemplate`, and 1235`SubcommandHelpTemplate` which may be reassigned or augmented, and full override 1236is possible by assigning a compatible func to the `cli.HelpPrinter` variable, 1237e.g.: 1238 1239<!-- { 1240 "output": "Ha HA. I pwnd the help!!1" 1241} --> 1242``` go 1243package main 1244 1245import ( 1246 "fmt" 1247 "io" 1248 "os" 1249 1250 "github.com/urfave/cli/v2" 1251) 1252 1253func main() { 1254 // EXAMPLE: Append to an existing template 1255 cli.AppHelpTemplate = fmt.Sprintf(`%s 1256 1257WEBSITE: http://awesometown.example.com 1258 1259SUPPORT: support@awesometown.example.com 1260 1261`, cli.AppHelpTemplate) 1262 1263 // EXAMPLE: Override a template 1264 cli.AppHelpTemplate = `NAME: 1265 {{.Name}} - {{.Usage}} 1266USAGE: 1267 {{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}} 1268 {{if len .Authors}} 1269AUTHOR: 1270 {{range .Authors}}{{ . }}{{end}} 1271 {{end}}{{if .Commands}} 1272COMMANDS: 1273{{range .Commands}}{{if not .HideHelp}} {{join .Names ", "}}{{ "\t"}}{{.Usage}}{{ "\n" }}{{end}}{{end}}{{end}}{{if .VisibleFlags}} 1274GLOBAL OPTIONS: 1275 {{range .VisibleFlags}}{{.}} 1276 {{end}}{{end}}{{if .Copyright }} 1277COPYRIGHT: 1278 {{.Copyright}} 1279 {{end}}{{if .Version}} 1280VERSION: 1281 {{.Version}} 1282 {{end}} 1283` 1284 1285 // EXAMPLE: Replace the `HelpPrinter` func 1286 cli.HelpPrinter = func(w io.Writer, templ string, data interface{}) { 1287 fmt.Println("Ha HA. I pwnd the help!!1") 1288 } 1289 1290 (&cli.App{}).Run(os.Args) 1291} 1292``` 1293 1294The default flag may be customized to something other than `-h/--help` by 1295setting `cli.HelpFlag`, e.g.: 1296 1297<!-- { 1298 "args": ["--halp"], 1299 "output": "haaaaalp.*HALP" 1300} --> 1301``` go 1302package main 1303 1304import ( 1305 "os" 1306 1307 "github.com/urfave/cli/v2" 1308) 1309 1310func main() { 1311 cli.HelpFlag = &cli.BoolFlag{ 1312 Name: "haaaaalp", Aliases: []string{"halp"}, 1313 Usage: "HALP", 1314 EnvVars: []string{"SHOW_HALP", "HALPPLZ"}, 1315 } 1316 1317 (&cli.App{}).Run(os.Args) 1318} 1319``` 1320 1321### Version Flag 1322 1323The default version flag (`-v/--version`) is defined as `cli.VersionFlag`, which 1324is checked by the cli internals in order to print the `App.Version` via 1325`cli.VersionPrinter` and break execution. 1326 1327#### Customization 1328 1329The default flag may be customized to something other than `-v/--version` by 1330setting `cli.VersionFlag`, e.g.: 1331 1332<!-- { 1333 "args": ["--print-version"], 1334 "output": "partay version v19\\.99\\.0" 1335} --> 1336``` go 1337package main 1338 1339import ( 1340 "os" 1341 1342 "github.com/urfave/cli/v2" 1343) 1344 1345func main() { 1346 cli.VersionFlag = &cli.BoolFlag{ 1347 Name: "print-version", Aliases: []string{"V"}, 1348 Usage: "print only the version", 1349 } 1350 1351 app := &cli.App{ 1352 Name: "partay", 1353 Version: "v19.99.0", 1354 } 1355 app.Run(os.Args) 1356} 1357``` 1358 1359Alternatively, the version printer at `cli.VersionPrinter` may be overridden, e.g.: 1360 1361<!-- { 1362 "args": ["--version"], 1363 "output": "version=v19\\.99\\.0 revision=fafafaf" 1364} --> 1365``` go 1366package main 1367 1368import ( 1369 "fmt" 1370 "os" 1371 1372 "github.com/urfave/cli/v2" 1373) 1374 1375var ( 1376 Revision = "fafafaf" 1377) 1378 1379func main() { 1380 cli.VersionPrinter = func(c *cli.Context) { 1381 fmt.Printf("version=%s revision=%s\n", c.App.Version, Revision) 1382 } 1383 1384 app := &cli.App{ 1385 Name: "partay", 1386 Version: "v19.99.0", 1387 } 1388 app.Run(os.Args) 1389} 1390``` 1391 1392### Timestamp Flag 1393 1394Using the timestamp flag is simple. Please refer to [`time.Parse`](https://golang.org/pkg/time/#example_Parse) to get possible formats. 1395 1396<!-- { 1397 "args": ["--meeting", "2019-08-12T15:04:05"], 1398 "output": "2019\\-08\\-12 15\\:04\\:05 \\+0000 UTC" 1399} --> 1400``` go 1401package main 1402 1403import ( 1404 "fmt" 1405 "log" 1406 "os" 1407 1408 "github.com/urfave/cli/v2" 1409) 1410 1411func main() { 1412 app := &cli.App{ 1413 Flags: []cli.Flag { 1414 &cli.TimestampFlag{Name: "meeting", Layout: "2006-01-02T15:04:05"}, 1415 }, 1416 Action: func(c *cli.Context) error { 1417 fmt.Printf("%s", c.Timestamp("meeting").String()) 1418 return nil 1419 }, 1420 } 1421 1422 err := app.Run(os.Args) 1423 if err != nil { 1424 log.Fatal(err) 1425 } 1426} 1427``` 1428 1429In this example the flag could be used like this : 1430 1431`myapp --meeting 2019-08-12T15:04:05` 1432 1433Side note: quotes may be necessary around the date depending on your layout (if you have spaces for instance) 1434 1435### Full API Example 1436 1437**Notice**: This is a contrived (functioning) example meant strictly for API 1438demonstration purposes. Use of one's imagination is encouraged. 1439 1440<!-- { 1441 "output": "made it!\nPhew!" 1442} --> 1443``` go 1444package main 1445 1446import ( 1447 "errors" 1448 "flag" 1449 "fmt" 1450 "io" 1451 "io/ioutil" 1452 "os" 1453 "time" 1454 1455 "github.com/urfave/cli/v2" 1456) 1457 1458func init() { 1459 cli.AppHelpTemplate += "\nCUSTOMIZED: you bet ur muffins\n" 1460 cli.CommandHelpTemplate += "\nYMMV\n" 1461 cli.SubcommandHelpTemplate += "\nor something\n" 1462 1463 cli.HelpFlag = &cli.BoolFlag{Name: "halp"} 1464 cli.VersionFlag = &cli.BoolFlag{Name: "print-version", Aliases: []string{"V"}} 1465 1466 cli.HelpPrinter = func(w io.Writer, templ string, data interface{}) { 1467 fmt.Fprintf(w, "best of luck to you\n") 1468 } 1469 cli.VersionPrinter = func(c *cli.Context) { 1470 fmt.Fprintf(c.App.Writer, "version=%s\n", c.App.Version) 1471 } 1472 cli.OsExiter = func(c int) { 1473 fmt.Fprintf(cli.ErrWriter, "refusing to exit %d\n", c) 1474 } 1475 cli.ErrWriter = ioutil.Discard 1476 cli.FlagStringer = func(fl cli.Flag) string { 1477 return fmt.Sprintf("\t\t%s", fl.Names()[0]) 1478 } 1479} 1480 1481type hexWriter struct{} 1482 1483func (w *hexWriter) Write(p []byte) (int, error) { 1484 for _, b := range p { 1485 fmt.Printf("%x", b) 1486 } 1487 fmt.Printf("\n") 1488 1489 return len(p), nil 1490} 1491 1492type genericType struct { 1493 s string 1494} 1495 1496func (g *genericType) Set(value string) error { 1497 g.s = value 1498 return nil 1499} 1500 1501func (g *genericType) String() string { 1502 return g.s 1503} 1504 1505func main() { 1506 app := &cli.App{ 1507 Name: "kənˈtrīv", 1508 Version: "v19.99.0", 1509 Compiled: time.Now(), 1510 Authors: []*cli.Author{ 1511 &cli.Author{ 1512 Name: "Example Human", 1513 Email: "human@example.com", 1514 }, 1515 }, 1516 Copyright: "(c) 1999 Serious Enterprise", 1517 HelpName: "contrive", 1518 Usage: "demonstrate available API", 1519 UsageText: "contrive - demonstrating the available API", 1520 ArgsUsage: "[args and such]", 1521 Commands: []*cli.Command{ 1522 &cli.Command{ 1523 Name: "doo", 1524 Aliases: []string{"do"}, 1525 Category: "motion", 1526 Usage: "do the doo", 1527 UsageText: "doo - does the dooing", 1528 Description: "no really, there is a lot of dooing to be done", 1529 ArgsUsage: "[arrgh]", 1530 Flags: []cli.Flag{ 1531 &cli.BoolFlag{Name: "forever", Aliases: []string{"forevvarr"}}, 1532 }, 1533 Subcommands: []*cli.Command{ 1534 &cli.Command{ 1535 Name: "wop", 1536 Action: wopAction, 1537 }, 1538 }, 1539 SkipFlagParsing: false, 1540 HideHelp: false, 1541 Hidden: false, 1542 HelpName: "doo!", 1543 BashComplete: func(c *cli.Context) { 1544 fmt.Fprintf(c.App.Writer, "--better\n") 1545 }, 1546 Before: func(c *cli.Context) error { 1547 fmt.Fprintf(c.App.Writer, "brace for impact\n") 1548 return nil 1549 }, 1550 After: func(c *cli.Context) error { 1551 fmt.Fprintf(c.App.Writer, "did we lose anyone?\n") 1552 return nil 1553 }, 1554 Action: func(c *cli.Context) error { 1555 c.Command.FullName() 1556 c.Command.HasName("wop") 1557 c.Command.Names() 1558 c.Command.VisibleFlags() 1559 fmt.Fprintf(c.App.Writer, "dodododododoodododddooooododododooo\n") 1560 if c.Bool("forever") { 1561 c.Command.Run(c) 1562 } 1563 return nil 1564 }, 1565 OnUsageError: func(c *cli.Context, err error, isSubcommand bool) error { 1566 fmt.Fprintf(c.App.Writer, "for shame\n") 1567 return err 1568 }, 1569 }, 1570 }, 1571 Flags: []cli.Flag{ 1572 &cli.BoolFlag{Name: "fancy"}, 1573 &cli.BoolFlag{Value: true, Name: "fancier"}, 1574 &cli.DurationFlag{Name: "howlong", Aliases: []string{"H"}, Value: time.Second * 3}, 1575 &cli.Float64Flag{Name: "howmuch"}, 1576 &cli.GenericFlag{Name: "wat", Value: &genericType{}}, 1577 &cli.Int64Flag{Name: "longdistance"}, 1578 &cli.Int64SliceFlag{Name: "intervals"}, 1579 &cli.IntFlag{Name: "distance"}, 1580 &cli.IntSliceFlag{Name: "times"}, 1581 &cli.StringFlag{Name: "dance-move", Aliases: []string{"d"}}, 1582 &cli.StringSliceFlag{Name: "names", Aliases: []string{"N"}}, 1583 &cli.UintFlag{Name: "age"}, 1584 &cli.Uint64Flag{Name: "bigage"}, 1585 }, 1586 EnableBashCompletion: true, 1587 HideHelp: false, 1588 HideVersion: false, 1589 BashComplete: func(c *cli.Context) { 1590 fmt.Fprintf(c.App.Writer, "lipstick\nkiss\nme\nlipstick\nringo\n") 1591 }, 1592 Before: func(c *cli.Context) error { 1593 fmt.Fprintf(c.App.Writer, "HEEEERE GOES\n") 1594 return nil 1595 }, 1596 After: func(c *cli.Context) error { 1597 fmt.Fprintf(c.App.Writer, "Phew!\n") 1598 return nil 1599 }, 1600 CommandNotFound: func(c *cli.Context, command string) { 1601 fmt.Fprintf(c.App.Writer, "Thar be no %q here.\n", command) 1602 }, 1603 OnUsageError: func(c *cli.Context, err error, isSubcommand bool) error { 1604 if isSubcommand { 1605 return err 1606 } 1607 1608 fmt.Fprintf(c.App.Writer, "WRONG: %#v\n", err) 1609 return nil 1610 }, 1611 Action: func(c *cli.Context) error { 1612 cli.DefaultAppComplete(c) 1613 cli.HandleExitCoder(errors.New("not an exit coder, though")) 1614 cli.ShowAppHelp(c) 1615 cli.ShowCommandCompletions(c, "nope") 1616 cli.ShowCommandHelp(c, "also-nope") 1617 cli.ShowCompletions(c) 1618 cli.ShowSubcommandHelp(c) 1619 cli.ShowVersion(c) 1620 1621 fmt.Printf("%#v\n", c.App.Command("doo")) 1622 if c.Bool("infinite") { 1623 c.App.Run([]string{"app", "doo", "wop"}) 1624 } 1625 1626 if c.Bool("forevar") { 1627 c.App.RunAsSubcommand(c) 1628 } 1629 c.App.Setup() 1630 fmt.Printf("%#v\n", c.App.VisibleCategories()) 1631 fmt.Printf("%#v\n", c.App.VisibleCommands()) 1632 fmt.Printf("%#v\n", c.App.VisibleFlags()) 1633 1634 fmt.Printf("%#v\n", c.Args().First()) 1635 if c.Args().Len() > 0 { 1636 fmt.Printf("%#v\n", c.Args().Get(1)) 1637 } 1638 fmt.Printf("%#v\n", c.Args().Present()) 1639 fmt.Printf("%#v\n", c.Args().Tail()) 1640 1641 set := flag.NewFlagSet("contrive", 0) 1642 nc := cli.NewContext(c.App, set, c) 1643 1644 fmt.Printf("%#v\n", nc.Args()) 1645 fmt.Printf("%#v\n", nc.Bool("nope")) 1646 fmt.Printf("%#v\n", !nc.Bool("nerp")) 1647 fmt.Printf("%#v\n", nc.Duration("howlong")) 1648 fmt.Printf("%#v\n", nc.Float64("hay")) 1649 fmt.Printf("%#v\n", nc.Generic("bloop")) 1650 fmt.Printf("%#v\n", nc.Int64("bonk")) 1651 fmt.Printf("%#v\n", nc.Int64Slice("burnks")) 1652 fmt.Printf("%#v\n", nc.Int("bips")) 1653 fmt.Printf("%#v\n", nc.IntSlice("blups")) 1654 fmt.Printf("%#v\n", nc.String("snurt")) 1655 fmt.Printf("%#v\n", nc.StringSlice("snurkles")) 1656 fmt.Printf("%#v\n", nc.Uint("flub")) 1657 fmt.Printf("%#v\n", nc.Uint64("florb")) 1658 1659 fmt.Printf("%#v\n", nc.FlagNames()) 1660 fmt.Printf("%#v\n", nc.IsSet("wat")) 1661 fmt.Printf("%#v\n", nc.Set("wat", "nope")) 1662 fmt.Printf("%#v\n", nc.NArg()) 1663 fmt.Printf("%#v\n", nc.NumFlags()) 1664 fmt.Printf("%#v\n", nc.Lineage()[1]) 1665 nc.Set("wat", "also-nope") 1666 1667 ec := cli.Exit("ohwell", 86) 1668 fmt.Fprintf(c.App.Writer, "%d", ec.ExitCode()) 1669 fmt.Printf("made it!\n") 1670 return ec 1671 }, 1672 Metadata: map[string]interface{}{ 1673 "layers": "many", 1674 "explicable": false, 1675 "whatever-values": 19.99, 1676 }, 1677 } 1678 1679 if os.Getenv("HEXY") != "" { 1680 app.Writer = &hexWriter{} 1681 app.ErrWriter = &hexWriter{} 1682 } 1683 1684 app.Run(os.Args) 1685} 1686 1687func wopAction(c *cli.Context) error { 1688 fmt.Fprintf(c.App.Writer, ":wave: over here, eh\n") 1689 return nil 1690} 1691``` 1692