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