diff options
author | Ondřej Surý <ondrej@sury.org> | 2011-09-13 13:13:40 +0200 |
---|---|---|
committer | Ondřej Surý <ondrej@sury.org> | 2011-09-13 13:13:40 +0200 |
commit | 5ff4c17907d5b19510a62e08fd8d3b11e62b431d (patch) | |
tree | c0650497e988f47be9c6f2324fa692a52dea82e1 /src/pkg/path | |
parent | 80f18fc933cf3f3e829c5455a1023d69f7b86e52 (diff) | |
download | golang-upstream/60.tar.gz |
Imported Upstream version 60upstream/60
Diffstat (limited to 'src/pkg/path')
-rw-r--r-- | src/pkg/path/Makefile | 14 | ||||
-rw-r--r-- | src/pkg/path/filepath/Makefile | 32 | ||||
-rw-r--r-- | src/pkg/path/filepath/match.go | 294 | ||||
-rw-r--r-- | src/pkg/path/filepath/match_test.go | 134 | ||||
-rw-r--r-- | src/pkg/path/filepath/path.go | 358 | ||||
-rw-r--r-- | src/pkg/path/filepath/path_plan9.go | 23 | ||||
-rw-r--r-- | src/pkg/path/filepath/path_test.go | 576 | ||||
-rw-r--r-- | src/pkg/path/filepath/path_unix.go | 23 | ||||
-rw-r--r-- | src/pkg/path/filepath/path_windows.go | 46 | ||||
-rw-r--r-- | src/pkg/path/match.go | 207 | ||||
-rw-r--r-- | src/pkg/path/match_test.go | 77 | ||||
-rw-r--r-- | src/pkg/path/path.go | 162 | ||||
-rw-r--r-- | src/pkg/path/path_test.go | 196 |
13 files changed, 2142 insertions, 0 deletions
diff --git a/src/pkg/path/Makefile b/src/pkg/path/Makefile new file mode 100644 index 000000000..fc3e2519c --- /dev/null +++ b/src/pkg/path/Makefile @@ -0,0 +1,14 @@ +# 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 +GOFILES=\ + match.go\ + path.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 000000000..af250ab33 --- /dev/null +++ b/src/pkg/path/filepath/Makefile @@ -0,0 +1,32 @@ +# 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_openbsd=\ + path_unix.go + +GOFILES_plan9=\ + path_plan9.go + +GOFILES_windows=\ + path_windows.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 000000000..7fcc214c0 --- /dev/null +++ b/src/pkg/path/filepath/match.go @@ -0,0 +1,294 @@ +// 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 occurs when the 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 + negated := chunk[0] == '^' + if negated { + 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 == negated { + 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 '/'). +// The only possible error return occurs when the pattern is malformed. +// +func Glob(pattern string) (matches []string, err os.Error) { + if !hasMeta(pattern) { + if _, err = os.Stat(pattern); err != nil { + return + } + return []string{pattern}, 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) { + return glob(dir, file, nil) + } + + 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. 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 + } + if !fi.IsDirectory() { + return + } + d, err := os.Open(dir) + if err != nil { + return + } + defer d.Close() + + names, err := d.Readdirnames(-1) + if err != nil { + return + } + sort.Strings(names) + + for _, n := range names { + matched, err := Match(pattern, n) + if err != nil { + return m, err + } + if matched { + m = append(m, Join(dir, n)) + } + } + return +} + +// 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 000000000..a1c8333f3 --- /dev/null +++ b/src/pkg/path/filepath/match_test.go @@ -0,0 +1,134 @@ +// 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" + "runtime" +) + +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, 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 errp(e os.Error) string { + if e == nil { + return "<nil>" + } + return e.String() +} + +func TestMatch(t *testing.T) { + if runtime.GOOS == "windows" { + // XXX: Don't pass for windows. + return + } + for _, tt := range matchTests { + ok, err := Match(tt.pattern, tt.s) + if ok != tt.match || err != tt.err { + t.Errorf("Match(%#q, %#q) = %v, %q want %v, %q", tt.pattern, tt.s, ok, errp(err), tt.match, errp(tt.err)) + } + } +} + +// 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 + } + } + 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) { + if runtime.GOOS == "windows" { + // XXX: Don't pass for windows. + return + } + for _, tt := range globTests { + 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 new file mode 100644 index 000000000..3d5b915c1 --- /dev/null +++ b/src/pkg/path/filepath/path.go @@ -0,0 +1,358 @@ +// 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 implements utility routines for manipulating filename paths +// in a way compatible with the target operating system-defined file paths. +package filepath + +import ( + "bytes" + "os" + "runtime" + "sort" + "strings" +) + +const ( + Separator = os.PathSeparator + ListSeparator = os.PathListSeparator +) + +// 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 { + vol := VolumeName(path) + path = path[len(vol):] + if path == "" { + return vol + "." + } + + rooted := os.IsPathSeparator(path[0]) + + // 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. + 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 os.IsPathSeparator(path[r]): + // empty path element + r++ + case path[r] == '.' && (r+1 == n || os.IsPathSeparator(path[r+1])): + // . element + r++ + case path[r] == '.' && path[r+1] == '.' && (r+2 == n || os.IsPathSeparator(path[r+2])): + // .. element: remove to last separator + r += 2 + switch { + case w > dotdot: + // can backtrack + w-- + for w > dotdot && !os.IsPathSeparator(buf[w]) { + 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 && !os.IsPathSeparator(path[r]); r++ { + buf[w] = path[r] + w++ + } + } + } + + // Turn empty string into "." + if w == 0 { + buf[w] = '.' + w++ + } + + return FromSlash(vol + 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)) +} + +// Split splits path immediately following the final Separator, +// separating it into a directory and file name component. +// 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 := len(path) - 1 + for i >= 0 && !os.IsPathSeparator(path[i]) { + i-- + } + 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 && !os.IsPathSeparator(path[i]); i-- { + if path[i] == '.' { + return path[i:] + } + } + 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) { + if runtime.GOOS == "windows" { + // Symlinks are not supported under windows. + _, err := os.Lstat(path) + if err != nil { + return "", err + } + return Clean(path), nil + } + 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 + string(Separator) + 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 Clean(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. +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) + 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 && os.IsPathSeparator(path[len(path)-1]) { + path = path[0 : len(path)-1] + } + // Find the last element + i := len(path) - 1 + for i >= 0 && !os.IsPathSeparator(path[i]) { + i-- + } + if i >= 0 { + path = path[i+1:] + } + // If empty now, it had only slashes. + if path == "" { + return string(Separator) + } + return path +} diff --git a/src/pkg/path/filepath/path_plan9.go b/src/pkg/path/filepath/path_plan9.go new file mode 100644 index 000000000..17b873f1a --- /dev/null +++ b/src/pkg/path/filepath/path_plan9.go @@ -0,0 +1,23 @@ +// 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" + +// 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 "" +} + +// HasPrefix tests whether the path p begins with prefix. +func HasPrefix(p, prefix string) bool { + return strings.HasPrefix(p, prefix) +} diff --git a/src/pkg/path/filepath/path_test.go b/src/pkg/path/filepath/path_test.go new file mode 100644 index 000000000..d2a10698e --- /dev/null +++ b/src/pkg/path/filepath/path_test.go @@ -0,0 +1,576 @@ +// 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" + "runtime" + "strings" + "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"}, +} + +var wincleantests = []PathTest{ + {`c:`, `c:.`}, + {`c:\`, `c:\`}, + {`c:\abc`, `c:\abc`}, + {`c:abc\..\..\.\.\..\def`, `c:..\..\def`}, + {`c:\abc\def\..\..`, `c:\`}, + {`c:..\abc`, `c:..\abc`}, + {`\`, `\`}, + {`/`, `\`}, +} + +func TestClean(t *testing.T) { + tests := cleantests + if runtime.GOOS == "windows" { + for i, _ := range tests { + tests[i].result = filepath.FromSlash(tests[i].result) + } + tests = append(tests, wincleantests...) + } + for _, test := range tests { + 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{"", ""}, ""}, +} + +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 + return filepath.Join(args...) +} + +func TestJoin(t *testing.T) { + if runtime.GOOS == "windows" { + jointests = append(jointests, winjointests...) + } + for _, test := range jointests { + if p := join(test.elem); p != filepath.FromSlash(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.Create(path) + 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) { + name = filepath.ToSlash(name) + 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) + + // Test permission errors. Only possible if we're not root + // and only on some file systems (AFS, FAT). To avoid errors during + // all.bash on those file systems, skip during gotest -short. + if os.Getuid() > 0 && !testing.Short() { + // 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.ToSlash(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}, +} + +var winisabstests = []IsAbsTest{ + {`C:\`, true}, + {`c\`, false}, + {`c::`, false}, + {`c:`, false}, + {`/`, false}, + {`\`, false}, + {`\Windows`, false}, + {`c:a\b`, false}, +} + +func TestIsAbs(t *testing.T) { + var tests []IsAbsTest + if runtime.GOOS == "windows" { + tests = append(tests, winisabstests...) + // All non-windows tests should fail, because they have no volume letter. + for _, test := range isabstests { + tests = append(tests, IsAbsTest{test.path, false}) + } + // All non-windows test should work as intended if prefixed with volume letter. + for _, test := range isabstests { + tests = append(tests, IsAbsTest{"c:" + test.path, test.isAbs}) + } + } else { + tests = isabstests + } + + for _, test := range tests { + 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"}, +} + +var EvalSymlinksAbsWindowsTests = []EvalSymlinksTest{ + {`c:\`, `c:\`}, +} + +func testEvalSymlinks(t *testing.T, tests []EvalSymlinksTest) { + for _, d := range tests { + if p, err := filepath.EvalSymlinks(d.path); err != nil { + t.Errorf("EvalSymlinks(%q) error: %v", d.path, err) + } else if filepath.Clean(p) != filepath.Clean(d.dest) { + t.Errorf("EvalSymlinks(%q)=%q, want %q", d.path, p, d.dest) + } + } +} + +func TestEvalSymlinks(t *testing.T) { + defer os.RemoveAll("test") + for _, d := range EvalSymlinksTestDirs { + var err os.Error + if d.dest == "" { + err = os.Mkdir(d.path, 0755) + } else { + if runtime.GOOS != "windows" { + err = os.Symlink(d.dest, d.path) + } + } + if err != nil { + t.Fatal(err) + } + } + var tests []EvalSymlinksTest + if runtime.GOOS == "windows" { + for _, d := range EvalSymlinksTests { + if d.path == d.dest { + // will test only real files and directories + tests = append(tests, d) + } + } + } else { + tests = EvalSymlinksTests + } + // relative + testEvalSymlinks(t, tests) + // 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 i, d := range tests { + tests[i].path = filepath.Join(testroot, d.path) + tests[i].dest = filepath.Join(testroot, d.dest) + } + if runtime.GOOS == "windows" { + for _, d := range EvalSymlinksAbsWindowsTests { + tests = append(tests, d) + } + } + testEvalSymlinks(t, tests) +} + +// Test paths relative to $GOROOT/src +var abstests = []string{ + "../AUTHORS", + "pkg/../../AUTHORS", + "Make.pkg", + "pkg/Makefile", + + // Already absolute + "$GOROOT/src/Make.pkg", + "$GOROOT/src/../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) + } + if filepath.IsAbs(path) && abspath != filepath.Clean(path) { + t.Errorf("Abs(%q)=%q, isn't clean", path, abspath) + } + } +} diff --git a/src/pkg/path/filepath/path_unix.go b/src/pkg/path/filepath/path_unix.go new file mode 100644 index 000000000..b2a4151c1 --- /dev/null +++ b/src/pkg/path/filepath/path_unix.go @@ -0,0 +1,23 @@ +// 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" + +// 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 "" +} + +// HasPrefix tests whether the path p begins with prefix. +func HasPrefix(p, prefix string) bool { + return strings.HasPrefix(p, prefix) +} diff --git a/src/pkg/path/filepath/path_windows.go b/src/pkg/path/filepath/path_windows.go new file mode 100644 index 000000000..2535697fd --- /dev/null +++ b/src/pkg/path/filepath/path_windows.go @@ -0,0 +1,46 @@ +// 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" + +// IsAbs returns true if the path is absolute. +func IsAbs(path string) (b bool) { + v := VolumeName(path) + if v == "" { + return false + } + path = path[len(v):] + if path == "" { + return false + } + return path[0] == '/' || path[0] == '\\' +} + +// VolumeName returns leading volume name. +// Given "C:\foo\bar" it returns "C:" under windows. +// On other platforms it returns "". +func VolumeName(path string) (v string) { + if len(path) < 2 { + return "" + } + // with drive letter + c := path[0] + if path[1] == ':' && + ('0' <= c && c <= '9' || 'a' <= c && c <= 'z' || + 'A' <= c && c <= 'Z') { + return path[:2] + } + return "" +} + +// HasPrefix tests whether the path p begins with prefix. +// It ignores case while comparing. +func HasPrefix(p, prefix string) bool { + if strings.HasPrefix(p, prefix) { + return true + } + return strings.HasPrefix(strings.ToLower(p), strings.ToLower(prefix)) +} diff --git a/src/pkg/path/match.go b/src/pkg/path/match.go new file mode 100644 index 000000000..efb8c5ce7 --- /dev/null +++ b/src/pkg/path/match.go @@ -0,0 +1,207 @@ +// 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" + "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-/ characters +// '?' matches any single non-/ 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, "/") < 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] != '/'; 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] == '/' { + 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 +} diff --git a/src/pkg/path/match_test.go b/src/pkg/path/match_test.go new file mode 100644 index 000000000..f377f1083 --- /dev/null +++ b/src/pkg/path/match_test.go @@ -0,0 +1,77 @@ +// 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 path + +import ( + "os" + "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, 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) { + for _, tt := range matchTests { + 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) + } + } +} diff --git a/src/pkg/path/path.go b/src/pkg/path/path.go new file mode 100644 index 000000000..235384667 --- /dev/null +++ b/src/pkg/path/path.go @@ -0,0 +1,162 @@ +// 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 path implements utility routines for manipulating slash-separated +// filename paths. +package path + +import ( + "strings" +) + +// 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 slashes with a single slash. +// 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. +// +// 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] == '/' + 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] == '/': + // empty path element + r++ + case path[r] == '.' && (r+1 == n || path[r+1] == '/'): + // . element + r++ + case path[r] == '.' && path[r+1] == '.' && (r+2 == n || path[r+2] == '/'): + // .. element: remove to last / + r += 2 + switch { + case w > dotdot: + // can backtrack + w-- + for w > dotdot && buf[w] != '/' { + w-- + } + case !rooted: + // cannot backtrack, but not rooted, so append .. element. + if w > 0 { + buf[w] = '/' + 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] = '/' + w++ + } + // copy element + for ; r < n && path[r] != '/'; r++ { + buf[w] = path[r] + w++ + } + } + } + + // Turn empty string into "." + if w == 0 { + buf[w] = '.' + w++ + } + + return string(buf[0:w]) +} + +// Split splits path immediately following the final path separator, +// separating it into a directory and file name component. +// 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.LastIndex(path, "/") + return path[:i+1], path[i+1:] +} + +// Join joins any number of path elements into a single path, adding a +// separating slash 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:], "/")) + } + } + return "" +} + +// Ext returns the file name extension used by path. +// The extension is the suffix beginning at the final dot +// in the final slash-separated 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] != '/'; i-- { + if path[i] == '.' { + return path[i:] + } + } + return "" +} + +// 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(path) > 0 && path[len(path)-1] == '/' { + path = path[0 : len(path)-1] + } + // Find the last element + if i := strings.LastIndex(path, "/"); i >= 0 { + path = path[i+1:] + } + // If empty now, it had only slashes. + if path == "" { + return "/" + } + return path +} + +// IsAbs returns true if the path is absolute. +func IsAbs(path string) bool { + return len(path) > 0 && path[0] == '/' +} diff --git a/src/pkg/path/path_test.go b/src/pkg/path/path_test.go new file mode 100644 index 000000000..1fd57cc80 --- /dev/null +++ b/src/pkg/path/path_test.go @@ -0,0 +1,196 @@ +// 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 path + +import ( + "testing" +) + +type CleanTest struct { + path, clean string +} + +var cleantests = []CleanTest{ + // 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 := Clean(test.path); s != test.clean { + t.Errorf("Clean(%q) = %q, want %q", test.path, s, test.clean) + } + } +} + +type SplitTest struct { + path, dir, file string +} + +var splittests = []SplitTest{ + {"a/b", "a/", "b"}, + {"a/b/", "a/b/", ""}, + {"a/", "a/", ""}, + {"a", "", "a"}, + {"/", "/", ""}, +} + +func TestSplit(t *testing.T) { + 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) + } + } +} + +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 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 := Ext(test.path); x != test.ext { + t.Errorf("Ext(%q) = %q, want %q", test.path, x, test.ext) + } + } +} + +var basetests = []CleanTest{ + // Already clean + {"", "."}, + {".", "."}, + {"/.", "."}, + {"/", "/"}, + {"////", "/"}, + {"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 := Base(test.path); s != test.clean { + t.Errorf("Base(%q) = %q, want %q", test.path, s, test.clean) + } + } +} + +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 := IsAbs(test.path); r != test.isAbs { + t.Errorf("IsAbs(%q) = %v, want %v", test.path, r, test.isAbs) + } + } +} |