1package code
2
3import (
4 "errors"
5 "go/build"
6 "go/parser"
7 "go/token"
8 "io/ioutil"
9 "path/filepath"
10 "regexp"
11 "strings"
12 "sync"
13
14 "golang.org/x/tools/go/packages"
15)
16
17var nameForPackageCache = sync.Map{}
18
19var gopaths []string
20
21func init() {
22 gopaths = filepath.SplitList(build.Default.GOPATH)
23 for i, p := range gopaths {
24 gopaths[i] = filepath.ToSlash(filepath.Join(p, "src"))
25 }
26}
27
28// NameForDir manually looks for package stanzas in files located in the given directory. This can be
29// much faster than having to consult go list, because we already know exactly where to look.
30func NameForDir(dir string) string {
31 dir, err := filepath.Abs(dir)
32 if err != nil {
33 return SanitizePackageName(filepath.Base(dir))
34 }
35 files, err := ioutil.ReadDir(dir)
36 if err != nil {
37 return SanitizePackageName(filepath.Base(dir))
38 }
39 fset := token.NewFileSet()
40 for _, file := range files {
41 if !strings.HasSuffix(strings.ToLower(file.Name()), ".go") {
42 continue
43 }
44
45 filename := filepath.Join(dir, file.Name())
46 if src, err := parser.ParseFile(fset, filename, nil, parser.PackageClauseOnly); err == nil {
47 return src.Name.Name
48 }
49 }
50
51 return SanitizePackageName(filepath.Base(dir))
52}
53
54// ImportPathForDir takes a path and returns a golang import path for the package
55func ImportPathForDir(dir string) (res string) {
56 dir, err := filepath.Abs(dir)
57 if err != nil {
58 panic(err)
59 }
60 dir = filepath.ToSlash(dir)
61
62 modDir := dir
63 assumedPart := ""
64 for {
65 f, err := ioutil.ReadFile(filepath.Join(modDir, "go.mod"))
66 if err == nil {
67 // found it, stop searching
68 return string(modregex.FindSubmatch(f)[1]) + assumedPart
69 }
70
71 assumedPart = "/" + filepath.Base(modDir) + assumedPart
72 parentDir, err := filepath.Abs(filepath.Join(modDir, ".."))
73 if err != nil {
74 panic(err)
75 }
76
77 if parentDir == modDir {
78 // Walked all the way to the root and didnt find anything :'(
79 break
80 }
81 modDir = parentDir
82 }
83
84 for _, gopath := range gopaths {
85 if len(gopath) < len(dir) && strings.EqualFold(gopath, dir[0:len(gopath)]) {
86 return dir[len(gopath)+1:]
87 }
88 }
89
90 return ""
91}
92
93var modregex = regexp.MustCompile("module (.*)\n")
94
95// NameForPackage returns the package name for a given import path. This can be really slow.
96func NameForPackage(importPath string) string {
97 if importPath == "" {
98 panic(errors.New("import path can not be empty"))
99 }
100 if v, ok := nameForPackageCache.Load(importPath); ok {
101 return v.(string)
102 }
103 importPath = QualifyPackagePath(importPath)
104 p, _ := packages.Load(&packages.Config{
105 Mode: packages.NeedName,
106 }, importPath)
107
108 if len(p) != 1 || p[0].Name == "" {
109 return SanitizePackageName(filepath.Base(importPath))
110 }
111
112 nameForPackageCache.Store(importPath, p[0].Name)
113
114 return p[0].Name
115}