src/pkg/path/filepath/path.go - The Go Programming Language

Golang

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