/cmd/gb/internal/match/match.go

https://github.com/constabulary/gb · Go · 145 lines · 110 code · 13 blank · 22 comment · 44 complexity · fcdfc6cfb4c4c64ac1527811e94d50e2 MD5 · raw file

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