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 }