1package cmd
2
3import (
4	"os"
5	"path/filepath"
6	"runtime"
7	"strings"
8)
9
10// Project contains name, license and paths to projects.
11type Project struct {
12	absPath string
13	cmdPath string
14	srcPath string
15	license License
16	name    string
17}
18
19// NewProject returns Project with specified project name.
20// If projectName is blank string, it returns nil.
21func NewProject(projectName string) *Project {
22	if projectName == "" {
23		return nil
24	}
25
26	p := new(Project)
27	p.name = projectName
28
29	// 1. Find already created protect.
30	p.absPath = findPackage(projectName)
31
32	// 2. If there are no created project with this path, and user is in GOPATH,
33	// then use GOPATH/src/projectName.
34	if p.absPath == "" {
35		wd, err := os.Getwd()
36		if err != nil {
37			er(err)
38		}
39		for _, srcPath := range srcPaths {
40			goPath := filepath.Dir(srcPath)
41			if filepathHasPrefix(wd, goPath) {
42				p.absPath = filepath.Join(srcPath, projectName)
43				break
44			}
45		}
46	}
47
48	// 3. If user is not in GOPATH, then use (first GOPATH)/src/projectName.
49	if p.absPath == "" {
50		p.absPath = filepath.Join(srcPaths[0], projectName)
51	}
52
53	return p
54}
55
56// findPackage returns full path to existing go package in GOPATHs.
57// findPackage returns "", if it can't find path.
58// If packageName is "", findPackage returns "".
59func findPackage(packageName string) string {
60	if packageName == "" {
61		return ""
62	}
63
64	for _, srcPath := range srcPaths {
65		packagePath := filepath.Join(srcPath, packageName)
66		if exists(packagePath) {
67			return packagePath
68		}
69	}
70
71	return ""
72}
73
74// NewProjectFromPath returns Project with specified absolute path to
75// package.
76// If absPath is blank string or if absPath is not actually absolute,
77// it returns nil.
78func NewProjectFromPath(absPath string) *Project {
79	if absPath == "" || !filepath.IsAbs(absPath) {
80		return nil
81	}
82
83	p := new(Project)
84	p.absPath = absPath
85	p.absPath = strings.TrimSuffix(p.absPath, findCmdDir(p.absPath))
86	p.name = filepath.ToSlash(trimSrcPath(p.absPath, p.SrcPath()))
87	return p
88}
89
90// trimSrcPath trims at the beginning of absPath the srcPath.
91func trimSrcPath(absPath, srcPath string) string {
92	relPath, err := filepath.Rel(srcPath, absPath)
93	if err != nil {
94		er("Cobra supports project only within $GOPATH: " + err.Error())
95	}
96	return relPath
97}
98
99// License returns the License object of project.
100func (p *Project) License() License {
101	if p.license.Text == "" && p.license.Name != "None" {
102		p.license = getLicense()
103	}
104
105	return p.license
106}
107
108// Name returns the name of project, e.g. "github.com/spf13/cobra"
109func (p Project) Name() string {
110	return p.name
111}
112
113// CmdPath returns absolute path to directory, where all commands are located.
114//
115// CmdPath returns blank string, only if p.AbsPath() is a blank string.
116func (p *Project) CmdPath() string {
117	if p.absPath == "" {
118		return ""
119	}
120	if p.cmdPath == "" {
121		p.cmdPath = filepath.Join(p.absPath, findCmdDir(p.absPath))
122	}
123	return p.cmdPath
124}
125
126// findCmdDir checks if base of absPath is cmd dir and returns it or
127// looks for existing cmd dir in absPath.
128// If the cmd dir doesn't exist, empty, or cannot be found,
129// it returns "cmd".
130func findCmdDir(absPath string) string {
131	if !exists(absPath) || isEmpty(absPath) {
132		return "cmd"
133	}
134
135	if isCmdDir(absPath) {
136		return filepath.Base(absPath)
137	}
138
139	files, _ := filepath.Glob(filepath.Join(absPath, "c*"))
140	for _, file := range files {
141		if isCmdDir(file) {
142			return filepath.Base(file)
143		}
144	}
145
146	return "cmd"
147}
148
149// isCmdDir checks if base of name is one of cmdDir.
150func isCmdDir(name string) bool {
151	name = filepath.Base(name)
152	for _, cmdDir := range cmdDirs {
153		if name == cmdDir {
154			return true
155		}
156	}
157	return false
158}
159
160// AbsPath returns absolute path of project.
161func (p Project) AbsPath() string {
162	return p.absPath
163}
164
165// SrcPath returns absolute path to $GOPATH/src where project is located.
166func (p *Project) SrcPath() string {
167	if p.srcPath != "" {
168		return p.srcPath
169	}
170	if p.absPath == "" {
171		p.srcPath = srcPaths[0]
172		return p.srcPath
173	}
174
175	for _, srcPath := range srcPaths {
176		if filepathHasPrefix(p.absPath, srcPath) {
177			p.srcPath = srcPath
178			break
179		}
180	}
181
182	return p.srcPath
183}
184
185func filepathHasPrefix(path string, prefix string) bool {
186	if len(path) <= len(prefix) {
187		return false
188	}
189	if runtime.GOOS == "windows" {
190		// Paths in windows are case-insensitive.
191		return strings.EqualFold(path[0:len(prefix)], prefix)
192	}
193	return path[0:len(prefix)] == prefix
194
195}
196