cmd: Rewrite

This commit is contained in:
Albert Nigmatzianov 2017-04-29 12:02:02 +02:00
parent 4061f41c9a
commit 32756eb440
9 changed files with 388 additions and 478 deletions

View File

@ -79,11 +79,11 @@ A few good real world examples may better illustrate this point.
In the following example, 'server' is a command, and 'port' is a flag: In the following example, 'server' is a command, and 'port' is a flag:
> hugo server --port=1313 hugo server --port=1313
In this command we are telling Git to clone the url bare. In this command we are telling Git to clone the url bare.
> git clone URL --bare git clone URL --bare
## Commands ## Commands
@ -130,7 +130,7 @@ Using Cobra is easy. First, use `go get` to install the latest version
of the library. This command will install the `cobra` generator executible of the library. This command will install the `cobra` generator executible
along with the library: along with the library:
> go get -v github.com/spf13/cobra/cobra go get -v github.com/spf13/cobra/cobra
Next, include Cobra in your application: Next, include Cobra in your application:
@ -180,7 +180,7 @@ commands you want. It's the easiest way to incorporate Cobra into your applicati
In order to use the cobra command, compile it using the following command: In order to use the cobra command, compile it using the following command:
> go get github.com/spf13/cobra/cobra go get github.com/spf13/cobra/cobra
This will create the cobra executable under your `$GOPATH/bin` directory. This will create the cobra executable under your `$GOPATH/bin` directory.

View File

@ -15,8 +15,8 @@ package cmd
import ( import (
"fmt" "fmt"
"os"
"path/filepath" "path/filepath"
"strings"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
@ -26,9 +26,8 @@ func init() {
RootCmd.AddCommand(addCmd) RootCmd.AddCommand(addCmd)
} }
var pName string var parentName string
// initialize Command
var addCmd = &cobra.Command{ var addCmd = &cobra.Command{
Use: "add [command name]", Use: "add [command name]",
Aliases: []string{"command"}, Aliases: []string{"command"},
@ -40,35 +39,28 @@ and register it to its parent (default RootCmd).
If you want your command to be public, pass in the command name If you want your command to be public, pass in the command name
with an initial uppercase letter. with an initial uppercase letter.
Example: cobra add server -> resulting in a new cmd/server.go Example: cobra add server -> resulting in a new cmd/server.go`,
`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
if len(args) != 1 { if len(args) < 1 {
er("add needs a name for the command") er("add needs a name for the command")
} }
guessProjectPath() wd, err := os.Getwd()
createCmdFile(args[0]) if err != nil {
er(err)
}
project := NewProjectFromPath(wd)
createCmdFile(project, args[0])
}, },
} }
func init() { func init() {
addCmd.Flags().StringVarP(&pName, "parent", "p", "RootCmd", "name of parent command for this command") addCmd.Flags().StringVarP(&parentName, "parent", "p", "RootCmd", "name of parent command for this command")
} }
func parentName() string { func createCmdFile(project *Project, cmdName string) {
if !strings.HasSuffix(strings.ToLower(pName), "cmd") { template := `{{comment .copyright}}
return pName + "Cmd" {{comment .license}}
}
return pName
}
func createCmdFile(cmdName string) {
lic := getLicense()
template := `{{ comment .copyright }}
{{ comment .license }}
package cmd package cmd
@ -79,8 +71,8 @@ import (
) )
// {{.cmdName}}Cmd represents the {{.cmdName}} command // {{.cmdName}}Cmd represents the {{.cmdName}} command
var {{ .cmdName }}Cmd = &cobra.Command{ var {{.cmdName}}Cmd = &cobra.Command{
Use: "{{ .cmdName }}", Use: "{{.cmdName}}",
Short: "A brief description of your command", Short: "A brief description of your command",
Long: ` + "`" + `A longer description that spans multiple lines and likely contains examples Long: ` + "`" + `A longer description that spans multiple lines and likely contains examples
and usage of using your command. For example: and usage of using your command. For example:
@ -89,13 +81,12 @@ Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files This application is a tool to generate the needed files
to quickly create a Cobra application.` + "`" + `, to quickly create a Cobra application.` + "`" + `,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
// TODO: Work your own magic here fmt.Println("{{.cmdName}} called")
fmt.Println("{{ .cmdName }} called")
}, },
} }
func init() { func init() {
{{ .parentName }}.AddCommand({{ .cmdName }}Cmd) {{.parentName}}.AddCommand({{.cmdName}}Cmd)
// Here you will define your flags and configuration settings. // Here you will define your flags and configuration settings.
@ -106,23 +97,26 @@ func init() {
// Cobra supports local flags which will only run when this command // Cobra supports local flags which will only run when this command
// is called directly, e.g.: // is called directly, e.g.:
// {{.cmdName}}Cmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") // {{.cmdName}}Cmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
} }
` `
var data map[string]interface{} data := make(map[string]interface{})
data = make(map[string]interface{})
data["copyright"] = copyrightLine() data["copyright"] = copyrightLine()
data["license"] = lic.Header data["license"] = project.License().Header
data["appName"] = projectName()
data["viper"] = viper.GetBool("useViper") data["viper"] = viper.GetBool("useViper")
data["parentName"] = parentName() data["parentName"] = parentName
data["cmdName"] = cmdName data["cmdName"] = cmdName
err := writeTemplateToFile(filepath.Join(ProjectPath(), guessCmdDir()), cmdName+".go", template, data) filePath := filepath.Join(project.AbsPath(), project.CmdDir(), cmdName+".go")
cmdScript, err := executeTemplate(template, data)
if err != nil { if err != nil {
er(err) er(err)
} }
fmt.Println(cmdName, "created at", filepath.Join(ProjectPath(), guessCmdDir(), cmdName+".go")) err = writeStringToFile(filePath, cmdScript)
if err != nil {
er(err)
}
fmt.Println(cmdName, "created at", filePath)
} }

