8000 path/filepath, internal/filepathlite: move parts of filepath to filep… · golang/go@ceef063 · GitHub
[go: up one dir, main page]

Skip to content

Commit ceef063

Browse files
committed
path/filepath, internal/filepathlite: move parts of filepath to filepathlite
The path/filepath package needs to depend on the os package to implement Abs, Walk, and other functions. Move the implementation of purely lexical functions from path/filepath into internal/filepathlite, so they can be used by os and other low-level packages. Change-Id: Id211e547d6f1f58c82419695ff2d75cd6cd14a12 Reviewed-on: https://go-review.googlesource.com/c/go/+/566556 Reviewed-by: Behroz Karimpor <behrozkarimpor201@gmail.com> Reviewed-by: Ian Lance Taylor <iant@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
1 parent ad22356 commit ceef063

File tree

11 files changed

+544
-447
lines changed

11 files changed

+544
-447
lines changed

src/internal/filepathlite/path.go

Lines changed: 255 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,274 @@
1-
// Copyright 2022 The Go Authors. All rights reserved.
1+
// Copyright 2024 The Go Authors. All rights reserved.
22
// Use of this source code is governed by a BSD-style
33
// license that can be found in the LICENSE file.
44

5-
// Package filepathlite manipulates operating-system file paths.
5+
// Package filepathlite implements a subset of path/filepath,
6+
// only using packages which may be imported by "os".
7+
//
8+
// Tests for these functions are in path/filepath.
69
package filepathlite
710

811
import (
912
"errors"
13+
"internal/stringslite"
1014
"io/fs"
15+
"slices"
1116
)
1217

1318
var errInvalidPath = errors.New("invalid path")
1419

