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:
> hugo server --port=1313
hugo server --port=1313
In this command we are telling Git to clone the url bare.
> git clone URL --bare
git clone URL --bare
## 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
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:
@ -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:
> 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.

View file

@ -15,8 +15,8 @@ package cmd
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/spf13/cobra"
"github.com/spf13/viper"
@ -26,9 +26,8 @@ func init() {
RootCmd.AddCommand(addCmd)
}
var pName string
var parentName string
// initialize Command
var addCmd = &cobra.Command{
Use: "add [command name]",
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
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) {
if len(args) != 1 {
if len(args) < 1 {
er("add needs a name for the command")
}
guessProjectPath()
createCmdFile(args[0])
wd, err := os.Getwd()
if err != nil {
er(err)
}
project := NewProjectFromPath(wd)
createCmdFile(project, args[0])
},
}
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 {
if !strings.HasSuffix(strings.ToLower(pName), "cmd") {
return pName + "Cmd"
}
return pName
}
func createCmdFile(cmdName string) {
lic := getLicense()
template := `{{ comment .copyright }}
{{ comment .license }}
func createCmdFile(project *Project, cmdName string) {
template := `{{comment .copyright}}
{{comment .license}}
package cmd
@ -79,8 +71,8 @@ import (
)
// {{.cmdName}}Cmd represents the {{.cmdName}} command
var {{ .cmdName }}Cmd = &cobra.Command{
Use: "{{ .cmdName }}",
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:
@ -89,13 +81,12 @@ 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) {
// TODO: Work your own magic here
fmt.Println("{{ .cmdName }} called")
fmt.Println("{{.cmdName}} called")
},
}
func init() {
{{ .parentName }}.AddCommand({{ .cmdName }}Cmd)
{{.parentName}}.AddCommand({{.cmdName}}Cmd)
// 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
// is called directly, e.g.:
// {{.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["license"] = lic.Header
data["appName"] = projectName()
data["license"] = project.License().Header
data["viper"] = viper.GetBool("useViper")
data["parentName"] = parentName()
data["parentName"] = parentName
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 {
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"
)
// var BaseDir = ""
// var AppName = ""
// var CommandDir = ""
var funcMap = template.FuncMap{
"comment": commentifyString,
}
var funcMap template.FuncMap
var projectPath = ""
var inputPath = ""
var projectBase = ""
var projectPath string
// for testing only
var testWd = ""
var cmdDirs = []string{"cmd", "cmds", "command", "commands"}
var cmdDirs = [...]string{"cmd", "cmds", "command", "commands"}
var goPaths, srcPaths []string
func init() {
funcMap = template.FuncMap{
"comment": commentifyString,
// Initialize goPaths and srcPaths
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{}) {
fmt.Println("Error:", msg)
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
}
}
os.Exit(1)
}
// isEmpty checks if a given path is empty.
func isEmpty(path string) (bool, error) {
if b, _ := exists(path); !b {
return false, fmt.Errorf("%q path does not exist", path)
}
func isEmpty(path string) bool {
fi, err := os.Stat(path)
if err != nil {
return false, err
er(err)
}
if fi.IsDir() {
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.
if err != nil {
er(err)
}
defer f.Close()
if err != nil {
return false, err
dirs, err := f.Readdirnames(1)
if err != nil && err != io.EOF {
er(err)
}
list, _ := f.Readdir(-1)
// f.Close() - see bug fix above
return len(list) == 0, nil
return len(dirs) == 0
}
return fi.Size() == 0, nil
return fi.Size() == 0
}
// isDir checks if a given path is a directory.
func isDir(path string) (bool, error) {
fi, err := os.Stat(path)
if err != nil {
return false, err
// exists checks if a file or directory exists.
func exists(path string) bool {
if path == "" {
return false
}
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 dirExists(path string) (bool, error) {
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)
func executeTemplate(tmplStr string, data interface{}) (string, error) {
tmpl, err := template.New("").Funcs(funcMap).Parse(tmplStr)
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)
err = tmpl.Execute(buf, data)
return buf, err
return buf.String(), 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) {
dir, _ := filepath.Split(inpath)
dir := filepath.Dir(inpath)
ospath := filepath.FromSlash(dir)
if ospath != "" {
err = os.MkdirAll(ospath, 0777) // rwx, rw, r
err = os.MkdirAll(ospath, 0777)
if err != nil {
return
}
}
ex, err := exists(inpath)
if err != nil {
return
}
if ex {
if 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)
if err != nil {
@ -297,15 +134,15 @@ func safeWriteToDisk(inpath string, r io.Reader) (err error) {
func commentifyString(in string) string {
var newlines []string
lines := strings.Split(in, "\n")
for _, x := range lines {
if !strings.HasPrefix(x, "//") {
if x != "" {
newlines = append(newlines, "// "+x)
for _, line := range lines {
if strings.HasPrefix(line, "//") {
newlines = append(newlines, line)
} else {
if line == "" {
newlines = append(newlines, "//")
}
} else {
newlines = append(newlines, x)
newlines = append(newlines, "// "+line)
}
}
}
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
import (
"bytes"
"fmt"
"os"
"strings"
"path"
"path/filepath"
"github.com/spf13/cobra"
"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.`,
Run: func(cmd *cobra.Command, args []string) {
switch len(args) {
case 0:
inputPath = ""
case 1:
inputPath = args[0]
default:
er("init doesn't support more than 1 parameter")
var project *Project
if len(args) == 0 {
wd, err := os.Getwd()
if err != nil {
er(err)
}
guessProjectPath()
initializePath(projectPath)
project = NewProjectFromPath(wd)
} else if len(args) == 1 {
project = NewProject(args[0])
} else {
er("please enter the name")
}
initializePath(project)
},
}
func initializePath(path string) {
b, err := exists(path)
func initializePath(project *Project) {
if !exists(project.AbsPath()) { // If path doesn't yet exist, create it
err := os.MkdirAll(project.AbsPath(), os.ModePerm)
if err != nil {
er(err)
}
if !b { // If path doesn't yet exist, create it
err := os.MkdirAll(path, os.ModePerm)
if err != nil {
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 {
} 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()
createMainFile()
createRootCmdFile()
// We have a directory and it's empty.. Time to initialize it.
createLicenseFile(project)
createMainFile(project)
createRootCmdFile(project)
}
func createLicenseFile() {
lic := getLicense()
// Don't bother writing a LICENSE file if there is no text.
if lic.Text != "" {
func createLicenseFile(project *Project) {
data := make(map[string]interface{})
// Try to remove the email address, if any
data["copyright"] = strings.Split(copyrightLine(), " <")[0]
data["appName"] = projectName()
data["copyright"] = copyrightLine()
// Generate license template from text and data.
r, _ := templateToReader(lic.Text, data)
buf := new(bytes.Buffer)
buf.ReadFrom(r)
text, err := executeTemplate(project.License().Text, data)
if err != nil {
er(err)
}
err := writeTemplateToFile(ProjectPath(), "LICENSE", buf.String(), data)
_ = err
// if err != nil {
// er(err)
// }
// Write license text to LICENSE file.
err = writeStringToFile(filepath.Join(project.AbsPath(), "LICENSE"), text)
if err != nil {
er(err)
}
}
func createMainFile() {
lic := getLicense()
func createMainFile(project *Project) {
mainTemplate := `{{ comment .copyright }}
{{if .license}}{{ comment .license }}{{end}}
template := `{{ comment .copyright }}
{{if .license}}{{ comment .license }}
{{end}}
package main
import "{{ .importpath }}"
@ -127,31 +108,25 @@ func main() {
}
`
data := make(map[string]interface{})
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.
r, _ := templateToReader(lic.Header, data)
buf := new(bytes.Buffer)
buf.ReadFrom(r)
data["license"] = buf.String()
mainScript, err := executeTemplate(mainTemplate, data)
if err != nil {
er(err)
}
data["importpath"] = guessImportPath() + "/" + guessCmdDir()
err := writeTemplateToFile(ProjectPath(), "main.go", template, data)
_ = err
// if err != nil {
// er(err)
// }
err = writeStringToFile(filepath.Join(project.AbsPath(), "main.go"), mainScript)
if err != nil {
er(err)
}
}
func createRootCmdFile() {
lic := getLicense()
func createRootCmdFile(project *Project) {
template := `{{comment .copyright}}
{{if .license}}{{comment .license}}{{end}}
template := `{{ comment .copyright }}
{{if .license}}{{ comment .license }}
{{end}}
package cmd
import (
@ -159,14 +134,14 @@ import (
"os"
"github.com/spf13/cobra"
{{ if .viper }} "github.com/spf13/viper"
{{ end }})
{{if .viper}}
var cfgFile string
{{ end }}
{{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 }}",
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:
@ -174,9 +149,9 @@ 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) { },
// 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 sets flags appropriately.
@ -189,57 +164,54 @@ func Execute() {
}
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,
// 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
// 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 }}
}{{ if .viper }}
// initConfig reads in config file and ENV variables if set.
func initConfig() {
if cfgFile != "" { // enable ability to specify config file via flag
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.AutomaticEnv() // read in environment variables that match
// If a config file is found, read it in.
if err := viper.ReadInConfig(); err == nil {
fmt.Println("Using config file:", viper.ConfigFileUsed())
}
}
{{ end }}`
}{{ end }}
`
data := make(map[string]interface{})
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["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 {
er(err)
}
fmt.Println("Your Cobra application is ready at")
fmt.Println(ProjectPath())
fmt.Println("Give it a try by going there and running `go run main.go`")
fmt.Println(project.AbsPath() + "\n")
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]`")
}

View file

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

View file

@ -16,6 +16,7 @@
package cmd
import (
"fmt"
"strings"
"time"
@ -42,9 +43,6 @@ func init() {
// Allows a user to not use a license.
Licenses["none"] = License{"None", []string{"none", "false"}, "", ""}
// Allows a user to use config for a custom license.
Licenses["custom"] = License{"Custom", []string{}, "", ""}
initApache2()
initMit()
initBsdClause3()
@ -53,49 +51,29 @@ func init() {
initGpl3()
initLgpl()
initAgpl()
// Licenses["apache20"] = License{
// Name: "Apache 2.0",
// PossibleMatches: []string{"apache", "apache20", ""},
// Header: `
// `,
// Text: `
// `,
// }
}
// TODO: Inspect project for existing license
func getLicense() License {
l := whichLicense()
if l != "" {
if x, ok := Licenses[l]; ok {
return x
}
}
return Licenses["apache"]
}
func whichLicense() string {
// if explicitly flagged, use that
// If explicitly flagged, use that.
if userLicense != "" {
return matchLicense(userLicense)
return findLicense(userLicense)
}
// if already present in the project, use that
// TODO: Inspect project for existing license
// default to viper's setting
// If user wants to have custom license, use that.
if viper.IsSet("license.header") || viper.IsSet("license.text") {
if custom, ok := Licenses["custom"]; ok {
custom.Header = viper.GetString("license.header")
custom.Text = viper.GetString("license.text")
Licenses["custom"] = custom
return "custom"
}
return License{Header: viper.GetString("license.header"),
Text: "license.text"}
}
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 {
@ -105,14 +83,27 @@ func copyrightLine() string {
return "Copyright © " + year + " " + author
}
// given a license name (in), try to match the license indicated
func matchLicense(in string) string {
func findLicense(name string) License {
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 _, match := range lic.PossibleMatches {
if strings.EqualFold(in, match) {
if strings.EqualFold(name, match) {
return key
}
}
}
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"
)
var cfgFile string
var userLicense string
var cfgFile, projectBase, userLicense string // are used for flags
// RootCmd represents the base command when called without any subcommands
var RootCmd = &cobra.Command{
@ -42,7 +41,7 @@ func Execute() {
}
func init() {
cobra.OnInitialize(initConfig)
initViper()
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().StringP("author", "a", "YOUR NAME", "Author name for copyright attribution")
@ -55,17 +54,16 @@ func init() {
viper.SetDefault("license", "apache")
}
// Read in config file and ENV variables if set.
func initConfig() {
func initViper() {
if cfgFile != "" { // enable ability to specify config file via flag
viper.SetConfigFile(cfgFile)
} else {
viper.AddConfigPath(os.Getenv("HOME"))
viper.SetConfigName(".cobra")
}
viper.SetConfigName(".cobra") // 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()
// If a config file is found, read it in.
if err := viper.ReadInConfig(); err == nil {
fmt.Println("Using config file:", viper.ConfigFileUsed())
}