Source file src/pkg/path/filepath/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 filepath implements utility routines for manipulating filename paths 6 // in a way compatible with the target operating system-defined file paths. 7 package filepath 8 9 import ( 10 "errors" 11 "os" 12 "sort" 13 "strings" 14 ) 15 16 const ( 17 Separator = os.PathSeparator 18 ListSeparator = os.PathListSeparator 19 ) 20 21 // Clean returns the shortest path name equivalent to path 22 // by purely lexical processing. It applies the following rules 23 // iteratively until no further processing can be done: 24 // 25 // 1. Replace multiple Separator elements with a single one. 26 // 2. Eliminate each . path name element (the current directory). 27 // 3. Eliminate each inner .. path name element (the parent directory) 28 // along with the non-.. element that precedes it. 29 // 4. Eliminate .. elements that begin a rooted path: 30 // that is, replace "/.." by "/" at the beginning of a path, 31 // assuming Separator is '/'. 32 // 33 // The returned path ends in a slash only if it represents a root directory, 34 // such as "/" on Unix or `C:\` on Windows. 35 // 36 // If the result of this process is an empty string, Clean 37 // returns the string ".". 38 // 39 // See also Rob Pike, ``Lexical File Names in Plan 9 or 40 // Getting Dot-Dot Right,'' 41 // http://plan9.bell-labs.com/sys/doc/lexnames.html 42 func Clean(path string) string { 43 vol := VolumeName(path) 44 path = path[len(vol):] 45 if path == "" { 46 if len(vol) > 1 && vol[1] != ':' { 47 // should be UNC 48 return FromSlash(vol) 49 } 50 return vol + "." 51 } 52 rooted := os.IsPathSeparator(path[0]) 53 54 // Invariants: 55 // reading from path; r is index of next byte to process. 56 // writing to buf; w is index of next byte to write. 57 // dotdot is index in buf where .. must stop, either because 58 // it is the leading slash or it is a leading ../../.. prefix. 59 n := len(path) 60 buf := []byte(path) 61 r, w, dotdot := 0, 0, 0 62 if rooted { 63 buf[0] = Separator 64 r, w, dotdot = 1, 1, 1 65 } 66 67 for r < n { 68 switch { 69 case os.IsPathSeparator(path[r]): 70 // empty path element 71 r++ 72 case path[r] == '.' && (r+1 == n || os.IsPathSeparator(path[r+1])): 73 // . element 74 r++ 75 case path[r] == '.' && path[r+1] == '.' && (r+2 == n || os.IsPathSeparator(path[r+2])): 76 // .. element: remove to last separator 77 r += 2 78 switch { 79 case w > dotdot: 80 // can backtrack 81 w-- 82 for w > dotdot && !os.IsPathSeparator(buf[w]) { 83 w-- 84 } 85 case !rooted: 86 // cannot backtrack, but not rooted, so append .. element. 87 if w > 0 { 88 buf[w] = Separator 89 w++ 90 } 91 buf[w] = '.' 92 w++ 93 buf[w] = '.' 94 w++ 95 dotdot = w 96 } 97 default: 98 // real path element. 99 // add slash if needed 100 if rooted && w != 1 || !rooted && w != 0 { 101 buf[w] = Separator 102 w++ 103 } 104 // copy element 105 for ; r < n && !os.IsPathSeparator(path[r]); r++ { 106 buf[w] = path[r] 107 w++ 108 } 109 } 110 } 111 112 // Turn empty string into "." 113 if w == 0 { 114 buf[w] = '.' 115 w++ 116 } 117 118 return FromSlash(vol + string(buf[0:w])) 119 } 120 121 // ToSlash returns the result of replacing each separator character 122 // in path with a slash ('/') character. Multiple separators are 123 // replaced by multiple slashes. 124 func ToSlash(path string) string { 125 if Separator == '/' { 126 return path 127 } 128 return strings.Replace(path, string(Separator), "/", -1) 129 } 130 131 // FromSlash returns the result of replacing each slash ('/') character 132 // in path with a separator character. Multiple slashes are replaced 133 // by multiple separators. 134 func FromSlash(path string) string { 135 if Separator == '/' { 136 return path 137 } 138 return strings.Replace(path, "/", string(Separator), -1) 139 } 140 141 // SplitList splits a list of paths joined by the OS-specific ListSeparator, 142 // usually found in PATH or GOPATH environment variables. 143 // Unlike strings.Split, SplitList returns an empty slice when passed an empty string. 144 func SplitList(path string) []string { 145 if path == "" { 146 return []string{} 147 } 148 return strings.Split(path, string(ListSeparator)) 149 } 150 151 // Split splits path immediately following the final Separator, 152 // separating it into a directory and file name component. 153 // If there is no Separator in path, Split returns an empty dir 154 // and file set to path. 155 // The returned values have the property that path = dir+file. 156 func Split(path string) (dir, file string) { 157 vol := VolumeName(path) 158 i := len(path) - 1 159 for i >= len(vol) && !os.IsPathSeparator(path[i]) { 160 i-- 161 } 162 return path[:i+1], path[i+1:] 163 } 164 165 // Join joins any number of path elements into a single path, adding 166 // a Separator if necessary. The result is Cleaned, in particular 167 // all empty strings are ignored. 168 func Join(elem ...string) string { 169 for i, e := range elem { 170 if e != "" { 171 return Clean(strings.Join(elem[i:], string(Separator))) 172 } 173 } 174 return "" 175 } 176 177 // Ext returns the file name extension used by path. 178 // The extension is the suffix beginning at the final dot 179 // in the final element of path; it is empty if there is 180 // no dot. 181 func Ext(path string) string { 182 for i := len(path) - 1; i >= 0 && !os.IsPathSeparator(path[i]); i-- { 183 if path[i] == '.' { 184 return path[i:] 185 } 186 } 187 return "" 188 } 189 190 // EvalSymlinks returns the path name after the evaluation of any symbolic 191 // links. 192 // If path is relative the result will be relative to the current directory, 193 // unless one of the components is an absolute symbolic link. 194 func EvalSymlinks(path string) (string, error) { 195 return evalSymlinks(path) 196 } 197 198 // Abs returns an absolute representation of path. 199 // If the path is not absolute it will be joined with the current 200 // working directory to turn it into an absolute path. The absolute 201 // path name for a given file is not guaranteed to be unique. 202 func Abs(path string) (string, error) { 203 if IsAbs(path) { 204 return Clean(path), nil 205 } 206 wd, err := os.Getwd() 207 if err != nil { 208 return "", err 209 } 210 return Join(wd, path), nil 211 } 212 213 // Rel returns a relative path that is lexically equivalent to targpath when 214 // joined to basepath with an intervening separator. That is, 215 // Join(basepath, Rel(basepath, targpath)) is equivalent to targpath itself. 216 // On success, the returned path will always be relative to basepath, 217 // even if basepath and targpath share no elements. 218 // An error is returned if targpath can't be made relative to basepath or if 219 // knowing the current working directory would be necessary to compute it. 220 func Rel(basepath, targpath string) (string, error) { 221 baseVol := VolumeName(basepath) 222 targVol := VolumeName(targpath) 223 base := Clean(basepath) 224 targ := Clean(targpath) 225 if targ == base { 226 return ".", nil 227 } 228 base = base[len(baseVol):] 229 targ = targ[len(targVol):] 230 if base == "." { 231 base = "" 232 } 233 // Can't use IsAbs - `\a` and `a` are both relative in Windows. 234 baseSlashed := len(base) > 0 && base[0] == Separator 235 targSlashed := len(targ) > 0 && targ[0] == Separator 236 if baseSlashed != targSlashed || baseVol != targVol { 237 return "", errors.New("Rel: can't make " + targ + " relative to " + base) 238 } 239 // Position base[b0:bi] and targ[t0:ti] at the first differing elements. 240 bl := len(base) 241 tl := len(targ) 242 var b0, bi, t0, ti int 243 for { 244 for bi < bl && base[bi] != Separator { 245 bi++ 246 } 247 for ti < tl && targ[ti] != Separator { 248 ti++ 249 } 250 if targ[t0:ti] != base[b0:bi] { 251 break 252 } 253 if bi < bl { 254 bi++ 255 } 256 if ti < tl { 257 ti++ 258 } 259 b0 = bi 260 t0 = ti 261 } 262 if base[b0:bi] == ".." { 263 return "", errors.New("Rel: can't make " + targ + " relative to " + base) 264 } 265 if b0 != bl { 266 // Base elements left. Must go up before going down. 267 seps := strings.Count(base[b0:bl], string(Separator)) 268 size := 2 + seps*3 269 if tl != t0 { 270 size += 1 + tl - t0 271 } 272 buf := make([]byte, size) 273 n := copy(buf, "..") 274 for i := 0; i < seps; i++ { 275 buf[n] = Separator 276 copy(buf[n+1:], "..") 277 n += 3 278 } 279 if t0 != tl { 280 buf[n] = Separator 281 copy(buf[n+1:], targ[t0:]) 282 } 283 return string(buf), nil 284 } 285 return targ[t0:], nil 286 } 287 288 // SkipDir is used as a return value from WalkFuncs to indicate that 289 // the directory named in the call is to be skipped. It is not returned 290 // as an error by any function. 291 var SkipDir = errors.New("skip this directory") 292 293 // WalkFunc is the type of the function called for each file or directory 294 // visited by Walk. If there was a problem walking to the file or directory 295 // named by path, the incoming error will describe the problem and the 296 // function can decide how to handle that error (and Walk will not descend 297 // into that directory). If an error is returned, processing stops. The 298 // sole exception is that if path is a directory and the function returns the 299 // special value SkipDir, the contents of the directory are skipped 300 // and processing continues as usual on the next file. 301 type WalkFunc func(path string, info os.FileInfo, err error) error 302 303 // walk recursively descends path, calling w. 304 func walk(path string, info os.FileInfo, walkFn WalkFunc) error { 305 err := walkFn(path, info, nil) 306 if err != nil { 307 if info.IsDir() && err == SkipDir { 308 return nil 309 } 310 return err 311 } 312 313 if !info.IsDir() { 314 return nil 315 } 316 317 list, err := readDir(path) 318 if err != nil { 319 return walkFn(path, info, err) 320 } 321 322 for _, fileInfo := range list { 323 if err = walk(Join(path, fileInfo.Name()), fileInfo, walkFn); err != nil { 324 return err 325 } 326 } 327 return nil 328 } 329 330 // Walk walks the file tree rooted at root, calling walkFn for each file or 331 // directory in the tree, including root. All errors that arise visiting files 332 // and directories are filtered by walkFn. The files are walked in lexical 333 // order, which makes the output deterministic but means that for very 334 // large directories Walk can be inefficient. 335 func Walk(root string, walkFn WalkFunc) error { 336 info, err := os.Lstat(root) 337 if err != nil { 338 return walkFn(root, nil, err) 339 } 340 return walk(root, info, walkFn) 341 } 342 343 // readDir reads the directory named by dirname and returns 344 // a sorted list of directory entries. 345 // Copied from io/ioutil to avoid the circular import. 346 func readDir(dirname string) ([]os.FileInfo, error) { 347 f, err := os.Open(dirname) 348 if err != nil { 349 return nil, err 350 } 351 list, err := f.Readdir(-1) 352 f.Close() 353 if err != nil { 354 return nil, err 355 } 356 sort.Sort(byName(list)) 357 return list, nil 358 } 359 360 // byName implements sort.Interface. 361 type byName []os.FileInfo 362 363 func (f byName) Len() int { return len(f) } 364 func (f byName) Less(i, j int) bool { return f[i].Name() < f[j].Name() } 365 func (f byName) Swap(i, j int) { f[i], f[j] = f[j], f[i] } 366 367 // Base returns the last element of path. 368 // Trailing path separators are removed before extracting the last element. 369 // If the path is empty, Base returns ".". 370 // If the path consists entirely of separators, Base returns a single separator. 371 func Base(path string) string { 372 if path == "" { 373 return "." 374 } 375 // Strip trailing slashes. 376 for len(path) > 0 && os.IsPathSeparator(path[len(path)-1]) { 377 path = path[0 : len(path)-1] 378 } 379 // Throw away volume name 380 path = path[len(VolumeName(path)):] 381 // Find the last element 382 i := len(path) - 1 383 for i >= 0 && !os.IsPathSeparator(path[i]) { 384 i-- 385 } 386 if i >= 0 { 387 path = path[i+1:] 388 } 389 // If empty now, it had only slashes. 390 if path == "" { 391 return string(Separator) 392 } 393 return path 394 } 395 396 // Dir returns all but the last element of path, typically the path's directory. 397 // Trailing path separators are removed before processing. 398 // If the path is empty, Dir returns ".". 399 // If the path consists entirely of separators, Dir returns a single separator. 400 // The returned path does not end in a separator unless it is the root directory. 401 func Dir(path string) string { 402 vol := VolumeName(path) 403 i := len(path) - 1 404 for i >= len(vol) && !os.IsPathSeparator(path[i]) { 405 i-- 406 } 407 dir := Clean(path[len(vol) : i+1]) 408 last := len(dir) - 1 409 if last > 0 && os.IsPathSeparator(dir[last]) { 410 dir = dir[:last] 411 } 412 if dir == "" { 413 dir = "." 414 } 415 return vol + dir 416 }