Skip to content

Commit

Permalink
Cobra generator now works within Go modules
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
spf13 committed Jul 1, 2021
1 parent 0463683 commit 25ac916
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 36 deletions.
78 changes: 64 additions & 14 deletions cobra/cmd/init.go
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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
}
19 changes: 3 additions & 16 deletions cobra/cmd/init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
}
Expand All @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion cobra/cmd/testdata/main.go.golden
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
10 changes: 5 additions & 5 deletions cobra/cmd/testdata/root.go.golden
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,16 @@ package cmd
import (
"fmt"
"os"
"github.com/spf13/cobra"

"github.com/spf13/cobra"
"github.com/spf13/viper"
)

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:
Expand All @@ -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.
Expand All @@ -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
Expand Down

0 comments on commit 25ac916

Please sign in to comment.