...
1 package code
2
3 import (
4 "bufio"
5 "fmt"
6 "go/build"
7 "go/parser"
8 "go/token"
9 "os"
10 "path/filepath"
11 "regexp"
12 "strings"
13 )
14
15 var gopaths []string
16
17 func init() {
18 gopaths = filepath.SplitList(build.Default.GOPATH)
19 for i, p := range gopaths {
20 gopaths[i] = filepath.ToSlash(filepath.Join(p, "src"))
21 }
22 }
23
24
25
26 func NameForDir(dir string) string {
27 dir, err := filepath.Abs(dir)
28 if err != nil {
29 return SanitizePackageName(filepath.Base(dir))
30 }
31 files, err := os.ReadDir(dir)
32 if err != nil {
33 return SanitizePackageName(filepath.Base(dir))
34 }
35 fset := token.NewFileSet()
36 for _, file := range files {
37 if !strings.HasSuffix(strings.ToLower(file.Name()), ".go") {
38 continue
39 }
40
41 filename := filepath.Join(dir, file.Name())
42 if src, err := parser.ParseFile(fset, filename, nil, parser.PackageClauseOnly); err == nil {
43 return src.Name.Name
44 }
45 }
46
47 return SanitizePackageName(filepath.Base(dir))
48 }
49
50 type goModuleSearchResult struct {
51 path string
52 goModPath string
53 moduleName string
54 }
55
56 var goModuleRootCache = map[string]goModuleSearchResult{}
57
58
59
60 func goModuleRoot(dir string) (string, bool) {
61 dir, err := filepath.Abs(dir)
62 if err != nil {
63 panic(err)
64 }
65 dir = filepath.ToSlash(dir)
66
67 dirs := []string{dir}
68 result := goModuleSearchResult{}
69
70 for {
71 modDir := dirs[len(dirs)-1]
72
73 if val, ok := goModuleRootCache[dir]; ok {
74 result = val
75 break
76 }
77
78 if content, err := os.ReadFile(filepath.Join(modDir, "go.mod")); err == nil {
79 moduleName := extractModuleName(content)
80 result = goModuleSearchResult{
81 path: moduleName,
82 goModPath: modDir,
83 moduleName: moduleName,
84 }
85 goModuleRootCache[modDir] = result
86 break
87 }
88
89 if modDir == "" || modDir == "." || modDir == "/" || strings.HasSuffix(modDir, "\\") {
90
91
92 goModuleRootCache[modDir] = result
93 break
94 }
95
96 dirs = append(dirs, filepath.Dir(modDir))
97 }
98
99
100 for _, d := range dirs[:len(dirs)-1] {
101 if result.moduleName == "" {
102
103 goModuleRootCache[d] = result
104 } else {
105 if relPath, err := filepath.Rel(result.goModPath, d); err != nil {
106 panic(err)
107 } else {
108 path := result.moduleName
109 relPath := filepath.ToSlash(relPath)
110 if !strings.HasSuffix(relPath, "/") {
111 path += "/"
112 }
113 path += relPath
114
115 goModuleRootCache[d] = goModuleSearchResult{
116 path: path,
117 goModPath: result.goModPath,
118 moduleName: result.moduleName,
119 }
120 }
121 }
122 }
123
124 res := goModuleRootCache[dir]
125 if res.moduleName == "" {
126 return "", false
127 }
128 return res.path, true
129 }
130
131 func extractModuleName(content []byte) string {
132 for {
133 advance, tkn, err := bufio.ScanLines(content, false)
134 if err != nil {
135 panic(fmt.Errorf("error parsing mod file: %w", err))
136 }
137 if advance == 0 {
138 break
139 }
140 s := strings.Trim(string(tkn), " \t")
141 if s != "" && !strings.HasPrefix(s, "//") {
142 break
143 }
144 if advance <= len(content) {
145 content = content[advance:]
146 }
147 }
148 moduleName := string(modregex.FindSubmatch(content)[1])
149 return moduleName
150 }
151
152
153 func ImportPathForDir(dir string) (res string) {
154 dir, err := filepath.Abs(dir)
155 if err != nil {
156 panic(err)
157 }
158 dir = filepath.ToSlash(dir)
159
160 modDir, ok := goModuleRoot(dir)
161 if ok {
162 return modDir
163 }
164
165 for _, gopath := range gopaths {
166 if len(gopath) < len(dir) && strings.EqualFold(gopath, dir[0:len(gopath)]) {
167 return dir[len(gopath)+1:]
168 }
169 }
170
171 return ""
172 }
173
174 var modregex = regexp.MustCompile(`module (\S*)`)
175
View as plain text