Code in this file has been forked from the "filepath" module in the Go source code to work around bugs with the WebAssembly build target. More information about why here: https://github.com/golang/go/issues/43768.
//////////////////////////////////////////////////////////////////////////////
Copyright (c) 2009 The Go Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

package fs

import (
	
	
	
	
)

type goFilepath struct {
	cwd           string
	isWindows     bool
	pathSeparator byte
}

func ( uint8) bool {
	return  == '\\' ||  == '/'
}
reservedNames lists reserved Windows names. Search for PRN in https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file for details.
var reservedNames = []string{
	"CON", "PRN", "AUX", "NUL",
	"COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
	"LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9",
}
isReservedName returns true, if path is Windows reserved name. See reservedNames for the full list.
func ( string) bool {
	if len() == 0 {
		return false
	}
	for ,  := range reservedNames {
		if strings.EqualFold(, ) {
			return true
		}
	}
	return false
}
IsAbs reports whether the path is absolute.
func ( goFilepath) ( string) bool {
	if !.isWindows {
		return strings.HasPrefix(, "/")
	}
	if isReservedName() {
		return true
	}
	 := .volumeNameLen()
	if  == 0 {
		return false
	}
	 = [:]
	if  == "" {
		return false
	}
	return isSlash([0])
}
Abs returns an absolute representation of path. If the path is not absolute it will be joined with the current working directory to turn it into an absolute path. The absolute path name for a given file is not guaranteed to be unique. Abs calls Clean on the result.
func ( goFilepath) ( string) (string, error) {
	if .isAbs() {
		return .clean(), nil
	}
	return .join([]string{.cwd, }), nil
}
IsPathSeparator reports whether c is a directory separator character.
func ( goFilepath) ( uint8) bool {
	return  == '/' || (.isWindows &&  == '\\')
}
volumeNameLen returns length of the leading volume name on Windows. It returns 0 elsewhere.
func ( goFilepath) ( string) int {
	if !.isWindows {
		return 0
	}
	if len() < 2 {
		return 0
with drive letter
	 := [0]
	if [1] == ':' && ('a' <=  &&  <= 'z' || 'A' <=  &&  <= 'Z') {
		return 2
is it UNC? https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
	if  := len();  >= 5 && isSlash([0]) && isSlash([1]) &&
first, leading `\\` and next shouldn't be `\`. its server name.
second, next '\' shouldn't be repeated.
			if isSlash([]) {
third, following something characters. its share name.
				if !isSlash([]) {
					if [] == '.' {
						break
					}
					for ;  < ; ++ {
						if isSlash([]) {
							break
						}
					}
					return 
				}
				break
			}
		}
	}
	return 0
}
EvalSymlinks returns the path name after the evaluation of any symbolic links. If path is relative the result will be relative to the current directory, unless one of the components is an absolute symbolic link. EvalSymlinks calls Clean on the result.
func ( goFilepath) ( string) (string, error) {
	 := .volumeNameLen()
	 := string(.pathSeparator)

	if  < len() && .isPathSeparator([]) {
		++
	}
	 := [:]
	 := 
	 := 0
	for ,  := , ;  < len();  =  {
		for  < len() && .isPathSeparator([]) {
			++
		}
		 = 
		for  < len() && !.isPathSeparator([]) {
			++
		}
On Windows, "." can be a symlink. We look it up, and use the value if it is absolute. If not, we just return ".".
		 := .isWindows && [.volumeNameLen():] == "."
The next path component is in path[start:end].
No more path components.
			break
Ignore path component ".".
			continue
Back up to previous component if possible. Note that volLen includes any leading slash.
Set r to the index of the last slash in dest, after the volume.
			var  int
			for  = len() - 1;  >= ; -- {
				if .isPathSeparator([]) {
					break
				}
			}
Either path has no slashes (it's empty or just "C:") or it ends in a ".." we had to keep. Either way, keep this "..".
				if len() >  {
					 += 
				}
				 += ".."
Discard everything since the last slash.
				 = [:]
			}
			continue
		}
Ordinary path component. Add it to result.

		if len() > .volumeNameLen() && !.isPathSeparator([len()-1]) {
			 += 
		}

		 += [:]
Resolve symlink.

		,  := os.Lstat()
		if  != nil {
			return "", 
		}

		if .Mode()&os.ModeSymlink == 0 {
			if !.Mode().IsDir() &&  < len() {
				return "", syscall.ENOTDIR
			}
			continue
		}
Found symlink.

		++
		if  > 255 {
			return "", errors.New("EvalSymlinks: too many links")
		}

		,  := os.Readlink()
		if  != nil {
			return "", 
		}

On Windows, if "." is a relative symlink, just return ".".
			break
		}

		 =  + [:]

		 := .volumeNameLen()
