github.com/99designs/gqlgen@v0.17.45/internal/code/imports.go (about) 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 // NameForDir manually looks for package stanzas in files located in the given directory. This can be 25 // much faster than having to consult go list, because we already know exactly where to look. 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 // goModuleRoot returns the root of the current go module if there is a go.mod file in the directory tree 59 // If not, it returns false 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 // Reached the top of the file tree which means go.mod file is not found 91 // Set root folder with a sentinel cache value 92 goModuleRootCache[modDir] = result 93 break 94 } 95 96 dirs = append(dirs, filepath.Dir(modDir)) 97 } 98 99 // create a cache for each path in a tree traversed, except the top one as it is already cached 100 for _, d := range dirs[:len(dirs)-1] { 101 if result.moduleName == "" { 102 // go.mod is not found in the tree, so the same sentinel value fits all the directories in a tree 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 // ImportPathForDir takes a path and returns a golang import path for the package 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*)`)