Repository: spf13/cobra
Pull request #60 (2015-03-03 07:42:44)

Add Command's RemoveCommand method
This method removes children commands of an existing command.

This allows to build CLI clients that can be extended by 3rd party tools, by adding some commands and removing/replacing some others. For instance to replace the “version” command.

Note: For now the 1st defined command is executed, so it is not possible to override an existing command.
But anyway, deleting an old command then adding a new one is the ultimate way to be certain there is no confusion.


Patch:
diff --git a/README.md b/README.md
index 64c29ef..f340f6f 100644
--- a/README.md
+++ b/README.md
@@ -143,6 +143,15 @@ A flag can also be assigned locally which will only apply to that specific comma
 
 	HugoCmd.Flags().StringVarP(&Source, "source", "s", "", "Source directory to read from")
 
+### Remove a command from its parent
+
+Removing a command is not a common action is simple program but it allows 3rd parties to customize an existing command tree.
+
+In this exemple, we remove the existing `VersionCmd` command of an existing root command, and we replace it by our own version.
+
+	mainlib.RootCmd.RemoveCommand(mainlib.VersionCmd)
+	mainlib.RootCmd.AddCommand(versionCmd)
+
 ### Once all commands and flags are defined, Execute the commands
 
 Execute should be run on the root for clarity, though it can be called on any command.
diff --git a/command.go b/command.go
index 6ac3ea7..9855fbe 100644
--- a/command.go
+++ b/command.go
@@ -553,6 +553,40 @@ func (c *Command) AddCommand(cmds ...*Command) {
 	}
 }
 
+// AddCommand removes one or more commands from a parent command.
+func (c *Command) RemoveCommand(cmds ...*Command) {
+	commands := []*Command{}
+main:
+	for _, command := range c.commands {
+		for _, cmd := range cmds {
+			if command == cmd {
+				command.parent = nil
+				continue main
+			}
+		}
+		commands = append(commands, command)
+	}
+	c.commands = commands
+	// recompute all lengths
+	c.commandsMaxUseLen = 0
+	c.commandsMaxCommandPathLen = 0
+	c.commandsMaxNameLen = 0
+	for _, command := range c.commands {
+		usageLen := len(command.Use)
+		if usageLen > c.commandsMaxUseLen {
+			c.commandsMaxUseLen = usageLen
+		}
+		commandPathLen := len(command.CommandPath())
+		if commandPathLen > c.commandsMaxCommandPathLen {
+			c.commandsMaxCommandPathLen = commandPathLen
+		}
+		nameLen := len(command.Name())
+		if nameLen > c.commandsMaxNameLen {
+			c.commandsMaxNameLen = nameLen
+		}
+	}
+}
+
 // Convenience method to Print to the defined output
 func (c *Command) Print(i ...interface{}) {
 	fmt.Fprint(c.Out(), i...)


Test patch:
diff --git a/cobra_test.go b/cobra_test.go
index 629865c..24ba72e 100644
--- a/cobra_test.go
+++ b/cobra_test.go
@@ -15,6 +15,7 @@ var flags1, flags2, flags3 string
 var flagi1, flagi2, flagi3, flagir int
 var globalFlag1 bool
 var flagEcho, rootcalled bool
+var versionUsed int
 
 var cmdPrint = &Command{
 	Use:   "print [string to print]",
@@ -65,6 +66,24 @@ var cmdRootWithRun = &Command{
 	},
 }
 
+var cmdVersion1 = &Command{
+	Use:   "version",
+	Short: "Print the version number",
+	Long:  `First version of the version command`,
+	Run: func(cmd *Command, args []string) {
+		versionUsed = 1
+	},
+}
+
+var cmdVersion2 = &Command{
+	Use:   "version",
+	Short: "Print the version number",
+	Long:  `Second version of the version command`,
+	Run: func(cmd *Command, args []string) {
+		versionUsed = 2
+	},
+}
+
 func flagInit() {
 	cmdEcho.ResetFlags()
 	cmdPrint.ResetFlags()
@@ -81,6 +100,8 @@ func flagInit() {
 	cmdEcho.Flags().BoolVarP(&flagb1, "boolone", "b", true, "help message for flag boolone")
 	cmdTimes.Flags().BoolVarP(&flagb2, "booltwo", "c", false, "help message for flag booltwo")
 	cmdPrint.Flags().BoolVarP(&flagb3, "boolthree", "b", true, "help message for flag boolthree")
+	cmdVersion1.ResetFlags()
+	cmdVersion2.ResetFlags()
 }
 
 func commandInit() {
@@ -559,3 +580,34 @@ func TestFlagsBeforeCommand(t *testing.T) {
 	}
 
 }
+
+func TestRemoveCommand(t *testing.T) {
+	versionUsed = 0
+	c := initializeWithRootCmd()
+	c.AddCommand(cmdVersion1)
+	c.RemoveCommand(cmdVersion1)
+	x := fullTester(c, "version")
+	if x.Error == nil {
+		t.Errorf("Removed command should not have been called\n")
+		return
+	}
+}
+
+func TestReplaceCommandWithRemove(t *testing.T) {
+	versionUsed = 0
+	c := initializeWithRootCmd()
+	c.AddCommand(cmdVersion1)
+	c.RemoveCommand(cmdVersion1)
+	c.AddCommand(cmdVersion2)
+	x := fullTester(c, "version")
+	if x.Error != nil {
+		t.Errorf("Valid Input shouldn't have errors, got:\n %q", x.Error)
+		return
+	}
+	if versionUsed == 1 {
+		t.Errorf("Removed command shouldn't be called\n")
+	}
+	if versionUsed != 2 {
+		t.Errorf("Replacing command should have been called but didn't\n")
+	}
+}