mirror of
				https://github.com/go-gitea/gitea
				synced 2025-10-26 00:48:29 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			457 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
		
			Vendored
		
	
	
	
			
		
		
	
	
			457 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
		
			Vendored
		
	
	
	
| package dots
 | |
| 
 | |
| import (
 | |
| 	"go/build"
 | |
| 	"log"
 | |
| 	"os"
 | |
| 	"path"
 | |
| 	"path/filepath"
 | |
| 	"regexp"
 | |
| 	"runtime"
 | |
| 	"strings"
 | |
| )
 | |
| 
 | |
| var (
 | |
| 	buildContext = build.Default
 | |
| 	goroot       = filepath.Clean(runtime.GOROOT())
 | |
| 	gorootSrc    = filepath.Join(goroot, "src")
 | |
| )
 | |
| 
 | |
| func flatten(arr [][]string) []string {
 | |
| 	var res []string
 | |
| 	for _, e := range arr {
 | |
| 		res = append(res, e...)
 | |
| 	}
 | |
| 	return res
 | |
| }
 | |
| 
 | |
| // Resolve accepts a slice of paths with optional "..." placeholder and a slice with paths to be skipped.
 | |
| // The final result is the set of all files from the selected directories subtracted with
 | |
| // the files in the skip slice.
 | |
| func Resolve(includePatterns, skipPatterns []string) ([]string, error) {
 | |
| 	skip, err := resolvePatterns(skipPatterns)
 | |
| 	filter := newPathFilter(flatten(skip))
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	pathSet := map[string]bool{}
 | |
| 	includePackages, err := resolvePatterns(includePatterns)
 | |
| 	include := flatten(includePackages)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	var result []string
 | |
| 	for _, i := range include {
 | |
| 		if _, ok := pathSet[i]; !ok && !filter(i) {
 | |
| 			pathSet[i] = true
 | |
| 			result = append(result, i)
 | |
| 		}
 | |
| 	}
 | |
| 	return result, err
 | |
| }
 | |
| 
 | |
| // ResolvePackages accepts a slice of paths with optional "..." placeholder and a slice with paths to be skipped.
 | |
| // The final result is the set of all files from the selected directories subtracted with
 | |
| // the files in the skip slice. The difference between `Resolve` and `ResolvePackages`
 | |
| // is that `ResolvePackages` preserves the package structure in the nested slices.
 | |
| func ResolvePackages(includePatterns, skipPatterns []string) ([][]string, error) {
 | |
| 	skip, err := resolvePatterns(skipPatterns)
 | |
| 	filter := newPathFilter(flatten(skip))
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	pathSet := map[string]bool{}
 | |
| 	include, err := resolvePatterns(includePatterns)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	var result [][]string
 | |
| 	for _, p := range include {
 | |
| 		var packageFiles []string
 | |
| 		for _, f := range p {
 | |
| 			if _, ok := pathSet[f]; !ok && !filter(f) {
 | |
| 				pathSet[f] = true
 | |
| 				packageFiles = append(packageFiles, f)
 | |
| 			}
 | |
| 		}
 | |
| 		result = append(result, packageFiles)
 | |
| 	}
 | |
| 	return result, err
 | |
| }
 | |
| 
 | |
| func isDir(filename string) bool {
 | |
| 	fi, err := os.Stat(filename)
 | |
| 	return err == nil && fi.IsDir()
 | |
| }
 | |
| 
 | |
| func exists(filename string) bool {
 | |
| 	_, err := os.Stat(filename)
 | |
| 	return err == nil
 | |
| }
 | |
| 
 | |
| func resolveDir(dirname string) ([]string, error) {
 | |
| 	pkg, err := build.ImportDir(dirname, 0)
 | |
| 	return resolveImportedPackage(pkg, err)
 | |
| }
 | |
| 
 | |
| func resolvePackage(pkgname string) ([]string, error) {
 | |
| 	pkg, err := build.Import(pkgname, ".", 0)
 | |
| 	return resolveImportedPackage(pkg, err)
 | |
| }
 | |
| 
 | |
