github.com/senomas/gqlgen@v0.17.11-0.20220626120754-9aee61b0716a/internal/code/imports.go (about)

     1  package code
     2  
     3  import (
     4  	"go/build"
     5  	"go/parser"
     6  	"go/token"
     7  	"os"
     8  	"path/filepath"
     9  	"regexp"
    10  	"strings"
    11  )
    12  
    13  var gopaths []string
    14  
    15  func init() {
    16  	gopaths = filepath.SplitList(build.Default.GOPATH)
    17  	for i, p := range gopaths {
    18  		gopaths[i] = filepath.ToSlash(filepath.Join(p, "src"))
    19  	}
    20  }
    21  
    22  // NameForDir manually looks for package stanzas in files located in the given directory. This can be
    23  // much faster than having to consult go list, because we already know exactly where to look.
    24  func NameForDir(dir string) string {
    25  	dir, err := filepath.Abs(dir)
    26  	if err != nil {
    27  		return SanitizePackageName(filepath.Base(dir))
    28  	}
    29  	files, err := os.ReadDir(dir)
    30  	if err != nil {
    31  		return SanitizePackageName(filepath.Base(dir))
    32  	}
    33  	fset := token.NewFileSet()
    34  	for _, file := range files {
    35  		if !strings.HasSuffix(strings.ToLower(file.Name()), ".go") {
    36  			continue
    37  		}
    38  
    39  		filename := filepath.Join(dir, file.Name())
    40  		if src, err := parser.ParseFile(fset, filename, nil, parser.PackageClauseOnly); err == nil {
    41  			return src.Name.Name
    42  		}
    43  	}
    44  
    45  	return SanitizePackageName(filepath.Base(dir))
    46  }
    47  
    48  type goModuleSearchResult struct {
    49  	path       string
    50  	goModPath  string
    51  	moduleName string
    52  }
    53  
    54  var goModuleRootCache = map[string]goModuleSearchResult{}
    55  
    56  // goModuleRoot returns the root of the current go module if there is a go.mod file in the directory tree
    57  // If not, it returns false
    58  func goModuleRoot(dir string) (string, bool) {
    59  	dir, err := filepath.Abs(dir)
    60  	if err != nil {
    61  		panic(err)
    62  	}
    63  	dir = filepath.ToSlash(dir)
    64  
    65  	dirs := []string{dir}
    66  	result := goModuleSearchResult{}
    67  
    68  	for {
    69  		modDir := dirs[len(dirs)-1]
    70  
    71  		if val, ok := goModuleRootCache[dir]; ok {
    72  			result = val
    73  			break
    74  		}
    75  
    76  		if content, err := os.ReadFile(filepath.Join(modDir, "go.mod")); err == nil {
    77  			moduleName := string(modregex.FindSubmatch(content)[1])
    78  			result = goModuleSearchResult{
    79  				path:       moduleName,
    80  				goModPath:  modDir,
    81  				moduleName: moduleName,
    82  			}
    83  			goModuleRootCache[modDir] = result
    84  			break
    85  		}
    86  
    87  		if modDir == "" || modDir == "." || modDir == "/" || strings.HasSuffix(modDir, "\\") {
    88  			// Reached the top of the file tree which means go.mod file is not found
    89  			// Set root folder with a sentinel cache value
    90  			goModuleRootCache[modDir] = result
    91  			break
    92  		}
    93  
    94  		dirs = append(dirs, filepath.Dir(modDir))
    95  	}
    96  
    97  	// create a cache for each path in a tree traversed, except the top one as it is already cached
    98  	for _, d := range dirs[:len(dirs)-1] {
    99  		if result.moduleName == "" {
   100  			// go.mod is not found in the tree, so the same sentinel value fits all the directories in a tree
   101  			goModuleRootCache[d] = result
   102  		} else {
   103  			if relPath, err := filepath.Rel(result.goModPath, d); err != nil {
   104  				panic(err)
   105  			} else {
   106  				path := result.moduleName
   107  				relPath := filepath.ToSlash(relPath)
   108  				if !strings.HasSuffix(relPath, "/") {
   109  					path += "/"
   110  				}
   111  				path += relPath
   112  
   113  				goModuleRootCache[d] = goModuleSearchResult{
   114  					path:       path,
   115  					goModPath:  result.goModPath,
   116  					moduleName: result.moduleName,
   117  				}
   118  			}
   119  		}
   120  	}
   121  
   122  	res := goModuleRootCache[dir]
   123  	if res.moduleName == "" {
   124  		return "", false
   125  	}
   126  	return res.path, true
   127  }
   128  
   129  // ImportPathForDir takes a path and returns a golang import path for the package
   130  func ImportPathForDir(dir string) (res string) {
   131  	dir, err := filepath.Abs(dir)
   132  	if err != nil {
   133  		panic(err)
   134  	}
   135  	dir = filepath.ToSlash(dir)
   136  
   137  	modDir, ok := goModuleRoot(dir)
   138  	if ok {
   139  		return modDir
   140  	}
   141  
   142  	for _, gopath := range gopaths {
   143  		if len(gopath) < len(dir) && strings.EqualFold(gopath, dir[0:len(gopath)]) {
   144  			return dir[len(gopath)+1:]
   145  		}
   146  	}
   147  
   148  	return ""
   149  }
   150  
   151  var modregex = regexp.MustCompile(`module ([^\s]*)`)