20+
// A lazybuf is a lazily constructed path buffer.
21+
// It supports append, reading previously appended bytes,
22+
// and retrieving the final string. It does not allocate a buffer
23+
// to hold the output until that output diverges from s.
24+
type lazybuf struct {
25+
path string
26+
buf []byte
27+
w int
28+
volAndPath string
29+
volLen int
30+
}
31+
32+
func (b *lazybuf) index(i int) byte {
33+
if b.buf != nil {
34+
return b.buf[i]
35+
}
36+
return b.path[i]
37+
}
38+
39+
func (b *lazybuf) append(c byte) {
40+
if b.buf == nil {
41+
if b.w < len(b.path) && b.path[b.w] == c {
42+
b.w++
43+
return
44+
}
45+
b.buf = make([]byte, len(b.path))
46+
copy(b.buf, b.path[:b.w])
47+
}
48+
b.buf[b.w] = c
49+
b.w++
50+
}
51+
52+
func (b *lazybuf) prepend(prefix ...byte) {
53+
b.buf = slices.Insert(b.buf, 0, prefix...)
54+
b.w += len(prefix)
55+
}
56+
57+
func (b *lazybuf) string() string {
58+
if b.buf == nil {
59+
return b.volAndPath[:b.volLen+b.w]
60+
}
61+
return b.volAndPath[:b.volLen] + string(b.buf[:b.w])
62+
}
63+
64+
// Clean is filepath.Clean.
65+
func Clean(path string) string {
66+
originalPath := path
67+
volLen := volumeNameLen(path)
68+
path = path[volLen:]
69+
if path == "" {
70+
if volLen > 1 && IsPathSeparator(originalPath[0]) && IsPathSeparator(originalPath[1]) {
71+
// should be UNC
72+
return FromSlash(originalPath)
73+
}
74+
return originalPath + "."
75+
}
76+
rooted := IsPathSeparator(path[0])
77+
78+
// Invariants:
79+
// reading from path; r is index of next byte to process.
80+
// writing to buf; w is index of next byte to write.
81+
// dotdot is index in buf where .. must stop, either because
82+
// it is the leading slash or it is a leading ../../.. prefix.
83+
n := len(path)
84+
out := lazybuf{path: path, volAndPath: originalPath, volLen: volLen}
85+
r, dotdot := 0, 0
86+
if rooted {
87+
out.append(Separator)
88+
r, dotdot = 1, 1
89+
}
90+
91+
for r < n {
92+
switch {
93+
case IsPathSeparator(path[r]):
94+
// empty path element
95+
r++
96+
case path[r] == '.' && (r+1 == n || IsPathSeparator(path[r+1])):
97+
// . element
98+
r++
99+
case path[r] == '.' && path[r+1] == '.' && (r+2 == n || IsPathSeparator(path[r+2])):
100+
// .. element: remove to last separator
101+
r += 2
102+
switch {
103+
case out.w > dotdot:
104+
// can backtrack
105+
out.w--
106+
for out.w > dotdot && !IsPathSeparator(out.index(out.w)) {
107+
out.w--
108+
}
109+
case !rooted:
110+
// cannot backtrack, but not rooted, so append .. element.
111+
if out.w > 0 {
112+
out.append(Separator)
113+
}
114+
out.append('.')
115+
out.append('.')
116+
dotdot = out.w
117+
}
118+
default:
119+
// real path element.
120+
// add slash if needed
121+
if rooted && out.w != 1 || !rooted && out.w != 0 {
122+
out.append(Separator)
123+
}
124+
// copy element
125+
for ; r < n && !IsPathSeparator(path[r]); r++ {
126+
out.append(path[r])
127+
}
128+
}
129+
}
130+
131+
// Turn empty string into "."
132+
if out.w == 0 {
133+
out.append('.')
134+
}
135+
136+
postClean(&out) // avoid creating absolute paths on Windows
137+
return FromSlash(out.string())
138+
}
139+
140+
// IsLocal is filepath.IsLocal.
141+
func IsLocal(path string) bool {
142+
return isLocal(path)
143+
}
144+
145+
func unixIsLocal(path string) bool {
146+
if IsAbs(path) || path == "" {
147+
return false
148+
}
149+
hasDots := false
150+
for p := path; p != ""; {
151+
var part string
152+
part, p, _ = stringslite.Cut(p, "/")
153+
if part == "." || part == ".." {
154+
hasDots = true
155+
break
156+
}
157+
}
158+
if hasDots {
159+
path = Clean(path)
160+
}
161+
if path == ".." || stringslite.HasPrefix(path, "../") {
162+
return false
163+
}
164+
return true
165+
}
166+
15167
// Localize is filepath.Localize.
16-
//
17-
// It is implemented in this package to avoid a dependency cycle
18-
// between os and file/filepath.
19-
//
20-
// Tests for this function are in path/filepath.
21168
func Localize(path string) (string, error) {
22169
if !fs.ValidPath(path) {
23170
return "", errInvalidPath
24171
}
25172
return localize(path)
26173
}
174+
175+
// ToSlash is filepath.ToSlash.
176+
func ToSlash(path string) string {
177+
if Separator == '/' {
178+
return path
179+
}
180+
return replaceStringByte(path, Separator, '/')
181+
}
182+
183+
// FromSlash is filepath.ToSlash.
184+
func FromSlash(path string) string {
185+
if Separator == '/' {
186+
return path
187+
}
188+
return replaceStringByte(path, '/', Separator)
189+
}
190+
191+
func replaceStringByte(s string, old, new byte) string {
192+
if stringslite.IndexByte(s, old) == -1 {
193+
return s
194+
}
195+
n := []byte(s)
196+
for i := range n {
197+
if n[i] == old {
198+
n[i] = new
199+
}
200+
}
201+
return string(n)
202+
}
203+
204+
// Split is filepath.Split.
205+
func Split(path string) (dir, file string) {
206+
vol := VolumeName(path)
207+
i := len(path) - 1
208+
for i >= len(vol) && !IsPathSeparator(path[i]) {
209+
i--
210+
}
211+
return path[:i+1], path[i+1:]
212+
}
213+
214+
// Ext is filepath.Ext.
215+
func Ext(path string) string {
216+
for i := len(path) - 1; i >= 0 && !IsPathSeparator(path[i]); i-- {
217+
if path[i] == '.' {
218+
return path[i:]
219+
}
220+
}
221+
return ""
222+
}
223+
224+
// Base is filepath.Base.
225+
func Base(path string) string {
226+
if path == "" {
227+
return "."
228+
}
229+
// Strip trailing slashes.
230+
for len(path) > 0 && IsPathSeparator(path[len(path)-1]) {
231+
path = path[0 : len(path)-1]
232+
}
233+
// Throw away volume name
234+
path = path[len(VolumeName(path)):]
235+
// Find the last element
236+
i := len(path) - 1
237+
for i >= 0 && !IsPathSeparator(path[i]) {
238+
i--
239+
}
240+
if i >= 0 {
241+
path = path[i+1:]
242+
}
243+
// If empty now, it had only slashes.
244+
if path == "" {
245+
return string(Separator)
246+
}
247+
return path
248+
}
249+
250+
// Dir is filepath.Dir.
251+
func Dir(path string) string {
252+
vol := VolumeName(path)
253+
i := len(path) - 1
254+
for i >= len(vol) && !IsPathSeparator(path[i]) {
255+
i--
256+
}
257+
dir := Clean(path[len(vol) : i+1])
258+
if dir == "." && len(vol) > 2 {
259+
// must be UNC
260+
return vol
261+
}
262+
return vol + dir
263+
}
264+
265+
// VolumeName is filepath.VolumeName.
266+
func VolumeName(path string) string {
267+
return FromSlash(path[:volumeNameLen(path)])
268+
}
269+
270+
// VolumeNameLen returns the length of the leading volume name on Windows.
271+
// It returns 0 elsewhere.
272+
func VolumeNameLen(path string) int {
273+
return volumeNameLen(path)
274+
}

