// 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 windows provides Windows-style path manipulation // that works on any platform. package winpath import ( "errors" "slices" "strings" ) 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 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 strings.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) }