...

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

View as plain text