cuelang.org/go@v0.10.1/pkg/path/path.go (about)

     1  // Copyright 2020 CUE Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Copyright 2009 The Go Authors. All rights reserved.
    16  // Use of this source code is governed by a BSD-style
    17  // license that can be found in the LICENSE file.
    18  
    19  // Package path implements utility routines for manipulating filename paths as
    20  // defined by targetted operating systems, and also paths that always use
    21  // forward slashes regardless of the operating system, such as URLs.
    22  package path
    23  
    24  import (
    25  	"errors"
    26  	"strings"
    27  )
    28  
    29  // A lazybuf is a lazily constructed path buffer.
    30  // It supports append, reading previously appended bytes,
    31  // and retrieving the final string. It does not allocate a buffer
    32  // to hold the output until that output diverges.
    33  type lazybuf struct {
    34  	path       string
    35  	buf        []byte
    36  	w          int
    37  	volAndPath string
    38  	volLen     int
    39  }
    40  
    41  func (b *lazybuf) index(i int) byte {
    42  	if b.buf != nil {
    43  		return b.buf[i]
    44  	}
    45  	return b.path[i]
    46  }
    47  
    48  func (b *lazybuf) append(c byte) {
    49  	if b.buf == nil {
    50  		if b.w < len(b.path) && b.path[b.w] == c {
    51  			b.w++
    52  			return
    53  		}
    54  		b.buf = make([]byte, len(b.path))
    55  		copy(b.buf, b.path[:b.w])
    56  	}
    57  	b.buf[b.w] = c
    58  	b.w++
    59  }
    60  
    61  func (b *lazybuf) string() string {
    62  	if b.buf == nil {
    63  		return b.volAndPath[:b.volLen+b.w]
    64  	}
    65  	return b.volAndPath[:b.volLen] + string(b.buf[:b.w])
    66  }
    67  
    68  // Clean returns the shortest path name equivalent to path
    69  // by purely lexical processing. The default value for os is Unix.
    70  // It applies the following rules
    71  // iteratively until no further processing can be done:
    72  //
    73  //  1. Replace multiple Separator elements with a single one.
    74  //  2. Eliminate each . path name element (the current directory).
    75  //  3. Eliminate each inner .. path name element (the parent directory)
    76  //     along with the non-.. element that precedes it.
    77  //  4. Eliminate .. elements that begin a rooted path:
    78  //     that is, replace "/.." by "/" at the beginning of a path,
    79  //     assuming Separator is '/'.
    80  //
    81  // The returned path ends in a slash only if it represents a root directory,
    82  // such as "/" on Unix or `C:\` on Windows.
    83  //
    84  // Finally, any occurrences of slash are replaced by Separator.
    85  //
    86  // If the result of this process is an empty string, Clean
    87  // returns the string ".".
    88  //
    89  // See also Rob Pike, “Lexical File Names in Plan 9 or
    90  // Getting Dot-Dot Right,”
    91  // https://9p.io/sys/doc/lexnames.html
    92  func Clean(path string, os OS) string {
    93  	return clean(path, getOS(os))
    94  }
    95  
    96  func clean(path string, os os) string {
    97  	originalPath := path
    98  	volLen := os.volumeNameLen(path)
    99  	path = path[volLen:]
   100  	if path == "" {
   101  		if volLen > 1 && originalPath[1] != ':' {
   102  			// should be UNC
   103  			return fromSlash(originalPath, os)
   104  		}
   105  		return originalPath + "."
   106  	}
   107  	rooted := os.IsPathSeparator(path[0])
   108  
   109  	// Invariants:
   110  	//	reading from path; r is index of next byte to process.
   111  	//	writing to buf; w is index of next byte to write.
   112  	//	dotdot is index in buf where .. must stop, either because
   113  	//		it is the leading slash or it is a leading ../../.. prefix.
   114  	n := len(path)
   115  	out := lazybuf{path: path, volAndPath: originalPath, volLen: volLen}
   116  	r, dotdot := 0, 0
   117  	if rooted {
   118  		out.append(os.Separator)
   119  		r, dotdot = 1, 1
   120  	}
   121  
   122  	for r < n {
   123  		switch {
   124  		case os.IsPathSeparator(path[r]):
   125  			// empty path element
   126  			r++
   127  		case path[r] == '.' && (r+1 == n || os.IsPathSeparator(path[r+1])):
   128  			// . element
   129  			r++
   130  		case path[r] == '.' && path[r+1] == '.' && (r+2 == n || os.IsPathSeparator(path[r+2])):
   131  			// .. element: remove to last separator
   132  			r += 2
   133  			switch {
   134  			case out.w > dotdot:
   135  				// can backtrack
   136  				out.w--
   137  				for out.w > dotdot && !os.IsPathSeparator(out.index(out.w)) {
   138  					out.w--
   139  				}
   140  			case !rooted:
   141  				// cannot backtrack, but not rooted, so append .. element.
   142  				if out.w > 0 {
   143  					out.append(os.Separator)
   144  				}
   145  				out.append('.')
   146  				out.append('.')
   147  				dotdot = out.w
   148  			}
   149  		default:
   150  			// real path element.
   151  			// add slash if needed
   152  			if rooted && out.w != 1 || !rooted && out.w != 0 {
   153  				out.append(os.Separator)
   154  			}
   155  			// copy element
   156  			for ; r < n && !os.IsPathSeparator(path[r]); r++ {
   157  				out.append(path[r])
   158  			}
   159  		}
   160  	}
   161  
   162  	// Turn empty string into "."
   163  	if out.w == 0 {
   164  		out.append('.')
   165  	}
   166  
   167  	return fromSlash(out.string(), os)
   168  }
   169  
   170  // ToSlash returns the result of replacing each separator character
   171  // in path with a slash ('/') character. Multiple separators are
   172  // replaced by multiple slashes.
   173  func ToSlash(path string, os OS) string {
   174  	return toSlash(path, getOS(os))
   175  }
   176  
   177  func toSlash(path string, os os) string {
   178  	if os.Separator == '/' {
   179  		return path
   180  	}
   181  	return strings.ReplaceAll(path, string(os.Separator), "/")
   182  }
   183  
   184  // FromSlash returns the result of replacing each slash ('/') character
   185  // in path with a separator character. Multiple slashes are replaced
   186  // by multiple separators.
   187  func FromSlash(path string, os OS) string {
   188  	return fromSlash(path, getOS(os))
   189  }
   190  
   191  func fromSlash(path string, os os) string {
   192  	if os.Separator == '/' {
   193  		return path
   194  	}
   195  	return strings.ReplaceAll(path, "/", string(os.Separator))
   196  }
   197  
   198  // SplitList splits a list of paths joined by the OS-specific ListSeparator,
   199  // usually found in PATH or GOPATH environment variables.
   200  // Unlike strings.Split, SplitList returns an empty slice when passed an empty
   201  // string.
   202  func SplitList(path string, os OS) []string {
   203  	return getOS(os).splitList(path)
   204  }
   205  
   206  // Split splits path immediately following the final slash and returns them as
   207  // the list [dir, file], separating it into a directory and file name component.
   208  // If there is no slash in path, Split returns an empty dir and file set to
   209  // path. The returned values have the property that path = dir+file.
   210  // The default value for os is Unix.
   211  func Split(path string, os OS) []string {
   212  	x := getOS(os)
   213  	vol := volumeName(path, x)
   214  	i := len(path) - 1
   215  	for i >= len(vol) && !x.IsPathSeparator(path[i]) {
   216  		i--
   217  	}
   218  	return []string{path[:i+1], path[i+1:]}
   219  }
   220  
   221  // Join joins any number of path elements into a single path,
   222  // separating them with an OS specific Separator. Empty elements
   223  // are ignored. The result is Cleaned. However, if the argument
   224  // list is empty or all its elements are empty, Join returns
   225  // an empty string.
   226  // On Windows, the result will only be a UNC path if the first
   227  // non-empty element is a UNC path.
   228  // The default value for os is Unix.
   229  func Join(elem []string, os OS) string {
   230  	return getOS(os).join(elem)
   231  }
   232  
   233  // Ext returns the file name extension used by path.
   234  // The extension is the suffix beginning at the final dot
   235  // in the final element of path; it is empty if there is
   236  // no dot. The default value for os is Unix.
   237  func Ext(path string, os OS) string {
   238  	x := getOS(os)
   239  	for i := len(path) - 1; i >= 0 && !x.IsPathSeparator(path[i]); i-- {
   240  		if path[i] == '.' {
   241  			return path[i:]
   242  		}
   243  	}
   244  	return ""
   245  }
   246  
   247  // Resolve reports the path of sub relative to dir. If sub is an absolute path,
   248  // or if dir is empty, it will return sub. If sub is empty, it will return dir.
   249  // Resolve calls Clean on the result. The default value for os is Unix.
   250  func Resolve(dir, sub string, os OS) string {
   251  	x := getOS(os)
   252  	if x.IsAbs(sub) {
   253  		return clean(sub, x)
   254  	}
   255  	dir = clean(dir, x)
   256  	return x.join([]string{dir, sub})
   257  }
   258  
   259  // Rel returns a relative path that is lexically equivalent to targpath when
   260  // joined to basepath with an intervening separator. That is,
   261  // Join(basepath, Rel(basepath, targpath)) is equivalent to targpath itself.
   262  // On success, the returned path will always be relative to basepath,
   263  // even if basepath and targpath share no elements.
   264  // An error is returned if targpath can't be made relative to basepath or if
   265  // knowing the current working directory would be necessary to compute it.
   266  // Rel calls Clean on the result. The default value for os is Unix.
   267  func Rel(basepath, targpath string, os OS) (string, error) {
   268  	x := getOS(os)
   269  	baseVol := volumeName(basepath, x)
   270  	targVol := volumeName(targpath, x)
   271  	base := clean(basepath, x)
   272  	targ := clean(targpath, x)
   273  	if x.sameWord(targ, base) {
   274  		return ".", nil
   275  	}
   276  	base = base[len(baseVol):]
   277  	targ = targ[len(targVol):]
   278  	if base == "." {
   279  		base = ""
   280  	}
   281  	// Can't use IsAbs - `\a` and `a` are both relative in Windows.
   282  	baseSlashed := len(base) > 0 && base[0] == x.Separator
   283  	targSlashed := len(targ) > 0 && targ[0] == x.Separator
   284  	if baseSlashed != targSlashed || !x.sameWord(baseVol, targVol) {
   285  		return "", errors.New("Rel: can't make " + targpath + " relative to " + basepath)
   286  	}
   287  	// Position base[b0:bi] and targ[t0:ti] at the first differing elements.
   288  	bl := len(base)
   289  	tl := len(targ)
   290  	var b0, bi, t0, ti int
   291  	for {
   292  		for bi < bl && base[bi] != x.Separator {
   293  			bi++
   294  		}
   295  		for ti < tl && targ[ti] != x.Separator {
   296  			ti++
   297  		}
   298  		if !x.sameWord(targ[t0:ti], base[b0:bi]) {
   299  			break
   300  		}
   301  		if bi < bl {
   302  			bi++
   303  		}
   304  		if ti < tl {
   305  			ti++
   306  		}
   307  		b0 = bi
   308  		t0 = ti
   309  	}
   310  	if base[b0:bi] == ".." {
   311  		return "", errors.New("Rel: can't make " + targpath + " relative to " + basepath)
   312  	}
   313  	if b0 != bl {
   314  		// Base elements left. Must go up before going down.
   315  		seps := strings.Count(base[b0:bl], string(x.Separator))
   316  		size := 2 + seps*3
   317  		if tl != t0 {
   318  			size += 1 + tl - t0
   319  		}
   320  		buf := make([]byte, size)
   321  		n := copy(buf, "..")
   322  		for i := 0; i < seps; i++ {
   323  			buf[n] = x.Separator
   324  			copy(buf[n+1:], "..")
   325  			n += 3
   326  		}
   327  		if t0 != tl {
   328  			buf[n] = x.Separator
   329  			copy(buf[n+1:], targ[t0:])
   330  		}
   331  		return string(buf), nil
   332  	}
   333  	return targ[t0:], nil
   334  }
   335  
   336  // Base returns the last element of path.
   337  // Trailing path separators are removed before extracting the last element.
   338  // If the path is empty, Base returns ".".
   339  // If the path consists entirely of separators, Base returns a single separator.
   340  // The default value for os is Unix.
   341  func Base(path string, os OS) string {
   342  	x := getOS(os)
   343  	if path == "" {
   344  		return "."
   345  	}
   346  	// Strip trailing slashes.
   347  	for len(path) > 0 && x.IsPathSeparator(path[len(path)-1]) {
   348  		path = path[0 : len(path)-1]
   349  	}
   350  	// Throw away volume name
   351  	path = path[x.volumeNameLen(path):]
   352  	// Find the last element
   353  	i := len(path) - 1
   354  	for i >= 0 && !x.IsPathSeparator(path[i]) {
   355  		i--
   356  	}
   357  	if i >= 0 {
   358  		path = path[i+1:]
   359  	}
   360  	// If empty now, it had only slashes.
   361  	if path == "" {
   362  		return string(x.Separator)
   363  	}
   364  	return path
   365  }
   366  
   367  // Dir returns all but the last element of path, typically the path's directory.
   368  // After dropping the final element, Dir calls Clean on the path and trailing
   369  // slashes are removed.
   370  // If the path is empty, Dir returns ".".
   371  // If the path consists entirely of separators, Dir returns a single separator.
   372  // The returned path does not end in a separator unless it is the root directory.
   373  // The default value for os is Unix.
   374  func Dir(path string, os OS) string {
   375  	x := getOS(os)
   376  	vol := volumeName(path, x)
   377  	i := len(path) - 1
   378  	for i >= len(vol) && !x.IsPathSeparator(path[i]) {
   379  		i--
   380  	}
   381  	dir := clean(path[len(vol):i+1], x)
   382  	if dir == "." && len(vol) > 2 {
   383  		// must be UNC
   384  		return vol
   385  	}
   386  	return vol + dir
   387  }
   388  
   389  // IsAbs reports whether the path is absolute. The default value for os is Unix.
   390  // Note that because IsAbs has a default value, it cannot be used as
   391  // a validator.
   392  func IsAbs(path string, os OS) bool {
   393  	return getOS(os).IsAbs(path)
   394  }
   395  
   396  // VolumeName returns leading volume name.
   397  // Given "C:\foo\bar" it returns "C:" on Windows.
   398  // Given "\\host\share\foo" it returns "\\host\share".
   399  // On other platforms it returns "".
   400  // The default value for os is Windows.
   401  func VolumeName(path string, os OS) string {
   402  	return volumeName(path, getOS(os))
   403  }
   404  
   405  func volumeName(path string, os os) string {
   406  	return path[:os.volumeNameLen(path)]
   407  }