From b45934648f38b8b5a2ff8fd22359563ba7abf615 Mon Sep 17 00:00:00 2001 From: Carlos Marx Date: Thu, 8 Jul 2021 11:14:40 -0300 Subject: [PATCH] Fix command name collision on generator When creating new commands via `cobra add`, it is not possible to add commands with the same name to different parents. They will just overwrite each other. With this patch, the command's object identifier is templated `parentCommandCmd`, the `Use` field `command` and the file name `parent_command.go`. Fixes #1059 Fixes #1131 Signed-off-by: Carlos Marx --- cobra/cmd/add.go | 46 ++++++++++++++++++++++++++++++++++++++++--- cobra/cmd/add_test.go | 35 +++++++++++++++++++++++++++++--- cobra/cmd/project.go | 8 +++++--- cobra/tpl/main.go | 6 +++--- 4 files changed, 83 insertions(+), 12 deletions(-) diff --git a/cobra/cmd/add.go b/cobra/cmd/add.go index 8377411e..a24ecd4e 100644 --- a/cobra/cmd/add.go +++ b/cobra/cmd/add.go @@ -46,10 +46,14 @@ Example: cobra add server -> resulting in a new cmd/server.go`, wd, err := os.Getwd() cobra.CheckErr(err) - commandName := validateCmdName(args[0]) + commandUse := validateCmdName(args[0]) + commandName, commandFileName := generateCmdFileName(commandUse, parentName) + command := &Command{ - CmdName: commandName, - CmdParent: parentName, + CmdName: commandName, + CmdUse: commandUse, + CmdFileName: commandFileName, + CmdParent: parentName, Project: &Project{ AbsolutePath: wd, Legal: getLicense(), @@ -122,3 +126,39 @@ func validateCmdName(source string) string { } return output } + +// generateCmdFileName returns a lowerCamelCase command name and a snake_case +// file name, combining the given command name and the parent command name. +func generateCmdFileName(cmd, parent string) (string, string) { + var cmdName string + var cmdFileName string + + if parent == "rootCmd" { + return cmd, cmd + } + + l := len(parent) + if parent[l-3:] == "Cmd" { + parent = parent[:l-3] + } + + upperCmd := string(unicode.ToUpper(rune(cmd[0]))) + cmd[1:] + lowerParent := string(unicode.ToLower(rune(parent[0]))) + parent[1:] + cmdName = lowerParent + upperCmd + + l = len(cmdName) + cmdFileName = string(cmdName[0]) + for i := 1; i < l; i++ { + if unicode.IsUpper(rune(cmdName[i])) { + cmdFileName += "_" + } + cmdFileName += string(unicode.ToLower(rune(cmdName[i]))) + } + + l = len(cmdFileName) + if cmdFileName[l-5:] == "_test" { + cmdFileName += "cmd" + } + + return cmdName, cmdFileName +} diff --git a/cobra/cmd/add_test.go b/cobra/cmd/add_test.go index 0b32ca67..3965d1ea 100644 --- a/cobra/cmd/add_test.go +++ b/cobra/cmd/add_test.go @@ -8,9 +8,11 @@ import ( func TestGoldenAddCmd(t *testing.T) { command := &Command{ - CmdName: "test", - CmdParent: parentName, - Project: getProject(), + CmdName: "test", + CmdUse: "test", + CmdFileName: "test", + CmdParent: parentName, + Project: getProject(), } defer os.RemoveAll(command.AbsolutePath) @@ -48,3 +50,30 @@ func TestValidateCmdName(t *testing.T) { } } } + +func TestGenerateCmdFileName(t *testing.T) { + testCases := []struct { + inputCmd string + inputParent string + expectedCmd string + expectedFile string + }{ + {"cmdname", "parent", "parentCmdname", "parent_cmdname"}, + {"cmdname", "parentCmd", "parentCmdname", "parent_cmdname"}, + {"CmdName", "ParentCmd", "parentCmdName", "parent_cmd_name"}, + {"cmdname", "granpaParentCmd", "granpaParentCmdname", "granpa_parent_cmdname"}, + {"test", "granpaParentCmd", "granpaParentTest", "granpa_parent_testcmd"}, + {"cmdname", "rootCmd", "cmdname", "cmdname"}, + } + + for _, testCase := range testCases { + got1, got2 := generateCmdFileName(testCase.inputCmd, testCase.inputParent) + if testCase.expectedCmd != got1 || testCase.expectedFile != got2 { + t.Errorf( + "Expected %q and %q, got %q and %q", + testCase.expectedCmd, testCase.expectedFile, + got1, got2, + ) + } + } +} diff --git a/cobra/cmd/project.go b/cobra/cmd/project.go index bd68a31d..35bde47f 100644 --- a/cobra/cmd/project.go +++ b/cobra/cmd/project.go @@ -21,8 +21,10 @@ type Project struct { } type Command struct { - CmdName string - CmdParent string + CmdName string + CmdUse string + CmdFileName string + CmdParent string *Project } @@ -83,7 +85,7 @@ func (p *Project) createLicenseFile() error { } func (c *Command) Create() error { - cmdFile, err := os.Create(fmt.Sprintf("%s/cmd/%s.go", c.AbsolutePath, c.CmdName)) + cmdFile, err := os.Create(fmt.Sprintf("%s/cmd/%s.go", c.AbsolutePath, c.CmdFileName)) if err != nil { return err } diff --git a/cobra/tpl/main.go b/cobra/tpl/main.go index ed3a98e1..e3dc06b5 100644 --- a/cobra/tpl/main.go +++ b/cobra/tpl/main.go @@ -113,9 +113,9 @@ import ( "github.com/spf13/cobra" ) -// {{ .CmdName }}Cmd represents the {{ .CmdName }} command +// {{ .CmdName }}Cmd represents the {{ .CmdUse }} command var {{ .CmdName }}Cmd = &cobra.Command{ - Use: "{{ .CmdName }}", + Use: "{{ .CmdUse }}", Short: "A brief description of your command", Long: ` + "`" + `A longer description that spans multiple lines and likely contains examples and usage of using your command. For example: @@ -124,7 +124,7 @@ Cobra is a CLI library for Go that empowers applications. This application is a tool to generate the needed files to quickly create a Cobra application.` + "`" + `, Run: func(cmd *cobra.Command, args []string) { - fmt.Println("{{ .CmdName }} called") + fmt.Println("{{ .CmdUse }} called") }, }