diff options
Diffstat (limited to 'src/pkg/path/filepath')
-rw-r--r-- | src/pkg/path/filepath/Makefile | 5 | ||||
-rw-r--r-- | src/pkg/path/filepath/match.go | 55 | ||||
-rw-r--r-- | src/pkg/path/filepath/match_test.go | 47 | ||||
-rw-r--r-- | src/pkg/path/filepath/path.go | 128 | ||||
-rw-r--r-- | src/pkg/path/filepath/path_plan9.go | 28 | ||||
-rw-r--r-- | src/pkg/path/filepath/path_test.go | 147 | ||||
-rw-r--r-- | src/pkg/path/filepath/path_unix.go | 18 | ||||
-rw-r--r-- | src/pkg/path/filepath/path_windows.go | 37 |
8 files changed, 399 insertions, 66 deletions
diff --git a/src/pkg/path/filepath/Makefile b/src/pkg/path/filepath/Makefile index 2330fc09d..bc26a7d6a 100644 --- a/src/pkg/path/filepath/Makefile +++ b/src/pkg/path/filepath/Makefile @@ -18,8 +18,11 @@ GOFILES_darwin=\ GOFILES_linux=\ path_unix.go +GOFILES_plan9=\ + path_plan9.go + GOFILES_windows=\ - path_unix.go + path_windows.go GOFILES+=$(GOFILES_$(GOOS)) diff --git a/src/pkg/path/filepath/match.go b/src/pkg/path/filepath/match.go index ad4053fa2..a05bb5f7e 100644 --- a/src/pkg/path/filepath/match.go +++ b/src/pkg/path/filepath/match.go @@ -32,7 +32,7 @@ var ErrBadPattern = os.NewError("syntax error in pattern") // 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. +// The only possible error return occurs when the pattern is malformed. // func Match(pattern, name string) (matched bool, err os.Error) { Pattern: @@ -211,13 +211,14 @@ func getEsc(chunk string) (r int, nchunk string, err os.Error) { // 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 '/'). +// The only possible error return occurs when the pattern is malformed. // -func Glob(pattern string) (matches []string) { +func Glob(pattern string) (matches []string, err os.Error) { if !hasMeta(pattern) { - if _, err := os.Stat(pattern); err == nil { - return []string{pattern} + if _, err = os.Stat(pattern); err != nil { + return } - return nil + return []string{pattern}, nil } dir, file := Split(pattern) @@ -230,48 +231,60 @@ func Glob(pattern string) (matches []string) { dir = dir[0 : len(dir)-1] // chop off trailing separator } - if hasMeta(dir) { - for _, d := range Glob(dir) { - matches = glob(d, file, matches) - } - } else { + if !hasMeta(dir) { return glob(dir, file, nil) } - return matches + + var m []string + m, err = Glob(dir) + if err != nil { + return + } + for _, d := range m { + matches, err = glob(d, file, matches) + if err != nil { + return + } + } + return } // glob searches for files matching pattern in the directory dir -// and appends them to matches. -func glob(dir, pattern string, matches []string) []string { +// and appends them to matches. If the directory cannot be +// opened, it returns the existing matches. New matches are +// added in lexicographical order. +// The only possible error return occurs when the pattern is malformed. +func glob(dir, pattern string, matches []string) (m []string, e os.Error) { + m = matches fi, err := os.Stat(dir) if err != nil { - return nil + return } if !fi.IsDirectory() { - return matches + return } - d, err := os.Open(dir, os.O_RDONLY, 0666) + d, err := os.Open(dir) if err != nil { - return nil + return } defer d.Close() names, err := d.Readdirnames(-1) if err != nil { - return nil + return } sort.SortStrings(names) for _, n := range names { matched, err := Match(pattern, n) if err != nil { - return matches + return m, err } if matched { - matches = append(matches, Join(dir, n)) + m = append(m, Join(dir, n)) } } - return matches + return } // hasMeta returns true if path contains any of the magic characters diff --git a/src/pkg/path/filepath/match_test.go b/src/pkg/path/filepath/match_test.go index ad0c90b75..43e1c1cc2 100644 --- a/src/pkg/path/filepath/match_test.go +++ b/src/pkg/path/filepath/match_test.go @@ -6,8 +6,9 @@ package filepath_test import ( "os" - "path/filepath" + . "path/filepath" "testing" + "runtime" ) type MatchTest struct { @@ -55,22 +56,26 @@ var matchTests = []MatchTest{ {"[\\-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}, + {"[]a]", "]", false, ErrBadPattern}, + {"[-]", "-", false, ErrBadPattern}, + {"[x-]", "x", false, ErrBadPattern}, + {"[x-]", "-", false, ErrBadPattern}, + {"[x-]", "z", false, ErrBadPattern}, + {"[-x]", "x", false, ErrBadPattern}, + {"[-x]", "-", false, ErrBadPattern}, + {"[-x]", "a", false, ErrBadPattern}, + {"\\", "a", false, ErrBadPattern}, + {"[a-b-c]", "a", false, ErrBadPattern}, {"*x", "xxx", true, nil}, } func TestMatch(t *testing.T) { + if runtime.GOOS == "windows" { + // XXX: Don't pass for windows. + return + } for _, tt := range matchTests { - ok, err := filepath.Match(tt.pattern, tt.s) + ok, err := 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) } @@ -79,6 +84,7 @@ func TestMatch(t *testing.T) { // contains returns true if vector contains the string s. func contains(vector []string, s string) bool { + s = ToSlash(s) for _, elem := range vector { if elem == s { return true @@ -97,10 +103,25 @@ var globTests = []struct { } func TestGlob(t *testing.T) { + if runtime.GOOS == "windows" { + // XXX: Don't pass for windows. + return + } for _, tt := range globTests { - matches := filepath.Glob(tt.pattern) + matches, err := Glob(tt.pattern) + if err != nil { + t.Errorf("Glob error for %q: %s", tt.pattern, err) + continue + } if !contains(matches, tt.result) { t.Errorf("Glob(%#q) = %#v want %v", tt.pattern, matches, tt.result) } } } + +func TestGlobError(t *testing.T) { + _, err := Glob("[7]") + if err != nil { + t.Error("expected error for bad pattern; got none") + } +} diff --git a/src/pkg/path/filepath/path.go b/src/pkg/path/filepath/path.go index 414df7d20..de673a725 100644 --- a/src/pkg/path/filepath/path.go +++ b/src/pkg/path/filepath/path.go @@ -8,12 +8,16 @@ package filepath import ( + "bytes" "os" "sort" "strings" ) -// BUG(niemeyer): Package filepath does not yet work on Windows. +const ( + SeparatorString = string(Separator) + ListSeparatorString = string(ListSeparator) +) // Clean returns the shortest path name equivalent to path // by purely lexical processing. It applies the following rules @@ -38,36 +42,39 @@ func Clean(path string) string { return "." } - rooted := path[0] == Separator - n := len(path) + rooted := IsAbs(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. + prefix := volumeName(path) + path = path[len(prefix):] + n := len(path) buf := []byte(path) r, w, dotdot := 0, 0, 0 if rooted { + buf[0] = Separator r, w, dotdot = 1, 1, 1 } for r < n { switch { - case path[r] == Separator: + case isSeparator(path[r]): // empty path element r++ - case path[r] == '.' && (r+1 == n || path[r+1] == Separator): + case path[r] == '.' && (r+1 == n || isSeparator(path[r+1])): // . element r++ - case path[r] == '.' && path[r+1] == '.' && (r+2 == n || path[r+2] == Separator): + case path[r] == '.' && path[r+1] == '.' && (r+2 == n || isSeparator(path[r+2])): // .. element: remove to last separator r += 2 switch { case w > dotdot: // can backtrack w-- - for w > dotdot && buf[w] != Separator { + for w > dotdot && !isSeparator(buf[w]) { w-- } case !rooted: @@ -90,7 +97,7 @@ func Clean(path string) string { w++ } // copy element - for ; r < n && path[r] != Separator; r++ { + for ; r < n && !isSeparator(path[r]); r++ { buf[w] = path[r] w++ } @@ -103,7 +110,7 @@ func Clean(path string) string { w++ } - return string(buf[0:w]) + return prefix + string(buf[0:w]) } // ToSlash returns the result of replacing each separator character @@ -112,7 +119,7 @@ func ToSlash(path string) string { if Separator == '/' { return path } - return strings.Replace(path, string(Separator), "/", -1) + return strings.Replace(path, SeparatorString, "/", -1) } // FromSlash returns the result of replacing each slash ('/') character @@ -121,7 +128,7 @@ func FromSlash(path string) string { if Separator == '/' { return path } - return strings.Replace(path, "/", string(Separator), -1) + return strings.Replace(path, "/", SeparatorString, -1) } // SplitList splits a list of paths joined by the OS-specific ListSeparator. @@ -129,7 +136,7 @@ func SplitList(path string) []string { if path == "" { return []string{} } - return strings.Split(path, string(ListSeparator), -1) + return strings.Split(path, ListSeparatorString, -1) } // Split splits path immediately following the final Separator, @@ -137,7 +144,10 @@ func SplitList(path string) []string { // 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)) + i := len(path) - 1 + for i >= 0 && !isSeparator(path[i]) { + i-- + } return path[:i+1], path[i+1:] } @@ -146,7 +156,7 @@ func Split(path string) (dir, file string) { func Join(elem ...string) string { for i, e := range elem { if e != "" { - return Clean(strings.Join(elem[i:], string(Separator))) + return Clean(strings.Join(elem[i:], SeparatorString)) } } return "" @@ -157,7 +167,7 @@ func Join(elem ...string) string { // 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-- { + for i := len(path) - 1; i >= 0 && !isSeparator(path[i]); i-- { if path[i] == '.' { return path[i:] } @@ -165,6 +175,77 @@ func Ext(path string) string { return "" } +// EvalSymlinks returns the path name after the evaluation of any symbolic +// links. +// If path is relative it will be evaluated relative to the current directory. +func EvalSymlinks(path string) (string, os.Error) { + const maxIter = 255 + originalPath := path + // consume path by taking each frontmost path element, + // expanding it if it's a symlink, and appending it to b + var b bytes.Buffer + for n := 0; path != ""; n++ { + if n > maxIter { + return "", os.NewError("EvalSymlinks: too many links in " + originalPath) + } + + // find next path component, p + i := strings.IndexRune(path, Separator) + var p string + if i == -1 { + p, path = path, "" + } else { + p, path = path[:i], path[i+1:] + } + + if p == "" { + if b.Len() == 0 { + // must be absolute path + b.WriteRune(Separator) + } + continue + } + + fi, err := os.Lstat(b.String() + p) + if err != nil { + return "", err + } + if !fi.IsSymlink() { + b.WriteString(p) + if path != "" { + b.WriteRune(Separator) + } + continue + } + + // it's a symlink, put it at the front of path + dest, err := os.Readlink(b.String() + p) + if err != nil { + return "", err + } + if IsAbs(dest) { + b.Reset() + } + path = dest + SeparatorString + path + } + return Clean(b.String()), nil +} + +// Abs returns an absolute representation of path. +// If the path is not absolute it will be joined with the current +// working directory to turn it into an absolute path. The absolute +// path name for a given file is not guaranteed to be unique. +func Abs(path string) (string, os.Error) { + if IsAbs(path) { + return path, nil + } + wd, err := os.Getwd() + if err != nil { + return "", err + } + return Join(wd, path), nil +} + // Visitor methods are invoked for corresponding file tree entries // visited by Walk. The parameter path is the full path of f relative // to root. @@ -199,7 +280,7 @@ func walk(path string, f *os.FileInfo, v Visitor, errors chan<- os.Error) { // 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) + f, err := os.Open(dirname) if err != nil { return nil, err } @@ -250,21 +331,20 @@ func Base(path string) string { return "." } // Strip trailing slashes. - for len(path) > 0 && path[len(path)-1] == Separator { + for len(path) > 0 && isSeparator(path[len(path)-1]) { path = path[0 : len(path)-1] } // Find the last element - if i := strings.LastIndex(path, string(Separator)); i >= 0 { + i := len(path) - 1 + for i >= 0 && !isSeparator(path[i]) { + i-- + } + if i >= 0 { path = path[i+1:] } // If empty now, it had only slashes. if path == "" { - return string(Separator) + return SeparatorString } 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_plan9.go b/src/pkg/path/filepath/path_plan9.go new file mode 100644 index 000000000..e40008364 --- /dev/null +++ b/src/pkg/path/filepath/path_plan9.go @@ -0,0 +1,28 @@ +// 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 "strings" + +const ( + Separator = '/' // OS-specific path separator + ListSeparator = 0 // OS-specific path list separator +) + +// isSeparator returns true if c is a directory separator character. +func isSeparator(c uint8) bool { + return Separator == c +} + +// IsAbs returns true if the path is absolute. +func IsAbs(path string) bool { + return strings.HasPrefix(path, "/") || strings.HasPrefix(path, "#") +} + +// volumeName returns the leading volume name on Windows. +// It returns "" elsewhere +func volumeName(path string) string { + return "" +} diff --git a/src/pkg/path/filepath/path_test.go b/src/pkg/path/filepath/path_test.go index 8f887f00b..b3b6eb5ab 100644 --- a/src/pkg/path/filepath/path_test.go +++ b/src/pkg/path/filepath/path_test.go @@ -9,6 +9,7 @@ import ( "path/filepath" "reflect" "runtime" + "strings" "testing" ) @@ -68,7 +69,7 @@ var cleantests = []PathTest{ func TestClean(t *testing.T) { for _, test := range cleantests { - if s := filepath.Clean(test.path); s != test.result { + if s := filepath.ToSlash(filepath.Clean(test.path)); s != test.result { t.Errorf("Clean(%q) = %q, want %q", test.path, s, test.result) } } @@ -161,6 +162,14 @@ var jointests = []JoinTest{ {[]string{"", ""}, ""}, } +var winjointests = []JoinTest{ + {[]string{`directory`, `file`}, `directory\file`}, + {[]string{`C:\Windows\`, `System32`}, `C:\Windows\System32`}, + {[]string{`C:\Windows\`, ``}, `C:\Windows`}, + {[]string{`C:\`, `Windows`}, `C:\Windows`}, + {[]string{`C:`, `Windows`}, `C:\Windows`}, +} + // join takes a []string and passes it to Join. func join(elem []string, args ...string) string { args = elem @@ -168,8 +177,11 @@ func join(elem []string, args ...string) string { } func TestJoin(t *testing.T) { + if runtime.GOOS == "windows" { + jointests = append(jointests, winjointests...) + } for _, test := range jointests { - if p := join(test.elem); p != test.path { + if p := join(test.elem); p != filepath.FromSlash(test.path) { t.Errorf("join(%q) = %q, want %q", test.elem, p, test.path) } } @@ -237,7 +249,7 @@ func walkTree(n *Node, path string, f func(path string, n *Node)) { 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) + fd, err := os.Create(path) if err != nil { t.Errorf("makeTree: %v", err) } @@ -261,6 +273,7 @@ func checkMarks(t *testing.T) { // Assumes that each node name is unique. Good enough for a test. func mark(name string) { + name = filepath.ToSlash(name) walkTree(tree, tree.name, func(path string, n *Node) { if n.name == name { n.mark++ @@ -302,7 +315,7 @@ func TestWalk(t *testing.T) { } checkMarks(t) - if os.Getuid() != 0 { + 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) @@ -361,7 +374,7 @@ var basetests = []PathTest{ func TestBase(t *testing.T) { for _, test := range basetests { - if s := filepath.Base(test.path); s != test.result { + if s := filepath.ToSlash(filepath.Base(test.path)); s != test.result { t.Errorf("Base(%q) = %q, want %q", test.path, s, test.result) } } @@ -372,7 +385,7 @@ type IsAbsTest struct { isAbs bool } -var isAbsTests = []IsAbsTest{ +var isabstests = []IsAbsTest{ {"", false}, {"/", true}, {"/usr/bin/gcc", true}, @@ -383,10 +396,130 @@ var isAbsTests = []IsAbsTest{ {"lala", false}, } +var winisabstests = []IsAbsTest{ + {`C:\`, true}, + {`c\`, false}, + {`c::`, false}, + {`/`, true}, + {`\`, true}, + {`\Windows`, true}, +} + func TestIsAbs(t *testing.T) { - for _, test := range isAbsTests { + if runtime.GOOS == "windows" { + isabstests = append(isabstests, winisabstests...) + } + 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) } } } + +type EvalSymlinksTest struct { + path, dest string +} + +var EvalSymlinksTestDirs = []EvalSymlinksTest{ + {"test", ""}, + {"test/dir", ""}, + {"test/dir/link3", "../../"}, + {"test/link1", "../test"}, + {"test/link2", "dir"}, +} + +var EvalSymlinksTests = []EvalSymlinksTest{ + {"test", "test"}, + {"test/dir", "test/dir"}, + {"test/dir/../..", "."}, + {"test/link1", "test"}, + {"test/link2", "test/dir"}, + {"test/link1/dir", "test/dir"}, + {"test/link2/..", "test"}, + {"test/dir/link3", "."}, + {"test/link2/link3/test", "test"}, +} + +func TestEvalSymlinks(t *testing.T) { + // Symlinks are not supported under windows. + if runtime.GOOS == "windows" { + return + } + defer os.RemoveAll("test") + for _, d := range EvalSymlinksTestDirs { + var err os.Error + if d.dest == "" { + err = os.Mkdir(d.path, 0755) + } else { + err = os.Symlink(d.dest, d.path) + } + if err != nil { + t.Fatal(err) + } + } + // relative + for _, d := range EvalSymlinksTests { + if p, err := filepath.EvalSymlinks(d.path); err != nil { + t.Errorf("EvalSymlinks(%q) error: %v", d.path, err) + } else if p != d.dest { + t.Errorf("EvalSymlinks(%q)=%q, want %q", d.path, p, d.dest) + } + } + // absolute + goroot, err := filepath.EvalSymlinks(os.Getenv("GOROOT")) + if err != nil { + t.Fatalf("EvalSymlinks(%q) error: %v", os.Getenv("GOROOT"), err) + } + testroot := filepath.Join(goroot, "src", "pkg", "path", "filepath") + for _, d := range EvalSymlinksTests { + a := EvalSymlinksTest{ + filepath.Join(testroot, d.path), + filepath.Join(testroot, d.dest), + } + if p, err := filepath.EvalSymlinks(a.path); err != nil { + t.Errorf("EvalSymlinks(%q) error: %v", a.path, err) + } else if p != a.dest { + t.Errorf("EvalSymlinks(%q)=%q, want %q", a.path, p, a.dest) + } + } +} + +// Test paths relative to $GOROOT/src +var abstests = []string{ + "../AUTHORS", + "pkg/../../AUTHORS", + "Make.pkg", + "pkg/Makefile", + + // Already absolute + "$GOROOT/src/Make.pkg", +} + +func TestAbs(t *testing.T) { + oldwd, err := os.Getwd() + if err != nil { + t.Fatal("Getwd failed: " + err.String()) + } + defer os.Chdir(oldwd) + goroot := os.Getenv("GOROOT") + cwd := filepath.Join(goroot, "src") + os.Chdir(cwd) + for _, path := range abstests { + path = strings.Replace(path, "$GOROOT", goroot, -1) + abspath, err := filepath.Abs(path) + if err != nil { + t.Errorf("Abs(%q) error: %v", path, err) + } + info, err := os.Stat(path) + if err != nil { + t.Errorf("%s: %s", path, err) + } + absinfo, err := os.Stat(abspath) + if err != nil || absinfo.Ino != info.Ino { + t.Errorf("Abs(%q)=%q, not the same file", path, abspath) + } + if !filepath.IsAbs(abspath) { + t.Errorf("Abs(%q)=%q, not an absolute path", path, abspath) + } + } +} diff --git a/src/pkg/path/filepath/path_unix.go b/src/pkg/path/filepath/path_unix.go index 7d07794e3..f8ac248fb 100644 --- a/src/pkg/path/filepath/path_unix.go +++ b/src/pkg/path/filepath/path_unix.go @@ -4,7 +4,25 @@ package filepath +import "strings" + const ( Separator = '/' // OS-specific path separator ListSeparator = ':' // OS-specific path list separator ) + +// isSeparator returns true if c is a directory separator character. +func isSeparator(c uint8) bool { + return Separator == c +} + +// IsAbs returns true if the path is absolute. +func IsAbs(path string) bool { + return strings.HasPrefix(path, "/") +} + +// volumeName returns the leading volume name on Windows. +// It returns "" elsewhere. +func volumeName(path string) string { + return "" +} diff --git a/src/pkg/path/filepath/path_windows.go b/src/pkg/path/filepath/path_windows.go new file mode 100644 index 000000000..dbd1c1e40 --- /dev/null +++ b/src/pkg/path/filepath/path_windows.go @@ -0,0 +1,37 @@ +// 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 +) + +// isSeparator returns true if c is a directory separator character. +func isSeparator(c uint8) bool { + // NOTE: Windows accept / as path separator. + return c == '\\' || c == '/' +} + +// IsAbs returns true if the path is absolute. +func IsAbs(path string) bool { + return path != "" && (volumeName(path) != "" || isSeparator(path[0])) +} + +// volumeName return leading volume name. +// If given "C:\foo\bar", return "C:" on windows. +func volumeName(path string) string { + if path == "" { + return "" + } + // with drive letter + c := path[0] + if len(path) > 2 && path[1] == ':' && isSeparator(path[2]) && + ('0' <= c && c <= '9' || 'a' <= c && c <= 'z' || + 'A' <= c && c <= 'Z') { + return path[0:2] + } + return "" +} |