goscript 0.0.38 → 0.0.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/compiler/analysis.go +15 -6
- package/compiler/compiler.go +184 -34
- package/compiler/expr-call.go +19 -9
- package/compiler/field.go +17 -3
- package/compiler/gs_dependencies_test.go +80 -0
- package/compiler/lit.go +12 -6
- package/compiler/output.go +10 -4
- package/compiler/spec.go +15 -2
- package/compiler/type-assert.go +111 -21
- package/compiler/type.go +37 -8
- package/dist/gs/builtin/builtin.d.ts +55 -0
- package/dist/gs/builtin/builtin.js +213 -0
- package/dist/gs/builtin/builtin.js.map +1 -1
- package/dist/gs/builtin/slice.js +13 -0
- package/dist/gs/builtin/slice.js.map +1 -1
- package/dist/gs/bytes/buffer.gs.d.ts +56 -0
- package/dist/gs/bytes/buffer.gs.js +611 -0
- package/dist/gs/bytes/buffer.gs.js.map +1 -0
- package/dist/gs/bytes/bytes.gs.d.ts +78 -0
- package/dist/gs/bytes/bytes.gs.js +1107 -0
- package/dist/gs/bytes/bytes.gs.js.map +1 -0
- package/dist/gs/bytes/index.d.ts +4 -0
- package/dist/gs/bytes/index.js +5 -0
- package/dist/gs/bytes/index.js.map +1 -0
- package/dist/gs/bytes/iter.gs.d.ts +9 -0
- package/dist/gs/bytes/iter.gs.js +143 -0
- package/dist/gs/bytes/iter.gs.js.map +1 -0
- package/dist/gs/bytes/reader.gs.d.ts +34 -0
- package/dist/gs/bytes/reader.gs.js +198 -0
- package/dist/gs/bytes/reader.gs.js.map +1 -0
- package/dist/gs/fmt/fmt.d.ts +49 -0
- package/dist/gs/fmt/fmt.js +322 -0
- package/dist/gs/fmt/fmt.js.map +1 -0
- package/dist/gs/fmt/index.d.ts +1 -0
- package/dist/gs/fmt/index.js +2 -0
- package/dist/gs/fmt/index.js.map +1 -0
- package/dist/gs/internal/bytealg/index.d.ts +14 -2
- package/dist/gs/internal/bytealg/index.js +114 -8
- package/dist/gs/internal/bytealg/index.js.map +1 -1
- package/dist/gs/path/filepath/index.d.ts +3 -0
- package/dist/gs/path/filepath/index.js +3 -0
- package/dist/gs/path/filepath/index.js.map +1 -0
- package/dist/gs/path/filepath/match.d.ts +3 -0
- package/dist/gs/path/filepath/match.js +212 -0
- package/dist/gs/path/filepath/match.js.map +1 -0
- package/dist/gs/path/filepath/path.d.ts +25 -0
- package/dist/gs/path/filepath/path.js +265 -0
- package/dist/gs/path/filepath/path.js.map +1 -0
- package/dist/gs/reflect/deepequal.d.ts +2 -1
- package/dist/gs/reflect/deepequal.js +5 -53
- package/dist/gs/reflect/deepequal.js.map +1 -1
- package/dist/gs/reflect/map.d.ts +14 -8
- package/dist/gs/reflect/map.js +15 -11
- package/dist/gs/reflect/map.js.map +1 -1
- package/dist/gs/reflect/type.d.ts +17 -9
- package/dist/gs/reflect/type.js +1 -1
- package/dist/gs/reflect/type.js.map +1 -1
- package/dist/gs/reflect/value.js +15 -6
- package/dist/gs/reflect/value.js.map +1 -1
- package/dist/gs/reflect/visiblefields.js +18 -12
- package/dist/gs/reflect/visiblefields.js.map +1 -1
- package/dist/gs/sort/index.d.ts +4 -0
- package/dist/gs/sort/index.js +4 -0
- package/dist/gs/sort/index.js.map +1 -0
- package/dist/gs/sort/search.gs.d.ts +6 -0
- package/dist/gs/sort/search.gs.js +125 -0
- package/dist/gs/sort/search.gs.js.map +1 -0
- package/dist/gs/sort/slice.gs.d.ts +4 -0
- package/dist/gs/sort/slice.gs.js +49 -0
- package/dist/gs/sort/slice.gs.js.map +1 -0
- package/dist/gs/sort/sort.gs.d.ts +37 -0
- package/dist/gs/sort/sort.gs.js +203 -0
- package/dist/gs/sort/sort.gs.js.map +1 -0
- package/dist/gs/unicode/utf8/utf8.d.ts +1 -1
- package/dist/gs/unicode/utf8/utf8.js +4 -2
- package/dist/gs/unicode/utf8/utf8.js.map +1 -1
- package/gs/builtin/builtin.ts +236 -0
- package/gs/builtin/slice.ts +17 -1
- package/gs/bytes/buffer.gs.ts +614 -0
- package/gs/bytes/bytes.gs.ts +1288 -0
- package/gs/bytes/godoc.txt +69 -0
- package/gs/bytes/index.ts +69 -0
- package/gs/bytes/iter.gs.ts +149 -0
- package/gs/bytes/metadata.go +12 -0
- package/gs/bytes/reader.gs.ts +230 -0
- package/gs/fmt/fmt.ts +407 -0
- package/gs/fmt/godoc.txt +382 -0
- package/gs/fmt/index.ts +31 -0
- package/gs/fmt/metadata.go +7 -0
- package/gs/internal/bytealg/index.ts +125 -10
- package/gs/internal/metadata.go +7 -0
- package/gs/io/metadata.go +11 -0
- package/gs/maps/metadata.go +8 -0
- package/gs/math/metadata.go +7 -0
- package/gs/os/metadata.go +17 -0
- package/gs/path/filepath/godoc.txt +35 -0
- package/gs/path/filepath/index.ts +27 -0
- package/gs/path/filepath/match.test.ts +274 -0
- package/gs/path/filepath/match.ts +249 -0
- package/gs/path/filepath/path.test.ts +246 -0
- package/gs/path/filepath/path.ts +328 -0
- package/gs/path/metadata.go +8 -0
- package/gs/reflect/deepequal.test.ts +41 -0
- package/gs/reflect/deepequal.ts +19 -4
- package/gs/reflect/map.test.ts +30 -0
- package/gs/reflect/map.ts +22 -18
- package/gs/reflect/metadata.go +7 -0
- package/gs/reflect/type.ts +19 -15
- package/gs/reflect/value.ts +21 -7
- package/gs/reflect/visiblefields.ts +17 -13
- package/gs/sort/godoc.txt +27 -0
- package/gs/sort/index.ts +24 -0
- package/gs/sort/search.gs.ts +128 -0
- package/gs/sort/slice.gs.ts +59 -0
- package/gs/sort/sort.gs.ts +227 -0
- package/gs/strconv/metadata.go +7 -0
- package/gs/strings/metadata.go +11 -0
- package/gs/sync/metadata.go +7 -0
- package/gs/unicode/utf8/utf8.ts +8 -5
- package/package.json +1 -1
- package/dist/gs/internal/testlog/index.d.ts +0 -1
- package/dist/gs/internal/testlog/index.js +0 -5
- package/dist/gs/internal/testlog/index.js.map +0 -1
- package/dist/gs/maps/iter.gs.d.ts +0 -7
- package/dist/gs/maps/iter.gs.js +0 -65
- package/dist/gs/maps/iter.gs.js.map +0 -1
- package/dist/gs/maps/maps.gs.d.ts +0 -7
- package/dist/gs/maps/maps.gs.js +0 -79
- package/dist/gs/maps/maps.gs.js.map +0 -1
- package/dist/gs/reflect/abi.d.ts +0 -59
- package/dist/gs/reflect/abi.gs.d.ts +0 -59
- package/dist/gs/reflect/abi.gs.js +0 -79
- package/dist/gs/reflect/abi.gs.js.map +0 -1
- package/dist/gs/reflect/abi.js +0 -79
- package/dist/gs/reflect/abi.js.map +0 -1
- package/dist/gs/reflect/badlinkname.d.ts +0 -52
- package/dist/gs/reflect/badlinkname.gs.d.ts +0 -52
- package/dist/gs/reflect/badlinkname.gs.js +0 -72
- package/dist/gs/reflect/badlinkname.gs.js.map +0 -1
- package/dist/gs/reflect/badlinkname.js +0 -72
- package/dist/gs/reflect/badlinkname.js.map +0 -1
- package/dist/gs/reflect/deepequal.gs.d.ts +0 -25
- package/dist/gs/reflect/deepequal.gs.js +0 -308
- package/dist/gs/reflect/deepequal.gs.js.map +0 -1
- package/dist/gs/reflect/float32reg_generic.gs.d.ts +0 -2
- package/dist/gs/reflect/float32reg_generic.gs.js +0 -10
- package/dist/gs/reflect/float32reg_generic.gs.js.map +0 -1
- package/dist/gs/reflect/index.gs.d.ts +0 -1
- package/dist/gs/reflect/index.gs.js +0 -3
- package/dist/gs/reflect/index.gs.js.map +0 -1
- package/dist/gs/reflect/iter.gs.d.ts +0 -3
- package/dist/gs/reflect/iter.gs.js +0 -24
- package/dist/gs/reflect/iter.gs.js.map +0 -1
- package/dist/gs/reflect/makefunc.gs.d.ts +0 -34
- package/dist/gs/reflect/makefunc.gs.js +0 -288
- package/dist/gs/reflect/makefunc.gs.js.map +0 -1
- package/dist/gs/reflect/map_swiss.gs.d.ts +0 -14
- package/dist/gs/reflect/map_swiss.gs.js +0 -70
- package/dist/gs/reflect/map_swiss.gs.js.map +0 -1
- package/dist/gs/reflect/reflect.gs.d.ts +0 -132
- package/dist/gs/reflect/reflect.gs.js +0 -437
- package/dist/gs/reflect/reflect.gs.js.map +0 -1
- package/dist/gs/reflect/swapper.gs.d.ts +0 -1
- package/dist/gs/reflect/swapper.gs.js +0 -32
- package/dist/gs/reflect/swapper.gs.js.map +0 -1
- package/dist/gs/reflect/type.gs.d.ts +0 -4
- package/dist/gs/reflect/type.gs.js +0 -21
- package/dist/gs/reflect/type.gs.js.map +0 -1
- package/dist/gs/reflect/value.gs.d.ts +0 -4
- package/dist/gs/reflect/value.gs.js +0 -12
- package/dist/gs/reflect/value.gs.js.map +0 -1
- package/dist/gs/reflect/visiblefields.gs.d.ts +0 -3
- package/dist/gs/reflect/visiblefields.gs.js +0 -123
- package/dist/gs/reflect/visiblefields.gs.js.map +0 -1
- package/dist/gs/stringslite/index.d.ts +0 -1
- package/dist/gs/stringslite/index.js +0 -2
- package/dist/gs/stringslite/index.js.map +0 -1
- package/dist/gs/stringslite/strings.d.ts +0 -11
- package/dist/gs/stringslite/strings.js +0 -67
- package/dist/gs/stringslite/strings.js.map +0 -1
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export { ErrBadPattern, Glob, Match } from './match.js'
|
|
2
|
+
export {
|
|
3
|
+
Abs,
|
|
4
|
+
Base,
|
|
5
|
+
Clean,
|
|
6
|
+
Dir,
|
|
7
|
+
EvalSymlinks,
|
|
8
|
+
Ext,
|
|
9
|
+
FromSlash,
|
|
10
|
+
IsAbs,
|
|
11
|
+
IsLocal,
|
|
12
|
+
Join,
|
|
13
|
+
ListSeparator,
|
|
14
|
+
Localize,
|
|
15
|
+
Rel,
|
|
16
|
+
Separator,
|
|
17
|
+
SkipAll,
|
|
18
|
+
SkipDir,
|
|
19
|
+
Split,
|
|
20
|
+
SplitList,
|
|
21
|
+
ToSlash,
|
|
22
|
+
VolumeName,
|
|
23
|
+
Walk,
|
|
24
|
+
WalkDir,
|
|
25
|
+
HasPrefix,
|
|
26
|
+
} from './path.js'
|
|
27
|
+
export type { WalkFunc } from './path.js'
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { Match, Glob, ErrBadPattern } from './match.js'
|
|
3
|
+
|
|
4
|
+
describe('path/filepath - Pattern matching functions', () => {
|
|
5
|
+
describe('Match', () => {
|
|
6
|
+
describe('Basic patterns', () => {
|
|
7
|
+
it('should match exact strings', () => {
|
|
8
|
+
const [matched, err] = Match('file.txt', 'file.txt')
|
|
9
|
+
expect(err).toBeNull()
|
|
10
|
+
expect(matched).toBe(true)
|
|
11
|
+
|
|
12
|
+
const [notMatched, err2] = Match('file.txt', 'other.txt')
|
|
13
|
+
expect(err2).toBeNull()
|
|
14
|
+
expect(notMatched).toBe(false)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it('should handle empty patterns and names', () => {
|
|
18
|
+
const [empty1, err1] = Match('', '')
|
|
19
|
+
expect(err1).toBeNull()
|
|
20
|
+
expect(empty1).toBe(true)
|
|
21
|
+
|
|
22
|
+
const [empty2, err2] = Match('', 'file')
|
|
23
|
+
expect(err2).toBeNull()
|
|
24
|
+
expect(empty2).toBe(false)
|
|
25
|
+
|
|
26
|
+
const [empty3, err3] = Match('pattern', '')
|
|
27
|
+
expect(err3).toBeNull()
|
|
28
|
+
expect(empty3).toBe(false)
|
|
29
|
+
})
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
describe('Wildcard patterns', () => {
|
|
33
|
+
it('should match with * (star)', () => {
|
|
34
|
+
const [match1, err1] = Match('*.txt', 'file.txt')
|
|
35
|
+
expect(err1).toBeNull()
|
|
36
|
+
expect(match1).toBe(true)
|
|
37
|
+
|
|
38
|
+
const [match2, err2] = Match('*.txt', 'file.doc')
|
|
39
|
+
expect(err2).toBeNull()
|
|
40
|
+
expect(match2).toBe(false)
|
|
41
|
+
|
|
42
|
+
const [match3, err3] = Match('file.*', 'file.txt')
|
|
43
|
+
expect(err3).toBeNull()
|
|
44
|
+
expect(match3).toBe(true)
|
|
45
|
+
|
|
46
|
+
const [match4, err4] = Match('*', 'anything')
|
|
47
|
+
expect(err4).toBeNull()
|
|
48
|
+
expect(match4).toBe(true)
|
|
49
|
+
|
|
50
|
+
const [match5, err5] = Match('*', '')
|
|
51
|
+
expect(err5).toBeNull()
|
|
52
|
+
expect(match5).toBe(true)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('should match with ? (question mark)', () => {
|
|
56
|
+
const [match1, err1] = Match('file?.txt', 'file1.txt')
|
|
57
|
+
expect(err1).toBeNull()
|
|
58
|
+
expect(match1).toBe(true)
|
|
59
|
+
|
|
60
|
+
const [match2, err2] = Match('file?.txt', 'fileAB.txt')
|
|
61
|
+
expect(err2).toBeNull()
|
|
62
|
+
expect(match2).toBe(false)
|
|
63
|
+
|
|
64
|
+
const [match3, err3] = Match('?', 'a')
|
|
65
|
+
expect(err3).toBeNull()
|
|
66
|
+
expect(match3).toBe(true)
|
|
67
|
+
|
|
68
|
+
const [match4, err4] = Match('?', '')
|
|
69
|
+
expect(err4).toBeNull()
|
|
70
|
+
expect(match4).toBe(false)
|
|
71
|
+
|
|
72
|
+
const [match5, err5] = Match('?', 'ab')
|
|
73
|
+
expect(err5).toBeNull()
|
|
74
|
+
expect(match5).toBe(false)
|
|
75
|
+
})
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
describe('Character classes', () => {
|
|
79
|
+
it('should match character ranges', () => {
|
|
80
|
+
const [match1, err1] = Match('[a-z]', 'c')
|
|
81
|
+
expect(err1).toBeNull()
|
|
82
|
+
expect(match1).toBe(true)
|
|
83
|
+
|
|
84
|
+
const [match2, err2] = Match('[a-z]', 'Z')
|
|
85
|
+
expect(err2).toBeNull()
|
|
86
|
+
expect(match2).toBe(false)
|
|
87
|
+
|
|
88
|
+
const [match3, err3] = Match('[0-9]', '5')
|
|
89
|
+
expect(err3).toBeNull()
|
|
90
|
+
expect(match3).toBe(true)
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it('should match specific characters', () => {
|
|
94
|
+
const [match1, err1] = Match('[abc]', 'b')
|
|
95
|
+
expect(err1).toBeNull()
|
|
96
|
+
expect(match1).toBe(true)
|
|
97
|
+
|
|
98
|
+
const [match2, err2] = Match('[abc]', 'd')
|
|
99
|
+
expect(err2).toBeNull()
|
|
100
|
+
expect(match2).toBe(false)
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
it('should handle negated character classes', () => {
|
|
104
|
+
const [match1, err1] = Match('[^abc]', 'd')
|
|
105
|
+
expect(err1).toBeNull()
|
|
106
|
+
expect(match1).toBe(true)
|
|
107
|
+
|
|
108
|
+
const [match2, err2] = Match('[^abc]', 'a')
|
|
109
|
+
expect(err2).toBeNull()
|
|
110
|
+
expect(match2).toBe(false)
|
|
111
|
+
|
|
112
|
+
const [match3, err3] = Match('[^a-z]', '1')
|
|
113
|
+
expect(err3).toBeNull()
|
|
114
|
+
expect(match3).toBe(true)
|
|
115
|
+
|
|
116
|
+
const [match4, err4] = Match('[^a-z]', 'c')
|
|
117
|
+
expect(err4).toBeNull()
|
|
118
|
+
expect(match4).toBe(false)
|
|
119
|
+
})
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
describe('Escaped characters', () => {
|
|
123
|
+
it('should handle escaped special characters', () => {
|
|
124
|
+
const [match1, err1] = Match('\\*', '*')
|
|
125
|
+
expect(err1).toBeNull()
|
|
126
|
+
expect(match1).toBe(true)
|
|
127
|
+
|
|
128
|
+
const [match2, err2] = Match('\\?', '?')
|
|
129
|
+
expect(err2).toBeNull()
|
|
130
|
+
expect(match2).toBe(true)
|
|
131
|
+
|
|
132
|
+
const [match3, err3] = Match('\\[', '[')
|
|
133
|
+
expect(err3).toBeNull()
|
|
134
|
+
expect(match3).toBe(true)
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
it('should handle escaped characters in character classes', () => {
|
|
138
|
+
const [match1, err1] = Match('[\\-]', '-')
|
|
139
|
+
expect(err1).toBeNull()
|
|
140
|
+
expect(match1).toBe(true)
|
|
141
|
+
|
|
142
|
+
const [match2, err2] = Match('[\\]]', ']')
|
|
143
|
+
expect(err2).toBeNull()
|
|
144
|
+
expect(match2).toBe(true)
|
|
145
|
+
})
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
describe('Complex patterns', () => {
|
|
149
|
+
it('should match complex combinations', () => {
|
|
150
|
+
const [match1, err1] = Match('dir/*', 'dir/file.txt')
|
|
151
|
+
expect(err1).toBeNull()
|
|
152
|
+
expect(match1).toBe(true)
|
|
153
|
+
|
|
154
|
+
const [match2, err2] = Match('*.{txt,doc}', 'file.txt')
|
|
155
|
+
expect(err2).toBeNull()
|
|
156
|
+
// This pattern is not supported in our implementation, should not match
|
|
157
|
+
expect(match2).toBe(false)
|
|
158
|
+
|
|
159
|
+
const [match3, err3] = Match('file[0-9].txt', 'file5.txt')
|
|
160
|
+
expect(err3).toBeNull()
|
|
161
|
+
expect(match3).toBe(true)
|
|
162
|
+
|
|
163
|
+
const [match4, err4] = Match('*.[tT][xX][tT]', 'file.TXT')
|
|
164
|
+
expect(err4).toBeNull()
|
|
165
|
+
expect(match4).toBe(true)
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
it('should not match across path separators with ?', () => {
|
|
169
|
+
const [match1, err1] = Match('dir?file', 'dir/file')
|
|
170
|
+
expect(err1).toBeNull()
|
|
171
|
+
expect(match1).toBe(false)
|
|
172
|
+
})
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
describe('Error cases', () => {
|
|
176
|
+
it('should return error for malformed patterns', () => {
|
|
177
|
+
const [_unclosed, err1] = Match('[unclosed', 'test')
|
|
178
|
+
expect(err1).toBe(ErrBadPattern)
|
|
179
|
+
|
|
180
|
+
const [_trailing, err2] = Match('trailing\\', 'test')
|
|
181
|
+
expect(err2).toBe(ErrBadPattern)
|
|
182
|
+
|
|
183
|
+
const [_bracket, err3] = Match('[', 'test')
|
|
184
|
+
expect(err3).toBe(ErrBadPattern)
|
|
185
|
+
})
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
describe('Real-world examples', () => {
|
|
189
|
+
it('should match common file patterns', () => {
|
|
190
|
+
// JavaScript files
|
|
191
|
+
const [js1, _js1] = Match('*.js', 'app.js')
|
|
192
|
+
expect(js1).toBe(true)
|
|
193
|
+
|
|
194
|
+
const [js2, _js2] = Match('*.js', 'app.jsx')
|
|
195
|
+
expect(js2).toBe(false)
|
|
196
|
+
|
|
197
|
+
// Backup files
|
|
198
|
+
const [bak1, _bak1] = Match('*.bak', 'file.txt.bak')
|
|
199
|
+
expect(bak1).toBe(true)
|
|
200
|
+
|
|
201
|
+
// Hidden files
|
|
202
|
+
const [hidden1, _hidden1] = Match('.*', '.hidden')
|
|
203
|
+
expect(hidden1).toBe(true)
|
|
204
|
+
|
|
205
|
+
const [hidden2, _hidden2] = Match('.*', 'visible')
|
|
206
|
+
expect(hidden2).toBe(false)
|
|
207
|
+
|
|
208
|
+
// Version numbers
|
|
209
|
+
const [ver1, _ver1] = Match('v[0-9]*', 'v1.2.3')
|
|
210
|
+
expect(ver1).toBe(true)
|
|
211
|
+
|
|
212
|
+
const [ver2, _ver2] = Match('v[0-9]*', 'version')
|
|
213
|
+
expect(ver2).toBe(false)
|
|
214
|
+
})
|
|
215
|
+
})
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
describe('Glob', () => {
|
|
219
|
+
it('should validate patterns but return empty results (no filesystem)', () => {
|
|
220
|
+
// Valid patterns should not error
|
|
221
|
+
const [files1, err1] = Glob('*.txt')
|
|
222
|
+
expect(err1).toBeNull()
|
|
223
|
+
expect(files1).toEqual([])
|
|
224
|
+
|
|
225
|
+
const [files2, err2] = Glob('dir/*')
|
|
226
|
+
expect(err2).toBeNull()
|
|
227
|
+
expect(files2).toEqual([])
|
|
228
|
+
|
|
229
|
+
// Invalid patterns should error
|
|
230
|
+
const [files3, err3] = Glob('[unclosed')
|
|
231
|
+
expect(err3).toBe(ErrBadPattern)
|
|
232
|
+
expect(files3).toEqual([])
|
|
233
|
+
})
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
describe('Pattern edge cases', () => {
|
|
237
|
+
it('should handle patterns at string boundaries', () => {
|
|
238
|
+
const [match1, _edge1] = Match('*txt', 'file.txt')
|
|
239
|
+
expect(match1).toBe(true)
|
|
240
|
+
|
|
241
|
+
const [match2, _edge2] = Match('file*', 'file.txt')
|
|
242
|
+
expect(match2).toBe(true)
|
|
243
|
+
|
|
244
|
+
const [match3, _edge3] = Match('*file*', 'myfile.txt')
|
|
245
|
+
expect(match3).toBe(true)
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
it('should handle multiple stars', () => {
|
|
249
|
+
const [match1, _star1] = Match('**', 'anything')
|
|
250
|
+
expect(match1).toBe(true)
|
|
251
|
+
|
|
252
|
+
const [match2, _star2] = Match('*.*.*', 'a.b.c')
|
|
253
|
+
expect(match2).toBe(true)
|
|
254
|
+
|
|
255
|
+
const [match3, _star3] = Match('*.*.*', 'a.b')
|
|
256
|
+
expect(match3).toBe(false)
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
it('should handle character class edge cases', () => {
|
|
260
|
+
// Empty character class (should be invalid)
|
|
261
|
+
const [_empty, err1] = Match('[]', 'test')
|
|
262
|
+
expect(err1).toBe(ErrBadPattern)
|
|
263
|
+
|
|
264
|
+
// Character class with dash
|
|
265
|
+
const [match1, err2] = Match('[a-]', 'a')
|
|
266
|
+
expect(err2).toBeNull()
|
|
267
|
+
expect(match1).toBe(true)
|
|
268
|
+
|
|
269
|
+
const [match2, err3] = Match('[a-]', '-')
|
|
270
|
+
expect(err3).toBeNull()
|
|
271
|
+
expect(match2).toBe(true)
|
|
272
|
+
})
|
|
273
|
+
})
|
|
274
|
+
})
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
// Pattern matching functions for filepath
|
|
2
|
+
|
|
3
|
+
export const ErrBadPattern = new Error('syntax error in pattern')
|
|
4
|
+
|
|
5
|
+
// Match reports whether name matches the shell file name pattern.
|
|
6
|
+
// The pattern syntax is:
|
|
7
|
+
//
|
|
8
|
+
// pattern:
|
|
9
|
+
// { term }
|
|
10
|
+
// term:
|
|
11
|
+
// '*' matches any sequence of non-Separator characters
|
|
12
|
+
// '?' matches any single non-Separator character
|
|
13
|
+
// '[' [ '^' ] { character-range } ']'
|
|
14
|
+
// character class (must be non-empty)
|
|
15
|
+
// c matches character c (c != '*', '?', '\\', '[')
|
|
16
|
+
// '\\' c matches character c
|
|
17
|
+
//
|
|
18
|
+
// character-range:
|
|
19
|
+
// c matches character c (c != '\\', '-', ']')
|
|
20
|
+
// '\\' c matches character c
|
|
21
|
+
// lo '-' hi matches character c for lo <= c <= hi
|
|
22
|
+
//
|
|
23
|
+
// Match requires pattern to match all of name, not just a substring.
|
|
24
|
+
// The only possible returned error is ErrBadPattern, when pattern
|
|
25
|
+
// is malformed.
|
|
26
|
+
export function Match(pattern: string, name: string): [boolean, Error | null] {
|
|
27
|
+
try {
|
|
28
|
+
// Validate pattern first
|
|
29
|
+
validatePattern(pattern)
|
|
30
|
+
return [matchPattern(pattern, name), null]
|
|
31
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
32
|
+
} catch (err) {
|
|
33
|
+
return [false, ErrBadPattern]
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function validatePattern(pattern: string): void {
|
|
38
|
+
let i = 0
|
|
39
|
+
|
|
40
|
+
while (i < pattern.length) {
|
|
41
|
+
const char = pattern[i]
|
|
42
|
+
|
|
43
|
+
switch (char) {
|
|
44
|
+
case '\\':
|
|
45
|
+
// Must be followed by another character
|
|
46
|
+
i++
|
|
47
|
+
if (i >= pattern.length) {
|
|
48
|
+
throw new Error('bad pattern')
|
|
49
|
+
}
|
|
50
|
+
i++
|
|
51
|
+
break
|
|
52
|
+
|
|
53
|
+
case '[': {
|
|
54
|
+
// Must have a properly closed character class
|
|
55
|
+
i++
|
|
56
|
+
let foundContent = false
|
|
57
|
+
let foundClose = false
|
|
58
|
+
|
|
59
|
+
// Skip negation
|
|
60
|
+
if (i < pattern.length && pattern[i] === '^') {
|
|
61
|
+
i++
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
while (i < pattern.length) {
|
|
65
|
+
if (pattern[i] === ']') {
|
|
66
|
+
foundClose = true
|
|
67
|
+
i++
|
|
68
|
+
break
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
foundContent = true
|
|
72
|
+
|
|
73
|
+
if (pattern[i] === '\\') {
|
|
74
|
+
i++ // Skip escape character
|
|
75
|
+
if (i >= pattern.length) {
|
|
76
|
+
throw new Error('bad pattern')
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
i++
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (!foundClose || !foundContent) {
|
|
83
|
+
throw new Error('bad pattern')
|
|
84
|
+
}
|
|
85
|
+
break
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
default:
|
|
89
|
+
i++
|
|
90
|
+
break
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function matchPattern(pattern: string, name: string): boolean {
|
|
96
|
+
let patternIndex = 0
|
|
97
|
+
let nameIndex = 0
|
|
98
|
+
|
|
99
|
+
while (patternIndex < pattern.length && nameIndex < name.length) {
|
|
100
|
+
const p = pattern[patternIndex]
|
|
101
|
+
|
|
102
|
+
switch (p) {
|
|
103
|
+
case '*':
|
|
104
|
+
// Handle star - match any sequence of characters
|
|
105
|
+
patternIndex++
|
|
106
|
+
if (patternIndex >= pattern.length) {
|
|
107
|
+
// Pattern ends with *, matches rest of name
|
|
108
|
+
return true
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Try to match the rest of the pattern with remaining name
|
|
112
|
+
for (let i = nameIndex; i <= name.length; i++) {
|
|
113
|
+
if (
|
|
114
|
+
matchPattern(pattern.substring(patternIndex), name.substring(i))
|
|
115
|
+
) {
|
|
116
|
+
return true
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return false
|
|
120
|
+
|
|
121
|
+
case '?':
|
|
122
|
+
// Match any single character except separator
|
|
123
|
+
if (name[nameIndex] === '/') {
|
|
124
|
+
return false
|
|
125
|
+
}
|
|
126
|
+
patternIndex++
|
|
127
|
+
nameIndex++
|
|
128
|
+
break
|
|
129
|
+
|
|
130
|
+
case '[': {
|
|
131
|
+
// Character class
|
|
132
|
+
const [matched, newPatternIndex] = matchCharClass(
|
|
133
|
+
pattern,
|
|
134
|
+
patternIndex,
|
|
135
|
+
name[nameIndex],
|
|
136
|
+
)
|
|
137
|
+
if (!matched) {
|
|
138
|
+
return false
|
|
139
|
+
}
|
|
140
|
+
patternIndex = newPatternIndex
|
|
141
|
+
nameIndex++
|
|
142
|
+
break
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
case '\\':
|
|
146
|
+
// Escaped character (pattern already validated)
|
|
147
|
+
patternIndex++
|
|
148
|
+
if (pattern[patternIndex] !== name[nameIndex]) {
|
|
149
|
+
return false
|
|
150
|
+
}
|
|
151
|
+
patternIndex++
|
|
152
|
+
nameIndex++
|
|
153
|
+
break
|
|
154
|
+
|
|
155
|
+
default:
|
|
156
|
+
// Literal character
|
|
157
|
+
if (p !== name[nameIndex]) {
|
|
158
|
+
return false
|
|
159
|
+
}
|
|
160
|
+
patternIndex++
|
|
161
|
+
nameIndex++
|
|
162
|
+
break
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Handle any remaining stars in pattern
|
|
167
|
+
while (patternIndex < pattern.length && pattern[patternIndex] === '*') {
|
|
168
|
+
patternIndex++
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Both pattern and name should be fully consumed
|
|
172
|
+
return patternIndex >= pattern.length && nameIndex >= name.length
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function matchCharClass(
|
|
176
|
+
pattern: string,
|
|
177
|
+
start: number,
|
|
178
|
+
char: string,
|
|
179
|
+
): [boolean, number] {
|
|
180
|
+
let index = start + 1
|
|
181
|
+
let negated = false
|
|
182
|
+
|
|
183
|
+
// Check for negation
|
|
184
|
+
if (index < pattern.length && pattern[index] === '^') {
|
|
185
|
+
negated = true
|
|
186
|
+
index++
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
let matched = false
|
|
190
|
+
|
|
191
|
+
while (index < pattern.length) {
|
|
192
|
+
if (pattern[index] === ']') {
|
|
193
|
+
index++
|
|
194
|
+
break
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (pattern[index] === '\\') {
|
|
198
|
+
// Escaped character
|
|
199
|
+
index++
|
|
200
|
+
if (pattern[index] === char) {
|
|
201
|
+
matched = true
|
|
202
|
+
}
|
|
203
|
+
index++
|
|
204
|
+
} else if (
|
|
205
|
+
index + 2 < pattern.length &&
|
|
206
|
+
pattern[index + 1] === '-' &&
|
|
207
|
+
pattern[index + 2] !== ']'
|
|
208
|
+
) {
|
|
209
|
+
// Character range
|
|
210
|
+
const lo = pattern[index]
|
|
211
|
+
const hi = pattern[index + 2]
|
|
212
|
+
if (char >= lo && char <= hi) {
|
|
213
|
+
matched = true
|
|
214
|
+
}
|
|
215
|
+
index += 3
|
|
216
|
+
} else {
|
|
217
|
+
// Single character
|
|
218
|
+
if (pattern[index] === char) {
|
|
219
|
+
matched = true
|
|
220
|
+
}
|
|
221
|
+
index++
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (negated) {
|
|
226
|
+
matched = !matched
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return [matched, index]
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Glob returns the names of all files matching pattern or null
|
|
233
|
+
// if there is no matching file. The syntax of patterns is the same
|
|
234
|
+
// as in Match. The pattern may describe hierarchical names such as
|
|
235
|
+
// /usr/*/bin/ed (assuming the Separator is '/').
|
|
236
|
+
//
|
|
237
|
+
// Glob ignores file system errors such as I/O errors reading directories.
|
|
238
|
+
// The only possible returned error is ErrBadPattern, when pattern is malformed.
|
|
239
|
+
export function Glob(pattern: string): [string[], Error | null] {
|
|
240
|
+
try {
|
|
241
|
+
// Validate the pattern using the same logic as Match
|
|
242
|
+
validatePattern(pattern)
|
|
243
|
+
// We don't have filesystem access, so return empty array
|
|
244
|
+
return [[], null]
|
|
245
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
246
|
+
} catch (err) {
|
|
247
|
+
return [[], ErrBadPattern]
|
|
248
|
+
}
|
|
249
|
+
}
|