github.com/kekek/gb@v0.4.5-0.20170222120241-d4ba64b0b297/cmd/gb/internal/match/match.go (about)

     1  package match
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"path"
     7  	"path/filepath"
     8  	"regexp"
     9  	"strings"
    10  
    11  	"github.com/constabulary/gb/internal/debug"
    12  )
    13  
    14  // importPathsNoDotExpansion returns the import paths to use for the given
    15  // command line, but it does no ... expansion.
    16  func importPathsNoDotExpansion(srcdir string, cwd string, args []string) []string {
    17  	srcdir, _ = filepath.Rel(srcdir, cwd)
    18  	debug.Debugf("%s %s", cwd, srcdir)
    19  	if srcdir == ".." {
    20  		srcdir = "."
    21  	}
    22  	if len(args) == 0 {
    23  		args = []string{"..."}
    24  	}
    25  	var out []string
    26  	for _, a := range args {
    27  		// Arguments are supposed to be import paths, but
    28  		// as a courtesy to Windows developers, rewrite \ to /
    29  		// in command-line arguments.  Handles .\... and so on.
    30  		if filepath.Separator == '\\' {
    31  			a = strings.Replace(a, `\`, `/`, -1)
    32  		}
    33  		a = path.Join(srcdir, path.Clean(a))
    34  		out = append(out, a)
    35  	}
    36  	return out
    37  }
    38  
    39  // ImportPaths returns the import paths to use for the given command line.
    40  func ImportPaths(srcdir, cwd string, args []string) []string {
    41  	args = importPathsNoDotExpansion(srcdir, cwd, args)
    42  	var out []string
    43  	for _, a := range args {
    44  		if strings.Contains(a, "...") {
    45  			pkgs, err := matchPackages(srcdir, a)
    46  			if err != nil {
    47  				fmt.Printf("could not load all packages: %v\n", err)
    48  			}
    49  			out = append(out, pkgs...)
    50  			continue
    51  		}
    52  		out = append(out, a)
    53  	}
    54  	return out
    55  }
    56  
    57  // matchPattern(pattern)(name) reports whether
    58  // name matches pattern.  Pattern is a limited glob
    59  // pattern in which '...' means 'any string' and there
    60  // is no other special syntax.
    61  func matchPattern(pattern string) func(name string) bool {
    62  	re := regexp.QuoteMeta(pattern)
    63  	re = strings.Replace(re, `\.\.\.`, `.*`, -1)
    64  	// Special case: foo/... matches foo too.
    65  	if strings.HasSuffix(re, `/.*`) {
    66  		re = re[:len(re)-len(`/.*`)] + `(/.*)?`
    67  	}
    68  	reg := regexp.MustCompile(`^` + re + `$`)
    69  	return func(name string) bool {
    70  		return reg.MatchString(name)
    71  	}
    72  }
    73  
    74  // hasPathPrefix reports whether the path s begins with the
    75  // elements in prefix.
    76  func hasPathPrefix(s, prefix string) bool {
    77  	switch {
    78  	default:
    79  		return false
    80  	case len(s) == len(prefix):
    81  		return s == prefix
    82  	case len(s) > len(prefix):
    83  		if prefix != "" && prefix[len(prefix)-1] == '/' {
    84  			return strings.HasPrefix(s, prefix)
    85  		}
    86  		return s[len(prefix)] == '/' && s[:len(prefix)] == prefix
    87  	}
    88  }
    89  
    90  // treeCanMatchPattern(pattern)(name) reports whether
    91  // name or children of name can possibly match pattern.
    92  // Pattern is the same limited glob accepted by matchPattern.
    93  func treeCanMatchPattern(pattern string) func(name string) bool {
    94  	wildCard := false
    95  	if i := strings.Index(pattern, "..."); i >= 0 {
    96  		wildCard = true
    97  		pattern = pattern[:i]
    98  	}
    99  	return func(name string) bool {
   100  		return len(name) <= len(pattern) && hasPathPrefix(pattern, name) ||
   101  			wildCard && strings.HasPrefix(name, pattern)
   102  	}
   103  }
   104  
   105  // matchPackages returns all the packages that can be found under the srcdir directory.
   106  // The pattern is a path including "...".
   107  func matchPackages(srcdir, pattern string) ([]string, error) {
   108  	debug.Debugf("matchPackages: %v", pattern)
   109  	match := matchPattern(pattern)
   110  	treeCanMatch := treeCanMatchPattern(pattern)
   111  
   112  	var pkgs []string
   113  
   114  	src := srcdir + string(filepath.Separator)
   115  	err := filepath.Walk(src, func(path string, fi os.FileInfo, err error) error {
   116  		if err != nil || !fi.IsDir() || path == src {
   117  			return nil
   118  		}
   119  
   120  		// Avoid .foo, _foo, and testdata directory trees.
   121  		if skipElem(fi.Name()) {
   122  			return filepath.SkipDir
   123  		}
   124  
   125  		name := filepath.ToSlash(path[len(src):])
   126  		if pattern == "std" && strings.Contains(name, ".") {
   127  			return filepath.SkipDir
   128  		}
   129  		if !treeCanMatch(name) {
   130  			return filepath.SkipDir
   131  		}
   132  		if match(name) {
   133  			pkgs = append(pkgs, name)
   134  		}
   135  		return nil
   136  	})
   137  	return pkgs, err
   138  }
   139  
   140  // IsLocalImport reports whether the import path is
   141  // a local import path, like ".", "..", "./foo", or "../foo".
   142  func isLocalImport(path string) bool {
   143  	return path == "." || path == ".." || strings.HasPrefix(path, "./") || strings.HasPrefix(path, "../")
   144  }
   145  
   146  // skipElem returns true of the path element should be ignored.thub.com/foo/bar" "github.com/quxx/bar"]
   147  func skipElem(elem string) bool {
   148  	return strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata"
   149  }