From a6cbaf61ecdb98ab42042dfafe29ef563a75eb4e Mon Sep 17 00:00:00 2001 From: Ian Walter Date: Wed, 27 Apr 2016 22:44:53 -0400 Subject: [PATCH 1/6] Working on #266: Ability to add nested commands * Modified `cobra add` command to be able to parse argument into nested command structure * Modified `createCmdFile` function to accept cmdPath * Modified cmdFile template to specify package name and to TitleCase command names so that they can be accessed by child packages. * Simplified `writeTemplateToFile` function so that just the file path is passed instead of base path and filename. --- cobra/cmd/add.go | 48 ++++++++++++++++++++++++++++++++++---------- cobra/cmd/helpers.go | 14 +++---------- cobra/cmd/init.go | 6 +++--- 3 files changed, 43 insertions(+), 25 deletions(-) diff --git a/cobra/cmd/add.go b/cobra/cmd/add.go index b89d4c47..7867e94a 100644 --- a/cobra/cmd/add.go +++ b/cobra/cmd/add.go @@ -14,6 +14,7 @@ package cmd import ( + "path" "fmt" "path/filepath" "strings" @@ -48,7 +49,25 @@ Example: cobra add server -> resulting in a new cmd/server.go er("add needs a name for the command") } guessProjectPath() - createCmdFile(args[0]) + + // Iterate over command parts delimited by "/". + currentPath := "" + cmdParts := strings.Split(args[0], "/") + for n, cmd := range cmdParts { + // + if n > 0 { + // Set parent name to previous command's name. + pName = cmdParts[n-1] + + // + currentPath += pName + "/" + } + + // TODO Create command file if it does not already exist. + createCmdFile(cmd, currentPath) + } + + }, } @@ -64,13 +83,13 @@ func parentName() string { return pName } -func createCmdFile(cmdName string) { +func createCmdFile(cmdName, cmdPath string) { lic := getLicense() template := `{{ comment .copyright }} {{ comment .license }} -package cmd +package {{ .packageName }} import ( "fmt" @@ -78,8 +97,8 @@ import ( "github.com/spf13/cobra" ) -// {{.cmdName}}Cmd represents the {{.cmdName}} command -var {{ .cmdName }}Cmd = &cobra.Command{ +// {{ .cmdName | title }}Cmd represents the {{ .cmdName }} command +{{ .cmdName | title }}Cmd := &cobra.Command{ Use: "{{ .cmdName }}", Short: "A brief description of your command", Long: ` + "`" + `A longer description that spans multiple lines and likely contains examples @@ -95,7 +114,7 @@ to quickly create a Cobra application.` + "`" + `, } func init() { - {{ .parentName }}.AddCommand({{ .cmdName }}Cmd) + {{ .parentName | title }}.AddCommand({{ .cmdName | title }}Cmd) // Here you will define your flags and configuration settings. @@ -110,8 +129,14 @@ func init() { } ` - var data map[string]interface{} - data = make(map[string]interface{}) + data := make(map[string]interface{}) + + data["packageName"] = "cmd" + + // + if cmdPath != "" { + data["packageName"] = path.Base(cmdPath) + } data["copyright"] = copyrightLine() data["license"] = lic.Header @@ -120,9 +145,10 @@ func init() { data["parentName"] = parentName() data["cmdName"] = cmdName - err := writeTemplateToFile(filepath.Join(ProjectPath(), guessCmdDir()), cmdName+".go", template, data) - if err != nil { + file := filepath.Join(ProjectPath(), guessCmdDir(), cmdPath, cmdName+".go") + + if err := writeTemplateToFile(file, template, data); err != nil { er(err) } - fmt.Println(cmdName, "created at", filepath.Join(ProjectPath(), guessCmdDir(), cmdName+".go")) + fmt.Println(cmdName, "created at", file) } diff --git a/cobra/cmd/helpers.go b/cobra/cmd/helpers.go index 7cd3be18..4218b034 100644 --- a/cobra/cmd/helpers.go +++ b/cobra/cmd/helpers.go @@ -224,21 +224,13 @@ func dirExists(path string) (bool, error) { return false, err } -func writeTemplateToFile(path string, file string, template string, data interface{}) error { - filename := filepath.Join(path, file) - +func writeTemplateToFile(file string, template string, data interface{}) error { r, err := templateToReader(template, data) - if err != nil { return err } - err = safeWriteToDisk(filename, r) - - if err != nil { - return err - } - return nil + return safeWriteToDisk(file, r) } func writeStringToFile(path, file, text string) error { @@ -254,7 +246,7 @@ func writeStringToFile(path, file, text string) error { } func templateToReader(tpl string, data interface{}) (io.Reader, error) { - tmpl := template.New("") + tmpl := template.New("").Funcs(template.FuncMap{"title": strings.Title}) tmpl.Funcs(funcMap) tmpl, err := tmpl.Parse(tpl) diff --git a/cobra/cmd/init.go b/cobra/cmd/init.go index 13792f12..8fe6c303 100644 --- a/cobra/cmd/init.go +++ b/cobra/cmd/init.go @@ -104,7 +104,7 @@ func createLicenseFile() { buf := new(bytes.Buffer) buf.ReadFrom(r) - err := writeTemplateToFile(ProjectPath(), "LICENSE", buf.String(), data) + err := writeTemplateToFile(ProjectPath()+"LICENSE", buf.String(), data) _ = err // if err != nil { // er(err) @@ -139,7 +139,7 @@ func main() { data["importpath"] = guessImportPath() + "/" + guessCmdDir() - err := writeTemplateToFile(ProjectPath(), "main.go", template, data) + err := writeTemplateToFile(ProjectPath()+"main.go", template, data) _ = err // if err != nil { // er(err) @@ -233,7 +233,7 @@ func initConfig() { data["viper"] = viper.GetBool("useViper") - err := writeTemplateToFile(ProjectPath()+string(os.PathSeparator)+guessCmdDir(), "root.go", template, data) + err := writeTemplateToFile(ProjectPath()+string(os.PathSeparator)+guessCmdDir()+"root.go", template, data) if err != nil { er(err) } From b4c7072b6b4a5a0e93cf7f3c8bafddc5f8b6b7a4 Mon Sep 17 00:00:00 2001 From: Ian Walter Date: Thu, 28 Apr 2016 21:23:46 -0400 Subject: [PATCH 2/6] Checking if cmd file exists before trying to create it --- cobra/cmd/add.go | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/cobra/cmd/add.go b/cobra/cmd/add.go index 7867e94a..70d4d7b1 100644 --- a/cobra/cmd/add.go +++ b/cobra/cmd/add.go @@ -14,6 +14,7 @@ package cmd import ( + "os" "path" "fmt" "path/filepath" @@ -63,8 +64,7 @@ Example: cobra add server -> resulting in a new cmd/server.go currentPath += pName + "/" } - // TODO Create command file if it does not already exist. - createCmdFile(cmd, currentPath) + createCmdFile(cmd, currentPath) } @@ -98,7 +98,7 @@ import ( ) // {{ .cmdName | title }}Cmd represents the {{ .cmdName }} command -{{ .cmdName | title }}Cmd := &cobra.Command{ +var {{ .cmdName | title }}Cmd = &cobra.Command{ Use: "{{ .cmdName }}", Short: "A brief description of your command", Long: ` + "`" + `A longer description that spans multiple lines and likely contains examples @@ -147,8 +147,13 @@ func init() { file := filepath.Join(ProjectPath(), guessCmdDir(), cmdPath, cmdName+".go") - if err := writeTemplateToFile(file, template, data); err != nil { - er(err) + // Check if the file exists before trying to create it. + if _, err := os.Stat(file); os.IsNotExist(err) { + if err := writeTemplateToFile(file, template, data); err != nil { + er(err) + } + fmt.Println(cmdName, "created at", file) + } else { + fmt.Println(cmdName, "already exists at", file) } - fmt.Println(cmdName, "created at", file) } From 274ca840ae9b1e51042a8bc7fac3a11577db391a Mon Sep 17 00:00:00 2001 From: Ian Walter Date: Mon, 2 May 2016 22:37:29 -0400 Subject: [PATCH 3/6] Fixing removal of filepath.join from writeTemplateToFile --- cobra/cmd/add.go | 11 ++++++----- cobra/cmd/helpers.go | 6 ++++-- cobra/cmd/init.go | 8 ++++---- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/cobra/cmd/add.go b/cobra/cmd/add.go index 70d4d7b1..08200f31 100644 --- a/cobra/cmd/add.go +++ b/cobra/cmd/add.go @@ -145,15 +145,16 @@ func init() { data["parentName"] = parentName() data["cmdName"] = cmdName - file := filepath.Join(ProjectPath(), guessCmdDir(), cmdPath, cmdName+".go") + path := filepath.Join(ProjectPath(), guessCmdDir(), cmdPath) + filename := cmdName + ".go" // Check if the file exists before trying to create it. - if _, err := os.Stat(file); os.IsNotExist(err) { - if err := writeTemplateToFile(file, template, data); err != nil { + if _, err := os.Stat(filepath.Join(path, filename)); os.IsNotExist(err) { + if err = writeTemplateToFile(path, filename, template, data); err != nil { er(err) } - fmt.Println(cmdName, "created at", file) + fmt.Println(cmdName, "created at", path) } else { - fmt.Println(cmdName, "already exists at", file) + fmt.Println(cmdName, "already exists at", path) } } diff --git a/cobra/cmd/helpers.go b/cobra/cmd/helpers.go index 4218b034..8ee3bb0f 100644 --- a/cobra/cmd/helpers.go +++ b/cobra/cmd/helpers.go @@ -224,13 +224,15 @@ func dirExists(path string) (bool, error) { return false, err } -func writeTemplateToFile(file string, template string, data interface{}) error { +func writeTemplateToFile(path, file, template string, data interface{}) error { + filename := filepath.Join(path, file) + r, err := templateToReader(template, data) if err != nil { return err } - return safeWriteToDisk(file, r) + return safeWriteToDisk(filename, r) } func writeStringToFile(path, file, text string) error { diff --git a/cobra/cmd/init.go b/cobra/cmd/init.go index 8fe6c303..cb0c8fd7 100644 --- a/cobra/cmd/init.go +++ b/cobra/cmd/init.go @@ -104,7 +104,7 @@ func createLicenseFile() { buf := new(bytes.Buffer) buf.ReadFrom(r) - err := writeTemplateToFile(ProjectPath()+"LICENSE", buf.String(), data) + err := writeTemplateToFile(ProjectPath(), "LICENSE", buf.String(), data) _ = err // if err != nil { // er(err) @@ -139,7 +139,7 @@ func main() { data["importpath"] = guessImportPath() + "/" + guessCmdDir() - err := writeTemplateToFile(ProjectPath()+"main.go", template, data) + err := writeTemplateToFile(ProjectPath(), "main.go", template, data) _ = err // if err != nil { // er(err) @@ -233,8 +233,8 @@ func initConfig() { data["viper"] = viper.GetBool("useViper") - err := writeTemplateToFile(ProjectPath()+string(os.PathSeparator)+guessCmdDir()+"root.go", template, data) - if err != nil { + path := ProjectPath() + string(os.PathSeparator) + guessCmdDir() + if err := writeTemplateToFile(path, "root.go", template, data); err != nil { er(err) } From 784ab4d6eb1a1b969be7ed8ea74d5b29fe42ff2c Mon Sep 17 00:00:00 2001 From: Ian Walter Date: Tue, 3 May 2016 00:56:28 -0400 Subject: [PATCH 4/6] Reversing cmd creation order so child cmds get added properly --- cobra/cmd/add.go | 48 ++++++++++++++++++++++++++------------------ cobra/cmd/helpers.go | 27 ++++++++++++++++++++++++- 2 files changed, 54 insertions(+), 21 deletions(-) diff --git a/cobra/cmd/add.go b/cobra/cmd/add.go index 08200f31..09c32c17 100644 --- a/cobra/cmd/add.go +++ b/cobra/cmd/add.go @@ -14,7 +14,6 @@ package cmd import ( - "os" "path" "fmt" "path/filepath" @@ -52,22 +51,13 @@ Example: cobra add server -> resulting in a new cmd/server.go guessProjectPath() // Iterate over command parts delimited by "/". - currentPath := "" cmdParts := strings.Split(args[0], "/") - for n, cmd := range cmdParts { - // - if n > 0 { - // Set parent name to previous command's name. - pName = cmdParts[n-1] - - // - currentPath += pName + "/" + for i := len(cmdParts) - 1; i >= 0; i-- { + if i > 0 { + pName = cmdParts[i-1] } - - createCmdFile(cmd, currentPath) + createCmdFile(cmdParts[i], buildPath(cmdParts, i - 1)) } - - }, } @@ -93,7 +83,8 @@ package {{ .packageName }} import ( "fmt" - + {{ if .importpath }} + "{{ .importpath }}"{{ end }} "github.com/spf13/cobra" ) @@ -114,18 +105,19 @@ to quickly create a Cobra application.` + "`" + `, } func init() { - {{ .parentName | title }}.AddCommand({{ .cmdName | title }}Cmd) + {{ if eq .parentName "RootCmd" }}{{ .parentName }}.AddCommand({{ .cmdName | title }}Cmd){{ end }} + {{ $cmdName := .cmdName }}{{ range $child := .children }} + {{ $cmdName | title }}Cmd.AddCommand({{ $cmdName }}.{{ $child | title }}Cmd){{ end }} // Here you will define your flags and configuration settings. // Cobra supports Persistent Flags which will work for this command // and all subcommands, e.g.: - // {{.cmdName}}Cmd.PersistentFlags().String("foo", "", "A help for foo") + // {{ .cmdName | title }}Cmd.PersistentFlags().String("foo", "", "A help for foo") // Cobra supports local flags which will only run when this command // is called directly, e.g.: - // {{.cmdName}}Cmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") - + // {{ .cmdName | title }}Cmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") } ` @@ -148,12 +140,28 @@ func init() { path := filepath.Join(ProjectPath(), guessCmdDir(), cmdPath) filename := cmdName + ".go" + children := getChildNames(path, cmdName) + if len(children) > 0 { + data["importpath"] = filepath.Join( + guessImportPath(), + guessCmdDir(), + cmdPath, + cmdName, + ) + } + data["children"] = children + // Check if the file exists before trying to create it. - if _, err := os.Stat(filepath.Join(path, filename)); os.IsNotExist(err) { + if doesExist, err := exists(filepath.Join(path, filename)); !doesExist { if err = writeTemplateToFile(path, filename, template, data); err != nil { er(err) } fmt.Println(cmdName, "created at", path) + } else if len(children) > 0 { + if err = writeTemplateToFile(path, filename, template, data); err != nil { + er(err) + } + fmt.Println(cmdName, "updated at", path) } else { fmt.Println(cmdName, "already exists at", path) } diff --git a/cobra/cmd/helpers.go b/cobra/cmd/helpers.go index 8ee3bb0f..9d025c58 100644 --- a/cobra/cmd/helpers.go +++ b/cobra/cmd/helpers.go @@ -248,7 +248,10 @@ func writeStringToFile(path, file, text string) error { } func templateToReader(tpl string, data interface{}) (io.Reader, error) { - tmpl := template.New("").Funcs(template.FuncMap{"title": strings.Title}) + tmpl := template.New("").Funcs(template.FuncMap{ + "title": strings.Title, + "eq": func(a, b interface{}) bool { return a == b }, + }) tmpl.Funcs(funcMap) tmpl, err := tmpl.Parse(tpl) @@ -348,3 +351,25 @@ func commentifyString(in string) string { } return strings.Join(newlines, "\n") } + +func buildPath(parts []string, end int) string { + if end > -1 { + return filepath.Join(buildPath(parts, end - 1), parts[end]) + } + return "" +} + +func getChildNames(path, name string) []string { + childNames := []string{} + fileNames := []string{} + if file, err := os.Open(filepath.Join(path, name)); err == nil { + fileNames, err = file.Readdirnames(0) + } + for _, name := range fileNames { + // Only consider files with the .go extension to be child commands. + if ext := filepath.Ext(name); ext == ".go" { + childNames = append(childNames, strings.TrimSuffix(name, ext)) + } + } + return childNames +} From 02a0b7db0c784ee7c69d1272113b0e122ac069d1 Mon Sep 17 00:00:00 2001 From: Ian Walter Date: Tue, 3 May 2016 22:09:50 -0400 Subject: [PATCH 5/6] Fixing addCmd templating and finishing #266 --- cobra/cmd/add.go | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/cobra/cmd/add.go b/cobra/cmd/add.go index 09c32c17..88257758 100644 --- a/cobra/cmd/add.go +++ b/cobra/cmd/add.go @@ -55,6 +55,8 @@ Example: cobra add server -> resulting in a new cmd/server.go for i := len(cmdParts) - 1; i >= 0; i-- { if i > 0 { pName = cmdParts[i-1] + } else { + pName = "RootCmd" } createCmdFile(cmdParts[i], buildPath(cmdParts, i - 1)) } @@ -84,7 +86,8 @@ package {{ .packageName }} import ( "fmt" {{ if .importpath }} - "{{ .importpath }}"{{ end }} + "{{ .importpath }}" + {{ end -}} "github.com/spf13/cobra" ) @@ -105,10 +108,14 @@ to quickly create a Cobra application.` + "`" + `, } func init() { - {{ if eq .parentName "RootCmd" }}{{ .parentName }}.AddCommand({{ .cmdName | title }}Cmd){{ end }} - {{ $cmdName := .cmdName }}{{ range $child := .children }} - {{ $cmdName | title }}Cmd.AddCommand({{ $cmdName }}.{{ $child | title }}Cmd){{ end }} + {{ if eq .parentName "RootCmd" -}} + {{ .parentName }}.AddCommand({{ .cmdName | title }}Cmd) + {{ end -}} + {{ $cmdName := .cmdName -}} + {{ range $child := .children -}} + {{ $cmdName | title }}Cmd.AddCommand({{ $cmdName }}.{{ $child | title }}Cmd) + {{ end -}} // Here you will define your flags and configuration settings. // Cobra supports Persistent Flags which will work for this command @@ -125,7 +132,7 @@ func init() { data["packageName"] = "cmd" - // + // Determine the package name based on the command file path. if cmdPath != "" { data["packageName"] = path.Base(cmdPath) } @@ -157,11 +164,6 @@ func init() { er(err) } fmt.Println(cmdName, "created at", path) - } else if len(children) > 0 { - if err = writeTemplateToFile(path, filename, template, data); err != nil { - er(err) - } - fmt.Println(cmdName, "updated at", path) } else { fmt.Println(cmdName, "already exists at", path) } From 7e288525f01b5a4b2eb158a279954a84f14fcbc2 Mon Sep 17 00:00:00 2001 From: Ian Walter Date: Tue, 3 May 2016 22:16:18 -0400 Subject: [PATCH 6/6] Making buildPath arg more descriptive --- cobra/cmd/helpers.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cobra/cmd/helpers.go b/cobra/cmd/helpers.go index 9d025c58..d2d0bb18 100644 --- a/cobra/cmd/helpers.go +++ b/cobra/cmd/helpers.go @@ -352,9 +352,9 @@ func commentifyString(in string) string { return strings.Join(newlines, "\n") } -func buildPath(parts []string, end int) string { - if end > -1 { - return filepath.Join(buildPath(parts, end - 1), parts[end]) +func buildPath(parts []string, endIndex int) string { + if endIndex > -1 { + return filepath.Join(buildPath(parts, endIndex - 1), parts[endIndex]) } return "" }