// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package path implements utility routines for manipulating slash-separated // paths. // // The path package should only be used for paths separated by forward // slashes, such as the paths in URLs. This package does not deal with // Windows paths with drive letters or backslashes; to manipulate // operating system paths, use the [path/filepath] package.
package path import // A lazybuf is a lazily constructed path buffer. // It supports append, reading previously appended bytes, // and retrieving the final string. It does not allocate a buffer // to hold the output until that output diverges from s. type lazybuf struct { s string buf []byte w int } func ( *lazybuf) ( int) byte { if .buf != nil { return .buf[] } return .s[] } func ( *lazybuf) ( byte) { if .buf == nil { if .w < len(.s) && .s[.w] == { .w++ return } .buf = make([]byte, len(.s)) copy(.buf, .s[:.w]) } .buf[.w] = .w++ } func ( *lazybuf) () string { if .buf == nil { return .s[:.w] } return string(.buf[:.w]) } // Clean returns the shortest path name equivalent to path // by purely lexical processing. It applies the following rules // iteratively until no further processing can be done: // // 1. Replace multiple slashes with a single slash. // 2. Eliminate each . path name element (the current directory). // 3. Eliminate each inner .. path name element (the parent directory) // along with the non-.. element that precedes it. // 4. Eliminate .. elements that begin a rooted path: // that is, replace "/.." by "/" at the beginning of a path. // // The returned path ends in a slash only if it is the root "/". // // If the result of this process is an empty string, Clean // returns the string ".". // // See also Rob Pike, “Lexical File Names in Plan 9 or // Getting Dot-Dot Right,” // https://9p.io/sys/doc/lexnames.html func ( string) string { if == "" { return "." } := [0] == '/' := len() // Invariants: // reading from path; r is index of next byte to process. // writing to buf; w is index of next byte to write. // dotdot is index in buf where .. must stop, either because // it is the leading slash or it is a leading ../../.. prefix. := lazybuf{s: } , := 0, 0 if { .append('/') , = 1, 1 } for < { switch { case [] == '/': // empty path element ++ case [] == '.' && (+1 == || [+1] == '/'): // . element ++ case [] == '.' && [+1] == '.' && (+2 == || [+2] == '/'): // .. element: remove to last / += 2 switch { case .w > : // can backtrack .w-- for .w > && .index(.w) != '/' { .w-- } case !: // cannot backtrack, but not rooted, so append .. element. if .w > 0 { .append('/') } .append('.') .append('.') = .w } default: // real path element. // add slash if needed if && .w != 1 || ! && .w != 0 { .append('/') } // copy element for ; < && [] != '/'; ++ { .append([]) } } } // Turn empty string into "." if .w == 0 { return "." } return .string() } // Split splits path immediately following the final slash, // separating it into a directory and file name component. // If there is no slash in path, Split returns an empty dir and // file set to path. // The returned values have the property that path = dir+file. func ( string) (, string) { := bytealg.LastIndexByteString(, '/') return [:+1], [+1:] } // Join joins any number of path elements into a single path, // separating them with slashes. Empty elements are ignored. // The result is Cleaned. However, if the argument list is // empty or all its elements are empty, Join returns // an empty string. func ( ...string) string { := 0 for , := range { += len() } if == 0 { return "" } := make([]byte, 0, +len()-1) for , := range { if len() > 0 || != "" { if len() > 0 { = append(, '/') } = append(, ...) } } return Clean(string()) } // Ext returns the file name extension used by path. // The extension is the suffix beginning at the final dot // in the final slash-separated element of path; // it is empty if there is no dot. func ( string) string { for := len() - 1; >= 0 && [] != '/'; -- { if [] == '.' { return [:] } } return "" } // Base returns the last element of path. // Trailing slashes are removed before extracting the last element. // If the path is empty, Base returns ".". // If the path consists entirely of slashes, Base returns "/". func ( string) string { if == "" { return "." } // Strip trailing slashes. for len() > 0 && [len()-1] == '/' { = [0 : len()-1] } // Find the last element if := bytealg.LastIndexByteString(, '/'); >= 0 { = [+1:] } // If empty now, it had only slashes. if == "" { return "/" } return } // IsAbs reports whether the path is absolute. func ( string) bool { return len() > 0 && [0] == '/' } // Dir returns all but the last element of path, typically the path's directory. // After dropping the final element using [Split], the path is Cleaned and trailing // slashes are removed. // If the path is empty, Dir returns ".". // If the path consists entirely of slashes followed by non-slash bytes, Dir // returns a single slash. In any other case, the returned path does not end in a // slash. func ( string) string { , := Split() return Clean() }