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