From: Russ Cox Date: Tue, 10 Jan 2012 00:23:00 +0000 (-0800) Subject: go: add ... patterns in import path arguments X-Git-Tag: weekly.2012-01-15~110 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=b8615a0931b05c37c7d8cf3c0e11a858888483de;p=gostls13.git go: add ... patterns in import path arguments R=golang-dev, r CC=golang-dev https://golang.org/cl/5530058 --- diff --git a/src/cmd/go/help.go b/src/cmd/go/help.go index f21d9b8c01..33716eff9e 100644 --- a/src/cmd/go/help.go +++ b/src/cmd/go/help.go @@ -30,6 +30,13 @@ lists all the packages on the local system. The special import path "std" is like all but expands to just the packages in the standard Go library. +An import path is a pattern if it includes one or more "..." wildcards, +each of which can match any string, including the empty string and +strings containing slashes. Such a pattern expands to all package +directories found in the GOPATH trees with names matching the +patterns. For example, encoding/... expands to all packages +in the encoding tree. + An import path can also name a package to be downloaded from a remote repository. Run 'go help remote' for details. diff --git a/src/cmd/go/main.go b/src/cmd/go/main.go index 7b7f4a450d..e451b3a03b 100644 --- a/src/cmd/go/main.go +++ b/src/cmd/go/main.go @@ -12,7 +12,9 @@ import ( "log" "os" "os/exec" + "path" "path/filepath" + "regexp" "strings" "text/template" ) @@ -185,15 +187,22 @@ func help(args []string) { // importPaths returns the import paths to use for the given command line. func importPaths(args []string) []string { - if len(args) == 1 { - if args[0] == "all" || args[0] == "std" { - return allPackages(args[0]) - } - } if len(args) == 0 { return []string{"."} } - return args + var out []string + for _, a := range args { + if (strings.HasPrefix(a, "./") || strings.HasPrefix(a, "../")) && strings.Contains(a, "...") { + out = append(out, allPackagesInFS(a)...) + continue + } + if a == "all" || a == "std" || strings.Contains(a, "...") { + out = append(out, allPackages(a)...) + continue + } + out = append(out, a) + } + return out } var atexitFuncs []func() @@ -236,9 +245,29 @@ func run(cmdline ...string) { } } +// 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) + reg := regexp.MustCompile(`^` + re + `$`) + return func(name string) bool { + return reg.MatchString(name) + } +} + // allPackages returns all the packages that can be found -// under the $GOPATH directories and $GOROOT. -func allPackages(what string) []string { +// under the $GOPATH directories and $GOROOT matching what. +// The pattern is either "all" (all packages), "std" (standard packages) +// or a path including "...". +func allPackages(pattern string) []string { + match := func(string) bool { return true } + if pattern != "all" && pattern != "std" { + match = matchPattern(pattern) + } + have := map[string]bool{ "builtin": true, // ignore pseudo-package that exists only for documentation } @@ -269,13 +298,15 @@ func allPackages(what string) []string { name = "cmd/" + name if !have[name] { have[name] = true - pkgs = append(pkgs, name) + if match(name) { + pkgs = append(pkgs, name) + } } return nil }) for _, t := range build.Path { - if what == "std" && !t.Goroot { + if pattern == "std" && !t.Goroot { continue } src := t.SrcDir() + string(filepath.Separator) @@ -284,26 +315,29 @@ func allPackages(what string) []string { return nil } - // Avoid testdata directory trees. - if strings.HasSuffix(path, string(filepath.Separator)+"testdata") { + // Avoid .foo and testdata directory trees. + _, elem := filepath.Split(path) + if strings.HasPrefix(elem, ".") || elem == "testdata" { return filepath.SkipDir } name := filepath.ToSlash(path[len(src):]) - if what == "std" && strings.Contains(name, ".") { + if pattern == "std" && strings.Contains(name, ".") { return filepath.SkipDir } if have[name] { return nil } + have[name] = true _, err = build.ScanDir(path) if err != nil { return nil } - pkgs = append(pkgs, name) - have[name] = true + if match(name) { + pkgs = append(pkgs, name) + } // Avoid go/build test data. // TODO: Move it into a testdata directory. @@ -314,5 +348,59 @@ func allPackages(what string) []string { return nil }) } + + if len(pkgs) == 0 { + fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern) + } + return pkgs +} + +// allPackagesInFS is like allPackages but is passed a pattern +// beginning ./ or ../, meaning it should scan the tree rooted +// at the given directory. There are ... in the pattern too. +func allPackagesInFS(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 + } + + // Avoid .foo and testdata directory trees. + _, elem := filepath.Split(path) + if strings.HasPrefix(elem, ".") || elem == "testdata" { + return filepath.SkipDir + } + + name := prefix + filepath.ToSlash(path) + if !match(name) { + return nil + } + if _, err = build.ScanDir(path); err != nil { + return nil + } + pkgs = append(pkgs, name) + return nil + }) + + if len(pkgs) == 0 { + fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern) + } return pkgs }