diff --git a/cmd/add.go b/cmd/add.go index 207c651..dee98cd 100644 --- a/cmd/add.go +++ b/cmd/add.go @@ -22,8 +22,10 @@ import ( ) var ( - packageName string - parentName string + packageName string + parentName string + dirName string + addCopyrightLine bool addCmd = &cobra.Command{ Use: "add [command name]", @@ -54,6 +56,9 @@ Example: cobra-cli add server -> resulting in a new cmd/server.go`, } wd, err := os.Getwd() + if dirName != "" { + wd += "/" + dirName + } cobra.CheckErr(err) commandName := validateCmdName(args[0]) @@ -63,9 +68,12 @@ Example: cobra-cli add server -> resulting in a new cmd/server.go`, Project: &Project{ AbsolutePath: wd, Legal: getLicense(), - Copyright: copyrightLine(), + LocalVars: localVars, }, } + if addCopyrightLine { + command.Copyright = copyrightLine() + } cobra.CheckErr(command.Create()) @@ -77,6 +85,8 @@ Example: cobra-cli add server -> resulting in a new cmd/server.go`, func init() { addCmd.Flags().StringVarP(&packageName, "package", "t", "", "target package name (e.g. github.com/spf13/hugo)") addCmd.Flags().StringVarP(&parentName, "parent", "p", "rootCmd", "variable name of parent command for this command") + addCmd.Flags().StringVarP(&dirName, "dir", "d", "", "relative path to the command's main package") + addCmd.Flags().BoolVarP(&addCopyrightLine, "copyright", "c", true, "add copyright line to generated command file") cobra.CheckErr(addCmd.Flags().MarkDeprecated("package", "this operation has been removed.")) } diff --git a/cmd/init.go b/cmd/init.go index aeda40a..c7ab565 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -69,14 +69,15 @@ func initializeProject(args []string) (string, error) { return "", err } + modName := getModImportPath() + if len(args) > 0 { if args[0] != "." { wd = fmt.Sprintf("%s/%s", wd, args[0]) + modName = fmt.Sprintf("%s/%s", modName, args[0]) } } - modName := getModImportPath() - project := &Project{ AbsolutePath: wd, PkgName: modName, @@ -84,6 +85,7 @@ func initializeProject(args []string) (string, error) { Copyright: copyrightLine(), Viper: viper.GetBool("useViper"), AppName: path.Base(modName), + LocalVars: localVars, } if err := project.Create(); err != nil { diff --git a/cmd/project.go b/cmd/project.go index ec8980e..569c9cd 100644 --- a/cmd/project.go +++ b/cmd/project.go @@ -3,6 +3,7 @@ package cmd import ( "fmt" "os" + "strings" "text/template" "github.com/spf13/cobra" @@ -18,6 +19,7 @@ type Project struct { Legal License Viper bool AppName string + LocalVars bool } type Command struct { @@ -30,7 +32,7 @@ func (p *Project) Create() error { // check if AbsolutePath exists if _, err := os.Stat(p.AbsolutePath); os.IsNotExist(err) { // create directory - if err := os.Mkdir(p.AbsolutePath, 0754); err != nil { + if err := os.MkdirAll(p.AbsolutePath, 0754); err != nil { return err } } @@ -50,7 +52,7 @@ func (p *Project) Create() error { // create cmd/root.go if _, err = os.Stat(fmt.Sprintf("%s/cmd", p.AbsolutePath)); os.IsNotExist(err) { - cobra.CheckErr(os.Mkdir(fmt.Sprintf("%s/cmd", p.AbsolutePath), 0751)) + cobra.CheckErr(os.MkdirAll(fmt.Sprintf("%s/cmd", p.AbsolutePath), 0751)) } rootFile, err := os.Create(fmt.Sprintf("%s/cmd/root.go", p.AbsolutePath)) if err != nil { @@ -58,7 +60,11 @@ func (p *Project) Create() error { } defer rootFile.Close() - rootTemplate := template.Must(template.New("root").Parse(string(tpl.RootTemplate()))) + tmpl := tpl.RootTemplateGlobal() + if p.LocalVars { + tmpl = tpl.RootTemplateLocal() + } + rootTemplate := template.Must(template.New("root").Parse(string(tmpl))) err = rootTemplate.Execute(rootFile, p) if err != nil { return err @@ -83,13 +89,34 @@ func (p *Project) createLicenseFile() error { } func (c *Command) Create() error { - cmdFile, err := os.Create(fmt.Sprintf("%s/cmd/%s.go", c.AbsolutePath, c.CmdName)) + filename := c.CmdName + if c.CmdParent != "rootCmd" { + filename = fmt.Sprintf("%s_%s", strings.TrimSuffix(c.CmdParent, "Cmd"), c.CmdName) + } + cmdFile, err := os.Create(fmt.Sprintf("%s/cmd/%s.go", c.AbsolutePath, filename)) if err != nil { return err } defer cmdFile.Close() - commandTemplate := template.Must(template.New("sub").Parse(string(tpl.AddCommandTemplate()))) + funcMap := template.FuncMap{ + // The name "title" is what the function will be called in the template text. + "typeName": func(cmd string, parent string) string { + name := strings.Title(cmd) + if parent != "rootCmd" { + name = strings.Title(strings.TrimSuffix(parent, "Cmd")) + name + } + name += "Cmd" + return name + }, + "title": strings.Title, + } + + tmpl := tpl.AddCommandTemplateGlobal() + if c.LocalVars { + tmpl = tpl.AddCommandTemplateLocal() + } + commandTemplate := template.Must(template.New("sub").Funcs(funcMap).Parse(string(tmpl))) err = commandTemplate.Execute(cmdFile, c) if err != nil { return err diff --git a/cmd/root.go b/cmd/root.go index d1fa407..4feefb8 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -26,6 +26,7 @@ var ( // Used for flags. cfgFile string userLicense string + localVars bool rootCmd = &cobra.Command{ Use: "cobra-cli", @@ -48,6 +49,7 @@ func init() { rootCmd.PersistentFlags().StringP("author", "a", "YOUR NAME", "author name for copyright attribution") rootCmd.PersistentFlags().StringVarP(&userLicense, "license", "l", "", "name of license for the project") rootCmd.PersistentFlags().Bool("viper", false, "use Viper for configuration") + rootCmd.PersistentFlags().BoolVarP(&localVars, "local", "L", false, "project uses local-scoped variables") cobra.CheckErr(viper.BindPFlag("author", rootCmd.PersistentFlags().Lookup("author"))) cobra.CheckErr(viper.BindPFlag("useViper", rootCmd.PersistentFlags().Lookup("viper"))) viper.SetDefault("author", "NAME HERE ") diff --git a/go.mod b/go.mod index 4acdff1..8a77e02 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/spf13/cobra-cli -go 1.15 +go 1.16 require ( github.com/spf13/cobra v1.6.1 diff --git a/tpl/add-command.global.tmpl b/tpl/add-command.global.tmpl new file mode 100644 index 0000000..c0b7eff --- /dev/null +++ b/tpl/add-command.global.tmpl @@ -0,0 +1,40 @@ +/* +{{ .Project.Copyright }} +{{ if .Legal.Header }}{{ .Legal.Header }}{{ end }} +*/ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +// {{ .CmdName }}Cmd represents the {{ .CmdName }} command +var {{ .CmdName }}Cmd = &cobra.Command{ + Use: "{{ .CmdName }}", + 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: + +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") + }, +} + +func init() { + {{ .CmdParent }}.AddCommand({{ .CmdName }}Cmd) + + // 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") + + // 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") +} diff --git a/tpl/add-command.local.tmpl b/tpl/add-command.local.tmpl new file mode 100644 index 0000000..81b4785 --- /dev/null +++ b/tpl/add-command.local.tmpl @@ -0,0 +1,55 @@ +{{ if or ( ne .Project.Copyright "" ) .Legal.Header }} +/* +{{ .Project.Copyright }} +{{ if .Legal.Header }}{{ .Legal.Header }}{{ end }} +*/ +{{ end }}package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +// {{ typeName .CmdName .CmdParent }} represents the {{ .CmdName }} command +type {{ typeName .CmdName .CmdParent }} struct { + cmd *cobra.Command +} + +func (c *{{ typeName .CmdName .CmdParent }}) RunE(_ *cobra.Command, args []string) error { + //Command execution goes here + + fmt.Printf("running %s", c.cmd.Use) + + return nil +} + +func Add{{ typeName .CmdName .CmdParent }}({{.CmdParent}} *cobra.Command) { + {{.CmdName}} := {{ typeName .CmdName .CmdParent }}{ + cmd: &cobra.Command{ + Use: "{{ .CmdName }}", + 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: + +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.`, + }, + } + // 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") + + // 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") + {{.CmdParent}}.AddCommand({{.CmdName}}.cmd) + {{.CmdName}}.cmd.RunE = {{.CmdName}}.RunE + + // Add child commands here + // Add{{title .CmdName}}ChildCmd({{.CmdName}}.cmd) +} + diff --git a/tpl/main.go b/tpl/main.go index 009524c..84c9702 100644 --- a/tpl/main.go +++ b/tpl/main.go @@ -13,6 +13,10 @@ package tpl +import ( + _ "embed" +) + func MainTemplate() []byte { return []byte(`/* {{ .Copyright }} @@ -28,136 +32,30 @@ func main() { `) } -func RootTemplate() []byte { - return []byte(`/* -{{ .Copyright }} -{{ if .Legal.Header }}{{ .Legal.Header }}{{ end }} -*/ -package cmd - -import ( -{{- if .Viper }} - "fmt"{{ end }} - "os" +//go:embed root.local.tmpl +var rootLocalTemplate []byte - "github.com/spf13/cobra" -{{- if .Viper }} - "github.com/spf13/viper"{{ end }} -) - -{{ if .Viper -}} -var cfgFile string -{{- end }} - -// rootCmd represents the base command when called without any subcommands -var rootCmd = &cobra.Command{ - Use: "{{ .AppName }}", - Short: "A brief description of your application", - Long: ` + "`" + `A longer description that spans multiple lines and likely contains -examples and usage of using your application. For example: - -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.` + "`" + `, - // Uncomment the following line if your bare application - // has an action associated with it: - // Run: func(cmd *cobra.Command, args []string) { }, +func RootTemplateLocal() []byte { + return rootLocalTemplate } -// Execute adds all child commands to the root command and sets flags appropriately. -// This is called by main.main(). It only needs to happen once to the rootCmd. -func Execute() { - err := rootCmd.Execute() - if err != nil { - os.Exit(1) - } -} +//go:embed root.global.tmpl +var rootGlobalTemplate []byte -func init() { -{{- if .Viper }} - cobra.OnInitialize(initConfig) -{{ end }} - // Here you will define your flags and configuration settings. - // Cobra supports persistent flags, which, if defined here, - // will be global for your application. -{{ if .Viper }} - rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.{{ .AppName }}.yaml)") -{{ else }} - // rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.{{ .AppName }}.yaml)") -{{ end }} - // Cobra also supports local flags, which will only run - // when this action is called directly. - rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +func RootTemplateGlobal() []byte { + return rootGlobalTemplate } -{{ if .Viper -}} -// initConfig reads in config file and ENV variables if set. -func initConfig() { - if cfgFile != "" { - // Use config file from the flag. - viper.SetConfigFile(cfgFile) - } else { - // Find home directory. - home, err := os.UserHomeDir() - cobra.CheckErr(err) - - // Search config in home directory with name ".{{ .AppName }}" (without extension). - viper.AddConfigPath(home) - viper.SetConfigType("yaml") - viper.SetConfigName(".{{ .AppName }}") - } +//go:embed add-command.local.tmpl +var localAddCommandTemplate []byte - viper.AutomaticEnv() // read in environment variables that match - - // If a config file is found, read it in. - if err := viper.ReadInConfig(); err == nil { - fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed()) - } -} -{{- end }} -`) -} - -func AddCommandTemplate() []byte { - return []byte(`/* -{{ .Project.Copyright }} -{{ if .Legal.Header }}{{ .Legal.Header }}{{ end }} -*/ -package cmd - -import ( - "fmt" - - "github.com/spf13/cobra" -) - -// {{ .CmdName }}Cmd represents the {{ .CmdName }} command -var {{ .CmdName }}Cmd = &cobra.Command{ - Use: "{{ .CmdName }}", - 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: - -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") - }, +func AddCommandTemplateLocal() []byte { + return localAddCommandTemplate } -func init() { - {{ .CmdParent }}.AddCommand({{ .CmdName }}Cmd) +//go:embed add-command.global.tmpl +var globalAddCommandTemplate []byte - // 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") - - // 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") -} -`) +func AddCommandTemplateGlobal() []byte { + return globalAddCommandTemplate } diff --git a/tpl/root.global.tmpl b/tpl/root.global.tmpl new file mode 100644 index 0000000..e37ccf6 --- /dev/null +++ b/tpl/root.global.tmpl @@ -0,0 +1,86 @@ +/* +{{ .Copyright }} +{{ if .Legal.Header }}{{ .Legal.Header }}{{ end }} +*/ +package cmd + +import ( +{{- if .Viper }} + "fmt"{{ end }} + "os" + + "github.com/spf13/cobra" +{{- if .Viper }} + "github.com/spf13/viper"{{ end }} +) + +{{ if .Viper -}} +var cfgFile string +{{- end }} + +// rootCmd represents the base command when called without any subcommands +var rootCmd = &cobra.Command{ + Use: "{{ .AppName }}", + Short: "A brief description of your application", + Long: `A longer description that spans multiple lines and likely contains +examples and usage of using your application. For example: + +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.`, + // Uncomment the following line if your bare application + // has an action associated with it: + // Run: func(cmd *cobra.Command, args []string) { }, +} + +// Execute adds all child commands to the root command and sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + err := rootCmd.Execute() + if err != nil { + os.Exit(1) + } +} + +func init() { +{{- if .Viper }} + cobra.OnInitialize(initConfig) +{{ end }} + // Here you will define your flags and configuration settings. + // Cobra supports persistent flags, which, if defined here, + // will be global for your application. +{{ if .Viper }} + rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.{{ .AppName }}.yaml)") +{{ else }} + // rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.{{ .AppName }}.yaml)") +{{ end }} + // Cobra also supports local flags, which will only run + // when this action is called directly. + rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} + +{{ if .Viper -}} +// initConfig reads in config file and ENV variables if set. +func initConfig() { + if cfgFile != "" { + // Use config file from the flag. + viper.SetConfigFile(cfgFile) + } else { + // Find home directory. + home, err := os.UserHomeDir() + cobra.CheckErr(err) + + // Search config in home directory with name ".{{ .AppName }}" (without extension). + viper.AddConfigPath(home) + viper.SetConfigType("yaml") + viper.SetConfigName(".{{ .AppName }}") + } + + viper.AutomaticEnv() // read in environment variables that match + + // If a config file is found, read it in. + if err := viper.ReadInConfig(); err == nil { + fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed()) + } +} +{{- end }} diff --git a/tpl/root.local.tmpl b/tpl/root.local.tmpl new file mode 100644 index 0000000..60ce617 --- /dev/null +++ b/tpl/root.local.tmpl @@ -0,0 +1,90 @@ +/* +{{ .Copyright }} +{{ if .Legal.Header }}{{ .Legal.Header }}{{ end }} +*/ +package cmd + +import ( +{{- if .Viper }} + "fmt"{{ end }} + "os" + + "github.com/spf13/cobra" +{{- if .Viper }} + "github.com/spf13/viper"{{ end }} +) + +{{ if .Viper -}} +var cfgFile string +{{- end }} + + +// Execute adds all child commands to the root command and sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { +// rootCmd represents the base command when called without any subcommands + rootCmd := &cobra.Command{ + Use: "{{ .AppName }}", + Short: "A brief description of your application", + Long: `A longer description that spans multiple lines and likely contains +examples and usage of using your application. For example: + +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.`, + // Uncomment the following line if your bare application + // has an action associated with it: + // Run: func(cmd *cobra.Command, args []string) { }, +} +{{- if .Viper }} + cobra.OnInitialize(initConfig) +{{ end }} + // Here you will define your flags and configuration settings. + // Cobra supports persistent flags, which, if defined here, + // will be global for your application. +{{ if .Viper }} + rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.{{ .AppName }}.yaml)") +{{ else }} + // rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.{{ .AppName }}.yaml)") +{{ end }} + // Cobra also supports local flags, which will only run + // when this action is called directly. + // rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") + + + // Add child commands here + // AddChildCmd(rootCmd) + + + err := rootCmd.Execute() + if err != nil { + os.Exit(1) + } +} + +{{ if .Viper -}} +// initConfig reads in config file and ENV variables if set. +func initConfig() { + if cfgFile != "" { + // Use config file from the flag. + viper.SetConfigFile(cfgFile) + } else { + // Find home directory. + home, err := os.UserHomeDir() + cobra.CheckErr(err) + + // Search config in home directory with name ".{{ .AppName }}" (without extension). + viper.AddConfigPath(home) + viper.SetConfigType("yaml") + viper.SetConfigName(".{{ .AppName }}") + } + + viper.AutomaticEnv() // read in environment variables that match + + // If a config file is found, read it in. + if err := viper.ReadInConfig(); err == nil { + fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed()) + } +} +{{- end }} +