diff --git a/README.md b/README.md index b39d8cb..77b6e28 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/cobra/cmd/add.go b/cobra/cmd/add.go index b89d4c4..09e4d9b 100644 --- a/cobra/cmd/add.go +++ b/cobra/cmd/add.go @@ -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) } diff --git a/cobra/cmd/helpers.go b/cobra/cmd/helpers.go index 6989bd7..3f78f89 100644 --- a/cobra/cmd/helpers.go +++ b/cobra/cmd/helpers.go @@ -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. - defer f.Close() if err != nil { - return false, err + er(err) } - list, _ := f.Readdir(-1) - // f.Close() - see bug fix above - return len(list) == 0, nil + defer f.Close() + dirs, err := f.Readdirnames(1) + 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. -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) - } else { - newlines = append(newlines, "//") - } + for _, line := range lines { + if strings.HasPrefix(line, "//") { + newlines = append(newlines, line) } else { - newlines = append(newlines, x) + if line == "" { + newlines = append(newlines, "//") + } else { + newlines = append(newlines, "// "+line) + } } } return strings.Join(newlines, "\n") diff --git a/cobra/cmd/helpers_test.go b/cobra/cmd/helpers_test.go deleted file mode 100644 index bce9471..0000000 --- a/cobra/cmd/helpers_test.go +++ /dev/null @@ -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")) -} diff --git a/cobra/cmd/init.go b/cobra/cmd/init.go index b77c986..3ff5165 100644 --- a/cobra/cmd/init.go +++ b/cobra/cmd/init.go @@ -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) + } + project = NewProjectFromPath(wd) + } else if len(args) == 1 { + project = NewProject(args[0]) + } else { + er("please enter the name") } - guessProjectPath() - initializePath(projectPath) + + 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) + } + } 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 { 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 { - 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) - // } + // 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.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]`") } diff --git a/cobra/cmd/license_apache_2.go b/cobra/cmd/license_apache_2.go index 878e1d9..3f33086 100644 --- a/cobra/cmd/license_apache_2.go +++ b/cobra/cmd/license_apache_2.go @@ -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 diff --git a/cobra/cmd/licenses.go b/cobra/cmd/licenses.go index 13ffbf7..50659ac 100644 --- a/cobra/cmd/licenses.go +++ b/cobra/cmd/licenses.go @@ -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 "" } diff --git a/cobra/cmd/project.go b/cobra/cmd/project.go new file mode 100644 index 0000000..eac4d8a --- /dev/null +++ b/cobra/cmd/project.go @@ -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 +} diff --git a/cobra/cmd/root.go b/cobra/cmd/root.go index 32386ac..89f5ce7 100644 --- a/cobra/cmd/root.go +++ b/cobra/cmd/root.go @@ -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()) }