Source file src/pkg/path/path.go
1 // Copyright 2009 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Package path implements utility routines for manipulating slash-separated 6 // paths. 7 package path 8 9 import ( 10 "strings" 11 ) 12 13 // Clean returns the shortest path name equivalent to path 14 // by purely lexical processing. It applies the following rules 15 // iteratively until no further processing can be done: 16 // 17 // 1. Replace multiple slashes with a single slash. 18 // 2. Eliminate each . path name element (the current directory). 19 // 3. Eliminate each inner .. path name element (the parent directory) 20 // along with the non-.. element that precedes it. 21 // 4. Eliminate .. elements that begin a rooted path: 22 // that is, replace "/.." by "/" at the beginning of a path. 23 // 24 // The returned path ends in a slash only if it is the root "/". 25 // 26 // If the result of this process is an empty string, Clean 27 // returns the string ".". 28 // 29 // See also Rob Pike, ``Lexical File Names in Plan 9 or 30 // Getting Dot-Dot Right,'' 31 // http://plan9.bell-labs.com/sys/doc/lexnames.html 32 func Clean(path string) string { 33 if path == "" { 34 return "." 35 } 36 37 rooted := path[0] == '/' 38 n := len(path) 39 40 // Invariants: 41 // reading from path; r is index of next byte to process. 42 // writing to buf; w is index of next byte to write. 43 // dotdot is index in buf where .. must stop, either because 44 // it is the leading slash or it is a leading ../../.. prefix. 45 buf := []byte(path) 46 r, w, dotdot := 0, 0, 0 47 if rooted { 48 r, w, dotdot = 1, 1, 1 49 } 50 51 for r < n { 52 switch { 53 case path[r] == '/': 54 // empty path element 55 r++ 56 case path[r] == '.' && (r+1 == n || path[r+1] == '/'): 57 // . element 58 r++ 59 case path[r] == '.' && path[r+1] == '.' && (r+2 == n || path[r+2] == '/'): 60 // .. element: remove to last / 61 r += 2 62 switch { 63 case w > dotdot: 64 // can backtrack 65 w-- 66 for w > dotdot && buf[w] != '/' { 67 w-- 68 } 69 case !rooted: 70 // cannot backtrack, but not rooted, so append .. element. 71 if w > 0 { 72 buf[w] = '/' 73 w++ 74 } 75 buf[w] = '.' 76 w++ 77 buf[w] = '.' 78 w++ 79 dotdot = w 80 } 81 default: 82 // real path element. 83 // add slash if needed 84 if rooted && w != 1 || !rooted && w != 0 { 85 buf[w] = '/' 86 w++ 87 } 88 // copy element 89 for ; r < n && path[r] != '/'; r++ { 90 buf[w] = path[r] 91 w++ 92 } 93 } 94 } 95 96 // Turn empty string into "." 97 if w == 0 { 98 buf[w] = '.' 99 w++ 100 } 101 102 return string(buf[0:w]) 103 } 104 105 // Split splits path immediately following the final slash. 106 // separating it into a directory and file name component. 107 // If there is no slash path, Split returns an empty dir and 108 // file set to path. 109 // The returned values have the property that path = dir+file. 110 func Split(path string) (dir, file string) { 111 i := strings.LastIndex(path, "/") 112 return path[:i+1], path[i+1:] 113 } 114 115 // Join joins any number of path elements into a single path, adding a 116 // separating slash if necessary. The result is Cleaned; in particular, 117 // all empty strings are ignored. 118 func Join(elem ...string) string { 119 for i, e := range elem { 120 if e != "" { 121 return Clean(strings.Join(elem[i:], "/")) 122 } 123 } 124 return "" 125 } 126 127 // Ext returns the file name extension used by path. 128 // The extension is the suffix beginning at the final dot 129 // in the final slash-separated element of path; 130 // it is empty if there is no dot. 131 func Ext(path string) string { 132 for i := len(path) - 1; i >= 0 && path[i] != '/'; i-- { 133 if path[i] == '.' { 134 return path[i:] 135 } 136 } 137 return "" 138 } 139 140 // Base returns the last element of path. 141 // Trailing slashes are removed before extracting the last element. 142 // If the path is empty, Base returns ".". 143 // If the path consists entirely of slashes, Base returns "/". 144 func Base(path string) string { 145 if path == "" { 146 return "." 147 } 148 // Strip trailing slashes. 149 for len(path) > 0 && path[len(path)-1] == '/' { 150 path = path[0 : len(path)-1] 151 } 152 // Find the last element 153 if i := strings.LastIndex(path, "/"); i >= 0 { 154 path = path[i+1:] 155 } 156 // If empty now, it had only slashes. 157 if path == "" { 158 return "/" 159 } 160 return path 161 } 162 163 // IsAbs returns true if the path is absolute. 164 func IsAbs(path string) bool { 165 return len(path) > 0 && path[0] == '/' 166 } 167 168 // Dir returns all but the last element of path, typically the path's directory. 169 // The path is Cleaned and trailing slashes are removed before processing. 170 // If the path is empty, Dir returns ".". 171 // If the path consists entirely of slashes followed by non-slash bytes, Dir 172 // returns a single slash. In any other case, the returned path does not end in a 173 // slash. 174 func Dir(path string) string { 175 dir, _ := Split(path) 176 dir = Clean(dir) 177 last := len(dir) - 1 178 if last > 0 && dir[last] == '/' { 179 dir = dir[:last] 180 } 181 if dir == "" { 182 dir = "." 183 } 184 return dir 185 }