Repository: spf13/cobra
Pull request #90 (2015-04-07 21:40:13)

Deprecated subcommands
They should still work, but shouldn't show up in help or usage output

eparis: @thockin and deprecation of subcommands
eparis: @thockin rewritten to use a string!

Patch:
diff --git a/bash_completions.go b/bash_completions.go
index dee95b3..485a9a3 100644
--- a/bash_completions.go
+++ b/bash_completions.go
@@ -199,6 +199,9 @@ func postscript(out *bytes.Buffer, name string) {
 func writeCommands(cmd *Command, out *bytes.Buffer) {
 	fmt.Fprintf(out, "    commands=()\n")
 	for _, c := range cmd.Commands() {
+		if len(c.Deprecated) > 0 {
+			continue
+		}
 		fmt.Fprintf(out, "    commands+=(%q)\n", c.Name())
 	}
 	fmt.Fprintf(out, "\n")
@@ -291,6 +294,9 @@ func writeRequiredNoun(cmd *Command, out *bytes.Buffer) {
 
 func gen(cmd *Command, out *bytes.Buffer) {
 	for _, c := range cmd.Commands() {
+		if len(c.Deprecated) > 0 {
+			continue
+		}
 		gen(c, out)
 	}
 	commandName := cmd.CommandPath()
diff --git a/command.go b/command.go
index ef802c6..24165d9 100644
--- a/command.go
+++ b/command.go
@@ -48,6 +48,8 @@ type Command struct {
 	ValidArgs []string
 	// Custom functions used by the bash autocompletion generator
 	BashCompletionFunction string
+	// Is this command deprecated and should print this string when used?
+	Deprecated string
 	// Full set of flags
 	flags *flag.FlagSet
 	// Set of flags childrens of this command will inherit
@@ -231,7 +233,7 @@ Examples:
 {{ .Example }}
 {{end}}{{ if .HasRunnableSubCommands}}
 
-Available Commands: {{range .Commands}}{{if .Runnable}}
+Available Commands: {{range .Commands}}{{if and (.Runnable) (not .Deprecated)}}
   {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}
 {{end}}
 {{ if .HasLocalFlags}}Flags:
@@ -239,7 +241,7 @@ Available Commands: {{range .Commands}}{{if .Runnable}}
 {{ if .HasInheritedFlags}}Global Flags:
 {{.InheritedFlags.FlagUsages}}{{end}}{{if or (.HasHelpSubCommands) (.HasRunnableSiblings)}}
 Additional help topics:
-{{if .HasHelpSubCommands}}{{range .Commands}}{{if not .Runnable}} {{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{if .HasRunnableSiblings }}{{range .Parent.Commands}}{{if .Runnable}}{{if not (eq .Name $cmd.Name) }}
+{{if .HasHelpSubCommands}}{{range .Commands}}{{if and (not .Runnable) (not .Deprecated)}} {{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{if .HasRunnableSiblings }}{{range .Parent.Commands}}{{if and (not .Runnable) (not .Deprecated)}}{{if not (eq .Name $cmd.Name) }}
   {{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{end}}
 {{end}}{{ if .HasSubCommands }}
 Use "{{.Root.Name}} help [command]" for more information about a command.
@@ -414,6 +416,10 @@ func (c *Command) execute(a []string) (err error) {
 		return fmt.Errorf("Called Execute() on a nil Command")
 	}
 
+	if len(c.Deprecated) > 0 {
+		c.Printf("Command %q is deprecated, %s\n", c.Name(), c.Deprecated)
+	}
+
 	err = c.ParseFlags(a)
 	if err == flag.ErrHelp {
 		c.Help()
diff --git a/md_docs.go b/md_docs.go
index ab5188e..4a57ebd 100644
--- a/md_docs.go
+++ b/md_docs.go
@@ -85,6 +85,9 @@ func GenMarkdown(cmd *Command, out *bytes.Buffer) {
 		sort.Sort(byName(children))
 
 		for _, child := range children {
+			if len(child.Deprecated) > 0 {
+				continue
+			}
 			cname := name + " " + child.Name()
 			link := cname + ".md"
 			link = strings.Replace(link, " ", "_", -1)


Test patch:
diff --git a/bash_completions_test.go b/bash_completions_test.go
index 12dc960..4b7d06c 100644
--- a/bash_completions_test.go
+++ b/bash_completions_test.go
@@ -11,6 +11,12 @@ import (
 var _ = fmt.Println
 var _ = os.Stderr
 
+func checkOmit(t *testing.T, found, unexpected string) {
+	if strings.Contains(found, unexpected) {
+		t.Errorf("Unexpected response.\nGot: %q\nBut should not have!\n", unexpected)
+	}
+}
+
 func check(t *testing.T, found, expected string) {
 	if !strings.Contains(found, expected) {
 		t.Errorf("Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", expected, found)
@@ -28,7 +34,7 @@ COMPREPLY=( "hello" )
 func TestBashCompletions(t *testing.T) {
 	c := initializeWithRootCmd()
 	cmdEcho.AddCommand(cmdTimes)
-	c.AddCommand(cmdEcho, cmdPrint)
+	c.AddCommand(cmdEcho, cmdPrint, cmdDeprecated)
 
 	// custom completion function
 	c.BashCompletionFunction = bash_completion_func
@@ -71,4 +77,6 @@ func TestBashCompletions(t *testing.T) {
 	check(t, str, `must_have_one_noun+=("pods")`)
 	// check for filename extention flags
 	check(t, str, `flags_completion+=("_filedir '@(json|yaml|yml)'")`)
+
+	checkOmit(t, str, cmdDeprecated.Name())
 }
diff --git a/cobra_test.go b/cobra_test.go
index 6feb8e5..648fc12 100644
--- a/cobra_test.go
+++ b/cobra_test.go
@@ -51,6 +51,15 @@ var cmdEchoSub = &Command{
 	},
 }
 
+var cmdDeprecated = &Command{
+	Use:        "deprecated [can't do anything here]",
+	Short:      "A command which is deprecated",
+	Long:       `an absolutely utterly useless command for testing deprecation!.`,
+	Deprecated: "Please use echo instead",
+	Run: func(cmd *Command, args []string) {
+	},
+}
+
 var cmdTimes = &Command{
 	Use:   "times [# times] [string to echo]",
 	Short: "Echo anything to the screen more times",
@@ -205,7 +214,7 @@ func fullTester(c *Command, input string) resulter {
 	// Testing flag with invalid input
 	c.SetOutput(buf)
 	cmdEcho.AddCommand(cmdTimes)
-	c.AddCommand(cmdPrint, cmdEcho, cmdSubNoRun)
+	c.AddCommand(cmdPrint, cmdEcho, cmdSubNoRun, cmdDeprecated)
 	c.SetArgs(strings.Split(input, " "))
 
 	err := c.Execute()
@@ -812,3 +821,9 @@ func TestReplaceCommandWithRemove(t *testing.T) {
 		t.Errorf("Replacing command should have been called but didn't\n")
 	}
 }
+
+func TestDeprecatedSub(t *testing.T) {
+	c := fullSetupTest("deprecated")
+
+	checkResultContains(t, c, cmdDeprecated.Deprecated)
+}
diff --git a/md_docs_test.go b/md_docs_test.go
index fd11ad3..defc941 100644
--- a/md_docs_test.go
+++ b/md_docs_test.go
@@ -14,7 +14,7 @@ var _ = os.Stderr
 func TestGenMdDoc(t *testing.T) {
 	c := initializeWithRootCmd()
 	// Need two commands to run the command alphabetical sort
-	cmdEcho.AddCommand(cmdTimes, cmdEchoSub)
+	cmdEcho.AddCommand(cmdTimes, cmdEchoSub, cmdDeprecated)
 	c.AddCommand(cmdPrint, cmdEcho)
 	cmdRootWithRun.PersistentFlags().StringVarP(&flags2a, "rootflag", "r", "two", strtwoParentHelp)
 
@@ -59,4 +59,9 @@ func TestGenMdDoc(t *testing.T) {
 	if !strings.Contains(found, expected) {
 		t.Errorf("Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", expected, found)
 	}
+
+	unexpected := cmdDeprecated.Short
+	if strings.Contains(found, unexpected) {
+		t.Errorf("Unexpected response.\nFound: %v\nBut should not have!!\n", unexpected)
+	}
 }