summaryrefslogtreecommitdiff
path: root/src/pkg/path/path.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/pkg/path/path.go')
-rw-r--r--src/pkg/path/path.go136
1 files changed, 136 insertions, 0 deletions
diff --git a/src/pkg/path/path.go b/src/pkg/path/path.go
new file mode 100644
index 000000000..a7e2c26c3
--- /dev/null
+++ b/src/pkg/path/path.go
@@ -0,0 +1,136 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// The path package implements utility routines for manipulating
+// slash-separated filename paths.
+package path
+
+import "io"
+
+// 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 := io.StringBytes(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 slash,
+// separating it into a directory and file name component.
+// If there is no slash in path, DirFile returns an empty dir and
+// file set to path.
+func Split(path string) (dir, file string) {
+ for i := len(path)-1; i >= 0; i-- {
+ if path[i] == '/' {
+ return path[0:i+1], path[i+1:len(path)];
+ }
+ }
+ return "", path
+}
+
+// Join joins dir and file into a single path, adding a separating
+// slash if necessary. If dir is empty, it returns file.
+func Join(dir, file string) string {
+ if dir == "" {
+ return file;
+ }
+ return Clean(dir + "/" + file);
+}
+
+// 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 {
+ dot := -1;
+ for i := len(path)-1; i >= 0 && path[i] != '/'; i-- {
+ if path[i] == '.' {
+ return path[i:len(path)];
+ }
+ }
+ return ""
+}
+