src/path/filepath/path_nonwindows.go renamed to src/internal/filepathlite/path_nonwindows.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@
44

55
//go:build !windows
66

7-
package filepath
7+
package filepathlite
88

99
func postClean(out *lazybuf) {}
Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,41 @@
1-
// Copyright 2023 The Go Authors. All rights reserved.
1+
// Copyright 2010 The Go Authors. All rights reserved.
22
// Use of this source code is governed by a BSD-style
33
// license that can be found in the LICENSE file.
44

55
package filepathlite
66

7-
import "internal/bytealg"
7+
import (
8+
"internal/bytealg"
9+
"internal/stringslite"
10+
)
11+
12+
const (
13+
Separator = '/' // OS-specific path separator
14+
ListSeparator = '\000' // OS-specific path list separator
15+
)
16+
17+
func IsPathSeparator(c uint8) bool {
18+
return Separator == c
19+
}
20+
21+
func isLocal(path string) bool {
22+
return unixIsLocal(path)
23+
}
824

925
func localize(path string) (string, error) {
1026
if path[0] == '#' || bytealg.IndexByteString(path, 0) >= 0 {
1127
return "", errInvalidPath
1228
}
1329
return path, nil
1430
}
31+
32+
// IsAbs reports whether the path is absolute.
33+
func IsAbs(path string) bool {
34+
return stringslite.HasPrefix(path, "/") || stringslite.HasPrefix(path, "#")
35+
}
36+
37+
// volumeNameLen returns length of the leading volume name on Windows.
38+
// It returns 0 elsewhere.
39+
func volumeNameLen(path string) int {
40+
return 0
41+
}
Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,43 @@
1-
// Copyright 2023 The Go Authors. All rights reserved.
1+
// Copyright 2010 The Go Authors. All rights reserved.
22
// Use of this source code is governed by a BSD-style
33
// license that can be found in the LICENSE file.
44

55
//go:build unix || (js && wasm) || wasip1
66

77
package filepathlite
88

9-
import "internal/bytealg"
9+
import (
10+
"internal/bytealg"
11+
"internal/stringslite"
12+
)
13+
14+
const (
15+
Separator = '/' // OS-specific path separator
16+
ListSeparator = ':' // OS-specific path list separator
17+
)
18+
19+
func IsPathSeparator(c uint8) bool {
20+
return Separator == c
21+
}
22+
23+
func isLocal(path string) bool {
24+
return unixIsLocal(path)
25+
}
1026

1127
func localize(path string) (string, error) {
1228
if bytealg.IndexByteString(path, 0) >= 0 {
1329
return "", errInvalidPath
1430
}
1531
return path, nil
1632
}
33+
34+
// IsAbs reports whether the path is absolute.
35+
func IsAbs(path string) bool {
36+
return stringslite.HasPrefix(path, "/")
37+
}
38+
39+
// volumeNameLen returns length of the leading volume name on Windows.
40+
// It returns 0 elsewhere.
41+
func volumeNameLen(path string) int {
42+
return 0
43+
}

0 commit comments

Comments
 (0)
0