| | |
| | |
| | |
| |
|
| | |
| | |
| | |
| | |
| | package filepathlite |
| |
|
| | import ( |
| | "errors" |
| | "internal/stringslite" |
| | "io/fs" |
| | "slices" |
| | ) |
| |
|
| | var errInvalidPath = errors.New("invalid path") |
| |
|
| | |
| | |
| | |
| | |
| | type lazybuf struct { |
| | path string |
| | buf []byte |
| | w int |
| | volAndPath string |
| | volLen int |
| | } |
| |
|
| | func (b *lazybuf) index(i int) byte { |
| | if b.buf != nil { |
| | return b.buf[i] |
| | } |
| | return b.path[i] |
| | } |
| |
|
| | func (b *lazybuf) append(c byte) { |
| | if b.buf == nil { |
| | if b.w < len(b.path) && b.path[b.w] == c { |
| | b.w++ |
| | return |
| | } |
| | b.buf = make([]byte, len(b.path)) |
| | copy(b.buf, b.path[:b.w]) |
| | } |
| | b.buf[b.w] = c |
| | b.w++ |
| | } |
| |
|
| | func (b *lazybuf) prepend(prefix ...byte) { |
| | b.buf = slices.Insert(b.buf, 0, prefix...) |
| | b.w += len(prefix) |
| | } |
| |
|
| | func (b *lazybuf) string() string { |
| | if b.buf == nil { |
| | return b.volAndPath[:b.volLen+b.w] |
| | } |
| | return b.volAndPath[:b.volLen] + string(b.buf[:b.w]) |
| | } |
| |
|
| | |
| | func Clean(path string) string { |
| | originalPath := path |
| | volLen := volumeNameLen(path) |
| | path = path[volLen:] |
| | if path == "" { |
| | if volLen > 1 && IsPathSeparator(originalPath[0]) && IsPathSeparator(originalPath[1]) { |
| | |
| | return FromSlash(originalPath) |
| | } |
| | return originalPath + "." |
| | } |
| | rooted := IsPathSeparator(path[0]) |
| |
|
| | |
| | |
| | |
| | |
| | |
| | n := len(path) |
| | out := lazybuf{path: path, volAndPath: originalPath, volLen: volLen} |
| | r, dotdot := 0, 0 |
| | if rooted { |
| | out.append(Separator) |
| | r, dotdot = 1, 1 |
| | } |
| |
|
| | for r < n { |
| | switch { |
| | case IsPathSeparator(path[r]): |
| | |
| | r++ |
| | case path[r] == '.' && (r+1 == n || IsPathSeparator(path[r+1])): |
| | |
| | r++ |
| | case path[r] == '.' && path[r+1] == '.' && (r+2 == n || IsPathSeparator(path[r+2])): |
| | |
| | r += 2 |
| | switch { |
| | case out.w > dotdot: |
| | |
| | out.w-- |
| | for out.w > dotdot && !IsPathSeparator(out.index(out.w)) { |
| | out.w-- |
| | } |
| | case !rooted: |
| | |
| | if out.w > 0 { |
| | out.append(Separator) |
| | } |
| | out.append('.') |
| | out.append('.') |
| | dotdot = out.w |
| | } |
| | default: |
| | |
| | |
| | if rooted && out.w != 1 || !rooted && out.w != 0 { |
| | out.append(Separator) |
| | } |
| | |
| | for ; r < n && !IsPathSeparator(path[r]); r++ { |
| | out.append(path[r]) |
| | } |
| | } |
| | } |
| |
|
| | |
| | if out.w == 0 { |
| | out.append('.') |
| | } |
| |
|
| | postClean(&out) |
| | return FromSlash(out.string()) |
| | } |
| |
|
| | |
| | func IsLocal(path string) bool { |
| | return isLocal(path) |
| | } |
| |
|
| | func unixIsLocal(path string) bool { |
| | if IsAbs(path) || path == "" { |
| | return false |
| | } |
| | hasDots := false |
| | for p := path; p != ""; { |
| | var part string |
| | part, p, _ = stringslite.Cut(p, "/") |
| | if part == "." || part == ".." { |
| | hasDots = true |
| | break |
| | } |
| | } |
| | if hasDots { |
| | path = Clean(path) |
| | } |
| | if path == ".." || stringslite.HasPrefix(path, "../") { |
| | return false |
| | } |
| | return true |
| | } |
| |
|
| | |
| | func Localize(path string) (string, error) { |
| | if !fs.ValidPath(path) { |
| | return "", errInvalidPath |
| | } |
| | return localize(path) |
| | } |
| |
|
| | |
| | func ToSlash(path string) string { |
| | if Separator == '/' { |
| | return path |
| | } |
| | return replaceStringByte(path, Separator, '/') |
| | } |
| |
|
| | |
| | func FromSlash(path string) string { |
| | if Separator == '/' { |
| | return path |
| | } |
| | return replaceStringByte(path, '/', Separator) |
| | } |
| |
|
| | func replaceStringByte(s string, old, new byte) string { |
| | if stringslite.IndexByte(s, old) == -1 { |
| | return s |
| | } |
| | n := []byte(s) |
| | for i := range n { |
| | if n[i] == old { |
| | n[i] = new |
| | } |
| | } |
| | return string(n) |
| | } |
| |
|
| | |
| | func Split(path string) (dir, file string) { |
| | vol := VolumeName(path) |
| | i := len(path) - 1 |
| | for i >= len(vol) && !IsPathSeparator(path[i]) { |
| | i-- |
| | } |
| | return path[:i+1], path[i+1:] |
| | } |
| |
|
| | |
| | func Ext(path string) string { |
| | for i := len(path) - 1; i >= 0 && !IsPathSeparator(path[i]); i-- { |
| | if path[i] == '.' { |
| | return path[i:] |
| | } |
| | } |
| | return "" |
| | } |
| |
|
| | |
| | func Base(path string) string { |
| | if path == "" { |
| | return "." |
| | } |
| | |
| | for len(path) > 0 && IsPathSeparator(path[len(path)-1]) { |
| | path = path[0 : len(path)-1] |
| | } |
| | |
| | path = path[len(VolumeName(path)):] |
| | |
| | i := len(path) - 1 |
| | for i >= 0 && !IsPathSeparator(path[i]) { |
| | i-- |
| | } |
| | if i >= 0 { |
| | path = path[i+1:] |
| | } |
| | |
| | if path == "" { |
| | return string(Separator) |
| | } |
| | return path |
| | } |
| |
|
| | |
| | func Dir(path string) string { |
| | vol := VolumeName(path) |
| | i := len(path) - 1 |
| | for i >= len(vol) && !IsPathSeparator(path[i]) { |
| | i-- |
| | } |
| | dir := Clean(path[len(vol) : i+1]) |
| | if dir == "." && len(vol) > 2 { |
| | |
| | return vol |
| | } |
| | return vol + dir |
| | } |
| |
|
| | |
| | func VolumeName(path string) string { |
| | return FromSlash(path[:volumeNameLen(path)]) |
| | } |
| |
|
| | |
| | |
| | func VolumeNameLen(path string) int { |
| | return volumeNameLen(path) |
| | } |
| |
|