...

Source file src/path/path.go

Documentation: path

		 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 path implements utility routines for manipulating slash-separated
		 6  // paths.
		 7  //
		 8  // The path package should only be used for paths separated by forward
		 9  // slashes, such as the paths in URLs. This package does not deal with
		10  // Windows paths with drive letters or backslashes; to manipulate
		11  // operating system paths, use the path/filepath package.
		12  package path
		13  
		14  // A lazybuf is a lazily constructed path buffer.
		15  // It supports append, reading previously appended bytes,
		16  // and retrieving the final string. It does not allocate a buffer
		17  // to hold the output until that output diverges from s.
		18  type lazybuf struct {
		19  	s	 string
		20  	buf []byte
		21  	w	 int
		22  }
		23  
		24  func (b *lazybuf) index(i int) byte {
		25  	if b.buf != nil {
		26  		return b.buf[i]
		27  	}
		28  	return b.s[i]
		29  }
		30  
		31  func (b *lazybuf) append(c byte) {
		32  	if b.buf == nil {
		33  		if b.w < len(b.s) && b.s[b.w] == c {
		34  			b.w++
		35  			return
		36  		}
		37  		b.buf = make([]byte, len(b.s))
		38  		copy(b.buf, b.s[:b.w])
		39  	}
		40  	b.buf[b.w] = c
		41  	b.w++
		42  }
		43  
		44  func (b *lazybuf) string() string {
		45  	if b.buf == nil {
		46  		return b.s[:b.w]
		47  	}
		48  	return string(b.buf[:b.w])
		49  }
		50  
		51  // Clean returns the shortest path name equivalent to path
		52  // by purely lexical processing. It applies the following rules
		53  // iteratively until no further processing can be done:
		54  //
		55  //	1. Replace multiple slashes with a single slash.
		56  //	2. Eliminate each . path name element (the current directory).
		57  //	3. Eliminate each inner .. path name element (the parent directory)
		58  //		 along with the non-.. element that precedes it.
		59  //	4. Eliminate .. elements that begin a rooted path:
		60  //		 that is, replace "/.." by "/" at the beginning of a path.
		61  //
		62  // The returned path ends in a slash only if it is the root "/".
		63  //
		64  // If the result of this process is an empty string, Clean
		65  // returns the string ".".
		66  //
		67  // See also Rob Pike, ``Lexical File Names in Plan 9 or
		68  // Getting Dot-Dot Right,''
		69  // https://9p.io/sys/doc/lexnames.html
		70  func Clean(path string) string {
		71  	if path == "" {
		72  		return "."
		73  	}
		74  
		75  	rooted := path[0] == '/'
		76  	n := len(path)
		77  
		78  	// Invariants:
		79  	//	reading from path; r is index of next byte to process.
		80  	//	writing to buf; w is index of next byte to write.
		81  	//	dotdot is index in buf where .. must stop, either because
		82  	//		it is the leading slash or it is a leading ../../.. prefix.
		83  	out := lazybuf{s: path}
		84  	r, dotdot := 0, 0
		85  	if rooted {
		86  		out.append('/')
		87  		r, dotdot = 1, 1
		88  	}
		89  
		90  	for r < n {
		91  		switch {
		92  		case path[r] == '/':
		93  			// empty path element
		94  			r++
		95  		case path[r] == '.' && (r+1 == n || path[r+1] == '/'):
		96  			// . element
		97  			r++
		98  		case path[r] == '.' && path[r+1] == '.' && (r+2 == n || path[r+2] == '/'):
		99  			// .. element: remove to last /
	 100  			r += 2
	 101  			switch {
	 102  			case out.w > dotdot:
	 103  				// can backtrack
	 104  				out.w--
	 105  				for out.w > dotdot && out.index(out.w) != '/' {
	 106  					out.w--
	 107  				}
	 108  			case !rooted:
	 109  				// cannot backtrack, but not rooted, so append .. element.
	 110  				if out.w > 0 {
	 111  					out.append('/')
	 112  				}
	 113  				out.append('.')
	 114  				out.append('.')
	 115  				dotdot = out.w
	 116  			}
	 117  		default:
	 118  			// real path element.
	 119  			// add slash if needed
	 120  			if rooted && out.w != 1 || !rooted && out.w != 0 {
	 121  				out.append('/')
	 122  			}
	 123  			// copy element
	 124  			for ; r < n && path[r] != '/'; r++ {
	 125  				out.append(path[r])
	 126  			}
	 127  		}
	 128  	}
	 129  
	 130  	// Turn empty string into "."
	 131  	if out.w == 0 {
	 132  		return "."
	 133  	}
	 134  
	 135  	return out.string()
	 136  }
	 137  
	 138  // lastSlash(s) is strings.LastIndex(s, "/") but we can't import strings.
	 139  func lastSlash(s string) int {
	 140  	i := len(s) - 1
	 141  	for i >= 0 && s[i] != '/' {
	 142  		i--
	 143  	}
	 144  	return i
	 145  }
	 146  
	 147  // Split splits path immediately following the final slash,
	 148  // separating it into a directory and file name component.
	 149  // If there is no slash in path, Split returns an empty dir and
	 150  // file set to path.
	 151  // The returned values have the property that path = dir+file.
	 152  func Split(path string) (dir, file string) {
	 153  	i := lastSlash(path)
	 154  	return path[:i+1], path[i+1:]
	 155  }
	 156  
	 157  // Join joins any number of path elements into a single path,
	 158  // separating them with slashes. Empty elements are ignored.
	 159  // The result is Cleaned. However, if the argument list is
	 160  // empty or all its elements are empty, Join returns
	 161  // an empty string.
	 162  func Join(elem ...string) string {
	 163  	size := 0
	 164  	for _, e := range elem {
	 165  		size += len(e)
	 166  	}
	 167  	if size == 0 {
	 168  		return ""
	 169  	}
	 170  	buf := make([]byte, 0, size+len(elem)-1)
	 171  	for _, e := range elem {
	 172  		if len(buf) > 0 || e != "" {
	 173  			if len(buf) > 0 {
	 174  				buf = append(buf, '/')
	 175  			}
	 176  			buf = append(buf, e...)
	 177  		}
	 178  	}
	 179  	return Clean(string(buf))
	 180  }
	 181  
	 182  // Ext returns the file name extension used by path.
	 183  // The extension is the suffix beginning at the final dot
	 184  // in the final slash-separated element of path;
	 185  // it is empty if there is no dot.
	 186  func Ext(path string) string {
	 187  	for i := len(path) - 1; i >= 0 && path[i] != '/'; i-- {
	 188  		if path[i] == '.' {
	 189  			return path[i:]
	 190  		}
	 191  	}
	 192  	return ""
	 193  }
	 194  
	 195  // Base returns the last element of path.
	 196  // Trailing slashes are removed before extracting the last element.
	 197  // If the path is empty, Base returns ".".
	 198  // If the path consists entirely of slashes, Base returns "/".
	 199  func Base(path string) string {
	 200  	if path == "" {
	 201  		return "."
	 202  	}
	 203  	// Strip trailing slashes.
	 204  	for len(path) > 0 && path[len(path)-1] == '/' {
	 205  		path = path[0 : len(path)-1]
	 206  	}
	 207  	// Find the last element
	 208  	if i := lastSlash(path); i >= 0 {
	 209  		path = path[i+1:]
	 210  	}
	 211  	// If empty now, it had only slashes.
	 212  	if path == "" {
	 213  		return "/"
	 214  	}
	 215  	return path
	 216  }
	 217  
	 218  // IsAbs reports whether the path is absolute.
	 219  func IsAbs(path string) bool {
	 220  	return len(path) > 0 && path[0] == '/'
	 221  }
	 222  
	 223  // Dir returns all but the last element of path, typically the path's directory.
	 224  // After dropping the final element using Split, the path is Cleaned and trailing
	 225  // slashes are removed.
	 226  // If the path is empty, Dir returns ".".
	 227  // If the path consists entirely of slashes followed by non-slash bytes, Dir
	 228  // returns a single slash. In any other case, the returned path does not end in a
	 229  // slash.
	 230  func Dir(path string) string {
	 231  	dir, _ := Split(path)
	 232  	return Clean(dir)
	 233  }
	 234  

View as plain text