Merge branch 'master' into forkMaster

# Conflicts:
#	cobra/cmd/init.go
This commit is contained in:
Raphael Tiersch 2017-12-22 15:21:14 +01:00
commit ad974c59c7
27 changed files with 2939 additions and 2471 deletions

806
README.md

File diff suppressed because it is too large Load diff

25
args.go
View file

@ -16,14 +16,14 @@ func legacyArgs(cmd *Command, args []string) error {
return nil return nil
} }
// root command with subcommands, do subcommand checking // root command with subcommands, do subcommand checking.
if !cmd.HasParent() && len(args) > 0 { if !cmd.HasParent() && len(args) > 0 {
return fmt.Errorf("unknown command %q for %q%s", args[0], cmd.CommandPath(), cmd.findSuggestions(args[0])) return fmt.Errorf("unknown command %q for %q%s", args[0], cmd.CommandPath(), cmd.findSuggestions(args[0]))
} }
return nil 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 { func NoArgs(cmd *Command, args []string) error {
if len(args) > 0 { if len(args) > 0 {
return fmt.Errorf("unknown command %q for %q", args[0], cmd.CommandPath()) 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 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 { func OnlyValidArgs(cmd *Command, args []string) error {
if len(cmd.ValidArgs) > 0 { if len(cmd.ValidArgs) > 0 {
for _, v := range args { for _, v := range args {
@ -43,21 +43,12 @@ func OnlyValidArgs(cmd *Command, args []string) error {
return nil return nil
} }
func stringInSlice(a string, list []string) bool { // ArbitraryArgs never returns an error.
for _, b := range list {
if b == a {
return true
}
}
return false
}
// ArbitraryArgs never returns an error
func ArbitraryArgs(cmd *Command, args []string) error { func ArbitraryArgs(cmd *Command, args []string) error {
return nil 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 { func MinimumNArgs(n int) PositionalArgs {
return func(cmd *Command, args []string) error { return func(cmd *Command, args []string) error {
if len(args) < n { 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 { func MaximumNArgs(n int) PositionalArgs {
return func(cmd *Command, args []string) error { return func(cmd *Command, args []string) error {
if len(args) > n { 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 { func ExactArgs(n int) PositionalArgs {
return func(cmd *Command, args []string) error { return func(cmd *Command, args []string) error {
if len(args) != n { 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 { func RangeArgs(min int, max int) PositionalArgs {
return func(cmd *Command, args []string) error { return func(cmd *Command, args []string) error {
if len(args) < min || len(args) > max { if len(args) < min || len(args) > max {

241
args_test.go Normal file
View file

@ -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)
}
}

View file

@ -92,7 +92,7 @@ __handle_reply()
cur="${cur#*=}" cur="${cur#*=}"
${flags_completion[${index}]} ${flags_completion[${index}]}
if [ -n "${ZSH_VERSION}" ]; then if [ -n "${ZSH_VERSION}" ]; then
# zfs completion needs --flag= prefix # zsh completion needs --flag= prefix
eval "COMPREPLY=( \"\${COMPREPLY[@]/#/${flag}=}\" )" eval "COMPREPLY=( \"\${COMPREPLY[@]/#/${flag}=}\" )"
fi fi
fi fi
@ -463,14 +463,14 @@ func gen(buf *bytes.Buffer, cmd *Command) {
} }
// GenBashCompletion generates bash completion file and writes to the passed writer. // 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) buf := new(bytes.Buffer)
writePreamble(buf, cmd.Name()) writePreamble(buf, c.Name())
if len(cmd.BashCompletionFunction) > 0 { if len(c.BashCompletionFunction) > 0 {
buf.WriteString(cmd.BashCompletionFunction + "\n") buf.WriteString(c.BashCompletionFunction + "\n")
} }
gen(buf, cmd) gen(buf, c)
writePostscript(buf, cmd.Name()) writePostscript(buf, c.Name())
_, err := buf.WriteTo(w) _, err := buf.WriteTo(w)
return err return err
@ -481,24 +481,24 @@ func nonCompletableFlag(flag *pflag.Flag) bool {
} }
// GenBashCompletionFile generates bash completion file. // GenBashCompletionFile generates bash completion file.
func (cmd *Command) GenBashCompletionFile(filename string) error { func (c *Command) GenBashCompletionFile(filename string) error {
outFile, err := os.Create(filename) outFile, err := os.Create(filename)
if err != nil { if err != nil {
return err return err
} }
defer outFile.Close() defer outFile.Close()
return cmd.GenBashCompletion(outFile) return c.GenBashCompletion(outFile)
} }
// MarkFlagRequired adds the BashCompOneRequiredFlag annotation to the named flag, if it exists. // MarkFlagRequired adds the BashCompOneRequiredFlag annotation to the named flag, if it exists.
func (cmd *Command) MarkFlagRequired(name string) error { func (c *Command) MarkFlagRequired(name string) error {
return MarkFlagRequired(cmd.Flags(), name) return MarkFlagRequired(c.Flags(), name)
} }
// MarkPersistentFlagRequired adds the BashCompOneRequiredFlag annotation to the named persistent flag, if it exists. // MarkPersistentFlagRequired adds the BashCompOneRequiredFlag annotation to the named persistent flag, if it exists.
func (cmd *Command) MarkPersistentFlagRequired(name string) error { func (c *Command) MarkPersistentFlagRequired(name string) error {
return MarkFlagRequired(cmd.PersistentFlags(), name) return MarkFlagRequired(c.PersistentFlags(), name)
} }
// MarkFlagRequired adds the BashCompOneRequiredFlag annotation to the named flag in the flag set, if it exists. // 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. // 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. // Generated bash autocompletion will select filenames for the flag, limiting to named extensions if provided.
func (cmd *Command) MarkFlagFilename(name string, extensions ...string) error { func (c *Command) MarkFlagFilename(name string, extensions ...string) error {
return MarkFlagFilename(cmd.Flags(), name, extensions...) return MarkFlagFilename(c.Flags(), name, extensions...)
} }
// MarkFlagCustom adds the BashCompCustom annotation to the named flag, if it exists. // MarkFlagCustom adds the BashCompCustom annotation to the named flag, if it exists.
// Generated bash autocompletion will call the bash function f for the flag. // Generated bash autocompletion will call the bash function f for the flag.
func (cmd *Command) MarkFlagCustom(name string, f string) error { func (c *Command) MarkFlagCustom(name string, f string) error {
return MarkFlagCustom(cmd.Flags(), name, f) return MarkFlagCustom(c.Flags(), name, f)
} }
// MarkPersistentFlagFilename adds the BashCompFilenameExt annotation to the named persistent flag, if it exists. // 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. // Generated bash autocompletion will select filenames for the flag, limiting to named extensions if provided.
func (cmd *Command) MarkPersistentFlagFilename(name string, extensions ...string) error { func (c *Command) MarkPersistentFlagFilename(name string, extensions ...string) error {
return MarkFlagFilename(cmd.PersistentFlags(), name, extensions...) return MarkFlagFilename(c.PersistentFlags(), name, extensions...)
} }
// MarkFlagFilename adds the BashCompFilenameExt annotation to the named flag in the flag set, if it exists. // MarkFlagFilename adds the BashCompFilenameExt annotation to the named flag in the flag set, if it exists.

View file

@ -10,13 +10,13 @@ import (
func checkOmit(t *testing.T, found, unexpected string) { func checkOmit(t *testing.T, found, unexpected string) {
if strings.Contains(found, unexpected) { 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) { func check(t *testing.T, found, expected string) {
if !strings.Contains(found, expected) { 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 return err
} }
go func() { go func() {
defer stdin.Close()
stdin.Write([]byte(s)) stdin.Write([]byte(s))
stdin.Close()
}() }()
return cmd.Run() return cmd.Run()
} }
// World worst custom function, just keep telling you to enter hello! // World worst custom function, just keep telling you to enter hello!
const ( const bashCompletionFunc = `__custom_func() {
bashCompletionFunc = `__custom_func() { COMPREPLY=( "hello" )
COMPREPLY=( "hello" )
} }
` `
)
func TestBashCompletions(t *testing.T) { func TestBashCompletions(t *testing.T) {
c := initializeWithRootCmd() rootCmd := &Command{
cmdEcho.AddCommand(cmdTimes) Use: "root",
c.AddCommand(cmdEcho, cmdPrint, cmdDeprecated, cmdColon) 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 // Filename.
c.BashCompletionFunction = bashCompletionFunc rootCmd.Flags().String("filename", "", "Enter a filename")
rootCmd.MarkFlagFilename("filename", "json", "yaml", "yml")
// required flag // Persistent filename.
c.MarkFlagRequired("introot") rootCmd.PersistentFlags().String("persistent-filename", "", "Enter a filename")
rootCmd.MarkPersistentFlagFilename("persistent-filename")
rootCmd.MarkPersistentFlagRequired("persistent-filename")
// valid nouns // Filename extensions.
validArgs := []string{"pod", "node", "service", "replicationcontroller"} rootCmd.Flags().String("filename-ext", "", "Enter a filename (extension limited)")
c.ValidArgs = validArgs rootCmd.MarkFlagFilename("filename-ext")
rootCmd.Flags().String("custom", "", "Enter a filename (extension limited)")
rootCmd.MarkFlagCustom("custom", "__complete_custom")
// noun aliases // Subdirectories in a given directory.
argAliases := []string{"pods", "nodes", "services", "replicationcontrollers", "po", "no", "svc", "rc"} rootCmd.Flags().String("theme", "", "theme to use (located in /themes/THEMENAME/)")
c.ArgAliases = argAliases rootCmd.Flags().SetAnnotation("theme", BashCompSubdirsInDir, []string{"themes"})
// filename echoCmd := &Command{
var flagval string Use: "echo [string to echo]",
c.Flags().StringVar(&flagval, "filename", "", "Enter a filename") Aliases: []string{"say"},
c.MarkFlagFilename("filename", "json", "yaml", "yml") Short: "Echo anything to the screen",
Long: "an utterly useless command for testing.",
Example: "Just run cobra-test echo",
Run: emptyRun,
}
// persistent filename printCmd := &Command{
var flagvalPersistent string Use: "print [string to print]",
c.PersistentFlags().StringVar(&flagvalPersistent, "persistent-filename", "", "Enter a filename") Args: MinimumNArgs(1),
c.MarkPersistentFlagFilename("persistent-filename") Short: "Print anything to the screen",
c.MarkPersistentFlagRequired("persistent-filename") Long: "an absolutely utterly useless command for testing.",
Run: emptyRun,
}
// filename extensions deprecatedCmd := &Command{
var flagvalExt string Use: "deprecated [can't do anything here]",
c.Flags().StringVar(&flagvalExt, "filename-ext", "", "Enter a filename (extension limited)") Args: NoArgs,
c.MarkFlagFilename("filename-ext") Short: "A command which is deprecated",
Long: "an absolutely utterly useless command for testing deprecation!.",
Deprecated: "Please use echo instead",
Run: emptyRun,
}
// filename extensions colonCmd := &Command{
var flagvalCustom string Use: "cmd:colon",
c.Flags().StringVar(&flagvalCustom, "custom", "", "Enter a filename (extension limited)") Run: emptyRun,
c.MarkFlagCustom("custom", "__complete_custom") }
// subdirectories in a given directory timesCmd := &Command{
var flagvalTheme string Use: "times [# times] [string to echo]",
c.Flags().StringVar(&flagvalTheme, "theme", "", "theme to use (located in /themes/THEMENAME/)") SuggestFor: []string{"counts"},
c.Flags().SetAnnotation("theme", BashCompSubdirsInDir, []string{"themes"}) 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) echoCmd.AddCommand(timesCmd)
c.GenBashCompletion(out) rootCmd.AddCommand(echoCmd, printCmd, deprecatedCmd, colonCmd)
str := out.String()
check(t, str, "_cobra-test") buf := new(bytes.Buffer)
check(t, str, "_cobra-test_echo") rootCmd.GenBashCompletion(buf)
check(t, str, "_cobra-test_echo_times") output := buf.String()
check(t, str, "_cobra-test_print")
check(t, str, "_cobra-test_cmd__colon") 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 for required flags
check(t, str, `must_have_one_flag+=("--introot=")`) check(t, output, `must_have_one_flag+=("--introot=")`)
check(t, str, `must_have_one_flag+=("--persistent-filename=")`) check(t, output, `must_have_one_flag+=("--persistent-filename=")`)
// check for custom completion function // check for custom completion function
check(t, str, `COMPREPLY=( "hello" )`) check(t, output, `COMPREPLY=( "hello" )`)
// check for required nouns // 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 for noun aliases
check(t, str, `noun_aliases+=("pods")`) check(t, output, `noun_aliases+=("pods")`)
check(t, str, `noun_aliases+=("rc")`) check(t, output, `noun_aliases+=("rc")`)
checkOmit(t, str, `must_have_one_noun+=("pods")`) checkOmit(t, output, `must_have_one_noun+=("pods")`)
// check for filename extension flags // check for filename extension flags
check(t, str, `flags_completion+=("_filedir")`) check(t, output, `flags_completion+=("_filedir")`)
// check for filename extension flags // check for filename extension flags
check(t, str, `must_have_one_noun+=("three")`) check(t, output, `must_have_one_noun+=("three")`)
// check for filename extention flags // check for filename extension flags
check(t, str, `flags_completion+=("__handle_filename_extension_flag json|yaml|yml")`) check(t, output, `flags_completion+=("__handle_filename_extension_flag json|yaml|yml")`)
// check for custom flags // 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 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 { if err := exec.Command("which", "shellcheck").Run(); err != nil {
return return
} }
err := runShellCheck(str) if err := runShellCheck(output); err != nil {
if err != nil {
t.Fatalf("shellcheck failed: %v", err) t.Fatalf("shellcheck failed: %v", err)
} }
} }
func TestBashCompletionHiddenFlag(t *testing.T) { func TestBashCompletionHiddenFlag(t *testing.T) {
var cmdTrue = &Command{ c := &Command{Use: "c", Run: emptyRun}
Use: "does nothing",
Run: func(cmd *Command, args []string) {},
}
const flagName = "hidden-foo-bar-baz" const flagName = "hiddenFlag"
c.Flags().Bool(flagName, false, "")
c.Flags().MarkHidden(flagName)
var flagValue bool buf := new(bytes.Buffer)
cmdTrue.Flags().BoolVar(&flagValue, flagName, false, "hidden flag") c.GenBashCompletion(buf)
cmdTrue.Flags().MarkHidden(flagName) output := buf.String()
out := new(bytes.Buffer) if strings.Contains(output, flagName) {
cmdTrue.GenBashCompletion(out) t.Errorf("Expected completion to not include %q flag: Got %v", flagName, output)
bashCompletion := out.String()
if strings.Contains(bashCompletion, flagName) {
t.Errorf("expected completion to not include %q flag: Got %v", flagName, bashCompletion)
} }
} }
func TestBashCompletionDeprecatedFlag(t *testing.T) { func TestBashCompletionDeprecatedFlag(t *testing.T) {
var cmdTrue = &Command{ c := &Command{Use: "c", Run: emptyRun}
Use: "does nothing",
Run: func(cmd *Command, args []string) {},
}
const flagName = "deprecated-foo-bar-baz" const flagName = "deprecated-flag"
c.Flags().Bool(flagName, false, "")
var flagValue bool c.Flags().MarkDeprecated(flagName, "use --not-deprecated instead")
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)
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
c.GenBashCompletion(buf)
output := buf.String()
b.ResetTimer() if strings.Contains(output, flagName) {
for i := 0; i < b.N; i++ { t.Errorf("expected completion to not include %q flag: Got %v", flagName, output)
buf.Reset()
if err := c.GenBashCompletion(buf); err != nil {
b.Fatal(err)
}
} }
} }

View file

@ -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()) { func OnInitialize(y ...func()) {
initializers = append(initializers, y...) initializers = append(initializers, y...)
} }
@ -188,3 +189,12 @@ func ld(s, t string, ignoreCase bool) int {
} }
return d[len(s)][len(t)] return d[len(s)][len(t)]
} }
func stringInSlice(a string, list []string) bool {
for _, b := range list {
if b == a {
return true
}
}
return false
}

94
cobra/README.md Normal file
View file

@ -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 <spf@spf13.com>
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**.

View file

@ -24,7 +24,7 @@ import (
func init() { func init() {
addCmd.Flags().StringVarP(&packageName, "package", "t", "", "target package name (e.g. github.com/spf13/hugo)") 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 var packageName, parentName string
@ -35,7 +35,7 @@ var addCmd = &cobra.Command{
Short: "Add a command to a Cobra Application", Short: "Add a command to a Cobra Application",
Long: `Add (cobra add) will create a new command, with a license and Long: `Add (cobra add) will create a new command, with a license and
the appropriate structure for a Cobra-based CLI application, 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 If you want your command to be public, pass in the command name
with an initial uppercase letter. with an initial uppercase letter.

View file

@ -18,9 +18,6 @@ import (
func TestGoldenAddCmd(t *testing.T) { func TestGoldenAddCmd(t *testing.T) {
projectName := "github.com/spf13/testproject" projectName := "github.com/spf13/testproject"
project := NewProject(projectName) project := NewProject(projectName)
// Initialize the project at first.
initializeProject(project)
defer os.RemoveAll(project.AbsPath()) defer os.RemoveAll(project.AbsPath())
viper.Set("author", "NAME HERE <EMAIL ADDRESS>") viper.Set("author", "NAME HERE <EMAIL ADDRESS>")
@ -30,6 +27,9 @@ func TestGoldenAddCmd(t *testing.T) {
defer viper.Set("license", nil) defer viper.Set("license", nil)
defer viper.Set("year", nil) defer viper.Set("year", nil)
// Initialize the project first.
initializeProject(project)
// Then add the "test" command. // Then add the "test" command.
cmdName := "test" cmdName := "test"
cmdPath := filepath.Join(project.CmdPath(), cmdName+".go") cmdPath := filepath.Join(project.CmdPath(), cmdName+".go")

View file

@ -18,6 +18,7 @@ import (
"fmt" "fmt"
"io" "io"
"os" "os"
"os/exec"
"path/filepath" "path/filepath"
"strings" "strings"
"text/template" "text/template"
@ -31,7 +32,27 @@ func init() {
envGoPath := os.Getenv("GOPATH") envGoPath := os.Getenv("GOPATH")
goPaths := filepath.SplitList(envGoPath) goPaths := filepath.SplitList(envGoPath)
if len(goPaths) == 0 { 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)) srcPaths = make([]string, 0, len(goPaths))
for _, goPath := range goPaths { for _, goPath := range goPaths {

View file

@ -159,8 +159,8 @@ var ({{if .viper}}
Environ environ.Environ Environ environ.Environ
) )
// RootCmd represents the base command when called without any subcommands // rootCmd represents the base command when called without any subcommands
var RootCmd = &cobra.Command{ var rootCmd = &cobra.Command{
Use: ApplicationName, Use: ApplicationName,
Short: "A brief description of your application", Short: "A brief description of your application",
Long: ` + "`" + `A longer description that spans multiple lines and likely contains 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. // 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. // This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() { func Execute() {
if err := RootCmd.Execute(); err != nil { if err := rootCmd.Execute(); err != nil {
fmt.Println(err) fmt.Println(err)
os.Exit(1) os.Exit(1)
} }
@ -200,7 +200,7 @@ func init() {
// Cobra also supports local flags, which will only run // Cobra also supports local flags, which will only run
// when this action is called directly. // 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 }} }{{ if .viper }}
// initConfig reads in config file and ENV variables if set. // initConfig reads in config file and ENV variables if set.

View file

@ -63,7 +63,7 @@ func getLicense() License {
// If user wants to have custom license, use that. // If user wants to have custom license, use that.
if viper.IsSet("license.header") || viper.IsSet("license.text") { if viper.IsSet("license.header") || viper.IsSet("license.text") {
return License{Header: viper.GetString("license.header"), 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. // If user wants to have built-in license, use that.

View file

@ -25,8 +25,8 @@ import (
var cfgFile string var cfgFile string
// RootCmd represents the base command when called without any subcommands // rootCmd represents the base command when called without any subcommands
var RootCmd = &cobra.Command{ var rootCmd = &cobra.Command{
Use: "testproject", Use: "testproject",
Short: "A brief description of your application", Short: "A brief description of your application",
Long: `A longer description that spans multiple lines and likely contains 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. // 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. // This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() { func Execute() {
if err := RootCmd.Execute(); err != nil { if err := rootCmd.Execute(); err != nil {
fmt.Println(err) fmt.Println(err)
os.Exit(1) os.Exit(1)
} }
@ -55,11 +55,11 @@ func init() {
// Here you will define your flags and configuration settings. // Here you will define your flags and configuration settings.
// Cobra supports persistent flags, which, if defined here, // Cobra supports persistent flags, which, if defined here,
// will be global for your application. // 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 // Cobra also supports local flags, which will only run
// when this action is called directly. // 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. // initConfig reads in config file and ENV variables if set.

View file

@ -36,7 +36,7 @@ to quickly create a Cobra application.`,
} }
func init() { func init() {
RootCmd.AddCommand(testCmd) rootCmd.AddCommand(testCmd)
// Here you will define your flags and configuration settings. // Here you will define your flags and configuration settings.

File diff suppressed because it is too large Load diff

View file

@ -54,13 +54,14 @@ type Command struct {
// ValidArgs is list of all valid non-flag arguments that are accepted in bash completions // ValidArgs is list of all valid non-flag arguments that are accepted in bash completions
ValidArgs []string ValidArgs []string
// Expected arguments
Args PositionalArgs
// ArgAliases is List of aliases for ValidArgs. // ArgAliases is List of aliases for ValidArgs.
// These are not suggested to the user in the bash completion, // These are not suggested to the user in the bash completion,
// but accepted if entered manually. // but accepted if entered manually.
ArgAliases []string ArgAliases []string
// Expected arguments
Args PositionalArgs
// BashCompletionFunction is custom functions used by the bash autocompletion generator. // BashCompletionFunction is custom functions used by the bash autocompletion generator.
BashCompletionFunction string BashCompletionFunction string
@ -74,6 +75,11 @@ type Command struct {
// group commands. // group commands.
Annotations map[string]string 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: // The *Run functions are executed in the following order:
// * PersistentPreRun() // * PersistentPreRun()
// * PreRun() // * PreRun()
@ -117,6 +123,10 @@ type Command struct {
// will be printed by generating docs for this command. // will be printed by generating docs for this command.
DisableAutoGenTag bool 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 // DisableSuggestions disables the suggestions based on Levenshtein distance
// that go along with 'unknown command' messages. // that go along with 'unknown command' messages.
DisableSuggestions bool DisableSuggestions bool
@ -124,8 +134,9 @@ type Command struct {
// Must be > 0. // Must be > 0.
SuggestionsMinimumDistance int SuggestionsMinimumDistance int
// name is the command name, usually the executable's name. // TraverseChildren parses flags on all parents before executing child command.
name string TraverseChildren bool
// commands is the list of commands supported by this program. // commands is the list of commands supported by this program.
commands []*Command commands []*Command
// parent is a parent command for this 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, // helpCommand is command with usage 'help'. If it's not defined by user,
// cobra uses default help command. // cobra uses default help command.
helpCommand *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 // 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 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. // SetGlobalNormalizationFunc sets a normalization function to all flag sets and also to child commands.
// The user should not have a cyclic dependency on commands. // The user should not have a cyclic dependency on commands.
func (c *Command) SetGlobalNormalizationFunc(n func(f *flag.FlagSet, name string) flag.NormalizedName) { 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}}` {{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 { func hasNoOptDefVal(name string, fs *flag.FlagSet) bool {
flag := fs.Lookup(name) flag := fs.Lookup(name)
if flag == nil { if flag == nil {
@ -474,13 +505,14 @@ func argsMinusFirstX(args []string, x string) []string {
return args 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 // Find the target command given the args and command tree
// Meant to be run on the highest node. Only searches down. // Meant to be run on the highest node. Only searches down.
func (c *Command) Find(args []string) (*Command, []string, error) { 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) var innerfind func(*Command, []string) (*Command, []string)
innerfind = func(c *Command, innerArgs []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 return c, innerArgs
} }
nextSubCmd := argsWOflags[0] 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 cmd := c.findNext(nextSubCmd)
if len(matches) == 1 { if cmd != nil {
return innerfind(matches[0], argsMinusFirstX(innerArgs, argsWOflags[0])) return innerfind(cmd, argsMinusFirstX(innerArgs, nextSubCmd))
} }
return c, innerArgs return c, innerArgs
} }
@ -538,6 +553,66 @@ func (c *Command) findSuggestions(arg string) string {
return suggestionsString 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. // SuggestionsFor provides suggestions for the typedName.
func (c *Command) SuggestionsFor(typedName string) []string { func (c *Command) SuggestionsFor(typedName string) []string {
suggestions := []string{} suggestions := []string{}
@ -575,10 +650,8 @@ func (c *Command) Root() *Command {
return c return c
} }
// ArgsLenAtDash will return the length of f.Args at the moment when a -- was // ArgsLenAtDash will return the length of c.Flags().Args at the moment
// found during arg parsing. This allows your program to know which args were // when a -- was found during args parsing.
// before the -- and which came after. (Description from
// https://godoc.org/github.com/spf13/pflag#FlagSet.ArgsLenAtDash).
func (c *Command) ArgsLenAtDash() int { func (c *Command) ArgsLenAtDash() int {
return c.Flags().ArgsLenAtDash() 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) 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 // overriding
c.InitDefaultHelpFlag() c.InitDefaultHelpFlag()
c.InitDefaultVersionFlag()
err = c.ParseFlags(a) err = c.ParseFlags(a)
if err != nil { if err != nil {
@ -611,7 +685,27 @@ func (c *Command) execute(a []string) (err error) {
return err 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 return flag.ErrHelp
} }
@ -645,6 +739,9 @@ func (c *Command) execute(a []string) (err error) {
c.PreRun(c, argWoFlags) c.PreRun(c, argWoFlags)
} }
if err := c.validateRequiredFlags(); err != nil {
return err
}
if c.RunE != nil { if c.RunE != nil {
if err := c.RunE(c, argWoFlags); err != nil { if err := c.RunE(c, argWoFlags); err != nil {
return err return err
@ -713,7 +810,12 @@ func (c *Command) ExecuteC() (cmd *Command, err error) {
args = c.args 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 err != nil {
// If found parse to a subcommand and then failed, talk about the subcommand // If found parse to a subcommand and then failed, talk about the subcommand
if cmd != nil { if cmd != nil {
@ -725,6 +827,7 @@ func (c *Command) ExecuteC() (cmd *Command, err error) {
} }
return c, err return c, err
} }
err = cmd.execute(flags) err = cmd.execute(flags)
if err != nil { if err != nil {
// Always show help if requested, even if SilenceErrors is in // 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) 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. // InitDefaultHelpFlag adds default help flag to c.
// It is called automatically by executing the c or by calling help and usage. // It is called automatically by executing the c or by calling help and usage.
// If c already has help flag, it will do nothing. // 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. // InitDefaultHelpCmd adds default help command to c.
// It is called automatically by executing the c or by calling help and usage. // 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. // 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) c.AddCommand(c.helpCommand)
} }
// ResetCommands used for testing. // ResetCommands delete parent, subcommand and help command from c.
func (c *Command) ResetCommands() { func (c *Command) ResetCommands() {
c.parent = nil
c.commands = nil c.commands = nil
c.helpCommand = nil c.helpCommand = nil
c.parentsPflags = nil c.parentsPflags = nil
@ -921,6 +1065,9 @@ func (c *Command) UseLine() string {
} else { } else {
useline = c.Use useline = c.Use
} }
if c.DisableFlagsInUseLine {
return useline
}
if c.HasAvailableFlags() && !strings.Contains(useline, "[flags]") { if c.HasAvailableFlags() && !strings.Contains(useline, "[flags]") {
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. // Name returns the command's name: the first word in the use line.
func (c *Command) Name() string { func (c *Command) Name() string {
if c.name == "" { name := c.Use
name := c.Use i := strings.Index(name, " ")
i := strings.Index(name, " ") if i >= 0 {
if i >= 0 { name = name[:i]
name = name[:i]
}
c.name = name
} }
return c.name return name
} }
// HasAlias determines if a given string is an alias of the command. // 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 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 { func (c *Command) NameAndAliases() string {
return strings.Join(append([]string{c.Name()}, c.Aliases...), ", ") 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 // sub commands
return false return false
} }
@ -1131,6 +1289,9 @@ func (c *Command) LocalFlags() *flag.FlagSet {
c.lflags.SetOutput(c.flagErrorBuf) c.lflags.SetOutput(c.flagErrorBuf)
} }
c.lflags.SortFlags = c.Flags().SortFlags c.lflags.SortFlags = c.Flags().SortFlags
if c.globNormFunc != nil {
c.lflags.SetNormalizeFunc(c.globNormFunc)
}
addToLocal := func(f *flag.Flag) { addToLocal := func(f *flag.Flag) {
if c.lflags.Lookup(f.Name) == nil && c.parentsPflags.Lookup(f.Name) == nil { 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() local := c.LocalFlags()
if c.globNormFunc != nil {
c.iflags.SetNormalizeFunc(c.globNormFunc)
}
c.parentsPflags.VisitAll(func(f *flag.Flag) { c.parentsPflags.VisitAll(func(f *flag.Flag) {
if c.iflags.Lookup(f.Name) == nil && local.Lookup(f.Name) == nil { if c.iflags.Lookup(f.Name) == nil && local.Lookup(f.Name) == nil {
c.iflags.AddFlag(f) c.iflags.AddFlag(f)
@ -1180,7 +1345,7 @@ func (c *Command) PersistentFlags() *flag.FlagSet {
return c.pflags return c.pflags
} }
// ResetFlags is used in testing. // ResetFlags deletes all flags from command.
func (c *Command) ResetFlags() { func (c *Command) ResetFlags() {
c.flagErrorBuf = new(bytes.Buffer) c.flagErrorBuf = new(bytes.Buffer)
c.flagErrorBuf.Reset() c.flagErrorBuf.Reset()
@ -1188,6 +1353,10 @@ func (c *Command) ResetFlags() {
c.flags.SetOutput(c.flagErrorBuf) c.flags.SetOutput(c.flagErrorBuf)
c.pflags = flag.NewFlagSet(c.Name(), flag.ContinueOnError) c.pflags = flag.NewFlagSet(c.Name(), flag.ContinueOnError)
c.pflags.SetOutput(c.flagErrorBuf) 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). // 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 return nil
} }
if c.flagErrorBuf == nil {
c.flagErrorBuf = new(bytes.Buffer)
}
beforeErrorBufLen := c.flagErrorBuf.Len() beforeErrorBufLen := c.flagErrorBuf.Len()
c.mergePersistentFlags() c.mergePersistentFlags()
err := c.Flags().Parse(args) err := c.Flags().Parse(args)
@ -1297,6 +1469,10 @@ func (c *Command) updateParentsPflags() {
c.parentsPflags.SortFlags = false c.parentsPflags.SortFlags = false
} }
if c.globNormFunc != nil {
c.parentsPflags.SetNormalizeFunc(c.globNormFunc)
}
c.Root().PersistentFlags().AddFlagSet(flag.CommandLine) c.Root().PersistentFlags().AddFlagSet(flag.CommandLine)
c.VisitParents(func(parent *Command) { c.VisitParents(func(parent *Command) {

File diff suppressed because it is too large Load diff

View file

@ -1,145 +1,86 @@
package doc package doc
import ( import (
"bytes"
"fmt"
"runtime"
"strings" "strings"
"testing" "testing"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
var flagb1, flagb2, flagb3, flagbr, flagbp bool func emptyRun(*cobra.Command, []string) {}
var flags1, flags2a, flags2b, flags3 string
var flagi1, flagi2, flagi3, flagir int
const strtwoParentHelp = "help message for parent flag strtwo" func init() {
const strtwoChildHelp = "help message for child flag strtwo" 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]", Use: "echo [string to echo]",
Aliases: []string{"say"}, Aliases: []string{"say"},
Short: "Echo anything to the screen", 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", Example: "Just run cobra-test echo",
} }
var cmdEchoSub = &cobra.Command{ var echoSubCmd = &cobra.Command{
Use: "echosub [string to print]", Use: "echosub [string to print]",
Short: "second sub command for echo", Short: "second sub command for echo",
Long: `an absolutely utterly useless command for testing gendocs!.`, Long: "an absolutely utterly useless command for testing gendocs!.",
Run: func(cmd *cobra.Command, args []string) {}, 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]", Use: "deprecated [can't do anything here]",
Short: "A command which is deprecated", Short: "A command which is deprecated",
Long: `an absolutely utterly useless command for testing deprecation!.`, Long: `an absolutely utterly useless command for testing deprecation!.`,
Deprecated: "Please use echo instead", Deprecated: "Please use echo instead",
} }
var cmdTimes = &cobra.Command{ var printCmd = &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{
Use: "print [string to print]", Use: "print [string to print]",
Short: "Print anything to the screen", Short: "Print anything to the screen",
Long: `an absolutely utterly useless command for testing.`, Long: `an absolutely utterly useless command for testing.`,
} }
var cmdRootNoRun = &cobra.Command{ func checkStringContains(t *testing.T, got, expected string) {
Use: "cobra-test", if !strings.Contains(got, expected) {
Short: "The root can run its own function", t.Errorf("Expected to contain: \n %v\nGot:\n %v\n", expected, got)
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 checkStringOmits(t *testing.T, found, expected string) { func checkStringOmits(t *testing.T, got, expected string) {
if strings.Contains(found, expected) { if strings.Contains(got, expected) {
logErr(t, found, 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())
}

View file

@ -18,135 +18,97 @@ func translate(in string) string {
} }
func TestGenManDoc(t *testing.T) { 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{ header := &GenManHeader{
Title: "Project", Title: "Project",
Section: "2", Section: "2",
} }
// We generate on a subcommand so we have both subcommands and parents // 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) t.Fatal(err)
} }
found := out.String() output := buf.String()
// Make sure parent has - in CommandPath() in SEE ALSO: // Make sure parent has - in CommandPath() in SEE ALSO:
parentPath := cmdEcho.Parent().CommandPath() parentPath := echoCmd.Parent().CommandPath()
dashParentPath := strings.Replace(parentPath, " ", "-", -1) dashParentPath := strings.Replace(parentPath, " ", "-", -1)
expected := translate(dashParentPath) expected := translate(dashParentPath)
expected = expected + "(" + header.Section + ")" expected = expected + "(" + header.Section + ")"
checkStringContains(t, found, expected) checkStringContains(t, output, expected)
// Our description checkStringContains(t, output, translate(echoCmd.Name()))
expected = translate(cmdEcho.Name()) checkStringContains(t, output, translate(echoCmd.Name()))
checkStringContains(t, found, expected) checkStringContains(t, output, "boolone")
checkStringContains(t, output, "rootflag")
// Better have our example checkStringContains(t, output, translate(rootCmd.Name()))
expected = translate(cmdEcho.Name()) checkStringContains(t, output, translate(echoSubCmd.Name()))
checkStringContains(t, found, expected) checkStringOmits(t, output, translate(deprecatedCmd.Name()))
checkStringContains(t, output, translate("Auto generated"))
// 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)
} }
func TestGenManNoGenTag(t *testing.T) { func TestGenManNoGenTag(t *testing.T) {
c := initializeWithRootCmd() echoCmd.DisableAutoGenTag = true
// Need two commands to run the command alphabetical sort defer func() { echoCmd.DisableAutoGenTag = false }()
cmdEcho.AddCommand(cmdTimes, cmdEchoSub, cmdDeprecated)
c.AddCommand(cmdPrint, cmdEcho)
cmdRootWithRun.PersistentFlags().StringVarP(&flags2a, "rootflag", "r", "two", strtwoParentHelp)
cmdEcho.DisableAutoGenTag = true
out := new(bytes.Buffer)
header := &GenManHeader{ header := &GenManHeader{
Title: "Project", Title: "Project",
Section: "2", Section: "2",
} }
// We generate on a subcommand so we have both subcommands and parents // 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) t.Fatal(err)
} }
found := out.String() output := buf.String()
unexpected := translate("#HISTORY") unexpected := translate("#HISTORY")
checkStringOmits(t, found, unexpected) checkStringOmits(t, output, unexpected)
} }
func TestGenManSeeAlso(t *testing.T) { 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} buf := new(bytes.Buffer)
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)
header := &GenManHeader{} header := &GenManHeader{}
if err := GenMan(top, header, out); err != nil { if err := GenMan(rootCmd, header, buf); err != nil {
t.Fatal(err) t.Fatal(err)
} }
scanner := bufio.NewScanner(buf)
scanner := bufio.NewScanner(out) if err := assertLineFound(scanner, ".SH SEE ALSO"); err != nil {
t.Fatalf("Couldn't find SEE ALSO section header: %v", err)
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 := assertNextLineEquals(scanner, ".PP"); err != nil {
if err := AssertNextLineEquals(scanner, ".PP"); err != nil { t.Fatalf("First line after SEE ALSO wasn't break-indent: %v", err)
t.Fatal(fmt.Errorf("First line after SEE ALSO wasn't break-indent: %s", err.Error()))
} }
if err := assertNextLineEquals(scanner, `\fBroot\-bbb(1)\fP, \fBroot\-ccc(1)\fP`); err != nil {
if err := AssertNextLineEquals(scanner, `\fBtop\-bbb(1)\fP, \fBtop\-ccc(1)\fP`); err != nil { t.Fatalf("Second line after SEE ALSO wasn't correct: %v", err)
t.Fatal(fmt.Errorf("Second line after SEE ALSO wasn't correct: %s", err.Error()))
} }
} }
func TestManPrintFlagsHidesShortDeperecated(t *testing.T) { func TestManPrintFlagsHidesShortDeperecated(t *testing.T) {
cmd := &cobra.Command{} c := &cobra.Command{}
flags := cmd.Flags() c.Flags().StringP("foo", "f", "default", "Foo flag")
flags.StringP("foo", "f", "default", "Foo flag") c.Flags().MarkShorthandDeprecated("foo", "don't use it no more")
flags.MarkShorthandDeprecated("foo", "don't use it no more")
out := new(bytes.Buffer) buf := new(bytes.Buffer)
manPrintFlags(out, flags) manPrintFlags(buf, c.Flags())
got := buf.String()
expected := "**--foo**=\"default\"\n\tFoo flag\n\n" expected := "**--foo**=\"default\"\n\tFoo flag\n\n"
if out.String() != expected { if got != expected {
t.Fatalf("Expected %s, but got %s", expected, out.String()) t.Errorf("Expected %v, got %v", expected, got)
} }
} }
func TestGenManTree(t *testing.T) { func TestGenManTree(t *testing.T) {
cmd := &cobra.Command{ c := &cobra.Command{Use: "do [OPTIONS] arg1 arg2"}
Use: "do [OPTIONS] arg1 arg2",
}
header := &GenManHeader{Section: "2"} header := &GenManHeader{Section: "2"}
tmpdir, err := ioutil.TempDir("", "test-gen-man-tree") tmpdir, err := ioutil.TempDir("", "test-gen-man-tree")
if err != nil { if err != nil {
@ -154,7 +116,7 @@ func TestGenManTree(t *testing.T) {
} }
defer os.RemoveAll(tmpdir) 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()) 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() { for scanner.Scan() {
line := scanner.Text() line := scanner.Text()
if line == expectedLine { if line == expectedLine {
@ -176,30 +138,29 @@ func AssertLineFound(scanner *bufio.Scanner, expectedLine string) error {
} }
if err := scanner.Err(); err != nil { 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() { if scanner.Scan() {
line := scanner.Text() line := scanner.Text()
if line == expectedLine { if line == expectedLine {
return nil 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 { 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) { func BenchmarkGenManToFile(b *testing.B) {
c := initializeWithRootCmd()
file, err := ioutil.TempFile("", "") file, err := ioutil.TempFile("", "")
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
@ -209,7 +170,7 @@ func BenchmarkGenManToFile(b *testing.B) {
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { 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) b.Fatal(err)
} }
} }

View file

@ -67,7 +67,7 @@ func GenMarkdownCustom(cmd *cobra.Command, w io.Writer, linkHandler func(string)
buf.WriteString("## " + name + "\n\n") buf.WriteString("## " + name + "\n\n")
buf.WriteString(short + "\n\n") buf.WriteString(short + "\n\n")
buf.WriteString("### Synopsis\n\n") buf.WriteString("### Synopsis\n\n")
buf.WriteString("\n" + long + "\n\n") buf.WriteString(long + "\n\n")
if cmd.Runnable() { if cmd.Runnable() {
buf.WriteString(fmt.Sprintf("```\n%s\n```\n\n", cmd.UseLine())) 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 return err
} }
if hasSeeAlso(cmd) { if hasSeeAlso(cmd) {
buf.WriteString("### SEE ALSO\n") buf.WriteString("### SEE ALSO\n\n")
if cmd.HasParent() { if cmd.HasParent() {
parent := cmd.Parent() parent := cmd.Parent()
pname := parent.CommandPath() pname := parent.CommandPath()

View file

@ -5,100 +5,51 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"testing" "testing"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
func TestGenMdDoc(t *testing.T) { func TestGenMdDoc(t *testing.T) {
c := initializeWithRootCmd() // We generate on subcommand so we have both subcommands and parents.
// Need two commands to run the command alphabetical sort buf := new(bytes.Buffer)
cmdEcho.AddCommand(cmdTimes, cmdEchoSub, cmdDeprecated) if err := GenMarkdown(echoCmd, buf); err != nil {
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 {
t.Fatal(err) t.Fatal(err)
} }
found := out.String() output := buf.String()
// Our description checkStringContains(t, output, echoCmd.Long)
expected := cmdEcho.Long checkStringContains(t, output, echoCmd.Example)
if !strings.Contains(found, expected) { checkStringContains(t, output, "boolone")
t.Errorf("Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", expected, found) checkStringContains(t, output, "rootflag")
} checkStringContains(t, output, rootCmd.Short)
checkStringContains(t, output, echoSubCmd.Short)
// Better have our example checkStringOmits(t, output, deprecatedCmd.Short)
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)
}
} }
func TestGenMdNoTag(t *testing.T) { func TestGenMdNoTag(t *testing.T) {
c := initializeWithRootCmd() rootCmd.DisableAutoGenTag = true
// Need two commands to run the command alphabetical sort defer func() { rootCmd.DisableAutoGenTag = false }()
cmdEcho.AddCommand(cmdTimes, cmdEchoSub, cmdDeprecated)
c.AddCommand(cmdPrint, cmdEcho)
c.DisableAutoGenTag = true
cmdRootWithRun.PersistentFlags().StringVarP(&flags2a, "rootflag", "r", "two", strtwoParentHelp)
out := new(bytes.Buffer)
if err := GenMarkdown(c, out); err != nil { buf := new(bytes.Buffer)
if err := GenMarkdown(rootCmd, buf); err != nil {
t.Fatal(err) t.Fatal(err)
} }
found := out.String() output := buf.String()
unexpected := "Auto generated"
checkStringOmits(t, found, unexpected)
checkStringOmits(t, output, "Auto generated")
} }
func TestGenMdTree(t *testing.T) { func TestGenMdTree(t *testing.T) {
cmd := &cobra.Command{ c := &cobra.Command{Use: "do [OPTIONS] arg1 arg2"}
Use: "do [OPTIONS] arg1 arg2",
}
tmpdir, err := ioutil.TempDir("", "test-gen-md-tree") tmpdir, err := ioutil.TempDir("", "test-gen-md-tree")
if err != nil { if err != nil {
t.Fatalf("Failed to create tmpdir: %s", err.Error()) t.Fatalf("Failed to create tmpdir: %v", err)
} }
defer os.RemoveAll(tmpdir) defer os.RemoveAll(tmpdir)
if err := GenMarkdownTree(cmd, tmpdir); err != nil { if err := GenMarkdownTree(c, tmpdir); err != nil {
t.Fatalf("GenMarkdownTree failed: %s", err.Error()) t.Fatalf("GenMarkdownTree failed: %v", err)
} }
if _, err := os.Stat(filepath.Join(tmpdir, "do.md")); err != nil { 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) { func BenchmarkGenMarkdownToFile(b *testing.B) {
c := initializeWithRootCmd()
file, err := ioutil.TempFile("", "") file, err := ioutil.TempFile("", "")
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
@ -117,7 +67,7 @@ func BenchmarkGenMarkdownToFile(b *testing.B) {
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
if err := GenMarkdown(c, file); err != nil { if err := GenMarkdown(rootCmd, file); err != nil {
b.Fatal(err) b.Fatal(err)
} }
} }

185
doc/rest_docs.go Normal file
View file

@ -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)
}

114
doc/rest_docs.md Normal file
View file

@ -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)
}
```

76
doc/rest_docs_test.go Normal file
View file

@ -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)
}
}
}

View file

@ -5,92 +5,42 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"testing" "testing"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
func TestGenYamlDoc(t *testing.T) { 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 // 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) t.Fatal(err)
} }
found := out.String() output := buf.String()
// Our description checkStringContains(t, output, echoCmd.Long)
expected := cmdEcho.Long checkStringContains(t, output, echoCmd.Example)
if !strings.Contains(found, expected) { checkStringContains(t, output, "boolone")
t.Errorf("Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", expected, found) checkStringContains(t, output, "rootflag")
} checkStringContains(t, output, rootCmd.Short)
checkStringContains(t, output, echoSubCmd.Short)
// 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)
}
} }
func TestGenYamlNoTag(t *testing.T) { func TestGenYamlNoTag(t *testing.T) {
c := initializeWithRootCmd() rootCmd.DisableAutoGenTag = true
// Need two commands to run the command alphabetical sort defer func() { rootCmd.DisableAutoGenTag = false }()
cmdEcho.AddCommand(cmdTimes, cmdEchoSub, cmdDeprecated)
c.AddCommand(cmdPrint, cmdEcho)
c.DisableAutoGenTag = true
cmdRootWithRun.PersistentFlags().StringVarP(&flags2a, "rootflag", "r", "two", strtwoParentHelp)
out := new(bytes.Buffer)
if err := GenYaml(c, out); err != nil { buf := new(bytes.Buffer)
if err := GenYaml(rootCmd, buf); err != nil {
t.Fatal(err) t.Fatal(err)
} }
found := out.String() output := buf.String()
unexpected := "Auto generated"
checkStringOmits(t, found, unexpected)
checkStringOmits(t, output, "Auto generated")
} }
func TestGenYamlTree(t *testing.T) { func TestGenYamlTree(t *testing.T) {
cmd := &cobra.Command{ c := &cobra.Command{Use: "do [OPTIONS] arg1 arg2"}
Use: "do [OPTIONS] arg1 arg2",
}
tmpdir, err := ioutil.TempDir("", "test-gen-yaml-tree") tmpdir, err := ioutil.TempDir("", "test-gen-yaml-tree")
if err != nil { if err != nil {
@ -98,7 +48,7 @@ func TestGenYamlTree(t *testing.T) {
} }
defer os.RemoveAll(tmpdir) 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()) t.Fatalf("GenYamlTree failed: %s", err.Error())
} }
@ -108,7 +58,6 @@ func TestGenYamlTree(t *testing.T) {
} }
func BenchmarkGenYamlToFile(b *testing.B) { func BenchmarkGenYamlToFile(b *testing.B) {
c := initializeWithRootCmd()
file, err := ioutil.TempFile("", "") file, err := ioutil.TempFile("", "")
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
@ -118,7 +67,7 @@ func BenchmarkGenYamlToFile(b *testing.B) {
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
if err := GenYaml(c, file); err != nil { if err := GenYaml(rootCmd, file); err != nil {
b.Fatal(err) b.Fatal(err)
} }
} }

View file

@ -4,17 +4,29 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"io" "io"
"os"
"strings" "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. // 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) buf := new(bytes.Buffer)
writeHeader(buf, cmd) writeHeader(buf, c)
maxDepth := maxDepth(cmd) maxDepth := maxDepth(c)
writeLevelMapping(buf, maxDepth) writeLevelMapping(buf, maxDepth)
writeLevelCases(buf, maxDepth, cmd) writeLevelCases(buf, maxDepth, c)
_, err := buf.WriteTo(w) _, err := buf.WriteTo(w)
return err return err

View file

@ -77,10 +77,11 @@ func TestZshCompletion(t *testing.T) {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
tc.root.GenZshCompletion(buf) tc.root.GenZshCompletion(buf)
completion := buf.String() output := buf.String()
for _, expectedExpression := range tc.expectedExpressions { for _, expectedExpression := range tc.expectedExpressions {
if !strings.Contains(completion, expectedExpression) { if !strings.Contains(output, expectedExpression) {
t.Errorf("expected completion to contain '%v' somewhere; got '%v'", expectedExpression, completion) t.Errorf("Expected completion to contain %q somewhere; got %q", expectedExpression, output)
} }
} }
}) })