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