...

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

View as plain text