// 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 cacheitem import "os" const _separator = os.PathSeparator // 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 (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) string() string { if b.buf == nil { return b.volAndPath[:b.volLen+b.w] } return b.volAndPath[:b.volLen] + string(b.buf[:b.w]) } // Clean is extracted from stdlib and removes `FromSlash` processing // of the stdlib version. // // 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 Clean(path string) string { originalPath := path volLen := volumeNameLen(path) path = path[volLen:] if path == "" { if volLen > 1 && originalPath[1] != ':' { // should be UNC // ORIGINAL: return FromSlash(originalPath) return originalPath } return originalPath + "." } rooted := os.IsPathSeparator(path[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. 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 os.IsPathSeparator(path[r]): // empty path element r++ case path[r] == '.' && r+1 == n: // . element r++ case path[r] == '.' && os.IsPathSeparator(path[r+1]): // ./ element r++ for r < len(path) && os.IsPathSeparator(path[r]) { r++ } if out.w == 0 && volumeNameLen(path[r:]) > 0 { // When joining prefix "." and an absolute path on Windows, // the prefix should not be removed. out.append('.') } case path[r] == '.' && path[r+1] == '.' && (r+2 == n || os.IsPathSeparator(path[r+2])): // .. element: remove to last separator r += 2 switch { case out.w > dotdot: // can backtrack out.w-- for out.w > dotdot && !os.IsPathSeparator(out.index(out.w)) { out.w-- } case !rooted: // cannot backtrack, but not rooted, so append .. element. if out.w > 0 { out.append(_separator) } out.append('.') out.append('.') dotdot = out.w } default: // real path element. // add slash if needed if rooted && out.w != 1 || !rooted && out.w != 0 { out.append(_separator) } // copy element for ; r < n && !os.IsPathSeparator(path[r]); r++ { out.append(path[r]) } } } // Turn empty string into "." if out.w == 0 { out.append('.') } // ORIGINAL: return FromSlash(out.string()) return out.string() }