From fd2b84c4e207c6fee22eb3184b183b4c46158de4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Vallette=20d=27Osia?= Date: Tue, 3 Mar 2015 15:50:53 +0900 Subject: [PATCH 1/3] Add Command's RemoveCommand method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This method removes children commands of an existing command. This allows to build CLI clients that can be extended by 3rd party tools, for instance by adding commands _and replacing the “version” command_. For now the 1st defined command will be executed, so it is not possible to override an existing command. But anyway, deleting old command then adding a new one is the ultimate way to be certain there is no confusion. --- command.go | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/command.go b/command.go index 6ac3ea70..9855fbe6 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...) From 0e7e122c81a5a00d7ba2e799599c7a04609c77b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Vallette=20d=27Osia?= Date: Fri, 13 Mar 2015 12:40:00 +0900 Subject: [PATCH 2/3] Add README section for RemoveCommand --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 64c29ef1..f340f6f1 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. From 5b56110fb0f3d94f4cd05303c6277fcc05877b2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Vallette=20d=27Osia?= Date: Fri, 13 Mar 2015 16:30:33 +0900 Subject: [PATCH 3/3] Add tests for RemoveCommand --- cobra_test.go | 52 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/cobra_test.go b/cobra_test.go index 629865c2..24ba72ee 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") + } +}