Symlink to drive name is an absolute path.
			if  < len() && .isPathSeparator([]) {
				++
			}
			 = [:]
			 = 
			 = len()
Symlink to absolute path.
			 = [:1]
			 = 1
Symlink to relative path; replace last path component in dest.
			var  int
			for  = len() - 1;  >= ; -- {
				if .isPathSeparator([]) {
					break
				}
			}
			if  <  {
				 = 
			} else {
				 = [:]
			}
			 = 0
		}
	}
	return .clean(), nil
}
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 {
	path       string
	buf        []byte
	w          int
	volAndPath string
	volLen     int
}

func ( *lazybuf) ( int) byte {
	if .buf != nil {
		return .buf[]
	}
	return .path[]
}

func ( *lazybuf) ( byte) {
	if .buf == nil {
		if .w < len(.path) && .path[.w] ==  {
			.w++
			return
		}
		.buf = make([]byte, len(.path))
		copy(.buf, .path[:.w])
	}
	.buf[.w] = 
	.w++
}

func ( *lazybuf) () string {
	if .buf == nil {
		return .volAndPath[:.volLen+.w]
	}
	return .volAndPath[:.volLen] + string(.buf[:.w])
}
FromSlash returns the result of replacing each slash ('/') character in path with a separator character. Multiple slashes are replaced by multiple separators.
func ( goFilepath) ( string) string {
	if !.isWindows {
		return 
	}
	return strings.ReplaceAll(, "/", "\\")
}
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 Separator elements with a single one. 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, assuming Separator is '/'. The returned path ends in a slash only if it represents a root directory, such as "/" on Unix or `C:\` on Windows. Finally, any occurrences of slash are replaced by Separator. 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 ( goFilepath) ( string) string {
	 := 
	 := .volumeNameLen()
	 = [:]
	if  == "" {
should be UNC
			return .fromSlash()
		}
		return  + "."
	}
	 := .isPathSeparator([0])
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.
	 := len()
	 := lazybuf{path: , volAndPath: , volLen: }
	,  := 0, 0
	if  {
		.append(.pathSeparator)
		,  = 1, 1
	}

	for  <  {
		switch {
empty path element
			++
. element
			++
.. element: remove to last separator
			 += 2
			switch {
can backtrack
				.w--
				for .w >  && !.isPathSeparator(.index(.w)) {
					.w--
				}
cannot backtrack, but not rooted, so append .. element.
				if .w > 0 {
					.append(.pathSeparator)
				}
				.append('.')
				.append('.')
				 = .w
			}
real path element. add slash if needed
			if  && .w != 1 || ! && .w != 0 {
				.append(.pathSeparator)
copy element
			for ;  <  && !.isPathSeparator([]); ++ {
				.append([])
			}
		}
	}
Turn empty string into "."
	if .w == 0 {
		.append('.')
	}

	return .fromSlash(.string())
}
VolumeName returns leading volume name. Given "C:\foo\bar" it returns "C:" on Windows. Given "\\host\share\foo" it returns "\\host\share". On other platforms it returns "".
func ( goFilepath) ( string) string {
	return [:.volumeNameLen()]
}
Base returns the last element of path. Trailing path separators are removed before extracting the last element. If the path is empty, Base returns ".". If the path consists entirely of separators, Base returns a single separator.
func ( goFilepath) ( string) string {
	if  == "" {
		return "."
Strip trailing slashes.
	for len() > 0 && .isPathSeparator([len()-1]) {
		 = [0 : len()-1]
Throw away volume name
Find the last element
	 := len() - 1
	for  >= 0 && !.isPathSeparator([]) {
		--
	}
	if  >= 0 {
		 = [+1:]
If empty now, it had only slashes.
	if  == "" {
		return string(.pathSeparator)
	}
	return 
}
Dir returns all but the last element of path, typically the path's directory. After dropping the final element, Dir calls Clean on the path and trailing slashes are removed. If the path is empty, Dir returns ".". If the path consists entirely of separators, Dir returns a single separator. The returned path does not end in a separator unless it is the root directory.
func ( goFilepath) ( string) string {
	 := .volumeName()
	 := len() - 1
	for  >= len() && !.isPathSeparator([]) {
		--
	}
	 := .clean([len() : +1])
must be UNC
		return 
	}
	return  + 
}
Ext returns the file name extension used by path. The extension is the suffix beginning at the final dot in the final element of path; it is empty if there is no dot.
func ( goFilepath) ( string) string {
	for  := len() - 1;  >= 0 && !.isPathSeparator([]); -- {
		if [] == '.' {
			return [:]
		}
	}
	return ""
}
Join joins any number of path elements into a single path, separating them with an OS specific Separator. 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. On Windows, the result will only be a UNC path if the first non-empty element is a UNC path.
func ( goFilepath) ( []string) string {
	for ,  := range  {
		if  != "" {
			if .isWindows {
				return .joinNonEmpty([:])
			}
			return .clean(strings.Join([:], string(.pathSeparator)))
		}
	}
	return ""
}
joinNonEmpty is like join, but it assumes that the first element is non-empty.
func ( goFilepath) ( []string) string {
First element is drive letter without terminating slash. Keep path relative to current directory on that drive. Skip empty elements.
		 := 1
		for ;  < len(); ++ {
			if [] != "" {
				break
			}
		}
		return .clean([0] + strings.Join([:], string(.pathSeparator)))
The following logic prevents Join from inadvertently creating a UNC path on Windows. Unless the first element is a UNC path, Join shouldn't create a UNC path. See golang.org/issue/9167.
	 := .clean(strings.Join(, string(.pathSeparator)))
	if !.isUNC() {
		return 
p == UNC only allowed when the first element is a UNC path.
	 := .clean([0])
	if .isUNC() {
		return 
head + tail == UNC, but joining two non-UNC paths should not result in a UNC path. Undo creation of UNC path.
	 := .clean(strings.Join([1:], string(.pathSeparator)))
	if [len()-1] == .pathSeparator {
		return  + 
	}
	return  + string(.pathSeparator) + 
}
isUNC reports whether path is a UNC path.
func ( goFilepath) ( string) bool {
	return .volumeNameLen() > 2
}
Rel returns a relative path that is lexically equivalent to targpath when joined to basepath with an intervening separator. That is, Join(basepath, Rel(basepath, targpath)) is equivalent to targpath itself. On success, the returned path will always be relative to basepath, even if basepath and targpath share no elements. An error is returned if targpath can't be made relative to basepath or if knowing the current working directory would be necessary to compute it. Rel calls Clean on the result.
func ( goFilepath) (,  string) (string, error) {
	 := .volumeName()
	 := .volumeName()
	 := .clean()
	 := .clean()
	if .sameWord(, ) {
		return ".", nil
	}
	 = [len():]
	 = [len():]
	if  == "." {
		 = ""
Can't use IsAbs - `\a` and `a` are both relative in Windows.
	 := len() > 0 && [0] == .pathSeparator
	 := len() > 0 && [0] == .pathSeparator
	if  !=  || !.sameWord(, ) {
		return "", errors.New("Rel: can't make " +  + " relative to " + )
Position base[b0:bi] and targ[t0:ti] at the first differing elements.
	 := len()
	 := len()
	var , , ,  int
	for {
		for  <  && [] != .pathSeparator {
			++
		}
		for  <  && [] != .pathSeparator {
			++
		}
		if !.sameWord([:], [:]) {
			break
		}
		if  <  {
			++
		}
		if  <  {
			++
		}
		 = 
		 = 
	}
	if [:] == ".." {
		return "", errors.New("Rel: can't make " +  + " relative to " + )
	}
Base elements left. Must go up before going down.
		 := strings.Count([:], string(.pathSeparator))
		 := 2 + *3
		if  !=  {
			 += 1 +  - 
		}
		 := make([]byte, )
		 := copy(, "..")
		for  := 0;  < ; ++ {
			[] = .pathSeparator
			copy([+1:], "..")
			 += 3
		}
		if  !=  {
			[] = .pathSeparator
			copy([+1:], [:])
		}
		return string(), nil
	}
	return [:], nil
}

func ( goFilepath) (,  string) bool {
	if !.isWindows {
		return  == 
	}
	return strings.EqualFold(, )