From 04ca4f824281bf0a17a5aaa5b1586978779b6200 Mon Sep 17 00:00:00 2001 From: Gustavo Niemeyer Date: Sun, 6 Mar 2011 17:33:23 -0500 Subject: [PATCH] path/filepath: new OS-specific path support The path package now contains only functions which deal with slashed paths, sensible for any OS when dealing with network paths or URLs. OS-specific functionality has been moved into the new path/filepath package. This also includes fixes for godoc, goinstall and other packages which were mixing slashed and OS-specific paths. R=rsc, gri, mattn, brainman CC=golang-dev https://golang.org/cl/4252044 --- src/cmd/ebnflint/ebnflint.go | 4 +- src/cmd/godoc/dirtrees.go | 8 +- src/cmd/godoc/godoc.go | 63 ++--- src/cmd/godoc/index.go | 8 +- src/cmd/godoc/main.go | 10 +- src/cmd/godoc/mapping.go | 47 ++-- src/cmd/godoc/utils.go | 12 +- src/cmd/gofmt/gofmt.go | 4 +- src/cmd/goinstall/download.go | 19 +- src/cmd/goinstall/main.go | 11 +- src/cmd/goinstall/parse.go | 12 +- src/cmd/govet/govet.go | 6 +- src/cmd/hgpatch/main.go | 4 +- src/pkg/Makefile | 1 + src/pkg/go/parser/interface.go | 4 +- src/pkg/go/printer/printer.go | 4 +- src/pkg/go/printer/printer_test.go | 6 +- src/pkg/go/scanner/scanner.go | 8 +- src/pkg/http/fs.go | 8 +- src/pkg/io/ioutil/tempfile.go | 7 +- src/pkg/path/Makefile | 12 - src/pkg/path/filepath/Makefile | 26 ++ src/pkg/path/filepath/match.go | 282 ++++++++++++++++++++ src/pkg/path/filepath/match_test.go | 106 ++++++++ src/pkg/path/filepath/path.go | 270 +++++++++++++++++++ src/pkg/path/filepath/path_test.go | 387 ++++++++++++++++++++++++++++ src/pkg/path/filepath/path_unix.go | 10 + src/pkg/path/match.go | 83 +----- src/pkg/path/match_test.go | 28 -- src/pkg/path/path.go | 78 +----- src/pkg/path/path_test.go | 159 ------------ src/pkg/path/path_unix.go | 11 - src/pkg/path/path_windows.go | 11 - 33 files changed, 1228 insertions(+), 481 deletions(-) create mode 100644 src/pkg/path/filepath/Makefile create mode 100644 src/pkg/path/filepath/match.go create mode 100644 src/pkg/path/filepath/match_test.go create mode 100644 src/pkg/path/filepath/path.go create mode 100644 src/pkg/path/filepath/path_test.go create mode 100644 src/pkg/path/filepath/path_unix.go delete mode 100644 src/pkg/path/path_unix.go delete mode 100644 src/pkg/path/path_windows.go diff --git a/src/cmd/ebnflint/ebnflint.go b/src/cmd/ebnflint/ebnflint.go index 5eb39873544..cac39179f2a 100644 --- a/src/cmd/ebnflint/ebnflint.go +++ b/src/cmd/ebnflint/ebnflint.go @@ -13,7 +13,7 @@ import ( "go/token" "io/ioutil" "os" - "path" + "path/filepath" ) @@ -91,7 +91,7 @@ func main() { os.Exit(1) } - if path.Ext(filename) == ".html" { + if filepath.Ext(filename) == ".html" { src = extractEBNF(src) } diff --git a/src/cmd/godoc/dirtrees.go b/src/cmd/godoc/dirtrees.go index d6d88c2f9a9..3ad7c8cfc50 100644 --- a/src/cmd/godoc/dirtrees.go +++ b/src/cmd/godoc/dirtrees.go @@ -14,7 +14,7 @@ import ( "io/ioutil" "log" "os" - pathutil "path" + "path/filepath" "strings" "unicode" ) @@ -32,7 +32,7 @@ type Directory struct { func isGoFile(f *os.FileInfo) bool { return f.IsRegular() && !strings.HasPrefix(f.Name, ".") && // ignore .files - pathutil.Ext(f.Name) == ".go" + filepath.Ext(f.Name) == ".go" } @@ -123,7 +123,7 @@ func (b *treeBuilder) newDirTree(fset *token.FileSet, path, name string, depth i // though the directory doesn't contain any real package files - was bug) if synopses[0] == "" { // no "optimal" package synopsis yet; continue to collect synopses - file, err := parser.ParseFile(fset, pathutil.Join(path, d.Name), nil, + file, err := parser.ParseFile(fset, filepath.Join(path, d.Name), nil, parser.ParseComments|parser.PackageClauseOnly) if err == nil { hasPkgFiles = true @@ -156,7 +156,7 @@ func (b *treeBuilder) newDirTree(fset *token.FileSet, path, name string, depth i i := 0 for _, d := range list { if isPkgDir(d) { - dd := b.newDirTree(fset, pathutil.Join(path, d.Name), d.Name, depth+1) + dd := b.newDirTree(fset, filepath.Join(path, d.Name), d.Name, depth+1) if dd != nil { dirs[i] = dd i++ diff --git a/src/cmd/godoc/godoc.go b/src/cmd/godoc/godoc.go index efb386f06e6..9dce5edf949 100644 --- a/src/cmd/godoc/godoc.go +++ b/src/cmd/godoc/godoc.go @@ -18,7 +18,8 @@ import ( "io/ioutil" "log" "os" - pathutil "path" + "path" + "path/filepath" "regexp" "runtime" "sort" @@ -81,8 +82,8 @@ var ( func initHandlers() { fsMap.Init(*pkgPath) fileServer = http.FileServer(*goroot, "") - cmdHandler = httpHandler{"/cmd/", pathutil.Join(*goroot, "src/cmd"), false} - pkgHandler = httpHandler{"/pkg/", pathutil.Join(*goroot, "src/pkg"), true} + cmdHandler = httpHandler{"/cmd/", filepath.Join(*goroot, "src", "cmd"), false} + pkgHandler = httpHandler{"/pkg/", filepath.Join(*goroot, "src", "pkg"), true} } @@ -97,7 +98,7 @@ func registerPublicHandlers(mux *http.ServeMux) { func initFSTree() { - fsTree.set(newDirectory(pathutil.Join(*goroot, *testDir), nil, -1)) + fsTree.set(newDirectory(filepath.Join(*goroot, *testDir), nil, -1)) invalidateIndex() } @@ -246,27 +247,30 @@ func initDirTrees() { // ---------------------------------------------------------------------------- // Path mapping -func absolutePath(path, defaultRoot string) string { - abspath := fsMap.ToAbsolute(path) +// Absolute paths are file system paths (backslash-separated on Windows), +// but relative paths are always slash-separated. + +func absolutePath(relpath, defaultRoot string) string { + abspath := fsMap.ToAbsolute(relpath) if abspath == "" { // no user-defined mapping found; use default mapping - abspath = pathutil.Join(defaultRoot, path) + abspath = filepath.Join(defaultRoot, filepath.FromSlash(relpath)) } return abspath } -func relativePath(path string) string { - relpath := fsMap.ToRelative(path) +func relativeURL(abspath string) string { + relpath := fsMap.ToRelative(abspath) if relpath == "" { - // prefix must end in '/' + // prefix must end in a path separator prefix := *goroot - if len(prefix) > 0 && prefix[len(prefix)-1] != '/' { - prefix += "/" + if len(prefix) > 0 && prefix[len(prefix)-1] != filepath.Separator { + prefix += string(filepath.Separator) } - if strings.HasPrefix(path, prefix) { + if strings.HasPrefix(abspath, prefix) { // no user-defined mapping found; use default mapping - relpath = path[len(prefix):] + relpath = filepath.ToSlash(abspath[len(prefix):]) } } // Only if path is an invalid absolute path is relpath == "" @@ -481,7 +485,7 @@ func urlFmt(w io.Writer, format string, x ...interface{}) { } // map path - relpath := relativePath(path) + relpath := relativeURL(path) // convert to relative URLs so that they can also // be used as relative file names in .txt templates @@ -598,7 +602,7 @@ func dirslashFmt(w io.Writer, format string, x ...interface{}) { // Template formatter for "localname" format. func localnameFmt(w io.Writer, format string, x ...interface{}) { - _, localname := pathutil.Split(x[0].(string)) + _, localname := filepath.Split(x[0].(string)) template.HTMLEscape(w, []byte(localname)) } @@ -630,7 +634,7 @@ var fmap = template.FormatterMap{ func readTemplate(name string) *template.Template { - path := pathutil.Join(*goroot, "lib/godoc/"+name) + path := filepath.Join(*goroot, "lib", "godoc", name) data, err := ioutil.ReadFile(path) if err != nil { log.Fatalf("ReadFile %s: %v", path, err) @@ -767,14 +771,13 @@ func applyTemplate(t *template.Template, name string, data interface{}) []byte { func redirect(w http.ResponseWriter, r *http.Request) (redirected bool) { - if canonical := pathutil.Clean(r.URL.Path) + "/"; r.URL.Path != canonical { + if canonical := path.Clean(r.URL.Path) + "/"; r.URL.Path != canonical { http.Redirect(w, r, canonical, http.StatusMovedPermanently) redirected = true } return } - func serveTextFile(w http.ResponseWriter, r *http.Request, abspath, relpath, title string) { src, err := ioutil.ReadFile(abspath) if err != nil { @@ -785,7 +788,7 @@ func serveTextFile(w http.ResponseWriter, r *http.Request, abspath, relpath, tit var buf bytes.Buffer buf.WriteString("
")
-	FormatText(&buf, src, 1, pathutil.Ext(abspath) == ".go", r.FormValue("h"), rangeSelection(r.FormValue("s")))
+	FormatText(&buf, src, 1, filepath.Ext(abspath) == ".go", r.FormValue("h"), rangeSelection(r.FormValue("s")))
 	buf.WriteString("
") servePage(w, title+" "+relpath, "", "", buf.Bytes()) @@ -822,7 +825,7 @@ func serveFile(w http.ResponseWriter, r *http.Request) { // pick off special cases and hand the rest to the standard file server switch r.URL.Path { case "/": - serveHTMLDoc(w, r, pathutil.Join(*goroot, "doc/root.html"), "doc/root.html") + serveHTMLDoc(w, r, filepath.Join(*goroot, "doc", "root.html"), "doc/root.html") return case "/doc/root.html": @@ -831,9 +834,9 @@ func serveFile(w http.ResponseWriter, r *http.Request) { return } - switch pathutil.Ext(abspath) { + switch path.Ext(relpath) { case ".html": - if strings.HasSuffix(abspath, "/index.html") { + if strings.HasSuffix(relpath, "/index.html") { // We'll show index.html for the directory. // Use the dir/ version as canonical instead of dir/index.html. http.Redirect(w, r, r.URL.Path[0:len(r.URL.Path)-len("index.html")], http.StatusMovedPermanently) @@ -858,8 +861,8 @@ func serveFile(w http.ResponseWriter, r *http.Request) { if redirect(w, r) { return } - if index := abspath + "/index.html"; isTextFile(index) { - serveHTMLDoc(w, r, index, relativePath(index)) + if index := filepath.Join(abspath, "index.html"); isTextFile(index) { + serveHTMLDoc(w, r, index, relativeURL(index)) return } serveDirectory(w, r, abspath, relpath) @@ -955,13 +958,13 @@ func (h *httpHandler) getPageInfo(abspath, relpath, pkgname string, mode PageInf // the package with dirname, and the 3rd choice is a package // that is not called "main" if there is exactly one such // package. Otherwise, don't select a package. - dirpath, dirname := pathutil.Split(abspath) + dirpath, dirname := filepath.Split(abspath) // If the dirname is "go" we might be in a sub-directory for // .go files - use the outer directory name instead for better // results. if dirname == "go" { - _, dirname = pathutil.Split(pathutil.Clean(dirpath)) + _, dirname = filepath.Split(filepath.Clean(dirpath)) } var choice3 *ast.Package @@ -1002,7 +1005,7 @@ func (h *httpHandler) getPageInfo(abspath, relpath, pkgname string, mode PageInf ast.PackageExports(pkg) } if mode&genDoc != 0 { - pdoc = doc.NewPackageDoc(pkg, pathutil.Clean(relpath)) // no trailing '/' in importpath + pdoc = doc.NewPackageDoc(pkg, path.Clean(relpath)) // no trailing '/' in importpath } else { past = ast.MergePackageFiles(pkg, ast.FilterUnassociatedComments) } @@ -1088,13 +1091,13 @@ func (h *httpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { title = "Package " + info.PDoc.PackageName case info.PDoc.PackageName == fakePkgName: // assume that the directory name is the command name - _, pkgname := pathutil.Split(pathutil.Clean(relpath)) + _, pkgname := path.Split(path.Clean(relpath)) title = "Command " + pkgname default: title = "Command " + info.PDoc.PackageName } default: - title = "Directory " + relativePath(info.Dirname) + title = "Directory " + relativeURL(info.Dirname) if *showTimestamps { subtitle = "Last update: " + time.SecondsToLocalTime(info.DirTime).String() } diff --git a/src/cmd/godoc/index.go b/src/cmd/godoc/index.go index 56f31f5cf08..5af4d15cb54 100644 --- a/src/cmd/godoc/index.go +++ b/src/cmd/godoc/index.go @@ -47,7 +47,7 @@ import ( "index/suffixarray" "io/ioutil" "os" - "path" + "path/filepath" "regexp" "sort" "strings" @@ -718,7 +718,7 @@ var whitelisted = map[string]bool{ // of "permitted" files for indexing. The filename must // be the directory-local name of the file. func isWhitelisted(filename string) bool { - key := path.Ext(filename) + key := filepath.Ext(filename) if key == "" { // file has no extension - use entire filename key = filename @@ -732,7 +732,7 @@ func (x *Indexer) visitFile(dirname string, f *os.FileInfo, fulltextIndex bool) return } - filename := path.Join(dirname, f.Name) + filename := filepath.Join(dirname, f.Name) goFile := false switch { @@ -757,7 +757,7 @@ func (x *Indexer) visitFile(dirname string, f *os.FileInfo, fulltextIndex bool) if fast != nil { // we've got a Go file to index x.current = file - dir, _ := path.Split(filename) + dir, _ := filepath.Split(filename) pak := Pak{dir, fast.Name.Name} x.file = &File{filename, pak} ast.Walk(x, fast) diff --git a/src/cmd/godoc/main.go b/src/cmd/godoc/main.go index ea1e3c42e13..1ebb802790f 100644 --- a/src/cmd/godoc/main.go +++ b/src/cmd/godoc/main.go @@ -36,7 +36,7 @@ import ( "io" "log" "os" - pathutil "path" + "path/filepath" "regexp" "runtime" "strings" @@ -314,14 +314,14 @@ func main() { if len(path) > 0 && path[0] == '.' { // assume cwd; don't assume -goroot cwd, _ := os.Getwd() // ignore errors - path = pathutil.Join(cwd, path) + path = filepath.Join(cwd, path) } relpath := path abspath := path - if !pathutil.IsAbs(path) { + if !filepath.IsAbs(path) { abspath = absolutePath(path, pkgHandler.fsRoot) } else { - relpath = relativePath(path) + relpath = relativeURL(path) } var mode PageInfoMode @@ -339,7 +339,7 @@ func main() { if info.IsEmpty() { // try again, this time assume it's a command - if !pathutil.IsAbs(path) { + if !filepath.IsAbs(path) { abspath = absolutePath(path, cmdHandler.fsRoot) } cmdInfo := cmdHandler.getPageInfo(abspath, relpath, "", mode) diff --git a/src/cmd/godoc/mapping.go b/src/cmd/godoc/mapping.go index 1d87bbc76eb..6ae9032e484 100644 --- a/src/cmd/godoc/mapping.go +++ b/src/cmd/godoc/mapping.go @@ -10,7 +10,8 @@ import ( "fmt" "io" "os" - pathutil "path" + "path" + "path/filepath" "sort" "strings" ) @@ -59,10 +60,10 @@ type mapping struct { } -// Init initializes the Mapping from a list of ':'-separated -// paths. Empty paths are ignored; relative paths are assumed -// to be relative to the current working directory and converted -// to absolute paths. For each path of the form: +// Init initializes the Mapping from a list of paths separated by +// filepath.ListSeparator. Empty paths are ignored; relative paths +// are assumed to be relative to the current working directory and +// converted to absolute paths. For each path of the form: // // dirname/localname // @@ -71,7 +72,7 @@ type mapping struct { // localname -> path // // is added to the Mapping object, in the order of occurrence. -// For instance, the argument: +// For instance, under Unix, the argument: // // /home/user:/home/build/public // @@ -81,12 +82,12 @@ type mapping struct { // public -> /home/build/public // func (m *Mapping) Init(paths string) { - pathlist := canonicalizePaths(strings.Split(paths, ":", -1), nil) + pathlist := canonicalizePaths(filepath.SplitList(paths), nil) list := make([]mapping, len(pathlist)) // create mapping list for i, path := range pathlist { - _, prefix := pathutil.Split(path) + _, prefix := filepath.Split(path) list[i] = mapping{prefix, path, new(RWValue)} } @@ -147,7 +148,7 @@ func (m *Mapping) Fprint(w io.Writer) { func splitFirst(path string) (head, tail string) { - i := strings.Index(path, "/") + i := strings.Index(path, string(filepath.Separator)) if i > 0 { // 0 < i < len(path) return path[0:i], path[i+1:] @@ -156,22 +157,23 @@ func splitFirst(path string) (head, tail string) { } -// ToAbsolute maps a relative path to an absolute path using the Mapping -// specified by the receiver. If the path cannot be mapped, the empty -// string is returned. +// ToAbsolute maps a slash-separated relative path to an absolute filesystem +// path using the Mapping specified by the receiver. If the path cannot +// be mapped, the empty string is returned. // -func (m *Mapping) ToAbsolute(path string) string { - prefix, tail := splitFirst(path) +func (m *Mapping) ToAbsolute(spath string) string { + fpath := filepath.FromSlash(spath) + prefix, tail := splitFirst(fpath) for _, e := range m.list { switch { case e.prefix == prefix: // use tail case e.prefix == "": - tail = path + tail = fpath default: continue // no match } - abspath := pathutil.Join(e.path, tail) + abspath := filepath.Join(e.path, tail) if _, err := os.Stat(abspath); err == nil { return abspath } @@ -181,15 +183,16 @@ func (m *Mapping) ToAbsolute(path string) string { } -// ToRelative maps an absolute path to a relative path using the Mapping -// specified by the receiver. If the path cannot be mapped, the empty -// string is returned. +// ToRelative maps an absolute filesystem path to a relative slash-separated +// path using the Mapping specified by the receiver. If the path cannot +// be mapped, the empty string is returned. // -func (m *Mapping) ToRelative(path string) string { +func (m *Mapping) ToRelative(fpath string) string { for _, e := range m.list { - if strings.HasPrefix(path, e.path) { + if strings.HasPrefix(fpath, e.path) { + spath := filepath.ToSlash(fpath) // /absolute/prefix/foo -> prefix/foo - return pathutil.Join(e.prefix, path[len(e.path):]) // Join will remove a trailing '/' + return path.Join(e.prefix, spath[len(e.path):]) // Join will remove a trailing '/' } } return "" // no match diff --git a/src/cmd/godoc/utils.go b/src/cmd/godoc/utils.go index d21e7e98634..9517aee7abe 100644 --- a/src/cmd/godoc/utils.go +++ b/src/cmd/godoc/utils.go @@ -10,7 +10,7 @@ import ( "io" "io/ioutil" "os" - pathutil "path" + "path/filepath" "sort" "strings" "sync" @@ -60,10 +60,10 @@ func canonicalizePaths(list []string, filter func(path string) bool) []string { continue // ignore empty paths (don't assume ".") } // len(path) > 0: normalize path - if pathutil.IsAbs(path) { - path = pathutil.Clean(path) + if filepath.IsAbs(path) { + path = filepath.Clean(path) } else { - path = pathutil.Join(cwd, path) + path = filepath.Join(cwd, path) } // we have a non-empty absolute path if filter != nil && !filter(path) { @@ -95,7 +95,7 @@ func canonicalizePaths(list []string, filter func(path string) bool) []string { // atomically renames that file to the file named by filename. // func writeFileAtomically(filename string, data []byte) os.Error { - f, err := ioutil.TempFile(pathutil.Split(filename)) + f, err := ioutil.TempFile(filepath.Split(filename)) if err != nil { return err } @@ -149,7 +149,7 @@ var textExt = map[string]bool{ // func isTextFile(filename string) bool { // if the extension is known, use it for decision making - if isText, found := textExt[pathutil.Ext(filename)]; found { + if isText, found := textExt[filepath.Ext(filename)]; found { return isText } diff --git a/src/cmd/gofmt/gofmt.go b/src/cmd/gofmt/gofmt.go index 41c12b88de3..224aee717d6 100644 --- a/src/cmd/gofmt/gofmt.go +++ b/src/cmd/gofmt/gofmt.go @@ -15,7 +15,7 @@ import ( "go/token" "io/ioutil" "os" - pathutil "path" + "path/filepath" "strings" ) @@ -181,7 +181,7 @@ func walkDir(path string) { done <- true }() // walk the tree - pathutil.Walk(path, v, v) + filepath.Walk(path, v, v) close(v) // terminate error handler loop <-done // wait for all errors to be reported } diff --git a/src/cmd/goinstall/download.go b/src/cmd/goinstall/download.go index 889f9d857bf..88befc0dc74 100644 --- a/src/cmd/goinstall/download.go +++ b/src/cmd/goinstall/download.go @@ -9,7 +9,7 @@ package main import ( "http" "os" - "path" + "path/filepath" "regexp" "strings" ) @@ -42,7 +42,7 @@ func download(pkg string) (string, os.Error) { return "", os.ErrorString("invalid path (contains ..)") } if m := bitbucket.FindStringSubmatch(pkg); m != nil { - if err := vcsCheckout(&hg, root+m[1], "http://"+m[1], m[1]); err != nil { + if err := vcsCheckout(&hg, m[1], "http://"+m[1], m[1]); err != nil { return "", err } return root + pkg, nil @@ -58,7 +58,7 @@ func download(pkg string) (string, os.Error) { // regexp only allows hg, svn to get through panic("missing case in download: " + pkg) } - if err := vcsCheckout(v, root+m[1], "https://"+m[1], m[1]); err != nil { + if err := vcsCheckout(v, m[1], "https://"+m[1], m[1]); err != nil { return "", err } return root + pkg, nil @@ -67,7 +67,7 @@ func download(pkg string) (string, os.Error) { if strings.HasSuffix(m[1], ".git") { return "", os.ErrorString("repository " + pkg + " should not have .git suffix") } - if err := vcsCheckout(&git, root+m[1], "http://"+m[1]+".git", m[1]); err != nil { + if err := vcsCheckout(&git, m[1], "http://"+m[1]+".git", m[1]); err != nil { return "", err } return root + pkg, nil @@ -75,7 +75,7 @@ func download(pkg string) (string, os.Error) { if m := launchpad.FindStringSubmatch(pkg); m != nil { // Either lp.net/[/[/]] // or lp.net/~//[/] - if err := vcsCheckout(&bzr, root+m[1], "https://"+m[1], m[1]); err != nil { + if err := vcsCheckout(&bzr, m[1], "https://"+m[1], m[1]); err != nil { return "", err } return root + pkg, nil @@ -172,17 +172,18 @@ func (v *vcs) updateRepo(dst string) os.Error { // exists and -u was specified on the command line) // the repository at tag/branch "release". If there is no // such tag or branch, it falls back to the repository tip. -func vcsCheckout(vcs *vcs, dst, repo, dashpath string) os.Error { - dir, err := os.Stat(dst + "/" + vcs.metadir) +func vcsCheckout(vcs *vcs, pkgprefix, repo, dashpath string) os.Error { + dst := filepath.Join(root, filepath.FromSlash(pkgprefix)) + dir, err := os.Stat(filepath.Join(dst, vcs.metadir)) if err == nil && !dir.IsDirectory() { return os.ErrorString("not a directory: " + dst) } if err != nil { - parent, _ := path.Split(dst) + parent, _ := filepath.Split(dst) if err := os.MkdirAll(parent, 0777); err != nil { return err } - if err := run("/", nil, vcs.cmd, vcs.clone, repo, dst); err != nil { + if err := run(string(filepath.Separator), nil, vcs.cmd, vcs.clone, repo, dst); err != nil { return err } if err := vcs.updateRepo(dst); err != nil { diff --git a/src/cmd/goinstall/main.go b/src/cmd/goinstall/main.go index f13aeb3bc07..34441be45df 100644 --- a/src/cmd/goinstall/main.go +++ b/src/cmd/goinstall/main.go @@ -15,7 +15,7 @@ import ( "io" "io/ioutil" "os" - "path" + "path/filepath" "runtime" "strings" ) @@ -34,7 +34,7 @@ var ( parents = make(map[string]string) root = runtime.GOROOT() visit = make(map[string]status) - logfile = path.Join(root, "goinstall.log") + logfile = filepath.Join(root, "goinstall.log") installedPkgs = make(map[string]bool) allpkg = flag.Bool("a", false, "install all previously installed packages") @@ -59,7 +59,7 @@ func main() { fmt.Fprintf(os.Stderr, "%s: no $GOROOT\n", argv0) os.Exit(1) } - root += "/src/pkg/" + root += filepath.FromSlash("/src/pkg/") // special case - "unsafe" is already installed visit["unsafe"] = done @@ -160,7 +160,7 @@ func install(pkg, parent string) { dir = pkg local = true } else if isStandardPath(pkg) { - dir = path.Join(root, pkg) + dir = filepath.Join(root, filepath.FromSlash(pkg)) local = true } else { var err os.Error @@ -216,7 +216,8 @@ func install(pkg, parent string) { // Is this a local path? /foo ./foo ../foo . .. func isLocalPath(s string) bool { - return strings.HasPrefix(s, "/") || strings.HasPrefix(s, "./") || strings.HasPrefix(s, "../") || s == "." || s == ".." + const sep = string(filepath.Separator) + return strings.HasPrefix(s, sep) || strings.HasPrefix(s, "."+sep) || strings.HasPrefix(s, ".."+sep) || s == "." || s == ".." } // Is this a standard package path? strings container/vector etc. diff --git a/src/cmd/goinstall/parse.go b/src/cmd/goinstall/parse.go index 679edfabcaa..014b8fcb20b 100644 --- a/src/cmd/goinstall/parse.go +++ b/src/cmd/goinstall/parse.go @@ -7,13 +7,13 @@ package main import ( - "path" - "os" - "log" - "strings" - "strconv" "go/ast" "go/parser" + "log" + "os" + "path/filepath" + "strconv" + "strings" ) @@ -64,7 +64,7 @@ func scanDir(dir string, allowMain bool) (info *dirInfo, err os.Error) { if !strings.HasSuffix(d.Name, ".go") || strings.HasSuffix(d.Name, "_test.go") { continue } - filename := path.Join(dir, d.Name) + filename := filepath.Join(dir, d.Name) pf, err := parser.ParseFile(fset, filename, nil, parser.ImportsOnly) if err != nil { return nil, err diff --git a/src/cmd/govet/govet.go b/src/cmd/govet/govet.go index 5619b12bad1..ff6421de898 100644 --- a/src/cmd/govet/govet.go +++ b/src/cmd/govet/govet.go @@ -15,7 +15,7 @@ import ( "go/parser" "go/token" "os" - "path" + "path/filepath" "strconv" "strings" ) @@ -99,7 +99,7 @@ func doFile(name string, reader io.Reader) { file.checkFile(name, parsedFile) } -// Visitor for path.Walk - trivial. Just calls doFile on each file. +// Visitor for filepath.Walk - trivial. Just calls doFile on each file. // TODO: if govet becomes richer, might want to process // a directory (package) at a time. type V struct{} @@ -124,7 +124,7 @@ func walkDir(root string) { } done <- true }() - path.Walk(root, V{}, errors) + filepath.Walk(root, V{}, errors) close(errors) <-done } diff --git a/src/cmd/hgpatch/main.go b/src/cmd/hgpatch/main.go index bd4b563f92c..2dcb5234c7f 100644 --- a/src/cmd/hgpatch/main.go +++ b/src/cmd/hgpatch/main.go @@ -14,7 +14,7 @@ import ( "io/ioutil" "os" "patch" - "path" + "path/filepath" "sort" "strings" ) @@ -186,7 +186,7 @@ func main() { // make parent directory for name, if necessary func makeParent(name string) { - parent, _ := path.Split(name) + parent, _ := filepath.Split(name) chk(mkdirAll(parent, 0755)) } diff --git a/src/pkg/Makefile b/src/pkg/Makefile index 331bb68e5a3..6e70690d1b3 100644 --- a/src/pkg/Makefile +++ b/src/pkg/Makefile @@ -118,6 +118,7 @@ DIRS=\ os/signal\ patch\ path\ + path/filepath\ rand\ reflect\ regexp\ diff --git a/src/pkg/go/parser/interface.go b/src/pkg/go/parser/interface.go index 84d699a6793..6f35b495efa 100644 --- a/src/pkg/go/parser/interface.go +++ b/src/pkg/go/parser/interface.go @@ -14,7 +14,7 @@ import ( "io" "io/ioutil" "os" - pathutil "path" + "path/filepath" ) @@ -198,7 +198,7 @@ func ParseDir(fset *token.FileSet, path string, filter func(*os.FileInfo) bool, for i := 0; i < len(list); i++ { d := &list[i] if filter == nil || filter(d) { - filenames[n] = pathutil.Join(path, d.Name) + filenames[n] = filepath.Join(path, d.Name) n++ } } diff --git a/src/pkg/go/printer/printer.go b/src/pkg/go/printer/printer.go index 48e2af1b736..90d9784ac97 100644 --- a/src/pkg/go/printer/printer.go +++ b/src/pkg/go/printer/printer.go @@ -12,7 +12,7 @@ import ( "go/token" "io" "os" - "path" + "path/filepath" "runtime" "tabwriter" ) @@ -244,7 +244,7 @@ func (p *printer) writeItem(pos token.Position, data []byte) { } if debug { // do not update p.pos - use write0 - _, filename := path.Split(pos.Filename) + _, filename := filepath.Split(pos.Filename) p.write0([]byte(fmt.Sprintf("[%s:%d:%d]", filename, pos.Line, pos.Column))) } p.write(data) diff --git a/src/pkg/go/printer/printer_test.go b/src/pkg/go/printer/printer_test.go index 565075aa20c..62b7269131c 100644 --- a/src/pkg/go/printer/printer_test.go +++ b/src/pkg/go/printer/printer_test.go @@ -11,7 +11,7 @@ import ( "go/ast" "go/parser" "go/token" - "path" + "path/filepath" "testing" ) @@ -129,8 +129,8 @@ var data = []entry{ func TestFiles(t *testing.T) { for _, e := range data { - source := path.Join(dataDir, e.source) - golden := path.Join(dataDir, e.golden) + source := filepath.Join(dataDir, e.source) + golden := filepath.Join(dataDir, e.golden) check(t, source, golden, e.mode) // TODO(gri) check that golden is idempotent //check(t, golden, golden, e.mode); diff --git a/src/pkg/go/scanner/scanner.go b/src/pkg/go/scanner/scanner.go index 2ae296b3f15..153707f5987 100644 --- a/src/pkg/go/scanner/scanner.go +++ b/src/pkg/go/scanner/scanner.go @@ -23,7 +23,7 @@ package scanner import ( "bytes" "go/token" - "path" + "path/filepath" "strconv" "unicode" "utf8" @@ -118,7 +118,7 @@ func (S *Scanner) Init(file *token.File, src []byte, err ErrorHandler, mode uint panic("file size does not match src len") } S.file = file - S.dir, _ = path.Split(file.Name()) + S.dir, _ = filepath.Split(file.Name()) S.src = src S.err = err S.mode = mode @@ -180,10 +180,10 @@ func (S *Scanner) interpretLineComment(text []byte) { if i := bytes.Index(text, []byte{':'}); i > 0 { if line, err := strconv.Atoi(string(text[i+1:])); err == nil && line > 0 { // valid //line filename:line comment; - filename := path.Clean(string(text[len(prefix):i])) + filename := filepath.Clean(string(text[len(prefix):i])) if filename[0] != '/' { // make filename relative to current directory - filename = path.Join(S.dir, filename) + filename = filepath.Join(S.dir, filename) } // update scanner position S.file.AddLineInfo(S.lineOffset, filename, line-1) // -1 since comment applies to next line diff --git a/src/pkg/http/fs.go b/src/pkg/http/fs.go index 8e16992e0f0..a4cd7072e12 100644 --- a/src/pkg/http/fs.go +++ b/src/pkg/http/fs.go @@ -11,7 +11,7 @@ import ( "io" "mime" "os" - "path" + "path/filepath" "strconv" "strings" "time" @@ -112,7 +112,7 @@ func serveFile(w ResponseWriter, r *Request, name string, redirect bool) { // use contents of index.html for directory, if present if d.IsDirectory() { - index := name + indexPage + index := name + filepath.FromSlash(indexPage) ff, err := os.Open(index, os.O_RDONLY, 0) if err == nil { defer ff.Close() @@ -135,7 +135,7 @@ func serveFile(w ResponseWriter, r *Request, name string, redirect bool) { code := StatusOK // use extension to find content type. - ext := path.Ext(name) + ext := filepath.Ext(name) if ctype := mime.TypeByExtension(ext); ctype != "" { w.SetHeader("Content-Type", ctype) } else { @@ -202,7 +202,7 @@ func (f *fileHandler) ServeHTTP(w ResponseWriter, r *Request) { return } path = path[len(f.prefix):] - serveFile(w, r, f.root+"/"+path, true) + serveFile(w, r, filepath.Join(f.root, filepath.FromSlash(path)), true) } // httpRange specifies the byte range to be sent to the client. diff --git a/src/pkg/io/ioutil/tempfile.go b/src/pkg/io/ioutil/tempfile.go index c7cc67b1b74..62f8849c0a0 100644 --- a/src/pkg/io/ioutil/tempfile.go +++ b/src/pkg/io/ioutil/tempfile.go @@ -6,6 +6,7 @@ package ioutil import ( "os" + "path/filepath" "strconv" ) @@ -46,8 +47,7 @@ func TempFile(dir, prefix string) (f *os.File, err os.Error) { nconflict := 0 for i := 0; i < 10000; i++ { - // TODO(rsc): use filepath.Join - name := dir + "/" + prefix + nextSuffix() + name := filepath.Join(dir, prefix+nextSuffix()) f, err = os.Open(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600) if pe, ok := err.(*os.PathError); ok && pe.Error == os.EEXIST { if nconflict++; nconflict > 10 { @@ -74,8 +74,7 @@ func TempDir(dir, prefix string) (name string, err os.Error) { nconflict := 0 for i := 0; i < 10000; i++ { - // TODO(rsc): use filepath.Join - try := dir + "/" + prefix + nextSuffix() + try := filepath.Join(dir, prefix+nextSuffix()) err = os.Mkdir(try, 0700) if pe, ok := err.(*os.PathError); ok && pe.Error == os.EEXIST { if nconflict++; nconflict > 10 { diff --git a/src/pkg/path/Makefile b/src/pkg/path/Makefile index 4371913e850..fc3e2519cec 100644 --- a/src/pkg/path/Makefile +++ b/src/pkg/path/Makefile @@ -9,18 +9,6 @@ GOFILES=\ match.go\ path.go\ -GOFILES_freebsd=\ - path_unix.go - -GOFILES_darwin=\ - path_unix.go - -GOFILES_linux=\ - path_unix.go - -GOFILES_windows=\ - path_windows.go - GOFILES+=$(GOFILES_$(GOOS)) include ../../Make.pkg diff --git a/src/pkg/path/filepath/Makefile b/src/pkg/path/filepath/Makefile new file mode 100644 index 00000000000..2330fc09ded --- /dev/null +++ b/src/pkg/path/filepath/Makefile @@ -0,0 +1,26 @@ +# Copyright 2009 The Go Authors. All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +include ../../../Make.inc + +TARG=path/filepath +GOFILES=\ + match.go\ + path.go\ + +GOFILES_freebsd=\ + path_unix.go + +GOFILES_darwin=\ + path_unix.go + +GOFILES_linux=\ + path_unix.go + +GOFILES_windows=\ + path_unix.go + +GOFILES+=$(GOFILES_$(GOOS)) + +include ../../../Make.pkg diff --git a/src/pkg/path/filepath/match.go b/src/pkg/path/filepath/match.go new file mode 100644 index 00000000000..ad4053fa242 --- /dev/null +++ b/src/pkg/path/filepath/match.go @@ -0,0 +1,282 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package filepath + +import ( + "os" + "sort" + "strings" + "utf8" +) + +var ErrBadPattern = os.NewError("syntax error in pattern") + +// Match returns true if name matches the shell file name pattern. +// The pattern syntax is: +// +// pattern: +// { term } +// term: +// '*' matches any sequence of non-Separator characters +// '?' matches any single non-Separator character +// '[' [ '^' ] { character-range } ']' +// character class (must be non-empty) +// c matches character c (c != '*', '?', '\\', '[') +// '\\' c matches character c +// +// character-range: +// c matches character c (c != '\\', '-', ']') +// '\\' c matches character c +// lo '-' hi matches character c for lo <= c <= hi +// +// Match requires pattern to match all of name, not just a substring. +// The only possible error return is when pattern is malformed. +// +func Match(pattern, name string) (matched bool, err os.Error) { +Pattern: + for len(pattern) > 0 { + var star bool + var chunk string + star, chunk, pattern = scanChunk(pattern) + if star && chunk == "" { + // Trailing * matches rest of string unless it has a /. + return strings.Index(name, string(Separator)) < 0, nil + } + // Look for match at current position. + t, ok, err := matchChunk(chunk, name) + // if we're the last chunk, make sure we've exhausted the name + // otherwise we'll give a false result even if we could still match + // using the star + if ok && (len(t) == 0 || len(pattern) > 0) { + name = t + continue + } + if err != nil { + return false, err + } + if star { + // Look for match skipping i+1 bytes. + // Cannot skip /. + for i := 0; i < len(name) && name[i] != Separator; i++ { + t, ok, err := matchChunk(chunk, name[i+1:]) + if ok { + // if we're the last chunk, make sure we exhausted the name + if len(pattern) == 0 && len(t) > 0 { + continue + } + name = t + continue Pattern + } + if err != nil { + return false, err + } + } + } + return false, nil + } + return len(name) == 0, nil +} + +// scanChunk gets the next segment of pattern, which is a non-star string +// possibly preceded by a star. +func scanChunk(pattern string) (star bool, chunk, rest string) { + for len(pattern) > 0 && pattern[0] == '*' { + pattern = pattern[1:] + star = true + } + inrange := false + var i int +Scan: + for i = 0; i < len(pattern); i++ { + switch pattern[i] { + case '\\': + // error check handled in matchChunk: bad pattern. + if i+1 < len(pattern) { + i++ + } + case '[': + inrange = true + case ']': + inrange = false + case '*': + if !inrange { + break Scan + } + } + } + return star, pattern[0:i], pattern[i:] +} + +// matchChunk checks whether chunk matches the beginning of s. +// If so, it returns the remainder of s (after the match). +// Chunk is all single-character operators: literals, char classes, and ?. +func matchChunk(chunk, s string) (rest string, ok bool, err os.Error) { + for len(chunk) > 0 { + if len(s) == 0 { + return + } + switch chunk[0] { + case '[': + // character class + r, n := utf8.DecodeRuneInString(s) + s = s[n:] + chunk = chunk[1:] + // possibly negated + notNegated := true + if len(chunk) > 0 && chunk[0] == '^' { + notNegated = false + chunk = chunk[1:] + } + // parse all ranges + match := false + nrange := 0 + for { + if len(chunk) > 0 && chunk[0] == ']' && nrange > 0 { + chunk = chunk[1:] + break + } + var lo, hi int + if lo, chunk, err = getEsc(chunk); err != nil { + return + } + hi = lo + if chunk[0] == '-' { + if hi, chunk, err = getEsc(chunk[1:]); err != nil { + return + } + } + if lo <= r && r <= hi { + match = true + } + nrange++ + } + if match != notNegated { + return + } + + case '?': + if s[0] == Separator { + return + } + _, n := utf8.DecodeRuneInString(s) + s = s[n:] + chunk = chunk[1:] + + case '\\': + chunk = chunk[1:] + if len(chunk) == 0 { + err = ErrBadPattern + return + } + fallthrough + + default: + if chunk[0] != s[0] { + return + } + s = s[1:] + chunk = chunk[1:] + } + } + return s, true, nil +} + +// getEsc gets a possibly-escaped character from chunk, for a character class. +func getEsc(chunk string) (r int, nchunk string, err os.Error) { + if len(chunk) == 0 || chunk[0] == '-' || chunk[0] == ']' { + err = ErrBadPattern + return + } + if chunk[0] == '\\' { + chunk = chunk[1:] + if len(chunk) == 0 { + err = ErrBadPattern + return + } + } + r, n := utf8.DecodeRuneInString(chunk) + if r == utf8.RuneError && n == 1 { + err = ErrBadPattern + } + nchunk = chunk[n:] + if len(nchunk) == 0 { + err = ErrBadPattern + } + return +} + +// Glob returns the names of all files matching pattern or nil +// if there is no matching file. The syntax of patterns is the same +// as in Match. The pattern may describe hierarchical names such as +// /usr/*/bin/ed (assuming the Separator is '/'). +// +func Glob(pattern string) (matches []string) { + if !hasMeta(pattern) { + if _, err := os.Stat(pattern); err == nil { + return []string{pattern} + } + return nil + } + + dir, file := Split(pattern) + switch dir { + case "": + dir = "." + case string(Separator): + // nothing + default: + dir = dir[0 : len(dir)-1] // chop off trailing separator + } + + if hasMeta(dir) { + for _, d := range Glob(dir) { + matches = glob(d, file, matches) + } + } else { + return glob(dir, file, nil) + } + return matches +} + +// glob searches for files matching pattern in the directory dir +// and appends them to matches. +func glob(dir, pattern string, matches []string) []string { + fi, err := os.Stat(dir) + if err != nil { + return nil + } + if !fi.IsDirectory() { + return matches + } + d, err := os.Open(dir, os.O_RDONLY, 0666) + if err != nil { + return nil + } + defer d.Close() + + names, err := d.Readdirnames(-1) + if err != nil { + return nil + } + sort.SortStrings(names) + + for _, n := range names { + matched, err := Match(pattern, n) + if err != nil { + return matches + } + if matched { + matches = append(matches, Join(dir, n)) + } + } + return matches +} + +// hasMeta returns true if path contains any of the magic characters +// recognized by Match. +func hasMeta(path string) bool { + // TODO(niemeyer): Should other magic characters be added here? + return strings.IndexAny(path, "*?[") >= 0 +} diff --git a/src/pkg/path/filepath/match_test.go b/src/pkg/path/filepath/match_test.go new file mode 100644 index 00000000000..ad0c90b75cf --- /dev/null +++ b/src/pkg/path/filepath/match_test.go @@ -0,0 +1,106 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package filepath_test + +import ( + "os" + "path/filepath" + "testing" +) + +type MatchTest struct { + pattern, s string + match bool + err os.Error +} + +var matchTests = []MatchTest{ + {"abc", "abc", true, nil}, + {"*", "abc", true, nil}, + {"*c", "abc", true, nil}, + {"a*", "a", true, nil}, + {"a*", "abc", true, nil}, + {"a*", "ab/c", false, nil}, + {"a*/b", "abc/b", true, nil}, + {"a*/b", "a/c/b", false, nil}, + {"a*b*c*d*e*/f", "axbxcxdxe/f", true, nil}, + {"a*b*c*d*e*/f", "axbxcxdxexxx/f", true, nil}, + {"a*b*c*d*e*/f", "axbxcxdxe/xxx/f", false, nil}, + {"a*b*c*d*e*/f", "axbxcxdxexxx/fff", false, nil}, + {"a*b?c*x", "abxbbxdbxebxczzx", true, nil}, + {"a*b?c*x", "abxbbxdbxebxczzy", false, nil}, + {"ab[c]", "abc", true, nil}, + {"ab[b-d]", "abc", true, nil}, + {"ab[e-g]", "abc", false, nil}, + {"ab[^c]", "abc", false, nil}, + {"ab[^b-d]", "abc", false, nil}, + {"ab[^e-g]", "abc", true, nil}, + {"a\\*b", "a*b", true, nil}, + {"a\\*b", "ab", false, nil}, + {"a?b", "a☺b", true, nil}, + {"a[^a]b", "a☺b", true, nil}, + {"a???b", "a☺b", false, nil}, + {"a[^a][^a][^a]b", "a☺b", false, nil}, + {"[a-ζ]*", "α", true, nil}, + {"*[a-ζ]", "A", false, nil}, + {"a?b", "a/b", false, nil}, + {"a*b", "a/b", false, nil}, + {"[\\]a]", "]", true, nil}, + {"[\\-]", "-", true, nil}, + {"[x\\-]", "x", true, nil}, + {"[x\\-]", "-", true, nil}, + {"[x\\-]", "z", false, nil}, + {"[\\-x]", "x", true, nil}, + {"[\\-x]", "-", true, nil}, + {"[\\-x]", "a", false, nil}, + {"[]a]", "]", false, filepath.ErrBadPattern}, + {"[-]", "-", false, filepath.ErrBadPattern}, + {"[x-]", "x", false, filepath.ErrBadPattern}, + {"[x-]", "-", false, filepath.ErrBadPattern}, + {"[x-]", "z", false, filepath.ErrBadPattern}, + {"[-x]", "x", false, filepath.ErrBadPattern}, + {"[-x]", "-", false, filepath.ErrBadPattern}, + {"[-x]", "a", false, filepath.ErrBadPattern}, + {"\\", "a", false, filepath.ErrBadPattern}, + {"[a-b-c]", "a", false, filepath.ErrBadPattern}, + {"*x", "xxx", true, nil}, +} + +func TestMatch(t *testing.T) { + for _, tt := range matchTests { + ok, err := filepath.Match(tt.pattern, tt.s) + if ok != tt.match || err != tt.err { + t.Errorf("Match(%#q, %#q) = %v, %v want %v, nil", tt.pattern, tt.s, ok, err, tt.match) + } + } +} + +// contains returns true if vector contains the string s. +func contains(vector []string, s string) bool { + for _, elem := range vector { + if elem == s { + return true + } + } + return false +} + +var globTests = []struct { + pattern, result string +}{ + {"match.go", "match.go"}, + {"mat?h.go", "match.go"}, + {"*", "match.go"}, + {"../*/match.go", "../filepath/match.go"}, +} + +func TestGlob(t *testing.T) { + for _, tt := range globTests { + matches := filepath.Glob(tt.pattern) + if !contains(matches, tt.result) { + t.Errorf("Glob(%#q) = %#v want %v", tt.pattern, matches, tt.result) + } + } +} diff --git a/src/pkg/path/filepath/path.go b/src/pkg/path/filepath/path.go new file mode 100644 index 00000000000..414df7d2089 --- /dev/null +++ b/src/pkg/path/filepath/path.go @@ -0,0 +1,270 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// The filepath package implements utility routines for manipulating +// filename paths in a way compatible with the target operating +// system-defined file paths. +package filepath + +import ( + "os" + "sort" + "strings" +) + +// BUG(niemeyer): Package filepath does not yet work on Windows. + +// Clean returns the shortest path name equivalent to path +// by purely lexical processing. It applies the following rules +// iteratively until no further processing can be done: +// +// 1. Replace multiple Separator elements with a single one. +// 2. Eliminate each . path name element (the current directory). +// 3. Eliminate each inner .. path name element (the parent directory) +// along with the non-.. element that precedes it. +// 4. Eliminate .. elements that begin a rooted path: +// that is, replace "/.." by "/" at the beginning of a path, +// assuming Separator is '/'. +// +// If the result of this process is an empty string, Clean +// returns the string ".". +// +// See also Rob Pike, ``Lexical File Names in Plan 9 or +// Getting Dot-Dot right,'' +// http://plan9.bell-labs.com/sys/doc/lexnames.html +func Clean(path string) string { + if path == "" { + return "." + } + + rooted := path[0] == Separator + n := len(path) + + // Invariants: + // reading from path; r is index of next byte to process. + // writing to buf; w is index of next byte to write. + // dotdot is index in buf where .. must stop, either because + // it is the leading slash or it is a leading ../../.. prefix. + buf := []byte(path) + r, w, dotdot := 0, 0, 0 + if rooted { + r, w, dotdot = 1, 1, 1 + } + + for r < n { + switch { + case path[r] == Separator: + // empty path element + r++ + case path[r] == '.' && (r+1 == n || path[r+1] == Separator): + // . element + r++ + case path[r] == '.' && path[r+1] == '.' && (r+2 == n || path[r+2] == Separator): + // .. element: remove to last separator + r += 2 + switch { + case w > dotdot: + // can backtrack + w-- + for w > dotdot && buf[w] != Separator { + w-- + } + case !rooted: + // cannot backtrack, but not rooted, so append .. element. + if w > 0 { + buf[w] = Separator + w++ + } + buf[w] = '.' + w++ + buf[w] = '.' + w++ + dotdot = w + } + default: + // real path element. + // add slash if needed + if rooted && w != 1 || !rooted && w != 0 { + buf[w] = Separator + w++ + } + // copy element + for ; r < n && path[r] != Separator; r++ { + buf[w] = path[r] + w++ + } + } + } + + // Turn empty string into "." + if w == 0 { + buf[w] = '.' + w++ + } + + return string(buf[0:w]) +} + +// ToSlash returns the result of replacing each separator character +// in path with a slash ('/') character. +func ToSlash(path string) string { + if Separator == '/' { + return path + } + return strings.Replace(path, string(Separator), "/", -1) +} + +// FromSlash returns the result of replacing each slash ('/') character +// in path with a separator character. +func FromSlash(path string) string { + if Separator == '/' { + return path + } + return strings.Replace(path, "/", string(Separator), -1) +} + +// SplitList splits a list of paths joined by the OS-specific ListSeparator. +func SplitList(path string) []string { + if path == "" { + return []string{} + } + return strings.Split(path, string(ListSeparator), -1) +} + +// Split splits path immediately following the final Separator, +// partitioning it into a directory and a file name components. +// If there are no separators in path, Split returns an empty base +// and file set to path. +func Split(path string) (dir, file string) { + i := strings.LastIndex(path, string(Separator)) + return path[:i+1], path[i+1:] +} + +// Join joins any number of path elements into a single path, adding +// a Separator if necessary. All empty strings are ignored. +func Join(elem ...string) string { + for i, e := range elem { + if e != "" { + return Clean(strings.Join(elem[i:], string(Separator))) + } + } + return "" +} + +// Ext returns the file name extension used by path. +// The extension is the suffix beginning at the final dot +// in the final element of path; it is empty if there is +// no dot. +func Ext(path string) string { + for i := len(path) - 1; i >= 0 && path[i] != Separator; i-- { + if path[i] == '.' { + return path[i:] + } + } + return "" +} + +// Visitor methods are invoked for corresponding file tree entries +// visited by Walk. The parameter path is the full path of f relative +// to root. +type Visitor interface { + VisitDir(path string, f *os.FileInfo) bool + VisitFile(path string, f *os.FileInfo) +} + +func walk(path string, f *os.FileInfo, v Visitor, errors chan<- os.Error) { + if !f.IsDirectory() { + v.VisitFile(path, f) + return + } + + if !v.VisitDir(path, f) { + return // skip directory entries + } + + list, err := readDir(path) + if err != nil { + if errors != nil { + errors <- err + } + } + + for _, e := range list { + walk(Join(path, e.Name), e, v, errors) + } +} + +// readDir reads the directory named by dirname and returns +// a list of sorted directory entries. +// Copied from io/ioutil to avoid the circular import. +func readDir(dirname string) ([]*os.FileInfo, os.Error) { + f, err := os.Open(dirname, os.O_RDONLY, 0) + if err != nil { + return nil, err + } + list, err := f.Readdir(-1) + f.Close() + if err != nil { + return nil, err + } + fi := make(fileInfoList, len(list)) + for i := range list { + fi[i] = &list[i] + } + sort.Sort(fi) + return fi, nil +} + +// A dirList implements sort.Interface. +type fileInfoList []*os.FileInfo + +func (f fileInfoList) Len() int { return len(f) } +func (f fileInfoList) Less(i, j int) bool { return f[i].Name < f[j].Name } +func (f fileInfoList) Swap(i, j int) { f[i], f[j] = f[j], f[i] } + +// Walk walks the file tree rooted at root, calling v.VisitDir or +// v.VisitFile for each directory or file in the tree, including root. +// If v.VisitDir returns false, Walk skips the directory's entries; +// otherwise it invokes itself for each directory entry in sorted order. +// An error reading a directory does not abort the Walk. +// If errors != nil, Walk sends each directory read error +// to the channel. Otherwise Walk discards the error. +func Walk(root string, v Visitor, errors chan<- os.Error) { + f, err := os.Lstat(root) + if err != nil { + if errors != nil { + errors <- err + } + return // can't progress + } + walk(root, f, v, errors) +} + +// Base returns the last element of path. +// Trailing path separators are removed before extracting the last element. +// If the path is empty, Base returns ".". +// If the path consists entirely of separators, Base returns a single separator. +func Base(path string) string { + if path == "" { + return "." + } + // Strip trailing slashes. + for len(path) > 0 && path[len(path)-1] == Separator { + path = path[0 : len(path)-1] + } + // Find the last element + if i := strings.LastIndex(path, string(Separator)); i >= 0 { + path = path[i+1:] + } + // If empty now, it had only slashes. + if path == "" { + return string(Separator) + } + return path +} + +// IsAbs returns true if the path is absolute. +func IsAbs(path string) bool { + return len(path) > 0 && path[0] == Separator +} diff --git a/src/pkg/path/filepath/path_test.go b/src/pkg/path/filepath/path_test.go new file mode 100644 index 00000000000..469ca6a8029 --- /dev/null +++ b/src/pkg/path/filepath/path_test.go @@ -0,0 +1,387 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package filepath_test + +import ( + "os" + "path/filepath" + "reflect" + "testing" +) + +type PathTest struct { + path, result string +} + +var cleantests = []PathTest{ + // Already clean + {"", "."}, + {"abc", "abc"}, + {"abc/def", "abc/def"}, + {"a/b/c", "a/b/c"}, + {".", "."}, + {"..", ".."}, + {"../..", "../.."}, + {"../../abc", "../../abc"}, + {"/abc", "/abc"}, + {"/", "/"}, + + // Remove trailing slash + {"abc/", "abc"}, + {"abc/def/", "abc/def"}, + {"a/b/c/", "a/b/c"}, + {"./", "."}, + {"../", ".."}, + {"../../", "../.."}, + {"/abc/", "/abc"}, + + // Remove doubled slash + {"abc//def//ghi", "abc/def/ghi"}, + {"//abc", "/abc"}, + {"///abc", "/abc"}, + {"//abc//", "/abc"}, + {"abc//", "abc"}, + + // Remove . elements + {"abc/./def", "abc/def"}, + {"/./abc/def", "/abc/def"}, + {"abc/.", "abc"}, + + // Remove .. elements + {"abc/def/ghi/../jkl", "abc/def/jkl"}, + {"abc/def/../ghi/../jkl", "abc/jkl"}, + {"abc/def/..", "abc"}, + {"abc/def/../..", "."}, + {"/abc/def/../..", "/"}, + {"abc/def/../../..", ".."}, + {"/abc/def/../../..", "/"}, + {"abc/def/../../../ghi/jkl/../../../mno", "../../mno"}, + + // Combinations + {"abc/./../def", "def"}, + {"abc//./../def", "def"}, + {"abc/../../././../def", "../../def"}, +} + +func TestClean(t *testing.T) { + for _, test := range cleantests { + if s := filepath.Clean(test.path); s != test.result { + t.Errorf("Clean(%q) = %q, want %q", test.path, s, test.result) + } + } +} + +const sep = filepath.Separator + +var slashtests = []PathTest{ + {"", ""}, + {"/", string(sep)}, + {"/a/b", string([]byte{sep, 'a', sep, 'b'})}, + {"a//b", string([]byte{'a', sep, sep, 'b'})}, +} + +func TestFromAndToSlash(t *testing.T) { + for _, test := range slashtests { + if s := filepath.FromSlash(test.path); s != test.result { + t.Errorf("FromSlash(%q) = %q, want %q", test.path, s, test.result) + } + if s := filepath.ToSlash(test.result); s != test.path { + t.Errorf("ToSlash(%q) = %q, want %q", test.result, s, test.path) + } + } +} + +type SplitListTest struct { + list string + result []string +} + +const lsep = filepath.ListSeparator + +var splitlisttests = []SplitListTest{ + {"", []string{}}, + {string([]byte{'a', lsep, 'b'}), []string{"a", "b"}}, + {string([]byte{lsep, 'a', lsep, 'b'}), []string{"", "a", "b"}}, +} + +func TestSplitList(t *testing.T) { + for _, test := range splitlisttests { + if l := filepath.SplitList(test.list); !reflect.DeepEqual(l, test.result) { + t.Errorf("SplitList(%q) = %s, want %s", test.list, l, test.result) + } + } +} + +type SplitTest struct { + path, dir, file string +} + +var unixsplittests = []SplitTest{ + {"a/b", "a/", "b"}, + {"a/b/", "a/b/", ""}, + {"a/", "a/", ""}, + {"a", "", "a"}, + {"/", "/", ""}, +} + +func TestSplit(t *testing.T) { + var splittests []SplitTest + splittests = unixsplittests + for _, test := range splittests { + if d, f := filepath.Split(test.path); d != test.dir || f != test.file { + t.Errorf("Split(%q) = %q, %q, want %q, %q", test.path, d, f, test.dir, test.file) + } + } +} + +type JoinTest struct { + elem []string + path string +} + +var jointests = []JoinTest{ + // zero parameters + {[]string{}, ""}, + + // one parameter + {[]string{""}, ""}, + {[]string{"a"}, "a"}, + + // two parameters + {[]string{"a", "b"}, "a/b"}, + {[]string{"a", ""}, "a"}, + {[]string{"", "b"}, "b"}, + {[]string{"/", "a"}, "/a"}, + {[]string{"/", ""}, "/"}, + {[]string{"a/", "b"}, "a/b"}, + {[]string{"a/", ""}, "a"}, + {[]string{"", ""}, ""}, +} + +// join takes a []string and passes it to Join. +func join(elem []string, args ...string) string { + args = elem + return filepath.Join(args...) +} + +func TestJoin(t *testing.T) { + for _, test := range jointests { + if p := join(test.elem); p != test.path { + t.Errorf("join(%q) = %q, want %q", test.elem, p, test.path) + } + } +} + +type ExtTest struct { + path, ext string +} + +var exttests = []ExtTest{ + {"path.go", ".go"}, + {"path.pb.go", ".go"}, + {"a.dir/b", ""}, + {"a.dir/b.go", ".go"}, + {"a.dir/", ""}, +} + +func TestExt(t *testing.T) { + for _, test := range exttests { + if x := filepath.Ext(test.path); x != test.ext { + t.Errorf("Ext(%q) = %q, want %q", test.path, x, test.ext) + } + } +} + +type Node struct { + name string + entries []*Node // nil if the entry is a file + mark int +} + +var tree = &Node{ + "testdata", + []*Node{ + &Node{"a", nil, 0}, + &Node{"b", []*Node{}, 0}, + &Node{"c", nil, 0}, + &Node{ + "d", + []*Node{ + &Node{"x", nil, 0}, + &Node{"y", []*Node{}, 0}, + &Node{ + "z", + []*Node{ + &Node{"u", nil, 0}, + &Node{"v", nil, 0}, + }, + 0, + }, + }, + 0, + }, + }, + 0, +} + +func walkTree(n *Node, path string, f func(path string, n *Node)) { + f(path, n) + for _, e := range n.entries { + walkTree(e, filepath.Join(path, e.name), f) + } +} + +func makeTree(t *testing.T) { + walkTree(tree, tree.name, func(path string, n *Node) { + if n.entries == nil { + fd, err := os.Open(path, os.O_CREAT, 0660) + if err != nil { + t.Errorf("makeTree: %v", err) + } + fd.Close() + } else { + os.Mkdir(path, 0770) + } + }) +} + +func markTree(n *Node) { walkTree(n, "", func(path string, n *Node) { n.mark++ }) } + +func checkMarks(t *testing.T) { + walkTree(tree, tree.name, func(path string, n *Node) { + if n.mark != 1 { + t.Errorf("node %s mark = %d; expected 1", path, n.mark) + } + n.mark = 0 + }) +} + +// Assumes that each node name is unique. Good enough for a test. +func mark(name string) { + walkTree(tree, tree.name, func(path string, n *Node) { + if n.name == name { + n.mark++ + } + }) +} + +type TestVisitor struct{} + +func (v *TestVisitor) VisitDir(path string, f *os.FileInfo) bool { + mark(f.Name) + return true +} + +func (v *TestVisitor) VisitFile(path string, f *os.FileInfo) { + mark(f.Name) +} + +func TestWalk(t *testing.T) { + makeTree(t) + + // 1) ignore error handling, expect none + v := &TestVisitor{} + filepath.Walk(tree.name, v, nil) + checkMarks(t) + + // 2) handle errors, expect none + errors := make(chan os.Error, 64) + filepath.Walk(tree.name, v, errors) + select { + case err := <-errors: + t.Errorf("no error expected, found: %s", err) + default: + // ok + } + checkMarks(t) + + if os.Getuid() != 0 { + // introduce 2 errors: chmod top-level directories to 0 + os.Chmod(filepath.Join(tree.name, tree.entries[1].name), 0) + os.Chmod(filepath.Join(tree.name, tree.entries[3].name), 0) + // mark respective subtrees manually + markTree(tree.entries[1]) + markTree(tree.entries[3]) + // correct double-marking of directory itself + tree.entries[1].mark-- + tree.entries[3].mark-- + + // 3) handle errors, expect two + errors = make(chan os.Error, 64) + os.Chmod(filepath.Join(tree.name, tree.entries[1].name), 0) + filepath.Walk(tree.name, v, errors) + Loop: + for i := 1; i <= 2; i++ { + select { + case <-errors: + // ok + default: + t.Errorf("%d. error expected, none found", i) + break Loop + } + } + select { + case err := <-errors: + t.Errorf("only two errors expected, found 3rd: %v", err) + default: + // ok + } + // the inaccessible subtrees were marked manually + checkMarks(t) + } + + // cleanup + os.Chmod(filepath.Join(tree.name, tree.entries[1].name), 0770) + os.Chmod(filepath.Join(tree.name, tree.entries[3].name), 0770) + if err := os.RemoveAll(tree.name); err != nil { + t.Errorf("removeTree: %v", err) + } +} + +var basetests = []PathTest{ + {"", "."}, + {".", "."}, + {"/.", "."}, + {"/", "/"}, + {"////", "/"}, + {"x/", "x"}, + {"abc", "abc"}, + {"abc/def", "def"}, + {"a/b/.x", ".x"}, + {"a/b/c.", "c."}, + {"a/b/c.x", "c.x"}, +} + +func TestBase(t *testing.T) { + for _, test := range basetests { + if s := filepath.Base(test.path); s != test.result { + t.Errorf("Base(%q) = %q, want %q", test.path, s, test.result) + } + } +} + +type IsAbsTest struct { + path string + isAbs bool +} + +var isAbsTests = []IsAbsTest{ + {"", false}, + {"/", true}, + {"/usr/bin/gcc", true}, + {"..", false}, + {"/a/../bb", true}, + {".", false}, + {"./", false}, + {"lala", false}, +} + +func TestIsAbs(t *testing.T) { + for _, test := range isAbsTests { + if r := filepath.IsAbs(test.path); r != test.isAbs { + t.Errorf("IsAbs(%q) = %v, want %v", test.path, r, test.isAbs) + } + } +} diff --git a/src/pkg/path/filepath/path_unix.go b/src/pkg/path/filepath/path_unix.go new file mode 100644 index 00000000000..7d07794e3fd --- /dev/null +++ b/src/pkg/path/filepath/path_unix.go @@ -0,0 +1,10 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package filepath + +const ( + Separator = '/' // OS-specific path separator + ListSeparator = ':' // OS-specific path list separator +) diff --git a/src/pkg/path/match.go b/src/pkg/path/match.go index dd3422c4256..efb8c5ce7fc 100644 --- a/src/pkg/path/match.go +++ b/src/pkg/path/match.go @@ -1,8 +1,11 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package path import ( "os" - "sort" "strings" "utf8" ) @@ -10,7 +13,7 @@ import ( var ErrBadPattern = os.NewError("syntax error in pattern") // Match returns true if name matches the shell file name pattern. -// The syntax used by pattern is: +// The pattern syntax is: // // pattern: // { term } @@ -75,7 +78,7 @@ Pattern: return len(name) == 0, nil } -// scanChunk gets the next section of pattern, which is a non-star string +// scanChunk gets the next segment of pattern, which is a non-star string // possibly preceded by a star. func scanChunk(pattern string) (star bool, chunk, rest string) { for len(pattern) > 0 && pattern[0] == '*' { @@ -92,7 +95,6 @@ Scan: if i+1 < len(pattern) { i++ } - continue case '[': inrange = true case ']': @@ -203,76 +205,3 @@ func getEsc(chunk string) (r int, nchunk string, err os.Error) { } return } - -// Glob returns the names of all files matching pattern or nil -// if there is no matching file. The syntax of patterns is the same -// as in Match. The pattern may describe hierarchical names such as -// /usr/*/bin/ed. -// -func Glob(pattern string) (matches []string) { - if !hasMeta(pattern) { - if _, err := os.Stat(pattern); err == nil { - return []string{pattern} - } - return nil - } - - dir, file := Split(pattern) - switch dir { - case "": - dir = "." - case "/": - // nothing - default: - dir = dir[0 : len(dir)-1] // chop off trailing '/' - } - - if hasMeta(dir) { - for _, d := range Glob(dir) { - matches = glob(d, file, matches) - } - } else { - return glob(dir, file, nil) - } - return matches -} - -// glob searches for files matching pattern in the directory dir -// and appends them to matches. -func glob(dir, pattern string, matches []string) []string { - fi, err := os.Stat(dir) - if err != nil { - return nil - } - if !fi.IsDirectory() { - return matches - } - d, err := os.Open(dir, os.O_RDONLY, 0666) - if err != nil { - return nil - } - defer d.Close() - - names, err := d.Readdirnames(-1) - if err != nil { - return nil - } - sort.SortStrings(names) - - for _, n := range names { - matched, err := Match(pattern, n) - if err != nil { - return matches - } - if matched { - matches = append(matches, Join(dir, n)) - } - } - return matches -} - -// hasMeta returns true if path contains any of the magic characters -// recognized by Match. -func hasMeta(path string) bool { - return strings.IndexAny(path, "*?[") != -1 -} diff --git a/src/pkg/path/match_test.go b/src/pkg/path/match_test.go index a1bf508e3f9..f377f1083b7 100644 --- a/src/pkg/path/match_test.go +++ b/src/pkg/path/match_test.go @@ -75,31 +75,3 @@ func TestMatch(t *testing.T) { } } } - -// contains returns true if vector contains the string s. -func contains(vector []string, s string) bool { - for _, elem := range vector { - if elem == s { - return true - } - } - return false -} - -var globTests = []struct { - pattern, result string -}{ - {"match.go", "match.go"}, - {"mat?h.go", "match.go"}, - {"*", "match.go"}, - {"../*/match.go", "../path/match.go"}, -} - -func TestGlob(t *testing.T) { - for _, tt := range globTests { - matches := Glob(tt.pattern) - if !contains(matches, tt.result) { - t.Errorf("Glob(%#q) = %#v want %v", tt.pattern, matches, tt.result) - } - } -} diff --git a/src/pkg/path/path.go b/src/pkg/path/path.go index 61eea88588b..658eec09387 100644 --- a/src/pkg/path/path.go +++ b/src/pkg/path/path.go @@ -7,8 +7,6 @@ package path import ( - "io/ioutil" - "os" "strings" ) @@ -107,7 +105,7 @@ func Clean(path string) string { // If there is no separator in path, Split returns an empty dir and // file set to path. func Split(path string) (dir, file string) { - i := strings.LastIndexAny(path, PathSeps) + i := strings.LastIndex(path, "/") return path[:i+1], path[i+1:] } @@ -135,78 +133,30 @@ func Ext(path string) string { return "" } -// Visitor methods are invoked for corresponding file tree entries -// visited by Walk. The parameter path is the full path of f relative -// to root. -type Visitor interface { - VisitDir(path string, f *os.FileInfo) bool - VisitFile(path string, f *os.FileInfo) -} - -func walk(path string, f *os.FileInfo, v Visitor, errors chan<- os.Error) { - if !f.IsDirectory() { - v.VisitFile(path, f) - return - } - - if !v.VisitDir(path, f) { - return // skip directory entries - } - - list, err := ioutil.ReadDir(path) - if err != nil { - if errors != nil { - errors <- err - } - } - - for _, e := range list { - walk(Join(path, e.Name), e, v, errors) - } -} - -// Walk walks the file tree rooted at root, calling v.VisitDir or -// v.VisitFile for each directory or file in the tree, including root. -// If v.VisitDir returns false, Walk skips the directory's entries; -// otherwise it invokes itself for each directory entry in sorted order. -// An error reading a directory does not abort the Walk. -// If errors != nil, Walk sends each directory read error -// to the channel. Otherwise Walk discards the error. -func Walk(root string, v Visitor, errors chan<- os.Error) { - f, err := os.Lstat(root) - if err != nil { - if errors != nil { - errors <- err - } - return // can't progress - } - walk(root, f, v, errors) -} - -// Base returns the last path element of the slash-separated name. -// Trailing slashes are removed before extracting the last element. If the name is -// empty, "." is returned. If it consists entirely of slashes, "/" is returned. -func Base(name string) string { - if name == "" { +// Base returns the last element of path. +// Trailing slashes are removed before extracting the last element. +// If the path is empty, Base returns ".". +// If the path consists entirely of slashes, Base returns "/". +func Base(path string) string { + if path == "" { return "." } // Strip trailing slashes. - for len(name) > 0 && name[len(name)-1] == '/' { - name = name[0 : len(name)-1] + for len(path) > 0 && path[len(path)-1] == '/' { + path = path[0 : len(path)-1] } // Find the last element - if i := strings.LastIndex(name, "/"); i >= 0 { - name = name[i+1:] + if i := strings.LastIndex(path, "/"); i >= 0 { + path = path[i+1:] } // If empty now, it had only slashes. - if name == "" { + if path == "" { return "/" } - return name + return path } // IsAbs returns true if the path is absolute. func IsAbs(path string) bool { - // TODO: Add Windows support - return strings.HasPrefix(path, "/") + return len(path) > 0 && path[0] == '/' } diff --git a/src/pkg/path/path_test.go b/src/pkg/path/path_test.go index ab0b48ad6ad..1fd57cc800e 100644 --- a/src/pkg/path/path_test.go +++ b/src/pkg/path/path_test.go @@ -5,8 +5,6 @@ package path import ( - "os" - "runtime" "testing" ) @@ -84,18 +82,7 @@ var splittests = []SplitTest{ {"/", "/", ""}, } -var winsplittests = []SplitTest{ - {`C:\Windows\System32`, `C:\Windows\`, `System32`}, - {`C:\Windows\`, `C:\Windows\`, ``}, - {`C:\Windows`, `C:\`, `Windows`}, - {`C:Windows`, `C:`, `Windows`}, - {`\\?\c:\`, `\\?\c:\`, ``}, -} - func TestSplit(t *testing.T) { - if runtime.GOOS == "windows" { - splittests = append(splittests, winsplittests...) - } for _, test := range splittests { if d, f := Split(test.path); d != test.dir || f != test.file { t.Errorf("Split(%q) = %q, %q, want %q, %q", test.path, d, f, test.dir, test.file) @@ -161,152 +148,6 @@ func TestExt(t *testing.T) { } } -type Node struct { - name string - entries []*Node // nil if the entry is a file - mark int -} - -var tree = &Node{ - "testdata", - []*Node{ - &Node{"a", nil, 0}, - &Node{"b", []*Node{}, 0}, - &Node{"c", nil, 0}, - &Node{ - "d", - []*Node{ - &Node{"x", nil, 0}, - &Node{"y", []*Node{}, 0}, - &Node{ - "z", - []*Node{ - &Node{"u", nil, 0}, - &Node{"v", nil, 0}, - }, - 0, - }, - }, - 0, - }, - }, - 0, -} - -func walkTree(n *Node, path string, f func(path string, n *Node)) { - f(path, n) - for _, e := range n.entries { - walkTree(e, Join(path, e.name), f) - } -} - -func makeTree(t *testing.T) { - walkTree(tree, tree.name, func(path string, n *Node) { - if n.entries == nil { - fd, err := os.Open(path, os.O_CREAT, 0660) - if err != nil { - t.Errorf("makeTree: %v", err) - } - fd.Close() - } else { - os.Mkdir(path, 0770) - } - }) -} - -func markTree(n *Node) { walkTree(n, "", func(path string, n *Node) { n.mark++ }) } - -func checkMarks(t *testing.T) { - walkTree(tree, tree.name, func(path string, n *Node) { - if n.mark != 1 { - t.Errorf("node %s mark = %d; expected 1", path, n.mark) - } - n.mark = 0 - }) -} - -// Assumes that each node name is unique. Good enough for a test. -func mark(name string) { - walkTree(tree, tree.name, func(path string, n *Node) { - if n.name == name { - n.mark++ - } - }) -} - -type TestVisitor struct{} - -func (v *TestVisitor) VisitDir(path string, f *os.FileInfo) bool { - mark(f.Name) - return true -} - -func (v *TestVisitor) VisitFile(path string, f *os.FileInfo) { - mark(f.Name) -} - -func TestWalk(t *testing.T) { - makeTree(t) - - // 1) ignore error handling, expect none - v := &TestVisitor{} - Walk(tree.name, v, nil) - checkMarks(t) - - // 2) handle errors, expect none - errors := make(chan os.Error, 64) - Walk(tree.name, v, errors) - select { - case err := <-errors: - t.Errorf("no error expected, found: %s", err) - default: - // ok - } - checkMarks(t) - - if os.Getuid() != 0 { - // introduce 2 errors: chmod top-level directories to 0 - os.Chmod(Join(tree.name, tree.entries[1].name), 0) - os.Chmod(Join(tree.name, tree.entries[3].name), 0) - // mark respective subtrees manually - markTree(tree.entries[1]) - markTree(tree.entries[3]) - // correct double-marking of directory itself - tree.entries[1].mark-- - tree.entries[3].mark-- - - // 3) handle errors, expect two - errors = make(chan os.Error, 64) - os.Chmod(Join(tree.name, tree.entries[1].name), 0) - Walk(tree.name, v, errors) - Loop: - for i := 1; i <= 2; i++ { - select { - case <-errors: - // ok - default: - t.Errorf("%d. error expected, none found", i) - break Loop - } - } - select { - case err := <-errors: - t.Errorf("only two errors expected, found 3rd: %v", err) - default: - // ok - } - // the inaccessible subtrees were marked manually - checkMarks(t) - } - - // cleanup - os.Chmod(Join(tree.name, tree.entries[1].name), 0770) - os.Chmod(Join(tree.name, tree.entries[3].name), 0770) - if err := os.RemoveAll(tree.name); err != nil { - t.Errorf("removeTree: %v", err) - } -} - var basetests = []CleanTest{ // Already clean {"", "."}, diff --git a/src/pkg/path/path_unix.go b/src/pkg/path/path_unix.go deleted file mode 100644 index 7e8c5eb8b9a..00000000000 --- a/src/pkg/path/path_unix.go +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright 2010 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package path - -const ( - DirSeps = `/` // directory separators - VolumeSeps = `` // volume separators - PathSeps = DirSeps + VolumeSeps // all path separators -) diff --git a/src/pkg/path/path_windows.go b/src/pkg/path/path_windows.go deleted file mode 100644 index 966eb49fb52..00000000000 --- a/src/pkg/path/path_windows.go +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright 2010 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package path - -const ( - DirSeps = `\/` // directory separators - VolumeSeps = `:` // volume separators - PathSeps = DirSeps + VolumeSeps // all path separators -)