summaryrefslogtreecommitdiff
path: root/src/pkg/path
diff options
context:
space:
mode:
Diffstat (limited to 'src/pkg/path')
-rw-r--r--src/pkg/path/filepath/example_unix_test.go39
-rw-r--r--src/pkg/path/filepath/match_test.go3
-rw-r--r--src/pkg/path/filepath/path.go130
-rw-r--r--src/pkg/path/filepath/path_plan9.go15
-rw-r--r--src/pkg/path/filepath/path_test.go52
-rw-r--r--src/pkg/path/filepath/path_unix.go15
-rw-r--r--src/pkg/path/filepath/path_windows.go55
-rw-r--r--src/pkg/path/filepath/path_windows_test.go89
-rw-r--r--src/pkg/path/filepath/symlink_windows.go10
-rw-r--r--src/pkg/path/path.go83
-rw-r--r--src/pkg/path/path_test.go11
11 files changed, 407 insertions, 95 deletions
diff --git a/src/pkg/path/filepath/example_unix_test.go b/src/pkg/path/filepath/example_unix_test.go
new file mode 100644
index 000000000..f3fe076c3
--- /dev/null
+++ b/src/pkg/path/filepath/example_unix_test.go
@@ -0,0 +1,39 @@
+// Copyright 2013 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.
+
+// +build !windows,!plan9
+
+package filepath_test
+
+import (
+ "fmt"
+ "path/filepath"
+)
+
+func ExampleSplitList() {
+ fmt.Println("On Unix:", filepath.SplitList("/a/b/c:/usr/bin"))
+ // Output:
+ // On Unix: [/a/b/c /usr/bin]
+}
+
+func ExampleRel() {
+ paths := []string{
+ "/a/b/c",
+ "/b/c",
+ "./b/c",
+ }
+ base := "/a"
+
+ fmt.Println("On Unix:")
+ for _, p := range paths {
+ rel, err := filepath.Rel(base, p)
+ fmt.Printf("%q: %q %v\n", p, rel, err)
+ }
+
+ // Output:
+ // On Unix:
+ // "/a/b/c": "b/c" <nil>
+ // "/b/c": "../b/c" <nil>
+ // "./b/c": "" Rel: can't make b/c relative to /a
+}
diff --git a/src/pkg/path/filepath/match_test.go b/src/pkg/path/filepath/match_test.go
index 7b0ea8017..f1bc60e35 100644
--- a/src/pkg/path/filepath/match_test.go
+++ b/src/pkg/path/filepath/match_test.go
@@ -2,10 +2,9 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-package filepath_test
+package filepath
import (
- . "path/filepath"
"runtime"
"strings"
"testing"
diff --git a/src/pkg/path/filepath/path.go b/src/pkg/path/filepath/path.go
index 815021bd0..f8c7e4b2f 100644
--- a/src/pkg/path/filepath/path.go
+++ b/src/pkg/path/filepath/path.go
@@ -13,6 +13,45 @@ import (
"strings"
)
+// A lazybuf is a lazily constructed path buffer.
+// It supports append, reading previously appended bytes,
+// and retrieving the final string. It does not allocate a buffer
+// to hold the output until that output diverges from s.
+type lazybuf struct {
+ path string
+ buf []byte
+ w int
+ volAndPath string
+ volLen int
+}
+
+func (b *lazybuf) index(i int) byte {
+ if b.buf != nil {
+ return b.buf[i]
+ }
+ return b.path[i]
+}
+
+func (b *lazybuf) append(c byte) {
+ if b.buf == nil {
+ if b.w < len(b.path) && b.path[b.w] == c {
+ b.w++
+ return
+ }
+ b.buf = make([]byte, len(b.path))
+ copy(b.buf, b.path[:b.w])
+ }
+ b.buf[b.w] = c
+ b.w++
+}
+
+func (b *lazybuf) string() string {
+ if b.buf == nil {
+ return b.volAndPath[:b.volLen+b.w]
+ }
+ return b.volAndPath[:b.volLen] + string(b.buf[:b.w])
+}
+
const (
Separator = os.PathSeparator
ListSeparator = os.PathListSeparator
@@ -40,14 +79,15 @@ const (
// 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):]
+ originalPath := path
+ volLen := volumeNameLen(path)
+ path = path[volLen:]
if path == "" {
- if len(vol) > 1 && vol[1] != ':' {
+ if volLen > 1 && originalPath[1] != ':' {
// should be UNC
- return FromSlash(vol)
+ return FromSlash(originalPath)
}
- return vol + "."
+ return originalPath + "."
}
rooted := os.IsPathSeparator(path[0])
@@ -57,11 +97,11 @@ func Clean(path string) string {
// 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
+ out := lazybuf{path: path, volAndPath: originalPath, volLen: volLen}
+ r, dotdot := 0, 0
if rooted {
- buf[0] = Separator
- r, w, dotdot = 1, 1, 1
+ out.append(Separator)
+ r, dotdot = 1, 1
}
for r < n {
@@ -76,46 +116,40 @@ func Clean(path string) string {
// .. element: remove to last separator
r += 2
switch {
- case w > dotdot:
+ case out.w > dotdot:
// can backtrack
- w--
- for w > dotdot && !os.IsPathSeparator(buf[w]) {
- w--
+ out.w--
+ for out.w > dotdot && !os.IsPathSeparator(out.index(out.w)) {
+ out.w--
}
case !rooted:
// cannot backtrack, but not rooted, so append .. element.
- if w > 0 {
- buf[w] = Separator
- w++
+ if out.w > 0 {
+ out.append(Separator)
}
- buf[w] = '.'
- w++
- buf[w] = '.'
- w++
- dotdot = w
+ out.append('.')
+ out.append('.')
+ dotdot = out.w
}
default:
// real path element.
// add slash if needed
- if rooted && w != 1 || !rooted && w != 0 {
- buf[w] = Separator
- w++
+ if rooted && out.w != 1 || !rooted && out.w != 0 {
+ out.append(Separator)
}
// copy element
for ; r < n && !os.IsPathSeparator(path[r]); r++ {
- buf[w] = path[r]
- w++
+ out.append(path[r])
}
}
}
// Turn empty string into "."
- if w == 0 {
- buf[w] = '.'
- w++
+ if out.w == 0 {
+ out.append('.')
}
- return FromSlash(vol + string(buf[0:w]))
+ return FromSlash(out.string())
}
// ToSlash returns the result of replacing each separator character
@@ -142,10 +176,7 @@ func FromSlash(path string) string {
// 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{}
- }
- return strings.Split(path, string(ListSeparator))
+ return splitList(path)
}
// Split splits path immediately following the final Separator,
@@ -291,13 +322,18 @@ func Rel(basepath, targpath string) (string, error) {
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.
+// visited by Walk. The path argument contains the argument to Walk as a
+// prefix; that is, if Walk is called with "dir", which is a directory
+// containing the file "a", the walk function will be called with argument
+// "dir/a". The info argument is the os.FileInfo for the named path.
+//
+// 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.
@@ -335,6 +371,7 @@ func walk(path string, info os.FileInfo, walkFn WalkFunc) error {
// 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.
+// Walk does not follow symbolic links.
func Walk(root string, walkFn WalkFunc) error {
info, err := os.Lstat(root)
if err != nil {
@@ -397,7 +434,8 @@ func Base(path string) string {
}
// Dir returns all but the last element of path, typically the path's directory.
-// Trailing path separators are removed before processing.
+// After dropping the final element, the path is Cleaned and trailing
+// slashes are removed.
// 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.
@@ -417,3 +455,11 @@ func Dir(path string) string {
}
return vol + dir
}
+
+// VolumeName returns leading volume name.
+// Given "C:\foo\bar" it returns "C:" under windows.
+// Given "\\host\share\foo" it returns "\\host\share".
+// On other platforms it returns "".
+func VolumeName(path string) (v string) {
+ return path[:volumeNameLen(path)]
+}
diff --git a/src/pkg/path/filepath/path_plan9.go b/src/pkg/path/filepath/path_plan9.go
index 59a5812dd..12e85aae0 100644
--- a/src/pkg/path/filepath/path_plan9.go
+++ b/src/pkg/path/filepath/path_plan9.go
@@ -11,13 +11,20 @@ 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 ""
+// volumeNameLen returns length of the leading volume name on Windows.
+// It returns 0 elsewhere.
+func volumeNameLen(path string) int {
+ return 0
}
// HasPrefix exists for historical compatibility and should not be used.
func HasPrefix(p, prefix string) bool {
return strings.HasPrefix(p, prefix)
}
+
+func splitList(path string) []string {
+ if path == "" {
+ return []string{}
+ }
+ return strings.Split(path, string(ListSeparator))
+}
diff --git a/src/pkg/path/filepath/path_test.go b/src/pkg/path/filepath/path_test.go
index 070905fd3..e768ad32f 100644
--- a/src/pkg/path/filepath/path_test.go
+++ b/src/pkg/path/filepath/path_test.go
@@ -20,7 +20,6 @@ type PathTest struct {
var cleantests = []PathTest{
// Already clean
- {"", "."},
{"abc", "abc"},
{"abc/def", "abc/def"},
{"a/b/c", "a/b/c"},
@@ -31,6 +30,9 @@ var cleantests = []PathTest{
{"/abc", "/abc"},
{"/", "/"},
+ // Empty is current dir
+ {"", "."},
+
// Remove trailing slash
{"abc/", "abc"},
{"abc/def/", "abc/def"},
@@ -61,6 +63,7 @@ var cleantests = []PathTest{
{"abc/def/../../..", ".."},
{"/abc/def/../../..", "/"},
{"abc/def/../../../ghi/jkl/../../../mno", "../../mno"},
+ {"/../abc", "/abc"},
// Combinations
{"abc/./../def", "def"},
@@ -99,6 +102,16 @@ func TestClean(t *testing.T) {
if s := filepath.Clean(test.path); s != test.result {
t.Errorf("Clean(%q) = %q, want %q", test.path, s, test.result)
}
+ if s := filepath.Clean(test.result); s != test.result {
+ t.Errorf("Clean(%q) = %q, want %q", test.result, s, test.result)
+ }
+ }
+
+ for _, test := range tests {
+ allocs := testing.AllocsPerRun(100, func() { filepath.Clean(test.result) })
+ if allocs > 0 {
+ t.Errorf("Clean(%q): %v allocs, want zero", test.result, allocs)
+ }
}
}
@@ -135,10 +148,36 @@ var splitlisttests = []SplitListTest{
{string([]byte{lsep, 'a', lsep, 'b'}), []string{"", "a", "b"}},
}
+var winsplitlisttests = []SplitListTest{
+ // quoted
+ {`"a"`, []string{`a`}},
+
+ // semicolon
+ {`";"`, []string{`;`}},
+ {`"a;b"`, []string{`a;b`}},
+ {`";";`, []string{`;`, ``}},
+ {`;";"`, []string{``, `;`}},
+
+ // partially quoted
+ {`a";"b`, []string{`a;b`}},
+ {`a; ""b`, []string{`a`, ` b`}},
+ {`"a;b`, []string{`a;b`}},
+ {`""a;b`, []string{`a`, `b`}},
+ {`"""a;b`, []string{`a;b`}},
+ {`""""a;b`, []string{`a`, `b`}},
+ {`a";b`, []string{`a;b`}},
+ {`a;b";c`, []string{`a`, `b;c`}},
+ {`"a";b";c`, []string{`a`, `b;c`}},
+}
+
func TestSplitList(t *testing.T) {
- for _, test := range splitlisttests {
+ tests := splitlisttests
+ if runtime.GOOS == "windows" {
+ tests = append(tests, winsplitlisttests...)
+ }
+ for _, test := range tests {
if l := filepath.SplitList(test.list); !reflect.DeepEqual(l, test.result) {
- t.Errorf("SplitList(%q) = %s, want %s", test.list, l, test.result)
+ t.Errorf("SplitList(%#q) = %#q, want %#q", test.list, l, test.result)
}
}
}
@@ -684,10 +723,15 @@ func TestAbs(t *testing.T) {
}
defer os.RemoveAll(root)
+ wd, err := os.Getwd()
+ if err != nil {
+ t.Fatal("getwd failed: ", err)
+ }
err = os.Chdir(root)
if err != nil {
t.Fatal("chdir failed: ", err)
}
+ defer os.Chdir(wd)
for _, dir := range absTestDirs {
err = os.Mkdir(dir, 0777)
@@ -871,7 +915,7 @@ func TestDriveLetterInEvalSymlinks(t *testing.T) {
}
func TestBug3486(t *testing.T) { // http://code.google.com/p/go/issues/detail?id=3486
- root, err := filepath.EvalSymlinks(os.Getenv("GOROOT"))
+ root, err := filepath.EvalSymlinks(runtime.GOROOT())
if err != nil {
t.Fatal(err)
}
diff --git a/src/pkg/path/filepath/path_unix.go b/src/pkg/path/filepath/path_unix.go
index 305e30727..cff7b2c65 100644
--- a/src/pkg/path/filepath/path_unix.go
+++ b/src/pkg/path/filepath/path_unix.go
@@ -13,13 +13,20 @@ 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 ""
+// volumeNameLen returns length of the leading volume name on Windows.
+// It returns 0 elsewhere.
+func volumeNameLen(path string) int {
+ return 0
}
// HasPrefix exists for historical compatibility and should not be used.
func HasPrefix(p, prefix string) bool {
return strings.HasPrefix(p, prefix)
}
+
+func splitList(path string) []string {
+ if path == "" {
+ return []string{}
+ }
+ return strings.Split(path, string(ListSeparator))
+}
diff --git a/src/pkg/path/filepath/path_windows.go b/src/pkg/path/filepath/path_windows.go
index 3dcd03021..e99997257 100644
--- a/src/pkg/path/filepath/path_windows.go
+++ b/src/pkg/path/filepath/path_windows.go
@@ -14,29 +14,27 @@ func isSlash(c uint8) bool {
// IsAbs returns true if the path is absolute.
func IsAbs(path string) (b bool) {
- v := VolumeName(path)
- if v == "" {
+ l := volumeNameLen(path)
+ if l == 0 {
return false
}
- path = path[len(v):]
+ path = path[l:]
if path == "" {
return false
}
return isSlash(path[0])
}
-// VolumeName returns leading volume name.
-// Given "C:\foo\bar" it returns "C:" under windows.
-// Given "\\host\share\foo" it returns "\\host\share".
-// On other platforms it returns "".
-func VolumeName(path string) (v string) {
+// volumeNameLen returns length of the leading volume name on Windows.
+// It returns 0 elsewhere.
+func volumeNameLen(path string) int {
if len(path) < 2 {
- return ""
+ return 0
}
// with drive letter
c := path[0]
if path[1] == ':' && ('a' <= c && c <= 'z' || 'A' <= c && c <= 'Z') {
- return path[:2]
+ return 2
}
// is it UNC
if l := len(path); l >= 5 && isSlash(path[0]) && isSlash(path[1]) &&
@@ -56,13 +54,13 @@ func VolumeName(path string) (v string) {
break
}
}
- return path[:n]
+ return n
}
break
}
}
}
- return ""
+ return 0
}
// HasPrefix exists for historical compatibility and should not be used.
@@ -72,3 +70,36 @@ func HasPrefix(p, prefix string) bool {
}
return strings.HasPrefix(strings.ToLower(p), strings.ToLower(prefix))
}
+
+func splitList(path string) []string {
+ // The same implementation is used in LookPath in os/exec;
+ // consider changing os/exec when changing this.
+
+ if path == "" {
+ return []string{}
+ }
+
+ // Split path, respecting but preserving quotes.
+ list := []string{}
+ start := 0
+ quo := false
+ for i := 0; i < len(path); i++ {
+ switch c := path[i]; {
+ case c == '"':
+ quo = !quo
+ case c == ListSeparator && !quo:
+ list = append(list, path[start:i])
+ start = i + 1
+ }
+ }
+ list = append(list, path[start:])
+
+ // Remove quotes.
+ for i, s := range list {
+ if strings.Contains(s, `"`) {
+ list[i] = strings.Replace(s, `"`, ``, -1)
+ }
+ }
+
+ return list
+}
diff --git a/src/pkg/path/filepath/path_windows_test.go b/src/pkg/path/filepath/path_windows_test.go
new file mode 100644
index 000000000..d8926adde
--- /dev/null
+++ b/src/pkg/path/filepath/path_windows_test.go
@@ -0,0 +1,89 @@
+package filepath_test
+
+import (
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "reflect"
+ "testing"
+)
+
+func TestWinSplitListTestsAreValid(t *testing.T) {
+ comspec := os.Getenv("ComSpec")
+ if comspec == "" {
+ t.Fatal("%ComSpec% must be set")
+ }
+
+ for ti, tt := range winsplitlisttests {
+ testWinSplitListTestIsValid(t, ti, tt, comspec)
+ }
+}
+
+func testWinSplitListTestIsValid(t *testing.T, ti int, tt SplitListTest,
+ comspec string) {
+
+ const (
+ cmdfile = `printdir.cmd`
+ perm os.FileMode = 0700
+ )
+
+ tmp, err := ioutil.TempDir("", "testWinSplitListTestIsValid")
+ if err != nil {
+ t.Fatalf("TempDir failed: %v", err)
+ }
+ defer os.RemoveAll(tmp)
+
+ for i, d := range tt.result {
+ if d == "" {
+ continue
+ }
+ if cd := filepath.Clean(d); filepath.VolumeName(cd) != "" ||
+ cd[0] == '\\' || cd == ".." || (len(cd) >= 3 && cd[0:3] == `..\`) {
+ t.Errorf("%d,%d: %#q refers outside working directory", ti, i, d)
+ return
+ }
+ dd := filepath.Join(tmp, d)
+ if _, err := os.Stat(dd); err == nil {
+ t.Errorf("%d,%d: %#q already exists", ti, i, d)
+ return
+ }
+ if err = os.MkdirAll(dd, perm); err != nil {
+ t.Errorf("%d,%d: MkdirAll(%#q) failed: %v", ti, i, dd, err)
+ return
+ }
+ fn, data := filepath.Join(dd, cmdfile), []byte("@echo "+d+"\r\n")
+ if err = ioutil.WriteFile(fn, data, perm); err != nil {
+ t.Errorf("%d,%d: WriteFile(%#q) failed: %v", ti, i, fn, err)
+ return
+ }
+ }
+
+ for i, d := range tt.result {
+ if d == "" {
+ continue
+ }
+ exp := []byte(d + "\r\n")
+ cmd := &exec.Cmd{
+ Path: comspec,
+ Args: []string{`/c`, cmdfile},
+ Env: []string{`Path=` + tt.list},
+ Dir: tmp,
+ }
+ out, err := cmd.CombinedOutput()
+ switch {
+ case err != nil:
+ t.Errorf("%d,%d: execution error %v\n%q", ti, i, err, out)
+ return
+ case !reflect.DeepEqual(out, exp):
+ t.Errorf("%d,%d: expected %#q, got %#q", ti, i, exp, out)
+ return
+ default:
+ // unshadow cmdfile in next directory
+ err = os.Remove(filepath.Join(tmp, d, cmdfile))
+ if err != nil {
+ t.Fatalf("Remove test command failed: %v", err)
+ }
+ }
+ }
+}
diff --git a/src/pkg/path/filepath/symlink_windows.go b/src/pkg/path/filepath/symlink_windows.go
index 1ee939928..9adc8a48a 100644
--- a/src/pkg/path/filepath/symlink_windows.go
+++ b/src/pkg/path/filepath/symlink_windows.go
@@ -9,7 +9,10 @@ import (
)
func toShort(path string) (string, error) {
- p := syscall.StringToUTF16(path)
+ p, err := syscall.UTF16FromString(path)
+ if err != nil {
+ return "", err
+ }
b := p // GetShortPathName says we can reuse buffer
n, err := syscall.GetShortPathName(&p[0], &b[0], uint32(len(b)))
if err != nil {
@@ -26,7 +29,10 @@ func toShort(path string) (string, error) {
}
func toLong(path string) (string, error) {
- p := syscall.StringToUTF16(path)
+ p, err := syscall.UTF16FromString(path)
+ if err != nil {
+ return "", err
+ }
b := p // GetLongPathName says we can reuse buffer
n, err := syscall.GetLongPathName(&p[0], &b[0], uint32(len(b)))
if err != nil {
diff --git a/src/pkg/path/path.go b/src/pkg/path/path.go
index a7e041568..bdb85c6b9 100644
--- a/src/pkg/path/path.go
+++ b/src/pkg/path/path.go
@@ -10,6 +10,43 @@ import (
"strings"
)
+// A lazybuf is a lazily constructed path buffer.
+// It supports append, reading previously appended bytes,
+// and retrieving the final string. It does not allocate a buffer
+// to hold the output until that output diverges from s.
+type lazybuf struct {
+ s string
+ buf []byte
+ w int
+}
+
+func (b *lazybuf) index(i int) byte {
+ if b.buf != nil {
+ return b.buf[i]
+ }
+ return b.s[i]
+}
+
+func (b *lazybuf) append(c byte) {
+ if b.buf == nil {
+ if b.w < len(b.s) && b.s[b.w] == c {
+ b.w++
+ return
+ }
+ b.buf = make([]byte, len(b.s))
+ copy(b.buf, b.s[:b.w])
+ }
+ b.buf[b.w] = c
+ b.w++
+}
+
+func (b *lazybuf) string() string {
+ if b.buf == nil {
+ return b.s[:b.w]
+ }
+ return string(b.buf[:b.w])
+}
+
// 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:
@@ -42,10 +79,11 @@ func Clean(path string) string {
// 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
+ out := lazybuf{s: path}
+ r, dotdot := 0, 0
if rooted {
- r, w, dotdot = 1, 1, 1
+ out.append('/')
+ r, dotdot = 1, 1
}
for r < n {
@@ -60,46 +98,40 @@ func Clean(path string) string {
// .. element: remove to last /
r += 2
switch {
- case w > dotdot:
+ case out.w > dotdot:
// can backtrack
- w--
- for w > dotdot && buf[w] != '/' {
- w--
+ out.w--
+ for out.w > dotdot && out.index(out.w) != '/' {
+ out.w--
}
case !rooted:
// cannot backtrack, but not rooted, so append .. element.
- if w > 0 {
- buf[w] = '/'
- w++
+ if out.w > 0 {
+ out.append('/')
}
- buf[w] = '.'
- w++
- buf[w] = '.'
- w++
- dotdot = w
+ out.append('.')
+ out.append('.')
+ dotdot = out.w
}
default:
// real path element.
// add slash if needed
- if rooted && w != 1 || !rooted && w != 0 {
- buf[w] = '/'
- w++
+ if rooted && out.w != 1 || !rooted && out.w != 0 {
+ out.append('/')
}
// copy element
for ; r < n && path[r] != '/'; r++ {
- buf[w] = path[r]
- w++
+ out.append(path[r])
}
}
}
// Turn empty string into "."
- if w == 0 {
- buf[w] = '.'
- w++
+ if out.w == 0 {
+ return "."
}
- return string(buf[0:w])
+ return out.string()
}
// Split splits path immediately following the final slash.
@@ -166,7 +198,8 @@ func IsAbs(path string) bool {
}
// Dir returns all but the last element of path, typically the path's directory.
-// The path is Cleaned and trailing slashes are removed before processing.
+// After dropping the final element using Split, the path is Cleaned and trailing
+// slashes are removed.
// If the path is empty, Dir returns ".".
// If the path consists entirely of slashes followed by non-slash bytes, Dir
// returns a single slash. In any other case, the returned path does not end in a
diff --git a/src/pkg/path/path_test.go b/src/pkg/path/path_test.go
index 77f080433..220ec1a0b 100644
--- a/src/pkg/path/path_test.go
+++ b/src/pkg/path/path_test.go
@@ -67,6 +67,16 @@ func TestClean(t *testing.T) {
if s := Clean(test.path); s != test.result {
t.Errorf("Clean(%q) = %q, want %q", test.path, s, test.result)
}
+ if s := Clean(test.result); s != test.result {
+ t.Errorf("Clean(%q) = %q, want %q", test.result, s, test.result)
+ }
+ }
+
+ for _, test := range cleantests {
+ allocs := testing.AllocsPerRun(100, func() { Clean(test.result) })
+ if allocs > 0 {
+ t.Errorf("Clean(%q): %v allocs, want zero", test.result, allocs)
+ }
}
}
@@ -181,6 +191,7 @@ var dirtests = []PathTest{
{"x/", "x"},
{"abc", "."},
{"abc/def", "abc"},
+ {"abc////def", "abc"},
{"a/b/.x", "a/b"},
{"a/b/c.", "a/b"},
{"a/b/c.x", "a/b"},