codekingpro/portable-devtools
114k
1// Copyright 2009 The Go Authors. All rights reserved.2// Use of this source code is governed by a BSD-style3// license that can be found in the LICENSE file.4 5// Package path implements utility routines for manipulating slash-separated6// paths.7//8// The path package should only be used for paths separated by forward9// slashes, such as the paths in URLs. This package does not deal with10// Windows paths with drive letters or backslashes; to manipulate11// operating system paths, use the [path/filepath] package.12package path13 14import "internal/bytealg"15 16// A lazybuf is a lazily constructed path buffer.17// It supports append, reading previously appended bytes,18// and retrieving the final string. It does not allocate a buffer19// to hold the output until that output diverges from s.20type lazybuf struct {21 s string22 buf []byte23 w int24}25 26func (b *lazybuf) index(i int) byte {27 if b.buf != nil {28 return b.buf[i]29 }30 return b.s[i]31}32 33func (b *lazybuf) append(c byte) {34 if b.buf == nil {35 if b.w < len(b.s) && b.s[b.w] == c {36 b.w++37 return38 }39 b.buf = make([]byte, len(b.s))40 copy(b.buf, b.s[:b.w])41 }42 b.buf[b.w] = c43 b.w++44}45 46func (b *lazybuf) string() string {47 if b.buf == nil {48 return b.s[:b.w]49 }50 return string(b.buf[:b.w])51}52 53// Clean returns the shortest path name equivalent to path54// by purely lexical processing. It applies the following rules55// iteratively until no further processing can be done:56//57// 1. Replace multiple slashes with a single slash.58// 2. Eliminate each . path name element (the current directory).59// 3. Eliminate each inner .. path name element (the parent directory)60// along with the non-.. element that precedes it.61// 4. Eliminate .. elements that begin a rooted path:62// that is, replace "/.." by "/" at the beginning of a path.63//64// The returned path ends in a slash only if it is the root "/".65//66// If the result of this process is an empty string, Clean67// returns the string ".".68//69// See also Rob Pike, “Lexical File Names in Plan 9 or70// Getting Dot-Dot Right,”71// https://9p.io/sys/doc/lexnames.html72func Clean(path string) string {73 if path == "" {74 return "."75 }76 77 rooted := path[0] == '/'78 n := len(path)79 80 // Invariants:81 // reading from path; r is index of next byte to process.82 // writing to buf; w is index of next byte to write.83 // dotdot is index in buf where .. must stop, either because84 // it is the leading slash or it is a leading ../../.. prefix.85 out := lazybuf{s: path}86 r, dotdot := 0, 087 if rooted {88 out.append('/')89 r, dotdot = 1, 190 }91 92 for r < n {93 switch {94 case path[r] == '/':95 // empty path element96 r++97 case path[r] == '.' && (r+1 == n || path[r+1] == '/'):98 // . element99 r++100 case path[r] == '.' && path[r+1] == '.' && (r+2 == n || path[r+2] == '/'):101 // .. element: remove to last /102 r += 2103 switch {104 case out.w > dotdot:105 // can backtrack106 out.w--107 for out.w > dotdot && out.index(out.w) != '/' {108 out.w--109 }110 case !rooted:111 // cannot backtrack, but not rooted, so append .. element.112 if out.w > 0 {113 out.append('/')114 }115 out.append('.')116 out.append('.')117 dotdot = out.w118 }119 default:120 // real path element.121 // add slash if needed122 if rooted && out.w != 1 || !rooted && out.w != 0 {123 out.append('/')124 }125 // copy element126 for ; r < n && path[r] != '/'; r++ {127 out.append(path[r])128 }129 }130 }131 132 // Turn empty string into "."133 if out.w == 0 {134 return "."135 }136 137 return out.string()138}139 140// Split splits path immediately following the final slash,141// separating it into a directory and file name component.142// If there is no slash in path, Split returns an empty dir and143// file set to path.144// The returned values have the property that path = dir+file.145func Split(path string) (dir, file string) {146 i := bytealg.LastIndexByteString(path, '/')147 return path[:i+1], path[i+1:]148}149 150// Join joins any number of path elements into a single path,151// separating them with slashes. Empty elements are ignored.152// The result is Cleaned. However, if the argument list is153// empty or all its elements are empty, Join returns154// an empty string.155func Join(elem ...string) string {156 size := 0157 for _, e := range elem {158 size += len(e)159 }160 if size == 0 {161 return ""162 }163 buf := make([]byte, 0, size+len(elem)-1)164 for _, e := range elem {165 if len(buf) > 0 || e != "" {166 if len(buf) > 0 {167 buf = append(buf, '/')168 }169 buf = append(buf, e...)170 }171 }172 return Clean(string(buf))173}174 175// Ext returns the file name extension used by path.176// The extension is the suffix beginning at the final dot177// in the final slash-separated element of path;178// it is empty if there is no dot.179func Ext(path string) string {180 for i := len(path) - 1; i >= 0 && path[i] != '/'; i-- {181 if path[i] == '.' {182 return path[i:]183 }184 }185 return ""186}187 188// Base returns the last element of path.189// Trailing slashes are removed before extracting the last element.190// If the path is empty, Base returns ".".191// If the path consists entirely of slashes, Base returns "/".192func Base(path string) string {193 if path == "" {194 return "."195 }196 // Strip trailing slashes.197 for len(path) > 0 && path[len(path)-1] == '/' {198 path = path[0 : len(path)-1]199 }200 // Find the last element201 if i := bytealg.LastIndexByteString(path, '/'); i >= 0 {202 path = path[i+1:]203 }204 // If empty now, it had only slashes.205 if path == "" {206 return "/"207 }208 return path209}210 211// IsAbs reports whether the path is absolute.212func IsAbs(path string) bool {213 return len(path) > 0 && path[0] == '/'214}215 216// Dir returns all but the last element of path, typically the path's directory.217// After dropping the final element using [Split], the path is Cleaned and trailing218// slashes are removed.219// If the path is empty, Dir returns ".".220// If the path consists entirely of slashes followed by non-slash bytes, Dir221// returns a single slash. In any other case, the returned path does not end in a222// slash.223func Dir(path string) string {224 dir, _ := Split(path)225 return Clean(dir)226}227 