]> Cypherpunks repositories - gostls13.git/commitdiff
go: add ... patterns in import path arguments
authorRuss Cox <rsc@golang.org>
Tue, 10 Jan 2012 00:23:00 +0000 (16:23 -0800)
committerRuss Cox <rsc@golang.org>
Tue, 10 Jan 2012 00:23:00 +0000 (16:23 -0800)
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/5530058

src/cmd/go/help.go
src/cmd/go/main.go

index f21d9b8c016dd8d80c93516202dc7a66f6770374..33716eff9ec7a973e66a29dfc218a136b452aa6f 100644 (file)
@@ -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.
 
index 7b7f4a450dd77b07f585f20f070bb21ca8696065..e451b3a03b4426f07c2b5471aff4d1a63063525f 100644 (file)
@@ -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
 }