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