1 package parser
2
3 import (
4 "bytes"
5 "fmt"
6 "go/build"
7 "io/ioutil"
8 "os"
9 "os/exec"
10 "path"
11 "path/filepath"
12 "strings"
13 "sync"
14 )
15
16 func getPkgPath(fname string, isDir bool) (string, error) {
17 if !filepath.IsAbs(fname) {
18 pwd, err := os.Getwd()
19 if err != nil {
20 return "", err
21 }
22 fname = filepath.Join(pwd, fname)
23 }
24
25 goModPath, _ := goModPath(fname, isDir)
26 if strings.Contains(goModPath, "go.mod") {
27 pkgPath, err := getPkgPathFromGoMod(fname, isDir, goModPath)
28 if err != nil {
29 return "", err
30 }
31
32 return pkgPath, nil
33 }
34
35 return getPkgPathFromGOPATH(fname, isDir)
36 }
37
38 var goModPathCache = struct {
39 paths map[string]string
40 sync.RWMutex
41 }{
42 paths: make(map[string]string),
43 }
44
45
46 func goModPath(fname string, isDir bool) (string, error) {
47 root := fname
48 if !isDir {
49 root = filepath.Dir(fname)
50 }
51
52 goModPathCache.RLock()
53 goModPath, ok := goModPathCache.paths[root]
54 goModPathCache.RUnlock()
55 if ok {
56 return goModPath, nil
57 }
58
59 defer func() {
60 goModPathCache.Lock()
61 goModPathCache.paths[root] = goModPath
62 goModPathCache.Unlock()
63 }()
64
65 cmd := exec.Command("go", "env", "GOMOD")
66 cmd.Dir = root
67
68 stdout, err := cmd.Output()
69 if err != nil {
70 return "", err
71 }
72
73 goModPath = string(bytes.TrimSpace(stdout))
74
75 return goModPath, nil
76 }
77
78 func getPkgPathFromGoMod(fname string, isDir bool, goModPath string) (string, error) {
79 modulePath := getModulePath(goModPath)
80 if modulePath == "" {
81 return "", fmt.Errorf("cannot determine module path from %s", goModPath)
82 }
83
84 rel := path.Join(modulePath, filePathToPackagePath(strings.TrimPrefix(fname, filepath.Dir(goModPath))))
85
86 if !isDir {
87 return path.Dir(rel), nil
88 }
89
90 return path.Clean(rel), nil
91 }
92
93 var pkgPathFromGoModCache = struct {
94 paths map[string]string
95 sync.RWMutex
96 }{
97 paths: make(map[string]string),
98 }
99
100 func getModulePath(goModPath string) string {
101 pkgPathFromGoModCache.RLock()
102 pkgPath, ok := pkgPathFromGoModCache.paths[goModPath]
103 pkgPathFromGoModCache.RUnlock()
104 if ok {
105 return pkgPath
106 }
107
108 defer func() {
109 pkgPathFromGoModCache.Lock()
110 pkgPathFromGoModCache.paths[goModPath] = pkgPath
111 pkgPathFromGoModCache.Unlock()
112 }()
113
114 data, err := ioutil.ReadFile(goModPath)
115 if err != nil {
116 return ""
117 }
118 pkgPath = modulePath(data)
119 return pkgPath
120 }
121
122 func getPkgPathFromGOPATH(fname string, isDir bool) (string, error) {
123 gopath := os.Getenv("GOPATH")
124 if gopath == "" {
125 gopath = build.Default.GOPATH
126 }
127
128 for _, p := range strings.Split(gopath, string(filepath.ListSeparator)) {
129 prefix := filepath.Join(p, "src") + string(filepath.Separator)
130 rel, err := filepath.Rel(prefix, fname)
131 if err == nil && !strings.HasPrefix(rel, ".."+string(filepath.Separator)) {
132 if !isDir {
133 return path.Dir(filePathToPackagePath(rel)), nil
134 } else {
135 return path.Clean(filePathToPackagePath(rel)), nil
136 }
137 }
138 }
139
140 return "", fmt.Errorf("file '%v' is not in GOPATH '%v'", fname, gopath)
141 }
142
143 func filePathToPackagePath(path string) string {
144 return filepath.ToSlash(path)
145 }
146
View as plain text