diff options
Diffstat (limited to 'src/pkg/path/filepath/path.go')
| -rw-r--r-- | src/pkg/path/filepath/path.go | 289 |
1 files changed, 172 insertions, 117 deletions
diff --git a/src/pkg/path/filepath/path.go b/src/pkg/path/filepath/path.go index 3d5b915c1..1e7487263 100644 --- a/src/pkg/path/filepath/path.go +++ b/src/pkg/path/filepath/path.go @@ -7,9 +7,8 @@ package filepath import ( - "bytes" + "errors" "os" - "runtime" "sort" "strings" ) @@ -35,15 +34,18 @@ const ( // returns the string ".". // // See also Rob Pike, ``Lexical File Names in Plan 9 or -// Getting Dot-Dot right,'' +// 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 == "" { + if len(vol) > 1 && vol[1] != ':' { + // should be UNC + return FromSlash(vol) + } return vol + "." } - rooted := os.IsPathSeparator(path[0]) // Invariants: @@ -114,7 +116,8 @@ func Clean(path string) string { } // ToSlash returns the result of replacing each separator character -// in path with a slash ('/') character. +// in path with a slash ('/') character. Multiple separators are +// replaced by multiple slashes. func ToSlash(path string) string { if Separator == '/' { return path @@ -123,7 +126,8 @@ func ToSlash(path string) string { } // FromSlash returns the result of replacing each slash ('/') character -// in path with a separator character. +// in path with a separator character. Multiple slashes are replaced +// by multiple separators. func FromSlash(path string) string { if Separator == '/' { return path @@ -131,7 +135,9 @@ func FromSlash(path string) string { return strings.Replace(path, "/", string(Separator), -1) } -// SplitList splits a list of paths joined by the OS-specific ListSeparator. +// SplitList splits a list of paths joined by the OS-specific ListSeparator, +// usually found in PATH or GOPATH environment variables. +// Unlike strings.Split, SplitList returns an empty slice when passed an empty string. func SplitList(path string) []string { if path == "" { return []string{} @@ -143,16 +149,19 @@ func SplitList(path string) []string { // 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. +// The returned values have the property that path = dir+file. func Split(path string) (dir, file string) { + vol := VolumeName(path) i := len(path) - 1 - for i >= 0 && !os.IsPathSeparator(path[i]) { + for i >= len(vol) && !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. +// a Separator if necessary. The result is Cleaned, in particular +// all empty strings are ignored. func Join(elem ...string) string { for i, e := range elem { if e != "" { @@ -177,73 +186,17 @@ func Ext(path string) string { // 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 +// If path is relative the result will be relative to the current directory, +// unless one of the components is an absolute symbolic link. +func EvalSymlinks(path string) (string, error) { + return evalSymlinks(path) } // 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) { +func Abs(path string) (string, error) { if IsAbs(path) { return Clean(path), nil } @@ -254,40 +207,140 @@ func Abs(path string) (string, os.Error) { 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) +// Rel returns a relative path that is lexically equivalent to targpath when +// joined to basepath with an intervening separator. That is, +// Join(basepath, Rel(basepath, targpath)) is equivalent to targpath itself. +// On success, the returned path will always be relative to basepath, +// even if basepath and targpath share no elements. +// An error is returned if targpath can't be made relative to basepath or if +// knowing the current working directory would be necessary to compute it. +func Rel(basepath, targpath string) (string, error) { + baseVol := VolumeName(basepath) + targVol := VolumeName(targpath) + base := Clean(basepath) + targ := Clean(targpath) + if targ == base { + return ".", nil + } + base = base[len(baseVol):] + targ = targ[len(targVol):] + if base == "." { + base = "" + } + // Can't use IsAbs - `\a` and `a` are both relative in Windows. + baseSlashed := len(base) > 0 && base[0] == Separator + targSlashed := len(targ) > 0 && targ[0] == Separator + if baseSlashed != targSlashed || baseVol != targVol { + return "", errors.New("Rel: can't make " + targ + " relative to " + base) + } + // Position base[b0:bi] and targ[t0:ti] at the first differing elements. + bl := len(base) + tl := len(targ) + var b0, bi, t0, ti int + for { + for bi < bl && base[bi] != Separator { + bi++ + } + for ti < tl && targ[ti] != Separator { + ti++ + } + if targ[t0:ti] != base[b0:bi] { + break + } + if bi < bl { + bi++ + } + if ti < tl { + ti++ + } + b0 = bi + t0 = ti + } + if base[b0:bi] == ".." { + return "", errors.New("Rel: can't make " + targ + " relative to " + base) + } + if b0 != bl { + // Base elements left. Must go up before going down. + seps := strings.Count(base[b0:bl], string(Separator)) + size := 2 + seps*3 + if tl != t0 { + size += 1 + tl - t0 + } + buf := make([]byte, size) + n := copy(buf, "..") + for i := 0; i < seps; i++ { + buf[n] = Separator + copy(buf[n+1:], "..") + n += 3 + } + if t0 != tl { + buf[n] = Separator + copy(buf[n+1:], targ[t0:]) + } + return string(buf), nil + } + return targ[t0:], nil } -func walk(path string, f *os.FileInfo, v Visitor, errors chan<- os.Error) { - if !f.IsDirectory() { - v.VisitFile(path, f) - return +// SkipDir is used as a return value from WalkFuncs to indicate that +// the directory named in the call is to be skipped. It is not returned +// as an error by any function. +var SkipDir = errors.New("skip this directory") + +// WalkFunc is the type of the function called for each file or directory +// visited by Walk. If there was a problem walking to the file or directory +// named by path, the incoming error will describe the problem and the +// function can decide how to handle that error (and Walk will not descend +// into that directory). If an error is returned, processing stops. The +// sole exception is that if path is a directory and the function returns the +// special value SkipDir, the contents of the directory are skipped +// and processing continues as usual on the next file. +type WalkFunc func(path string, info os.FileInfo, err error) error + +// walk recursively descends path, calling w. +func walk(path string, info os.FileInfo, walkFn WalkFunc) error { + err := walkFn(path, info, nil) + if err != nil { + if info.IsDir() && err == SkipDir { + return nil + } + return err } - if !v.VisitDir(path, f) { - return // skip directory entries + if !info.IsDir() { + return nil } list, err := readDir(path) if err != nil { - if errors != nil { - errors <- err + return walkFn(path, info, err) + } + + for _, fileInfo := range list { + if err = walk(Join(path, fileInfo.Name()), fileInfo, walkFn); err != nil { + return err } } + return nil +} - for _, e := range list { - walk(Join(path, e.Name), e, v, errors) +// Walk walks the file tree rooted at root, calling walkFn for each file or +// directory in the tree, including root. All errors that arise visiting files +// and directories are filtered by walkFn. The files are walked in lexical +// order, which makes the output deterministic but means that for very +// large directories Walk can be inefficient. +func Walk(root string, walkFn WalkFunc) error { + info, err := os.Lstat(root) + if err != nil { + return walkFn(root, nil, err) } + return walk(root, info, walkFn) } // readDir reads the directory named by dirname and returns -// a list of sorted directory entries. +// a sorted list of directory entries. // Copied from io/ioutil to avoid the circular import. -func readDir(dirname string) ([]*os.FileInfo, os.Error) { +func readDir(dirname string) ([]os.FileInfo, error) { f, err := os.Open(dirname) if err != nil { return nil, err @@ -297,38 +350,16 @@ func readDir(dirname string) ([]*os.FileInfo, os.Error) { 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 + sort.Sort(byName(list)) + return list, nil } -// A dirList implements sort.Interface. -type fileInfoList []*os.FileInfo +// byName implements sort.Interface. +type byName []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) -} +func (f byName) Len() int { return len(f) } +func (f byName) Less(i, j int) bool { return f[i].Name() < f[j].Name() } +func (f byName) Swap(i, j int) { f[i], f[j] = f[j], f[i] } // Base returns the last element of path. // Trailing path separators are removed before extracting the last element. @@ -342,6 +373,8 @@ func Base(path string) string { for len(path) > 0 && os.IsPathSeparator(path[len(path)-1]) { path = path[0 : len(path)-1] } + // Throw away volume name + path = path[len(VolumeName(path)):] // Find the last element i := len(path) - 1 for i >= 0 && !os.IsPathSeparator(path[i]) { @@ -356,3 +389,25 @@ func Base(path string) string { } return path } + +// Dir returns all but the last element of path, typically the path's directory. +// Trailing path separators are removed before processing. +// If the path is empty, Dir returns ".". +// If the path consists entirely of separators, Dir returns a single separator. +// The returned path does not end in a separator unless it is the root directory. +func Dir(path string) string { + vol := VolumeName(path) + i := len(path) - 1 + for i >= len(vol) && !os.IsPathSeparator(path[i]) { + i-- + } + dir := Clean(path[len(vol) : i+1]) + last := len(dir) - 1 + if last > 0 && os.IsPathSeparator(dir[last]) { + dir = dir[:last] + } + if dir == "" { + dir = "." + } + return vol + dir +} |