| func resolveImportedPackage(pkg *build.Package, err error) ([]string, error) {
 | |
| 	if err != nil {
 | |
| 		if _, nogo := err.(*build.NoGoError); nogo {
 | |
| 			// Don't complain if the failure is due to no Go source files.
 | |
| 			return nil, nil
 | |
| 		}
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	var files []string
 | |
| 	files = append(files, pkg.GoFiles...)
 | |
| 	files = append(files, pkg.CgoFiles...)
 | |
| 	files = append(files, pkg.TestGoFiles...)
 | |
| 	if pkg.Dir != "." {
 | |
| 		for i, f := range files {
 | |
| 			files[i] = filepath.Join(pkg.Dir, f)
 | |
| 		}
 | |
| 	}
 | |
| 	return files, nil
 | |
| }
 | |
| 
 | |
| func resolvePatterns(patterns []string) ([][]string, error) {
 | |
| 	var files [][]string
 | |
| 	for _, pattern := range patterns {
 | |
| 		f, err := resolvePattern(pattern)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		files = append(files, f...)
 | |
| 	}
 | |
| 	return files, nil
 | |
| }
 | |
| 
 | |
| func resolvePattern(pattern string) ([][]string, error) {
 | |
| 	// dirsRun, filesRun, and pkgsRun indicate whether golint is applied to
 | |
| 	// directory, file or package targets. The distinction affects which
 | |
| 	// checks are run. It is no valid to mix target types.
 | |
| 	var dirsRun, filesRun, pkgsRun int
 | |
| 	var matches []string
 | |
| 
 | |
| 	if strings.HasSuffix(pattern, "/...") && isDir(pattern[:len(pattern)-len("/...")]) {
 | |
| 		dirsRun = 1
 | |
| 		for _, dirname := range matchPackagesInFS(pattern) {
 | |
| 			matches = append(matches, dirname)
 | |
| 		}
 | |
| 	} else if isDir(pattern) {
 | |
| 		dirsRun = 1
 | |
| 		matches = append(matches, pattern)
 | |
| 	} else if exists(pattern) {
 | |
| 		filesRun = 1
 | |
| 		matches = append(matches, pattern)
 | |
| 	} else {
 | |
| 		pkgsRun = 1
 | |
| 		matches = append(matches, pattern)
 | |
| 	}
 | |
| 
 | |
| 	result := [][]string{}
 | |
| 	switch {
 | |
| 	case dirsRun == 1:
 | |
| 		for _, dir := range matches {
 | |
| 			res, err := resolveDir(dir)
 | |
| 			if err != nil {
 | |
| 				return nil, err
 | |
| 			}
 | |
| 			result = append(result, res)
 | |
| 		}
 | |
| 	case filesRun == 1:
 | |
| 		return [][]string{matches}, nil
 | |
| 	case pkgsRun == 1:
 | |
| 		for _, pkg := range importPaths(matches) {
 | |
| 			res, err := resolvePackage(pkg)
 | |
| 			if err != nil {
 | |
| 				return nil, err
 | |
| 			}
 | |
| 			result = append(result, res)
 | |
| 		}
 | |
| 	}
 | |
| 	return result, nil
 | |
| }
 | |
| 
 | |
| func newPathFilter(skip []string) func(string) bool {
 | |
| 	filter := map[string]bool{}
 | |
| 	for _, name := range skip {
 | |
| 		filter[name] = true
 | |
| 	}
 | |
| 
 | |
| 	return func(path string) bool {
 | |
| 		base := filepath.Base(path)
 | |
| 		if filter[base] || filter[path] {
 | |
| 			return true
 | |
| 		}
 | |
| 		return base != "." && base != ".." && strings.ContainsAny(base[0:1], "_.")
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // importPathsNoDotExpansion returns the import paths to use for the given
 | |
| // command line, but it does no ... expansion.
 | |
| func importPathsNoDotExpansion(args []string) []string {
 | |
| 	if len(args) == 0 {
 | |
| 		return []string{"."}
 | |
| 	}
 | |
| 	var out []string
 | |
| 	for _, a := range args {
 | |
| 		// Arguments are supposed to be import paths, but
 | |
| 		// as a courtesy to Windows developers, rewrite \ to /
 | |
| 		// in command-line arguments.  Handles .\... and so on.
 | |
| 		if filepath.Separator == '\\' {
 | |
| 			a = strings.Replace(a, `\`, `/`, -1)
 | |
| 		}
 | |
| 
 | |
| 		// Put argument in canonical form, but preserve leading ./.
 | |
| 		if strings.HasPrefix(a, "./") {
 | |
| 			a = "./" + path.Clean(a)
 | |
| 			if a == "./." {
 | |
| 				a = "."
 | |
| 			}
 | |
| 		} else {
 | |
| 			a = path.Clean(a)
 | |
| 		}
 | |
| 		if a == "all" || a == "std" {
 | |
| 			out = append(out, matchPackages(a)...)
 | |
| 			continue
 | |
| 		}
 | |
| 		out = append(out, a)
 | |
| 	}
 | |
| 	return out
 | |
| }
 | |
| 
 | |
| // importPaths returns the import paths to use for the given command line.
 | |
| func importPaths(args []string) []string {
 | |
| 	args = importPathsNoDotExpansion(args)
 | |
| 	var out []string
 | |
| 	for _, a := range args {
 | |
| 		if strings.Contains(a, "...") {
 | |
| 			if build.IsLocalImport(a) {
 | |
| 				out = append(out, matchPackagesInFS(a)...)
 | |
| 			} else {
 | |
| 				out = append(out, matchPackages(a)...)
 | |
| 			}
 | |
| 			continue
 | |
| 		}
 | |
| 		out = append(out, a)
 | |
| 	}
 | |
| 	return out
 | |
| }
 | |
| 
 | |
| // matchPattern(pattern)(name) reports whether
 | |
| // name matches pattern.  Pattern is a limited glob
 | |
| // pattern in which '...' means 'any string' and there
 | |
| // is no other special syntax.
 | |
| func matchPattern(pattern string) func(name string) bool {
 | |
| 	re := regexp.QuoteMeta(pattern)
 | |
| 	re = strings.Replace(re, `\.\.\.`, `.*`, -1)
 | |
| 	// Special case: foo/... matches foo too.
 | |
| 	if strings.HasSuffix(re, `/.*`) {
 | |
| 		re = re[:len(re)-len(`/.*`)] + `(/.*)?`
 | |
| 	}
 | |
| 	reg := regexp.MustCompile(`^` + re + `$`)
 | |
| 	return func(name string) bool {
 | |
| 		return reg.MatchString(name)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // hasPathPrefix reports whether the path s begins with the
 | |
| // elements in prefix.
 | |
| func hasPathPrefix(s, prefix string) bool {
 | |
| 	switch {
 | |
| 	default:
 | |
| 		return false
 | |
| 	case len(s) == len(prefix):
 | |
| 		return s == prefix
 | |
| 	case len(s) > len(prefix):
 | |
| 		if prefix != "" && prefix[len(prefix)-1] == '/' {
 | |
| 			return strings.HasPrefix(s, prefix)
 | |
| 		}
 | |
| 		return s[len(prefix)] == '/' && s[:len(prefix)] == prefix
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // treeCanMatchPattern(pattern)(name) reports whether
 | |
| // name or children of name can possibly match pattern.
 | |
| // Pattern is the same limited glob accepted by matchPattern.
 | |
| func treeCanMatchPattern(pattern string) func(name string) bool {
 | |
| 	wildCard := false
 | |
| 	if i := strings.Index(pattern, "..."); i >= 0 {
 | |
| 		wildCard = true
 | |
| 		pattern = pattern[:i]
 | |
| 	}
 | |
| 	return func(name string) bool {
 | |
| 		return len(name) <= len(pattern) && hasPathPrefix(pattern, name) ||
 | |
| 			wildCard && strings.HasPrefix(name, pattern)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func matchPackages(pattern string) []string {
 | |
| 	match := func(string) bool { return true }
 | |
| 	treeCanMatch := func(string) bool { return true }
 | |
| 	if pattern != "all" && pattern != "std" {
 | |
| 		match = matchPattern(pattern)
 | |
| 		treeCanMatch = treeCanMatchPattern(pattern)
 | |
| 	}
 | |
| 
 | |
| 	have := map[string]bool{
 | |
| 		"builtin": true, // ignore pseudo-package that exists only for documentation
 | |
| 	}
 | |
| 	if !buildContext.CgoEnabled {
 | |
| 		have["runtime/cgo"] = true // ignore during walk
 | |
| 	}
 | |
| 	var pkgs []string
 | |
| 
 | |
| 	// Commands
 | |
| 	cmd := filepath.Join(goroot, "src/cmd") + string(filepath.Separator)
 | |
| 	filepath.Walk(cmd, func(path string, fi os.FileInfo, err error) error {
 | |
| 		if err != nil || !fi.IsDir() || path == cmd {
 | |
| 			return nil
 | |
| 		}
 | |
| 		name := path[len(cmd):]
 | |
| 		if !treeCanMatch(name) {
 | |
| 			return filepath.SkipDir
 | |
| 		}
 | |
| 		// Commands are all in cmd/, not in subdirectories.
 | |
| 		if strings.Contains(name, string(filepath.Separator)) {
 | |
| 			return filepath.SkipDir
 | |
| 		}
 | |
| 
 | |
| 		// We use, e.g., cmd/gofmt as the pseudo import path for gofmt.
 | |
| 		name = "cmd/" + name
 | |
| 		if have[name] {
 | |
| 			return nil
 | |
| 		}
 | |
| 		have[name] = true
 | |
| 		if !match(name) {
 | |
| 			return nil
 | |
| 		}
 | |
| 		_, err = buildContext.ImportDir(path, 0)
 | |
| 		if err != nil {
 | |
| 			if _, noGo := err.(*build.NoGoError); !noGo {
 | |
| 				log.Print(err)
 | |
| 			}
 | |
| 			return nil
 | |
| 		}
 | |
| 		pkgs = append(pkgs, name)
 | |
| 		return nil
 | |
| 	})
 | |
| 
 | |
| 	for _, src := range buildContext.SrcDirs() {
 | |
| 		if (pattern == "std" || pattern == "cmd") && src != gorootSrc {
 | |
| 			continue
 | |
| 		}
 | |
| 		src = filepath.Clean(src) + string(filepath.Separator)
 | |
| 		root := src
 | |
| 		if pattern == "cmd" {
 | |
| 			root += "cmd" + string(filepath.Separator)
 | |
| 		}
 | |
| 		filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
 | |
| 			if err != nil || !fi.IsDir() || path == src {
 | |
| 				return nil
 | |
| 			}
 | |
| 
 | |
| 			// Avoid .foo, _foo, and testdata directory trees.
 | |
| 			_, elem := filepath.Split(path)
 | |
| 			if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" {
 | |
| 				return filepath.SkipDir
 | |
| 			}
 | |
| 
 | |
| 			name := filepath.ToSlash(path[len(src):])
 | |
| 			if pattern == "std" && (strings.Contains(name, ".") || name == "cmd") {
 | |
| 				// The name "std" is only the standard library.
 | |
| 				// If the name is cmd, it's the root of the command tree.
 | |
| 				return filepath.SkipDir
 | |
| 			}
 | |
| 			if !treeCanMatch(name) {
 | |
| 				return filepath.SkipDir
 | |
| 			}
 | |
| 			if have[name] {
 | |
| 				return nil
 | |
| 			}
 | |
| 			have[name] = true
 | |
| 			if !match(name) {
 | |
| 				return nil
 | |
| 			}
 | |
| 			_, err = buildContext.ImportDir(path, 0)
 | |
| 			if err != nil {
 | |
| 				if _, noGo := err.(*build.NoGoError); noGo {
 | |
| 					return nil
 | |
| 				}
 | |
| 			}
 | |
| 			pkgs = append(pkgs, name)
 | |
| 			return nil
 | |
| 		})
 | |
| 	}
 | |
| 	return pkgs
 | |
| }
 | |
| 
 | |
| func matchPackagesInFS(pattern string) []string {
 | |
| 	// Find directory to begin the scan.
 | |
| 	// Could be smarter but this one optimization
 | |
| 	// is enough for now, since ... is usually at the
 | |
| 	// end of a path.
 | |
| 	i := strings.Index(pattern, "...")
 | |
| 	dir, _ := path.Split(pattern[:i])
 | |
| 
 | |
| 	// pattern begins with ./ or ../.
 | |
| 	// path.Clean will discard the ./ but not the ../.
 | |
| 	// We need to preserve the ./ for pattern matching
 | |
| 	// and in the returned import paths.
 | |
| 	prefix := ""
 | |
| 	if strings.HasPrefix(pattern, "./") {
 | |
| 		prefix = "./"
 | |
| 	}
 | |
| 	match := matchPattern(pattern)
 | |
| 
 | |
| 	var pkgs []string
 | |
| 	filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error {
 | |
| 		if err != nil || !fi.IsDir() {
 | |
| 			return nil
 | |
| 		}
 | |
| 		if path == dir {
 | |
| 			// filepath.Walk starts at dir and recurses. For the recursive case,
 | |
| 			// the path is the result of filepath.Join, which calls filepath.Clean.
 | |
| 			// The initial case is not Cleaned, though, so we do this explicitly.
 | |
| 			//
 | |
| 			// This converts a path like "./io/" to "io". Without this step, running
 | |
| 			// "cd $GOROOT/src/pkg; go list ./io/..." would incorrectly skip the io
 | |
| 			// package, because prepending the prefix "./" to the unclean path would
 | |
| 			// result in "././io", and match("././io") returns false.
 | |
| 			path = filepath.Clean(path)
 | |
| 		}
 | |
| 
 | |
| 		// Avoid .foo, _foo, and testdata directory trees, but do not avoid "." or "..".
 | |
| 		_, elem := filepath.Split(path)
 | |
| 		dot := strings.HasPrefix(elem, ".") && elem != "." && elem != ".."
 | |
| 		if dot || strings.HasPrefix(elem, "_") || elem == "testdata" {
 | |
| 			return filepath.SkipDir
 | |
| 		}
 | |
| 
 | |
| 		name := prefix + filepath.ToSlash(path)
 | |
| 		if !match(name) {
 | |
| 			return nil
 | |
| 		}
 | |
| 		if _, err = build.ImportDir(path, 0); err != nil {
 | |
| 			if _, noGo := err.(*build.NoGoError); !noGo {
 | |
| 				log.Print(err)
 | |
| 			}
 | |
| 			return nil
 | |
| 		}
 | |
| 		pkgs = append(pkgs, name)
 | |
| 		return nil
 | |
| 	})
 | |
| 	return pkgs
 | |
| }
 |