// Copyright 2024 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 filepathlite implements a subset of path/filepath, // only using packages which may be imported by "os". // // Tests for these functions are in path/filepath.
package filepathlite import ( ) var errInvalidPath = errors.New("invalid path") // 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) ( ...byte) { .buf = slices.Insert(.buf, 0, ...) .w += len() } func ( *lazybuf) () string { if .buf == nil { return .volAndPath[:.volLen+.w] } return .volAndPath[:.volLen] + string(.buf[:.w]) } // Clean is filepath.Clean. func ( string) string { := := volumeNameLen() = [:] if == "" { if > 1 && IsPathSeparator([0]) && IsPathSeparator([1]) { // 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(Separator) , = 1, 1 } for < { switch { case IsPathSeparator([]): // empty path element ++ case [] == '.' && (+1 == || IsPathSeparator([+1])): // . element ++ case [] == '.' && [+1] == '.' && (+2 == || IsPathSeparator([+2])): // .. element: remove to last separator += 2 switch { case .w > : // can backtrack .w-- for .w > && !IsPathSeparator(.index(.w)) { .w-- } case !: // cannot backtrack, but not rooted, so append .. element. if .w > 0 { .append(Separator) } .append('.') .append('.') = .w } default: // real path element. // add slash if needed if && .w != 1 || ! && .w != 0 { .append(Separator) } // copy element for ; < && !IsPathSeparator([]); ++ { .append([]) } } } // Turn empty string into "." if .w == 0 { .append('.') } postClean(&) // avoid creating absolute paths on Windows return FromSlash(.string()) } // IsLocal is filepath.IsLocal. func ( string) bool { return isLocal() } func unixIsLocal( string) bool { if IsAbs() || == "" { return false } := false for := ; != ""; { var string , , _ = stringslite.Cut(, "/") if == "." || == ".." { = true break } } if { = Clean() } if == ".." || stringslite.HasPrefix(, "../") { return false } return true } // Localize is filepath.Localize. func ( string) (string, error) { if !fs.ValidPath() { return "", errInvalidPath } return localize() } // ToSlash is filepath.ToSlash. func ( string) string { if Separator == '/' { return } return replaceStringByte(, Separator, '/') } // FromSlash is filepath.ToSlash. func ( string) string { if Separator == '/' { return } return replaceStringByte(, '/', Separator) } func replaceStringByte( string, , byte) string { if stringslite.IndexByte(, ) == -1 { return } := []byte() for := range { if [] == { [] = } } return string() } // Split is filepath.Split. func ( string) (, string) { := VolumeName() := len() - 1 for >= len() && !IsPathSeparator([]) { -- } return [:+1], [+1:] } // Ext is filepath.Ext. func ( string) string { for := len() - 1; >= 0 && !IsPathSeparator([]); -- { if [] == '.' { return [:] } } return "" } // Base is filepath.Base. func ( string) string { if == "" { return "." } // Strip trailing slashes. for len() > 0 && IsPathSeparator([len()-1]) { = [0 : len()-1] } // Throw away volume name = [len(VolumeName()):] // Find the last element := len() - 1 for >= 0 && !IsPathSeparator([]) { -- } if >= 0 { = [+1:] } // If empty now, it had only slashes. if == "" { return string(Separator) } return } // Dir is filepath.Dir. func ( string) string { := VolumeName() := len() - 1 for >= len() && !IsPathSeparator([]) { -- } := Clean([len() : +1]) if == "." && len() > 2 { // must be UNC return } return + } // VolumeName is filepath.VolumeName. func ( string) string { return FromSlash([:volumeNameLen()]) } // VolumeNameLen returns the length of the leading volume name on Windows. // It returns 0 elsewhere. func ( string) int { return volumeNameLen() }