View File

@ -23,266 +23,103 @@ import (
"text/template" "text/template"
) )
// var BaseDir = "" var funcMap = template.FuncMap{
// var AppName = "" "comment": commentifyString,
// var CommandDir = "" }
var funcMap template.FuncMap var projectPath string
var projectPath = ""
var inputPath = ""
var projectBase = ""
// for testing only var cmdDirs = [...]string{"cmd", "cmds", "command", "commands"}
var testWd = "" var goPaths, srcPaths []string
var cmdDirs = []string{"cmd", "cmds", "command", "commands"}
func init() { func init() {
funcMap = template.FuncMap{ // Initialize goPaths and srcPaths
"comment": commentifyString, envGoPath := os.Getenv("GOPATH")
if envGoPath == "" {
er("$GOPATH is not set")
}
goPaths = filepath.SplitList(envGoPath)
srcPaths = make([]string, 0, len(goPaths))
for _, goPath := range goPaths {
srcPaths = append(srcPaths, filepath.Join(goPath, "src"))
} }
} }
func er(msg interface{}) { func er(msg interface{}) {
fmt.Println("Error:", msg) fmt.Println("Error:", msg)
os.Exit(-1) os.Exit(1)
}
// Check if a file or directory exists.
func exists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
func ProjectPath() string {
if projectPath == "" {
guessProjectPath()
}
return projectPath
}
// wrapper of the os package so we can test better
func getWd() (string, error) {
if testWd == "" {
return os.Getwd()
}
return testWd, nil
}
func guessCmdDir() string {
guessProjectPath()
if b, _ := isEmpty(projectPath); b {
return "cmd"
}
files, _ := filepath.Glob(projectPath + string(os.PathSeparator) + "c*")
for _, f := range files {
for _, c := range cmdDirs {
if f == c {
return c
}
}
}
return "cmd"
}
func guessImportPath() string {
guessProjectPath()
if !strings.HasPrefix(projectPath, getSrcPath()) {
er("Cobra only supports project within $GOPATH")
}
return filepath.ToSlash(filepath.Clean(strings.TrimPrefix(projectPath, getSrcPath())))
}
func getSrcPath() string {
return filepath.Join(os.Getenv("GOPATH"), "src") + string(os.PathSeparator)
}
func projectName() string {
return filepath.Base(ProjectPath())
}
func guessProjectPath() {
// if no path is provided... assume CWD.
if inputPath == "" {
x, err := getWd()
if err != nil {
er(err)
}
// inspect CWD
base := filepath.Base(x)
// if we are in the cmd directory.. back up
for _, c := range cmdDirs {
if base == c {
projectPath = filepath.Dir(x)
return
}
}
if projectPath == "" {
projectPath = filepath.Clean(x)
return
}
}
srcPath := getSrcPath()
// if provided, inspect for logical locations
if strings.ContainsRune(inputPath, os.PathSeparator) {
if filepath.IsAbs(inputPath) || filepath.HasPrefix(inputPath, string(os.PathSeparator)) {
// if Absolute, use it
projectPath = filepath.Clean(inputPath)
return
}
// If not absolute but contains slashes,
// assuming it means create it from $GOPATH
count := strings.Count(inputPath, string(os.PathSeparator))
switch count {
// If only one directory deep, assume "github.com"
case 1:
projectPath = filepath.Join(srcPath, "github.com", inputPath)
return
case 2:
projectPath = filepath.Join(srcPath, inputPath)
return
default:
er("Unknown directory")
}
} else {
// hardest case.. just a word.
if projectBase == "" {
x, err := getWd()
if err == nil {
projectPath = filepath.Join(x, inputPath)
return
}
er(err)
} else {
projectPath = filepath.Join(srcPath, projectBase, inputPath)
return
}
}
} }
// isEmpty checks if a given path is empty. // isEmpty checks if a given path is empty.
func isEmpty(path string) (bool, error) { func isEmpty(path string) bool {
if b, _ := exists(path); !b {
return false, fmt.Errorf("%q path does not exist", path)
}
fi, err := os.Stat(path) fi, err := os.Stat(path)
if err != nil { if err != nil {
return false, err er(err)
} }
if fi.IsDir() { if fi.IsDir() {
f, err := os.Open(path) f, err := os.Open(path)
// FIX: Resource leak - f.close() should be called here by defer or is missed
// if the err != nil branch is taken.
defer f.Close()
if err != nil { if err != nil {
return false, err er(err)
} }
list, _ := f.Readdir(-1) defer f.Close()
// f.Close() - see bug fix above dirs, err := f.Readdirnames(1)
return len(list) == 0, nil if err != nil && err != io.EOF {
er(err)
}
return len(dirs) == 0
} }
return fi.Size() == 0, nil return fi.Size() == 0
} }
// isDir checks if a given path is a directory. // exists checks if a file or directory exists.
func isDir(path string) (bool, error) { func exists(path string) bool {
fi, err := os.Stat(path) if path == "" {
if err != nil { return false
return false, err
} }
return fi.IsDir(), nil _, err := os.Stat(path)
if err == nil {
return true
}
if !os.IsNotExist(err) {
er(err)
}
return false
} }
// dirExists checks if a path exists and is a directory. func executeTemplate(tmplStr string, data interface{}) (string, error) {
func dirExists(path string) (bool, error) { tmpl, err := template.New("").Funcs(funcMap).Parse(tmplStr)
fi, err := os.Stat(path)
if err == nil && fi.IsDir() {
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
func writeTemplateToFile(path string, file string, template string, data interface{}) error {
filename := filepath.Join(path, file)
r, err := templateToReader(template, data)
if err != nil { if err != nil {
return err return "", err
} }
err = safeWriteToDisk(filename, r)
if err != nil {
return err
}
return nil
}
func writeStringToFile(path, file, text string) error {
filename := filepath.Join(path, file)
r := strings.NewReader(text)
err := safeWriteToDisk(filename, r)
if err != nil {
return err
}
return nil
}
func templateToReader(tpl string, data interface{}) (io.Reader, error) {
tmpl := template.New("")
tmpl.Funcs(funcMap)
tmpl, err := tmpl.Parse(tpl)
if err != nil {
return nil, err
}
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
err = tmpl.Execute(buf, data) err = tmpl.Execute(buf, data)
return buf.String(), err
return buf, err
} }
// Same as WriteToDisk but checks to see if file/directory already exists. func writeStringToFile(path string, s string) error {
return safeWriteToDisk(path, strings.NewReader(s))
}
// safeWriteToDisk as WriteToDisk but checks to see if file/directory already exists.
func safeWriteToDisk(inpath string, r io.Reader) (err error) { func safeWriteToDisk(inpath string, r io.Reader) (err error) {
dir, _ := filepath.Split(inpath) dir := filepath.Dir(inpath)
ospath := filepath.FromSlash(dir) ospath := filepath.FromSlash(dir)
if ospath != "" { if ospath != "" {
err = os.MkdirAll(ospath, 0777) // rwx, rw, r err = os.MkdirAll(ospath, 0777)
if err != nil { if err != nil {
return return
} }
} }
ex, err := exists(inpath) if exists(inpath) {
if err != nil {
return
}
if ex {
return fmt.Errorf("%v already exists", inpath) return fmt.Errorf("%v already exists", inpath)
} }
if _, err := os.Stat(inpath); err != nil && !os.IsNotExist(err) {
return err
}
file, err := os.Create(inpath) file, err := os.Create(inpath)
if err != nil { if err != nil {
@ -297,15 +134,15 @@ func safeWriteToDisk(inpath string, r io.Reader) (err error) {
func commentifyString(in string) string { func commentifyString(in string) string {
var newlines []string var newlines []string
lines := strings.Split(in, "\n") lines := strings.Split(in, "\n")
for _, x := range lines { for _, line := range lines {
if !strings.HasPrefix(x, "//") { if strings.HasPrefix(line, "//") {
if x != "" { newlines = append(newlines, line)
newlines = append(newlines, "// "+x)
} else {
newlines = append(newlines, "//")
}
} else { } else {
newlines = append(newlines, x) if line == "" {
newlines = append(newlines, "//")
} else {
newlines = append(newlines, "// "+line)
}
} }
} }
return strings.Join(newlines, "\n") return strings.Join(newlines, "\n")

View File

@ -1,35 +0,0 @@
package cmd
import (
"path/filepath"
"testing"
)
func checkGuess(t *testing.T, wd, input, expected string) {
testWd = wd
inputPath = input
guessProjectPath()
if projectPath != expected {
t.Errorf("Unexpected Project Path. \n Got: %q\nExpected: %q\n", projectPath, expected)
}
reset()
}
func reset() {
testWd = ""
inputPath = ""
projectPath = ""
}
func TestProjectPath(t *testing.T) {
checkGuess(t, "", filepath.Join("github.com", "spf13", "hugo"), filepath.Join(getSrcPath(), "github.com", "spf13", "hugo"))
checkGuess(t, "", filepath.Join("spf13", "hugo"), filepath.Join(getSrcPath(), "github.com", "spf13", "hugo"))
checkGuess(t, "", filepath.Join("/", "bar", "foo"), filepath.Join("/", "bar", "foo"))
checkGuess(t, "/bar/foo", "baz", filepath.Join("/", "bar", "foo", "baz"))
checkGuess(t, "/bar/foo/cmd", "", filepath.Join("/", "bar", "foo"))
checkGuess(t, "/bar/foo/command", "", filepath.Join("/", "bar", "foo"))
checkGuess(t, "/bar/foo/commands", "", filepath.Join("/", "bar", "foo"))
checkGuess(t, "github.com/spf13/hugo/../hugo", "", filepath.Join("github.com", "spf13", "hugo"))
}

View File

@ -14,10 +14,10 @@
package cmd package cmd
import ( import (
"bytes"
"fmt" "fmt"
"os" "os"
"strings" "path"
"path/filepath"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
@ -45,79 +45,60 @@ and the appropriate structure for a Cobra-based CLI application.
Init will not use an existing directory with contents.`, Init will not use an existing directory with contents.`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
switch len(args) { var project *Project
case 0: if len(args) == 0 {
inputPath = "" wd, err := os.Getwd()
if err != nil {
case 1: er(err)
inputPath = args[0] }
project = NewProjectFromPath(wd)
default: } else if len(args) == 1 {
er("init doesn't support more than 1 parameter") project = NewProject(args[0])
} else {
er("please enter the name")
} }
guessProjectPath()
initializePath(projectPath) initializePath(project)
}, },
} }
func initializePath(path string) { func initializePath(project *Project) {
b, err := exists(path) if !exists(project.AbsPath()) { // If path doesn't yet exist, create it
err := os.MkdirAll(project.AbsPath(), os.ModePerm)
if err != nil {
er(err)
}
} else if !isEmpty(project.AbsPath()) { // If path exists and is not empty don't use it
er("Cobra will not create a new project in a non empty directory")
}
// We have a directory and it's empty.. Time to initialize it.
createLicenseFile(project)
createMainFile(project)
createRootCmdFile(project)
}
func createLicenseFile(project *Project) {
data := make(map[string]interface{})
data["copyright"] = copyrightLine()
// Generate license template from text and data.
text, err := executeTemplate(project.License().Text, data)
if err != nil { if err != nil {
er(err) er(err)
} }
if !b { // If path doesn't yet exist, create it // Write license text to LICENSE file.
err := os.MkdirAll(path, os.ModePerm) err = writeStringToFile(filepath.Join(project.AbsPath(), "LICENSE"), text)
if err != nil { if err != nil {
er(err) er(err)
}
} else { // If path exists and is not empty don't use it
empty, err := exists(path)
if err != nil {
er(err)
}
if !empty {
er("Cobra will not create a new project in a non empty directory")
}
}
// We have a directory and it's empty.. Time to initialize it.
createLicenseFile()
createMainFile()
createRootCmdFile()
}
func createLicenseFile() {
lic := getLicense()
// Don't bother writing a LICENSE file if there is no text.
if lic.Text != "" {
data := make(map[string]interface{})
// Try to remove the email address, if any
data["copyright"] = strings.Split(copyrightLine(), " <")[0]
data["appName"] = projectName()
// Generate license template from text and data.
r, _ := templateToReader(lic.Text, data)
buf := new(bytes.Buffer)
buf.ReadFrom(r)
err := writeTemplateToFile(ProjectPath(), "LICENSE", buf.String(), data)
_ = err
// if err != nil {
// er(err)
// }
} }
} }
func createMainFile() { func createMainFile(project *Project) {
lic := getLicense() mainTemplate := `{{ comment .copyright }}
{{if .license}}{{ comment .license }}{{end}}
template := `{{ comment .copyright }}
{{if .license}}{{ comment .license }}
{{end}}
package main package main
import "{{ .importpath }}" import "{{ .importpath }}"
@ -127,31 +108,25 @@ func main() {
} }
` `
data := make(map[string]interface{}) data := make(map[string]interface{})
data["copyright"] = copyrightLine() data["copyright"] = copyrightLine()
data["appName"] = projectName() data["license"] = project.License().Header
data["importpath"] = path.Join(project.Name(), project.CmdDir())
// Generate license template from header and data. mainScript, err := executeTemplate(mainTemplate, data)
r, _ := templateToReader(lic.Header, data) if err != nil {
buf := new(bytes.Buffer) er(err)
buf.ReadFrom(r) }
data["license"] = buf.String()
data["importpath"] = guessImportPath() + "/" + guessCmdDir() err = writeStringToFile(filepath.Join(project.AbsPath(), "main.go"), mainScript)
if err != nil {
err := writeTemplateToFile(ProjectPath(), "main.go", template, data) er(err)
_ = err }
// if err != nil {
// er(err)
// }
} }
func createRootCmdFile() { func createRootCmdFile(project *Project) {
lic := getLicense() template := `{{comment .copyright}}
{{if .license}}{{comment .license}}{{end}}
template := `{{ comment .copyright }}
{{if .license}}{{ comment .license }}
{{end}}
package cmd package cmd
import ( import (
@ -159,14 +134,14 @@ import (
"os" "os"
"github.com/spf13/cobra" "github.com/spf13/cobra"
{{ if .viper }} "github.com/spf13/viper" {{if .viper}} "github.com/spf13/viper"{{end}}
{{ end }}) )
{{if .viper}}
var cfgFile string {{if .viper}}var cfgFile string{{end}}
{{ end }}
// RootCmd represents the base command when called without any subcommands // RootCmd represents the base command when called without any subcommands
var RootCmd = &cobra.Command{ var RootCmd = &cobra.Command{
Use: "{{ .appName }}", Use: "{{.appName}}",
Short: "A brief description of your application", Short: "A brief description of your application",
Long: ` + "`" + `A longer description that spans multiple lines and likely contains Long: ` + "`" + `A longer description that spans multiple lines and likely contains
examples and usage of using your application. For example: examples and usage of using your application. For example:
@ -174,9 +149,9 @@ examples and usage of using your application. For example:
Cobra is a CLI library for Go that empowers applications. Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files This application is a tool to generate the needed files
to quickly create a Cobra application.` + "`" + `, to quickly create a Cobra application.` + "`" + `,
// Uncomment the following line if your bare application // Uncomment the following line if your bare application
// has an action associated with it: // has an action associated with it:
// Run: func(cmd *cobra.Command, args []string) { }, // Run: func(cmd *cobra.Command, args []string) { },
} }
// Execute adds all child commands to the root command sets flags appropriately. // Execute adds all child commands to the root command sets flags appropriately.
@ -189,57 +164,54 @@ func Execute() {
} }
func init() { func init() {
{{ if .viper }} cobra.OnInitialize(initConfig) {{if .viper}} cobra.OnInitialize(initConfig){{end}}
{{ end }} // Here you will define your flags and configuration settings. // Here you will define your flags and configuration settings.
// Cobra supports Persistent Flags, which, if defined here, // Cobra supports Persistent Flags, which, if defined here,
// will be global for your application. // will be global for your application.{{ if .viper }}
{{ 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)") // RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.{{ .appName }}.yaml)"){{ end }}
{{ else }}
// RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.{{ .appName }}.yaml)") // Cobra also supports local flags, which will only run
{{ end }} // Cobra also supports local flags, which will only run
// when this action is called directly. // when this action is called directly.
RootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") RootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
} }{{ if .viper }}
{{ if .viper }}
// initConfig reads in config file and ENV variables if set. // initConfig reads in config file and ENV variables if set.
func initConfig() { func initConfig() {
if cfgFile != "" { // enable ability to specify config file via flag if cfgFile != "" { // enable ability to specify config file via flag
viper.SetConfigFile(cfgFile) viper.SetConfigFile(cfgFile)
} else {
viper.SetConfigName(".{{ .appName }}") // name of config file (without extension)
viper.AddConfigPath(os.Getenv("HOME")) // adding home directory as first search path
} }
viper.SetConfigName(".{{ .appName }}") // name of config file (without extension)
viper.AddConfigPath(os.Getenv("HOME")) // adding home directory as first search path
viper.AutomaticEnv() // read in environment variables that match viper.AutomaticEnv() // read in environment variables that match
// If a config file is found, read it in. // If a config file is found, read it in.
if err := viper.ReadInConfig(); err == nil { if err := viper.ReadInConfig(); err == nil {
fmt.Println("Using config file:", viper.ConfigFileUsed()) fmt.Println("Using config file:", viper.ConfigFileUsed())
} }
} }{{ end }}
{{ end }}` `
data := make(map[string]interface{}) data := make(map[string]interface{})
data["copyright"] = copyrightLine() data["copyright"] = copyrightLine()
data["appName"] = projectName()
// Generate license template from header and data.
r, _ := templateToReader(lic.Header, data)
buf := new(bytes.Buffer)
buf.ReadFrom(r)
data["license"] = buf.String()
data["viper"] = viper.GetBool("useViper") data["viper"] = viper.GetBool("useViper")
data["license"] = project.License().Header
err := writeTemplateToFile(ProjectPath()+string(os.PathSeparator)+guessCmdDir(), "root.go", template, data) rootCmdScript, err := executeTemplate(template, data)
if err != nil {
er(err)
}
err = writeStringToFile(filepath.Join(project.AbsPath(), project.CmdDir(), "root.go"), rootCmdScript)
if err != nil { if err != nil {
er(err) er(err)
} }
fmt.Println("Your Cobra application is ready at") fmt.Println("Your Cobra application is ready at")
fmt.Println(ProjectPath()) fmt.Println(project.AbsPath() + "\n")
fmt.Println("Give it a try by going there and running `go run main.go`") fmt.Println("Give it a try by going there and running `go run main.go`.")
fmt.Println("Add commands to it by running `cobra add [cmdname]`") fmt.Println("Add commands to it by running `cobra add [cmdname]`")
} }

View File

@ -19,18 +19,17 @@ func initApache2() {
Licenses["apache"] = License{ Licenses["apache"] = License{
Name: "Apache 2.0", Name: "Apache 2.0",
PossibleMatches: []string{"apache", "apache20", "apache 2.0", "apache2.0", "apache-2.0"}, PossibleMatches: []string{"apache", "apache20", "apache 2.0", "apache2.0", "apache-2.0"},
Header: ` Header: `Licensed under the Apache License, Version 2.0 (the "License");
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
// you may not use this file except in compliance with the License. You may obtain a copy of the License at
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS,
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and
// See the License for the specific language governing permissions and limitations under the License.`,
// limitations under the License.`,
Text: ` Text: `
Apache License Apache License
Version 2.0, January 2004 Version 2.0, January 2004

View File

@ -16,6 +16,7 @@
package cmd package cmd
import ( import (
"fmt"
"strings" "strings"
"time" "time"
@ -42,9 +43,6 @@ func init() {
// Allows a user to not use a license. // Allows a user to not use a license.
Licenses["none"] = License{"None", []string{"none", "false"}, "", ""} Licenses["none"] = License{"None", []string{"none", "false"}, "", ""}
// Allows a user to use config for a custom license.
Licenses["custom"] = License{"Custom", []string{}, "", ""}
initApache2() initApache2()
initMit() initMit()
initBsdClause3() initBsdClause3()
@ -53,49 +51,29 @@ func init() {
initGpl3() initGpl3()
initLgpl() initLgpl()
initAgpl() initAgpl()
// Licenses["apache20"] = License{
// Name: "Apache 2.0",
// PossibleMatches: []string{"apache", "apache20", ""},
// Header: `
// `,
// Text: `
// `,
// }
} }
// TODO: Inspect project for existing license
func getLicense() License { func getLicense() License {
l := whichLicense() // If explicitly flagged, use that.
if l != "" {
if x, ok := Licenses[l]; ok {
return x
}
}
return Licenses["apache"]
}
func whichLicense() string {
// if explicitly flagged, use that
if userLicense != "" { if userLicense != "" {
return matchLicense(userLicense) return findLicense(userLicense)
} }
// if already present in the project, use that // If user wants to have custom license, use that.
// TODO: Inspect project for existing license
// default to viper's setting
if viper.IsSet("license.header") || viper.IsSet("license.text") { if viper.IsSet("license.header") || viper.IsSet("license.text") {
if custom, ok := Licenses["custom"]; ok { return License{Header: viper.GetString("license.header"),
custom.Header = viper.GetString("license.header") Text: "license.text"}
custom.Text = viper.GetString("license.text")
Licenses["custom"] = custom
return "custom"
}
} }
return matchLicense(viper.GetString("license")) // If user wants to have built-in license, use that.
if viper.IsSet("license") {
return findLicense(viper.GetString("license"))
}
// If user didn't set any license, use Apache 2.0 by default.
fmt.Println("apache")
return Licenses["apache"]
} }
func copyrightLine() string { func copyrightLine() string {
@ -105,14 +83,27 @@ func copyrightLine() string {
return "Copyright © " + year + " " + author return "Copyright © " + year + " " + author
} }
// given a license name (in), try to match the license indicated func findLicense(name string) License {
func matchLicense(in string) string { found := matchLicense(name)
if found == "" {
er(fmt.Errorf("unknown license %q", name))
}
return Licenses[found]
}
// given a license name, try to match the license indicated
func matchLicense(name string) string {
if name == "" {
return ""
}
for key, lic := range Licenses { for key, lic := range Licenses {
for _, match := range lic.PossibleMatches { for _, match := range lic.PossibleMatches {
if strings.EqualFold(in, match) { if strings.EqualFold(name, match) {
return key return key
} }
} }
} }
return "" return ""
} }

154
cobra/cmd/project.go Normal file
View File

@ -0,0 +1,154 @@
package cmd
import (
"os"
"path/filepath"
"strings"
)
type Project struct {
absPath string
cmdDir string
srcPath string
license License
name string
}
func NewProject(projectName string) *Project {
if projectName == "" {
return nil
}
p := new(Project)
p.name = projectName
// 1. Find already created protect.
p.absPath = findPackage(projectName)
// 2. If there are no created project with this path and user in GOPATH,
// then use GOPATH+projectName.
if p.absPath == "" {
wd, err := os.Getwd()
if err != nil {
er(err)
}
for _, goPath := range goPaths {
if filepath.HasPrefix(wd, goPath) {
p.absPath = filepath.Join(goPath, projectName)
break
}
}
}
// 3. If user is not in GOPATH, then use (first GOPATH)+projectName.
if p.absPath == "" {
p.absPath = filepath.Join(srcPaths[0], projectName)
}
return p
}
// findPackage returns full path to go package. It supports multiple GOPATHs.
// findPackage returns "", if it can't find path.
// If packageName is "", findPackage returns "" too.
//
// For example, package "github.com/spf13/hugo"
// is located in /home/user/go/src/github.com/spf13/hugo,
// then `findPackage("github.com/spf13/hugo")`
// will return "/home/user/go/src/github.com/spf13/hugo"
func findPackage(packageName string) string {
if packageName == "" {
return ""
}
for _, srcPath := range srcPaths {
packagePath := filepath.Join(srcPath, packageName)
if exists(packagePath) {
return packagePath
}
}
return ""
}
func NewProjectFromPath(absPath string) *Project {
if absPath == "" || !filepath.IsAbs(absPath) {
return nil
}
p := new(Project)
p.absPath = absPath
p.absPath = strings.TrimSuffix(p.absPath, p.CmdDir())
p.name = filepath.ToSlash(trimSrcPath(p.absPath, p.SrcPath()))
return p
}
func trimSrcPath(absPath, srcPath string) string {
relPath, err := filepath.Rel(srcPath, absPath)
if err != nil {
er("Cobra only supports project within $GOPATH")
}
return relPath
}
func (p *Project) License() License {
if p.license.Text == "" { // check if license is not blank
p.license = getLicense()
}
return p.license
}
func (p Project) Name() string {
return p.name
}
func (p *Project) CmdDir() string {
if p.absPath == "" {
return ""
}
if p.cmdDir == "" {
p.cmdDir = findCmdDir(p.absPath)
}
return p.cmdDir
}
func findCmdDir(absPath string) string {
if !exists(absPath) || isEmpty(absPath) {
return "cmd"
}
files, _ := filepath.Glob(filepath.Join(absPath, "c*"))
for _, f := range files {
for _, c := range cmdDirs {
if f == c {
return c
}
}
}
return "cmd"
}
func (p Project) AbsPath() string {
return p.absPath
}
func (p *Project) SrcPath() string {
if p.srcPath != "" {
return p.srcPath
}
if p.absPath == "" {
p.srcPath = srcPaths[0]
return p.srcPath
}
for _, srcPath := range srcPaths {
if strings.HasPrefix(p.absPath, srcPath) {
p.srcPath = srcPath
break
}
}
return p.srcPath
}

View File

@ -21,8 +21,7 @@ import (
"github.com/spf13/viper" "github.com/spf13/viper"
) )
var cfgFile string var cfgFile, projectBase, userLicense string // are used for flags
var userLicense string
// RootCmd represents the base command when called without any subcommands // RootCmd represents the base command when called without any subcommands
var RootCmd = &cobra.Command{ var RootCmd = &cobra.Command{
@ -42,7 +41,7 @@ func Execute() {
} }
func init() { func init() {
cobra.OnInitialize(initConfig) initViper()
RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.cobra.yaml)") RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.cobra.yaml)")
RootCmd.PersistentFlags().StringVarP(&projectBase, "projectbase", "b", "", "base project directory, e.g. github.com/spf13/") RootCmd.PersistentFlags().StringVarP(&projectBase, "projectbase", "b", "", "base project directory, e.g. github.com/spf13/")
RootCmd.PersistentFlags().StringP("author", "a", "YOUR NAME", "Author name for copyright attribution") RootCmd.PersistentFlags().StringP("author", "a", "YOUR NAME", "Author name for copyright attribution")
@ -55,17 +54,16 @@ func init() {
viper.SetDefault("license", "apache") viper.SetDefault("license", "apache")
} }
// Read in config file and ENV variables if set. func initViper() {
func initConfig() {
if cfgFile != "" { // enable ability to specify config file via flag if cfgFile != "" { // enable ability to specify config file via flag
viper.SetConfigFile(cfgFile) viper.SetConfigFile(cfgFile)
} else {
viper.AddConfigPath(os.Getenv("HOME"))
viper.SetConfigName(".cobra")
} }
viper.SetConfigName(".cobra") // name of config file (without extension) viper.AutomaticEnv()
viper.AddConfigPath(os.Getenv("HOME")) // adding home directory as first search path
viper.AutomaticEnv() // read in environment variables that match
// If a config file is found, read it in.
if err := viper.ReadInConfig(); err == nil { if err := viper.ReadInConfig(); err == nil {
fmt.Println("Using config file:", viper.ConfigFileUsed()) fmt.Println("Using config file:", viper.ConfigFileUsed())
} }