diff --git a/README.md b/README.md index a38137b2..f887d603 100644 --- a/README.md +++ b/README.md @@ -19,13 +19,34 @@ Many of the most widely used Go projects are built using Cobra including: * [GiantSwarm's swarm](https://github.com/giantswarm/cli) * [Nanobox](https://github.com/nanobox-io/nanobox)/[Nanopack](https://github.com/nanopack) * [rclone](http://rclone.org/) - +* [nehm](https://github.com/bogem/nehm) +* [Pouch](https://github.com/alibaba/pouch) [![Build Status](https://travis-ci.org/spf13/cobra.svg "Travis CI status")](https://travis-ci.org/spf13/cobra) [![CircleCI status](https://circleci.com/gh/spf13/cobra.png?circle-token=:circle-token "CircleCI status")](https://circleci.com/gh/spf13/cobra) [![GoDoc](https://godoc.org/github.com/spf13/cobra?status.svg)](https://godoc.org/github.com/spf13/cobra) -![cobra](https://cloud.githubusercontent.com/assets/173412/10911369/84832a8e-8212-11e5-9f82-cc96660a4794.gif) +# Table of Contents + +- [Overview](#overview) +- [Concepts](#concepts) + * [Commands](#commands) + * [Flags](#flags) +- [Installing](#installing) +- [Getting Started](#getting-started) + * [Using the Cobra Generator](#using-the-cobra-generator) + * [Using the Cobra Library](#using-the-cobra-library) + * [Working with Flags](#working-with-flags) + * [Positional and Custom Arguments](#positional-and-custom-arguments) + * [Example](#example) + * [Help Command](#help-command) + * [Usage Message](#usage-message) + * [PreRun and PostRun Hooks](#prerun-and-postrun-hooks) + * [Suggestions when "unknown command" happens](#suggestions-when-unknown-command-happens) + * [Generating documentation for your command](#generating-documentation-for-your-command) + * [Generating bash completions](#generating-bash-completions) +- [Contributing](#contributing) +- [License](#license) # Overview @@ -43,7 +64,6 @@ Cobra provides: * Easy generation of applications & commands with `cobra init appname` & `cobra add cmdname` * Intelligent suggestions (`app srver`... did you mean `app server`?) * Automatic help generation for commands and flags -* Automatic detailed help for `app help [command]` * Automatic help flag recognition of `-h`, `--help`, etc. * Automatically generated bash autocomplete for your application * Automatically generated man pages for your application @@ -51,16 +71,6 @@ Cobra provides: * The flexibility to define your own help, usage, etc. * Optional tight integration with [viper](http://github.com/spf13/viper) for 12-factor apps -Cobra has an exceptionally clean interface and simple design without needless -constructors or initialization methods. - -Applications built with Cobra commands are designed to be as user-friendly as -possible. Flags can be placed before or after the command (as long as a -confusing space isn’t provided). Both short and long flags can be used. A -command need not even be fully typed. Help is automatically generated and -available for the application or for a specific command using either the help -command or the `--help` flag. - # Concepts Cobra is built on a structure of commands, arguments & flags. @@ -93,20 +103,11 @@ have children commands and optionally run an action. In the example above, 'server' is the command. -A Command has the following structure: - -```go -type Command struct { - Use string // The one-line usage message. - Short string // The short description shown in the 'help' output. - Long string // The long message shown in the 'help ' output. - Run func(cmd *Command, args []string) // Run runs the command. -} -``` +[More about cobra.Command](https://godoc.org/github.com/spf13/cobra#Command) ## Flags -A Flag is a way to modify the behavior of a command. Cobra supports +A flag is a way to modify the behavior of a command. Cobra supports fully POSIX-compliant flags as well as the Go [flag package](https://golang.org/pkg/flag/). A Cobra command can define flags that persist through to children commands and flags that are only available to that command. @@ -117,14 +118,6 @@ Flag functionality is provided by the [pflag library](https://github.com/spf13/pflag), a fork of the flag standard library which maintains the same interface while adding POSIX compliance. -## Usage - -Cobra works by creating a set of commands and then organizing them into a tree. -The tree defines the structure of the application. - -Once each command is defined with its corresponding flags, then the -tree is assigned to the commander which is finally executed. - # Installing Using Cobra is easy. First, use `go get` to install the latest version of the library. This command will install the `cobra` generator executable @@ -159,17 +152,14 @@ In a Cobra app, typically the main.go file is very bare. It serves one purpose: package main import ( - "fmt" - "os" + "fmt" + "os" - "{pathToYourApp}/cmd" + "{pathToYourApp}/cmd" ) func main() { - if err := cmd.RootCmd.Execute(); err != nil { - fmt.Println(err) - os.Exit(1) - } + cmd.Execute() } ``` @@ -178,121 +168,29 @@ func main() { Cobra provides its own program that will create your application and add any commands you want. It's the easiest way to incorporate Cobra into your application. -In order to use the cobra command, compile it using the following command: +[Here](https://github.com/spf13/cobra/blob/master/cobra/README.md) you can find more information about it. - go get github.com/spf13/cobra/cobra +## Using the Cobra Library -This will create the cobra executable under your `$GOPATH/bin` directory. - -### cobra init - -The `cobra init [yourApp]` command will create your initial application code -for you. It is a very powerful application that will populate your program with -the right structure so you can immediately enjoy all the benefits of Cobra. It -will also automatically apply the license you specify to your application. - -Cobra init is pretty smart. You can provide it a full path, or simply a path -similar to what is expected in the import. - -``` -cobra init github.com/spf13/newAppName -``` - -### cobra add - -Once an application is initialized Cobra can create additional commands for you. -Let's say you created an app and you wanted the following commands for it: - -* app serve -* app config -* app config create - -In your project directory (where your main.go file is) you would run the following: - -``` -cobra add serve -cobra add config -cobra add create -p 'configCmd' -``` - -*Note: Use camelCase (not snake_case/snake-case) for command names. -Otherwise, you will encounter errors. -For example, `cobra add add-user` is incorrect, but `cobra add addUser` is valid.* - -Once you have run these three commands you would have an app structure similar to -the following: - -``` - ▾ app/ - ▾ cmd/ - serve.go - config.go - create.go - main.go -``` - -At this point you can run `go run main.go` and it would run your app. `go run -main.go serve`, `go run main.go config`, `go run main.go config create` along -with `go run main.go help serve`, etc. would all work. - -Obviously you haven't added your own code to these yet. The commands are ready -for you to give them their tasks. Have fun! - -### Configuring the cobra generator - -The Cobra generator will be easier to use if you provide a simple configuration -file which will help you eliminate providing a bunch of repeated information in -flags over and over. - -An example ~/.cobra.yaml file: - -```yaml -author: Steve Francia -license: MIT -``` - -You can specify no license by setting `license` to `none` or you can specify -a custom license: - -```yaml -license: - header: This file is part of {{ .appName }}. - text: | - {{ .copyright }} - - This is my license. There are many like it, but this one is mine. - My license is my best friend. It is my life. I must master it as I must - master my life. -``` - -You can also use built-in licenses. For example, **GPLv2**, **GPLv3**, **LGPL**, -**AGPL**, **MIT**, **2-Clause BSD** or **3-Clause BSD**. - -## Manually implementing Cobra - -To manually implement Cobra you need to create a bare main.go file and a RootCmd file. +To manually implement Cobra you need to create a bare main.go file and a rootCmd file. You will optionally provide additional commands as you see fit. -### Create the root command - -The root command represents your binary itself. - -#### Manually create rootCmd +### Create rootCmd Cobra doesn't require any special constructors. Simply create your commands. Ideally you place this in app/cmd/root.go: ```go -var RootCmd = &cobra.Command{ - Use: "hugo", - Short: "Hugo is a very fast static site generator", - Long: `A Fast and Flexible Static Site Generator built with +var rootCmd = &cobra.Command{ + Use: "hugo", + Short: "Hugo is a very fast static site generator", + Long: `A Fast and Flexible Static Site Generator built with love by spf13 and friends in Go. Complete documentation is available at http://hugo.spf13.com`, - Run: func(cmd *cobra.Command, args []string) { - // Do Stuff Here - }, + Run: func(cmd *cobra.Command, args []string) { + // Do Stuff Here + }, } ``` @@ -302,54 +200,50 @@ For example cmd/root.go: ```go import ( - "fmt" - "os" + "fmt" + "os" - homedir "github.com/mitchellh/go-homedir" - "github.com/spf13/cobra" - "github.com/spf13/viper" + homedir "github.com/mitchellh/go-homedir" + "github.com/spf13/cobra" + "github.com/spf13/viper" ) func init() { - cobra.OnInitialize(initConfig) - RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.cobra.yaml)") - RootCmd.PersistentFlags().StringVarP(&projectBase, "projectbase", "b", "", "base project directory eg. github.com/spf13/") - RootCmd.PersistentFlags().StringP("author", "a", "YOUR NAME", "Author name for copyright attribution") - RootCmd.PersistentFlags().StringVarP(&userLicense, "license", "l", "", "Name of license for the project (can provide `licensetext` in config)") - RootCmd.PersistentFlags().Bool("viper", true, "Use Viper for configuration") - viper.BindPFlag("author", RootCmd.PersistentFlags().Lookup("author")) - viper.BindPFlag("projectbase", RootCmd.PersistentFlags().Lookup("projectbase")) - viper.BindPFlag("useViper", RootCmd.PersistentFlags().Lookup("viper")) - viper.SetDefault("author", "NAME HERE ") - viper.SetDefault("license", "apache") -} - -func Execute() { - RootCmd.Execute() + cobra.OnInitialize(initConfig) + rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.cobra.yaml)") + rootCmd.PersistentFlags().StringVarP(&projectBase, "projectbase", "b", "", "base project directory eg. github.com/spf13/") + rootCmd.PersistentFlags().StringP("author", "a", "YOUR NAME", "Author name for copyright attribution") + rootCmd.PersistentFlags().StringVarP(&userLicense, "license", "l", "", "Name of license for the project (can provide `licensetext` in config)") + rootCmd.PersistentFlags().Bool("viper", true, "Use Viper for configuration") + viper.BindPFlag("author", rootCmd.PersistentFlags().Lookup("author")) + viper.BindPFlag("projectbase", rootCmd.PersistentFlags().Lookup("projectbase")) + viper.BindPFlag("useViper", rootCmd.PersistentFlags().Lookup("viper")) + viper.SetDefault("author", "NAME HERE ") + viper.SetDefault("license", "apache") } func initConfig() { // Don't forget to read config either from cfgFile or from home directory! - if cfgFile != "" { - // Use config file from the flag. - viper.SetConfigFile(cfgFile) - } else { - // Find home directory. - home, err := homedir.Dir() - if err != nil { - fmt.Println(err) - os.Exit(1) - } + if cfgFile != "" { + // Use config file from the flag. + viper.SetConfigFile(cfgFile) + } else { + // Find home directory. + home, err := homedir.Dir() + if err != nil { + fmt.Println(err) + os.Exit(1) + } - // Search config in home directory with name ".cobra" (without extension). - viper.AddConfigPath(home) - viper.SetConfigName(".cobra") - } + // Search config in home directory with name ".cobra" (without extension). + viper.AddConfigPath(home) + viper.SetConfigName(".cobra") + } - if err := viper.ReadInConfig(); err != nil { - fmt.Println("Can't read config:", err) - os.Exit(1) - } + if err := viper.ReadInConfig(); err != nil { + fmt.Println("Can't read config:", err) + os.Exit(1) + } } ``` @@ -364,17 +258,14 @@ In a Cobra app, typically the main.go file is very bare. It serves, one purpose, package main import ( - "fmt" - "os" + "fmt" + "os" - "{pathToYourApp}/cmd" + "{pathToYourApp}/cmd" ) func main() { - if err := cmd.RootCmd.Execute(); err != nil { - fmt.Println(err) - os.Exit(1) - } + cmd.Execute() } ``` @@ -390,48 +281,25 @@ populate it with the following: package cmd import ( - "github.com/spf13/cobra" - "fmt" + "fmt" + + "github.com/spf13/cobra" ) func init() { - RootCmd.AddCommand(versionCmd) + rootCmd.AddCommand(versionCmd) } var versionCmd = &cobra.Command{ - Use: "version", - Short: "Print the version number of Hugo", - Long: `All software has versions. This is Hugo's`, - Run: func(cmd *cobra.Command, args []string) { - fmt.Println("Hugo Static Site Generator v0.9 -- HEAD") - }, + Use: "version", + Short: "Print the version number of Hugo", + Long: `All software has versions. This is Hugo's`, + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("Hugo Static Site Generator v0.9 -- HEAD") + }, } ``` -### Attach command to its parent - - -If you notice in the above example we attach the command to its parent. In -this case the parent is the rootCmd. In this example we are attaching it to the -root, but commands can be attached at any level. - -```go -RootCmd.AddCommand(versionCmd) -``` - -### Remove a command from its parent - -Removing a command is not a common action in simple programs, but it allows 3rd -parties to customize an existing command tree. - -In this example, we remove the existing `VersionCmd` command of an existing -root command, and we replace it with our own version: - -```go -mainlib.RootCmd.RemoveCommand(mainlib.VersionCmd) -mainlib.RootCmd.AddCommand(versionCmd) -``` - ## Working with Flags Flags provide modifiers to control how the action command operates. @@ -456,7 +324,7 @@ command it's assigned to as well as every command under that command. For global flags, assign a flag as a persistent flag on the root. ```go -RootCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose output") +rootCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose output") ``` ### Local Flags @@ -464,7 +332,20 @@ RootCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose out A flag can also be assigned locally which will only apply to that specific command. ```go -RootCmd.Flags().StringVarP(&Source, "source", "s", "", "Source directory to read from") +rootCmd.Flags().StringVarP(&Source, "source", "s", "", "Source directory to read from") +``` + +### Local Flag on Parent Commands + +By default Cobra only parses local flags on the target command, any local flags on +parent commands are ignored. By enabling `Command.TraverseChildren` Cobra will +parse local flags on each command before executing the target command. + +```go +command := cobra.Command{ + Use: "print [OPTIONS] [COMMANDS]", + TraverseChildren: true, +} ``` ### Bind Flags with Config @@ -474,8 +355,8 @@ You can also bind your flags with [viper](https://github.com/spf13/viper): var author string func init() { - RootCmd.PersistentFlags().StringVar(&author, "author", "YOUR NAME", "Author name for copyright attribution") - viper.BindPFlag("author", RootCmd.PersistentFlags().Lookup("author")) + rootCmd.PersistentFlags().StringVar(&author, "author", "YOUR NAME", "Author name for copyright attribution") + viper.BindPFlag("author", rootCmd.PersistentFlags().Lookup("author")) } ``` @@ -487,32 +368,37 @@ More in [viper documentation](https://github.com/spf13/viper#working-with-flags) ## Positional and Custom Arguments -Validation of positional arguments can be specified using the `Args` field. +Validation of positional arguments can be specified using the `Args` field +of `Command`. -The follow validators are built in: +The following validators are built in: - `NoArgs` - the command will report an error if there are any positional args. - `ArbitraryArgs` - the command will accept any args. -- `OnlyValidArgs` - the command will report an error if there are any positional args that are not in the ValidArgs list. +- `OnlyValidArgs` - the command will report an error if there are any positional args that are not in the `ValidArgs` field of `Command`. - `MinimumNArgs(int)` - the command will report an error if there are not at least N positional args. - `MaximumNArgs(int)` - the command will report an error if there are more than N positional args. - `ExactArgs(int)` - the command will report an error if there are not exactly N positional args. - `RangeArgs(min, max)` - the command will report an error if the number of args is not between the minimum and maximum number of expected args. -A custom validator can be provided like this: +An example of setting the custom validator: ```go - -Args: func validColorArgs(cmd *cobra.Command, args []string) error { - if err := cli.RequiresMinArgs(1)(cmd, args); err != nil { - return err - } - if myapp.IsValidColor(args[0]) { - return nil - } - return fmt.Errorf("Invalid color specified: %s", args[0]) +var cmd = &cobra.Command{ + Short: "hello", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("requires at least one arg") + } + if myapp.IsValidColor(args[0]) { + return nil + } + return fmt.Errorf("invalid color specified: %s", args[0]) + }, + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("Hello, World!") + }, } - ``` ## Example @@ -530,62 +416,62 @@ More documentation about flags is available at https://github.com/spf13/pflag package main import ( - "fmt" - "strings" + "fmt" + "strings" - "github.com/spf13/cobra" + "github.com/spf13/cobra" ) func main() { + var echoTimes int - var echoTimes int + var cmdPrint = &cobra.Command{ + Use: "print [string to print]", + Short: "Print anything to the screen", + Long: `print is for printing anything back to the screen. +For many years people have printed back to the screen.`, + Args: cobra.MinimumNArgs(1), + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("Print: " + strings.Join(args, " ")) + }, + } - var cmdPrint = &cobra.Command{ - Use: "print [string to print]", - Short: "Print anything to the screen", - Long: `print is for printing anything back to the screen. - For many years people have printed back to the screen. - `, - Run: func(cmd *cobra.Command, args []string) { - fmt.Println("Print: " + strings.Join(args, " ")) - }, - } + var cmdEcho = &cobra.Command{ + Use: "echo [string to echo]", + Short: "Echo anything to the screen", + Long: `echo is for echoing anything back. +Echo works a lot like print, except it has a child command.`, + Args: cobra.MinimumNArgs(1), + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("Print: " + strings.Join(args, " ")) + }, + } - var cmdEcho = &cobra.Command{ - Use: "echo [string to echo]", - Short: "Echo anything to the screen", - Long: `echo is for echoing anything back. - Echo works a lot like print, except it has a child command. - `, - Run: func(cmd *cobra.Command, args []string) { - fmt.Println("Print: " + strings.Join(args, " ")) - }, - } + var cmdTimes = &cobra.Command{ + Use: "times [# times] [string to echo]", + Short: "Echo anything to the screen more times", + Long: `echo things multiple times back to the user by providing +a count and a string.`, + Args: cobra.MinimumNArgs(1), + Run: func(cmd *cobra.Command, args []string) { + for i := 0; i < echoTimes; i++ { + fmt.Println("Echo: " + strings.Join(args, " ")) + } + }, + } - var cmdTimes = &cobra.Command{ - Use: "times [# times] [string to echo]", - Short: "Echo anything to the screen more times", - Long: `echo things multiple times back to the user by providing - a count and a string.`, - Run: func(cmd *cobra.Command, args []string) { - for i := 0; i < echoTimes; i++ { - fmt.Println("Echo: " + strings.Join(args, " ")) - } - }, - } + cmdTimes.Flags().IntVarP(&echoTimes, "times", "t", 1, "times to echo the input") - cmdTimes.Flags().IntVarP(&echoTimes, "times", "t", 1, "times to echo the input") - - var rootCmd = &cobra.Command{Use: "app"} - rootCmd.AddCommand(cmdPrint, cmdEcho) - cmdEcho.AddCommand(cmdTimes) - rootCmd.Execute() + var rootCmd = &cobra.Command{Use: "app"} + rootCmd.AddCommand(cmdPrint, cmdEcho) + cmdEcho.AddCommand(cmdTimes) + rootCmd.Execute() } ``` For a more complete example of a larger application, please checkout [Hugo](http://gohugo.io/). -## The Help Command +## Help Command Cobra automatically adds a help command to your application when you have subcommands. This will be called when a user runs 'app help'. Additionally, help will also @@ -598,60 +484,28 @@ create' is called. Every command will automatically have the '--help' flag adde The following output is automatically generated by Cobra. Nothing beyond the command and flag definitions are needed. - > hugo help + $ cobra help - hugo is the main command, used to build your Hugo site. - - Hugo is a Fast and Flexible Static Site Generator - built with love by spf13 and friends in Go. - - Complete documentation is available at http://gohugo.io/. + Cobra is a CLI library for Go that empowers applications. + This application is a tool to generate the needed files + to quickly create a Cobra application. Usage: - hugo [flags] - hugo [command] + cobra [command] Available Commands: - server Hugo runs its own webserver to render the files - version Print the version number of Hugo - config Print the site configuration - check Check content in the source directory - benchmark Benchmark hugo by building a site a number of times. - convert Convert your content to different formats - new Create new content for your site - list Listing out various types of content - undraft Undraft changes the content's draft status from 'True' to 'False' - genautocomplete Generate shell autocompletion script for Hugo - gendoc Generate Markdown documentation for the Hugo CLI. - genman Generate man page for Hugo - import Import your site from others. + add Add a command to a Cobra Application + help Help about any command + init Initialize a Cobra Application Flags: - -b, --baseURL="": hostname (and path) to the root, e.g. http://spf13.com/ - -D, --buildDrafts[=false]: include content marked as draft - -F, --buildFuture[=false]: include content with publishdate in the future - --cacheDir="": filesystem path to cache directory. Defaults: $TMPDIR/hugo_cache/ - --canonifyURLs[=false]: if true, all relative URLs will be canonicalized using baseURL - --config="": config file (default is path/config.yaml|json|toml) - -d, --destination="": filesystem path to write files to - --disableRSS[=false]: Do not build RSS files - --disableSitemap[=false]: Do not build Sitemap file - --editor="": edit new content with this editor, if provided - --ignoreCache[=false]: Ignores the cache directory for reading but still writes to it - --log[=false]: Enable Logging - --logFile="": Log File path (if set, logging enabled automatically) - --noTimes[=false]: Don't sync modification time of files - --pluralizeListTitles[=true]: Pluralize titles in lists using inflect - --preserveTaxonomyNames[=false]: Preserve taxonomy names as written ("Gérard Depardieu" vs "gerard-depardieu") - -s, --source="": filesystem path to read files relative from - --stepAnalysis[=false]: display memory and timing of different steps of the program - -t, --theme="": theme to use (located in /themes/THEMENAME/) - --uglyURLs[=false]: if true, use /filename.html instead of /filename/ - -v, --verbose[=false]: verbose output - --verboseLog[=false]: verbose logging - -w, --watch[=false]: watch filesystem for changes and recreate as needed + -a, --author string author name for copyright attribution (default "YOUR NAME") + --config string config file (default is $HOME/.cobra.yaml) + -h, --help help for cobra + -l, --license string name of license for the project + --viper use Viper for configuration (default true) - Use "hugo [command] --help" for more information about a command. + Use "cobra [command] --help" for more information about a command. Help is just a command like any other. There is no special logic or behavior @@ -659,38 +513,18 @@ around it. In fact, you can provide your own if you want. ### Defining your own help -You can provide your own Help command or your own template for the default command to use. - -The default help command is +You can provide your own Help command or your own template for the default command to use +with following functions: ```go -func (c *Command) initHelp() { - if c.helpCommand == nil { - c.helpCommand = &Command{ - Use: "help [command]", - Short: "Help about any command", - Long: `Help provides help for any command in the application. - Simply type ` + c.Name() + ` help [path to command] for full details.`, - Run: c.HelpFunc(), - } - } - c.AddCommand(c.helpCommand) -} -``` - -You can provide your own command, function or template through the following methods: - -```go -command.SetHelpCommand(cmd *Command) - -command.SetHelpFunc(f func(*Command, []string)) - -command.SetHelpTemplate(s string) +cmd.SetHelpCommand(cmd *Command) +cmd.SetHelpFunc(f func(*Command, []string)) +cmd.SetHelpTemplate(s string) ``` The latter two will also apply to any children commands. -## Usage +## Usage Message When the user provides an invalid flag or invalid command, Cobra responds by showing the user the 'usage'. @@ -699,71 +533,42 @@ showing the user the 'usage'. You may recognize this from the help above. That's because the default help embeds the usage as part of its output. + $ cobra --invalid + Error: unknown flag: --invalid Usage: - hugo [flags] - hugo [command] + cobra [command] Available Commands: - server Hugo runs its own webserver to render the files - version Print the version number of Hugo - config Print the site configuration - check Check content in the source directory - benchmark Benchmark hugo by building a site a number of times. - convert Convert your content to different formats - new Create new content for your site - list Listing out various types of content - undraft Undraft changes the content's draft status from 'True' to 'False' - genautocomplete Generate shell autocompletion script for Hugo - gendoc Generate Markdown documentation for the Hugo CLI. - genman Generate man page for Hugo - import Import your site from others. + add Add a command to a Cobra Application + help Help about any command + init Initialize a Cobra Application Flags: - -b, --baseURL="": hostname (and path) to the root, e.g. http://spf13.com/ - -D, --buildDrafts[=false]: include content marked as draft - -F, --buildFuture[=false]: include content with publishdate in the future - --cacheDir="": filesystem path to cache directory. Defaults: $TMPDIR/hugo_cache/ - --canonifyURLs[=false]: if true, all relative URLs will be canonicalized using baseURL - --config="": config file (default is path/config.yaml|json|toml) - -d, --destination="": filesystem path to write files to - --disableRSS[=false]: Do not build RSS files - --disableSitemap[=false]: Do not build Sitemap file - --editor="": edit new content with this editor, if provided - --ignoreCache[=false]: Ignores the cache directory for reading but still writes to it - --log[=false]: Enable Logging - --logFile="": Log File path (if set, logging enabled automatically) - --noTimes[=false]: Don't sync modification time of files - --pluralizeListTitles[=true]: Pluralize titles in lists using inflect - --preserveTaxonomyNames[=false]: Preserve taxonomy names as written ("Gérard Depardieu" vs "gerard-depardieu") - -s, --source="": filesystem path to read files relative from - --stepAnalysis[=false]: display memory and timing of different steps of the program - -t, --theme="": theme to use (located in /themes/THEMENAME/) - --uglyURLs[=false]: if true, use /filename.html instead of /filename/ - -v, --verbose[=false]: verbose output - --verboseLog[=false]: verbose logging - -w, --watch[=false]: watch filesystem for changes and recreate as needed + -a, --author string author name for copyright attribution (default "YOUR NAME") + --config string config file (default is $HOME/.cobra.yaml) + -h, --help help for cobra + -l, --license string name of license for the project + --viper use Viper for configuration (default true) + + Use "cobra [command] --help" for more information about a command. ### Defining your own usage You can provide your own usage function or template for Cobra to use. - -The default usage function is: - -```go -return func(c *Command) error { - err := tmpl(c.Out(), c.UsageTemplate(), c) - return err -} -``` - Like help, the function and template are overridable through public methods: ```go -command.SetUsageFunc(f func(*Command) error) - -command.SetUsageTemplate(s string) +cmd.SetUsageFunc(f func(*Command) error) +cmd.SetUsageTemplate(s string) ``` -## PreRun or PostRun Hooks +## Version Flag + +Cobra adds a top-level '--version' flag if the Version field is set on the root command. +Running an application with the '--version' flag will print the version to stdout using +the version template. The template can be customized using the +`cmd.SetVersionTemplate(s string)` function. + +## PreRun and PostRun Hooks It is possible to run functions before or after the main `Run` function of your command. The `PersistentPreRun` and `PreRun` functions will be executed before `Run`. `PersistentPostRun` and `PostRun` will be executed after `Run`. The `Persistent*Run` functions will be inherited by children if they do not declare their own. These functions are run in the following order: @@ -779,105 +584,73 @@ An example of two commands which use all of these features is below. When the s package main import ( - "fmt" + "fmt" - "github.com/spf13/cobra" + "github.com/spf13/cobra" ) func main() { - var rootCmd = &cobra.Command{ - Use: "root [sub]", - Short: "My root command", - PersistentPreRun: func(cmd *cobra.Command, args []string) { - fmt.Printf("Inside rootCmd PersistentPreRun with args: %v\n", args) - }, - PreRun: func(cmd *cobra.Command, args []string) { - fmt.Printf("Inside rootCmd PreRun with args: %v\n", args) - }, - Run: func(cmd *cobra.Command, args []string) { - fmt.Printf("Inside rootCmd Run with args: %v\n", args) - }, - PostRun: func(cmd *cobra.Command, args []string) { - fmt.Printf("Inside rootCmd PostRun with args: %v\n", args) - }, - PersistentPostRun: func(cmd *cobra.Command, args []string) { - fmt.Printf("Inside rootCmd PersistentPostRun with args: %v\n", args) - }, - } + var rootCmd = &cobra.Command{ + Use: "root [sub]", + Short: "My root command", + PersistentPreRun: func(cmd *cobra.Command, args []string) { + fmt.Printf("Inside rootCmd PersistentPreRun with args: %v\n", args) + }, + PreRun: func(cmd *cobra.Command, args []string) { + fmt.Printf("Inside rootCmd PreRun with args: %v\n", args) + }, + Run: func(cmd *cobra.Command, args []string) { + fmt.Printf("Inside rootCmd Run with args: %v\n", args) + }, + PostRun: func(cmd *cobra.Command, args []string) { + fmt.Printf("Inside rootCmd PostRun with args: %v\n", args) + }, + PersistentPostRun: func(cmd *cobra.Command, args []string) { + fmt.Printf("Inside rootCmd PersistentPostRun with args: %v\n", args) + }, + } - var subCmd = &cobra.Command{ - Use: "sub [no options!]", - Short: "My subcommand", - PreRun: func(cmd *cobra.Command, args []string) { - fmt.Printf("Inside subCmd PreRun with args: %v\n", args) - }, - Run: func(cmd *cobra.Command, args []string) { - fmt.Printf("Inside subCmd Run with args: %v\n", args) - }, - PostRun: func(cmd *cobra.Command, args []string) { - fmt.Printf("Inside subCmd PostRun with args: %v\n", args) - }, - PersistentPostRun: func(cmd *cobra.Command, args []string) { - fmt.Printf("Inside subCmd PersistentPostRun with args: %v\n", args) - }, - } + var subCmd = &cobra.Command{ + Use: "sub [no options!]", + Short: "My subcommand", + PreRun: func(cmd *cobra.Command, args []string) { + fmt.Printf("Inside subCmd PreRun with args: %v\n", args) + }, + Run: func(cmd *cobra.Command, args []string) { + fmt.Printf("Inside subCmd Run with args: %v\n", args) + }, + PostRun: func(cmd *cobra.Command, args []string) { + fmt.Printf("Inside subCmd PostRun with args: %v\n", args) + }, + PersistentPostRun: func(cmd *cobra.Command, args []string) { + fmt.Printf("Inside subCmd PersistentPostRun with args: %v\n", args) + }, + } - rootCmd.AddCommand(subCmd) + rootCmd.AddCommand(subCmd) - rootCmd.SetArgs([]string{""}) - _ = rootCmd.Execute() - fmt.Print("\n") - rootCmd.SetArgs([]string{"sub", "arg1", "arg2"}) - _ = rootCmd.Execute() + rootCmd.SetArgs([]string{""}) + rootCmd.Execute() + fmt.Println() + rootCmd.SetArgs([]string{"sub", "arg1", "arg2"}) + rootCmd.Execute() } ``` +Output: +``` +Inside rootCmd PersistentPreRun with args: [] +Inside rootCmd PreRun with args: [] +Inside rootCmd Run with args: [] +Inside rootCmd PostRun with args: [] +Inside rootCmd PersistentPostRun with args: [] -## Alternative Error Handling - -Cobra also has functions where the return signature is an error. This allows for errors to bubble up to the top, -providing a way to handle the errors in one location. The current list of functions that return an error is: - -* PersistentPreRunE -* PreRunE -* RunE -* PostRunE -* PersistentPostRunE - -If you would like to silence the default `error` and `usage` output in favor of your own, you can set `SilenceUsage` -and `SilenceErrors` to `true` on the command. A child command respects these flags if they are set on the parent -command. - -**Example Usage using RunE:** - -```go -package main - -import ( - "errors" - "log" - - "github.com/spf13/cobra" -) - -func main() { - var rootCmd = &cobra.Command{ - Use: "hugo", - Short: "Hugo is a very fast static site generator", - Long: `A Fast and Flexible Static Site Generator built with - love by spf13 and friends in Go. - Complete documentation is available at http://hugo.spf13.com`, - RunE: func(cmd *cobra.Command, args []string) error { - // Do Stuff Here - return errors.New("some random error") - }, - } - - if err := rootCmd.Execute(); err != nil { - log.Fatal(err) - } -} +Inside rootCmd PersistentPreRun with args: [arg1 arg2] +Inside subCmd PreRun with args: [arg1 arg2] +Inside subCmd Run with args: [arg1 arg2] +Inside subCmd PostRun with args: [arg1 arg2] +Inside subCmd PersistentPostRun with args: [arg1 arg2] ``` ## Suggestions when "unknown command" happens @@ -920,51 +693,28 @@ Did you mean this? Run 'kubectl help' for usage. ``` -## Generating Markdown-formatted documentation for your command +## Generating documentation for your command -Cobra can generate a Markdown-formatted document based on the subcommands, flags, etc. A simple example of how to do this for your command can be found in [Markdown Docs](doc/md_docs.md). +Cobra can generate documentation based on subcommands, flags, etc. in the following formats: -## Generating man pages for your command +- [Markdown](doc/md_docs.md) +- [ReStructured Text](doc/rest_docs.md) +- [Man Page](doc/man_docs.md) -Cobra can generate a man page based on the subcommands, flags, etc. A simple example of how to do this for your command can be found in [Man Docs](doc/man_docs.md). - -## Generating bash completions for your command +## Generating bash completions Cobra can generate a bash-completion file. If you add more information to your command, these completions can be amazingly powerful and flexible. Read more about it in [Bash Completions](bash_completions.md). -## Debugging - -Cobra provides a ‘DebugFlags’ method on a command which, when called, will print -out everything Cobra knows about the flags for each command. - -### Example - -```go -command.DebugFlags() -``` - -## Extensions - -Libraries for extending Cobra: - -* [cmdns](https://github.com/gosuri/cmdns): Enables name spacing a command's immediate children. It provides an alternative way to structure subcommands, similar to `heroku apps:create` and `ovrclk clusters:launch`. - -## Contributing +# Contributing 1. Fork it -2. Create your feature branch (`git checkout -b my-new-feature`) -3. Commit your changes (`git commit -am 'Add some feature'`) -4. Push to the branch (`git push origin my-new-feature`) -5. Create new Pull Request +2. Download your fork to your PC (`git clone https://github.com/your_username/cobra && cd cobra`) +3. Create your feature branch (`git checkout -b my-new-feature`) +4. Make changes and add them (`git add .`) +5. Commit your changes (`git commit -m 'Add some feature'`) +6. Push to the branch (`git push origin my-new-feature`) +7. Create new pull request -## Contributors - -Names in no particular order: - -* [spf13](https://github.com/spf13), -[eparis](https://github.com/eparis), -[bep](https://github.com/bep), and many more! - -## License +# License Cobra is released under the Apache 2.0 license. See [LICENSE.txt](https://github.com/spf13/cobra/blob/master/LICENSE.txt) diff --git a/args.go b/args.go index 94a6ca27..a5d8a927 100644 --- a/args.go +++ b/args.go @@ -16,14 +16,14 @@ func legacyArgs(cmd *Command, args []string) error { return nil } - // root command with subcommands, do subcommand checking + // root command with subcommands, do subcommand checking. if !cmd.HasParent() && len(args) > 0 { return fmt.Errorf("unknown command %q for %q%s", args[0], cmd.CommandPath(), cmd.findSuggestions(args[0])) } return nil } -// NoArgs returns an error if any args are included +// NoArgs returns an error if any args are included. func NoArgs(cmd *Command, args []string) error { if len(args) > 0 { return fmt.Errorf("unknown command %q for %q", args[0], cmd.CommandPath()) @@ -31,7 +31,7 @@ func NoArgs(cmd *Command, args []string) error { return nil } -// OnlyValidArgs returns an error if any args are not in the list of ValidArgs +// OnlyValidArgs returns an error if any args are not in the list of ValidArgs. func OnlyValidArgs(cmd *Command, args []string) error { if len(cmd.ValidArgs) > 0 { for _, v := range args { @@ -43,21 +43,12 @@ func OnlyValidArgs(cmd *Command, args []string) error { return nil } -func stringInSlice(a string, list []string) bool { - for _, b := range list { - if b == a { - return true - } - } - return false -} - -// ArbitraryArgs never returns an error +// ArbitraryArgs never returns an error. func ArbitraryArgs(cmd *Command, args []string) error { return nil } -// MinimumNArgs returns an error if there is not at least N args +// MinimumNArgs returns an error if there is not at least N args. func MinimumNArgs(n int) PositionalArgs { return func(cmd *Command, args []string) error { if len(args) < n { @@ -67,7 +58,7 @@ func MinimumNArgs(n int) PositionalArgs { } } -// MaximumNArgs returns an error if there are more than N args +// MaximumNArgs returns an error if there are more than N args. func MaximumNArgs(n int) PositionalArgs { return func(cmd *Command, args []string) error { if len(args) > n { @@ -77,7 +68,7 @@ func MaximumNArgs(n int) PositionalArgs { } } -// ExactArgs returns an error if there are not exactly n args +// ExactArgs returns an error if there are not exactly n args. func ExactArgs(n int) PositionalArgs { return func(cmd *Command, args []string) error { if len(args) != n { @@ -87,7 +78,7 @@ func ExactArgs(n int) PositionalArgs { } } -// RangeArgs returns an error if the number of args is not within the expected range +// RangeArgs returns an error if the number of args is not within the expected range. func RangeArgs(min int, max int) PositionalArgs { return func(cmd *Command, args []string) error { if len(args) < min || len(args) > max { diff --git a/args_test.go b/args_test.go new file mode 100644 index 00000000..d797b6f5 --- /dev/null +++ b/args_test.go @@ -0,0 +1,241 @@ +package cobra + +import ( + "strings" + "testing" +) + +func TestNoArgs(t *testing.T) { + c := &Command{Use: "c", Args: NoArgs, Run: emptyRun} + + output, err := executeCommand(c) + if output != "" { + t.Errorf("Unexpected string: %v", output) + } + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } +} + +func TestNoArgsWithArgs(t *testing.T) { + c := &Command{Use: "c", Args: NoArgs, Run: emptyRun} + + _, err := executeCommand(c, "illegal") + if err == nil { + t.Fatal("Expected an error") + } + + got := err.Error() + expected := `unknown command "illegal" for "c"` + if got != expected { + t.Errorf("Expected: %q, got: %q", expected, got) + } +} + +func TestOnlyValidArgs(t *testing.T) { + c := &Command{ + Use: "c", + Args: OnlyValidArgs, + ValidArgs: []string{"one", "two"}, + Run: emptyRun, + } + + output, err := executeCommand(c, "one", "two") + if output != "" { + t.Errorf("Unexpected output: %v", output) + } + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } +} + +func TestOnlyValidArgsWithInvalidArgs(t *testing.T) { + c := &Command{ + Use: "c", + Args: OnlyValidArgs, + ValidArgs: []string{"one", "two"}, + Run: emptyRun, + } + + _, err := executeCommand(c, "three") + if err == nil { + t.Fatal("Expected an error") + } + + got := err.Error() + expected := `invalid argument "three" for "c"` + if got != expected { + t.Errorf("Expected: %q, got: %q", expected, got) + } +} + +func TestArbitraryArgs(t *testing.T) { + c := &Command{Use: "c", Args: ArbitraryArgs, Run: emptyRun} + output, err := executeCommand(c, "a", "b") + if output != "" { + t.Errorf("Unexpected output: %v", output) + } + if err != nil { + t.Errorf("Unexpected error: %v", err) + } +} + +func TestMinimumNArgs(t *testing.T) { + c := &Command{Use: "c", Args: MinimumNArgs(2), Run: emptyRun} + output, err := executeCommand(c, "a", "b", "c") + if output != "" { + t.Errorf("Unexpected output: %v", output) + } + if err != nil { + t.Errorf("Unexpected error: %v", err) + } +} + +func TestMinimumNArgsWithLessArgs(t *testing.T) { + c := &Command{Use: "c", Args: MinimumNArgs(2), Run: emptyRun} + _, err := executeCommand(c, "a") + + if err == nil { + t.Fatal("Expected an error") + } + + got := err.Error() + expected := "requires at least 2 arg(s), only received 1" + if got != expected { + t.Fatalf("Expected %q, got %q", expected, got) + } +} + +func TestMaximumNArgs(t *testing.T) { + c := &Command{Use: "c", Args: MaximumNArgs(3), Run: emptyRun} + output, err := executeCommand(c, "a", "b") + if output != "" { + t.Errorf("Unexpected output: %v", output) + } + if err != nil { + t.Errorf("Unexpected error: %v", err) + } +} + +func TestMaximumNArgsWithMoreArgs(t *testing.T) { + c := &Command{Use: "c", Args: MaximumNArgs(2), Run: emptyRun} + _, err := executeCommand(c, "a", "b", "c") + + if err == nil { + t.Fatal("Expected an error") + } + + got := err.Error() + expected := "accepts at most 2 arg(s), received 3" + if got != expected { + t.Fatalf("Expected %q, got %q", expected, got) + } +} + +func TestExactArgs(t *testing.T) { + c := &Command{Use: "c", Args: ExactArgs(3), Run: emptyRun} + output, err := executeCommand(c, "a", "b", "c") + if output != "" { + t.Errorf("Unexpected output: %v", output) + } + if err != nil { + t.Errorf("Unexpected error: %v", err) + } +} + +func TestExactArgsWithInvalidCount(t *testing.T) { + c := &Command{Use: "c", Args: ExactArgs(2), Run: emptyRun} + _, err := executeCommand(c, "a", "b", "c") + + if err == nil { + t.Fatal("Expected an error") + } + + got := err.Error() + expected := "accepts 2 arg(s), received 3" + if got != expected { + t.Fatalf("Expected %q, got %q", expected, got) + } +} + +func TestRangeArgs(t *testing.T) { + c := &Command{Use: "c", Args: RangeArgs(2, 4), Run: emptyRun} + output, err := executeCommand(c, "a", "b", "c") + if output != "" { + t.Errorf("Unexpected output: %v", output) + } + if err != nil { + t.Errorf("Unexpected error: %v", err) + } +} + +func TestRangeArgsWithInvalidCount(t *testing.T) { + c := &Command{Use: "c", Args: RangeArgs(2, 4), Run: emptyRun} + _, err := executeCommand(c, "a") + + if err == nil { + t.Fatal("Expected an error") + } + + got := err.Error() + expected := "accepts between 2 and 4 arg(s), received 1" + if got != expected { + t.Fatalf("Expected %q, got %q", expected, got) + } +} + +func TestRootTakesNoArgs(t *testing.T) { + rootCmd := &Command{Use: "root", Run: emptyRun} + childCmd := &Command{Use: "child", Run: emptyRun} + rootCmd.AddCommand(childCmd) + + _, err := executeCommand(rootCmd, "illegal", "args") + if err == nil { + t.Fatal("Expected an error") + } + + got := err.Error() + expected := `unknown command "illegal" for "root"` + if !strings.Contains(got, expected) { + t.Errorf("expected %q, got %q", expected, got) + } +} + +func TestRootTakesArgs(t *testing.T) { + rootCmd := &Command{Use: "root", Args: ArbitraryArgs, Run: emptyRun} + childCmd := &Command{Use: "child", Run: emptyRun} + rootCmd.AddCommand(childCmd) + + _, err := executeCommand(rootCmd, "legal", "args") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } +} + +func TestChildTakesNoArgs(t *testing.T) { + rootCmd := &Command{Use: "root", Run: emptyRun} + childCmd := &Command{Use: "child", Args: NoArgs, Run: emptyRun} + rootCmd.AddCommand(childCmd) + + _, err := executeCommand(rootCmd, "child", "illegal", "args") + if err == nil { + t.Fatal("Expected an error") + } + + got := err.Error() + expected := `unknown command "illegal" for "root child"` + if !strings.Contains(got, expected) { + t.Errorf("expected %q, got %q", expected, got) + } +} + +func TestChildTakesArgs(t *testing.T) { + rootCmd := &Command{Use: "root", Run: emptyRun} + childCmd := &Command{Use: "child", Args: ArbitraryArgs, Run: emptyRun} + rootCmd.AddCommand(childCmd) + + _, err := executeCommand(rootCmd, "child", "legal", "args") + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } +} diff --git a/bash_completions.go b/bash_completions.go index e0cfb349..c19fe7a0 100644 --- a/bash_completions.go +++ b/bash_completions.go @@ -92,7 +92,7 @@ __handle_reply() cur="${cur#*=}" ${flags_completion[${index}]} if [ -n "${ZSH_VERSION}" ]; then - # zfs completion needs --flag= prefix + # zsh completion needs --flag= prefix eval "COMPREPLY=( \"\${COMPREPLY[@]/#/${flag}=}\" )" fi fi @@ -463,14 +463,14 @@ func gen(buf *bytes.Buffer, cmd *Command) { } // GenBashCompletion generates bash completion file and writes to the passed writer. -func (cmd *Command) GenBashCompletion(w io.Writer) error { +func (c *Command) GenBashCompletion(w io.Writer) error { buf := new(bytes.Buffer) - writePreamble(buf, cmd.Name()) - if len(cmd.BashCompletionFunction) > 0 { - buf.WriteString(cmd.BashCompletionFunction + "\n") + writePreamble(buf, c.Name()) + if len(c.BashCompletionFunction) > 0 { + buf.WriteString(c.BashCompletionFunction + "\n") } - gen(buf, cmd) - writePostscript(buf, cmd.Name()) + gen(buf, c) + writePostscript(buf, c.Name()) _, err := buf.WriteTo(w) return err @@ -481,24 +481,24 @@ func nonCompletableFlag(flag *pflag.Flag) bool { } // GenBashCompletionFile generates bash completion file. -func (cmd *Command) GenBashCompletionFile(filename string) error { +func (c *Command) GenBashCompletionFile(filename string) error { outFile, err := os.Create(filename) if err != nil { return err } defer outFile.Close() - return cmd.GenBashCompletion(outFile) + return c.GenBashCompletion(outFile) } // MarkFlagRequired adds the BashCompOneRequiredFlag annotation to the named flag, if it exists. -func (cmd *Command) MarkFlagRequired(name string) error { - return MarkFlagRequired(cmd.Flags(), name) +func (c *Command) MarkFlagRequired(name string) error { + return MarkFlagRequired(c.Flags(), name) } // MarkPersistentFlagRequired adds the BashCompOneRequiredFlag annotation to the named persistent flag, if it exists. -func (cmd *Command) MarkPersistentFlagRequired(name string) error { - return MarkFlagRequired(cmd.PersistentFlags(), name) +func (c *Command) MarkPersistentFlagRequired(name string) error { + return MarkFlagRequired(c.PersistentFlags(), name) } // MarkFlagRequired adds the BashCompOneRequiredFlag annotation to the named flag in the flag set, if it exists. @@ -508,20 +508,20 @@ func MarkFlagRequired(flags *pflag.FlagSet, name string) error { // MarkFlagFilename adds the BashCompFilenameExt annotation to the named flag, if it exists. // Generated bash autocompletion will select filenames for the flag, limiting to named extensions if provided. -func (cmd *Command) MarkFlagFilename(name string, extensions ...string) error { - return MarkFlagFilename(cmd.Flags(), name, extensions...) +func (c *Command) MarkFlagFilename(name string, extensions ...string) error { + return MarkFlagFilename(c.Flags(), name, extensions...) } // MarkFlagCustom adds the BashCompCustom annotation to the named flag, if it exists. // Generated bash autocompletion will call the bash function f for the flag. -func (cmd *Command) MarkFlagCustom(name string, f string) error { - return MarkFlagCustom(cmd.Flags(), name, f) +func (c *Command) MarkFlagCustom(name string, f string) error { + return MarkFlagCustom(c.Flags(), name, f) } // MarkPersistentFlagFilename adds the BashCompFilenameExt annotation to the named persistent flag, if it exists. // Generated bash autocompletion will select filenames for the flag, limiting to named extensions if provided. -func (cmd *Command) MarkPersistentFlagFilename(name string, extensions ...string) error { - return MarkFlagFilename(cmd.PersistentFlags(), name, extensions...) +func (c *Command) MarkPersistentFlagFilename(name string, extensions ...string) error { + return MarkFlagFilename(c.PersistentFlags(), name, extensions...) } // MarkFlagFilename adds the BashCompFilenameExt annotation to the named flag in the flag set, if it exists. diff --git a/bash_completions_test.go b/bash_completions_test.go index 071a6a2a..a0da8714 100644 --- a/bash_completions_test.go +++ b/bash_completions_test.go @@ -10,13 +10,13 @@ import ( func checkOmit(t *testing.T, found, unexpected string) { if strings.Contains(found, unexpected) { - t.Errorf("Unexpected response.\nGot: %q\nBut should not have!\n", unexpected) + t.Errorf("Got: %q\nBut should not have!\n", unexpected) } } func check(t *testing.T, found, expected string) { if !strings.Contains(found, expected) { - t.Errorf("Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", expected, found) + t.Errorf("Expecting to contain: \n %q\nGot:\n %q\n", expected, found) } } @@ -33,162 +33,164 @@ func runShellCheck(s string) error { return err } go func() { - defer stdin.Close() stdin.Write([]byte(s)) + stdin.Close() }() return cmd.Run() } // World worst custom function, just keep telling you to enter hello! -const ( - bashCompletionFunc = `__custom_func() { -COMPREPLY=( "hello" ) +const bashCompletionFunc = `__custom_func() { + COMPREPLY=( "hello" ) } ` -) func TestBashCompletions(t *testing.T) { - c := initializeWithRootCmd() - cmdEcho.AddCommand(cmdTimes) - c.AddCommand(cmdEcho, cmdPrint, cmdDeprecated, cmdColon) + rootCmd := &Command{ + Use: "root", + ArgAliases: []string{"pods", "nodes", "services", "replicationcontrollers", "po", "no", "svc", "rc"}, + ValidArgs: []string{"pod", "node", "service", "replicationcontroller"}, + BashCompletionFunction: bashCompletionFunc, + Run: emptyRun, + } + rootCmd.Flags().IntP("introot", "i", -1, "help message for flag introot") + rootCmd.MarkFlagRequired("introot") - // custom completion function - c.BashCompletionFunction = bashCompletionFunc + // Filename. + rootCmd.Flags().String("filename", "", "Enter a filename") + rootCmd.MarkFlagFilename("filename", "json", "yaml", "yml") - // required flag - c.MarkFlagRequired("introot") + // Persistent filename. + rootCmd.PersistentFlags().String("persistent-filename", "", "Enter a filename") + rootCmd.MarkPersistentFlagFilename("persistent-filename") + rootCmd.MarkPersistentFlagRequired("persistent-filename") - // valid nouns - validArgs := []string{"pod", "node", "service", "replicationcontroller"} - c.ValidArgs = validArgs + // Filename extensions. + rootCmd.Flags().String("filename-ext", "", "Enter a filename (extension limited)") + rootCmd.MarkFlagFilename("filename-ext") + rootCmd.Flags().String("custom", "", "Enter a filename (extension limited)") + rootCmd.MarkFlagCustom("custom", "__complete_custom") - // noun aliases - argAliases := []string{"pods", "nodes", "services", "replicationcontrollers", "po", "no", "svc", "rc"} - c.ArgAliases = argAliases + // Subdirectories in a given directory. + rootCmd.Flags().String("theme", "", "theme to use (located in /themes/THEMENAME/)") + rootCmd.Flags().SetAnnotation("theme", BashCompSubdirsInDir, []string{"themes"}) - // filename - var flagval string - c.Flags().StringVar(&flagval, "filename", "", "Enter a filename") - c.MarkFlagFilename("filename", "json", "yaml", "yml") + echoCmd := &Command{ + Use: "echo [string to echo]", + Aliases: []string{"say"}, + Short: "Echo anything to the screen", + Long: "an utterly useless command for testing.", + Example: "Just run cobra-test echo", + Run: emptyRun, + } - // persistent filename - var flagvalPersistent string - c.PersistentFlags().StringVar(&flagvalPersistent, "persistent-filename", "", "Enter a filename") - c.MarkPersistentFlagFilename("persistent-filename") - c.MarkPersistentFlagRequired("persistent-filename") + printCmd := &Command{ + Use: "print [string to print]", + Args: MinimumNArgs(1), + Short: "Print anything to the screen", + Long: "an absolutely utterly useless command for testing.", + Run: emptyRun, + } - // filename extensions - var flagvalExt string - c.Flags().StringVar(&flagvalExt, "filename-ext", "", "Enter a filename (extension limited)") - c.MarkFlagFilename("filename-ext") + deprecatedCmd := &Command{ + Use: "deprecated [can't do anything here]", + Args: NoArgs, + Short: "A command which is deprecated", + Long: "an absolutely utterly useless command for testing deprecation!.", + Deprecated: "Please use echo instead", + Run: emptyRun, + } - // filename extensions - var flagvalCustom string - c.Flags().StringVar(&flagvalCustom, "custom", "", "Enter a filename (extension limited)") - c.MarkFlagCustom("custom", "__complete_custom") + colonCmd := &Command{ + Use: "cmd:colon", + Run: emptyRun, + } - // subdirectories in a given directory - var flagvalTheme string - c.Flags().StringVar(&flagvalTheme, "theme", "", "theme to use (located in /themes/THEMENAME/)") - c.Flags().SetAnnotation("theme", BashCompSubdirsInDir, []string{"themes"}) + timesCmd := &Command{ + Use: "times [# times] [string to echo]", + SuggestFor: []string{"counts"}, + Args: OnlyValidArgs, + ValidArgs: []string{"one", "two", "three", "four"}, + Short: "Echo anything to the screen more times", + Long: "a slightly useless command for testing.", + Run: emptyRun, + } - out := new(bytes.Buffer) - c.GenBashCompletion(out) - str := out.String() + echoCmd.AddCommand(timesCmd) + rootCmd.AddCommand(echoCmd, printCmd, deprecatedCmd, colonCmd) - check(t, str, "_cobra-test") - check(t, str, "_cobra-test_echo") - check(t, str, "_cobra-test_echo_times") - check(t, str, "_cobra-test_print") - check(t, str, "_cobra-test_cmd__colon") + buf := new(bytes.Buffer) + rootCmd.GenBashCompletion(buf) + output := buf.String() + + check(t, output, "_root") + check(t, output, "_root_echo") + check(t, output, "_root_echo_times") + check(t, output, "_root_print") + check(t, output, "_root_cmd__colon") // check for required flags - check(t, str, `must_have_one_flag+=("--introot=")`) - check(t, str, `must_have_one_flag+=("--persistent-filename=")`) + check(t, output, `must_have_one_flag+=("--introot=")`) + check(t, output, `must_have_one_flag+=("--persistent-filename=")`) // check for custom completion function - check(t, str, `COMPREPLY=( "hello" )`) + check(t, output, `COMPREPLY=( "hello" )`) // check for required nouns - check(t, str, `must_have_one_noun+=("pod")`) + check(t, output, `must_have_one_noun+=("pod")`) // check for noun aliases - check(t, str, `noun_aliases+=("pods")`) - check(t, str, `noun_aliases+=("rc")`) - checkOmit(t, str, `must_have_one_noun+=("pods")`) + check(t, output, `noun_aliases+=("pods")`) + check(t, output, `noun_aliases+=("rc")`) + checkOmit(t, output, `must_have_one_noun+=("pods")`) // check for filename extension flags - check(t, str, `flags_completion+=("_filedir")`) + check(t, output, `flags_completion+=("_filedir")`) // check for filename extension flags - check(t, str, `must_have_one_noun+=("three")`) - // check for filename extention flags - check(t, str, `flags_completion+=("__handle_filename_extension_flag json|yaml|yml")`) + check(t, output, `must_have_one_noun+=("three")`) + // check for filename extension flags + check(t, output, `flags_completion+=("__handle_filename_extension_flag json|yaml|yml")`) // check for custom flags - check(t, str, `flags_completion+=("__complete_custom")`) + check(t, output, `flags_completion+=("__complete_custom")`) // check for subdirs_in_dir flags - check(t, str, `flags_completion+=("__handle_subdirs_in_dir_flag themes")`) + check(t, output, `flags_completion+=("__handle_subdirs_in_dir_flag themes")`) - checkOmit(t, str, cmdDeprecated.Name()) + checkOmit(t, output, deprecatedCmd.Name()) - // if available, run shellcheck against the script + // If available, run shellcheck against the script. if err := exec.Command("which", "shellcheck").Run(); err != nil { return } - err := runShellCheck(str) - if err != nil { + if err := runShellCheck(output); err != nil { t.Fatalf("shellcheck failed: %v", err) } } func TestBashCompletionHiddenFlag(t *testing.T) { - var cmdTrue = &Command{ - Use: "does nothing", - Run: func(cmd *Command, args []string) {}, - } + c := &Command{Use: "c", Run: emptyRun} - const flagName = "hidden-foo-bar-baz" + const flagName = "hiddenFlag" + c.Flags().Bool(flagName, false, "") + c.Flags().MarkHidden(flagName) - var flagValue bool - cmdTrue.Flags().BoolVar(&flagValue, flagName, false, "hidden flag") - cmdTrue.Flags().MarkHidden(flagName) + buf := new(bytes.Buffer) + c.GenBashCompletion(buf) + output := buf.String() - out := new(bytes.Buffer) - cmdTrue.GenBashCompletion(out) - bashCompletion := out.String() - if strings.Contains(bashCompletion, flagName) { - t.Errorf("expected completion to not include %q flag: Got %v", flagName, bashCompletion) + if strings.Contains(output, flagName) { + t.Errorf("Expected completion to not include %q flag: Got %v", flagName, output) } } func TestBashCompletionDeprecatedFlag(t *testing.T) { - var cmdTrue = &Command{ - Use: "does nothing", - Run: func(cmd *Command, args []string) {}, - } + c := &Command{Use: "c", Run: emptyRun} - const flagName = "deprecated-foo-bar-baz" - - var flagValue bool - cmdTrue.Flags().BoolVar(&flagValue, flagName, false, "hidden flag") - cmdTrue.Flags().MarkDeprecated(flagName, "use --does-not-exist instead") - - out := new(bytes.Buffer) - cmdTrue.GenBashCompletion(out) - bashCompletion := out.String() - if strings.Contains(bashCompletion, flagName) { - t.Errorf("expected completion to not include %q flag: Got %v", flagName, bashCompletion) - } -} - -func BenchmarkBashCompletion(b *testing.B) { - c := initializeWithRootCmd() - cmdEcho.AddCommand(cmdTimes) - c.AddCommand(cmdEcho, cmdPrint, cmdDeprecated, cmdColon) + const flagName = "deprecated-flag" + c.Flags().Bool(flagName, false, "") + c.Flags().MarkDeprecated(flagName, "use --not-deprecated instead") buf := new(bytes.Buffer) + c.GenBashCompletion(buf) + output := buf.String() - b.ResetTimer() - for i := 0; i < b.N; i++ { - buf.Reset() - if err := c.GenBashCompletion(buf); err != nil { - b.Fatal(err) - } + if strings.Contains(output, flagName) { + t.Errorf("expected completion to not include %q flag: Got %v", flagName, output) } } diff --git a/cobra.go b/cobra.go index 8928cefc..7010fd15 100644 --- a/cobra.go +++ b/cobra.go @@ -70,7 +70,8 @@ func AddTemplateFuncs(tmplFuncs template.FuncMap) { } } -// OnInitialize takes a series of func() arguments and appends them to a slice of func(). +// OnInitialize sets the passed functions to be run when each command's +// Execute method is called. func OnInitialize(y ...func()) { initializers = append(initializers, y...) } @@ -188,3 +189,12 @@ func ld(s, t string, ignoreCase bool) int { } return d[len(s)][len(t)] } + +func stringInSlice(a string, list []string) bool { + for _, b := range list { + if b == a { + return true + } + } + return false +} diff --git a/cobra/README.md b/cobra/README.md new file mode 100644 index 00000000..6054f95c --- /dev/null +++ b/cobra/README.md @@ -0,0 +1,94 @@ +# Cobra Generator + +Cobra provides its own program that will create your application and add any +commands you want. It's the easiest way to incorporate Cobra into your application. + +In order to use the cobra command, compile it using the following command: + + go get github.com/spf13/cobra/cobra + +This will create the cobra executable under your `$GOPATH/bin` directory. + +### cobra init + +The `cobra init [app]` command will create your initial application code +for you. It is a very powerful application that will populate your program with +the right structure so you can immediately enjoy all the benefits of Cobra. It +will also automatically apply the license you specify to your application. + +Cobra init is pretty smart. You can provide it a full path, or simply a path +similar to what is expected in the import. + +``` +cobra init github.com/spf13/newApp +``` + +### cobra add + +Once an application is initialized, Cobra can create additional commands for you. +Let's say you created an app and you wanted the following commands for it: + +* app serve +* app config +* app config create + +In your project directory (where your main.go file is) you would run the following: + +``` +cobra add serve +cobra add config +cobra add create -p 'configCmd' +``` + +*Note: Use camelCase (not snake_case/snake-case) for command names. +Otherwise, you will encounter errors. +For example, `cobra add add-user` is incorrect, but `cobra add addUser` is valid.* + +Once you have run these three commands you would have an app structure similar to +the following: + +``` + ▾ app/ + ▾ cmd/ + serve.go + config.go + create.go + main.go +``` + +At this point you can run `go run main.go` and it would run your app. `go run +main.go serve`, `go run main.go config`, `go run main.go config create` along +with `go run main.go help serve`, etc. would all work. + +Obviously you haven't added your own code to these yet. The commands are ready +for you to give them their tasks. Have fun! + +### Configuring the cobra generator + +The Cobra generator will be easier to use if you provide a simple configuration +file which will help you eliminate providing a bunch of repeated information in +flags over and over. + +An example ~/.cobra.yaml file: + +```yaml +author: Steve Francia +license: MIT +``` + +You can specify no license by setting `license` to `none` or you can specify +a custom license: + +```yaml +license: + header: This file is part of {{ .appName }}. + text: | + {{ .copyright }} + + This is my license. There are many like it, but this one is mine. + My license is my best friend. It is my life. I must master it as I must + master my life. +``` + +You can also use built-in licenses. For example, **GPLv2**, **GPLv3**, **LGPL**, +**AGPL**, **MIT**, **2-Clause BSD** or **3-Clause BSD**. diff --git a/cobra/cmd/add.go b/cobra/cmd/add.go index 30f83662..fb22096a 100644 --- a/cobra/cmd/add.go +++ b/cobra/cmd/add.go @@ -24,7 +24,7 @@ import ( func init() { addCmd.Flags().StringVarP(&packageName, "package", "t", "", "target package name (e.g. github.com/spf13/hugo)") - addCmd.Flags().StringVarP(&parentName, "parent", "p", "RootCmd", "name of parent command for this command") + addCmd.Flags().StringVarP(&parentName, "parent", "p", "rootCmd", "variable name of parent command for this command") } var packageName, parentName string @@ -35,7 +35,7 @@ var addCmd = &cobra.Command{ Short: "Add a command to a Cobra Application", Long: `Add (cobra add) will create a new command, with a license and the appropriate structure for a Cobra-based CLI application, -and register it to its parent (default RootCmd). +and register it to its parent (default rootCmd). If you want your command to be public, pass in the command name with an initial uppercase letter. diff --git a/cobra/cmd/add_test.go b/cobra/cmd/add_test.go index 0c46bc58..b920e2b9 100644 --- a/cobra/cmd/add_test.go +++ b/cobra/cmd/add_test.go @@ -18,9 +18,6 @@ import ( func TestGoldenAddCmd(t *testing.T) { projectName := "github.com/spf13/testproject" project := NewProject(projectName) - - // Initialize the project at first. - initializeProject(project) defer os.RemoveAll(project.AbsPath()) viper.Set("author", "NAME HERE ") @@ -30,6 +27,9 @@ func TestGoldenAddCmd(t *testing.T) { defer viper.Set("license", nil) defer viper.Set("year", nil) + // Initialize the project first. + initializeProject(project) + // Then add the "test" command. cmdName := "test" cmdPath := filepath.Join(project.CmdPath(), cmdName+".go") diff --git a/cobra/cmd/helpers.go b/cobra/cmd/helpers.go index c5e261ce..e5b37ec7 100644 --- a/cobra/cmd/helpers.go +++ b/cobra/cmd/helpers.go @@ -18,6 +18,7 @@ import ( "fmt" "io" "os" + "os/exec" "path/filepath" "strings" "text/template" @@ -31,7 +32,27 @@ func init() { envGoPath := os.Getenv("GOPATH") goPaths := filepath.SplitList(envGoPath) if len(goPaths) == 0 { - er("$GOPATH is not set") + // Adapted from https://github.com/Masterminds/glide/pull/798/files. + // As of Go 1.8 the GOPATH is no longer required to be set. Instead there + // is a default value. If there is no GOPATH check for the default value. + // Note, checking the GOPATH first to avoid invoking the go toolchain if + // possible. + + goExecutable := os.Getenv("COBRA_GO_EXECUTABLE") + if len(goExecutable) <= 0 { + goExecutable = "go" + } + + out, err := exec.Command(goExecutable, "env", "GOPATH").Output() + if err != nil { + er(err) + } + + toolchainGoPath := strings.TrimSpace(string(out)) + goPaths = filepath.SplitList(toolchainGoPath) + if len(goPaths) == 0 { + er("$GOPATH is not set") + } } srcPaths = make([]string, 0, len(goPaths)) for _, goPath := range goPaths { diff --git a/cobra/cmd/init.go b/cobra/cmd/init.go index ceb50b3f..db34bbb7 100755 --- a/cobra/cmd/init.go +++ b/cobra/cmd/init.go @@ -159,8 +159,8 @@ var ({{if .viper}} Environ environ.Environ ) -// RootCmd represents the base command when called without any subcommands -var RootCmd = &cobra.Command{ +// rootCmd represents the base command when called without any subcommands +var rootCmd = &cobra.Command{ Use: ApplicationName, Short: "A brief description of your application", Long: ` + "`" + `A longer description that spans multiple lines and likely contains @@ -177,7 +177,7 @@ to quickly create a Cobra application.` + "`" + `, // Execute adds all child commands to the root command and sets flags appropriately. // This is called by main.main(). It only needs to happen once to the rootCmd. func Execute() { - if err := RootCmd.Execute(); err != nil { + if err := rootCmd.Execute(); err != nil { fmt.Println(err) os.Exit(1) } @@ -200,7 +200,7 @@ func init() { // Cobra also supports local flags, which will only run // when this action is called directly. - RootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") + rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") }{{ if .viper }} // initConfig reads in config file and ENV variables if set. diff --git a/cobra/cmd/licenses.go b/cobra/cmd/licenses.go index cf2a6b7a..a070134d 100644 --- a/cobra/cmd/licenses.go +++ b/cobra/cmd/licenses.go @@ -63,7 +63,7 @@ func getLicense() License { // If user wants to have custom license, use that. if viper.IsSet("license.header") || viper.IsSet("license.text") { return License{Header: viper.GetString("license.header"), - Text: "license.text"} + Text: viper.GetString("license.text")} } // If user wants to have built-in license, use that. diff --git a/cobra/cmd/testdata/root.go.golden b/cobra/cmd/testdata/root.go.golden index 8eeeae89..a76a7672 100644 --- a/cobra/cmd/testdata/root.go.golden +++ b/cobra/cmd/testdata/root.go.golden @@ -25,8 +25,8 @@ import ( var cfgFile string -// RootCmd represents the base command when called without any subcommands -var RootCmd = &cobra.Command{ +// rootCmd represents the base command when called without any subcommands +var rootCmd = &cobra.Command{ Use: "testproject", Short: "A brief description of your application", Long: `A longer description that spans multiple lines and likely contains @@ -43,7 +43,7 @@ to quickly create a Cobra application.`, // Execute adds all child commands to the root command and sets flags appropriately. // This is called by main.main(). It only needs to happen once to the rootCmd. func Execute() { - if err := RootCmd.Execute(); err != nil { + if err := rootCmd.Execute(); err != nil { fmt.Println(err) os.Exit(1) } @@ -55,11 +55,11 @@ func init() { // Here you will define your flags and configuration settings. // Cobra supports persistent flags, which, if defined here, // will be global for your application. - RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.testproject.yaml)") + rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.testproject.yaml)") // Cobra also supports local flags, which will only run // when this action is called directly. - RootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") + rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") } // initConfig reads in config file and ENV variables if set. diff --git a/cobra/cmd/testdata/test.go.golden b/cobra/cmd/testdata/test.go.golden index 58405680..ed644275 100644 --- a/cobra/cmd/testdata/test.go.golden +++ b/cobra/cmd/testdata/test.go.golden @@ -36,7 +36,7 @@ to quickly create a Cobra application.`, } func init() { - RootCmd.AddCommand(testCmd) + rootCmd.AddCommand(testCmd) // Here you will define your flags and configuration settings. diff --git a/cobra_test.go b/cobra_test.go index d5df951e..0d1755bd 100644 --- a/cobra_test.go +++ b/cobra_test.go @@ -1,1200 +1,10 @@ package cobra import ( - "bytes" - "fmt" - "os" - "reflect" - "runtime" - "strings" "testing" "text/template" - - "github.com/spf13/pflag" ) -var tp, te, tt, tr []string -var rootPersPre, echoPre, echoPersPre, timesPersPre []string -var flagb1, flagb2, flagb3, flagbr, flagbp bool -var flags1, flags2a, flags2b, flags3, outs string -var flagi1, flagi2, flagi3, flagi4, flagir int -var rootcalled bool -var versionUsed int - -const strtwoParentHelp = "help message for parent flag strtwo" -const strtwoChildHelp = "help message for child flag strtwo" - -var cmdHidden = &Command{ - Use: "hide [secret string to print]", - Short: "Print anything to screen (if command is known)", - Long: `an absolutely utterly useless command for testing.`, - Run: func(cmd *Command, args []string) { - outs = "hidden" - }, - Hidden: true, -} - -var cmdPrint = &Command{ - Use: "print [string to print]", - Args: MinimumNArgs(1), - Short: "Print anything to the screen", - Long: `an absolutely utterly useless command for testing.`, - Run: func(cmd *Command, args []string) { - tp = args - }, -} - -var cmdEcho = &Command{ - Use: "echo [string to echo]", - Aliases: []string{"say"}, - Short: "Echo anything to the screen", - Long: `an utterly useless command for testing.`, - Example: "Just run cobra-test echo", - PersistentPreRun: func(cmd *Command, args []string) { - echoPersPre = args - }, - PreRun: func(cmd *Command, args []string) { - echoPre = args - }, - Run: func(cmd *Command, args []string) { - te = args - }, -} - -var cmdEchoSub = &Command{ - Use: "echosub [string to print]", - Short: "second sub command for echo", - Long: `an absolutely utterly useless command for testing gendocs!.`, - Run: func(cmd *Command, args []string) { - }, -} - -var cmdDeprecated = &Command{ - Use: "deprecated [can't do anything here]", - Short: "A command which is deprecated", - Long: `an absolutely utterly useless command for testing deprecation!.`, - Deprecated: "Please use echo instead", - Run: func(cmd *Command, args []string) { - }, - Args: NoArgs, -} - -var cmdTimes = &Command{ - Use: "times [# times] [string to echo]", - SuggestFor: []string{"counts"}, - Short: "Echo anything to the screen more times", - Long: `a slightly useless command for testing.`, - PersistentPreRun: func(cmd *Command, args []string) { - timesPersPre = args - }, - Run: func(cmd *Command, args []string) { - tt = args - }, - Args: OnlyValidArgs, - ValidArgs: []string{"one", "two", "three", "four"}, -} - -var cmdRootNoRun = &Command{ - Use: "cobra-test", - Short: "The root can run its own function", - Long: "The root description for help", - PersistentPreRun: func(cmd *Command, args []string) { - rootPersPre = args - }, -} - -var cmdRootSameName = &Command{ - Use: "print", - Short: "Root with the same name as a subcommand", - Long: "The root description for help", -} - -var cmdRootTakesArgs = &Command{ - Use: "root-with-args [random args]", - Short: "The root can run it's own function and takes args!", - Long: "The root description for help, and some args", - Run: func(cmd *Command, args []string) { - tr = args - }, - Args: ArbitraryArgs, -} - -var cmdRootWithRun = &Command{ - Use: "cobra-test", - Short: "The root can run its own function", - Long: "The root description for help", - Run: func(cmd *Command, args []string) { - tr = args - rootcalled = true - }, -} - -var cmdSubNoRun = &Command{ - Use: "subnorun", - Short: "A subcommand without a Run function", - Long: "A long output about a subcommand without a Run function", -} - -var cmdCustomFlags = &Command{ - Use: "customflags [flags] -- REMOTE_COMMAND", - Short: "A command that expects flags in a custom location", - Long: "A long output about a command that expects flags in a custom location", - Run: func(cmd *Command, args []string) { - }, -} - -var cmdVersion1 = &Command{ - Use: "version", - Short: "Print the version number", - Long: `First version of the version command`, - Run: func(cmd *Command, args []string) { - versionUsed = 1 - }, -} - -var cmdVersion2 = &Command{ - Use: "version", - Short: "Print the version number", - Long: `Second version of the version command`, - Run: func(cmd *Command, args []string) { - versionUsed = 2 - }, -} - -var cmdColon = &Command{ - Use: "cmd:colon", - Run: func(cmd *Command, args []string) { - }, -} - -func flagInit() { - cmdEcho.ResetFlags() - cmdPrint.ResetFlags() - cmdTimes.ResetFlags() - cmdRootNoRun.ResetFlags() - cmdRootSameName.ResetFlags() - cmdRootWithRun.ResetFlags() - cmdSubNoRun.ResetFlags() - cmdCustomFlags.ResetFlags() - cmdVersion1.ResetFlags() - cmdVersion2.ResetFlags() - - cmdRootNoRun.PersistentFlags().StringVarP(&flags2a, "strtwo", "t", "two", strtwoParentHelp) - cmdCustomFlags.Flags().IntVar(&flagi4, "intfour", 456, "help message for flag intfour") - cmdEcho.Flags().BoolVarP(&flagb1, "boolone", "b", true, "help message for flag boolone") - cmdEcho.Flags().IntVarP(&flagi1, "intone", "i", 123, "help message for flag intone") - cmdEcho.PersistentFlags().BoolVarP(&flagbp, "persistentbool", "p", false, "help message for flag persistentbool") - cmdEcho.PersistentFlags().StringVarP(&flags1, "strone", "s", "one", "help message for flag strone") - cmdPrint.Flags().IntVarP(&flagi3, "intthree", "i", 345, "help message for flag intthree") - cmdTimes.Flags().BoolVarP(&flagb2, "booltwo", "c", false, "help message for flag booltwo") - cmdTimes.Flags().IntVarP(&flagi2, "inttwo", "j", 234, "help message for flag inttwo") - cmdTimes.Flags().StringVarP(&flags2b, "strtwo", "t", "2", strtwoChildHelp) - cmdTimes.PersistentFlags().StringVarP(&flags2b, "strtwo", "t", "2", strtwoChildHelp) - cmdPrint.Flags().BoolVarP(&flagb3, "boolthree", "b", true, "help message for flag boolthree") - cmdPrint.PersistentFlags().StringVarP(&flags3, "strthree", "s", "three", "help message for flag strthree") -} - -func commandInit() { - cmdEcho.ResetCommands() - cmdPrint.ResetCommands() - cmdTimes.ResetCommands() - cmdRootNoRun.ResetCommands() - cmdRootSameName.ResetCommands() - cmdRootWithRun.ResetCommands() - cmdSubNoRun.ResetCommands() - cmdCustomFlags.ResetCommands() -} - -func initialize() *Command { - tt, tp, te = nil, nil, nil - rootPersPre, echoPre, echoPersPre, timesPersPre = nil, nil, nil, nil - - var c = cmdRootNoRun - flagInit() - commandInit() - return c -} - -func initializeWithSameName() *Command { - tt, tp, te = nil, nil, nil - rootPersPre, echoPre, echoPersPre, timesPersPre = nil, nil, nil, nil - var c = cmdRootSameName - flagInit() - commandInit() - return c -} - -func initializeWithRootCmd() *Command { - cmdRootWithRun.ResetCommands() - tt, tp, te, tr, rootcalled = nil, nil, nil, nil, false - flagInit() - cmdRootWithRun.Flags().BoolVarP(&flagbr, "boolroot", "b", false, "help message for flag boolroot") - cmdRootWithRun.Flags().IntVarP(&flagir, "introot", "i", 321, "help message for flag introot") - commandInit() - return cmdRootWithRun -} - -type resulter struct { - Error error - Output string - Command *Command -} - -func fullSetupTest(args ...string) resulter { - c := initializeWithRootCmd() - - return fullTester(c, args...) -} - -func noRRSetupTestSilenced(args ...string) resulter { - c := initialize() - c.SilenceErrors = true - c.SilenceUsage = true - return fullTester(c, args...) -} - -func noRRSetupTest(args ...string) resulter { - c := initialize() - - return fullTester(c, args...) -} - -func rootOnlySetupTest(args ...string) resulter { - c := initializeWithRootCmd() - - return simpleTester(c, args...) -} - -func simpleTester(c *Command, args ...string) resulter { - buf := new(bytes.Buffer) - // Testing flag with invalid input - c.SetOutput(buf) - c.SetArgs(args) - - err := c.Execute() - output := buf.String() - - return resulter{err, output, c} -} - -func simpleTesterC(c *Command, args ...string) resulter { - buf := new(bytes.Buffer) - // Testing flag with invalid input - c.SetOutput(buf) - c.SetArgs(args) - - cmd, err := c.ExecuteC() - output := buf.String() - - return resulter{err, output, cmd} -} - -func fullTester(c *Command, args ...string) resulter { - buf := new(bytes.Buffer) - // Testing flag with invalid input - c.SetOutput(buf) - cmdEcho.AddCommand(cmdTimes) - c.AddCommand(cmdPrint, cmdEcho, cmdSubNoRun, cmdCustomFlags, cmdDeprecated) - c.SetArgs(args) - - err := c.Execute() - output := buf.String() - - return resulter{err, output, c} -} - -func logErr(t *testing.T, found, expected string) { - out := new(bytes.Buffer) - - _, _, line, ok := runtime.Caller(2) - if ok { - fmt.Fprintf(out, "Line: %d ", line) - } - fmt.Fprintf(out, "Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", expected, found) - t.Errorf(out.String()) -} - -func checkStringContains(t *testing.T, found, expected string) { - if !strings.Contains(found, expected) { - logErr(t, found, expected) - } -} - -func checkResultContains(t *testing.T, x resulter, check string) { - checkStringContains(t, x.Output, check) -} - -func checkStringOmits(t *testing.T, found, expected string) { - if strings.Contains(found, expected) { - logErr(t, found, expected) - } -} - -func checkResultOmits(t *testing.T, x resulter, check string) { - checkStringOmits(t, x.Output, check) -} - -func checkOutputContains(t *testing.T, c *Command, check string) { - buf := new(bytes.Buffer) - c.SetOutput(buf) - c.Execute() - - if !strings.Contains(buf.String(), check) { - logErr(t, buf.String(), check) - } -} - -func TestSingleCommand(t *testing.T) { - noRRSetupTest("print", "one", "two") - - if te != nil || tt != nil { - t.Error("Wrong command called") - } - if tp == nil { - t.Error("Wrong command called") - } - if strings.Join(tp, " ") != "one two" { - t.Error("Command didn't parse correctly") - } -} - -func TestChildCommand(t *testing.T) { - noRRSetupTest("echo", "times", "one", "two") - - if te != nil || tp != nil { - t.Error("Wrong command called") - } - if tt == nil { - t.Error("Wrong command called") - } - if strings.Join(tt, " ") != "one two" { - t.Error("Command didn't parse correctly") - } -} - -func TestCommandAlias(t *testing.T) { - noRRSetupTest("say", "times", "one", "two") - - if te != nil || tp != nil { - t.Error("Wrong command called") - } - if tt == nil { - t.Error("Wrong command called") - } - if strings.Join(tt, " ") != "one two" { - t.Error("Command didn't parse correctly") - } -} - -func TestPrefixMatching(t *testing.T) { - EnablePrefixMatching = true - noRRSetupTest("ech", "times", "one", "two") - - if te != nil || tp != nil { - t.Error("Wrong command called") - } - if tt == nil { - t.Error("Wrong command called") - } - if strings.Join(tt, " ") != "one two" { - t.Error("Command didn't parse correctly") - } - - EnablePrefixMatching = false -} - -func TestNoPrefixMatching(t *testing.T) { - EnablePrefixMatching = false - - noRRSetupTest("ech", "times", "one", "two") - - if !(tt == nil && te == nil && tp == nil) { - t.Error("Wrong command called") - } -} - -func TestAliasPrefixMatching(t *testing.T) { - EnablePrefixMatching = true - noRRSetupTest("sa", "times", "one", "two") - - if te != nil || tp != nil { - t.Error("Wrong command called") - } - if tt == nil { - t.Error("Wrong command called") - } - if strings.Join(tt, " ") != "one two" { - t.Error("Command didn't parse correctly") - } - EnablePrefixMatching = false -} - -func TestChildSameName(t *testing.T) { - c := initializeWithSameName() - c.AddCommand(cmdPrint, cmdEcho) - c.SetArgs([]string{"print", "one", "two"}) - c.Execute() - - if te != nil || tt != nil { - t.Error("Wrong command called") - } - if tp == nil { - t.Error("Wrong command called") - } - if strings.Join(tp, " ") != "one two" { - t.Error("Command didn't parse correctly") - } -} - -func TestGrandChildSameName(t *testing.T) { - c := initializeWithSameName() - cmdTimes.AddCommand(cmdPrint) - c.AddCommand(cmdTimes) - c.SetArgs([]string{"times", "print", "one", "two"}) - c.Execute() - - if te != nil || tt != nil { - t.Error("Wrong command called") - } - if tp == nil { - t.Error("Wrong command called") - } - if strings.Join(tp, " ") != "one two" { - t.Error("Command didn't parse correctly") - } -} - -func TestUsage(t *testing.T) { - x := fullSetupTest("help") - checkResultContains(t, x, cmdRootWithRun.Use+" [flags]") - x = fullSetupTest("help", "customflags") - checkResultContains(t, x, cmdCustomFlags.Use) - checkResultOmits(t, x, cmdCustomFlags.Use+" [flags]") -} - -func TestRootTakesNoArgs(t *testing.T) { - c := initializeWithSameName() - c.AddCommand(cmdPrint, cmdEcho) - result := simpleTester(c, "illegal") - - if result.Error == nil { - t.Fatal("Expected an error") - } - - expectedError := `unknown command "illegal" for "print"` - if !strings.Contains(result.Error.Error(), expectedError) { - t.Errorf("exptected %v, got %v", expectedError, result.Error.Error()) - } -} - -func TestRootTakesArgs(t *testing.T) { - c := cmdRootTakesArgs - result := simpleTester(c, "legal") - - if result.Error != nil { - t.Errorf("expected no error, but got %v", result.Error) - } -} - -func TestSubCmdTakesNoArgs(t *testing.T) { - result := fullSetupTest("deprecated", "illegal") - - if result.Error == nil { - t.Fatal("Expected an error") - } - - expectedError := `unknown command "illegal" for "cobra-test deprecated"` - if !strings.Contains(result.Error.Error(), expectedError) { - t.Errorf("expected %v, got %v", expectedError, result.Error.Error()) - } -} - -func TestSubCmdTakesArgs(t *testing.T) { - noRRSetupTest("echo", "times", "one", "two") - if strings.Join(tt, " ") != "one two" { - t.Error("Command didn't parse correctly") - } -} - -func TestCmdOnlyValidArgs(t *testing.T) { - result := noRRSetupTest("echo", "times", "one", "two", "five") - - if result.Error == nil { - t.Fatal("Expected an error") - } - - expectedError := `invalid argument "five"` - if !strings.Contains(result.Error.Error(), expectedError) { - t.Errorf("expected %v, got %v", expectedError, result.Error.Error()) - } -} - -func TestFlagLong(t *testing.T) { - noRRSetupTest("echo", "--intone=13", "something", "--", "here") - - if cmdEcho.ArgsLenAtDash() != 1 { - t.Errorf("expected argsLenAtDash: %d but got %d", 1, cmdRootNoRun.ArgsLenAtDash()) - } - if strings.Join(te, " ") != "something here" { - t.Errorf("flags didn't leave proper args remaining..%s given", te) - } - if flagi1 != 13 { - t.Errorf("int flag didn't get correct value, had %d", flagi1) - } - if flagi2 != 234 { - t.Errorf("default flag value changed, 234 expected, %d given", flagi2) - } -} - -func TestFlagShort(t *testing.T) { - noRRSetupTest("echo", "-i13", "--", "something", "here") - - if cmdEcho.ArgsLenAtDash() != 0 { - t.Errorf("expected argsLenAtDash: %d but got %d", 0, cmdRootNoRun.ArgsLenAtDash()) - } - if strings.Join(te, " ") != "something here" { - t.Errorf("flags didn't leave proper args remaining..%s given", te) - } - if flagi1 != 13 { - t.Errorf("int flag didn't get correct value, had %d", flagi1) - } - if flagi2 != 234 { - t.Errorf("default flag value changed, 234 expected, %d given", flagi2) - } - - noRRSetupTest("echo", "-i", "13", "something", "here") - - if strings.Join(te, " ") != "something here" { - t.Errorf("flags didn't leave proper args remaining..%s given", te) - } - if flagi1 != 13 { - t.Errorf("int flag didn't get correct value, had %d", flagi1) - } - if flagi2 != 234 { - t.Errorf("default flag value changed, 234 expected, %d given", flagi2) - } - - noRRSetupTest("print", "-i99", "one", "two") - - if strings.Join(tp, " ") != "one two" { - t.Errorf("flags didn't leave proper args remaining..%s given", tp) - } - if flagi3 != 99 { - t.Errorf("int flag didn't get correct value, had %d", flagi3) - } - if flagi1 != 123 { - t.Errorf("default flag value changed on different command with same shortname, 234 expected, %d given", flagi2) - } -} - -func TestChildCommandFlags(t *testing.T) { - noRRSetupTest("echo", "times", "-j", "99", "one", "two") - - if strings.Join(tt, " ") != "one two" { - t.Errorf("flags didn't leave proper args remaining..%s given", tt) - } - - // Testing with flag that shouldn't be persistent - r := noRRSetupTest("echo", "times", "-j", "99", "-i77", "one", "two") - - if r.Error == nil { - t.Errorf("invalid flag should generate error") - } - - if !strings.Contains(r.Error.Error(), "unknown shorthand") { - t.Errorf("Wrong error message displayed, \n %s", r.Error) - } - - if flagi2 != 99 { - t.Errorf("flag value should be 99, %d given", flagi2) - } - - if flagi1 != 123 { - t.Errorf("unset flag should have default value, expecting 123, given %d", flagi1) - } - - // Testing with flag only existing on child - r = noRRSetupTest("echo", "-j", "99", "-i77", "one", "two") - - if r.Error == nil { - t.Errorf("invalid flag should generate error") - } - if !strings.Contains(r.Error.Error(), "unknown shorthand flag") { - t.Errorf("Wrong error message displayed, \n %s", r.Error) - } - - // Testing with persistent flag overwritten by child - noRRSetupTest("echo", "times", "--strtwo=child", "one", "two") - - if flags2b != "child" { - t.Errorf("flag value should be child, %s given", flags2b) - } - - if flags2a != "two" { - t.Errorf("unset flag should have default value, expecting two, given %s", flags2a) - } - - // Testing flag with invalid input - r = noRRSetupTest("echo", "-i10E") - - if r.Error == nil { - t.Errorf("invalid input should generate error") - } - if !strings.Contains(r.Error.Error(), "invalid syntax") { - t.Errorf("Wrong error message displayed, \n %s", r.Error) - } -} - -func TestTrailingCommandFlags(t *testing.T) { - x := fullSetupTest("echo", "two", "-x") - - if x.Error == nil { - t.Errorf("invalid flag should generate error") - } -} - -func TestInvalidSubcommandFlags(t *testing.T) { - cmd := initializeWithRootCmd() - cmd.AddCommand(cmdTimes) - - result := simpleTester(cmd, "times", "--inttwo=2", "--badflag=bar") - // given that we are not checking here result.Error we check for - // stock usage message - checkResultContains(t, result, "cobra-test times [# times]") - if strings.Contains(result.Error.Error(), "unknown flag: --inttwo") { - t.Errorf("invalid --badflag flag shouldn't fail on 'unknown' --inttwo flag") - } - -} - -func TestSubcommandExecuteC(t *testing.T) { - cmd := initializeWithRootCmd() - double := &Command{ - Use: "double message", - Run: func(c *Command, args []string) { - msg := strings.Join(args, " ") - c.Println(msg, msg) - }, - } - - echo := &Command{ - Use: "echo message", - Run: func(c *Command, args []string) { - msg := strings.Join(args, " ") - c.Println(msg) - }, - } - - cmd.AddCommand(double, echo) - - result := simpleTesterC(cmd, "double", "hello", "world") - checkResultContains(t, result, "hello world hello world") - - if result.Command.Name() != "double" { - t.Errorf("invalid cmd returned from ExecuteC: should be 'double' but got %s", result.Command.Name()) - } - - result = simpleTesterC(cmd, "echo", "msg", "to", "be", "echoed") - checkResultContains(t, result, "msg to be echoed") - - if result.Command.Name() != "echo" { - t.Errorf("invalid cmd returned from ExecuteC: should be 'echo' but got %s", result.Command.Name()) - } -} - -func TestSubcommandArgEvaluation(t *testing.T) { - cmd := initializeWithRootCmd() - - first := &Command{ - Use: "first", - Run: func(cmd *Command, args []string) { - }, - } - cmd.AddCommand(first) - - second := &Command{ - Use: "second", - Run: func(cmd *Command, args []string) { - fmt.Fprintf(cmd.OutOrStdout(), "%v", args) - }, - } - first.AddCommand(second) - - result := simpleTester(cmd, "first", "second", "first", "third") - - expectedOutput := fmt.Sprint([]string{"first third"}) - if result.Output != expectedOutput { - t.Errorf("exptected %v, got %v", expectedOutput, result.Output) - } -} - -func TestPersistentFlags(t *testing.T) { - fullSetupTest("echo", "-s", "something", "-p", "more", "here") - - // persistentFlag should act like normal flag on its own command - if strings.Join(te, " ") != "more here" { - t.Errorf("flags didn't leave proper args remaining..%s given", te) - } - if flags1 != "something" { - t.Errorf("string flag didn't get correct value, had %v", flags1) - } - if !flagbp { - t.Errorf("persistent bool flag not parsed correctly. Expected true, had %v", flagbp) - } - - // persistentFlag should act like normal flag on its own command - fullSetupTest("echo", "times", "-s", "again", "-c", "-p", "one", "two") - - if strings.Join(tt, " ") != "one two" { - t.Errorf("flags didn't leave proper args remaining. %s given", tt) - } - - if flags1 != "again" { - t.Errorf("string flag didn't get correct value, had %v", flags1) - } - - if !flagb2 { - t.Errorf("local flag not parsed correctly. Expected true, had %v", flagb2) - } - if !flagbp { - t.Errorf("persistent bool flag not parsed correctly. Expected true, had %v", flagbp) - } -} - -func TestHelpCommand(t *testing.T) { - x := fullSetupTest("help") - checkResultContains(t, x, cmdRootWithRun.Long) - - x = fullSetupTest("help", "echo") - checkResultContains(t, x, cmdEcho.Long) - - x = fullSetupTest("help", "echo", "times") - checkResultContains(t, x, cmdTimes.Long) -} - -func TestChildCommandHelp(t *testing.T) { - c := noRRSetupTest("print", "--help") - checkResultContains(t, c, strtwoParentHelp) - r := noRRSetupTest("echo", "times", "--help") - checkResultContains(t, r, strtwoChildHelp) -} - -func TestNonRunChildHelp(t *testing.T) { - x := noRRSetupTest("subnorun") - checkResultContains(t, x, cmdSubNoRun.Long) -} - -func TestRunnableRootCommand(t *testing.T) { - x := fullSetupTest("") - - if !rootcalled { - t.Errorf("Root Function was not called\n out:%v", x.Error) - } -} - -func TestVisitParents(t *testing.T) { - c := &Command{Use: "app"} - sub := &Command{Use: "sub"} - dsub := &Command{Use: "dsub"} - sub.AddCommand(dsub) - c.AddCommand(sub) - total := 0 - add := func(x *Command) { - total++ - } - sub.VisitParents(add) - if total != 1 { - t.Errorf("Should have visited 1 parent but visited %d", total) - } - - total = 0 - dsub.VisitParents(add) - if total != 2 { - t.Errorf("Should have visited 2 parent but visited %d", total) - } - - total = 0 - c.VisitParents(add) - if total != 0 { - t.Errorf("Should have not visited any parent but visited %d", total) - } -} - -func TestRunnableRootCommandNilInput(t *testing.T) { - c := initializeWithRootCmd() - - buf := new(bytes.Buffer) - // Testing flag with invalid input - c.SetOutput(buf) - cmdEcho.AddCommand(cmdTimes) - c.AddCommand(cmdPrint, cmdEcho) - c.SetArgs([]string{}) - - err := c.Execute() - if err != nil { - t.Errorf("Execute() failed with %v", err) - } - - if !rootcalled { - t.Errorf("Root Function was not called") - } -} - -func TestRunnableRootCommandEmptyInput(t *testing.T) { - args := []string{"", "--introot=12", ""} - c := initializeWithRootCmd() - - buf := new(bytes.Buffer) - // Testing flag with invalid input - c.SetOutput(buf) - cmdEcho.AddCommand(cmdTimes) - c.AddCommand(cmdPrint, cmdEcho) - c.SetArgs(args) - - c.Execute() - - if !rootcalled { - t.Errorf("Root Function was not called.\nOutput was:\n%s\n", buf) - } -} - -func TestInvalidSubcommandWhenArgsAllowed(t *testing.T) { - fullSetupTest("echo", "invalid-sub") - - if te[0] != "invalid-sub" { - t.Errorf("Subcommand didn't work...") - } -} - -func TestRootFlags(t *testing.T) { - fullSetupTest("-i", "17", "-b") - - if !flagbr { - t.Errorf("flag value should be true, %v given", flagbr) - } - - if flagir != 17 { - t.Errorf("flag value should be 17, %d given", flagir) - } -} - -func TestRootHelp(t *testing.T) { - x := fullSetupTest("--help") - - checkResultContains(t, x, "Available Commands:") - checkResultContains(t, x, "for more information about a command") - - if strings.Contains(x.Output, "unknown flag: --help") { - t.Errorf("--help shouldn't trigger an error, Got: \n %s", x.Output) - } - - if strings.Contains(x.Output, cmdEcho.Use) { - t.Errorf("--help shouldn't display subcommand's usage, Got: \n %s", x.Output) - } - - x = fullSetupTest("echo", "--help") - - if strings.Contains(x.Output, cmdTimes.Use) { - t.Errorf("--help shouldn't display subsubcommand's usage, Got: \n %s", x.Output) - } - - checkResultContains(t, x, "Available Commands:") - checkResultContains(t, x, "for more information about a command") - - if strings.Contains(x.Output, "unknown flag: --help") { - t.Errorf("--help shouldn't trigger an error, Got: \n %s", x.Output) - } - -} - -func TestFlagAccess(t *testing.T) { - initialize() - - local := cmdTimes.LocalFlags() - inherited := cmdTimes.InheritedFlags() - - for _, f := range []string{"inttwo", "strtwo", "booltwo"} { - if local.Lookup(f) == nil { - t.Errorf("LocalFlags expected to contain %s, Got: nil", f) - } - } - if inherited.Lookup("strone") == nil { - t.Errorf("InheritedFlags expected to contain strone, Got: nil") - } - if inherited.Lookup("strtwo") != nil { - t.Errorf("InheritedFlags shouldn not contain overwritten flag strtwo") - } -} - -func TestNoNRunnableRootCommandNilInput(t *testing.T) { - c := initialize() - - buf := new(bytes.Buffer) - // Testing flag with invalid input - c.SetOutput(buf) - cmdEcho.AddCommand(cmdTimes) - c.AddCommand(cmdPrint, cmdEcho) - c.SetArgs([]string{}) - - c.Execute() - - if !strings.Contains(buf.String(), cmdRootNoRun.Long) { - t.Errorf("Expected to get help output, Got: \n %s", buf) - } -} - -func TestRootNoCommandHelp(t *testing.T) { - x := rootOnlySetupTest("--help") - - checkResultOmits(t, x, "Available Commands:") - checkResultOmits(t, x, "for more information about a command") - - if strings.Contains(x.Output, "unknown flag: --help") { - t.Errorf("--help shouldn't trigger an error, Got: \n %s", x.Output) - } - - x = rootOnlySetupTest("echo", "--help") - - checkResultOmits(t, x, "Available Commands:") - checkResultOmits(t, x, "for more information about a command") - - if strings.Contains(x.Output, "unknown flag: --help") { - t.Errorf("--help shouldn't trigger an error, Got: \n %s", x.Output) - } -} - -func TestRootUnknownCommand(t *testing.T) { - r := noRRSetupTest("bogus") - s := "Error: unknown command \"bogus\" for \"cobra-test\"\nRun 'cobra-test --help' for usage.\n" - - if r.Output != s { - t.Errorf("Unexpected response.\nExpecting to be:\n %q\nGot:\n %q\n", s, r.Output) - } - - r = noRRSetupTest("--strtwo=a", "bogus") - if r.Output != s { - t.Errorf("Unexpected response.\nExpecting to be:\n %q\nGot:\n %q\n", s, r.Output) - } -} - -func TestRootUnknownCommandSilenced(t *testing.T) { - r := noRRSetupTestSilenced("bogus") - - if r.Output != "" { - t.Errorf("Unexpected response.\nExpecting to be: \n\"\"\n Got:\n %q\n", r.Output) - } - - r = noRRSetupTestSilenced("--strtwo=a", "bogus") - if r.Output != "" { - t.Errorf("Unexpected response.\nExpecting to be:\n\"\"\nGot:\n %q\n", r.Output) - } -} - -func TestRootSuggestions(t *testing.T) { - outputWithSuggestions := "Error: unknown command \"%s\" for \"cobra-test\"\n\nDid you mean this?\n\t%s\n\nRun 'cobra-test --help' for usage.\n" - outputWithoutSuggestions := "Error: unknown command \"%s\" for \"cobra-test\"\nRun 'cobra-test --help' for usage.\n" - - cmd := initializeWithRootCmd() - cmd.AddCommand(cmdTimes) - - tests := map[string]string{ - "time": "times", - "tiems": "times", - "tims": "times", - "timeS": "times", - "rimes": "times", - "ti": "times", - "t": "times", - "timely": "times", - "ri": "", - "timezone": "", - "foo": "", - "counts": "times", - } - - for typo, suggestion := range tests { - for _, suggestionsDisabled := range []bool{false, true} { - cmd.DisableSuggestions = suggestionsDisabled - result := simpleTester(cmd, typo) - expected := "" - if len(suggestion) == 0 || suggestionsDisabled { - expected = fmt.Sprintf(outputWithoutSuggestions, typo) - } else { - expected = fmt.Sprintf(outputWithSuggestions, typo, suggestion) - } - if result.Output != expected { - t.Errorf("Unexpected response.\nExpecting to be:\n %q\nGot:\n %q\n", expected, result.Output) - } - } - } -} - -func TestFlagsBeforeCommand(t *testing.T) { - // short without space - x := fullSetupTest("-i10", "echo") - if x.Error != nil { - t.Errorf("Valid Input shouldn't have errors, got:\n %q", x.Error) - } - - x = noRRSetupTest("echo", "-i=10") - if x.Error != nil { - t.Errorf("Valid Input shouldn't have errors, got:\n %s", x.Error) - } - - // long with equals - x = noRRSetupTest("--intone=123", "echo", "one", "two") - if x.Error != nil { - t.Errorf("Valid Input shouldn't have errors, got:\n %s", x.Error) - } - - // With parsing error properly reported - x = fullSetupTest("-i10E", "echo") - if !strings.Contains(x.Error.Error(), "invalid syntax") { - t.Errorf("Wrong error message displayed, \n %s", x.Error) - } -} - -func TestRemoveCommand(t *testing.T) { - versionUsed = 0 - c := initializeWithRootCmd() - c.AddCommand(cmdVersion1) - c.RemoveCommand(cmdVersion1) - x := fullTester(c, "version") - if x.Error == nil { - t.Errorf("Removed command should not have been called\n") - return - } -} - -func TestCommandWithoutSubcommands(t *testing.T) { - c := initializeWithRootCmd() - - x := simpleTester(c, "") - if x.Error != nil { - t.Errorf("Calling command without subcommands should not have error: %v", x.Error) - return - } -} - -func TestCommandWithoutSubcommandsWithArg(t *testing.T) { - c := initializeWithRootCmd() - expectedArgs := []string{"arg"} - - x := simpleTester(c, "arg") - if x.Error != nil { - t.Errorf("Calling command without subcommands but with arg should not have error: %v", x.Error) - return - } - if !reflect.DeepEqual(expectedArgs, tr) { - t.Errorf("Calling command without subcommands but with arg has wrong args: expected: %v, actual: %v", expectedArgs, tr) - return - } -} - -func TestReplaceCommandWithRemove(t *testing.T) { - versionUsed = 0 - c := initializeWithRootCmd() - c.AddCommand(cmdVersion1) - c.RemoveCommand(cmdVersion1) - c.AddCommand(cmdVersion2) - x := fullTester(c, "version") - if x.Error != nil { - t.Errorf("Valid Input shouldn't have errors, got:\n %q", x.Error) - return - } - if versionUsed == 1 { - t.Errorf("Removed command shouldn't be called\n") - } - if versionUsed != 2 { - t.Errorf("Replacing command should have been called but didn't\n") - } -} - -func TestDeprecatedSub(t *testing.T) { - c := fullSetupTest("deprecated") - - checkResultContains(t, c, cmdDeprecated.Deprecated) -} - -func TestPreRun(t *testing.T) { - noRRSetupTest("echo", "one", "two") - if echoPre == nil || echoPersPre == nil { - t.Error("PreRun or PersistentPreRun not called") - } - if rootPersPre != nil || timesPersPre != nil { - t.Error("Wrong *Pre functions called!") - } - - noRRSetupTest("echo", "times", "one", "two") - if timesPersPre == nil { - t.Error("PreRun or PersistentPreRun not called") - } - if echoPre != nil || echoPersPre != nil || rootPersPre != nil { - t.Error("Wrong *Pre functions called!") - } - - noRRSetupTest("print", "one", "two") - if rootPersPre == nil { - t.Error("Parent PersistentPreRun not called but should not have been") - } - if echoPre != nil || echoPersPre != nil || timesPersPre != nil { - t.Error("Wrong *Pre functions called!") - } -} - -// Check if cmdEchoSub gets PersistentPreRun from rootCmd even if is added last -func TestPeristentPreRunPropagation(t *testing.T) { - rootCmd := initialize() - - // First add the cmdEchoSub to cmdPrint - cmdPrint.AddCommand(cmdEchoSub) - // Now add cmdPrint to rootCmd - rootCmd.AddCommand(cmdPrint) - - rootCmd.SetArgs([]string{"print", "echosub", "lala"}) - rootCmd.Execute() - - if len(rootPersPre) == 0 || rootPersPre[0] != "lala" { - t.Error("RootCmd PersistentPreRun not called but should have been") - } -} - -func TestGlobalNormFuncPropagation(t *testing.T) { - normFunc := func(f *pflag.FlagSet, name string) pflag.NormalizedName { - return pflag.NormalizedName(name) - } - - rootCmd := initialize() - rootCmd.SetGlobalNormalizationFunc(normFunc) - if reflect.ValueOf(normFunc).Pointer() != reflect.ValueOf(rootCmd.GlobalNormalizationFunc()).Pointer() { - t.Error("rootCmd seems to have a wrong normalization function") - } - - // First add the cmdEchoSub to cmdPrint - cmdPrint.AddCommand(cmdEchoSub) - if cmdPrint.GlobalNormalizationFunc() != nil && cmdEchoSub.GlobalNormalizationFunc() != nil { - t.Error("cmdPrint and cmdEchoSub should had no normalization functions") - } - - // Now add cmdPrint to rootCmd - rootCmd.AddCommand(cmdPrint) - if reflect.ValueOf(cmdPrint.GlobalNormalizationFunc()).Pointer() != reflect.ValueOf(rootCmd.GlobalNormalizationFunc()).Pointer() || - reflect.ValueOf(cmdEchoSub.GlobalNormalizationFunc()).Pointer() != reflect.ValueOf(rootCmd.GlobalNormalizationFunc()).Pointer() { - t.Error("cmdPrint and cmdEchoSub should had the normalization function of rootCmd") - } -} - -func TestFlagOnPflagCommandLine(t *testing.T) { - flagName := "flagOnCommandLine" - pflag.String(flagName, "", "about my flag") - r := fullSetupTest("--help") - - checkResultContains(t, r, flagName) - - // Reset pflag.CommandLine flagset. - pflag.CommandLine = pflag.NewFlagSet(os.Args[0], pflag.ExitOnError) -} - func TestAddTemplateFunctions(t *testing.T) { AddTemplateFunc("t", func() bool { return true }) AddTemplateFuncs(template.FuncMap{ @@ -1202,43 +12,11 @@ func TestAddTemplateFunctions(t *testing.T) { "h": func() string { return "Hello," }, "w": func() string { return "world." }}) - const usage = "Hello, world." - c := &Command{} c.SetUsageTemplate(`{{if t}}{{h}}{{end}}{{if f}}{{h}}{{end}} {{w}}`) - if us := c.UsageString(); us != usage { - t.Errorf("c.UsageString() != \"%s\", is \"%s\"", usage, us) - } -} - -func TestUsageIsNotPrintedTwice(t *testing.T) { - var cmd = &Command{Use: "root"} - var sub = &Command{Use: "sub"} - cmd.AddCommand(sub) - - r := simpleTester(cmd, "") - if strings.Count(r.Output, "Usage:") != 1 { - t.Error("Usage output is not printed exactly once") - } -} - -func BenchmarkInheritedFlags(b *testing.B) { - initialize() - cmdEcho.AddCommand(cmdTimes) - b.ResetTimer() - - for i := 0; i < b.N; i++ { - cmdTimes.InheritedFlags() - } -} - -func BenchmarkLocalFlags(b *testing.B) { - initialize() - cmdEcho.AddCommand(cmdTimes) - b.ResetTimer() - - for i := 0; i < b.N; i++ { - cmdTimes.LocalFlags() + const expected = "Hello, world." + if got := c.UsageString(); got != expected { + t.Errorf("Expected UsageString: %v\nGot: %v", expected, got) } } diff --git a/command.go b/command.go index 4f65d770..5fefb58d 100644 --- a/command.go +++ b/command.go @@ -54,13 +54,14 @@ type Command struct { // ValidArgs is list of all valid non-flag arguments that are accepted in bash completions ValidArgs []string + // Expected arguments + Args PositionalArgs + // ArgAliases is List of aliases for ValidArgs. // These are not suggested to the user in the bash completion, // but accepted if entered manually. ArgAliases []string - // Expected arguments - Args PositionalArgs // BashCompletionFunction is custom functions used by the bash autocompletion generator. BashCompletionFunction string @@ -74,6 +75,11 @@ type Command struct { // group commands. Annotations map[string]string + // Version defines the version for this command. If this value is non-empty and the command does not + // define a "version" flag, a "version" boolean flag will be added to the command and, if specified, + // will print content of the "Version" variable. + Version string + // The *Run functions are executed in the following order: // * PersistentPreRun() // * PreRun() @@ -117,6 +123,10 @@ type Command struct { // will be printed by generating docs for this command. DisableAutoGenTag bool + // DisableFlagsInUseLine will disable the addition of [flags] to the usage + // line of a command when printing help or generating docs + DisableFlagsInUseLine bool + // DisableSuggestions disables the suggestions based on Levenshtein distance // that go along with 'unknown command' messages. DisableSuggestions bool @@ -124,8 +134,9 @@ type Command struct { // Must be > 0. SuggestionsMinimumDistance int - // name is the command name, usually the executable's name. - name string + // TraverseChildren parses flags on all parents before executing child command. + TraverseChildren bool + // commands is the list of commands supported by this program. commands []*Command // parent is a parent command for this command. @@ -171,6 +182,8 @@ type Command struct { // helpCommand is command with usage 'help'. If it's not defined by user, // cobra uses default help command. helpCommand *Command + // versionTemplate is the version template defined by user. + versionTemplate string } // SetArgs sets arguments for the command. It is set to os.Args[1:] by default, if desired, can be overridden @@ -216,6 +229,11 @@ func (c *Command) SetHelpTemplate(s string) { c.helpTemplate = s } +// SetVersionTemplate sets version template to be used. Application can use it to set custom template. +func (c *Command) SetVersionTemplate(s string) { + c.versionTemplate = s +} + // SetGlobalNormalizationFunc sets a normalization function to all flag sets and also to child commands. // The user should not have a cyclic dependency on commands. func (c *Command) SetGlobalNormalizationFunc(n func(f *flag.FlagSet, name string) flag.NormalizedName) { @@ -405,6 +423,19 @@ func (c *Command) HelpTemplate() string { {{end}}{{if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}` } +// VersionTemplate return version template for the command. +func (c *Command) VersionTemplate() string { + if c.versionTemplate != "" { + return c.versionTemplate + } + + if c.HasParent() { + return c.parent.VersionTemplate() + } + return `{{with .Name}}{{printf "%s " .}}{{end}}{{printf "version %s" .Version}} +` +} + func hasNoOptDefVal(name string, fs *flag.FlagSet) bool { flag := fs.Lookup(name) if flag == nil { @@ -474,13 +505,14 @@ func argsMinusFirstX(args []string, x string) []string { return args } +func isFlagArg(arg string) bool { + return ((len(arg) >= 3 && arg[1] == '-') || + (len(arg) >= 2 && arg[0] == '-' && arg[1] != '-')) +} + // Find the target command given the args and command tree // Meant to be run on the highest node. Only searches down. func (c *Command) Find(args []string) (*Command, []string, error) { - if c == nil { - return nil, nil, fmt.Errorf("Called find() on a nil Command") - } - var innerfind func(*Command, []string) (*Command, []string) innerfind = func(c *Command, innerArgs []string) (*Command, []string) { @@ -489,28 +521,11 @@ func (c *Command) Find(args []string) (*Command, []string, error) { return c, innerArgs } nextSubCmd := argsWOflags[0] - matches := make([]*Command, 0) - for _, cmd := range c.commands { - if cmd.Name() == nextSubCmd || cmd.HasAlias(nextSubCmd) { // exact name or alias match - return innerfind(cmd, argsMinusFirstX(innerArgs, nextSubCmd)) - } - if EnablePrefixMatching { - if strings.HasPrefix(cmd.Name(), nextSubCmd) { // prefix match - matches = append(matches, cmd) - } - for _, x := range cmd.Aliases { - if strings.HasPrefix(x, nextSubCmd) { - matches = append(matches, cmd) - } - } - } - } - // only accept a single prefix match - multiple matches would be ambiguous - if len(matches) == 1 { - return innerfind(matches[0], argsMinusFirstX(innerArgs, argsWOflags[0])) + cmd := c.findNext(nextSubCmd) + if cmd != nil { + return innerfind(cmd, argsMinusFirstX(innerArgs, nextSubCmd)) } - return c, innerArgs } @@ -538,6 +553,66 @@ func (c *Command) findSuggestions(arg string) string { return suggestionsString } +func (c *Command) findNext(next string) *Command { + matches := make([]*Command, 0) + for _, cmd := range c.commands { + if cmd.Name() == next || cmd.HasAlias(next) { + return cmd + } + if EnablePrefixMatching && cmd.hasNameOrAliasPrefix(next) { + matches = append(matches, cmd) + } + } + + if len(matches) == 1 { + return matches[0] + } + return nil +} + +// Traverse the command tree to find the command, and parse args for +// each parent. +func (c *Command) Traverse(args []string) (*Command, []string, error) { + flags := []string{} + inFlag := false + + for i, arg := range args { + switch { + // A long flag with a space separated value + case strings.HasPrefix(arg, "--") && !strings.Contains(arg, "="): + // TODO: this isn't quite right, we should really check ahead for 'true' or 'false' + inFlag = !hasNoOptDefVal(arg[2:], c.Flags()) + flags = append(flags, arg) + continue + // A short flag with a space separated value + case strings.HasPrefix(arg, "-") && !strings.Contains(arg, "=") && len(arg) == 2 && !shortHasNoOptDefVal(arg[1:], c.Flags()): + inFlag = true + flags = append(flags, arg) + continue + // The value for a flag + case inFlag: + inFlag = false + flags = append(flags, arg) + continue + // A flag without a value, or with an `=` separated value + case isFlagArg(arg): + flags = append(flags, arg) + continue + } + + cmd := c.findNext(arg) + if cmd == nil { + return c, args, nil + } + + if err := c.ParseFlags(flags); err != nil { + return nil, args, err + } + return cmd.Traverse(args[i+1:]) + } + return c, args, nil +} + // SuggestionsFor provides suggestions for the typedName. func (c *Command) SuggestionsFor(typedName string) []string { suggestions := []string{} @@ -575,10 +650,8 @@ func (c *Command) Root() *Command { return c } -// ArgsLenAtDash will return the length of f.Args at the moment when a -- was -// found during arg parsing. This allows your program to know which args were -// before the -- and which came after. (Description from -// https://godoc.org/github.com/spf13/pflag#FlagSet.ArgsLenAtDash). +// ArgsLenAtDash will return the length of c.Flags().Args at the moment +// when a -- was found during args parsing. func (c *Command) ArgsLenAtDash() int { return c.Flags().ArgsLenAtDash() } @@ -592,9 +665,10 @@ func (c *Command) execute(a []string) (err error) { c.Printf("Command %q is deprecated, %s\n", c.Name(), c.Deprecated) } - // initialize help flag as the last point possible to allow for user + // initialize help and version flag at the last point possible to allow for user // overriding c.InitDefaultHelpFlag() + c.InitDefaultVersionFlag() err = c.ParseFlags(a) if err != nil { @@ -611,7 +685,27 @@ func (c *Command) execute(a []string) (err error) { return err } - if helpVal || !c.Runnable() { + if helpVal { + return flag.ErrHelp + } + + // for back-compat, only add version flag behavior if version is defined + if c.Version != "" { + versionVal, err := c.Flags().GetBool("version") + if err != nil { + c.Println("\"version\" flag declared as non-bool. Please correct your code") + return err + } + if versionVal { + err := tmpl(c.OutOrStdout(), c.VersionTemplate(), c) + if err != nil { + c.Println(err) + } + return err + } + } + + if !c.Runnable() { return flag.ErrHelp } @@ -645,6 +739,9 @@ func (c *Command) execute(a []string) (err error) { c.PreRun(c, argWoFlags) } + if err := c.validateRequiredFlags(); err != nil { + return err + } if c.RunE != nil { if err := c.RunE(c, argWoFlags); err != nil { return err @@ -713,7 +810,12 @@ func (c *Command) ExecuteC() (cmd *Command, err error) { args = c.args } - cmd, flags, err := c.Find(args) + var flags []string + if c.TraverseChildren { + cmd, flags, err = c.Traverse(args) + } else { + cmd, flags, err = c.Find(args) + } if err != nil { // If found parse to a subcommand and then failed, talk about the subcommand if cmd != nil { @@ -725,6 +827,7 @@ func (c *Command) ExecuteC() (cmd *Command, err error) { } return c, err } + err = cmd.execute(flags) if err != nil { // Always show help if requested, even if SilenceErrors is in @@ -756,6 +859,25 @@ func (c *Command) ValidateArgs(args []string) error { return c.Args(c, args) } +func (c *Command) validateRequiredFlags() error { + flags := c.Flags() + missingFlagNames := []string{} + flags.VisitAll(func(pflag *flag.Flag) { + requiredAnnotation, found := pflag.Annotations[BashCompOneRequiredFlag] + if !found { + return + } + if (requiredAnnotation[0] == "true") && !pflag.Changed { + missingFlagNames = append(missingFlagNames, pflag.Name) + } + }) + + if len(missingFlagNames) > 0 { + return fmt.Errorf(`required flag(s) "%s" not set`, strings.Join(missingFlagNames, `", "`)) + } + return nil +} + // InitDefaultHelpFlag adds default help flag to c. // It is called automatically by executing the c or by calling help and usage. // If c already has help flag, it will do nothing. @@ -772,6 +894,27 @@ func (c *Command) InitDefaultHelpFlag() { } } +// InitDefaultVersionFlag adds default version flag to c. +// It is called automatically by executing the c. +// If c already has a version flag, it will do nothing. +// If c.Version is empty, it will do nothing. +func (c *Command) InitDefaultVersionFlag() { + if c.Version == "" { + return + } + + c.mergePersistentFlags() + if c.Flags().Lookup("version") == nil { + usage := "version for " + if c.Name() == "" { + usage += "this command" + } else { + usage += c.Name() + } + c.Flags().Bool("version", false, usage) + } +} + // InitDefaultHelpCmd adds default help command to c. // It is called automatically by executing the c or by calling help and usage. // If c already has help command or c has no subcommands, it will do nothing. @@ -803,8 +946,9 @@ Simply type ` + c.Name() + ` help [path to command] for full details.`, c.AddCommand(c.helpCommand) } -// ResetCommands used for testing. +// ResetCommands delete parent, subcommand and help command from c. func (c *Command) ResetCommands() { + c.parent = nil c.commands = nil c.helpCommand = nil c.parentsPflags = nil @@ -921,6 +1065,9 @@ func (c *Command) UseLine() string { } else { useline = c.Use } + if c.DisableFlagsInUseLine { + return useline + } if c.HasAvailableFlags() && !strings.Contains(useline, "[flags]") { useline += " [flags]" } @@ -970,15 +1117,12 @@ func (c *Command) DebugFlags() { // Name returns the command's name: the first word in the use line. func (c *Command) Name() string { - if c.name == "" { - name := c.Use - i := strings.Index(name, " ") - if i >= 0 { - name = name[:i] - } - c.name = name + name := c.Use + i := strings.Index(name, " ") + if i >= 0 { + name = name[:i] } - return c.name + return name } // HasAlias determines if a given string is an alias of the command. @@ -991,7 +1135,21 @@ func (c *Command) HasAlias(s string) bool { return false } -// NameAndAliases returns string containing name and all aliases +// hasNameOrAliasPrefix returns true if the Name or any of aliases start +// with prefix +func (c *Command) hasNameOrAliasPrefix(prefix string) bool { + if strings.HasPrefix(c.Name(), prefix) { + return true + } + for _, alias := range c.Aliases { + if strings.HasPrefix(alias, prefix) { + return true + } + } + return false +} + +// NameAndAliases returns a list of the command name and all aliases func (c *Command) NameAndAliases() string { return strings.Join(append([]string{c.Name()}, c.Aliases...), ", ") } @@ -1077,7 +1235,7 @@ func (c *Command) HasAvailableSubCommands() bool { } } - // the command either has no sub comamnds, or no available (non deprecated/help/hidden) + // the command either has no sub commands, or no available (non deprecated/help/hidden) // sub commands return false } @@ -1131,6 +1289,9 @@ func (c *Command) LocalFlags() *flag.FlagSet { c.lflags.SetOutput(c.flagErrorBuf) } c.lflags.SortFlags = c.Flags().SortFlags + if c.globNormFunc != nil { + c.lflags.SetNormalizeFunc(c.globNormFunc) + } addToLocal := func(f *flag.Flag) { if c.lflags.Lookup(f.Name) == nil && c.parentsPflags.Lookup(f.Name) == nil { @@ -1155,6 +1316,10 @@ func (c *Command) InheritedFlags() *flag.FlagSet { } local := c.LocalFlags() + if c.globNormFunc != nil { + c.iflags.SetNormalizeFunc(c.globNormFunc) + } + c.parentsPflags.VisitAll(func(f *flag.Flag) { if c.iflags.Lookup(f.Name) == nil && local.Lookup(f.Name) == nil { c.iflags.AddFlag(f) @@ -1180,7 +1345,7 @@ func (c *Command) PersistentFlags() *flag.FlagSet { return c.pflags } -// ResetFlags is used in testing. +// ResetFlags deletes all flags from command. func (c *Command) ResetFlags() { c.flagErrorBuf = new(bytes.Buffer) c.flagErrorBuf.Reset() @@ -1188,6 +1353,10 @@ func (c *Command) ResetFlags() { c.flags.SetOutput(c.flagErrorBuf) c.pflags = flag.NewFlagSet(c.Name(), flag.ContinueOnError) c.pflags.SetOutput(c.flagErrorBuf) + + c.lflags = nil + c.iflags = nil + c.parentsPflags = nil } // HasFlags checks if the command contains any flags (local plus persistent from the entire structure). @@ -1263,6 +1432,9 @@ func (c *Command) ParseFlags(args []string) error { return nil } + if c.flagErrorBuf == nil { + c.flagErrorBuf = new(bytes.Buffer) + } beforeErrorBufLen := c.flagErrorBuf.Len() c.mergePersistentFlags() err := c.Flags().Parse(args) @@ -1297,6 +1469,10 @@ func (c *Command) updateParentsPflags() { c.parentsPflags.SortFlags = false } + if c.globNormFunc != nil { + c.parentsPflags.SetNormalizeFunc(c.globNormFunc) + } + c.Root().PersistentFlags().AddFlagSet(flag.CommandLine) c.VisitParents(func(parent *Command) { diff --git a/command_test.go b/command_test.go index aa6658f8..d3dde152 100644 --- a/command_test.go +++ b/command_test.go @@ -11,27 +11,450 @@ import ( "github.com/spf13/pflag" ) -// test to ensure hidden commands run as intended -func TestHiddenCommandExecutes(t *testing.T) { +func emptyRun(*Command, []string) {} - // ensure that outs does not already equal what the command will be setting it - // to, if it did this test would not actually be testing anything... - if outs == "hidden" { - t.Errorf("outs should NOT EQUAL hidden") - } +func executeCommand(root *Command, args ...string) (output string, err error) { + _, output, err = executeCommandC(root, args...) + return output, err +} - cmdHidden.Execute() +func executeCommandC(root *Command, args ...string) (c *Command, output string, err error) { + buf := new(bytes.Buffer) + root.SetOutput(buf) + root.SetArgs(args) - // upon running the command, the value of outs should now be 'hidden' - if outs != "hidden" { - t.Errorf("Hidden command failed to run!") + c, err = root.ExecuteC() + + return c, buf.String(), err +} + +func resetCommandLineFlagSet() { + pflag.CommandLine = pflag.NewFlagSet(os.Args[0], pflag.ExitOnError) +} + +func checkStringContains(t *testing.T, got, expected string) { + if !strings.Contains(got, expected) { + t.Errorf("Expected to contain: \n %v\nGot:\n %v\n", expected, got) } } -// test to ensure hidden commands do not show up in usage/help text -func TestHiddenCommandIsHidden(t *testing.T) { - if cmdHidden.IsAvailableCommand() { - t.Errorf("Hidden command found!") +func checkStringOmits(t *testing.T, got, expected string) { + if strings.Contains(got, expected) { + t.Errorf("Expected to not contain: \n %v\nGot: %v", expected, got) + } +} + +func TestSingleCommand(t *testing.T) { + var rootCmdArgs []string + rootCmd := &Command{ + Use: "root", + Args: ExactArgs(2), + Run: func(_ *Command, args []string) { rootCmdArgs = args }, + } + aCmd := &Command{Use: "a", Args: NoArgs, Run: emptyRun} + bCmd := &Command{Use: "b", Args: NoArgs, Run: emptyRun} + rootCmd.AddCommand(aCmd, bCmd) + + output, err := executeCommand(rootCmd, "one", "two") + if output != "" { + t.Errorf("Unexpected output: %v", output) + } + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + got := strings.Join(rootCmdArgs, " ") + expected := "one two" + if got != expected { + t.Errorf("rootCmdArgs expected: %q, got: %q", expected, got) + } +} + +func TestChildCommand(t *testing.T) { + var child1CmdArgs []string + rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} + child1Cmd := &Command{ + Use: "child1", + Args: ExactArgs(2), + Run: func(_ *Command, args []string) { child1CmdArgs = args }, + } + child2Cmd := &Command{Use: "child2", Args: NoArgs, Run: emptyRun} + rootCmd.AddCommand(child1Cmd, child2Cmd) + + output, err := executeCommand(rootCmd, "child1", "one", "two") + if output != "" { + t.Errorf("Unexpected output: %v", output) + } + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + got := strings.Join(child1CmdArgs, " ") + expected := "one two" + if got != expected { + t.Errorf("child1CmdArgs expected: %q, got: %q", expected, got) + } +} + +func TestCallCommandWithoutSubcommands(t *testing.T) { + rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} + _, err := executeCommand(rootCmd) + if err != nil { + t.Errorf("Calling command without subcommands should not have error: %v", err) + } +} + +func TestRootExecuteUnknownCommand(t *testing.T) { + rootCmd := &Command{Use: "root", Run: emptyRun} + rootCmd.AddCommand(&Command{Use: "child", Run: emptyRun}) + + output, _ := executeCommand(rootCmd, "unknown") + + expected := "Error: unknown command \"unknown\" for \"root\"\nRun 'root --help' for usage.\n" + + if output != expected { + t.Errorf("Expected:\n %q\nGot:\n %q\n", expected, output) + } +} + +func TestSubcommandExecuteC(t *testing.T) { + rootCmd := &Command{Use: "root", Run: emptyRun} + childCmd := &Command{Use: "child", Run: emptyRun} + rootCmd.AddCommand(childCmd) + + c, output, err := executeCommandC(rootCmd, "child") + if output != "" { + t.Errorf("Unexpected output: %v", output) + } + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + if c.Name() != "child" { + t.Errorf(`invalid command returned from ExecuteC: expected "child"', got %q`, c.Name()) + } +} + +func TestRootUnknownCommandSilenced(t *testing.T) { + rootCmd := &Command{Use: "root", Run: emptyRun} + rootCmd.SilenceErrors = true + rootCmd.SilenceUsage = true + rootCmd.AddCommand(&Command{Use: "child", Run: emptyRun}) + + output, _ := executeCommand(rootCmd, "unknown") + if output != "" { + t.Errorf("Expected blank output, because of silenced usage.\nGot:\n %q\n", output) + } +} + +func TestCommandAlias(t *testing.T) { + var timesCmdArgs []string + rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} + echoCmd := &Command{ + Use: "echo", + Aliases: []string{"say", "tell"}, + Args: NoArgs, + Run: emptyRun, + } + timesCmd := &Command{ + Use: "times", + Args: ExactArgs(2), + Run: func(_ *Command, args []string) { timesCmdArgs = args }, + } + echoCmd.AddCommand(timesCmd) + rootCmd.AddCommand(echoCmd) + + output, err := executeCommand(rootCmd, "tell", "times", "one", "two") + if output != "" { + t.Errorf("Unexpected output: %v", output) + } + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + got := strings.Join(timesCmdArgs, " ") + expected := "one two" + if got != expected { + t.Errorf("timesCmdArgs expected: %v, got: %v", expected, got) + } +} + +func TestEnablePrefixMatching(t *testing.T) { + EnablePrefixMatching = true + + var aCmdArgs []string + rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} + aCmd := &Command{ + Use: "aCmd", + Args: ExactArgs(2), + Run: func(_ *Command, args []string) { aCmdArgs = args }, + } + bCmd := &Command{Use: "bCmd", Args: NoArgs, Run: emptyRun} + rootCmd.AddCommand(aCmd, bCmd) + + output, err := executeCommand(rootCmd, "a", "one", "two") + if output != "" { + t.Errorf("Unexpected output: %v", output) + } + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + got := strings.Join(aCmdArgs, " ") + expected := "one two" + if got != expected { + t.Errorf("aCmdArgs expected: %q, got: %q", expected, got) + } + + EnablePrefixMatching = false +} + +func TestAliasPrefixMatching(t *testing.T) { + EnablePrefixMatching = true + + var timesCmdArgs []string + rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} + echoCmd := &Command{ + Use: "echo", + Aliases: []string{"say", "tell"}, + Args: NoArgs, + Run: emptyRun, + } + timesCmd := &Command{ + Use: "times", + Args: ExactArgs(2), + Run: func(_ *Command, args []string) { timesCmdArgs = args }, + } + echoCmd.AddCommand(timesCmd) + rootCmd.AddCommand(echoCmd) + + output, err := executeCommand(rootCmd, "sa", "times", "one", "two") + if output != "" { + t.Errorf("Unexpected output: %v", output) + } + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + got := strings.Join(timesCmdArgs, " ") + expected := "one two" + if got != expected { + t.Errorf("timesCmdArgs expected: %v, got: %v", expected, got) + } + + EnablePrefixMatching = false +} + +// TestChildSameName checks the correct behaviour of cobra in cases, +// when an application with name "foo" and with subcommand "foo" +// is executed with args "foo foo". +func TestChildSameName(t *testing.T) { + var fooCmdArgs []string + rootCmd := &Command{Use: "foo", Args: NoArgs, Run: emptyRun} + fooCmd := &Command{ + Use: "foo", + Args: ExactArgs(2), + Run: func(_ *Command, args []string) { fooCmdArgs = args }, + } + barCmd := &Command{Use: "bar", Args: NoArgs, Run: emptyRun} + rootCmd.AddCommand(fooCmd, barCmd) + + output, err := executeCommand(rootCmd, "foo", "one", "two") + if output != "" { + t.Errorf("Unexpected output: %v", output) + } + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + got := strings.Join(fooCmdArgs, " ") + expected := "one two" + if got != expected { + t.Errorf("fooCmdArgs expected: %v, got: %v", expected, got) + } +} + +// TestGrandChildSameName checks the correct behaviour of cobra in cases, +// when user has a root command and a grand child +// with the same name. +func TestGrandChildSameName(t *testing.T) { + var fooCmdArgs []string + rootCmd := &Command{Use: "foo", Args: NoArgs, Run: emptyRun} + barCmd := &Command{Use: "bar", Args: NoArgs, Run: emptyRun} + fooCmd := &Command{ + Use: "foo", + Args: ExactArgs(2), + Run: func(_ *Command, args []string) { fooCmdArgs = args }, + } + barCmd.AddCommand(fooCmd) + rootCmd.AddCommand(barCmd) + + output, err := executeCommand(rootCmd, "bar", "foo", "one", "two") + if output != "" { + t.Errorf("Unexpected output: %v", output) + } + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + got := strings.Join(fooCmdArgs, " ") + expected := "one two" + if got != expected { + t.Errorf("fooCmdArgs expected: %v, got: %v", expected, got) + } +} + +func TestFlagLong(t *testing.T) { + var cArgs []string + c := &Command{ + Use: "c", + Args: ArbitraryArgs, + Run: func(_ *Command, args []string) { cArgs = args }, + } + + var intFlagValue int + var stringFlagValue string + c.Flags().IntVar(&intFlagValue, "intf", -1, "") + c.Flags().StringVar(&stringFlagValue, "sf", "", "") + + output, err := executeCommand(c, "--intf=7", "--sf=abc", "one", "--", "two") + if output != "" { + t.Errorf("Unexpected output: %v", err) + } + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + if c.ArgsLenAtDash() != 1 { + t.Errorf("Expected ArgsLenAtDash: %v but got %v", 1, c.ArgsLenAtDash()) + } + if intFlagValue != 7 { + t.Errorf("Expected intFlagValue: %v, got %v", 7, intFlagValue) + } + if stringFlagValue != "abc" { + t.Errorf("Expected stringFlagValue: %q, got %q", "abc", stringFlagValue) + } + + got := strings.Join(cArgs, " ") + expected := "one two" + if got != expected { + t.Errorf("Expected arguments: %q, got %q", expected, got) + } +} + +func TestFlagShort(t *testing.T) { + var cArgs []string + c := &Command{ + Use: "c", + Args: ArbitraryArgs, + Run: func(_ *Command, args []string) { cArgs = args }, + } + + var intFlagValue int + var stringFlagValue string + c.Flags().IntVarP(&intFlagValue, "intf", "i", -1, "") + c.Flags().StringVarP(&stringFlagValue, "sf", "s", "", "") + + output, err := executeCommand(c, "-i", "7", "-sabc", "one", "two") + if output != "" { + t.Errorf("Unexpected output: %v", err) + } + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + if intFlagValue != 7 { + t.Errorf("Expected flag value: %v, got %v", 7, intFlagValue) + } + if stringFlagValue != "abc" { + t.Errorf("Expected stringFlagValue: %q, got %q", "abc", stringFlagValue) + } + + got := strings.Join(cArgs, " ") + expected := "one two" + if got != expected { + t.Errorf("Expected arguments: %q, got %q", expected, got) + } +} + +func TestChildFlag(t *testing.T) { + rootCmd := &Command{Use: "root", Run: emptyRun} + childCmd := &Command{Use: "child", Run: emptyRun} + rootCmd.AddCommand(childCmd) + + var intFlagValue int + childCmd.Flags().IntVarP(&intFlagValue, "intf", "i", -1, "") + + output, err := executeCommand(rootCmd, "child", "-i7") + if output != "" { + t.Errorf("Unexpected output: %v", err) + } + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + if intFlagValue != 7 { + t.Errorf("Expected flag value: %v, got %v", 7, intFlagValue) + } +} + +func TestChildFlagWithParentLocalFlag(t *testing.T) { + rootCmd := &Command{Use: "root", Run: emptyRun} + childCmd := &Command{Use: "child", Run: emptyRun} + rootCmd.AddCommand(childCmd) + + var intFlagValue int + rootCmd.Flags().StringP("sf", "s", "", "") + childCmd.Flags().IntVarP(&intFlagValue, "intf", "i", -1, "") + + _, err := executeCommand(rootCmd, "child", "-i7", "-sabc") + if err == nil { + t.Errorf("Invalid flag should generate error") + } + + checkStringContains(t, err.Error(), "unknown shorthand") + + if intFlagValue != 7 { + t.Errorf("Expected flag value: %v, got %v", 7, intFlagValue) + } +} + +func TestFlagInvalidInput(t *testing.T) { + rootCmd := &Command{Use: "root", Run: emptyRun} + rootCmd.Flags().IntP("intf", "i", -1, "") + + _, err := executeCommand(rootCmd, "-iabc") + if err == nil { + t.Errorf("Invalid flag value should generate error") + } + + checkStringContains(t, err.Error(), "invalid syntax") +} + +func TestFlagBeforeCommand(t *testing.T) { + rootCmd := &Command{Use: "root", Run: emptyRun} + childCmd := &Command{Use: "child", Run: emptyRun} + rootCmd.AddCommand(childCmd) + + var flagValue int + childCmd.Flags().IntVarP(&flagValue, "intf", "i", -1, "") + + // With short flag. + _, err := executeCommand(rootCmd, "-i7", "child") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if flagValue != 7 { + t.Errorf("Expected flag value: %v, got %v", 7, flagValue) + } + + // With long flag. + _, err = executeCommand(rootCmd, "--intf=8", "child") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if flagValue != 8 { + t.Errorf("Expected flag value: %v, got %v", 9, flagValue) } } @@ -45,11 +468,11 @@ func TestStripFlags(t *testing.T) { []string{"foo", "bar"}, }, { - []string{"foo", "--bar", "-b"}, + []string{"foo", "--str", "-s"}, []string{"foo"}, }, { - []string{"-b", "foo", "--bar", "bar"}, + []string{"-s", "foo", "--str", "bar"}, []string{}, }, { @@ -65,7 +488,7 @@ func TestStripFlags(t *testing.T) { []string{"echo"}, }, { - []string{"-ib", "echo", "-bfoo", "baz"}, + []string{"-ib", "echo", "-sfoo", "baz"}, []string{"echo", "baz"}, }, { @@ -73,15 +496,15 @@ func TestStripFlags(t *testing.T) { []string{"bar", "blah"}, }, { - []string{"--int=baz", "-bbar", "-i", "foo", "blah"}, + []string{"--int=baz", "-sbar", "-i", "foo", "blah"}, []string{"blah"}, }, { - []string{"--cat", "bar", "-i", "foo", "blah"}, + []string{"--bool", "bar", "-i", "foo", "blah"}, []string{"bar", "blah"}, }, { - []string{"-c", "bar", "-i", "foo", "blah"}, + []string{"-b", "bar", "-i", "foo", "blah"}, []string{"bar", "blah"}, }, { @@ -94,61 +517,814 @@ func TestStripFlags(t *testing.T) { }, } - cmdPrint := &Command{ - Use: "print [string to print]", - Short: "Print anything to the screen", - Long: `an utterly useless command for testing.`, - Run: func(cmd *Command, args []string) { - tp = args - }, - } + c := &Command{Use: "c", Run: emptyRun} + c.PersistentFlags().BoolP("persist", "p", false, "") + c.Flags().IntP("int", "i", -1, "") + c.Flags().StringP("str", "s", "", "") + c.Flags().BoolP("bool", "b", false, "") - var flagi int - var flagstr string - var flagbool bool - cmdPrint.PersistentFlags().BoolVarP(&flagbool, "persist", "p", false, "help for persistent one") - cmdPrint.Flags().IntVarP(&flagi, "int", "i", 345, "help message for flag int") - cmdPrint.Flags().StringVarP(&flagstr, "bar", "b", "bar", "help message for flag string") - cmdPrint.Flags().BoolVarP(&flagbool, "cat", "c", false, "help message for flag bool") - - for _, test := range tests { - output := stripFlags(test.input, cmdPrint) - if !reflect.DeepEqual(test.output, output) { - t.Errorf("expected: %v, got: %v", test.output, output) + for i, test := range tests { + got := stripFlags(test.input, c) + if !reflect.DeepEqual(test.output, got) { + t.Errorf("(%v) Expected: %v, got: %v", i, test.output, got) } } } func TestDisableFlagParsing(t *testing.T) { - targs := []string{} - cmdPrint := &Command{ + var cArgs []string + c := &Command{ + Use: "c", DisableFlagParsing: true, - Run: func(cmd *Command, args []string) { - targs = args + Run: func(_ *Command, args []string) { + cArgs = args }, } + args := []string{"cmd", "-v", "-race", "-file", "foo.go"} - cmdPrint.SetArgs(args) - err := cmdPrint.Execute() - if err != nil { - t.Error(err) + output, err := executeCommand(c, args...) + if output != "" { + t.Errorf("Unexpected output: %v", output) } - if !reflect.DeepEqual(args, targs) { - t.Errorf("expected: %v, got: %v", args, targs) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + if !reflect.DeepEqual(args, cArgs) { + t.Errorf("Expected: %v, got: %v", args, cArgs) + } +} + +func TestPersistentFlagsOnSameCommand(t *testing.T) { + var rootCmdArgs []string + rootCmd := &Command{ + Use: "root", + Args: ArbitraryArgs, + Run: func(_ *Command, args []string) { rootCmdArgs = args }, + } + + var flagValue int + rootCmd.PersistentFlags().IntVarP(&flagValue, "intf", "i", -1, "") + + output, err := executeCommand(rootCmd, "-i7", "one", "two") + if output != "" { + t.Errorf("Unexpected output: %v", output) + } + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + got := strings.Join(rootCmdArgs, " ") + expected := "one two" + if got != expected { + t.Errorf("rootCmdArgs expected: %q, got %q", expected, got) + } + if flagValue != 7 { + t.Errorf("flagValue expected: %v, got %v", 7, flagValue) + } +} + +// TestEmptyInputs checks, +// if flags correctly parsed with blank strings in args. +func TestEmptyInputs(t *testing.T) { + c := &Command{Use: "c", Run: emptyRun} + + var flagValue int + c.Flags().IntVarP(&flagValue, "intf", "i", -1, "") + + output, err := executeCommand(c, "", "-i7", "") + if output != "" { + t.Errorf("Unexpected output: %v", output) + } + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + if flagValue != 7 { + t.Errorf("flagValue expected: %v, got %v", 7, flagValue) + } +} + +func TestOverwrittenFlag(t *testing.T) { + // TODO: This test fails, but should work. + t.Skip() + + parent := &Command{Use: "parent", Run: emptyRun} + child := &Command{Use: "child", Run: emptyRun} + + parent.PersistentFlags().Bool("boolf", false, "") + parent.PersistentFlags().Int("intf", -1, "") + child.Flags().String("strf", "", "") + child.Flags().Int("intf", -1, "") + + parent.AddCommand(child) + + childInherited := child.InheritedFlags() + childLocal := child.LocalFlags() + + if childLocal.Lookup("strf") == nil { + t.Error(`LocalFlags expected to contain "strf", got "nil"`) + } + if childInherited.Lookup("boolf") == nil { + t.Error(`InheritedFlags expected to contain "boolf", got "nil"`) + } + + if childInherited.Lookup("intf") != nil { + t.Errorf(`InheritedFlags should not contain overwritten flag "intf"`) + } + if childLocal.Lookup("intf") == nil { + t.Error(`LocalFlags expected to contain "intf", got "nil"`) + } +} + +func TestPersistentFlagsOnChild(t *testing.T) { + var childCmdArgs []string + rootCmd := &Command{Use: "root", Run: emptyRun} + childCmd := &Command{ + Use: "child", + Args: ArbitraryArgs, + Run: func(_ *Command, args []string) { childCmdArgs = args }, + } + rootCmd.AddCommand(childCmd) + + var parentFlagValue int + var childFlagValue int + rootCmd.PersistentFlags().IntVarP(&parentFlagValue, "parentf", "p", -1, "") + childCmd.Flags().IntVarP(&childFlagValue, "childf", "c", -1, "") + + output, err := executeCommand(rootCmd, "child", "-c7", "-p8", "one", "two") + if output != "" { + t.Errorf("Unexpected output: %v", output) + } + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + got := strings.Join(childCmdArgs, " ") + expected := "one two" + if got != expected { + t.Errorf("childCmdArgs expected: %q, got %q", expected, got) + } + if parentFlagValue != 8 { + t.Errorf("parentFlagValue expected: %v, got %v", 8, parentFlagValue) + } + if childFlagValue != 7 { + t.Errorf("childFlagValue expected: %v, got %v", 7, childFlagValue) + } +} + +func TestRequiredFlags(t *testing.T) { + c := &Command{Use: "c", Run: emptyRun} + c.Flags().String("foo1", "", "") + c.MarkFlagRequired("foo1") + c.Flags().String("foo2", "", "") + c.MarkFlagRequired("foo2") + c.Flags().String("bar", "", "") + + expected := fmt.Sprintf("required flag(s) %q, %q not set", "foo1", "foo2") + + _, err := executeCommand(c) + got := err.Error() + + if got != expected { + t.Errorf("Expected error: %q, got: %q", expected, got) + } +} + +func TestPersistentRequiredFlags(t *testing.T) { + parent := &Command{Use: "parent", Run: emptyRun} + parent.PersistentFlags().String("foo1", "", "") + parent.MarkPersistentFlagRequired("foo1") + parent.PersistentFlags().String("foo2", "", "") + parent.MarkPersistentFlagRequired("foo2") + parent.Flags().String("foo3", "", "") + + child := &Command{Use: "child", Run: emptyRun} + child.Flags().String("bar1", "", "") + child.MarkFlagRequired("bar1") + child.Flags().String("bar2", "", "") + child.MarkFlagRequired("bar2") + child.Flags().String("bar3", "", "") + + parent.AddCommand(child) + + expected := fmt.Sprintf("required flag(s) %q, %q, %q, %q not set", "bar1", "bar2", "foo1", "foo2") + + _, err := executeCommand(parent, "child") + if err.Error() != expected { + t.Errorf("Expected %q, got %q", expected, err.Error()) } } func TestInitHelpFlagMergesFlags(t *testing.T) { usage := "custom flag" - baseCmd := Command{Use: "testcmd"} - baseCmd.PersistentFlags().Bool("help", false, usage) - cmd := Command{Use: "do"} - baseCmd.AddCommand(&cmd) + rootCmd := &Command{Use: "root"} + rootCmd.PersistentFlags().Bool("help", false, "custom flag") + childCmd := &Command{Use: "child"} + rootCmd.AddCommand(childCmd) - cmd.InitDefaultHelpFlag() - actual := cmd.Flags().Lookup("help").Usage - if actual != usage { - t.Fatalf("Expected the help flag from the base command with usage '%s', but got the default with usage '%s'", usage, actual) + childCmd.InitDefaultHelpFlag() + got := childCmd.Flags().Lookup("help").Usage + if got != usage { + t.Errorf("Expected the help flag from the root command with usage: %v\nGot the default with usage: %v", usage, got) + } +} + +func TestHelpCommandExecuted(t *testing.T) { + rootCmd := &Command{Use: "root", Long: "Long description", Run: emptyRun} + rootCmd.AddCommand(&Command{Use: "child", Run: emptyRun}) + + output, err := executeCommand(rootCmd, "help") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + checkStringContains(t, output, rootCmd.Long) +} + +func TestHelpCommandExecutedOnChild(t *testing.T) { + rootCmd := &Command{Use: "root", Run: emptyRun} + childCmd := &Command{Use: "child", Long: "Long description", Run: emptyRun} + rootCmd.AddCommand(childCmd) + + output, err := executeCommand(rootCmd, "help", "child") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + checkStringContains(t, output, childCmd.Long) +} + +func TestSetHelpCommand(t *testing.T) { + c := &Command{Use: "c", Run: emptyRun} + c.AddCommand(&Command{Use: "empty", Run: emptyRun}) + + expected := "WORKS" + c.SetHelpCommand(&Command{ + Use: "help [command]", + Short: "Help about any command", + Long: `Help provides help for any command in the application. + Simply type ` + c.Name() + ` help [path to command] for full details.`, + Run: func(c *Command, _ []string) { c.Print(expected) }, + }) + + got, err := executeCommand(c, "help") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + if got != expected { + t.Errorf("Expected to contain %q, got %q", expected, got) + } +} + +func TestHelpFlagExecuted(t *testing.T) { + rootCmd := &Command{Use: "root", Long: "Long description", Run: emptyRun} + + output, err := executeCommand(rootCmd, "--help") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + checkStringContains(t, output, rootCmd.Long) +} + +func TestHelpFlagExecutedOnChild(t *testing.T) { + rootCmd := &Command{Use: "root", Run: emptyRun} + childCmd := &Command{Use: "child", Long: "Long description", Run: emptyRun} + rootCmd.AddCommand(childCmd) + + output, err := executeCommand(rootCmd, "child", "--help") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + checkStringContains(t, output, childCmd.Long) +} + +// TestHelpFlagInHelp checks, +// if '--help' flag is shown in help for child (executing `parent help child`), +// that has no other flags. +// Related to https://github.com/spf13/cobra/issues/302. +func TestHelpFlagInHelp(t *testing.T) { + parentCmd := &Command{Use: "parent", Run: func(*Command, []string) {}} + + childCmd := &Command{Use: "child", Run: func(*Command, []string) {}} + parentCmd.AddCommand(childCmd) + + output, err := executeCommand(parentCmd, "help", "child") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + checkStringContains(t, output, "[flags]") +} + +func TestFlagsInUsage(t *testing.T) { + rootCmd := &Command{Use: "root", Args: NoArgs, Run: func(*Command, []string) {}} + output, err := executeCommand(rootCmd, "--help") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + checkStringContains(t, output, "[flags]") +} + +func TestHelpExecutedOnNonRunnableChild(t *testing.T) { + rootCmd := &Command{Use: "root", Run: emptyRun} + childCmd := &Command{Use: "child", Long: "Long description"} + rootCmd.AddCommand(childCmd) + + output, err := executeCommand(rootCmd, "child") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + checkStringContains(t, output, childCmd.Long) +} + +func TestVersionFlagExecuted(t *testing.T) { + rootCmd := &Command{Use: "root", Version: "1.0.0", Run: emptyRun} + + output, err := executeCommand(rootCmd, "--version", "arg1") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + checkStringContains(t, output, "root version 1.0.0") +} + +func TestVersionTemplate(t *testing.T) { + rootCmd := &Command{Use: "root", Version: "1.0.0", Run: emptyRun} + rootCmd.SetVersionTemplate(`customized version: {{.Version}}`) + + output, err := executeCommand(rootCmd, "--version", "arg1") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + checkStringContains(t, output, "customized version: 1.0.0") +} + +func TestVersionFlagExecutedOnSubcommand(t *testing.T) { + rootCmd := &Command{Use: "root", Version: "1.0.0"} + rootCmd.AddCommand(&Command{Use: "sub", Run: emptyRun}) + + output, err := executeCommand(rootCmd, "--version", "sub") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + checkStringContains(t, output, "root version 1.0.0") +} + +func TestVersionFlagOnlyAddedToRoot(t *testing.T) { + rootCmd := &Command{Use: "root", Version: "1.0.0", Run: emptyRun} + rootCmd.AddCommand(&Command{Use: "sub", Run: emptyRun}) + + _, err := executeCommand(rootCmd, "sub", "--version") + if err == nil { + t.Errorf("Expected error") + } + + checkStringContains(t, err.Error(), "unknown flag: --version") +} + +func TestVersionFlagOnlyExistsIfVersionNonEmpty(t *testing.T) { + rootCmd := &Command{Use: "root", Run: emptyRun} + + _, err := executeCommand(rootCmd, "--version") + if err == nil { + t.Errorf("Expected error") + } + checkStringContains(t, err.Error(), "unknown flag: --version") +} + +func TestUsageIsNotPrintedTwice(t *testing.T) { + var cmd = &Command{Use: "root"} + var sub = &Command{Use: "sub"} + cmd.AddCommand(sub) + + output, _ := executeCommand(cmd, "") + if strings.Count(output, "Usage:") != 1 { + t.Error("Usage output is not printed exactly once") + } +} + +func TestVisitParents(t *testing.T) { + c := &Command{Use: "app"} + sub := &Command{Use: "sub"} + dsub := &Command{Use: "dsub"} + sub.AddCommand(dsub) + c.AddCommand(sub) + + total := 0 + add := func(x *Command) { + total++ + } + sub.VisitParents(add) + if total != 1 { + t.Errorf("Should have visited 1 parent but visited %d", total) + } + + total = 0 + dsub.VisitParents(add) + if total != 2 { + t.Errorf("Should have visited 2 parents but visited %d", total) + } + + total = 0 + c.VisitParents(add) + if total != 0 { + t.Errorf("Should have visited no parents but visited %d", total) + } +} + +func TestSuggestions(t *testing.T) { + rootCmd := &Command{Use: "root", Run: emptyRun} + timesCmd := &Command{ + Use: "times", + SuggestFor: []string{"counts"}, + Run: emptyRun, + } + rootCmd.AddCommand(timesCmd) + + templateWithSuggestions := "Error: unknown command \"%s\" for \"root\"\n\nDid you mean this?\n\t%s\n\nRun 'root --help' for usage.\n" + templateWithoutSuggestions := "Error: unknown command \"%s\" for \"root\"\nRun 'root --help' for usage.\n" + + tests := map[string]string{ + "time": "times", + "tiems": "times", + "tims": "times", + "timeS": "times", + "rimes": "times", + "ti": "times", + "t": "times", + "timely": "times", + "ri": "", + "timezone": "", + "foo": "", + "counts": "times", + } + + for typo, suggestion := range tests { + for _, suggestionsDisabled := range []bool{true, false} { + rootCmd.DisableSuggestions = suggestionsDisabled + + var expected string + output, _ := executeCommand(rootCmd, typo) + + if suggestion == "" || suggestionsDisabled { + expected = fmt.Sprintf(templateWithoutSuggestions, typo) + } else { + expected = fmt.Sprintf(templateWithSuggestions, typo, suggestion) + } + + if output != expected { + t.Errorf("Unexpected response.\nExpected:\n %q\nGot:\n %q\n", expected, output) + } + } + } +} + +func TestRemoveCommand(t *testing.T) { + rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} + childCmd := &Command{Use: "child", Run: emptyRun} + rootCmd.AddCommand(childCmd) + rootCmd.RemoveCommand(childCmd) + + _, err := executeCommand(rootCmd, "child") + if err == nil { + t.Error("Expected error on calling removed command. Got nil.") + } +} + +func TestReplaceCommandWithRemove(t *testing.T) { + childUsed := 0 + rootCmd := &Command{Use: "root", Run: emptyRun} + child1Cmd := &Command{ + Use: "child", + Run: func(*Command, []string) { childUsed = 1 }, + } + child2Cmd := &Command{ + Use: "child", + Run: func(*Command, []string) { childUsed = 2 }, + } + rootCmd.AddCommand(child1Cmd) + rootCmd.RemoveCommand(child1Cmd) + rootCmd.AddCommand(child2Cmd) + + output, err := executeCommand(rootCmd, "child") + if output != "" { + t.Errorf("Unexpected output: %v", output) + } + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + if childUsed == 1 { + t.Error("Removed command shouldn't be called") + } + if childUsed != 2 { + t.Error("Replacing command should have been called but didn't") + } +} + +func TestDeprecatedCommand(t *testing.T) { + rootCmd := &Command{Use: "root", Run: emptyRun} + deprecatedCmd := &Command{ + Use: "deprecated", + Deprecated: "This command is deprecated", + Run: emptyRun, + } + rootCmd.AddCommand(deprecatedCmd) + + output, err := executeCommand(rootCmd, "deprecated") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + checkStringContains(t, output, deprecatedCmd.Deprecated) +} + +func TestHooks(t *testing.T) { + var ( + persPreArgs string + preArgs string + runArgs string + postArgs string + persPostArgs string + ) + + c := &Command{ + Use: "c", + PersistentPreRun: func(_ *Command, args []string) { + persPreArgs = strings.Join(args, " ") + }, + PreRun: func(_ *Command, args []string) { + preArgs = strings.Join(args, " ") + }, + Run: func(_ *Command, args []string) { + runArgs = strings.Join(args, " ") + }, + PostRun: func(_ *Command, args []string) { + postArgs = strings.Join(args, " ") + }, + PersistentPostRun: func(_ *Command, args []string) { + persPostArgs = strings.Join(args, " ") + }, + } + + output, err := executeCommand(c, "one", "two") + if output != "" { + t.Errorf("Unexpected output: %v", output) + } + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + if persPreArgs != "one two" { + t.Errorf("Expected persPreArgs %q, got %q", "one two", persPreArgs) + } + if preArgs != "one two" { + t.Errorf("Expected preArgs %q, got %q", "one two", preArgs) + } + if runArgs != "one two" { + t.Errorf("Expected runArgs %q, got %q", "one two", runArgs) + } + if postArgs != "one two" { + t.Errorf("Expected postArgs %q, got %q", "one two", postArgs) + } + if persPostArgs != "one two" { + t.Errorf("Expected persPostArgs %q, got %q", "one two", persPostArgs) + } +} + +func TestPersistentHooks(t *testing.T) { + var ( + parentPersPreArgs string + parentPreArgs string + parentRunArgs string + parentPostArgs string + parentPersPostArgs string + ) + + var ( + childPersPreArgs string + childPreArgs string + childRunArgs string + childPostArgs string + childPersPostArgs string + ) + + parentCmd := &Command{ + Use: "parent", + PersistentPreRun: func(_ *Command, args []string) { + parentPersPreArgs = strings.Join(args, " ") + }, + PreRun: func(_ *Command, args []string) { + parentPreArgs = strings.Join(args, " ") + }, + Run: func(_ *Command, args []string) { + parentRunArgs = strings.Join(args, " ") + }, + PostRun: func(_ *Command, args []string) { + parentPostArgs = strings.Join(args, " ") + }, + PersistentPostRun: func(_ *Command, args []string) { + parentPersPostArgs = strings.Join(args, " ") + }, + } + + childCmd := &Command{ + Use: "child", + PersistentPreRun: func(_ *Command, args []string) { + childPersPreArgs = strings.Join(args, " ") + }, + PreRun: func(_ *Command, args []string) { + childPreArgs = strings.Join(args, " ") + }, + Run: func(_ *Command, args []string) { + childRunArgs = strings.Join(args, " ") + }, + PostRun: func(_ *Command, args []string) { + childPostArgs = strings.Join(args, " ") + }, + PersistentPostRun: func(_ *Command, args []string) { + childPersPostArgs = strings.Join(args, " ") + }, + } + parentCmd.AddCommand(childCmd) + + output, err := executeCommand(parentCmd, "child", "one", "two") + if output != "" { + t.Errorf("Unexpected output: %v", output) + } + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + // TODO: This test fails, but should not. + // Related to https://github.com/spf13/cobra/issues/252. + // + // if parentPersPreArgs != "one two" { + // t.Errorf("Expected parentPersPreArgs %q, got %q", "one two", parentPersPreArgs) + // } + if parentPreArgs != "" { + t.Errorf("Expected blank parentPreArgs, got %q", parentPreArgs) + } + if parentRunArgs != "" { + t.Errorf("Expected blank parentRunArgs, got %q", parentRunArgs) + } + if parentPostArgs != "" { + t.Errorf("Expected blank parentPostArgs, got %q", parentPostArgs) + } + // TODO: This test fails, but should not. + // Related to https://github.com/spf13/cobra/issues/252. + // + // if parentPersPostArgs != "one two" { + // t.Errorf("Expected parentPersPostArgs %q, got %q", "one two", parentPersPostArgs) + // } + + if childPersPreArgs != "one two" { + t.Errorf("Expected childPersPreArgs %q, got %q", "one two", childPersPreArgs) + } + if childPreArgs != "one two" { + t.Errorf("Expected childPreArgs %q, got %q", "one two", childPreArgs) + } + if childRunArgs != "one two" { + t.Errorf("Expected childRunArgs %q, got %q", "one two", childRunArgs) + } + if childPostArgs != "one two" { + t.Errorf("Expected childPostArgs %q, got %q", "one two", childPostArgs) + } + if childPersPostArgs != "one two" { + t.Errorf("Expected childPersPostArgs %q, got %q", "one two", childPersPostArgs) + } +} + +// Related to https://github.com/spf13/cobra/issues/521. +func TestGlobalNormFuncPropagation(t *testing.T) { + normFunc := func(f *pflag.FlagSet, name string) pflag.NormalizedName { + return pflag.NormalizedName(name) + } + + rootCmd := &Command{Use: "root", Run: emptyRun} + childCmd := &Command{Use: "child", Run: emptyRun} + rootCmd.AddCommand(childCmd) + + rootCmd.SetGlobalNormalizationFunc(normFunc) + if reflect.ValueOf(normFunc).Pointer() != reflect.ValueOf(rootCmd.GlobalNormalizationFunc()).Pointer() { + t.Error("rootCmd seems to have a wrong normalization function") + } + + if reflect.ValueOf(normFunc).Pointer() != reflect.ValueOf(childCmd.GlobalNormalizationFunc()).Pointer() { + t.Error("childCmd should have had the normalization function of rootCmd") + } +} + +// Related to https://github.com/spf13/cobra/issues/521. +func TestNormPassedOnLocal(t *testing.T) { + toUpper := func(f *pflag.FlagSet, name string) pflag.NormalizedName { + return pflag.NormalizedName(strings.ToUpper(name)) + } + + c := &Command{} + c.Flags().Bool("flagname", true, "this is a dummy flag") + c.SetGlobalNormalizationFunc(toUpper) + if c.LocalFlags().Lookup("flagname") != c.LocalFlags().Lookup("FLAGNAME") { + t.Error("Normalization function should be passed on to Local flag set") + } +} + +// Related to https://github.com/spf13/cobra/issues/521. +func TestNormPassedOnInherited(t *testing.T) { + toUpper := func(f *pflag.FlagSet, name string) pflag.NormalizedName { + return pflag.NormalizedName(strings.ToUpper(name)) + } + + c := &Command{} + c.SetGlobalNormalizationFunc(toUpper) + + child1 := &Command{} + c.AddCommand(child1) + + c.PersistentFlags().Bool("flagname", true, "") + + child2 := &Command{} + c.AddCommand(child2) + + inherited := child1.InheritedFlags() + if inherited.Lookup("flagname") == nil || inherited.Lookup("flagname") != inherited.Lookup("FLAGNAME") { + t.Error("Normalization function should be passed on to inherited flag set in command added before flag") + } + + inherited = child2.InheritedFlags() + if inherited.Lookup("flagname") == nil || inherited.Lookup("flagname") != inherited.Lookup("FLAGNAME") { + t.Error("Normalization function should be passed on to inherited flag set in command added after flag") + } +} + +// Related to https://github.com/spf13/cobra/issues/521. +func TestConsistentNormalizedName(t *testing.T) { + toUpper := func(f *pflag.FlagSet, name string) pflag.NormalizedName { + return pflag.NormalizedName(strings.ToUpper(name)) + } + n := func(f *pflag.FlagSet, name string) pflag.NormalizedName { + return pflag.NormalizedName(name) + } + + c := &Command{} + c.Flags().Bool("flagname", true, "") + c.SetGlobalNormalizationFunc(toUpper) + c.SetGlobalNormalizationFunc(n) + + if c.LocalFlags().Lookup("flagname") == c.LocalFlags().Lookup("FLAGNAME") { + t.Error("Normalizing flag names should not result in duplicate flags") + } +} + +func TestFlagOnPflagCommandLine(t *testing.T) { + flagName := "flagOnCommandLine" + pflag.String(flagName, "", "about my flag") + + c := &Command{Use: "c", Run: emptyRun} + c.AddCommand(&Command{Use: "child", Run: emptyRun}) + + output, _ := executeCommand(c, "--help") + checkStringContains(t, output, flagName) + + resetCommandLineFlagSet() +} + +// TestHiddenCommandExecutes checks, +// if hidden commands run as intended. +func TestHiddenCommandExecutes(t *testing.T) { + executed := false + c := &Command{ + Use: "c", + Hidden: true, + Run: func(*Command, []string) { executed = true }, + } + + output, err := executeCommand(c) + if output != "" { + t.Errorf("Unexpected output: %v", output) + } + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + if !executed { + t.Error("Hidden command should have been executed") + } +} + +// test to ensure hidden commands do not show up in usage/help text +func TestHiddenCommandIsHidden(t *testing.T) { + c := &Command{Use: "c", Hidden: true, Run: emptyRun} + if c.IsAvailableCommand() { + t.Errorf("Hidden command should be unavailable") } } @@ -158,15 +1334,16 @@ func TestCommandsAreSorted(t *testing.T) { originalNames := []string{"middle", "zlast", "afirst"} expectedNames := []string{"afirst", "middle", "zlast"} - var tmpCommand = &Command{Use: "tmp"} + var rootCmd = &Command{Use: "root"} for _, name := range originalNames { - tmpCommand.AddCommand(&Command{Use: name}) + rootCmd.AddCommand(&Command{Use: name}) } - for i, c := range tmpCommand.Commands() { - if expectedNames[i] != c.Name() { - t.Errorf("expected: %s, got: %s", expectedNames[i], c.Name()) + for i, c := range rootCmd.Commands() { + got := c.Name() + if expectedNames[i] != got { + t.Errorf("Expected: %s, got: %s", expectedNames[i], got) } } @@ -178,15 +1355,16 @@ func TestEnableCommandSortingIsDisabled(t *testing.T) { originalNames := []string{"middle", "zlast", "afirst"} - var tmpCommand = &Command{Use: "tmp"} + var rootCmd = &Command{Use: "root"} for _, name := range originalNames { - tmpCommand.AddCommand(&Command{Use: name}) + rootCmd.AddCommand(&Command{Use: name}) } - for i, c := range tmpCommand.Commands() { - if originalNames[i] != c.Name() { - t.Errorf("expected: %s, got: %s", originalNames[i], c.Name()) + for i, c := range rootCmd.Commands() { + got := c.Name() + if originalNames[i] != got { + t.Errorf("expected: %s, got: %s", originalNames[i], got) } } @@ -194,33 +1372,27 @@ func TestEnableCommandSortingIsDisabled(t *testing.T) { } func TestSetOutput(t *testing.T) { - cmd := &Command{} - cmd.SetOutput(nil) - if out := cmd.OutOrStdout(); out != os.Stdout { - t.Fatalf("expected setting output to nil to revert back to stdout, got %v", out) + c := &Command{} + c.SetOutput(nil) + if out := c.OutOrStdout(); out != os.Stdout { + t.Errorf("Expected setting output to nil to revert back to stdout") } } func TestFlagErrorFunc(t *testing.T) { - cmd := &Command{ - Use: "print", - RunE: func(cmd *Command, args []string) error { - return nil - }, - } - expectedFmt := "This is expected: %s" + c := &Command{Use: "c", Run: emptyRun} - cmd.SetFlagErrorFunc(func(c *Command, err error) error { + expectedFmt := "This is expected: %v" + c.SetFlagErrorFunc(func(_ *Command, err error) error { return fmt.Errorf(expectedFmt, err) }) - cmd.SetArgs([]string{"--bogus-flag"}) - cmd.SetOutput(new(bytes.Buffer)) - err := cmd.Execute() + _, err := executeCommand(c, "--unknown-flag") - expected := fmt.Sprintf(expectedFmt, "unknown flag: --bogus-flag") - if err.Error() != expected { - t.Errorf("expected %v, got %v", expected, err.Error()) + got := err.Error() + expected := fmt.Sprintf(expectedFmt, "unknown flag: --unknown-flag") + if got != expected { + t.Errorf("Expected %v, got %v", expected, got) } } @@ -228,19 +1400,19 @@ func TestFlagErrorFunc(t *testing.T) { // if cmd.LocalFlags() is unsorted when cmd.Flags().SortFlags set to false. // Related to https://github.com/spf13/cobra/issues/404. func TestSortedFlags(t *testing.T) { - cmd := &Command{} - cmd.Flags().SortFlags = false + c := &Command{} + c.Flags().SortFlags = false names := []string{"C", "B", "A", "D"} for _, name := range names { - cmd.Flags().Bool(name, false, "") + c.Flags().Bool(name, false, "") } i := 0 - cmd.LocalFlags().VisitAll(func(f *pflag.Flag) { + c.LocalFlags().VisitAll(func(f *pflag.Flag) { if i == len(names) { return } - if isStringInStringSlice(f.Name, names) { + if stringInSlice(f.Name, names) { if names[i] != f.Name { t.Errorf("Incorrect order. Expected %v, got %v", names[i], f.Name) } @@ -249,101 +1421,145 @@ func TestSortedFlags(t *testing.T) { }) } -// contains checks, if s is in ss. -func isStringInStringSlice(s string, ss []string) bool { - for _, v := range ss { - if v == s { - return true - } - } - return false -} - -// TestHelpFlagInHelp checks, -// if '--help' flag is shown in help for child (executing `parent help child`), -// that has no other flags. -// Related to https://github.com/spf13/cobra/issues/302. -func TestHelpFlagInHelp(t *testing.T) { - output := new(bytes.Buffer) - parent := &Command{Use: "parent", Run: func(*Command, []string) {}} - parent.SetOutput(output) - - child := &Command{Use: "child", Run: func(*Command, []string) {}} - parent.AddCommand(child) - - parent.SetArgs([]string{"help", "child"}) - err := parent.Execute() - if err != nil { - t.Fatal(err) - } - - if !strings.Contains(output.String(), "[flags]") { - t.Errorf("\nExpecting to contain: %v\nGot: %v", "[flags]", output.String()) - } -} - // TestMergeCommandLineToFlags checks, // if pflag.CommandLine is correctly merged to c.Flags() after first call // of c.mergePersistentFlags. // Related to https://github.com/spf13/cobra/issues/443. func TestMergeCommandLineToFlags(t *testing.T) { pflag.Bool("boolflag", false, "") - c := &Command{Use: "c", Run: func(*Command, []string) {}} + c := &Command{Use: "c", Run: emptyRun} c.mergePersistentFlags() if c.Flags().Lookup("boolflag") == nil { t.Fatal("Expecting to have flag from CommandLine in c.Flags()") } - // Reset pflag.CommandLine flagset. - pflag.CommandLine = pflag.NewFlagSet(os.Args[0], pflag.ExitOnError) + resetCommandLineFlagSet() } // TestUseDeprecatedFlags checks, // if cobra.Execute() prints a message, if a deprecated flag is used. // Related to https://github.com/spf13/cobra/issues/463. func TestUseDeprecatedFlags(t *testing.T) { - c := &Command{Use: "c", Run: func(*Command, []string) {}} - output := new(bytes.Buffer) - c.SetOutput(output) + c := &Command{Use: "c", Run: emptyRun} c.Flags().BoolP("deprecated", "d", false, "deprecated flag") c.Flags().MarkDeprecated("deprecated", "This flag is deprecated") - c.SetArgs([]string{"c", "-d"}) - if err := c.Execute(); err != nil { + output, err := executeCommand(c, "c", "-d") + if err != nil { t.Error("Unexpected error:", err) } - if !strings.Contains(output.String(), "This flag is deprecated") { - t.Errorf("Expected to contain deprecated message, but got %q", output.String()) + checkStringContains(t, output, "This flag is deprecated") +} + +func TestTraverseWithParentFlags(t *testing.T) { + rootCmd := &Command{Use: "root", TraverseChildren: true} + rootCmd.Flags().String("str", "", "") + rootCmd.Flags().BoolP("bool", "b", false, "") + + childCmd := &Command{Use: "child"} + childCmd.Flags().Int("int", -1, "") + + rootCmd.AddCommand(childCmd) + + c, args, err := rootCmd.Traverse([]string{"-b", "--str", "ok", "child", "--int"}) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if len(args) != 1 && args[0] != "--add" { + t.Errorf("Wrong args: %v", args) + } + if c.Name() != childCmd.Name() { + t.Errorf("Expected command: %q, got: %q", childCmd.Name(), c.Name()) } } -// TestSetHelpCommand checks, if SetHelpCommand works correctly. -func TestSetHelpCommand(t *testing.T) { - c := &Command{Use: "c", Run: func(*Command, []string) {}} - output := new(bytes.Buffer) - c.SetOutput(output) - c.SetArgs([]string{"help"}) +func TestTraverseNoParentFlags(t *testing.T) { + rootCmd := &Command{Use: "root", TraverseChildren: true} + rootCmd.Flags().String("foo", "", "foo things") - // Help will not be shown, if c has no subcommands. - c.AddCommand(&Command{ - Use: "empty", - Run: func(cmd *Command, args []string) {}, - }) + childCmd := &Command{Use: "child"} + childCmd.Flags().String("str", "", "") + rootCmd.AddCommand(childCmd) - correctMessage := "WORKS" - c.SetHelpCommand(&Command{ - Use: "help [command]", - Short: "Help about any command", - Long: `Help provides help for any command in the application. - Simply type ` + c.Name() + ` help [path to command] for full details.`, - Run: func(c *Command, args []string) { c.Print(correctMessage) }, - }) - - if err := c.Execute(); err != nil { - t.Error("Unexpected error:", err) + c, args, err := rootCmd.Traverse([]string{"child"}) + if err != nil { + t.Errorf("Unexpected error: %v", err) } - - if output.String() != correctMessage { - t.Errorf("Expected to contain %q message, but got %q", correctMessage, output.String()) + if len(args) != 0 { + t.Errorf("Wrong args %v", args) + } + if c.Name() != childCmd.Name() { + t.Errorf("Expected command: %q, got: %q", childCmd.Name(), c.Name()) + } +} + +func TestTraverseWithBadParentFlags(t *testing.T) { + rootCmd := &Command{Use: "root", TraverseChildren: true} + + childCmd := &Command{Use: "child"} + childCmd.Flags().String("str", "", "") + rootCmd.AddCommand(childCmd) + + expected := "unknown flag: --str" + + c, _, err := rootCmd.Traverse([]string{"--str", "ok", "child"}) + if err == nil || !strings.Contains(err.Error(), expected) { + t.Errorf("Expected error, %q, got %q", expected, err) + } + if c != nil { + t.Errorf("Expected nil command") + } +} + +func TestTraverseWithBadChildFlag(t *testing.T) { + rootCmd := &Command{Use: "root", TraverseChildren: true} + rootCmd.Flags().String("str", "", "") + + childCmd := &Command{Use: "child"} + rootCmd.AddCommand(childCmd) + + // Expect no error because the last commands args shouldn't be parsed in + // Traverse. + c, args, err := rootCmd.Traverse([]string{"child", "--str"}) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if len(args) != 1 && args[0] != "--str" { + t.Errorf("Wrong args: %v", args) + } + if c.Name() != childCmd.Name() { + t.Errorf("Expected command %q, got: %q", childCmd.Name(), c.Name()) + } +} + +func TestTraverseWithTwoSubcommands(t *testing.T) { + rootCmd := &Command{Use: "root", TraverseChildren: true} + + subCmd := &Command{Use: "sub", TraverseChildren: true} + rootCmd.AddCommand(subCmd) + + subsubCmd := &Command{ + Use: "subsub", + } + subCmd.AddCommand(subsubCmd) + + c, _, err := rootCmd.Traverse([]string{"sub", "subsub"}) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if c.Name() != subsubCmd.Name() { + t.Fatalf("Expected command: %q, got %q", subsubCmd.Name(), c.Name()) + } +} + +// TestUpdateName checks if c.Name() updates on changed c.Use. +// Related to https://github.com/spf13/cobra/pull/422#discussion_r143918343. +func TestUpdateName(t *testing.T) { + c := &Command{Use: "name xyz"} + originalName := c.Name() + + c.Use = "changedName abc" + if originalName == c.Name() || c.Name() != "changedName" { + t.Error("c.Name() should be updated on changed c.Use") } } diff --git a/doc/cmd_test.go b/doc/cmd_test.go index a4b5568f..d29c577d 100644 --- a/doc/cmd_test.go +++ b/doc/cmd_test.go @@ -1,145 +1,86 @@ package doc import ( - "bytes" - "fmt" - "runtime" "strings" "testing" "github.com/spf13/cobra" ) -var flagb1, flagb2, flagb3, flagbr, flagbp bool -var flags1, flags2a, flags2b, flags3 string -var flagi1, flagi2, flagi3, flagir int +func emptyRun(*cobra.Command, []string) {} -const strtwoParentHelp = "help message for parent flag strtwo" -const strtwoChildHelp = "help message for child flag strtwo" +func init() { + rootCmd.PersistentFlags().StringP("rootflag", "r", "two", "") + rootCmd.PersistentFlags().StringP("strtwo", "t", "two", "help message for parent flag strtwo") -var cmdEcho = &cobra.Command{ + echoCmd.PersistentFlags().StringP("strone", "s", "one", "help message for flag strone") + echoCmd.PersistentFlags().BoolP("persistentbool", "p", false, "help message for flag persistentbool") + echoCmd.Flags().IntP("intone", "i", 123, "help message for flag intone") + echoCmd.Flags().BoolP("boolone", "b", true, "help message for flag boolone") + + timesCmd.PersistentFlags().StringP("strtwo", "t", "2", "help message for child flag strtwo") + timesCmd.Flags().IntP("inttwo", "j", 234, "help message for flag inttwo") + timesCmd.Flags().BoolP("booltwo", "c", false, "help message for flag booltwo") + + printCmd.PersistentFlags().StringP("strthree", "s", "three", "help message for flag strthree") + printCmd.Flags().IntP("intthree", "i", 345, "help message for flag intthree") + printCmd.Flags().BoolP("boolthree", "b", true, "help message for flag boolthree") + + echoCmd.AddCommand(timesCmd, echoSubCmd, deprecatedCmd) + rootCmd.AddCommand(printCmd, echoCmd) +} + +var rootCmd = &cobra.Command{ + Use: "root", + Short: "Root short description", + Long: "Root long description", + Run: emptyRun, +} + +var echoCmd = &cobra.Command{ Use: "echo [string to echo]", Aliases: []string{"say"}, Short: "Echo anything to the screen", - Long: `an utterly useless command for testing.`, + Long: "an utterly useless command for testing", Example: "Just run cobra-test echo", } -var cmdEchoSub = &cobra.Command{ +var echoSubCmd = &cobra.Command{ Use: "echosub [string to print]", Short: "second sub command for echo", - Long: `an absolutely utterly useless command for testing gendocs!.`, - Run: func(cmd *cobra.Command, args []string) {}, + Long: "an absolutely utterly useless command for testing gendocs!.", + Run: emptyRun, } -var cmdDeprecated = &cobra.Command{ +var timesCmd = &cobra.Command{ + Use: "times [# times] [string to echo]", + SuggestFor: []string{"counts"}, + Short: "Echo anything to the screen more times", + Long: `a slightly useless command for testing.`, + Run: emptyRun, +} + +var deprecatedCmd = &cobra.Command{ Use: "deprecated [can't do anything here]", Short: "A command which is deprecated", Long: `an absolutely utterly useless command for testing deprecation!.`, Deprecated: "Please use echo instead", } -var cmdTimes = &cobra.Command{ - Use: "times [# times] [string to echo]", - SuggestFor: []string{"counts"}, - Short: "Echo anything to the screen more times", - Long: `a slightly useless command for testing.`, - PersistentPreRun: func(cmd *cobra.Command, args []string) {}, - Run: func(cmd *cobra.Command, args []string) {}, -} - -var cmdPrint = &cobra.Command{ +var printCmd = &cobra.Command{ Use: "print [string to print]", Short: "Print anything to the screen", Long: `an absolutely utterly useless command for testing.`, } -var cmdRootNoRun = &cobra.Command{ - Use: "cobra-test", - Short: "The root can run its own function", - Long: "The root description for help", -} - -var cmdRootSameName = &cobra.Command{ - Use: "print", - Short: "Root with the same name as a subcommand", - Long: "The root description for help", -} - -var cmdRootWithRun = &cobra.Command{ - Use: "cobra-test", - Short: "The root can run its own function", - Long: "The root description for help", -} - -var cmdSubNoRun = &cobra.Command{ - Use: "subnorun", - Short: "A subcommand without a Run function", - Long: "A long output about a subcommand without a Run function", -} - -var cmdVersion1 = &cobra.Command{ - Use: "version", - Short: "Print the version number", - Long: `First version of the version command`, -} - -var cmdVersion2 = &cobra.Command{ - Use: "version", - Short: "Print the version number", - Long: `Second version of the version command`, -} - -func flagInit() { - cmdEcho.ResetFlags() - cmdPrint.ResetFlags() - cmdTimes.ResetFlags() - cmdRootNoRun.ResetFlags() - cmdRootSameName.ResetFlags() - cmdRootWithRun.ResetFlags() - cmdSubNoRun.ResetFlags() - cmdRootNoRun.PersistentFlags().StringVarP(&flags2a, "strtwo", "t", "two", strtwoParentHelp) - cmdEcho.Flags().IntVarP(&flagi1, "intone", "i", 123, "help message for flag intone") - cmdTimes.Flags().IntVarP(&flagi2, "inttwo", "j", 234, "help message for flag inttwo") - cmdPrint.Flags().IntVarP(&flagi3, "intthree", "i", 345, "help message for flag intthree") - cmdEcho.PersistentFlags().StringVarP(&flags1, "strone", "s", "one", "help message for flag strone") - cmdEcho.PersistentFlags().BoolVarP(&flagbp, "persistentbool", "p", false, "help message for flag persistentbool") - cmdTimes.PersistentFlags().StringVarP(&flags2b, "strtwo", "t", "2", strtwoChildHelp) - cmdPrint.PersistentFlags().StringVarP(&flags3, "strthree", "s", "three", "help message for flag strthree") - cmdEcho.Flags().BoolVarP(&flagb1, "boolone", "b", true, "help message for flag boolone") - cmdTimes.Flags().BoolVarP(&flagb2, "booltwo", "c", false, "help message for flag booltwo") - cmdPrint.Flags().BoolVarP(&flagb3, "boolthree", "b", true, "help message for flag boolthree") - cmdVersion1.ResetFlags() - cmdVersion2.ResetFlags() -} - -func initializeWithRootCmd() *cobra.Command { - cmdRootWithRun.ResetCommands() - flagInit() - cmdRootWithRun.Flags().BoolVarP(&flagbr, "boolroot", "b", false, "help message for flag boolroot") - cmdRootWithRun.Flags().IntVarP(&flagir, "introot", "i", 321, "help message for flag introot") - return cmdRootWithRun -} - -func checkStringContains(t *testing.T, found, expected string) { - if !strings.Contains(found, expected) { - logErr(t, found, expected) +func checkStringContains(t *testing.T, got, expected string) { + if !strings.Contains(got, expected) { + t.Errorf("Expected to contain: \n %v\nGot:\n %v\n", expected, got) } } -func checkStringOmits(t *testing.T, found, expected string) { - if strings.Contains(found, expected) { - logErr(t, found, expected) +func checkStringOmits(t *testing.T, got, expected string) { + if strings.Contains(got, expected) { + t.Errorf("Expected to not contain: \n %v\nGot: %v", expected, got) } } - -func logErr(t *testing.T, found, expected string) { - out := new(bytes.Buffer) - - _, _, line, ok := runtime.Caller(2) - if ok { - fmt.Fprintf(out, "Line: %d ", line) - } - fmt.Fprintf(out, "Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", expected, found) - t.Errorf(out.String()) -} diff --git a/doc/man_docs_test.go b/doc/man_docs_test.go index 87991063..62f85e47 100644 --- a/doc/man_docs_test.go +++ b/doc/man_docs_test.go @@ -18,135 +18,97 @@ func translate(in string) string { } func TestGenManDoc(t *testing.T) { - c := initializeWithRootCmd() - // Need two commands to run the command alphabetical sort - cmdEcho.AddCommand(cmdTimes, cmdEchoSub, cmdDeprecated) - c.AddCommand(cmdPrint, cmdEcho) - cmdRootWithRun.PersistentFlags().StringVarP(&flags2a, "rootflag", "r", "two", strtwoParentHelp) - - out := new(bytes.Buffer) - header := &GenManHeader{ Title: "Project", Section: "2", } + // We generate on a subcommand so we have both subcommands and parents - if err := GenMan(cmdEcho, header, out); err != nil { + buf := new(bytes.Buffer) + if err := GenMan(echoCmd, header, buf); err != nil { t.Fatal(err) } - found := out.String() + output := buf.String() // Make sure parent has - in CommandPath() in SEE ALSO: - parentPath := cmdEcho.Parent().CommandPath() + parentPath := echoCmd.Parent().CommandPath() dashParentPath := strings.Replace(parentPath, " ", "-", -1) expected := translate(dashParentPath) expected = expected + "(" + header.Section + ")" - checkStringContains(t, found, expected) + checkStringContains(t, output, expected) - // Our description - expected = translate(cmdEcho.Name()) - checkStringContains(t, found, expected) - - // Better have our example - expected = translate(cmdEcho.Name()) - checkStringContains(t, found, expected) - - // A local flag - expected = "boolone" - checkStringContains(t, found, expected) - - // persistent flag on parent - expected = "rootflag" - checkStringContains(t, found, expected) - - // We better output info about our parent - expected = translate(cmdRootWithRun.Name()) - checkStringContains(t, found, expected) - - // And about subcommands - expected = translate(cmdEchoSub.Name()) - checkStringContains(t, found, expected) - - unexpected := translate(cmdDeprecated.Name()) - checkStringOmits(t, found, unexpected) - - // auto generated - expected = translate("Auto generated") - checkStringContains(t, found, expected) + checkStringContains(t, output, translate(echoCmd.Name())) + checkStringContains(t, output, translate(echoCmd.Name())) + checkStringContains(t, output, "boolone") + checkStringContains(t, output, "rootflag") + checkStringContains(t, output, translate(rootCmd.Name())) + checkStringContains(t, output, translate(echoSubCmd.Name())) + checkStringOmits(t, output, translate(deprecatedCmd.Name())) + checkStringContains(t, output, translate("Auto generated")) } func TestGenManNoGenTag(t *testing.T) { - c := initializeWithRootCmd() - // Need two commands to run the command alphabetical sort - cmdEcho.AddCommand(cmdTimes, cmdEchoSub, cmdDeprecated) - c.AddCommand(cmdPrint, cmdEcho) - cmdRootWithRun.PersistentFlags().StringVarP(&flags2a, "rootflag", "r", "two", strtwoParentHelp) - cmdEcho.DisableAutoGenTag = true - out := new(bytes.Buffer) + echoCmd.DisableAutoGenTag = true + defer func() { echoCmd.DisableAutoGenTag = false }() header := &GenManHeader{ Title: "Project", Section: "2", } + // We generate on a subcommand so we have both subcommands and parents - if err := GenMan(cmdEcho, header, out); err != nil { + buf := new(bytes.Buffer) + if err := GenMan(echoCmd, header, buf); err != nil { t.Fatal(err) } - found := out.String() + output := buf.String() unexpected := translate("#HISTORY") - checkStringOmits(t, found, unexpected) + checkStringOmits(t, output, unexpected) } func TestGenManSeeAlso(t *testing.T) { - noop := func(cmd *cobra.Command, args []string) {} + rootCmd := &cobra.Command{Use: "root", Run: emptyRun} + aCmd := &cobra.Command{Use: "aaa", Run: emptyRun, Hidden: true} // #229 + bCmd := &cobra.Command{Use: "bbb", Run: emptyRun} + cCmd := &cobra.Command{Use: "ccc", Run: emptyRun} + rootCmd.AddCommand(aCmd, bCmd, cCmd) - top := &cobra.Command{Use: "top", Run: noop} - aaa := &cobra.Command{Use: "aaa", Run: noop, Hidden: true} // #229 - bbb := &cobra.Command{Use: "bbb", Run: noop} - ccc := &cobra.Command{Use: "ccc", Run: noop} - top.AddCommand(aaa, bbb, ccc) - - out := new(bytes.Buffer) + buf := new(bytes.Buffer) header := &GenManHeader{} - if err := GenMan(top, header, out); err != nil { + if err := GenMan(rootCmd, header, buf); err != nil { t.Fatal(err) } + scanner := bufio.NewScanner(buf) - scanner := bufio.NewScanner(out) - - if err := AssertLineFound(scanner, ".SH SEE ALSO"); err != nil { - t.Fatal(fmt.Errorf("Couldn't find SEE ALSO section header: %s", err.Error())) + if err := assertLineFound(scanner, ".SH SEE ALSO"); err != nil { + t.Fatalf("Couldn't find SEE ALSO section header: %v", err) } - - if err := AssertNextLineEquals(scanner, ".PP"); err != nil { - t.Fatal(fmt.Errorf("First line after SEE ALSO wasn't break-indent: %s", err.Error())) + if err := assertNextLineEquals(scanner, ".PP"); err != nil { + t.Fatalf("First line after SEE ALSO wasn't break-indent: %v", err) } - - if err := AssertNextLineEquals(scanner, `\fBtop\-bbb(1)\fP, \fBtop\-ccc(1)\fP`); err != nil { - t.Fatal(fmt.Errorf("Second line after SEE ALSO wasn't correct: %s", err.Error())) + if err := assertNextLineEquals(scanner, `\fBroot\-bbb(1)\fP, \fBroot\-ccc(1)\fP`); err != nil { + t.Fatalf("Second line after SEE ALSO wasn't correct: %v", err) } } func TestManPrintFlagsHidesShortDeperecated(t *testing.T) { - cmd := &cobra.Command{} - flags := cmd.Flags() - flags.StringP("foo", "f", "default", "Foo flag") - flags.MarkShorthandDeprecated("foo", "don't use it no more") + c := &cobra.Command{} + c.Flags().StringP("foo", "f", "default", "Foo flag") + c.Flags().MarkShorthandDeprecated("foo", "don't use it no more") - out := new(bytes.Buffer) - manPrintFlags(out, flags) + buf := new(bytes.Buffer) + manPrintFlags(buf, c.Flags()) + got := buf.String() expected := "**--foo**=\"default\"\n\tFoo flag\n\n" - if out.String() != expected { - t.Fatalf("Expected %s, but got %s", expected, out.String()) + if got != expected { + t.Errorf("Expected %v, got %v", expected, got) } } func TestGenManTree(t *testing.T) { - cmd := &cobra.Command{ - Use: "do [OPTIONS] arg1 arg2", - } + c := &cobra.Command{Use: "do [OPTIONS] arg1 arg2"} header := &GenManHeader{Section: "2"} tmpdir, err := ioutil.TempDir("", "test-gen-man-tree") if err != nil { @@ -154,7 +116,7 @@ func TestGenManTree(t *testing.T) { } defer os.RemoveAll(tmpdir) - if err := GenManTree(cmd, header, tmpdir); err != nil { + if err := GenManTree(c, header, tmpdir); err != nil { t.Fatalf("GenManTree failed: %s", err.Error()) } @@ -167,7 +129,7 @@ func TestGenManTree(t *testing.T) { } } -func AssertLineFound(scanner *bufio.Scanner, expectedLine string) error { +func assertLineFound(scanner *bufio.Scanner, expectedLine string) error { for scanner.Scan() { line := scanner.Text() if line == expectedLine { @@ -176,30 +138,29 @@ func AssertLineFound(scanner *bufio.Scanner, expectedLine string) error { } if err := scanner.Err(); err != nil { - return fmt.Errorf("AssertLineFound: scan failed: %s", err.Error()) + return fmt.Errorf("scan failed: %s", err) } - return fmt.Errorf("AssertLineFound: hit EOF before finding %#v", expectedLine) + return fmt.Errorf("hit EOF before finding %v", expectedLine) } -func AssertNextLineEquals(scanner *bufio.Scanner, expectedLine string) error { +func assertNextLineEquals(scanner *bufio.Scanner, expectedLine string) error { if scanner.Scan() { line := scanner.Text() if line == expectedLine { return nil } - return fmt.Errorf("AssertNextLineEquals: got %#v, not %#v", line, expectedLine) + return fmt.Errorf("got %v, not %v", line, expectedLine) } if err := scanner.Err(); err != nil { - return fmt.Errorf("AssertNextLineEquals: scan failed: %s", err.Error()) + return fmt.Errorf("scan failed: %v", err) } - return fmt.Errorf("AssertNextLineEquals: hit EOF before finding %#v", expectedLine) + return fmt.Errorf("hit EOF before finding %v", expectedLine) } func BenchmarkGenManToFile(b *testing.B) { - c := initializeWithRootCmd() file, err := ioutil.TempFile("", "") if err != nil { b.Fatal(err) @@ -209,7 +170,7 @@ func BenchmarkGenManToFile(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { - if err := GenMan(c, nil, file); err != nil { + if err := GenMan(rootCmd, nil, file); err != nil { b.Fatal(err) } } diff --git a/doc/md_docs.go b/doc/md_docs.go index 68cf5bf6..d7a2c2b6 100644 --- a/doc/md_docs.go +++ b/doc/md_docs.go @@ -67,7 +67,7 @@ func GenMarkdownCustom(cmd *cobra.Command, w io.Writer, linkHandler func(string) buf.WriteString("## " + name + "\n\n") buf.WriteString(short + "\n\n") buf.WriteString("### Synopsis\n\n") - buf.WriteString("\n" + long + "\n\n") + buf.WriteString(long + "\n\n") if cmd.Runnable() { buf.WriteString(fmt.Sprintf("```\n%s\n```\n\n", cmd.UseLine())) @@ -82,7 +82,7 @@ func GenMarkdownCustom(cmd *cobra.Command, w io.Writer, linkHandler func(string) return err } if hasSeeAlso(cmd) { - buf.WriteString("### SEE ALSO\n") + buf.WriteString("### SEE ALSO\n\n") if cmd.HasParent() { parent := cmd.Parent() pname := parent.CommandPath() diff --git a/doc/md_docs_test.go b/doc/md_docs_test.go index ba6b9a46..b0fa68c0 100644 --- a/doc/md_docs_test.go +++ b/doc/md_docs_test.go @@ -5,100 +5,51 @@ import ( "io/ioutil" "os" "path/filepath" - "strings" "testing" "github.com/spf13/cobra" ) func TestGenMdDoc(t *testing.T) { - c := initializeWithRootCmd() - // Need two commands to run the command alphabetical sort - cmdEcho.AddCommand(cmdTimes, cmdEchoSub, cmdDeprecated) - c.AddCommand(cmdPrint, cmdEcho) - cmdRootWithRun.PersistentFlags().StringVarP(&flags2a, "rootflag", "r", "two", strtwoParentHelp) - - out := new(bytes.Buffer) - - // We generate on s subcommand so we have both subcommands and parents - if err := GenMarkdown(cmdEcho, out); err != nil { + // We generate on subcommand so we have both subcommands and parents. + buf := new(bytes.Buffer) + if err := GenMarkdown(echoCmd, buf); err != nil { t.Fatal(err) } - found := out.String() + output := buf.String() - // Our description - expected := cmdEcho.Long - if !strings.Contains(found, expected) { - t.Errorf("Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", expected, found) - } - - // Better have our example - expected = cmdEcho.Example - if !strings.Contains(found, expected) { - t.Errorf("Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", expected, found) - } - - // A local flag - expected = "boolone" - if !strings.Contains(found, expected) { - t.Errorf("Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", expected, found) - } - - // persistent flag on parent - expected = "rootflag" - if !strings.Contains(found, expected) { - t.Errorf("Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", expected, found) - } - - // We better output info about our parent - expected = cmdRootWithRun.Short - if !strings.Contains(found, expected) { - t.Errorf("Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", expected, found) - } - - // And about subcommands - expected = cmdEchoSub.Short - if !strings.Contains(found, expected) { - t.Errorf("Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", expected, found) - } - - unexpected := cmdDeprecated.Short - if strings.Contains(found, unexpected) { - t.Errorf("Unexpected response.\nFound: %v\nBut should not have!!\n", unexpected) - } + checkStringContains(t, output, echoCmd.Long) + checkStringContains(t, output, echoCmd.Example) + checkStringContains(t, output, "boolone") + checkStringContains(t, output, "rootflag") + checkStringContains(t, output, rootCmd.Short) + checkStringContains(t, output, echoSubCmd.Short) + checkStringOmits(t, output, deprecatedCmd.Short) } func TestGenMdNoTag(t *testing.T) { - c := initializeWithRootCmd() - // Need two commands to run the command alphabetical sort - cmdEcho.AddCommand(cmdTimes, cmdEchoSub, cmdDeprecated) - c.AddCommand(cmdPrint, cmdEcho) - c.DisableAutoGenTag = true - cmdRootWithRun.PersistentFlags().StringVarP(&flags2a, "rootflag", "r", "two", strtwoParentHelp) - out := new(bytes.Buffer) + rootCmd.DisableAutoGenTag = true + defer func() { rootCmd.DisableAutoGenTag = false }() - if err := GenMarkdown(c, out); err != nil { + buf := new(bytes.Buffer) + if err := GenMarkdown(rootCmd, buf); err != nil { t.Fatal(err) } - found := out.String() - - unexpected := "Auto generated" - checkStringOmits(t, found, unexpected) + output := buf.String() + checkStringOmits(t, output, "Auto generated") } func TestGenMdTree(t *testing.T) { - cmd := &cobra.Command{ - Use: "do [OPTIONS] arg1 arg2", - } + c := &cobra.Command{Use: "do [OPTIONS] arg1 arg2"} tmpdir, err := ioutil.TempDir("", "test-gen-md-tree") if err != nil { - t.Fatalf("Failed to create tmpdir: %s", err.Error()) + t.Fatalf("Failed to create tmpdir: %v", err) } defer os.RemoveAll(tmpdir) - if err := GenMarkdownTree(cmd, tmpdir); err != nil { - t.Fatalf("GenMarkdownTree failed: %s", err.Error()) + if err := GenMarkdownTree(c, tmpdir); err != nil { + t.Fatalf("GenMarkdownTree failed: %v", err) } if _, err := os.Stat(filepath.Join(tmpdir, "do.md")); err != nil { @@ -107,7 +58,6 @@ func TestGenMdTree(t *testing.T) { } func BenchmarkGenMarkdownToFile(b *testing.B) { - c := initializeWithRootCmd() file, err := ioutil.TempFile("", "") if err != nil { b.Fatal(err) @@ -117,7 +67,7 @@ func BenchmarkGenMarkdownToFile(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { - if err := GenMarkdown(c, file); err != nil { + if err := GenMarkdown(rootCmd, file); err != nil { b.Fatal(err) } } diff --git a/doc/rest_docs.go b/doc/rest_docs.go new file mode 100644 index 00000000..4913e3ee --- /dev/null +++ b/doc/rest_docs.go @@ -0,0 +1,185 @@ +//Copyright 2015 Red Hat Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package doc + +import ( + "bytes" + "fmt" + "io" + "os" + "path/filepath" + "sort" + "strings" + "time" + + "github.com/spf13/cobra" +) + +func printOptionsReST(buf *bytes.Buffer, cmd *cobra.Command, name string) error { + flags := cmd.NonInheritedFlags() + flags.SetOutput(buf) + if flags.HasFlags() { + buf.WriteString("Options\n") + buf.WriteString("~~~~~~~\n\n::\n\n") + flags.PrintDefaults() + buf.WriteString("\n") + } + + parentFlags := cmd.InheritedFlags() + parentFlags.SetOutput(buf) + if parentFlags.HasFlags() { + buf.WriteString("Options inherited from parent commands\n") + buf.WriteString("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n::\n\n") + parentFlags.PrintDefaults() + buf.WriteString("\n") + } + return nil +} + +// linkHandler for default ReST hyperlink markup +func defaultLinkHandler(name, ref string) string { + return fmt.Sprintf("`%s <%s.rst>`_", name, ref) +} + +// GenReST creates reStructured Text output. +func GenReST(cmd *cobra.Command, w io.Writer) error { + return GenReSTCustom(cmd, w, defaultLinkHandler) +} + +// GenReSTCustom creates custom reStructured Text output. +func GenReSTCustom(cmd *cobra.Command, w io.Writer, linkHandler func(string, string) string) error { + cmd.InitDefaultHelpCmd() + cmd.InitDefaultHelpFlag() + + buf := new(bytes.Buffer) + name := cmd.CommandPath() + + short := cmd.Short + long := cmd.Long + if len(long) == 0 { + long = short + } + ref := strings.Replace(name, " ", "_", -1) + + buf.WriteString(".. _" + ref + ":\n\n") + buf.WriteString(name + "\n") + buf.WriteString(strings.Repeat("-", len(name)) + "\n\n") + buf.WriteString(short + "\n\n") + buf.WriteString("Synopsis\n") + buf.WriteString("~~~~~~~~\n\n") + buf.WriteString("\n" + long + "\n\n") + + if cmd.Runnable() { + buf.WriteString(fmt.Sprintf("::\n\n %s\n\n", cmd.UseLine())) + } + + if len(cmd.Example) > 0 { + buf.WriteString("Examples\n") + buf.WriteString("~~~~~~~~\n\n") + buf.WriteString(fmt.Sprintf("::\n\n%s\n\n", indentString(cmd.Example, " "))) + } + + if err := printOptionsReST(buf, cmd, name); err != nil { + return err + } + if hasSeeAlso(cmd) { + buf.WriteString("SEE ALSO\n") + buf.WriteString("~~~~~~~~\n\n") + if cmd.HasParent() { + parent := cmd.Parent() + pname := parent.CommandPath() + ref = strings.Replace(pname, " ", "_", -1) + buf.WriteString(fmt.Sprintf("* %s \t - %s\n", linkHandler(pname, ref), parent.Short)) + cmd.VisitParents(func(c *cobra.Command) { + if c.DisableAutoGenTag { + cmd.DisableAutoGenTag = c.DisableAutoGenTag + } + }) + } + + children := cmd.Commands() + sort.Sort(byName(children)) + + for _, child := range children { + if !child.IsAvailableCommand() || child.IsAdditionalHelpTopicCommand() { + continue + } + cname := name + " " + child.Name() + ref = strings.Replace(cname, " ", "_", -1) + buf.WriteString(fmt.Sprintf("* %s \t - %s\n", linkHandler(cname, ref), child.Short)) + } + buf.WriteString("\n") + } + if !cmd.DisableAutoGenTag { + buf.WriteString("*Auto generated by spf13/cobra on " + time.Now().Format("2-Jan-2006") + "*\n") + } + _, err := buf.WriteTo(w) + return err +} + +// GenReSTTree will generate a ReST page for this command and all +// descendants in the directory given. +// This function may not work correctly if your command names have `-` in them. +// If you have `cmd` with two subcmds, `sub` and `sub-third`, +// and `sub` has a subcommand called `third`, it is undefined which +// help output will be in the file `cmd-sub-third.1`. +func GenReSTTree(cmd *cobra.Command, dir string) error { + emptyStr := func(s string) string { return "" } + return GenReSTTreeCustom(cmd, dir, emptyStr, defaultLinkHandler) +} + +// GenReSTTreeCustom is the the same as GenReSTTree, but +// with custom filePrepender and linkHandler. +func GenReSTTreeCustom(cmd *cobra.Command, dir string, filePrepender func(string) string, linkHandler func(string, string) string) error { + for _, c := range cmd.Commands() { + if !c.IsAvailableCommand() || c.IsAdditionalHelpTopicCommand() { + continue + } + if err := GenReSTTreeCustom(c, dir, filePrepender, linkHandler); err != nil { + return err + } + } + + basename := strings.Replace(cmd.CommandPath(), " ", "_", -1) + ".rst" + filename := filepath.Join(dir, basename) + f, err := os.Create(filename) + if err != nil { + return err + } + defer f.Close() + + if _, err := io.WriteString(f, filePrepender(filename)); err != nil { + return err + } + if err := GenReSTCustom(cmd, f, linkHandler); err != nil { + return err + } + return nil +} + +// adapted from: https://github.com/kr/text/blob/main/indent.go +func indentString(s, p string) string { + var res []byte + b := []byte(s) + prefix := []byte(p) + bol := true + for _, c := range b { + if bol && c != '\n' { + res = append(res, prefix...) + } + res = append(res, c) + bol = c == '\n' + } + return string(res) +} diff --git a/doc/rest_docs.md b/doc/rest_docs.md new file mode 100644 index 00000000..6098430e --- /dev/null +++ b/doc/rest_docs.md @@ -0,0 +1,114 @@ +# Generating ReStructured Text Docs For Your Own cobra.Command + +Generating ReST pages from a cobra command is incredibly easy. An example is as follows: + +```go +package main + +import ( + "log" + + "github.com/spf13/cobra" + "github.com/spf13/cobra/doc" +) + +func main() { + cmd := &cobra.Command{ + Use: "test", + Short: "my test program", + } + err := doc.GenReSTTree(cmd, "/tmp") + if err != nil { + log.Fatal(err) + } +} +``` + +That will get you a ReST document `/tmp/test.rst` + +## Generate ReST docs for the entire command tree + +This program can actually generate docs for the kubectl command in the kubernetes project + +```go +package main + +import ( + "log" + "io/ioutil" + "os" + + "k8s.io/kubernetes/pkg/kubectl/cmd" + cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" + + "github.com/spf13/cobra/doc" +) + +func main() { + kubectl := cmd.NewKubectlCommand(cmdutil.NewFactory(nil), os.Stdin, ioutil.Discard, ioutil.Discard) + err := doc.GenReSTTree(kubectl, "./") + if err != nil { + log.Fatal(err) + } +} +``` + +This will generate a whole series of files, one for each command in the tree, in the directory specified (in this case "./") + +## Generate ReST docs for a single command + +You may wish to have more control over the output, or only generate for a single command, instead of the entire command tree. If this is the case you may prefer to `GenReST` instead of `GenReSTTree` + +```go + out := new(bytes.Buffer) + err := doc.GenReST(cmd, out) + if err != nil { + log.Fatal(err) + } +``` + +This will write the ReST doc for ONLY "cmd" into the out, buffer. + +## Customize the output + +Both `GenReST` and `GenReSTTree` have alternate versions with callbacks to get some control of the output: + +```go +func GenReSTTreeCustom(cmd *Command, dir string, filePrepender func(string) string, linkHandler func(string, string) string) error { + //... +} +``` + +```go +func GenReSTCustom(cmd *Command, out *bytes.Buffer, linkHandler func(string, string) string) error { + //... +} +``` + +The `filePrepender` will prepend the return value given the full filepath to the rendered ReST file. A common use case is to add front matter to use the generated documentation with [Hugo](http://gohugo.io/): + +```go +const fmTemplate = `--- +date: %s +title: "%s" +slug: %s +url: %s +--- +` +filePrepender := func(filename string) string { + now := time.Now().Format(time.RFC3339) + name := filepath.Base(filename) + base := strings.TrimSuffix(name, path.Ext(name)) + url := "/commands/" + strings.ToLower(base) + "/" + return fmt.Sprintf(fmTemplate, now, strings.Replace(base, "_", " ", -1), base, url) +} +``` + +The `linkHandler` can be used to customize the rendered links to the commands, given a command name and reference. This is useful while converting rst to html or while generating documentation with tools like Sphinx where `:ref:` is used: + +```go +// Sphinx cross-referencing format +linkHandler := func(name, ref string) string { + return fmt.Sprintf(":ref:`%s <%s>`", name, ref) +} +``` diff --git a/doc/rest_docs_test.go b/doc/rest_docs_test.go new file mode 100644 index 00000000..aa3186e8 --- /dev/null +++ b/doc/rest_docs_test.go @@ -0,0 +1,76 @@ +package doc + +import ( + "bytes" + "io/ioutil" + "os" + "path/filepath" + "testing" + + "github.com/spf13/cobra" +) + +func TestGenRSTDoc(t *testing.T) { + // We generate on a subcommand so we have both subcommands and parents + buf := new(bytes.Buffer) + if err := GenReST(echoCmd, buf); err != nil { + t.Fatal(err) + } + output := buf.String() + + checkStringContains(t, output, echoCmd.Long) + checkStringContains(t, output, echoCmd.Example) + checkStringContains(t, output, "boolone") + checkStringContains(t, output, "rootflag") + checkStringContains(t, output, rootCmd.Short) + checkStringContains(t, output, echoSubCmd.Short) + checkStringOmits(t, output, deprecatedCmd.Short) +} + +func TestGenRSTNoTag(t *testing.T) { + rootCmd.DisableAutoGenTag = true + defer func() { rootCmd.DisableAutoGenTag = false }() + + buf := new(bytes.Buffer) + if err := GenReST(rootCmd, buf); err != nil { + t.Fatal(err) + } + output := buf.String() + + unexpected := "Auto generated" + checkStringOmits(t, output, unexpected) +} + +func TestGenRSTTree(t *testing.T) { + c := &cobra.Command{Use: "do [OPTIONS] arg1 arg2"} + + tmpdir, err := ioutil.TempDir("", "test-gen-rst-tree") + if err != nil { + t.Fatalf("Failed to create tmpdir: %s", err.Error()) + } + defer os.RemoveAll(tmpdir) + + if err := GenReSTTree(c, tmpdir); err != nil { + t.Fatalf("GenReSTTree failed: %s", err.Error()) + } + + if _, err := os.Stat(filepath.Join(tmpdir, "do.rst")); err != nil { + t.Fatalf("Expected file 'do.rst' to exist") + } +} + +func BenchmarkGenReSTToFile(b *testing.B) { + file, err := ioutil.TempFile("", "") + if err != nil { + b.Fatal(err) + } + defer os.Remove(file.Name()) + defer file.Close() + + b.ResetTimer() + for i := 0; i < b.N; i++ { + if err := GenReST(rootCmd, file); err != nil { + b.Fatal(err) + } + } +} diff --git a/doc/yaml_docs_test.go b/doc/yaml_docs_test.go index 29e985e4..c5a63594 100644 --- a/doc/yaml_docs_test.go +++ b/doc/yaml_docs_test.go @@ -5,92 +5,42 @@ import ( "io/ioutil" "os" "path/filepath" - "strings" "testing" "github.com/spf13/cobra" ) func TestGenYamlDoc(t *testing.T) { - c := initializeWithRootCmd() - // Need two commands to run the command alphabetical sort - cmdEcho.AddCommand(cmdTimes, cmdEchoSub, cmdDeprecated) - c.AddCommand(cmdPrint, cmdEcho) - cmdRootWithRun.PersistentFlags().StringVarP(&flags2a, "rootflag", "r", "two", strtwoParentHelp) - - out := new(bytes.Buffer) - // We generate on s subcommand so we have both subcommands and parents - if err := GenYaml(cmdEcho, out); err != nil { + buf := new(bytes.Buffer) + if err := GenYaml(echoCmd, buf); err != nil { t.Fatal(err) } - found := out.String() + output := buf.String() - // Our description - expected := cmdEcho.Long - if !strings.Contains(found, expected) { - t.Errorf("Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", expected, found) - } - - // Better have our example - expected = cmdEcho.Example - if !strings.Contains(found, expected) { - t.Errorf("Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", expected, found) - } - - // A local flag - expected = "boolone" - if !strings.Contains(found, expected) { - t.Errorf("Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", expected, found) - } - - // persistent flag on parent - expected = "rootflag" - if !strings.Contains(found, expected) { - t.Errorf("Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", expected, found) - } - - // We better output info about our parent - expected = cmdRootWithRun.Short - if !strings.Contains(found, expected) { - t.Errorf("Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", expected, found) - } - - // And about subcommands - expected = cmdEchoSub.Short - if !strings.Contains(found, expected) { - t.Errorf("Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", expected, found) - } - - unexpected := cmdDeprecated.Short - if strings.Contains(found, unexpected) { - t.Errorf("Unexpected response.\nFound: %v\nBut should not have!!\n", unexpected) - } + checkStringContains(t, output, echoCmd.Long) + checkStringContains(t, output, echoCmd.Example) + checkStringContains(t, output, "boolone") + checkStringContains(t, output, "rootflag") + checkStringContains(t, output, rootCmd.Short) + checkStringContains(t, output, echoSubCmd.Short) } func TestGenYamlNoTag(t *testing.T) { - c := initializeWithRootCmd() - // Need two commands to run the command alphabetical sort - cmdEcho.AddCommand(cmdTimes, cmdEchoSub, cmdDeprecated) - c.AddCommand(cmdPrint, cmdEcho) - c.DisableAutoGenTag = true - cmdRootWithRun.PersistentFlags().StringVarP(&flags2a, "rootflag", "r", "two", strtwoParentHelp) - out := new(bytes.Buffer) + rootCmd.DisableAutoGenTag = true + defer func() { rootCmd.DisableAutoGenTag = false }() - if err := GenYaml(c, out); err != nil { + buf := new(bytes.Buffer) + if err := GenYaml(rootCmd, buf); err != nil { t.Fatal(err) } - found := out.String() - - unexpected := "Auto generated" - checkStringOmits(t, found, unexpected) + output := buf.String() + checkStringOmits(t, output, "Auto generated") } func TestGenYamlTree(t *testing.T) { - cmd := &cobra.Command{ - Use: "do [OPTIONS] arg1 arg2", - } + c := &cobra.Command{Use: "do [OPTIONS] arg1 arg2"} tmpdir, err := ioutil.TempDir("", "test-gen-yaml-tree") if err != nil { @@ -98,7 +48,7 @@ func TestGenYamlTree(t *testing.T) { } defer os.RemoveAll(tmpdir) - if err := GenYamlTree(cmd, tmpdir); err != nil { + if err := GenYamlTree(c, tmpdir); err != nil { t.Fatalf("GenYamlTree failed: %s", err.Error()) } @@ -108,7 +58,6 @@ func TestGenYamlTree(t *testing.T) { } func BenchmarkGenYamlToFile(b *testing.B) { - c := initializeWithRootCmd() file, err := ioutil.TempFile("", "") if err != nil { b.Fatal(err) @@ -118,7 +67,7 @@ func BenchmarkGenYamlToFile(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { - if err := GenYaml(c, file); err != nil { + if err := GenYaml(rootCmd, file); err != nil { b.Fatal(err) } } diff --git a/zsh_completions.go b/zsh_completions.go index b350aeec..889c22e2 100644 --- a/zsh_completions.go +++ b/zsh_completions.go @@ -4,17 +4,29 @@ import ( "bytes" "fmt" "io" + "os" "strings" ) +// GenZshCompletionFile generates zsh completion file. +func (c *Command) GenZshCompletionFile(filename string) error { + outFile, err := os.Create(filename) + if err != nil { + return err + } + defer outFile.Close() + + return c.GenZshCompletion(outFile) +} + // GenZshCompletion generates a zsh completion file and writes to the passed writer. -func (cmd *Command) GenZshCompletion(w io.Writer) error { +func (c *Command) GenZshCompletion(w io.Writer) error { buf := new(bytes.Buffer) - writeHeader(buf, cmd) - maxDepth := maxDepth(cmd) + writeHeader(buf, c) + maxDepth := maxDepth(c) writeLevelMapping(buf, maxDepth) - writeLevelCases(buf, maxDepth, cmd) + writeLevelCases(buf, maxDepth, c) _, err := buf.WriteTo(w) return err diff --git a/zsh_completions_test.go b/zsh_completions_test.go index 08b85159..34e69496 100644 --- a/zsh_completions_test.go +++ b/zsh_completions_test.go @@ -77,10 +77,11 @@ func TestZshCompletion(t *testing.T) { t.Run(tc.name, func(t *testing.T) { buf := new(bytes.Buffer) tc.root.GenZshCompletion(buf) - completion := buf.String() + output := buf.String() + for _, expectedExpression := range tc.expectedExpressions { - if !strings.Contains(completion, expectedExpression) { - t.Errorf("expected completion to contain '%v' somewhere; got '%v'", expectedExpression, completion) + if !strings.Contains(output, expectedExpression) { + t.Errorf("Expected completion to contain %q somewhere; got %q", expectedExpression, output) } } })