...
Run Format

Source file src/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	// 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	

View as plain text