Cobra generator now works within Go modules

Pretty major change in behavior, but with modules a change is needed.
Now cobra can initialize and add from within any Go module.
The experience is simplified and streamlined, but requires `go mod init` to happen first.
This commit is contained in:
Steve Francia 2021-07-01 17:47:45 -04:00
parent c9edb78acc
commit 9388e79fb4
4 changed files with 73 additions and 36 deletions

View file

@ -1,4 +1,4 @@
// Copyright © 2015 Steve Francia <spf@spf13.com>.
// Copyright © 2021 Steve Francia <spf@spf13.com>.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -14,42 +14,41 @@
package cmd
import (
"encoding/json"
"fmt"
"os"
"os/exec"
"path"
"path/filepath"
"strings"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var (
pkgName string
initCmd = &cobra.Command{
Use: "init [name]",
Use: "init [path]",
Aliases: []string{"initialize", "initialise", "create"},
Short: "Initialize a Cobra Application",
Long: `Initialize (cobra init) will create a new application, with a license
and the appropriate structure for a Cobra-based CLI application.
* If a name is provided, a directory with that name will be created in the current directory;
* If no name is provided, the current directory will be assumed;
Cobra init must be run inside of a go module (please run "go mod init <MODNAME>" first)
`,
Run: func(_ *cobra.Command, args []string) {
projectPath, err := initializeProject(args)
cobra.CheckErr(err)
cobra.CheckErr(goGet("github.com/spf13/cobra"))
if viper.GetBool("useViper") {
cobra.CheckErr(goGet("github.com/spf13/viper"))
}
fmt.Printf("Your Cobra application is ready at\n%s\n", projectPath)
},
}
)
func init() {
initCmd.Flags().StringVar(&pkgName, "pkg-name", "", "fully qualified pkg name")
cobra.CheckErr(initCmd.MarkFlagRequired("pkg-name"))
}
func initializeProject(args []string) (string, error) {
wd, err := os.Getwd()
if err != nil {
@ -62,13 +61,15 @@ func initializeProject(args []string) (string, error) {
}
}
modName := getModImportPath()
project := &Project{
AbsolutePath: wd,
PkgName: pkgName,
PkgName: modName,
Legal: getLicense(),
Copyright: copyrightLine(),
Viper: viper.GetBool("useViper"),
AppName: path.Base(pkgName),
AppName: path.Base(modName),
}
if err := project.Create(); err != nil {
@ -77,3 +78,52 @@ func initializeProject(args []string) (string, error) {
return project.AbsolutePath, nil
}
func getModImportPath() string {
mod, cd := parseModInfo()
return path.Join(mod.Path, fileToURL(strings.TrimPrefix(cd.Dir, mod.Dir)))
}
func fileToURL(in string) string {
i := strings.Split(in, string(filepath.Separator))
return path.Join(i...)
}
func parseModInfo() (Mod, CurDir) {
var mod Mod
var dir CurDir
m := modInfoJSON("-m")
cobra.CheckErr(json.Unmarshal(m, &mod))
// Unsure why, but if no module is present Path is set to this string.
if mod.Path == "command-line-arguments" {
cobra.CheckErr("Please run `go mod init <MODNAME>` before `cobra init`")
}
e := modInfoJSON("-e")
cobra.CheckErr(json.Unmarshal(e, &dir))
return mod, dir
}
type Mod struct {
Path, Dir, GoMod string
}
type CurDir struct {
Dir string
}
func goGet(mod string) error {
cmd := exec.Command("go", "get", mod)
return cmd.Run()
}
func modInfoJSON(args ...string) []byte {
cmdArgs := append([]string{"list", "-json"}, args...)
out, err := exec.Command("go", cmdArgs...).Output()
cobra.CheckErr(err)
return out
}

View file

@ -16,8 +16,8 @@ func getProject() *Project {
AbsolutePath: fmt.Sprintf("%s/testproject", wd),
Legal: getLicense(),
Copyright: copyrightLine(),
AppName: "testproject",
PkgName: "github.com/spf13/testproject",
AppName: "cmd",
PkgName: "github.com/spf13/cobra/cobra/cmd/cmd",
Viper: true,
}
}
@ -37,29 +37,16 @@ func TestGoldenInitCmd(t *testing.T) {
expectErr bool
}{
{
name: "successfully creates a project with name",
name: "successfully creates a project based on module",
args: []string{"testproject"},
pkgName: "github.com/spf13/testproject",
expectErr: false,
},
{
name: "returns error when passing an absolute path for project",
args: []string{dir},
pkgName: "github.com/spf13/testproject",
expectErr: true,
},
{
name: "returns error when passing a relative path for project",
args: []string{"github.com/spf13/testproject"},
pkgName: "github.com/spf13/testproject",
expectErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assertNoErr(t, initCmd.Flags().Set("pkg-name", tt.pkgName))
viper.Set("useViper", true)
viper.Set("license", "apache")
projectPath, err := initializeProject(tt.args)

View file

@ -15,7 +15,7 @@ limitations under the License.
*/
package main
import "github.com/spf13/testproject/cmd"
import "github.com/spf13/cobra/cobra/cmd/cmd"
func main() {
cmd.Execute()

View file

@ -18,8 +18,8 @@ package cmd
import (
"fmt"
"os"
"github.com/spf13/cobra"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
@ -27,7 +27,7 @@ var cfgFile string
// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Use: "testproject",
Use: "cmd",
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:
@ -53,7 +53,7 @@ func init() {
// Cobra supports persistent flags, which, if defined here,
// will be global for your application.
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.testproject.yaml)")
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.cmd.yaml)")
// Cobra also supports local flags, which will only run
// when this action is called directly.
@ -70,10 +70,10 @@ func initConfig() {
home, err := os.UserHomeDir()
cobra.CheckErr(err)
// Search config in home directory with name ".testproject" (without extension).
// Search config in home directory with name ".cmd" (without extension).
viper.AddConfigPath(home)
viper.SetConfigType("yaml")
viper.SetConfigName(".testproject")
viper.SetConfigName(".cmd")
}
viper.AutomaticEnv() // read in environment variables that match