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