goscript 0.2.3 → 0.2.4
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/lowering.go +57 -8
- package/compiler/override-registry_test.go +125 -0
- package/compiler/skeleton_test.go +49 -2
- package/dist/gs/compress/gzip/index.d.ts +41 -0
- package/dist/gs/compress/gzip/index.js +235 -0
- package/dist/gs/compress/gzip/index.js.map +1 -0
- package/dist/gs/io/fs/glob.js +1 -1
- package/dist/gs/io/fs/glob.js.map +1 -1
- package/dist/gs/io/fs/readlink.d.ts +1 -1
- package/dist/gs/io/fs/readlink.js +2 -2
- package/dist/gs/io/fs/readlink.js.map +1 -1
- package/dist/gs/io/fs/stat.d.ts +4 -2
- package/dist/gs/io/fs/stat.js +12 -73
- package/dist/gs/io/fs/stat.js.map +1 -1
- package/dist/gs/io/fs/sub.d.ts +2 -2
- package/dist/gs/io/fs/sub.js +7 -7
- package/dist/gs/io/fs/sub.js.map +1 -1
- package/dist/gs/io/fs/walk.js +1 -1
- package/dist/gs/io/fs/walk.js.map +1 -1
- package/dist/gs/net/http/index.d.ts +18 -14
- package/dist/gs/net/http/index.js +44 -23
- package/dist/gs/net/http/index.js.map +1 -1
- package/dist/gs/net/http/pprof/index.d.ts +5 -5
- package/dist/gs/net/http/pprof/index.js +21 -21
- package/dist/gs/net/http/pprof/index.js.map +1 -1
- package/gs/builtin/runtime-contract.test.ts +25 -0
- package/gs/compress/gzip/index.test.ts +86 -0
- package/gs/compress/gzip/index.ts +297 -0
- package/gs/compress/gzip/meta.json +6 -0
- package/gs/compress/gzip/parity.json +45 -0
- package/gs/embed/index.test.ts +1 -1
- package/gs/io/fs/glob.ts +1 -1
- package/gs/io/fs/meta.json +3 -0
- package/gs/io/fs/readlink.test.ts +2 -2
- package/gs/io/fs/readlink.ts +5 -2
- package/gs/io/fs/stat.test.ts +79 -0
- package/gs/io/fs/stat.ts +24 -10
- package/gs/io/fs/sub.test.ts +93 -0
- package/gs/io/fs/sub.ts +9 -9
- package/gs/io/fs/walk.ts +1 -1
- package/gs/net/http/index.test.ts +207 -2
- package/gs/net/http/index.ts +68 -37
- package/gs/net/http/meta.json +3 -1
- package/gs/net/http/pprof/index.test.ts +4 -4
- package/gs/net/http/pprof/index.ts +30 -27
- package/package.json +1 -1
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import * as $ from '@goscript/builtin/index.js'
|
|
2
|
+
import * as errors from '@goscript/errors/index.js'
|
|
3
|
+
import * as io from '@goscript/io/index.js'
|
|
4
|
+
|
|
5
|
+
type maybeAsyncWriter = {
|
|
6
|
+
Write(p: $.Bytes): [number, $.GoError] | Promise<[number, $.GoError]>
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
type compressionRuntime = {
|
|
10
|
+
gzipSync?: (data: Uint8Array, opts?: Record<string, unknown>) => Uint8Array
|
|
11
|
+
gunzipSync?: (data: Uint8Array) => Uint8Array
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const NoCompression = 0
|
|
15
|
+
export const BestSpeed = 1
|
|
16
|
+
export const BestCompression = 9
|
|
17
|
+
export const DefaultCompression = -1
|
|
18
|
+
export const HuffmanOnly = -2
|
|
19
|
+
|
|
20
|
+
export let ErrChecksum = errors.New('gzip: invalid checksum')
|
|
21
|
+
export let ErrHeader = errors.New('gzip: invalid header')
|
|
22
|
+
|
|
23
|
+
export function __goscript_set_ErrChecksum(value: $.GoError): void {
|
|
24
|
+
ErrChecksum = value
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function __goscript_set_ErrHeader(value: $.GoError): void {
|
|
28
|
+
ErrHeader = value
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export class Header {
|
|
32
|
+
Comment = ''
|
|
33
|
+
Extra: $.Bytes = null
|
|
34
|
+
ModTime: any = null
|
|
35
|
+
Name = ''
|
|
36
|
+
OS = 255
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export class Reader {
|
|
40
|
+
private data: Uint8Array = new Uint8Array(0)
|
|
41
|
+
private offset = 0
|
|
42
|
+
private pending: Promise<{ data: Uint8Array | null; err: $.GoError }> | null =
|
|
43
|
+
null
|
|
44
|
+
|
|
45
|
+
constructor(r?: io.Reader | null) {
|
|
46
|
+
if (r != null) {
|
|
47
|
+
this.Reset(r)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async Read(p: $.Bytes): Promise<[number, $.GoError]> {
|
|
52
|
+
const pending = this.pending
|
|
53
|
+
if (pending != null) {
|
|
54
|
+
this.pending = null
|
|
55
|
+
const result = await pending
|
|
56
|
+
if (result.err != null) {
|
|
57
|
+
return [0, result.err]
|
|
58
|
+
}
|
|
59
|
+
this.data = result.data ?? new Uint8Array(0)
|
|
60
|
+
this.offset = 0
|
|
61
|
+
}
|
|
62
|
+
if (this.offset >= this.data.length) {
|
|
63
|
+
return [0, io.EOF]
|
|
64
|
+
}
|
|
65
|
+
const out = $.bytesToUint8Array(p)
|
|
66
|
+
const n = Math.min(out.length, this.data.length - this.offset)
|
|
67
|
+
out.set(this.data.subarray(this.offset, this.offset + n))
|
|
68
|
+
this.offset += n
|
|
69
|
+
return [n, null]
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
Close(): $.GoError {
|
|
73
|
+
return null
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
Reset(r: io.Reader | null): $.GoError {
|
|
77
|
+
if (r == null) {
|
|
78
|
+
return errors.New('gzip: nil reader')
|
|
79
|
+
}
|
|
80
|
+
const result = readGunzipped(r)
|
|
81
|
+
this.data = new Uint8Array(0)
|
|
82
|
+
this.offset = 0
|
|
83
|
+
if (result instanceof Promise) {
|
|
84
|
+
this.pending = result
|
|
85
|
+
return null
|
|
86
|
+
}
|
|
87
|
+
this.pending = null
|
|
88
|
+
if (result.err != null) {
|
|
89
|
+
return result.err
|
|
90
|
+
}
|
|
91
|
+
this.data = result.data ?? new Uint8Array(0)
|
|
92
|
+
return null
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export class Writer {
|
|
97
|
+
private chunks: Uint8Array[] = []
|
|
98
|
+
private closed = false
|
|
99
|
+
|
|
100
|
+
constructor(
|
|
101
|
+
private w: io.Writer | null,
|
|
102
|
+
private level = DefaultCompression,
|
|
103
|
+
) {}
|
|
104
|
+
|
|
105
|
+
Write(p: $.Bytes): [number, $.GoError] {
|
|
106
|
+
if (this.closed) {
|
|
107
|
+
return [0, errors.New('gzip: writer closed')]
|
|
108
|
+
}
|
|
109
|
+
const data = $.bytesToUint8Array(p)
|
|
110
|
+
this.chunks.push(data.slice())
|
|
111
|
+
return [data.length, null]
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async Close(): Promise<$.GoError> {
|
|
115
|
+
if (this.closed) {
|
|
116
|
+
return null
|
|
117
|
+
}
|
|
118
|
+
this.closed = true
|
|
119
|
+
if (this.w == null) {
|
|
120
|
+
return errors.New('gzip: nil writer')
|
|
121
|
+
}
|
|
122
|
+
const compressed = await gzipBytes(concat(this.chunks), this.level)
|
|
123
|
+
const writer = $.pointerValue<maybeAsyncWriter>(this.w)
|
|
124
|
+
const [, err] = await writer.Write(compressed)
|
|
125
|
+
return err
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
Flush(): $.GoError {
|
|
129
|
+
return null
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
Reset(w: io.Writer | null): void {
|
|
133
|
+
this.w = w
|
|
134
|
+
this.chunks = []
|
|
135
|
+
this.closed = false
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function NewReader(
|
|
140
|
+
r: io.Reader | null,
|
|
141
|
+
): [io.ReadCloser | null, $.GoError] {
|
|
142
|
+
const reader = new Reader()
|
|
143
|
+
const err = reader.Reset(r)
|
|
144
|
+
if (err != null) {
|
|
145
|
+
return [null, err]
|
|
146
|
+
}
|
|
147
|
+
return [reader as any, null]
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function NewWriter(w: io.Writer | null): Writer {
|
|
151
|
+
return new Writer(w)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function NewWriterLevel(
|
|
155
|
+
w: io.Writer | null,
|
|
156
|
+
level: number,
|
|
157
|
+
): [Writer | null, $.GoError] {
|
|
158
|
+
if (level < HuffmanOnly || level > BestCompression) {
|
|
159
|
+
return [null, errors.New(`gzip: invalid compression level: ${level}`)]
|
|
160
|
+
}
|
|
161
|
+
return [new Writer(w, level), null]
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function gzipBytes(data: Uint8Array, level: number): Promise<Uint8Array> {
|
|
165
|
+
const runtime = nodeCompressionRuntime()
|
|
166
|
+
if (runtime?.gzipSync != null) {
|
|
167
|
+
return runtime.gzipSync(data, { level })
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const CompressionStreamCtor = (globalThis as any).CompressionStream
|
|
171
|
+
if (typeof CompressionStreamCtor !== 'function') {
|
|
172
|
+
throw new Error('compress/gzip: CompressionStream unavailable')
|
|
173
|
+
}
|
|
174
|
+
return streamTransform(data, new CompressionStreamCtor('gzip'))
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function gunzipBytes(
|
|
178
|
+
data: Uint8Array,
|
|
179
|
+
): Uint8Array | Promise<Uint8Array> {
|
|
180
|
+
const runtime = nodeCompressionRuntime()
|
|
181
|
+
if (runtime?.gunzipSync != null) {
|
|
182
|
+
return runtime.gunzipSync(data)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const DecompressionStreamCtor = (globalThis as any).DecompressionStream
|
|
186
|
+
if (typeof DecompressionStreamCtor !== 'function') {
|
|
187
|
+
throw new Error('compress/gzip: DecompressionStream unavailable')
|
|
188
|
+
}
|
|
189
|
+
return streamTransform(data, new DecompressionStreamCtor('gzip'))
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async function streamTransform(
|
|
193
|
+
data: Uint8Array,
|
|
194
|
+
stream: ReadableWritablePair<Uint8Array, Uint8Array>,
|
|
195
|
+
): Promise<Uint8Array> {
|
|
196
|
+
const body = new ArrayBuffer(data.length)
|
|
197
|
+
new Uint8Array(body).set(data)
|
|
198
|
+
const response = new Response(
|
|
199
|
+
new Blob([body]).stream().pipeThrough(stream as any),
|
|
200
|
+
)
|
|
201
|
+
return new Uint8Array(await response.arrayBuffer())
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function readGunzipped(
|
|
205
|
+
r: io.Reader,
|
|
206
|
+
):
|
|
207
|
+
| { data: Uint8Array | null; err: $.GoError }
|
|
208
|
+
| Promise<{ data: Uint8Array | null; err: $.GoError }> {
|
|
209
|
+
const chunks: Uint8Array[] = []
|
|
210
|
+
const buf = $.makeSlice<number>(1, undefined, 'byte')
|
|
211
|
+
while (true) {
|
|
212
|
+
const read = r.Read(buf)
|
|
213
|
+
if (read instanceof Promise) {
|
|
214
|
+
return readGunzippedAsync(read, r, buf, chunks)
|
|
215
|
+
}
|
|
216
|
+
const [n, err] = read
|
|
217
|
+
recordChunk(chunks, buf, n)
|
|
218
|
+
if (err != null) {
|
|
219
|
+
if (err === io.EOF) {
|
|
220
|
+
return inflateRecorded(chunks)
|
|
221
|
+
}
|
|
222
|
+
return { data: null, err }
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async function readGunzippedAsync(
|
|
228
|
+
read: Promise<[number, $.GoError]>,
|
|
229
|
+
r: io.Reader,
|
|
230
|
+
buf: $.Bytes,
|
|
231
|
+
chunks: Uint8Array[],
|
|
232
|
+
): Promise<{ data: Uint8Array | null; err: $.GoError }> {
|
|
233
|
+
while (true) {
|
|
234
|
+
const [n, err] = await read
|
|
235
|
+
recordChunk(chunks, buf, n)
|
|
236
|
+
if (err != null) {
|
|
237
|
+
if (err === io.EOF) {
|
|
238
|
+
return inflateRecorded(chunks)
|
|
239
|
+
}
|
|
240
|
+
return { data: null, err }
|
|
241
|
+
}
|
|
242
|
+
read = r.Read(buf) as any
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function recordChunk(chunks: Uint8Array[], buf: $.Bytes, n: number): void {
|
|
247
|
+
if (n > 0) {
|
|
248
|
+
chunks.push($.bytesToUint8Array($.goSlice(buf, 0, n)).slice())
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function inflateRecorded(
|
|
253
|
+
chunks: Uint8Array[],
|
|
254
|
+
): { data: Uint8Array | null; err: $.GoError } | Promise<{
|
|
255
|
+
data: Uint8Array | null
|
|
256
|
+
err: $.GoError
|
|
257
|
+
}> {
|
|
258
|
+
try {
|
|
259
|
+
const inflated = gunzipBytes(concat(chunks))
|
|
260
|
+
if (inflated instanceof Promise) {
|
|
261
|
+
return inflated
|
|
262
|
+
.then((data) => ({ data, err: null }))
|
|
263
|
+
.catch(() => ({ data: null, err: ErrHeader }))
|
|
264
|
+
}
|
|
265
|
+
return { data: inflated, err: null }
|
|
266
|
+
} catch {
|
|
267
|
+
return { data: null, err: ErrHeader }
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function concat(chunks: Uint8Array[]): Uint8Array {
|
|
272
|
+
let length = 0
|
|
273
|
+
for (const chunk of chunks) {
|
|
274
|
+
length += chunk.length
|
|
275
|
+
}
|
|
276
|
+
const out = new Uint8Array(length)
|
|
277
|
+
let offset = 0
|
|
278
|
+
for (const chunk of chunks) {
|
|
279
|
+
out.set(chunk, offset)
|
|
280
|
+
offset += chunk.length
|
|
281
|
+
}
|
|
282
|
+
return out
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function nodeCompressionRuntime(): compressionRuntime | null {
|
|
286
|
+
const processObj = (globalThis as any).process
|
|
287
|
+
if (processObj && typeof processObj.getBuiltinModule === 'function') {
|
|
288
|
+
const mod = processObj.getBuiltinModule('zlib')
|
|
289
|
+
if (mod && typeof mod.gzipSync === 'function') {
|
|
290
|
+
return {
|
|
291
|
+
gzipSync: (data, opts) => new Uint8Array(mod.gzipSync(data, opts)),
|
|
292
|
+
gunzipSync: (data) => new Uint8Array(mod.gunzipSync(data)),
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return null
|
|
297
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"strict": true,
|
|
4
|
+
"symbols": {
|
|
5
|
+
"BestCompression": {
|
|
6
|
+
"status": "real"
|
|
7
|
+
},
|
|
8
|
+
"BestSpeed": {
|
|
9
|
+
"status": "real"
|
|
10
|
+
},
|
|
11
|
+
"DefaultCompression": {
|
|
12
|
+
"status": "real"
|
|
13
|
+
},
|
|
14
|
+
"ErrChecksum": {
|
|
15
|
+
"status": "real"
|
|
16
|
+
},
|
|
17
|
+
"ErrHeader": {
|
|
18
|
+
"status": "real"
|
|
19
|
+
},
|
|
20
|
+
"Header": {
|
|
21
|
+
"status": "real"
|
|
22
|
+
},
|
|
23
|
+
"HuffmanOnly": {
|
|
24
|
+
"status": "real"
|
|
25
|
+
},
|
|
26
|
+
"NewReader": {
|
|
27
|
+
"status": "real"
|
|
28
|
+
},
|
|
29
|
+
"NewWriter": {
|
|
30
|
+
"status": "real"
|
|
31
|
+
},
|
|
32
|
+
"NewWriterLevel": {
|
|
33
|
+
"status": "real"
|
|
34
|
+
},
|
|
35
|
+
"NoCompression": {
|
|
36
|
+
"status": "real"
|
|
37
|
+
},
|
|
38
|
+
"Reader": {
|
|
39
|
+
"status": "real"
|
|
40
|
+
},
|
|
41
|
+
"Writer": {
|
|
42
|
+
"status": "real"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
package/gs/embed/index.test.ts
CHANGED
package/gs/io/fs/glob.ts
CHANGED
package/gs/io/fs/meta.json
CHANGED
|
@@ -35,8 +35,8 @@ describe('io/fs ReadLink', () => {
|
|
|
35
35
|
expect(err?.Error()).toBe('readlink link: invalid argument')
|
|
36
36
|
})
|
|
37
37
|
|
|
38
|
-
it('uses Lstat from ReadLinkFS implementations', () => {
|
|
39
|
-
const [, err] = Lstat(new linkFS(), 'link')
|
|
38
|
+
it('uses Lstat from ReadLinkFS implementations', async () => {
|
|
39
|
+
const [, err] = await Lstat(new linkFS(), 'link')
|
|
40
40
|
|
|
41
41
|
expect(err).toBe(ErrInvalid)
|
|
42
42
|
})
|
package/gs/io/fs/readlink.ts
CHANGED
|
@@ -60,10 +60,13 @@ export function ReadLink(fsys: FS, name: string): [string, $.GoError] {
|
|
|
60
60
|
return sym!.ReadLink(name)
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
export function Lstat(
|
|
63
|
+
export async function Lstat(
|
|
64
|
+
fsys: FS,
|
|
65
|
+
name: string,
|
|
66
|
+
): Promise<[FileInfo, $.GoError]> {
|
|
64
67
|
const { value: sym, ok } = $.typeAssert<ReadLinkFS>(fsys, 'ReadLinkFS')
|
|
65
68
|
if (!ok) {
|
|
66
|
-
return Stat(fsys, name)
|
|
69
|
+
return await Stat(fsys, name)
|
|
67
70
|
}
|
|
68
71
|
return sym!.Lstat(name)
|
|
69
72
|
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import * as $ from '@goscript/builtin/index.js'
|
|
3
|
+
|
|
4
|
+
import { File, FileInfo, Stat } from './index.js'
|
|
5
|
+
|
|
6
|
+
function fileInfo(name: string, isDir = false): FileInfo {
|
|
7
|
+
return {
|
|
8
|
+
IsDir(): boolean {
|
|
9
|
+
return isDir
|
|
10
|
+
},
|
|
11
|
+
ModTime(): any {
|
|
12
|
+
return null
|
|
13
|
+
},
|
|
14
|
+
Mode(): number {
|
|
15
|
+
return isDir ? 0o040000 : 0
|
|
16
|
+
},
|
|
17
|
+
Name(): string {
|
|
18
|
+
return name
|
|
19
|
+
},
|
|
20
|
+
Size(): number {
|
|
21
|
+
return 0
|
|
22
|
+
},
|
|
23
|
+
Sys(): null {
|
|
24
|
+
return null
|
|
25
|
+
},
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
describe('io/fs Stat override', () => {
|
|
30
|
+
it('awaits async StatFS implementations', async () => {
|
|
31
|
+
const info = fileInfo('pkg', true)
|
|
32
|
+
|
|
33
|
+
const [got, err] = await Stat(
|
|
34
|
+
{
|
|
35
|
+
async Stat(_name: string): Promise<[FileInfo, $.GoError]> {
|
|
36
|
+
return [info, null]
|
|
37
|
+
},
|
|
38
|
+
Open(_name: string): [File, $.GoError] {
|
|
39
|
+
throw new Error('StatFS path should not open')
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
'pkg',
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
expect(err).toBeNull()
|
|
46
|
+
expect(got).toBe(info)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('awaits async Open, File.Stat, and Close fallback implementations', async () => {
|
|
50
|
+
const info = fileInfo('file.txt')
|
|
51
|
+
let closed = false
|
|
52
|
+
const file: File = {
|
|
53
|
+
async Close(): Promise<$.GoError> {
|
|
54
|
+
closed = true
|
|
55
|
+
return null
|
|
56
|
+
},
|
|
57
|
+
Read(_p0: Uint8Array): [number, $.GoError] {
|
|
58
|
+
return [0, null]
|
|
59
|
+
},
|
|
60
|
+
async Stat(): Promise<[FileInfo, $.GoError]> {
|
|
61
|
+
expect(closed).toBe(false)
|
|
62
|
+
return [info, null]
|
|
63
|
+
},
|
|
64
|
+
} as unknown as File
|
|
65
|
+
|
|
66
|
+
const [got, err] = await Stat(
|
|
67
|
+
{
|
|
68
|
+
async Open(_name: string): Promise<[File, $.GoError]> {
|
|
69
|
+
return [file, null]
|
|
70
|
+
},
|
|
71
|
+
} as any,
|
|
72
|
+
'file.txt',
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
expect(err).toBeNull()
|
|
76
|
+
expect(got).toBe(info)
|
|
77
|
+
expect(closed).toBe(true)
|
|
78
|
+
})
|
|
79
|
+
})
|
package/gs/io/fs/stat.ts
CHANGED
|
@@ -1,14 +1,25 @@
|
|
|
1
1
|
import * as $ from '@goscript/builtin/index.js'
|
|
2
|
-
import { FS, FileInfo } from './fs.js'
|
|
2
|
+
import { FS, File, FileInfo } from './fs.js'
|
|
3
|
+
|
|
4
|
+
type maybePromise<T> = T | Promise<T>
|
|
3
5
|
|
|
4
6
|
export type StatFS =
|
|
5
7
|
| null
|
|
6
8
|
| ({
|
|
7
9
|
// Stat returns a FileInfo describing the file.
|
|
8
10
|
// If there is an error, it should be of type *PathError.
|
|
9
|
-
Stat(name: string): [FileInfo, $.GoError]
|
|
11
|
+
Stat(name: string): maybePromise<[FileInfo, $.GoError]>
|
|
10
12
|
} & FS)
|
|
11
13
|
|
|
14
|
+
type asyncFS = null | {
|
|
15
|
+
Open(name: string): maybePromise<[File, $.GoError]>
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type asyncFile = null | {
|
|
19
|
+
Close(): maybePromise<$.GoError>
|
|
20
|
+
Stat(): maybePromise<[FileInfo, $.GoError]>
|
|
21
|
+
}
|
|
22
|
+
|
|
12
23
|
$.registerInterfaceType(
|
|
13
24
|
'StatFS',
|
|
14
25
|
null, // Zero value for interface is null
|
|
@@ -42,21 +53,24 @@ $.registerInterfaceType(
|
|
|
42
53
|
//
|
|
43
54
|
// If fs implements [StatFS], Stat calls fs.Stat.
|
|
44
55
|
// Otherwise, Stat opens the [File] to stat it.
|
|
45
|
-
export function Stat(
|
|
46
|
-
|
|
56
|
+
export async function Stat(
|
|
57
|
+
fsys: FS,
|
|
58
|
+
name: string,
|
|
59
|
+
): Promise<[FileInfo, $.GoError]> {
|
|
47
60
|
{
|
|
48
61
|
let { value: fsysTyped, ok: ok } = $.typeAssert<StatFS>(fsys, 'StatFS')
|
|
49
62
|
if (ok) {
|
|
50
|
-
return fsysTyped!.Stat(name)
|
|
63
|
+
return await fsysTyped!.Stat(name)
|
|
51
64
|
}
|
|
52
65
|
}
|
|
53
66
|
|
|
54
|
-
let [file, err] = fsys!.Open(name)
|
|
67
|
+
let [file, err] = await (fsys as asyncFS)!.Open(name)
|
|
55
68
|
if (err != null) {
|
|
56
69
|
return [null, err]
|
|
57
70
|
}
|
|
58
|
-
|
|
59
|
-
file!.
|
|
60
|
-
}
|
|
61
|
-
|
|
71
|
+
try {
|
|
72
|
+
return await (file as asyncFile)!.Stat()
|
|
73
|
+
} finally {
|
|
74
|
+
await (file as asyncFile)!.Close()
|
|
75
|
+
}
|
|
62
76
|
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import * as $ from '@goscript/builtin/index.js'
|
|
3
|
+
|
|
4
|
+
import { File, FileInfo, Sub, Stat } from './index.js'
|
|
5
|
+
|
|
6
|
+
function fileInfo(name: string, isDir = false): FileInfo {
|
|
7
|
+
return {
|
|
8
|
+
IsDir(): boolean {
|
|
9
|
+
return isDir
|
|
10
|
+
},
|
|
11
|
+
ModTime(): any {
|
|
12
|
+
return null
|
|
13
|
+
},
|
|
14
|
+
Mode(): number {
|
|
15
|
+
return isDir ? 0o040000 : 0
|
|
16
|
+
},
|
|
17
|
+
Name(): string {
|
|
18
|
+
return name
|
|
19
|
+
},
|
|
20
|
+
Size(): number {
|
|
21
|
+
return 0
|
|
22
|
+
},
|
|
23
|
+
Sys(): null {
|
|
24
|
+
return null
|
|
25
|
+
},
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
describe('io/fs Sub override', () => {
|
|
30
|
+
it('preserves the backing filesystem and directory in fallback subFS', async () => {
|
|
31
|
+
const opened: string[] = []
|
|
32
|
+
const dirInfo = fileInfo('pkg', true)
|
|
33
|
+
const file: File = {
|
|
34
|
+
async Close(): Promise<$.GoError> {
|
|
35
|
+
return null
|
|
36
|
+
},
|
|
37
|
+
Read(_p0: Uint8Array): [number, $.GoError] {
|
|
38
|
+
return [0, null]
|
|
39
|
+
},
|
|
40
|
+
async Stat(): Promise<[FileInfo, $.GoError]> {
|
|
41
|
+
return [dirInfo, null]
|
|
42
|
+
},
|
|
43
|
+
} as unknown as File
|
|
44
|
+
|
|
45
|
+
const [sub, subErr] = await Sub(
|
|
46
|
+
{
|
|
47
|
+
async Open(name: string): Promise<[File, $.GoError]> {
|
|
48
|
+
opened.push(name)
|
|
49
|
+
return [file, null]
|
|
50
|
+
},
|
|
51
|
+
} as any,
|
|
52
|
+
'pkg',
|
|
53
|
+
)
|
|
54
|
+
expect(subErr).toBeNull()
|
|
55
|
+
|
|
56
|
+
const [got, statErr] = await Stat(sub, '.')
|
|
57
|
+
expect(statErr).toBeNull()
|
|
58
|
+
expect(got).toBe(dirInfo)
|
|
59
|
+
expect(opened).toEqual(['pkg'])
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('joins nested fallback subFS directories', async () => {
|
|
63
|
+
const opened: string[] = []
|
|
64
|
+
const fileInfoValue = fileInfo('index.js')
|
|
65
|
+
const file: File = {
|
|
66
|
+
async Close(): Promise<$.GoError> {
|
|
67
|
+
return null
|
|
68
|
+
},
|
|
69
|
+
Read(_p0: Uint8Array): [number, $.GoError] {
|
|
70
|
+
return [0, null]
|
|
71
|
+
},
|
|
72
|
+
async Stat(): Promise<[FileInfo, $.GoError]> {
|
|
73
|
+
return [fileInfoValue, null]
|
|
74
|
+
},
|
|
75
|
+
} as unknown as File
|
|
76
|
+
|
|
77
|
+
const backingFS = {
|
|
78
|
+
async Open(name: string): Promise<[File, $.GoError]> {
|
|
79
|
+
opened.push(name)
|
|
80
|
+
return [file, null]
|
|
81
|
+
},
|
|
82
|
+
} as any
|
|
83
|
+
const [pkgFS, pkgErr] = await Sub(backingFS, '@scope')
|
|
84
|
+
expect(pkgErr).toBeNull()
|
|
85
|
+
const [clientFS, clientErr] = await Sub(pkgFS, 'client')
|
|
86
|
+
expect(clientErr).toBeNull()
|
|
87
|
+
|
|
88
|
+
const [got, statErr] = await Stat(clientFS, 'index.js')
|
|
89
|
+
expect(statErr).toBeNull()
|
|
90
|
+
expect(got).toBe(fileInfoValue)
|
|
91
|
+
expect(opened).toEqual(['@scope/client/index.js'])
|
|
92
|
+
})
|
|
93
|
+
})
|
package/gs/io/fs/sub.ts
CHANGED
|
@@ -12,7 +12,7 @@ export type SubFS =
|
|
|
12
12
|
| null
|
|
13
13
|
| ({
|
|
14
14
|
// Sub returns an FS corresponding to the subtree rooted at dir.
|
|
15
|
-
Sub(dir: string): [FS, $.GoError]
|
|
15
|
+
Sub(dir: string): [FS, $.GoError] | Promise<[FS, $.GoError]>
|
|
16
16
|
} & FS)
|
|
17
17
|
|
|
18
18
|
$.registerInterfaceType(
|
|
@@ -56,7 +56,7 @@ $.registerInterfaceType(
|
|
|
56
56
|
// does not check for symbolic links inside "/prefix" that point to
|
|
57
57
|
// other directories. That is, [os.DirFS] is not a general substitute for a
|
|
58
58
|
// chroot-style security mechanism, and Sub does not change that fact.
|
|
59
|
-
export function Sub(fsys: FS, dir: string): [FS, $.GoError] {
|
|
59
|
+
export async function Sub(fsys: FS, dir: string): Promise<[FS, $.GoError]> {
|
|
60
60
|
if (!ValidPath(dir)) {
|
|
61
61
|
return [null, new PathError({ Err: ErrInvalid, Op: 'sub', Path: dir })]
|
|
62
62
|
}
|
|
@@ -66,10 +66,10 @@ export function Sub(fsys: FS, dir: string): [FS, $.GoError] {
|
|
|
66
66
|
{
|
|
67
67
|
let { value: fsysTyped, ok: ok } = $.typeAssert<SubFS>(fsys, 'SubFS')
|
|
68
68
|
if (ok) {
|
|
69
|
-
return fsysTyped!.Sub(dir)
|
|
69
|
+
return await fsysTyped!.Sub(dir)
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
|
-
return [new subFS({}), null]
|
|
72
|
+
return [new subFS({ fsys, dir }) as unknown as FS, null]
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
class subFS {
|
|
@@ -153,14 +153,14 @@ class subFS {
|
|
|
153
153
|
return err
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
-
public Open(name: string): [File, $.GoError] {
|
|
156
|
+
public async Open(name: string): Promise<[File, $.GoError]> {
|
|
157
157
|
const f = this
|
|
158
158
|
let [full, err] = f!.fullName('open', name)
|
|
159
159
|
if (err != null) {
|
|
160
160
|
return [null, err]
|
|
161
161
|
}
|
|
162
162
|
let file: File
|
|
163
|
-
;[file, err] = f!.fsys!.Open(full)
|
|
163
|
+
;[file, err] = await (f!.fsys as any)!.Open(full)
|
|
164
164
|
return [file, f!.fixErr(err)]
|
|
165
165
|
}
|
|
166
166
|
|
|
@@ -225,13 +225,13 @@ class subFS {
|
|
|
225
225
|
public Sub(dir: string): [FS, $.GoError] {
|
|
226
226
|
const f = this
|
|
227
227
|
if (dir == '.') {
|
|
228
|
-
return [f, null]
|
|
228
|
+
return [f as unknown as FS, null]
|
|
229
229
|
}
|
|
230
|
-
let [
|
|
230
|
+
let [full, err] = f!.fullName('sub', dir)
|
|
231
231
|
if (err != null) {
|
|
232
232
|
return [null, err]
|
|
233
233
|
}
|
|
234
|
-
return [new subFS({}), null]
|
|
234
|
+
return [new subFS({ fsys: f.fsys, dir: full }) as unknown as FS, null]
|
|
235
235
|
}
|
|
236
236
|
|
|
237
237
|
// Register this type with the runtime type system
|