Merge pull request #7776 from eparis/minor-bash-fixes

Cobra update and bash completions fix
This commit is contained in:
Jeff Lowdermilk 2015-05-05 17:12:39 -07:00
commit 221553b147
10 changed files with 276 additions and 56 deletions

2
Godeps/Godeps.json generated
View File

@ -484,7 +484,7 @@
},
{
"ImportPath": "github.com/spf13/cobra",
"Rev": "69e5f196b5d30673deb07a2221d89cf62e4b74ae"
"Rev": "bba56042cf767e329430e7c7f68c3f9f640b4b8b"
},
{
"ImportPath": "github.com/spf13/pflag",

View File

@ -348,6 +348,76 @@ Like help the function and template are over ridable through public methods.
command.SetUsageTemplate(s string)
## PreRun or 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`. `PersistendPostRun` and `PostRun` will be executed after `Run`. The `Persistent*Run` functions will be inherrited by children if they do not declare their own. These function are run in the following order:
- `PersistentPreRun`
- `PreRun`
- `Run`
- `PostRun`
- `PersistenPostRun`
And example of two commands which use all of these features is below. When the subcommand in executed it will run the root command's `PersistentPreRun` but not the root command's `PersistentPostRun`
```go
package main
import (
"fmt"
"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 subCmd = &cobra.Command{
Use: "sub [no options!]",
Short: "My sub command",
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.SetArgs([]string{""})
_ = rootCmd.Execute()
fmt.Print("\n")
rootCmd.SetArgs([]string{"sub", "arg1", "arg2"})
_ = rootCmd.Execute()
}
```
## 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)

View File

@ -22,7 +22,7 @@ func preamble(out *bytes.Buffer) {
__debug()
{
if [[ -n ${BASH_COMP_DEBUG_FILE} ]]; then
echo "$*" >> ${BASH_COMP_DEBUG_FILE}
echo "$*" >> "${BASH_COMP_DEBUG_FILE}"
fi
}
@ -93,6 +93,13 @@ __handle_reply()
fi
}
# The arguments should be in the form "ext1|ext2|extn"
__handle_filename_extension_flag()
{
local ext="$1"
_filedir "@(${ext})"
}
__handle_flag()
{
__debug "${FUNCNAME}: c is $c words[c] is ${words[c]}"
@ -173,10 +180,9 @@ __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
local cur prev words cword
_init_completion -s || return
local completions_func
local c=0
local flags=()
local two_word_flags=()
@ -199,6 +205,9 @@ func postscript(out *bytes.Buffer, name string) {
func writeCommands(cmd *Command, out *bytes.Buffer) {
fmt.Fprintf(out, " commands=()\n")
for _, c := range cmd.Commands() {
if len(c.Deprecated) > 0 {
continue
}
fmt.Fprintf(out, " commands+=(%q)\n", c.Name())
}
fmt.Fprintf(out, "\n")
@ -211,7 +220,7 @@ func writeFlagHandler(name string, annotations map[string][]string, out *bytes.B
fmt.Fprintf(out, " flags_with_completion+=(%q)\n", name)
ext := strings.Join(value, "|")
ext = "_filedir '@(" + ext + ")'"
ext = "__handle_filename_extension_flag " + ext
fmt.Fprintf(out, " flags_completion+=(%q)\n", ext)
}
}
@ -291,6 +300,9 @@ func writeRequiredNoun(cmd *Command, out *bytes.Buffer) {
func gen(cmd *Command, out *bytes.Buffer) {
for _, c := range cmd.Commands() {
if len(c.Deprecated) > 0 {
continue
}
gen(c, out)
}
commandName := cmd.CommandPath()

View File

@ -123,15 +123,18 @@ and you'll get something like
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"
annotations := []string{"json", "yaml", "yml"}
annotation := make(map[string][]string)
annotation[cobra.BashCompFilenameExt] = annotations
flag := &pflag.Flag{"filename", "f", usage, value, value.String(), false, annotation}
flag := &pflag.Flag{
Name: "filename",
Shorthand: "f",
Usage: usage,
Value: value,
DefValue: value.String(),
Annotations: annotation,
}
cmd.Flags().AddFlag(flag)
```

View File

@ -11,6 +11,12 @@ import (
var _ = fmt.Println
var _ = os.Stderr
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)
}
}
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)
@ -28,7 +34,7 @@ COMPREPLY=( "hello" )
func TestBashCompletions(t *testing.T) {
c := initializeWithRootCmd()
cmdEcho.AddCommand(cmdTimes)
c.AddCommand(cmdEcho, cmdPrint)
c.AddCommand(cmdEcho, cmdPrint, cmdDeprecated)
// custom completion function
c.BashCompletionFunction = bash_completion_func
@ -70,5 +76,7 @@ func TestBashCompletions(t *testing.T) {
// 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)'")`)
check(t, str, `flags_completion+=("__handle_filename_extension_flag json|yaml|yml")`)
checkOmit(t, str, cmdDeprecated.Name())
}

View File

@ -13,6 +13,7 @@ var _ = fmt.Println
var _ = os.Stderr
var tp, te, tt, t1 []string
var rootPersPre, echoPre, echoPersPre, timesPersPre []string
var flagb1, flagb2, flagb3, flagbr, flagbp bool
var flags1, flags2a, flags2b, flags3 string
var flagi1, flagi2, flagi3, flagir int
@ -38,6 +39,12 @@ var cmdEcho = &Command{
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
},
@ -51,10 +58,22 @@ var cmdEchoSub = &Command{
},
}
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) {
},
}
var cmdTimes = &Command{
Use: "times [# times] [string to echo]",
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
},
@ -64,6 +83,9 @@ var cmdRootNoRun = &Command{
Use: "cobra-test",
Short: "The root can run it's own function",
Long: "The root description for help",
PersistentPreRun: func(cmd *Command, args []string) {
rootPersPre = args
},
}
var cmdRootSameName = &Command{
@ -140,6 +162,8 @@ func commandInit() {
func initialize() *Command {
tt, tp, te = nil, nil, nil
rootPersPre, echoPre, echoPersPre, timesPersPre = nil, nil, nil, nil
var c = cmdRootNoRun
flagInit()
commandInit()
@ -148,6 +172,7 @@ func initialize() *Command {
func initializeWithSameName() *Command {
tt, tp, te = nil, nil, nil
rootPersPre, echoPre, echoPersPre, timesPersPre = nil, nil, nil, nil
var c = cmdRootSameName
flagInit()
commandInit()
@ -205,7 +230,7 @@ func fullTester(c *Command, input string) resulter {
// Testing flag with invalid input
c.SetOutput(buf)
cmdEcho.AddCommand(cmdTimes)
c.AddCommand(cmdPrint, cmdEcho, cmdSubNoRun)
c.AddCommand(cmdPrint, cmdEcho, cmdSubNoRun, cmdDeprecated)
c.SetArgs(strings.Split(input, " "))
err := c.Execute()
@ -812,3 +837,52 @@ func TestReplaceCommandWithRemove(t *testing.T) {
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(strings.Split("print echosub lala", " "))
rootCmd.Execute()
if rootPersPre == nil || len(rootPersPre) == 0 || rootPersPre[0] != "lala" {
t.Error("RootCmd PersistentPreRun not called but should have been")
}
}

View File

@ -48,15 +48,31 @@ type Command struct {
ValidArgs []string
// Custom functions used by the bash autocompletion generator
BashCompletionFunction string
// Is this command deprecated and should print this string when used?
Deprecated string
// Full set of flags
flags *flag.FlagSet
// Set of flags childrens of this command will inherit
pflags *flag.FlagSet
// Flags that are declared specifically by this command (not inherited).
lflags *flag.FlagSet
// Run runs the command.
// The args are the arguments after the command name.
// The *Run functions are executed in the following order:
// * PersistentPreRun()
// * PreRun()
// * Run()
// * PostRun()
// * PersistentPostRun()
// All functions get the same args, the arguments after the command name
// PersistentPreRun: children of this command will inherit and execute
PersistentPreRun func(cmd *Command, args []string)
// PreRun: children of this command will not inherit.
PreRun func(cmd *Command, args []string)
// Run: Typically the actual work function. Most commands will only implement this
Run func(cmd *Command, args []string)
// PostRun: run after the Run command.
PostRun func(cmd *Command, args []string)
// PersistentPostRun: children of this command will inherit and execute after PostRun
PersistentPostRun func(cmd *Command, args []string)
// Commands is the list of commands supported by this program.
commands []*Command
// Parent Command for this command
@ -231,7 +247,7 @@ Examples:
{{ .Example }}
{{end}}{{ if .HasRunnableSubCommands}}
Available Commands: {{range .Commands}}{{if .Runnable}}
Available Commands: {{range .Commands}}{{if and (.Runnable) (not .Deprecated)}}
{{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}
{{end}}
{{ if .HasLocalFlags}}Flags:
@ -239,7 +255,7 @@ Available Commands: {{range .Commands}}{{if .Runnable}}
{{ if .HasInheritedFlags}}Global Flags:
{{.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) }}
{{if .HasHelpSubCommands}}{{range .Commands}}{{if and (not .Runnable) (not .Deprecated)}} {{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{if .HasRunnableSiblings }}{{range .Parent.Commands}}{{if and (not .Runnable) (not .Deprecated)}}{{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.
@ -414,6 +430,10 @@ func (c *Command) execute(a []string) (err error) {
return fmt.Errorf("Called Execute() on a nil Command")
}
if len(c.Deprecated) > 0 {
c.Printf("Command %q is deprecated, %s\n", c.Name(), c.Deprecated)
}
err = c.ParseFlags(a)
if err == flag.ErrHelp {
c.Help()
@ -432,19 +452,40 @@ func (c *Command) execute(a []string) (err error) {
c.Usage()
r.SetOutput(out)
return err
} else {
// If help is called, regardless of other flags, we print that.
// Print help also if c.Run is nil.
if c.helpFlagVal || !c.Runnable() {
c.Help()
return nil
}
c.preRun()
argWoFlags := c.Flags().Args()
c.Run(c, argWoFlags)
}
// If help is called, regardless of other flags, we print that.
// Print help also if c.Run is nil.
if c.helpFlagVal || !c.Runnable() {
c.Help()
return nil
}
c.preRun()
argWoFlags := c.Flags().Args()
for p := c; p != nil; p = p.Parent() {
if p.PersistentPreRun != nil {
p.PersistentPreRun(c, argWoFlags)
break
}
}
if c.PreRun != nil {
c.PreRun(c, argWoFlags)
}
c.Run(c, argWoFlags)
if c.PostRun != nil {
c.PostRun(c, argWoFlags)
}
for p := c; p != nil; p = p.Parent() {
if p.PersistentPostRun != nil {
p.PersistentPostRun(c, argWoFlags)
break
}
}
return nil
}
func (c *Command) preRun() {
@ -495,20 +536,9 @@ func (c *Command) Execute() (err error) {
args = c.args
}
if len(args) == 0 {
// Only the executable is called and the root is runnable, run it
if c.Runnable() {
err = c.execute([]string(nil))
} else {
c.Help()
}
} else {
cmd, flags, e := c.Find(args)
if e != nil {
err = e
} else {
err = cmd.execute(flags)
}
cmd, flags, err := c.Find(args)
if err == nil {
err = cmd.execute(flags)
}
if err != nil {
@ -535,7 +565,9 @@ func (c *Command) initHelp() {
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(),
Run: c.HelpFunc(),
PersistentPreRun: func(cmd *Command, args []string) {},
PersistentPostRun: func(cmd *Command, args []string) {},
}
}
c.AddCommand(c.helpCommand)
@ -948,6 +980,13 @@ func (c *Command) mergePersistentFlags() {
c.PersistentFlags().VisitAll(addtolocal)
}
rmerge = func(x *Command) {
if !x.HasParent() {
flag.CommandLine.VisitAll(func(f *flag.Flag) {
if x.PersistentFlags().Lookup(f.Name) == nil {
x.PersistentFlags().AddFlag(f)
}
})
}
if x.HasPersistentFlags() {
x.PersistentFlags().VisitAll(func(f *flag.Flag) {
if c.Flags().Lookup(f.Name) == nil {

View File

@ -85,6 +85,9 @@ func GenMarkdown(cmd *Command, out *bytes.Buffer) {
sort.Sort(byName(children))
for _, child := range children {
if len(child.Deprecated) > 0 {
continue
}
cname := name + " " + child.Name()
link := cname + ".md"
link = strings.Replace(link, " ", "_", -1)

View File

@ -14,7 +14,7 @@ var _ = os.Stderr
func TestGenMdDoc(t *testing.T) {
c := initializeWithRootCmd()
// Need two commands to run the command alphabetical sort
cmdEcho.AddCommand(cmdTimes, cmdEchoSub)
cmdEcho.AddCommand(cmdTimes, cmdEchoSub, cmdDeprecated)
c.AddCommand(cmdPrint, cmdEcho)
cmdRootWithRun.PersistentFlags().StringVarP(&flags2a, "rootflag", "r", "two", strtwoParentHelp)
@ -59,4 +59,9 @@ func TestGenMdDoc(t *testing.T) {
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)
}
}

View File

@ -4,7 +4,7 @@
__debug()
{
if [[ -n ${BASH_COMP_DEBUG_FILE} ]]; then
echo "$*" >> ${BASH_COMP_DEBUG_FILE}
echo "$*" >> "${BASH_COMP_DEBUG_FILE}"
fi
}
@ -75,6 +75,13 @@ __handle_reply()
fi
}
# The arguments should be in the form "ext1|ext2|extn"
__handle_filename_extension_flag()
{
local ext="$1"
_filedir "@(${ext})"
}
__handle_flag()
{
__debug "${FUNCNAME}: c is $c words[c] is ${words[c]}"
@ -297,10 +304,10 @@ _kubectl_create()
flags+=("--filename=")
flags_with_completion+=("--filename")
flags_completion+=("_filedir '@(json|yaml|yml)'")
flags_completion+=("__handle_filename_extension_flag json|yaml|yml")
two_word_flags+=("-f")
flags_with_completion+=("-f")
flags_completion+=("_filedir '@(json|yaml|yml)'")
flags_completion+=("__handle_filename_extension_flag json|yaml|yml")
flags+=("--help")
flags+=("-h")
@ -322,10 +329,10 @@ _kubectl_update()
flags+=("--filename=")
flags_with_completion+=("--filename")
flags_completion+=("_filedir '@(json|yaml|yml)'")
flags_completion+=("__handle_filename_extension_flag json|yaml|yml")
two_word_flags+=("-f")
flags_with_completion+=("-f")
flags_completion+=("_filedir '@(json|yaml|yml)'")
flags_completion+=("__handle_filename_extension_flag json|yaml|yml")
flags+=("--help")
flags+=("-h")
flags+=("--patch=")
@ -351,10 +358,10 @@ _kubectl_delete()
flags+=("--cascade")
flags+=("--filename=")
flags_with_completion+=("--filename")
flags_completion+=("_filedir '@(json|yaml|yml)'")
flags_completion+=("__handle_filename_extension_flag json|yaml|yml")
two_word_flags+=("-f")
flags_with_completion+=("-f")
flags_completion+=("_filedir '@(json|yaml|yml)'")
flags_completion+=("__handle_filename_extension_flag json|yaml|yml")
flags+=("--grace-period=")
flags+=("--help")
flags+=("-h")
@ -575,10 +582,10 @@ _kubectl_stop()
flags+=("--all")
flags+=("--filename=")
flags_with_completion+=("--filename")
flags_completion+=("_filedir '@(json|yaml|yml)'")
flags_completion+=("__handle_filename_extension_flag json|yaml|yml")
two_word_flags+=("-f")
flags_with_completion+=("-f")
flags_completion+=("_filedir '@(json|yaml|yml)'")
flags_completion+=("__handle_filename_extension_flag json|yaml|yml")
flags+=("--grace-period=")
flags+=("--help")
flags+=("-h")
@ -942,10 +949,9 @@ _kubectl()
__start_kubectl()
{
local cur prev words cword split
local cur prev words cword
_init_completion -s || return
local completions_func
local c=0
local flags=()
local two_word_flags=()