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