From ef815836d2728ceb5afc45b8e3f0ca5f86d43b56 Mon Sep 17 00:00:00 2001 From: Eric Paris Date: Sun, 12 Apr 2015 14:48:54 -0400 Subject: [PATCH] Update spf13/cobra to the latest --- Godeps/Godeps.json | 2 +- .../src/github.com/spf13/cobra/.travis.yml | 4 +- .../src/github.com/spf13/cobra/README.md | 18 +- .../spf13/cobra/bash_completions.go | 343 ++++++++++++++++++ .../spf13/cobra/bash_completions.md | 146 ++++++++ .../spf13/cobra/bash_completions_test.go | 74 ++++ .../src/github.com/spf13/cobra/cobra.go | 8 + .../src/github.com/spf13/cobra/cobra_test.go | 199 +++++++++- .../src/github.com/spf13/cobra/command.go | 117 +++--- .../github.com/spf13/cobra/command_test.go | 90 +++++ .../src/github.com/spf13/cobra/md_docs.go | 121 ++++++ .../src/github.com/spf13/cobra/md_docs.md | 35 ++ .../github.com/spf13/cobra/md_docs_test.go | 64 ++++ 13 files changed, 1151 insertions(+), 70 deletions(-) create mode 100644 Godeps/_workspace/src/github.com/spf13/cobra/bash_completions.go create mode 100644 Godeps/_workspace/src/github.com/spf13/cobra/bash_completions.md create mode 100644 Godeps/_workspace/src/github.com/spf13/cobra/bash_completions_test.go create mode 100644 Godeps/_workspace/src/github.com/spf13/cobra/command_test.go create mode 100644 Godeps/_workspace/src/github.com/spf13/cobra/md_docs.go create mode 100644 Godeps/_workspace/src/github.com/spf13/cobra/md_docs.md create mode 100644 Godeps/_workspace/src/github.com/spf13/cobra/md_docs_test.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index d61484f4d53..866031536e6 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -396,7 +396,7 @@ }, { "ImportPath": "github.com/spf13/cobra", - "Rev": "79bd93d369fb73d640179208d4e2b1a748915567" + "Rev": "c0da825198c75814463e1b3018e42e438b771a5b" }, { "ImportPath": "github.com/spf13/pflag", diff --git a/Godeps/_workspace/src/github.com/spf13/cobra/.travis.yml b/Godeps/_workspace/src/github.com/spf13/cobra/.travis.yml index 6de9ac2318f..dc43afd61b9 100644 --- a/Godeps/_workspace/src/github.com/spf13/cobra/.travis.yml +++ b/Godeps/_workspace/src/github.com/spf13/cobra/.travis.yml @@ -1,6 +1,8 @@ language: go go: - - 1.1 + - 1.3 + - 1.4.2 + - tip script: - go test ./... - go build diff --git a/Godeps/_workspace/src/github.com/spf13/cobra/README.md b/Godeps/_workspace/src/github.com/spf13/cobra/README.md index 64c29ef1899..1b29a86e528 100644 --- a/Godeps/_workspace/src/github.com/spf13/cobra/README.md +++ b/Godeps/_workspace/src/github.com/spf13/cobra/README.md @@ -2,7 +2,7 @@ A Commander for modern go CLI interactions -[![Build Status](https://travis-ci.org/spf13/cobra.png)](https://travis-ci.org/spf13/cobra) +[![Build Status](https://travis-ci.org/spf13/cobra.svg)](https://travis-ci.org/spf13/cobra) ## Overview @@ -143,6 +143,15 @@ A flag can also be assigned locally which will only apply to that specific comma HugoCmd.Flags().StringVarP(&Source, "source", "s", "", "Source directory to read from") +### Remove a command from its parent + +Removing a command is not a common action is simple program but it allows 3rd parties to customize an existing command tree. + +In this exemple, we remove the existing `VersionCmd` command of an existing root command, and we replace it by our own version. + + mainlib.RootCmd.RemoveCommand(mainlib.VersionCmd) + mainlib.RootCmd.AddCommand(versionCmd) + ### Once all commands and flags are defined, Execute the commands Execute should be run on the root for clarity, though it can be called on any command. @@ -339,6 +348,13 @@ Like help the function and template are over ridable through public methods. command.SetUsageTemplate(s string) +## Generating markdown formatted 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](md_docs.md) + +## Generating bash completions for your command + +Cobra can generate a bash completions file. If you add more information to your command these completions can be amazingly powerful and flexible. Read more about [Bash Completions](bash_completions.md) ## Debugging diff --git a/Godeps/_workspace/src/github.com/spf13/cobra/bash_completions.go b/Godeps/_workspace/src/github.com/spf13/cobra/bash_completions.go new file mode 100644 index 00000000000..1c5640745f1 --- /dev/null +++ b/Godeps/_workspace/src/github.com/spf13/cobra/bash_completions.go @@ -0,0 +1,343 @@ +package cobra + +import ( + "bytes" + "fmt" + "os" + "strings" + + "github.com/spf13/pflag" +) + +const ( + BashCompFilenameExt = "cobra_annotation_bash_completion_filename_extentions" + BashCompOneRequiredFlag = "cobra_annotation_bash_completion_one_required_flag" +) + +func preamble(out *bytes.Buffer) { + fmt.Fprintf(out, `#!/bin/bash + + +__debug() +{ + if [[ -n ${BASH_COMP_DEBUG_FILE} ]]; then + echo "$*" >> ${BASH_COMP_DEBUG_FILE} + fi +} + +__index_of_word() +{ + local w word=$1 + shift + index=0 + for w in "$@"; do + [[ $w = "$word" ]] && return + index=$((index+1)) + done + index=-1 +} + +__contains_word() +{ + local w word=$1; shift + for w in "$@"; do + [[ $w = "$word" ]] && return + done + return 1 +} + +__handle_reply() +{ + __debug "${FUNCNAME}" + case $cur in + -*) + compopt -o nospace + local allflags + if [ ${#must_have_one_flag[@]} -ne 0 ]; then + allflags=("${must_have_one_flag[@]}") + else + allflags=("${flags[*]} ${two_word_flags[*]}") + fi + COMPREPLY=( $(compgen -W "${allflags[*]}" -- "$cur") ) + [[ $COMPREPLY == *= ]] || compopt +o nospace + return 0; + ;; + esac + + # check if we are handling a flag with special work handling + local index + __index_of_word "${prev}" "${flags_with_completion[@]}" + if [[ ${index} -ge 0 ]]; then + ${flags_completion[${index}]} + return + fi + + # we are parsing a flag and don't have a special handler, no completion + if [[ ${cur} != "${words[cword]}" ]]; then + return + fi + + local completions + if [[ ${#must_have_one_flag[@]} -ne 0 ]]; then + completions=("${must_have_one_flag[@]}") + elif [[ ${#must_have_one_noun[@]} -ne 0 ]]; then + completions=("${must_have_one_noun[@]}") + else + completions=("${commands[@]}") + fi + COMPREPLY=( $(compgen -W "${completions[*]}" -- "$cur") ) + + if [[ ${#COMPREPLY[@]} -eq 0 ]]; then + declare -F __custom_func >/dev/null && __custom_func + fi +} + +__handle_flag() +{ + __debug "${FUNCNAME}: c is $c words[c] is ${words[c]}" + + # if a command required a flag, and we found it, unset must_have_one_flag() + local flagname=${words[c]} + # if the word contained an = + if [[ ${words[c]} == *"="* ]]; then + flagname=${flagname%%=*} # strip everything after the = + flagname="${flagname}=" # but put the = back + fi + __debug "${FUNCNAME}: looking for ${flagname}" + if __contains_word "${flagname}" "${must_have_one_flag[@]}"; then + must_have_one_flag=() + fi + + # skip the argument to a two word flag + if __contains_word "${words[c]}" "${two_word_flags[@]}"; then + c=$((c+1)) + # if we are looking for a flags value, don't show commands + if [[ $c -eq $cword ]]; then + commands=() + fi + fi + + # skip the flag itself + c=$((c+1)) + +} + +__handle_noun() +{ + __debug "${FUNCNAME}: c is $c words[c] is ${words[c]}" + + if __contains_word "${words[c]}" "${must_have_one_noun[@]}"; then + must_have_one_noun=() + fi + + nouns+=("${words[c]}") + c=$((c+1)) +} + +__handle_command() +{ + __debug "${FUNCNAME}: c is $c words[c] is ${words[c]}" + + local next_command + if [[ -n ${last_command} ]]; then + next_command="_${last_command}_${words[c]}" + else + next_command="_${words[c]}" + fi + c=$((c+1)) + __debug "${FUNCNAME}: looking for ${next_command}" + declare -F $next_command >/dev/null && $next_command +} + +__handle_word() +{ + if [[ $c -ge $cword ]]; then + __handle_reply + return + fi + __debug "${FUNCNAME}: c is $c words[c] is ${words[c]}" + if [[ "${words[c]}" == -* ]]; then + __handle_flag + elif __contains_word "${words[c]}" "${commands[@]}"; then + __handle_command + else + __handle_noun + fi + __handle_word +} + +`) +} + +func postscript(out *bytes.Buffer, name string) { + fmt.Fprintf(out, "__start_%s()\n", name) + fmt.Fprintf(out, `{ + local cur prev words cword split + _init_completion -s || return + + local completions_func + local c=0 + local flags=() + local two_word_flags=() + local flags_with_completion=() + local flags_completion=() + local commands=("%s") + local must_have_one_flag=() + local must_have_one_noun=() + local last_command + local nouns=() + + __handle_word +} + +`, name) + fmt.Fprintf(out, "complete -F __start_%s %s\n", name, name) + fmt.Fprintf(out, "# ex: ts=4 sw=4 et filetype=sh\n") +} + +func writeCommands(cmd *Command, out *bytes.Buffer) { + fmt.Fprintf(out, " commands=()\n") + for _, c := range cmd.Commands() { + fmt.Fprintf(out, " commands+=(%q)\n", c.Name()) + } + fmt.Fprintf(out, "\n") +} + +func writeFlagHandler(name string, annotations map[string][]string, out *bytes.Buffer) { + for key, value := range annotations { + switch key { + case BashCompFilenameExt: + fmt.Fprintf(out, " flags_with_completion+=(%q)\n", name) + + ext := strings.Join(value, "|") + ext = "_filedir '@(" + ext + ")'" + fmt.Fprintf(out, " flags_completion+=(%q)\n", ext) + } + } +} + +func writeShortFlag(flag *pflag.Flag, out *bytes.Buffer) { + b := (flag.Value.Type() == "bool") + name := flag.Shorthand + format := " " + if !b { + format += "two_word_" + } + format += "flags+=(\"-%s\")\n" + fmt.Fprintf(out, format, name) + writeFlagHandler("-"+name, flag.Annotations, out) +} + +func writeFlag(flag *pflag.Flag, out *bytes.Buffer) { + b := (flag.Value.Type() == "bool") + name := flag.Name + format := " flags+=(\"--%s" + if !b { + format += "=" + } + format += "\")\n" + fmt.Fprintf(out, format, name) + writeFlagHandler("--"+name, flag.Annotations, out) +} + +func writeFlags(cmd *Command, out *bytes.Buffer) { + fmt.Fprintf(out, ` flags=() + two_word_flags=() + flags_with_completion=() + flags_completion=() + +`) + cmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) { + writeFlag(flag, out) + if len(flag.Shorthand) > 0 { + writeShortFlag(flag, out) + } + }) + + fmt.Fprintf(out, "\n") +} + +func writeRequiredFlag(cmd *Command, out *bytes.Buffer) { + fmt.Fprintf(out, " must_have_one_flag=()\n") + flags := cmd.NonInheritedFlags() + flags.VisitAll(func(flag *pflag.Flag) { + for key, _ := range flag.Annotations { + switch key { + case BashCompOneRequiredFlag: + format := " must_have_one_flag+=(\"--%s" + b := (flag.Value.Type() == "bool") + if !b { + format += "=" + } + format += "\")\n" + fmt.Fprintf(out, format, flag.Name) + + if len(flag.Shorthand) > 0 { + fmt.Fprintf(out, " must_have_one_flag+=(\"-%s\")\n", flag.Shorthand) + } + } + } + }) +} + +func writeRequiredNoun(cmd *Command, out *bytes.Buffer) { + fmt.Fprintf(out, " must_have_one_noun=()\n") + for _, value := range cmd.ValidArgs { + fmt.Fprintf(out, " must_have_one_noun+=(%q)\n", value) + } +} + +func gen(cmd *Command, out *bytes.Buffer) { + for _, c := range cmd.Commands() { + gen(c, out) + } + commandName := cmd.CommandPath() + commandName = strings.Replace(commandName, " ", "_", -1) + fmt.Fprintf(out, "_%s()\n{\n", commandName) + fmt.Fprintf(out, " last_command=%q\n", commandName) + writeCommands(cmd, out) + writeFlags(cmd, out) + writeRequiredFlag(cmd, out) + writeRequiredNoun(cmd, out) + fmt.Fprintf(out, "}\n\n") +} + +func (cmd *Command) GenBashCompletion(out *bytes.Buffer) { + preamble(out) + if len(cmd.BashCompletionFunction) > 0 { + fmt.Fprintf(out, "%s\n", cmd.BashCompletionFunction) + } + gen(cmd, out) + postscript(out, cmd.Name()) +} + +func (cmd *Command) GenBashCompletionFile(filename string) error { + out := new(bytes.Buffer) + + cmd.GenBashCompletion(out) + + outFile, err := os.Create(filename) + if err != nil { + return err + } + defer outFile.Close() + + _, err = outFile.Write(out.Bytes()) + if err != nil { + return err + } + return nil +} + +func (cmd *Command) MarkFlagRequired(name string) { + flag := cmd.Flags().Lookup(name) + if flag == nil { + return + } + if flag.Annotations == nil { + flag.Annotations = make(map[string][]string) + } + annotation := make([]string, 1) + annotation[0] = "true" + flag.Annotations[BashCompOneRequiredFlag] = annotation +} diff --git a/Godeps/_workspace/src/github.com/spf13/cobra/bash_completions.md b/Godeps/_workspace/src/github.com/spf13/cobra/bash_completions.md new file mode 100644 index 00000000000..e1a5d56d00b --- /dev/null +++ b/Godeps/_workspace/src/github.com/spf13/cobra/bash_completions.md @@ -0,0 +1,146 @@ +# Generating Bash Completions For Your Own cobra.Command + +Generating bash completions from a cobra command is incredibly easy. An actual program which does so for the kubernetes kubectl binary is as follows: + +```go +package main + +import ( + "io/ioutil" + "os" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd" +) + +func main() { + kubectl := cmd.NewFactory(nil).NewKubectlCommand(os.Stdin, ioutil.Discard, ioutil.Discard) + kubectl.GenBashCompletionFile("out.sh") +} +``` + +That will get you completions of subcommands and flags. If you make additional annotations to your code, you can get even more intelligent and flexible behavior. + +## Creating your own custom functions + +Some more actual code that works in kubernetes: + +```bash +const ( + bash_completion_func = `__kubectl_parse_get() +{ + local kubectl_output out + if kubectl_output=$(kubectl get --no-headers "$1" 2>/dev/null); then + out=($(echo "${kubectl_output}" | awk '{print $1}')) + COMPREPLY=( $( compgen -W "${out[*]}" -- "$cur" ) ) + fi +} + +__kubectl_get_resource() +{ + if [[ ${#nouns[@]} -eq 0 ]]; then + return 1 + fi + __kubectl_parse_get ${nouns[${#nouns[@]} -1]} + if [[ $? -eq 0 ]]; then + return 0 + fi +} + +__custom_func() { + case ${last_command} in + kubectl_get | kubectl_describe | kubectl_delete | kubectl_stop) + __kubectl_get_resource + return + ;; + *) + ;; + esac +} +`) +``` + +And then I set that in my command definition: + +```go +cmds := &cobra.Command{ + Use: "kubectl", + Short: "kubectl controls the Kubernetes cluster manager", + Long: `kubectl controls the Kubernetes cluster manager. + +Find more information at https://github.com/GoogleCloudPlatform/kubernetes.`, + Run: runHelp, + BashCompletionFunction: bash_completion_func, +} +``` + +The `BashCompletionFunction` option is really only valid/useful on the root command. Doing the above will cause `__custom_func()` to be called when the built in processor was unable to find a solution. In the case of kubernetes a valid command might look something like `kubectl get pod [mypod]`. If you type `kubectl get pod [tab][tab]` the `__customc_func()` will run because the cobra.Command only understood "kubectl" and "get." `__custom_func()` will see that the cobra.Command is "kubectl_get" and will thus call another helper `__kubectl_get_resource()`. `__kubectl_get_resource` will look at the 'nouns' collected. In our example the only noun will be `pod`. So it will call `__kubectl_parse_get pod`. `__kubectl_parse_get` will actually call out to kubernetes and get any pods. It will then set `COMPREPLY` to valid pods! + +## Have the completions code complete your 'nouns' + +In the above example "pod" was assumed to already be typed. But if you want `kubectl get [tab][tab]` to show a list of valid "nouns" you have to set them. Simplified code from `kubectl get` looks like: + +```go +validArgs []string = { "pods", "nodes", "services", "replicationControllers" } + +cmd := &cobra.Command{ + Use: "get [(-o|--output=)json|yaml|template|...] (RESOURCE [NAME] | RESOURCE/NAME ...)", + Short: "Display one or many resources", + Long: get_long, + Example: get_example, + Run: func(cmd *cobra.Command, args []string) { + err := RunGet(f, out, cmd, args) + util.CheckErr(err) + }, + ValidArgs: validArgs, +} +``` + +Notice we put the "ValidArgs" on the "get" subcommand. Doing so will give results like + +```bash +# kubectl get [tab][tab] +nodes pods replicationControllers services +``` + +## Mark flags as required + +Most of the time completions will only show subcommands. But if a flag is required to make a subcommand work, you probably want it to show up when the user types [tab][tab]. Marking a flag as 'Required' is incredibly easy. + +```go +cmd.MarkFlagRequired("pod") +cmd.MarkFlagRequired("container") +``` + +and you'll get something like + +```bash +# kubectl exec [tab][tab][tab] +-c --container= -p --pod= +``` + +# Specify valid filename extentions for flags that take a filename + +In this example we use --filename= and expect to get a json or yaml file as the argument. To make this easier we annotate the --filename flag with valid filename extensions. + +```go + annotations := make([]string, 3) + annotations[0] = "json" + annotations[1] = "yaml" + annotations[2] = "yml" + + annotation := make(map[string][]string) + annotation[cobra.BashCompFilenameExt] = annotations + + flag := &pflag.Flag{"filename", "f", usage, value, value.String(), false, annotation} + cmd.Flags().AddFlag(flag) +``` + +Now when you run a command with this filename flag you'll get something like + +```bash +# kubectl create -f +test/ example/ rpmbuild/ +hello.yml test.json +``` + +So while there are many other files in the CWD it only shows me subdirs and those with valid extensions. diff --git a/Godeps/_workspace/src/github.com/spf13/cobra/bash_completions_test.go b/Godeps/_workspace/src/github.com/spf13/cobra/bash_completions_test.go new file mode 100644 index 00000000000..12dc96049cd --- /dev/null +++ b/Godeps/_workspace/src/github.com/spf13/cobra/bash_completions_test.go @@ -0,0 +1,74 @@ +package cobra + +import ( + "bytes" + "fmt" + "os" + "strings" + "testing" +) + +var _ = fmt.Println +var _ = os.Stderr + +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) + } +} + +// World worst custom function, just keep telling you to enter hello! +const ( + bash_completion_func = `__custom_func() { +COMPREPLY=( "hello" ) +} +` +) + +func TestBashCompletions(t *testing.T) { + c := initializeWithRootCmd() + cmdEcho.AddCommand(cmdTimes) + c.AddCommand(cmdEcho, cmdPrint) + + // custom completion function + c.BashCompletionFunction = bash_completion_func + + // required flag + c.MarkFlagRequired("introot") + + // valid nounds + validArgs := []string{"pods", "nodes", "services", "replicationControllers"} + c.ValidArgs = validArgs + + // filename extentions + annotations := make([]string, 3) + annotations[0] = "json" + annotations[1] = "yaml" + annotations[2] = "yml" + + annotation := make(map[string][]string) + annotation[BashCompFilenameExt] = annotations + + var flagval string + c.Flags().StringVar(&flagval, "filename", "", "Enter a filename") + flag := c.Flags().Lookup("filename") + flag.Annotations = annotation + + out := new(bytes.Buffer) + c.GenBashCompletion(out) + str := out.String() + + 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 for required flags + check(t, str, `must_have_one_flag+=("--introot=")`) + // check for custom completion function + check(t, str, `COMPREPLY=( "hello" )`) + // check for required nouns + check(t, str, `must_have_one_noun+=("pods")`) + // check for filename extention flags + check(t, str, `flags_completion+=("_filedir '@(json|yaml|yml)'")`) +} diff --git a/Godeps/_workspace/src/github.com/spf13/cobra/cobra.go b/Godeps/_workspace/src/github.com/spf13/cobra/cobra.go index 1b5ca36024a..78b92b0af3b 100644 --- a/Godeps/_workspace/src/github.com/spf13/cobra/cobra.go +++ b/Godeps/_workspace/src/github.com/spf13/cobra/cobra.go @@ -31,6 +31,14 @@ var initializers []func() // Set this to true to enable it var EnablePrefixMatching bool = false +// enables an information splash screen on Windows if the CLI is started from explorer.exe. +var EnableWindowsMouseTrap bool = true + +var MousetrapHelpText string = `This is a command line tool + +You need to open cmd.exe and run it from there. +` + //OnInitialize takes a series of func() arguments and appends them to a slice of func(). func OnInitialize(y ...func()) { for _, x := range y { diff --git a/Godeps/_workspace/src/github.com/spf13/cobra/cobra_test.go b/Godeps/_workspace/src/github.com/spf13/cobra/cobra_test.go index 120bc5c7ffb..a258508947f 100644 --- a/Godeps/_workspace/src/github.com/spf13/cobra/cobra_test.go +++ b/Godeps/_workspace/src/github.com/spf13/cobra/cobra_test.go @@ -3,11 +3,14 @@ package cobra import ( "bytes" "fmt" + "os" + "runtime" "strings" "testing" ) var _ = fmt.Println +var _ = os.Stderr var tp, te, tt, t1 []string var flagb1, flagb2, flagb3, flagbr, flagbp bool @@ -15,6 +18,7 @@ var flags1, flags2a, flags2b, flags3 string var flagi1, flagi2, flagi3, flagir int var globalFlag1 bool var flagEcho, rootcalled bool +var versionUsed int const strtwoParentHelp = "help message for parent flag strtwo" const strtwoChildHelp = "help message for child flag strtwo" @@ -22,7 +26,7 @@ const strtwoChildHelp = "help message for child flag strtwo" var cmdPrint = &Command{ Use: "print [string to print]", Short: "Print anything to the screen", - Long: `an utterly useless command for testing.`, + Long: `an absolutely utterly useless command for testing.`, Run: func(cmd *Command, args []string) { tp = args }, @@ -33,15 +37,24 @@ var cmdEcho = &Command{ Aliases: []string{"say"}, Short: "Echo anything to the screen", Long: `an utterly useless command for testing.`, + Example: "Just run cobra-test echo", 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 cmdTimes = &Command{ Use: "times [# times] [string to echo]", Short: "Echo anything to the screen more times", - Long: `an slightly useless command for testing.`, + Long: `a slightly useless command for testing.`, Run: func(cmd *Command, args []string) { tt = args }, @@ -68,6 +81,30 @@ var cmdRootWithRun = &Command{ }, } +var cmdSubNoRun = &Command{ + Use: "subnorun", + Short: "A subcommand without a Run function", + Long: "A long output about a subcommand without a Run function", +} + +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 + }, +} + func flagInit() { cmdEcho.ResetFlags() cmdPrint.ResetFlags() @@ -75,6 +112,7 @@ func flagInit() { 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") @@ -86,6 +124,8 @@ func flagInit() { 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 commandInit() { @@ -95,6 +135,7 @@ func commandInit() { cmdRootNoRun.ResetCommands() cmdRootSameName.ResetCommands() cmdRootWithRun.ResetCommands() + cmdSubNoRun.ResetCommands() } func initialize() *Command { @@ -164,7 +205,7 @@ func fullTester(c *Command, input string) resulter { // Testing flag with invalid input c.SetOutput(buf) cmdEcho.AddCommand(cmdTimes) - c.AddCommand(cmdPrint, cmdEcho) + c.AddCommand(cmdPrint, cmdEcho, cmdSubNoRun) c.SetArgs(strings.Split(input, " ")) err := c.Execute() @@ -173,15 +214,26 @@ func fullTester(c *Command, input string) resulter { 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 checkResultContains(t *testing.T, x resulter, check string) { if !strings.Contains(x.Output, check) { - t.Errorf("Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", check, x.Output) + logErr(t, x.Output, check) } } func checkResultOmits(t *testing.T, x resulter, check string) { if strings.Contains(x.Output, check) { - t.Errorf("Unexpected response.\nExpecting to omit: \n %q\nGot:\n %q\n", check, x.Output) + logErr(t, x.Output, check) } } @@ -191,7 +243,7 @@ func checkOutputContains(t *testing.T, c *Command, check string) { c.Execute() if !strings.Contains(buf.String(), check) { - t.Errorf("Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", check, buf.String()) + logErr(t, buf.String(), check) } } @@ -417,6 +469,20 @@ func TestTrailingCommandFlags(t *testing.T) { } } +func TestInvalidSubCommandFlags(t *testing.T) { + cmd := initializeWithRootCmd() + cmd.AddCommand(cmdTimes) + + result := simpleTester(cmd, "times --inttwo=2 --badflag=bar") + + checkResultContains(t, result, "unknown flag: --badflag") + + if strings.Contains(result.Output, "unknown flag: --inttwo") { + t.Errorf("invalid --badflag flag shouldn't fail on 'unknown' --inttwo flag") + } + +} + func TestPersistentFlags(t *testing.T) { fullSetupTest("echo -s something -p more here") @@ -451,11 +517,14 @@ func TestPersistentFlags(t *testing.T) { } func TestHelpCommand(t *testing.T) { - c := fullSetupTest("help echo") - checkResultContains(t, c, cmdEcho.Long) + x := fullSetupTest("help") + checkResultContains(t, x, cmdRootWithRun.Long) - r := fullSetupTest("help echo times") - checkResultContains(t, r, cmdTimes.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) { @@ -465,6 +534,11 @@ func TestChildCommandHelp(t *testing.T) { checkResultContains(t, r, strtwoChildHelp) } +func TestNonRunChildHelp(t *testing.T) { + x := noRRSetupTest("subnorun") + checkResultContains(t, x, cmdSubNoRun.Long) +} + func TestRunnableRootCommand(t *testing.T) { fullSetupTest("") @@ -473,6 +547,53 @@ func TestRunnableRootCommand(t *testing.T) { } } +func TestRunnableRootCommandNilInput(t *testing.T) { + empty_arg := make([]string, 0) + c := initializeWithRootCmd() + + buf := new(bytes.Buffer) + // Testing flag with invalid input + c.SetOutput(buf) + cmdEcho.AddCommand(cmdTimes) + c.AddCommand(cmdPrint, cmdEcho) + c.SetArgs(empty_arg) + + c.Execute() + + if rootcalled != true { + t.Errorf("Root Function was not called") + } +} + +func TestRunnableRootCommandEmptyInput(t *testing.T) { + args := make([]string, 3) + args[0] = "" + args[1] = "--introot=12" + args[2] = "" + 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 != true { + t.Errorf("Root Function was not called.\n\nOutput was:\n\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") @@ -534,6 +655,24 @@ func TestFlagAccess(t *testing.T) { } } +func TestNoNRunnableRootCommandNilInput(t *testing.T) { + args := make([]string, 0) + c := initialize() + + 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 !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") @@ -554,6 +693,15 @@ func TestRootNoCommandHelp(t *testing.T) { } } +func TestRootUnknownCommand(t *testing.T) { + r := noRRSetupTest("bogus") + s := "Error: unknown command \"bogus\"\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) + } +} + func TestFlagsBeforeCommand(t *testing.T) { // short without space x := fullSetupTest("-i10 echo") @@ -607,3 +755,34 @@ func TestFlagsBeforeCommand(t *testing.T) { } } + +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 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") + } +} diff --git a/Godeps/_workspace/src/github.com/spf13/cobra/command.go b/Godeps/_workspace/src/github.com/spf13/cobra/command.go index a7d90886c3f..59654c25cb4 100644 --- a/Godeps/_workspace/src/github.com/spf13/cobra/command.go +++ b/Godeps/_workspace/src/github.com/spf13/cobra/command.go @@ -18,11 +18,13 @@ package cobra import ( "bytes" "fmt" + "github.com/inconshreveable/mousetrap" + flag "github.com/spf13/pflag" "io" "os" + "runtime" "strings" - - flag "github.com/spf13/pflag" + "time" ) // Command is just that, a command for your application. @@ -42,6 +44,10 @@ type Command struct { Long string // Examples of how to use the command Example string + // List of all valid non-flag arguments, used for bash completions *TODO* actually validate these + ValidArgs []string + // Custom functions used by the bash autocompletion generator + BashCompletionFunction string // Full set of flags flags *flag.FlagSet // Set of flags childrens of this command will inherit @@ -223,7 +229,7 @@ Aliases: Examples: {{ .Example }} -{{end}}{{ if .HasSubCommands}} +{{end}}{{ if .HasRunnableSubCommands}} Available Commands: {{range .Commands}}{{if .Runnable}} {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}} @@ -231,8 +237,9 @@ Available Commands: {{range .Commands}}{{if .Runnable}} {{ if .HasLocalFlags}}Flags: {{.LocalFlags.FlagUsages}}{{end}} {{ if .HasInheritedFlags}}Global Flags: -{{.InheritedFlags.FlagUsages}}{{end}}{{if .HasParent}}{{if and (gt .Commands 0) (gt .Parent.Commands 1) }} -Additional help topics: {{if gt .Commands 0 }}{{range .Commands}}{{if not .Runnable}} {{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{if gt .Parent.Commands 1 }}{{range .Parent.Commands}}{{if .Runnable}}{{if not (eq .Name $cmd.Name) }}{{end}} +{{.InheritedFlags.FlagUsages}}{{end}}{{if or (.HasHelpSubCommands) (.HasRunnableSiblings)}} +Additional help topics: +{{if .HasHelpSubCommands}}{{range .Commands}}{{if not .Runnable}} {{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{if .HasRunnableSiblings }}{{range .Parent.Commands}}{{if .Runnable}}{{if not (eq .Name $cmd.Name) }} {{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{end}} {{end}}{{ if .HasSubCommands }} Use "{{.Root.Name}} help [command]" for more information about a command. @@ -305,6 +312,8 @@ func stripFlags(args []string, c *Command) []string { inFlag = true case inFlag: inFlag = false + case y == "": + // strip empty commands, as the go tests expect this to be ok.... case !strings.HasPrefix(y, "-"): commands = append(commands, y) inFlag = false @@ -375,10 +384,9 @@ func (c *Command) Find(arrs []string) (*Command, []string, error) { commandFound, a := innerfind(c, arrs) - // if commander returned and the first argument (if it exists) doesn't - // match the command name, return nil & error - if commandFound.Name() == c.Name() && len(arrs[0]) > 0 && commandFound.Name() != arrs[0] { - return nil, a, fmt.Errorf("unknown command %q\nRun 'help' for usage.\n", a[0]) + // If we matched on the root, but we asked for a subcommand, return an error + if commandFound.Name() == c.Name() && len(stripFlags(arrs, c)) > 0 && commandFound.Name() != arrs[0] { + return nil, a, fmt.Errorf("unknown command %q", a[0]) } return commandFound, a, nil @@ -398,16 +406,6 @@ func (c *Command) Root() *Command { return findRoot(c) } -// execute the command determined by args and the command tree -func (c *Command) findAndExecute(args []string) (err error) { - - cmd, a, e := c.Find(args) - if e != nil { - return e - } - return cmd.execute(a) -} - func (c *Command) execute(a []string) (err error) { if c == nil { return fmt.Errorf("Called Execute() on a nil Command") @@ -474,6 +472,14 @@ func (c *Command) Execute() (err error) { return c.Root().Execute() } + if EnableWindowsMouseTrap && runtime.GOOS == "windows" { + if mousetrap.StartedByExplorer() { + c.Print(MousetrapHelpText) + time.Sleep(5 * time.Second) + os.Exit(1) + } + } + // initialize help as the last point possible to allow for user // overriding c.initHelp() @@ -494,55 +500,21 @@ func (c *Command) Execute() (err error) { c.Help() } } else { - err = c.findAndExecute(args) - } - - // Now handle the case where the root is runnable and only flags are provided - if err != nil && c.Runnable() { - // This is pretty much a custom version of the *Command.execute method - // with a few differences because it's the final command (no fall back) - e := c.ParseFlags(args) + cmd, flags, e := c.Find(args) if e != nil { - // Flags parsing had an error. - // If an error happens here, we have to report it to the user - c.Println(e.Error()) - // If an error happens search also for subcommand info about that - if c.cmdErrorBuf != nil && c.cmdErrorBuf.Len() > 0 { - c.Println(c.cmdErrorBuf.String()) - } else { - c.Usage() - } err = e - return } else { - // If help is called, regardless of other flags, we print that - if c.helpFlagVal { - c.Help() - return nil - } - - argWoFlags := c.Flags().Args() - if len(argWoFlags) > 0 { - // If there are arguments (not flags) one of the earlier - // cases should have caught it.. It means invalid usage - // print the usage - c.Usage() - } else { - // Only flags left... Call root.Run - c.preRun() - c.Run(c, argWoFlags) - err = nil - } + err = cmd.execute(flags) } } if err != nil { if err == flag.ErrHelp { c.Help() + } else { c.Println("Error:", err.Error()) - c.Printf("%v: invalid command %#q\n", c.Root().Name(), os.Args[1:]) - c.Printf("Run '%v help' for usage\n", c.Root().Name()) + c.Printf("Run '%v help' for usage.\n", c.Root().Name()) } } @@ -787,6 +759,37 @@ func (c *Command) HasSubCommands() bool { return len(c.commands) > 0 } +func (c *Command) HasRunnableSiblings() bool { + if !c.HasParent() { + return false + } + for _, sub := range c.parent.commands { + if sub.Runnable() { + return true + } + } + return false +} + +func (c *Command) HasHelpSubCommands() bool { + for _, sub := range c.commands { + if !sub.Runnable() { + return true + } + } + return false +} + +// Determine if the command has runnable children commands +func (c *Command) HasRunnableSubCommands() bool { + for _, sub := range c.commands { + if sub.Runnable() { + return true + } + } + return false +} + // Determine if the command is a child command func (c *Command) HasParent() bool { return c.parent != nil diff --git a/Godeps/_workspace/src/github.com/spf13/cobra/command_test.go b/Godeps/_workspace/src/github.com/spf13/cobra/command_test.go new file mode 100644 index 00000000000..477d84e7c8b --- /dev/null +++ b/Godeps/_workspace/src/github.com/spf13/cobra/command_test.go @@ -0,0 +1,90 @@ +package cobra + +import ( + "reflect" + "testing" +) + +func TestStripFlags(t *testing.T) { + tests := []struct { + input []string + output []string + }{ + { + []string{"foo", "bar"}, + []string{"foo", "bar"}, + }, + { + []string{"foo", "--bar", "-b"}, + []string{"foo"}, + }, + { + []string{"-b", "foo", "--bar", "bar"}, + []string{}, + }, + { + []string{"-i10", "echo"}, + []string{"echo"}, + }, + { + []string{"-i=10", "echo"}, + []string{"echo"}, + }, + { + []string{"--int=100", "echo"}, + []string{"echo"}, + }, + { + []string{"-ib", "echo", "-bfoo", "baz"}, + []string{"echo", "baz"}, + }, + { + []string{"-i=baz", "bar", "-i", "foo", "blah"}, + []string{"bar", "blah"}, + }, + { + []string{"--int=baz", "-bbar", "-i", "foo", "blah"}, + []string{"blah"}, + }, + { + []string{"--cat", "bar", "-i", "foo", "blah"}, + []string{"bar", "blah"}, + }, + { + []string{"-c", "bar", "-i", "foo", "blah"}, + []string{"bar", "blah"}, + }, + { + []string{"--persist", "bar"}, + []string{"bar"}, + }, + { + []string{"-p", "bar"}, + []string{"bar"}, + }, + } + + 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 + }, + } + + 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) + } + } +} diff --git a/Godeps/_workspace/src/github.com/spf13/cobra/md_docs.go b/Godeps/_workspace/src/github.com/spf13/cobra/md_docs.go new file mode 100644 index 00000000000..71bc8d5bd96 --- /dev/null +++ b/Godeps/_workspace/src/github.com/spf13/cobra/md_docs.go @@ -0,0 +1,121 @@ +//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 cobra + +import ( + "bytes" + "fmt" + "os" + "sort" + "strings" + "time" +) + +func printOptions(out *bytes.Buffer, cmd *Command, name string) { + flags := cmd.NonInheritedFlags() + flags.SetOutput(out) + if flags.HasFlags() { + fmt.Fprintf(out, "### Options\n\n```\n") + flags.PrintDefaults() + fmt.Fprintf(out, "```\n\n") + } + + parentFlags := cmd.InheritedFlags() + parentFlags.SetOutput(out) + if parentFlags.HasFlags() { + fmt.Fprintf(out, "### Options inherrited from parent commands\n\n```\n") + parentFlags.PrintDefaults() + fmt.Fprintf(out, "```\n\n") + } +} + +type byName []*Command + +func (s byName) Len() int { return len(s) } +func (s byName) Swap(i, j int) { s[i], s[j] = s[j], s[i] } +func (s byName) Less(i, j int) bool { return s[i].Name() < s[j].Name() } + +func GenMarkdown(cmd *Command, out *bytes.Buffer) { + name := cmd.CommandPath() + + short := cmd.Short + long := cmd.Long + if len(long) == 0 { + long = short + } + + fmt.Fprintf(out, "## %s\n\n", name) + fmt.Fprintf(out, "%s\n\n", short) + fmt.Fprintf(out, "### Synopsis\n\n") + fmt.Fprintf(out, "\n%s\n\n", long) + + if cmd.Runnable() { + fmt.Fprintf(out, "```\n%s\n```\n\n", cmd.UseLine()) + } + + if len(cmd.Example) > 0 { + fmt.Fprintf(out, "### Examples\n\n") + fmt.Fprintf(out, "```\n%s\n```\n\n", cmd.Example) + } + + printOptions(out, cmd, name) + + if len(cmd.Commands()) > 0 || cmd.HasParent() { + fmt.Fprintf(out, "### SEE ALSO\n") + if cmd.HasParent() { + parent := cmd.Parent() + pname := parent.CommandPath() + link := pname + ".md" + link = strings.Replace(link, " ", "_", -1) + fmt.Fprintf(out, "* [%s](%s)\t - %s\n", pname, link, parent.Short) + } + + children := cmd.Commands() + sort.Sort(byName(children)) + + for _, child := range children { + cname := name + " " + child.Name() + link := cname + ".md" + link = strings.Replace(link, " ", "_", -1) + fmt.Fprintf(out, "* [%s](%s)\t - %s\n", cname, link, child.Short) + } + fmt.Fprintf(out, "\n") + } + + fmt.Fprintf(out, "###### Auto generated by spf13/cobra at %s\n", time.Now().UTC()) +} + +func GenMarkdownTree(cmd *Command, dir string) { + for _, c := range cmd.Commands() { + GenMarkdownTree(c, dir) + } + + out := new(bytes.Buffer) + + GenMarkdown(cmd, out) + + filename := cmd.CommandPath() + filename = dir + strings.Replace(filename, " ", "_", -1) + ".md" + outFile, err := os.Create(filename) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + defer outFile.Close() + _, err = outFile.Write(out.Bytes()) + if err != nil { + fmt.Println(err) + os.Exit(1) + } +} diff --git a/Godeps/_workspace/src/github.com/spf13/cobra/md_docs.md b/Godeps/_workspace/src/github.com/spf13/cobra/md_docs.md new file mode 100644 index 00000000000..43b6c994713 --- /dev/null +++ b/Godeps/_workspace/src/github.com/spf13/cobra/md_docs.md @@ -0,0 +1,35 @@ +# Generating Markdown Docs For Your Own cobra.Command + +## Generate markdown docs for the entire command tree + +This program can actually generate docs for the kubectl command in the kubernetes project + +```go +package main + +import ( + "io/ioutil" + "os" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd" + "github.com/spf13/cobra" +) + +func main() { + kubectl := cmd.NewFactory(nil).NewKubectlCommand(os.Stdin, ioutil.Discard, ioutil.Discard) + cobra.GenMarkdownTree(kubectl, "./") +} +``` + +This will generate a whole series of files, one for each command in the tree, in the directory specified (in this case "./") + +## Generate markdown 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 `GenMarkdown()` instead of `GenMarkdownTree` + +```go + out := new(bytes.Buffer) + cobra.GenMarkdown(cmd, out) +``` + +This will write the markdown doc for ONLY "cmd" into the out, buffer. diff --git a/Godeps/_workspace/src/github.com/spf13/cobra/md_docs_test.go b/Godeps/_workspace/src/github.com/spf13/cobra/md_docs_test.go new file mode 100644 index 00000000000..33613af3a2b --- /dev/null +++ b/Godeps/_workspace/src/github.com/spf13/cobra/md_docs_test.go @@ -0,0 +1,64 @@ +package cobra + +import ( + "bytes" + "fmt" + "os" + "strings" + "testing" +) + +var _ = fmt.Println +var _ = os.Stderr + +func TestGenMdDoc(t *testing.T) { + c := initializeWithRootCmd() + // Need two commands to run the command alphabetical sort + cmdEcho.AddCommand(cmdTimes, cmdEchoSub) + 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 + GenMarkdown(cmdEcho, out) + found := out.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) + } + + fmt.Fprintf(os.Stdout, "%s\n", found) +}