instrumentality 0.0.7 → 0.0.8
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/README.md +2 -2
- package/dist/base.d.ts +11 -12
- package/dist/base.d.ts.map +1 -1
- package/dist/base.js +23 -39
- package/dist/dom.d.ts +1 -6
- package/dist/dom.d.ts.map +1 -1
- package/dist/dom.js +1 -6
- package/dist/road.d.ts +130 -92
- package/dist/road.d.ts.map +1 -1
- package/dist/road.js +435 -313
- package/package.json +5 -3
- package/src/base.ts +22 -43
- package/src/dom.ts +1 -10
- package/src/road.ts +394 -350
package/src/road.ts
CHANGED
|
@@ -1,28 +1,27 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as cr from "node:crypto"
|
|
2
2
|
import * as fs from "node:fs"; import { constants as fsc } from "node:fs"
|
|
3
3
|
import * as fp from "node:fs/promises"
|
|
4
4
|
import * as ph from "node:path"
|
|
5
5
|
import * as os from "node:os"
|
|
6
|
-
import * as cr from "node:crypto"
|
|
7
6
|
import { on } from "node:events"
|
|
8
|
-
import
|
|
7
|
+
import { InsErr } from "./base.ts"
|
|
9
8
|
|
|
10
9
|
|
|
11
10
|
|
|
12
|
-
/** Subclass of {@link
|
|
13
|
-
export class
|
|
14
|
-
export {
|
|
11
|
+
/** Subclass of {@link InsErr} that represents an error thrown from this specific module of the library */
|
|
12
|
+
export class Err extends InsErr { override name = "Instrumentality-Road-Error" }
|
|
13
|
+
export { Err as RoadError, Err as RdErr }
|
|
15
14
|
|
|
16
15
|
|
|
17
16
|
|
|
18
17
|
/**
|
|
19
18
|
* Returns the constructor function corresponding to the file mode (statmode).
|
|
20
19
|
*
|
|
21
|
-
* @param statmode_ The file mode to check.
|
|
20
|
+
* @param statmode_ The file mode or {@link fs.Dirent} to check.
|
|
22
21
|
* @returns The constructor function corresponding to the road type (e.g., {@link File}, {@link Folder}, etc.).
|
|
23
|
-
* @throws If the file mode is unknown, throws a {@link
|
|
22
|
+
* @throws If the file mode is unknown, throws a {@link Err}.
|
|
24
23
|
*/
|
|
25
|
-
export function
|
|
24
|
+
export function resolveStat(statmode_: number): typeof File | typeof Folder | typeof BlockDevice | typeof CharacterDevice | typeof SymbolicLink | typeof Fifo | typeof Socket {
|
|
26
25
|
switch (statmode_ & fsc.S_IFMT) {
|
|
27
26
|
case fsc.S_IFREG: return File
|
|
28
27
|
case fsc.S_IFDIR: return Folder
|
|
@@ -31,9 +30,30 @@ export function resolveMode(statmode_: number): typeof File | typeof Folder | ty
|
|
|
31
30
|
case fsc.S_IFLNK: return SymbolicLink
|
|
32
31
|
case fsc.S_IFIFO: return Fifo
|
|
33
32
|
case fsc.S_IFSOCK: return Socket
|
|
34
|
-
default: throw new
|
|
33
|
+
default: throw new Err(`Unknown mode type ${statmode_} (statmode is most likely corrupted)`)
|
|
35
34
|
}
|
|
36
35
|
}
|
|
36
|
+
export { resolveStat as resStat }
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Returns the constructor function corresponding to the type of the given {@link fs.Dirent}.
|
|
40
|
+
*
|
|
41
|
+
* @param dirent The directory entry to check.
|
|
42
|
+
* @returns The constructor function corresponding to the road type (e.g., {@link File}, {@link Folder}, etc.).
|
|
43
|
+
* @throws If the directory entry type is unknown, throws a {@link Err}.
|
|
44
|
+
*/
|
|
45
|
+
export function resolveDirent(dirent: fs.Dirent): typeof File | typeof Folder | typeof BlockDevice | typeof CharacterDevice | typeof SymbolicLink | typeof Fifo | typeof Socket {
|
|
46
|
+
// Order by likelihood: files/dicts are most common, followed by symbolic links
|
|
47
|
+
if (dirent.isFile()) return File
|
|
48
|
+
if (dirent.isDirectory()) return Folder
|
|
49
|
+
if (dirent.isSymbolicLink()) return SymbolicLink
|
|
50
|
+
if (dirent.isBlockDevice()) return BlockDevice
|
|
51
|
+
if (dirent.isCharacterDevice()) return CharacterDevice
|
|
52
|
+
if (dirent.isFIFO()) return Fifo
|
|
53
|
+
if (dirent.isSocket()) return Socket
|
|
54
|
+
throw new Err(`Unknown dirent type for ${dirent.name}`)
|
|
55
|
+
}
|
|
56
|
+
export { resolveDirent as resDirent }
|
|
37
57
|
|
|
38
58
|
|
|
39
59
|
|
|
@@ -45,19 +65,21 @@ export function resolveMode(statmode_: number): typeof File | typeof Folder | ty
|
|
|
45
65
|
* @throws If {@link fp.lstat}/{@link fs.lstatSync} fails to retrieved the status of {@link path_}.
|
|
46
66
|
*/
|
|
47
67
|
export async function factory(path_: string) {
|
|
48
|
-
return new (
|
|
68
|
+
return new (resolveStat((await fp.lstat(path_)).mode))(path_, false)
|
|
49
69
|
}
|
|
70
|
+
export { factory as fac, factory as mk }
|
|
50
71
|
/** Sync version of {@link factory}. */
|
|
51
72
|
export function factorySync(path_: string) {
|
|
52
|
-
return new (
|
|
73
|
+
return new (resolveStat(fs.lstatSync(path_).mode))(path_, false)
|
|
53
74
|
}
|
|
75
|
+
export { factorySync as facSync, factorySync as mkSync }
|
|
54
76
|
|
|
55
77
|
|
|
56
78
|
|
|
57
79
|
/**
|
|
58
80
|
* A map that keeps track of locked roads to prevent concurrent modifications.
|
|
59
81
|
*
|
|
60
|
-
* @key The absolute path of the road that is currently locked.
|
|
82
|
+
* @key The **absolute** and **normalized** (**resolved**) path of the road that is currently locked.
|
|
61
83
|
* @value A promise that resolves when the lock on the road is released.
|
|
62
84
|
*
|
|
63
85
|
* @remarks The map is initialized lazily when the first lock is created to minimize import-time side effects.
|
|
@@ -98,84 +120,80 @@ export abstract class Road {
|
|
|
98
120
|
* @param path_ Any path-like string that can be resolved to an absolute path. It will be resolved to an absolute path using {@link ph.resolve}.
|
|
99
121
|
* @param typeCheck_ If true, the constructor will check if the path corresponds to the expected type of road (e.g., file, folder, etc.) and throw an error if it doesn't. If false, no type checking will be performed.
|
|
100
122
|
*
|
|
101
|
-
* @throws If {@link typeCheck_} is true and the path does not correspond to the expected type of road, a {@link
|
|
123
|
+
* @throws If {@link typeCheck_} is true and the path does not correspond to the expected type of road, a {@link Err} will be thrown.
|
|
102
124
|
*/
|
|
103
|
-
constructor(path_: string, typeCheck_: boolean
|
|
125
|
+
constructor(path_: string, typeCheck_: boolean) {
|
|
104
126
|
this.pointsTo = ph.resolve(path_)
|
|
105
127
|
if (typeCheck_ && !this.checkSync())
|
|
106
|
-
throw new
|
|
128
|
+
throw new Err(`Type mismatch: '${this.isAt}'`)
|
|
107
129
|
}
|
|
108
130
|
|
|
131
|
+
/** @returns An instance of {@link Folder} representing the parent directory of the current road. */
|
|
132
|
+
parent(): Folder { return new Folder(ph.dirname(this.isAt), false) }
|
|
133
|
+
/**
|
|
134
|
+
* An async generator that yields the ancestors of the current road, starting from its parent and moving up the directory tree until it reaches the root.
|
|
135
|
+
*
|
|
136
|
+
* @yields Each ancestor folder as a {@link Folder} instance.
|
|
137
|
+
*/
|
|
138
|
+
*ancestorsIt() {
|
|
139
|
+
let current: Folder = this.parent()
|
|
140
|
+
let parent = current.parent()
|
|
141
|
+
while (current.isAt !== parent.isAt) {
|
|
142
|
+
yield current
|
|
143
|
+
current = parent
|
|
144
|
+
parent = current.parent()
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
/** @returns An array of {@link Folder} instances representing the ancestors of the current road. */
|
|
148
|
+
ancestors(): Folder[] { return [...this.ancestorsIt()] }
|
|
149
|
+
|
|
109
150
|
/**
|
|
110
151
|
* Aquires a lock for the path represented by this Road instance, preventing concurrent modifications from this and other Road instances pointing to the same path.
|
|
111
152
|
*
|
|
112
|
-
* @param cb_ A callback function that will be called when the lock is released. This is used internally to manage the lock state.
|
|
113
153
|
* @returns An object with a dispose method that releases the lock when called.
|
|
114
|
-
* @throws If the road is immutable, a {@link
|
|
154
|
+
* @throws If the road is immutable, a {@link Err} will be thrown.
|
|
115
155
|
*
|
|
116
|
-
* @remarks This method MUST be used with a `using` statement to ensure that the lock is released properly. Failing to do so
|
|
117
|
-
* Non-deterministic release is unfortunately not possible due to the nature of JavaScript's garbage collection, thus the lock must be released deterministically by the user (
|
|
156
|
+
* @remarks This method MUST be used with a `using` statement to ensure that the lock is released properly. Failing to do so WILL result in deadlocks and other concurrency issues.
|
|
157
|
+
* Non-deterministic release is unfortunately not possible due to the nature of JavaScript's garbage collection, thus the lock must be released deterministically by the user (for which the `using` statement is a convenient way to do so).
|
|
158
|
+
* @remarks Methods may call other methods that each acquire their own locks, and release them independently, meaning from the perspective of other instances, the lock may appear to be free even if the original method still has work to do.
|
|
159
|
+
* To fix this, it's recommeneded to aquire a lock at the beginning and use fs/fp operations within the locked context.
|
|
118
160
|
*/
|
|
119
|
-
protected async lock(
|
|
161
|
+
protected async lock(): Promise<Disposable & AsyncDisposable> {
|
|
120
162
|
if (!this.mutable)
|
|
121
|
-
throw new
|
|
122
|
-
lockedRoads ??= new Map
|
|
163
|
+
throw new Err(`Road to '${this.isAt}' is immutable.`)
|
|
164
|
+
lockedRoads ??= new Map()
|
|
165
|
+
const { promise, resolve } = Promise.withResolvers<void>()
|
|
166
|
+
const previous = lockedRoads.get(this.isAt)
|
|
167
|
+
lockedRoads.set(this.isAt, promise)
|
|
123
168
|
const isAt = this.isAt
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
[Symbol.dispose]() {
|
|
128
|
-
cb_()
|
|
129
|
-
lockedRoads!.delete(isAt)
|
|
130
|
-
},
|
|
131
|
-
async [Symbol.asyncDispose]() {
|
|
132
|
-
cb_()
|
|
169
|
+
const dispose = () => {
|
|
170
|
+
resolve()
|
|
171
|
+
if (lockedRoads!.get(isAt) === promise)
|
|
133
172
|
lockedRoads!.delete(isAt)
|
|
134
|
-
}
|
|
135
173
|
}
|
|
174
|
+
await previous
|
|
175
|
+
return { [Symbol.dispose]: dispose, [Symbol.asyncDispose]: dispose as any }
|
|
136
176
|
}
|
|
137
177
|
/**
|
|
138
178
|
* Sync version of {@link lock}.
|
|
139
|
-
* @throws
|
|
179
|
+
* @throws If the road is currently locked by another operation as it cannot wait for the lock to be released in a synchronous context.
|
|
140
180
|
*/
|
|
141
|
-
protected lockSync(
|
|
181
|
+
protected lockSync(): Disposable & AsyncDisposable {
|
|
142
182
|
if (!this.mutable)
|
|
143
|
-
throw new
|
|
144
|
-
lockedRoads ??= new Map
|
|
183
|
+
throw new Err(`Road to '${this.isAt}' is immutable.`)
|
|
184
|
+
lockedRoads ??= new Map()
|
|
185
|
+
const { promise, resolve } = Promise.withResolvers<void>()
|
|
186
|
+
if (lockedRoads.has(this.isAt))
|
|
187
|
+
throw new Err(`Road to '${this.isAt}' is already locked.`)
|
|
188
|
+
lockedRoads.set(this.isAt, promise)
|
|
145
189
|
const isAt = this.isAt
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
return {
|
|
150
|
-
[Symbol.dispose]() {
|
|
190
|
+
const dispose = () => {
|
|
191
|
+
resolve()
|
|
192
|
+
if (lockedRoads!.get(isAt) === promise)
|
|
151
193
|
lockedRoads!.delete(isAt)
|
|
152
|
-
cb_()
|
|
153
|
-
},
|
|
154
|
-
async [Symbol.asyncDispose]() {
|
|
155
|
-
lockedRoads!.delete(isAt)
|
|
156
|
-
cb_()
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/** @returns An instance of {@link Folder} representing the parent directory of the current road. */
|
|
162
|
-
parent(): Folder { return new Folder(ph.dirname(this.isAt), false) }
|
|
163
|
-
/**
|
|
164
|
-
* An async generator that yields the ancestors of the current road, starting from its parent and moving up the directory tree until it reaches the root.
|
|
165
|
-
*
|
|
166
|
-
* @yields Each ancestor folder as a {@link Folder} instance.
|
|
167
|
-
*/
|
|
168
|
-
*ancestorsIt() {
|
|
169
|
-
let current: Folder = this.parent()
|
|
170
|
-
let parent = current.parent()
|
|
171
|
-
while (current.isAt !== parent.isAt) {
|
|
172
|
-
yield current
|
|
173
|
-
current = parent
|
|
174
|
-
parent = current.parent()
|
|
175
194
|
}
|
|
195
|
+
return { [Symbol.dispose]: dispose, [Symbol.asyncDispose]: dispose as any }
|
|
176
196
|
}
|
|
177
|
-
/** @returns An array of {@link Folder} instances representing the ancestors of the current road. */
|
|
178
|
-
ancestors(): Folder[] { return [...this.ancestorsIt()] }
|
|
179
197
|
|
|
180
198
|
/**
|
|
181
199
|
* Watches the current entry for changes and resolves when the entry becomes accessible (i.e., exists and can be accessed).
|
|
@@ -223,20 +241,56 @@ export abstract class Road {
|
|
|
223
241
|
lstat() { return fp.lstat(this.isAt) }
|
|
224
242
|
/** @returns The result of {@link fs.lstatSync} for the current entry. */
|
|
225
243
|
lstatSync() { return fs.lstatSync(this.isAt) }
|
|
226
|
-
/** @returns The result of {@link fp.stat} for the current entry. */
|
|
227
|
-
stat() { return fp.stat(this.isAt) }
|
|
228
|
-
/** @returns The result of {@link fs.statSync} for the current entry. */
|
|
229
|
-
statSync() { return fs.statSync(this.isAt) }
|
|
230
244
|
|
|
231
|
-
|
|
232
|
-
abstract
|
|
233
|
-
abstract
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
245
|
+
/** Get the size of the this entry in bytes. */
|
|
246
|
+
abstract size(): Promise<number>
|
|
247
|
+
abstract sizeSync(): number
|
|
248
|
+
|
|
249
|
+
/** Wrapper around {@link fp.rm} with locking. */
|
|
250
|
+
async delete(): Promise<void> {
|
|
251
|
+
using _ = await this.lock()
|
|
252
|
+
await fp.rm(this.isAt, { recursive: true, force: true })
|
|
253
|
+
}
|
|
254
|
+
/** Wrapper around {@link fs.rmSync} with locking. */
|
|
255
|
+
deleteSync(): void {
|
|
256
|
+
using _ = this.lockSync()
|
|
257
|
+
fs.rmSync(this.isAt, { recursive: true, force: true })
|
|
258
|
+
}
|
|
259
|
+
async copy(into_: Folder, options_?: fs.CopyOptions): Promise<this> {
|
|
260
|
+
const newPath = into_.join(this.name)
|
|
261
|
+
await fp.cp(this.isAt, newPath, { recursive: true, ...options_ })
|
|
262
|
+
return new (this.constructor as new (path: string, typeCheck: boolean) => this)(newPath, false)
|
|
263
|
+
}
|
|
264
|
+
copySync(into_: Folder, options_?: fs.CopySyncOptions): this {
|
|
265
|
+
const newPath = into_.join(this.name)
|
|
266
|
+
fs.cpSync(this.isAt, newPath, { recursive: true, ...options_ })
|
|
267
|
+
return new (this.constructor as new (path: string, typeCheck: boolean) => this)(newPath, false)
|
|
268
|
+
}
|
|
269
|
+
async move(into_: Folder): Promise<void> {
|
|
270
|
+
using _ = await this.lock()
|
|
271
|
+
const newPath = into_.join(this.name)
|
|
272
|
+
await fp.rename(this.isAt, newPath)
|
|
273
|
+
this.pointsTo = newPath
|
|
274
|
+
}
|
|
275
|
+
moveSync(into_: Folder): void {
|
|
276
|
+
using _ = this.lockSync()
|
|
277
|
+
const newPath = into_.join(this.name)
|
|
278
|
+
fs.renameSync(this.isAt, newPath)
|
|
279
|
+
this.pointsTo = newPath
|
|
280
|
+
}
|
|
281
|
+
async rename(newName_: string): Promise<void> {
|
|
282
|
+
using _ = await this.lock()
|
|
283
|
+
const newPath = this.parent().join(newName_)
|
|
284
|
+
await fp.rename(this.isAt, newPath)
|
|
285
|
+
this.pointsTo = newPath
|
|
286
|
+
}
|
|
287
|
+
renameSync(newName_: string): void {
|
|
288
|
+
using _ = this.lockSync()
|
|
289
|
+
const newPath = this.parent().join(newName_)
|
|
290
|
+
fs.renameSync(this.isAt, newPath)
|
|
291
|
+
this.pointsTo = newPath
|
|
292
|
+
}
|
|
293
|
+
|
|
240
294
|
|
|
241
295
|
// jsdocs for the abstract methods are in the subclasses
|
|
242
296
|
abstract check(): Promise<boolean>
|
|
@@ -250,10 +304,6 @@ export abstract class Road {
|
|
|
250
304
|
isFolder(): this is Folder { return false as const }
|
|
251
305
|
/** Type narrowing for {@link Folder} (similar to `instanceof` without unnecessary runtime checks). */
|
|
252
306
|
isDirectory(): this is Folder { return false as const }
|
|
253
|
-
/** Type narrowing for {@link Folder} (similar to `instanceof` without unnecessary runtime checks). */
|
|
254
|
-
isDict(): this is Folder { return false as const }
|
|
255
|
-
/** Type narrowing for {@link Folder} (similar to `instanceof` without unnecessary runtime checks). */
|
|
256
|
-
isDictionary(): this is Folder { return false as const }
|
|
257
307
|
/** Type narrowing for {@link SymbolicLink} (similar to `instanceof` without unnecessary runtime checks). */
|
|
258
308
|
isSymlink(): this is SymbolicLink { return false as const }
|
|
259
309
|
/** Type narrowing for {@link SymbolicLink} (similar to `instanceof` without unnecessary runtime checks). */
|
|
@@ -269,28 +319,47 @@ export abstract class Road {
|
|
|
269
319
|
/** Type narrowing for {@link Socket} (similar to `instanceof` without unnecessary runtime checks). */
|
|
270
320
|
isSocket(): this is Socket { return false as const }
|
|
271
321
|
}
|
|
272
|
-
|
|
322
|
+
/** Constructor type for a subclass of {@link Road}. */
|
|
323
|
+
export type road_t<T extends Road> = new (...args_: ConstructorParameters<typeof Road>) => T
|
|
273
324
|
|
|
274
325
|
|
|
275
326
|
|
|
276
327
|
/** Subclass of {@link Road} that represents a file. */
|
|
277
328
|
export class File extends Road {
|
|
329
|
+
/**
|
|
330
|
+
* Creates a new file at the specified path if it does not already exist.
|
|
331
|
+
*
|
|
332
|
+
* @param at_ The path at which to create the file.
|
|
333
|
+
* @returns A promise that resolves to the newly created `File` instance.
|
|
334
|
+
* @throws if {@link fp.writeFile} throws.
|
|
335
|
+
*/
|
|
278
336
|
static async create(at_: string) {
|
|
279
337
|
try { await fp.access(at_, fsc.W_OK) }
|
|
280
338
|
catch { await fp.writeFile(at_, "") }
|
|
281
339
|
return new File(at_, true)
|
|
282
340
|
}
|
|
341
|
+
/** Alias for {@link File.create}. */
|
|
283
342
|
static readonly mk: typeof File.create = File.create
|
|
343
|
+
/** Synchronous version of {@link File.create}. */
|
|
284
344
|
static createSync(at_: string) {
|
|
285
345
|
try { fs.accessSync(at_, fs.constants.W_OK) }
|
|
286
346
|
catch { fs.writeFileSync(at_, "") }
|
|
287
347
|
return new File(at_, true)
|
|
288
348
|
}
|
|
349
|
+
/** Alias for {@link File.createSync}. */
|
|
289
350
|
static readonly mkSync: typeof File.createSync = File.createSync
|
|
290
351
|
|
|
352
|
+
/** The file extension of this file, including the leading dot. */
|
|
291
353
|
get ext() { return ph.extname(this.isAt) }
|
|
354
|
+
/** The file name without its extension. */
|
|
292
355
|
get noExt() { return ph.basename(this.isAt, this.ext) }
|
|
293
356
|
|
|
357
|
+
/**
|
|
358
|
+
* Reads the contents of the file.
|
|
359
|
+
*
|
|
360
|
+
* @returns A promise that resolves to the contents of the file as a `Buffer` or `string`, depending on the specified encoding.
|
|
361
|
+
* @throws If the file can't be read due to permission issues or other filesystem errors.
|
|
362
|
+
*/
|
|
294
363
|
async read(): Promise<Buffer>
|
|
295
364
|
async read(encoding_: BufferEncoding, flag_?: string): Promise<string>
|
|
296
365
|
async read(encoding_?: BufferEncoding, flag_?: string): Promise<Buffer | string> {
|
|
@@ -308,16 +377,45 @@ export class File extends Road {
|
|
|
308
377
|
return fs.readFileSync(this.isAt)
|
|
309
378
|
}
|
|
310
379
|
|
|
380
|
+
async sameAs(other_: File): Promise<boolean> {
|
|
381
|
+
if (this.isAt === other_.isAt)
|
|
382
|
+
return true
|
|
383
|
+
else if ((await fp.lstat(this.isAt)).size !== (await fp.lstat(other_.isAt)).size)
|
|
384
|
+
return false
|
|
385
|
+
const iter1 = this.itBuff()
|
|
386
|
+
const iter2 = other_.itBuff()
|
|
387
|
+
while (true) {
|
|
388
|
+
const [a, b] = await Promise.all([iter1.next(), iter2.next()])
|
|
389
|
+
if (a.done && b.done) return true
|
|
390
|
+
if (a.done !== b.done) return false
|
|
391
|
+
if (!a.value!.equals(b.value!)) return false
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
sameAsSync(other_: File): boolean {
|
|
395
|
+
if (this.isAt === other_.isAt)
|
|
396
|
+
return true
|
|
397
|
+
else if (fs.lstatSync(this.isAt).size !== fs.lstatSync(other_.isAt).size)
|
|
398
|
+
return false
|
|
399
|
+
const iter1 = this.itBuffSync()
|
|
400
|
+
const iter2 = other_.itBuffSync()
|
|
401
|
+
while (true) {
|
|
402
|
+
const a = iter1.next()
|
|
403
|
+
const b = iter2.next()
|
|
404
|
+
if (a.done && b.done) return true
|
|
405
|
+
if (a.done !== b.done) return false
|
|
406
|
+
if (!a.value!.equals(b.value!)) return false
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
311
410
|
async *itBuff(chunkSize_: number = 64 * 1024, flags_: string | number = 'r', mode_?: fs.Mode) {
|
|
312
411
|
const fd = await fp.open(this.isAt, flags_, mode_)
|
|
313
412
|
try {
|
|
314
413
|
const buffer = Buffer.alloc(chunkSize_)
|
|
315
414
|
let bytesRead: number
|
|
316
415
|
do {
|
|
317
|
-
|
|
318
|
-
bytesRead = readResult.bytesRead
|
|
416
|
+
bytesRead = (await fd.read(buffer, 0, chunkSize_, null)).bytesRead
|
|
319
417
|
if (bytesRead > 0)
|
|
320
|
-
yield buffer.subarray(0, bytesRead)
|
|
418
|
+
yield Buffer.from(buffer.subarray(0, bytesRead))
|
|
321
419
|
} while (bytesRead === chunkSize_)
|
|
322
420
|
} finally {
|
|
323
421
|
await fd.close()
|
|
@@ -331,12 +429,81 @@ export class File extends Road {
|
|
|
331
429
|
do {
|
|
332
430
|
bytesRead = fs.readSync(fd, buffer, 0, chunkSize_, null)
|
|
333
431
|
if (bytesRead > 0)
|
|
334
|
-
yield buffer.subarray(0, bytesRead)
|
|
432
|
+
yield Buffer.from(buffer.subarray(0, bytesRead))
|
|
335
433
|
} while (bytesRead === chunkSize_)
|
|
336
434
|
} finally {
|
|
337
435
|
fs.closeSync(fd)
|
|
338
436
|
}
|
|
339
437
|
}
|
|
438
|
+
async *itLines(chunkSize_: number = 64 * 1024, flags_: string | number = 'r', mode_?: fs.Mode) {
|
|
439
|
+
const decoder = new TextDecoder("utf-8", { fatal: true })
|
|
440
|
+
let carry = ""
|
|
441
|
+
|
|
442
|
+
for await (const chunk of this.itBuff(chunkSize_, flags_, mode_)) {
|
|
443
|
+
carry += decoder.decode(chunk, { stream: true })
|
|
444
|
+
let newlineINdex: number
|
|
445
|
+
while ((newlineINdex = carry.indexOf("\n")) !== -1) {
|
|
446
|
+
const line = carry.slice(0, newlineINdex)
|
|
447
|
+
yield line.endsWith("\r") ? line.slice(0, -1) : line
|
|
448
|
+
carry = carry.slice(newlineINdex + 1)
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
carry += decoder.decode()
|
|
452
|
+
if (carry.length > 0)
|
|
453
|
+
yield carry.endsWith("\r") ? carry.slice(0, -1) : carry
|
|
454
|
+
}
|
|
455
|
+
*itLinesSync(chunkSize_: number = 64 * 1024, flags_: string | number = 'r', mode_?: fs.Mode) {
|
|
456
|
+
const decoder = new TextDecoder("utf-8", { fatal: true })
|
|
457
|
+
let carry = ""
|
|
458
|
+
|
|
459
|
+
for (const chunk of this.itBuffSync(chunkSize_, flags_, mode_)) {
|
|
460
|
+
carry += decoder.decode(chunk, { stream: true })
|
|
461
|
+
let newlineINdex: number
|
|
462
|
+
while ((newlineINdex = carry.indexOf("\n")) !== -1) {
|
|
463
|
+
const line = carry.slice(0, newlineINdex)
|
|
464
|
+
yield line.endsWith("\r") ? line.slice(0, -1) : line
|
|
465
|
+
carry = carry.slice(newlineINdex + 1)
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
carry += decoder.decode()
|
|
469
|
+
if (carry.length > 0)
|
|
470
|
+
yield carry.endsWith("\r") ? carry.slice(0, -1) : carry
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
async hash(algorithm_?: string, options_?: cr.HashOptions): Promise<Buffer>
|
|
474
|
+
async hash(algorithm_?: string, options_?: cr.HashOptions, encoding_?: BufferEncoding): Promise<string>
|
|
475
|
+
async hash(algorithm_ = "sha256", options_?: cr.HashOptions, encoding_?: BufferEncoding): Promise<Buffer | string> {
|
|
476
|
+
const hash = cr.createHash(algorithm_, options_)
|
|
477
|
+
for await (const chunk of this.itBuff())
|
|
478
|
+
hash.update(chunk)
|
|
479
|
+
return encoding_ ? hash.digest(encoding_) : hash.digest()
|
|
480
|
+
}
|
|
481
|
+
hashSync(algorithm_?: string, options_?: cr.HashOptions): Buffer
|
|
482
|
+
hashSync(algorithm_?: string, options_?: cr.HashOptions, encoding_?: BufferEncoding): string
|
|
483
|
+
hashSync(algorithm_ = "sha256", options_?: cr.HashOptions, encoding_?: BufferEncoding): Buffer | string {
|
|
484
|
+
const hash = cr.createHash(algorithm_, options_)
|
|
485
|
+
for (const chunk of this.itBuffSync())
|
|
486
|
+
hash.update(chunk)
|
|
487
|
+
return encoding_ ? hash.digest(encoding_) : hash.digest()
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
async size(): Promise<number> { return (await this.lstat()).size }
|
|
491
|
+
sizeSync(): number { return this.lstatSync().size }
|
|
492
|
+
|
|
493
|
+
async writeAtomic(data_: Buffer | string, options_?: fs.WriteFileOptions) {
|
|
494
|
+
using _ = await this.lock()
|
|
495
|
+
const temp = await Temp(File) // Don't use `using` as we won't clean it up
|
|
496
|
+
await fp.rename(temp.isAt, this.parent().join(temp.name)) // Move temp file to the same directory as the target
|
|
497
|
+
await fp.writeFile(temp.isAt, data_, options_) // Write data to the temp file
|
|
498
|
+
await fp.rename(temp.isAt, this.isAt) // Replace the target file with the temp file atomically
|
|
499
|
+
}
|
|
500
|
+
writeAtomicSync(data_: Buffer | string, options_?: fs.WriteFileOptions) {
|
|
501
|
+
using _ = this.lockSync()
|
|
502
|
+
const temp = TempSync(File) // Don't use `using` as we won't clean it up
|
|
503
|
+
fs.renameSync(temp.isAt, this.parent().join(temp.name)) // Move temp file to the same directory as the target
|
|
504
|
+
fs.writeFileSync(temp.isAt, data_, options_) // Write data to the temp file
|
|
505
|
+
fs.renameSync(temp.isAt, this.isAt) // Replace the target file with the temp file atomically
|
|
506
|
+
}
|
|
340
507
|
|
|
341
508
|
async write(data_: Buffer | string, options_?: fs.WriteFileOptions) {
|
|
342
509
|
using _ = await this.lock()
|
|
@@ -355,134 +522,16 @@ export class File extends Road {
|
|
|
355
522
|
fs.appendFileSync(this.isAt, data_, options_)
|
|
356
523
|
}
|
|
357
524
|
|
|
358
|
-
async
|
|
359
|
-
|
|
360
|
-
await fp.rm(this.isAt, { force: true })
|
|
361
|
-
}
|
|
362
|
-
deleteSync() {
|
|
363
|
-
using _ = this.lockSync()
|
|
364
|
-
fs.rmSync(this.isAt, { force: true })
|
|
365
|
-
}
|
|
366
|
-
async move(into_: Folder) {
|
|
367
|
-
using _ = await this.lock()
|
|
368
|
-
const newPath = into_.join(this.name)
|
|
369
|
-
await fp.rename(this.isAt, newPath)
|
|
370
|
-
this.pointsTo = newPath
|
|
371
|
-
}
|
|
372
|
-
moveSync(into_: Folder) {
|
|
373
|
-
using _ = this.lockSync()
|
|
374
|
-
const newPath = into_.join(this.name)
|
|
375
|
-
fs.renameSync(this.isAt, newPath)
|
|
376
|
-
this.pointsTo = newPath
|
|
377
|
-
}
|
|
378
|
-
async copy(into_: Folder): Promise<this> {
|
|
379
|
-
const newPath = into_.join(this.name)
|
|
380
|
-
await fp.copyFile(this.isAt, newPath)
|
|
381
|
-
return new File(newPath, false) as this
|
|
382
|
-
}
|
|
383
|
-
copySync(into_: Folder): this {
|
|
384
|
-
const newPath = into_.join(this.name)
|
|
385
|
-
fs.copyFileSync(this.isAt, newPath)
|
|
386
|
-
return new File(newPath, false) as this
|
|
387
|
-
}
|
|
388
|
-
async rename(to_: string) {
|
|
389
|
-
using _ = await this.lock()
|
|
390
|
-
const newPath = this.parent().join(to_)
|
|
391
|
-
await fp.rename(this.isAt, newPath)
|
|
392
|
-
this.pointsTo = newPath
|
|
393
|
-
}
|
|
394
|
-
renameSync(to_: string) {
|
|
395
|
-
using _ = this.lockSync()
|
|
396
|
-
const newPath = this.parent().join(to_)
|
|
397
|
-
fs.renameSync(this.isAt, newPath)
|
|
398
|
-
this.pointsTo = newPath
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
async check(): Promise<boolean> { return (await fp.lstat(this.isAt)).isFile() }
|
|
402
|
-
checkSync(): boolean { return fs.lstatSync(this.isAt).isFile() }
|
|
525
|
+
async check(): Promise<boolean> { try { return (await fp.lstat(this.isAt)).isFile() } catch { return false } }
|
|
526
|
+
checkSync(): boolean { try { return fs.lstatSync(this.isAt).isFile() } catch { return false } }
|
|
403
527
|
|
|
404
528
|
override isFile(): this is File { return true as const }
|
|
405
529
|
}
|
|
406
530
|
|
|
407
531
|
|
|
408
|
-
// Bizarre functions for file I/O
|
|
409
|
-
export async function hash(f_: File, algorithm_?: string, options_?: cr.HashOptions): Promise<Buffer>
|
|
410
|
-
export async function hash(f_: File, algorithm_?: string, options_?: cr.HashOptions, encoding_?: BufferEncoding): Promise<string>
|
|
411
|
-
export async function hash(f_: File, algorithm_ = "sha256", options_?: cr.HashOptions, encoding_?: BufferEncoding): Promise<Buffer | string> {
|
|
412
|
-
const hash = cr.createHash(algorithm_, options_)
|
|
413
|
-
for await (const chunk of f_.itBuff())
|
|
414
|
-
hash.update(chunk)
|
|
415
|
-
return encoding_ ? hash.digest(encoding_) : hash.digest()
|
|
416
|
-
}
|
|
417
|
-
export function hashSync(f_: File, algorithm_?: string, options_?: cr.HashOptions): Buffer
|
|
418
|
-
export function hashSync(f_: File, algorithm_?: string, options_?: cr.HashOptions, encoding_?: BufferEncoding): string
|
|
419
|
-
export function hashSync(f_: File, algorithm_ = "sha256", options_?: cr.HashOptions, encoding_?: BufferEncoding): Buffer | string {
|
|
420
|
-
const hash = cr.createHash(algorithm_, options_)
|
|
421
|
-
for (const chunk of f_.itBuffSync())
|
|
422
|
-
hash.update(chunk)
|
|
423
|
-
return encoding_ ? hash.digest(encoding_) : hash.digest()
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
export async function streamHash(f_: File, algorithm_?: string, options_?: cr.HashOptions): Promise<Buffer>
|
|
427
|
-
export async function streamHash(f_: File, algorithm_?: string, options_?: cr.HashOptions, encoding_?: BufferEncoding): Promise<string>
|
|
428
|
-
export async function streamHash(f_: File, algorithm_ = "sha256", options_?: cr.HashOptions, encoding_?: BufferEncoding): Promise<Buffer | string> {
|
|
429
|
-
const hash = cr.createHash(algorithm_, options_)
|
|
430
|
-
for await (const chunk of f_.itBuff())
|
|
431
|
-
hash.update(chunk)
|
|
432
|
-
return encoding_ ? hash.digest(encoding_) : hash.digest()
|
|
433
|
-
}
|
|
434
|
-
export function streamHashSync(f_: File, algorithm_?: string, options_?: cr.HashOptions): Buffer
|
|
435
|
-
export function streamHashSync(f_: File, algorithm_?: string, options_?: cr.HashOptions, encoding_?: BufferEncoding): string
|
|
436
|
-
export function streamHashSync(f_: File, algorithm_ = "sha256", options_?: cr.HashOptions, encoding_?: BufferEncoding): Buffer | string {
|
|
437
|
-
const hash = cr.createHash(algorithm_, options_)
|
|
438
|
-
for (const chunk of f_.itBuffSync())
|
|
439
|
-
hash.update(chunk)
|
|
440
|
-
return encoding_ ? hash.digest(encoding_) : hash.digest()
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
export async function* itLines(f_: File, options_: fs.ReadStreamOptions = { encoding: 'utf-8' }) {
|
|
444
|
-
const readStream = fs.createReadStream(f_.isAt, options_)
|
|
445
|
-
const rlInterface = rl.createInterface({ input: readStream, crlfDelay: Infinity })
|
|
446
|
-
try {
|
|
447
|
-
for await (const line of rlInterface)
|
|
448
|
-
yield line
|
|
449
|
-
} finally {
|
|
450
|
-
rlInterface.close()
|
|
451
|
-
readStream.destroy()
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
export async function fileSameAs(f1_: File, f2_: File): Promise<boolean> {
|
|
456
|
-
if (f1_.isAt === f2_.isAt)
|
|
457
|
-
return true
|
|
458
|
-
else if ((await fp.lstat(f1_.isAt)).size !== (await fp.lstat(f2_.isAt)).size)
|
|
459
|
-
return false
|
|
460
|
-
const iter1 = f1_.itBuff()
|
|
461
|
-
const iter2 = f2_.itBuff()
|
|
462
|
-
while (true) {
|
|
463
|
-
const [a, b] = await Promise.all([iter1.next(), iter2.next()])
|
|
464
|
-
if (a.done && b.done) return true
|
|
465
|
-
if (a.done !== b.done) return false
|
|
466
|
-
if (!a.value!.equals(b.value!)) return false
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
export function fileSameAsSync(f1_: File, f2_: File): boolean {
|
|
470
|
-
if (f1_.isAt === f2_.isAt)
|
|
471
|
-
return true
|
|
472
|
-
else if (fs.statSync(f1_.isAt).size !== fs.statSync(f2_.isAt).size)
|
|
473
|
-
return false
|
|
474
|
-
const iter1 = f1_.itBuffSync()
|
|
475
|
-
const iter2 = f2_.itBuffSync()
|
|
476
|
-
while (true) {
|
|
477
|
-
const a = iter1.next()
|
|
478
|
-
const b = iter2.next()
|
|
479
|
-
if (a.done && b.done) return true
|
|
480
|
-
if (a.done !== b.done) return false
|
|
481
|
-
if (!a.value!.equals(b.value!)) return false
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
|
|
485
532
|
|
|
533
|
+
/** Helper type for filtering {@link Road} instances. */
|
|
534
|
+
export type filter_t<T extends Road> = ((road: Road) => road is T)
|
|
486
535
|
|
|
487
536
|
export class Folder extends Road {
|
|
488
537
|
static async create(at_: string) {
|
|
@@ -503,50 +552,75 @@ export class Folder extends Road {
|
|
|
503
552
|
}
|
|
504
553
|
|
|
505
554
|
it(): AsyncIterable<Road>
|
|
506
|
-
it<T extends Road>(
|
|
507
|
-
async *it<T extends Road>(
|
|
508
|
-
for (const
|
|
509
|
-
const road =
|
|
510
|
-
if (!
|
|
555
|
+
it<T extends Road>(filter_: filter_t<T>): AsyncIterable<T>
|
|
556
|
+
async *it<T extends Road>(filter_?: filter_t<T>): AsyncIterable<Road> | AsyncIterable<T> {
|
|
557
|
+
for (const entry of await fp.readdir(this.isAt, { withFileTypes: true })) {
|
|
558
|
+
const road = new (resolveDirent(entry))(this.join(entry.name), false)
|
|
559
|
+
if (!filter_ || filter_(road))
|
|
511
560
|
yield road
|
|
512
561
|
}
|
|
513
562
|
}
|
|
514
563
|
itSync(): Iterable<Road>
|
|
515
|
-
itSync<T extends Road>(
|
|
516
|
-
*itSync<T extends Road>(
|
|
517
|
-
for (const entry of fs.readdirSync(this.isAt)) {
|
|
518
|
-
const road =
|
|
519
|
-
if (!
|
|
564
|
+
itSync<T extends Road>(filter_: filter_t<T>): Iterable<T>
|
|
565
|
+
*itSync<T extends Road>(filter_?: filter_t<T>): Iterable<Road> | Iterable<T> {
|
|
566
|
+
for (const entry of fs.readdirSync(this.isAt, { withFileTypes: true })) {
|
|
567
|
+
const road = new (resolveDirent(entry))(this.join(entry.name), false)
|
|
568
|
+
if (!filter_ || filter_(road))
|
|
520
569
|
yield road
|
|
521
570
|
}
|
|
522
571
|
}
|
|
572
|
+
|
|
523
573
|
async list(): Promise<Road[]>
|
|
524
|
-
async list<T extends Road>(
|
|
525
|
-
async list<T extends Road>(
|
|
526
|
-
const entries = (await fp.readdir(this.isAt)).map(
|
|
574
|
+
async list<T extends Road>(filter_: filter_t<T>): Promise<T[]>
|
|
575
|
+
async list<T extends Road>(filter_?: filter_t<T>): Promise<Road[] | T[]> {
|
|
576
|
+
const entries = (await fp.readdir(this.isAt, { withFileTypes: true })).map(e => new (resolveDirent(e))(this.join(e.name), false))
|
|
527
577
|
const resolvedEntries = await Promise.all(entries)
|
|
528
|
-
if (!
|
|
578
|
+
if (!filter_)
|
|
529
579
|
return resolvedEntries
|
|
530
|
-
return resolvedEntries.filter(entry => entry
|
|
580
|
+
return resolvedEntries.filter(entry => filter_(entry))
|
|
531
581
|
}
|
|
532
582
|
listSync(): Road[]
|
|
533
|
-
listSync<T extends Road>(
|
|
534
|
-
listSync<T extends Road>(
|
|
535
|
-
const entries = fs.readdirSync(this.isAt).map(
|
|
536
|
-
if (!
|
|
583
|
+
listSync<T extends Road>(filter_: filter_t<T>): T[]
|
|
584
|
+
listSync<T extends Road>(filter_?: filter_t<T>): Road[] | T[] {
|
|
585
|
+
const entries = fs.readdirSync(this.isAt, { withFileTypes: true }).map(e => new (resolveDirent(e))(this.join(e.name), false))
|
|
586
|
+
if (!filter_)
|
|
537
587
|
return entries
|
|
538
|
-
return entries.filter(entry => entry
|
|
588
|
+
return entries.filter(entry => filter_(entry))
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
walk(): AsyncIterable<Road>
|
|
592
|
+
walk<T extends Road>(filter_: filter_t<T>): AsyncIterable<T>
|
|
593
|
+
walk(filter_: (r: Road) => boolean): AsyncIterable<Road>
|
|
594
|
+
async *walk<T extends Road>(filter_?: filter_t<T> | ((r: Road) => boolean)): AsyncIterable<T> | AsyncIterable<Road> {
|
|
595
|
+
for await (const entry of this.it()) {
|
|
596
|
+
if (!filter_ || filter_(entry))
|
|
597
|
+
yield entry
|
|
598
|
+
|
|
599
|
+
if (entry.isDir())
|
|
600
|
+
yield* entry.walk(filter_!)
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
walkSync(): Iterable<Road>
|
|
604
|
+
walkSync<T extends Road>(filter_: filter_t<T>): Iterable<T>
|
|
605
|
+
walkSync(filter_: (r: Road) => boolean): Iterable<Road>
|
|
606
|
+
*walkSync<T extends Road>(filter_?: filter_t<T> | ((r: Road) => boolean)): Iterable<T> | Iterable<Road> {
|
|
607
|
+
for (const entry of this.itSync()) {
|
|
608
|
+
if (!filter_ || filter_(entry))
|
|
609
|
+
yield entry
|
|
610
|
+
|
|
611
|
+
if (entry.isDir())
|
|
612
|
+
yield* entry.walkSync(filter_!)
|
|
613
|
+
}
|
|
539
614
|
}
|
|
540
615
|
|
|
541
616
|
async find(name_: string): Promise<Road | null>
|
|
542
|
-
async find<T extends Road>(name_: string,
|
|
543
|
-
async find<T extends Road>(name_: string,
|
|
617
|
+
async find<T extends Road>(name_: string, expect_: road_t<T>): Promise<T | null>
|
|
618
|
+
async find<T extends Road>(name_: string, expect_?: road_t<T>): Promise<Road | T | null> {
|
|
544
619
|
try {
|
|
545
|
-
await fp.access(this.join(name_), fs.constants.F_OK)
|
|
546
620
|
const found = await factory(this.join(name_))
|
|
547
|
-
if (!
|
|
621
|
+
if (!expect_)
|
|
548
622
|
return found
|
|
549
|
-
if (found instanceof
|
|
623
|
+
if (found instanceof expect_)
|
|
550
624
|
return found as T
|
|
551
625
|
return null
|
|
552
626
|
} catch {
|
|
@@ -554,16 +628,16 @@ export class Folder extends Road {
|
|
|
554
628
|
}
|
|
555
629
|
}
|
|
556
630
|
findSync(name_: string): Road | null
|
|
557
|
-
findSync<T extends Road>(name_: string,
|
|
558
|
-
findSync<T extends Road>(name_: string,
|
|
631
|
+
findSync<T extends Road>(name_: string, expect_: road_t<T>): T | null
|
|
632
|
+
findSync<T extends Road>(name_: string, expect_?: road_t<T>): Road | T | null {
|
|
559
633
|
try {
|
|
560
634
|
const found = factorySync(this.join(name_))
|
|
561
|
-
if (!
|
|
635
|
+
if (!expect_)
|
|
562
636
|
return found
|
|
563
|
-
if (found instanceof
|
|
637
|
+
if (found instanceof expect_)
|
|
564
638
|
return found as T
|
|
565
639
|
return null
|
|
566
|
-
} catch {
|
|
640
|
+
} catch(e: unknown) {
|
|
567
641
|
return null
|
|
568
642
|
}
|
|
569
643
|
}
|
|
@@ -579,57 +653,25 @@ export class Folder extends Road {
|
|
|
579
653
|
return factorySync(newPath) as unknown as T
|
|
580
654
|
}
|
|
581
655
|
|
|
582
|
-
async
|
|
583
|
-
|
|
584
|
-
await
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
using _ = this.lockSync()
|
|
588
|
-
fs.rmSync(this.isAt, options_)
|
|
589
|
-
}
|
|
590
|
-
async move(into_: Folder) {
|
|
591
|
-
using _ = await this.lock()
|
|
592
|
-
const newPath = into_.join(this.name)
|
|
593
|
-
await fp.rename(this.isAt, newPath)
|
|
594
|
-
this.pointsTo = newPath
|
|
595
|
-
}
|
|
596
|
-
moveSync(into_: Folder) {
|
|
597
|
-
using _ = this.lockSync()
|
|
598
|
-
const newPath = into_.join(this.name)
|
|
599
|
-
fs.renameSync(this.isAt, newPath)
|
|
600
|
-
this.pointsTo = newPath
|
|
656
|
+
async size(): Promise<number> {
|
|
657
|
+
let size = 0
|
|
658
|
+
for await (const entry of this.it())
|
|
659
|
+
size += await entry.size()
|
|
660
|
+
return size
|
|
601
661
|
}
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
copySync(into_: Folder): this {
|
|
608
|
-
const newPath = into_.join(this.name)
|
|
609
|
-
fs.cpSync(this.isAt, newPath, { recursive: true })
|
|
610
|
-
return new Folder(newPath, false) as this
|
|
611
|
-
}
|
|
612
|
-
async rename(to_: string) {
|
|
613
|
-
using _ = await this.lock()
|
|
614
|
-
const newPath = this.parent().join(to_)
|
|
615
|
-
await fp.rename(this.isAt, newPath)
|
|
616
|
-
this.pointsTo = newPath
|
|
617
|
-
}
|
|
618
|
-
renameSync(to_: string) {
|
|
619
|
-
using _ = this.lockSync()
|
|
620
|
-
const newPath = this.parent().join(to_)
|
|
621
|
-
fs.renameSync(this.isAt, newPath)
|
|
622
|
-
this.pointsTo = newPath
|
|
662
|
+
sizeSync(): number {
|
|
663
|
+
let size = 0
|
|
664
|
+
for (const entry of this.itSync())
|
|
665
|
+
size += entry.sizeSync()
|
|
666
|
+
return size
|
|
623
667
|
}
|
|
624
668
|
|
|
625
|
-
async check(): Promise<boolean> { return (await fp.lstat(this.isAt)).isDirectory() }
|
|
626
|
-
checkSync(): boolean { return fs.lstatSync(this.isAt).isDirectory() }
|
|
669
|
+
async check(): Promise<boolean> { try { return (await fp.lstat(this.isAt)).isDirectory() } catch { return false } }
|
|
670
|
+
checkSync(): boolean { try { return fs.lstatSync(this.isAt).isDirectory() } catch { return false } }
|
|
627
671
|
|
|
628
672
|
override isFolder(): this is Folder { return true as const }
|
|
629
673
|
override isDir(): this is Folder { return true as const }
|
|
630
674
|
override isDirectory(): this is Folder { return true as const }
|
|
631
|
-
override isDict(): this is Folder { return true as const }
|
|
632
|
-
override isDictionary(): this is Folder { return true as const }
|
|
633
675
|
}
|
|
634
676
|
|
|
635
677
|
|
|
@@ -662,112 +704,106 @@ export class SymbolicLink extends Road {
|
|
|
662
704
|
return factorySync(ph.resolve(ph.dirname(this.isAt), fs.readlinkSync(this.isAt)))
|
|
663
705
|
}
|
|
664
706
|
async retarget(to_: Road) {
|
|
665
|
-
await this.
|
|
666
|
-
|
|
707
|
+
using _ = await this.lock()
|
|
708
|
+
await fp.unlink(this.isAt)
|
|
709
|
+
await fp.symlink(to_.isAt, this.isAt)
|
|
667
710
|
}
|
|
668
711
|
retargetSync(to_: Road) {
|
|
669
|
-
this.
|
|
712
|
+
using _ = this.lockSync()
|
|
713
|
+
fs.unlinkSync(this.isAt)
|
|
670
714
|
fs.symlinkSync(to_.isAt, this.isAt)
|
|
671
715
|
}
|
|
672
716
|
|
|
673
|
-
async
|
|
717
|
+
async size(): Promise<number> { return (await this.lstat()).size }
|
|
718
|
+
sizeSync(): number { return this.lstatSync().size }
|
|
719
|
+
|
|
720
|
+
override async delete() {
|
|
674
721
|
using _ = await this.lock()
|
|
675
722
|
await fp.unlink(this.isAt)
|
|
676
723
|
}
|
|
677
|
-
deleteSync() {
|
|
724
|
+
override deleteSync() {
|
|
678
725
|
using _ = this.lockSync()
|
|
679
726
|
fs.unlinkSync(this.isAt)
|
|
680
727
|
}
|
|
681
|
-
async move(into_: Folder) {
|
|
682
|
-
using _ = await this.lock()
|
|
683
|
-
const newPath = into_.join(this.name)
|
|
684
|
-
await fp.rename(this.isAt, newPath)
|
|
685
|
-
this.pointsTo = newPath
|
|
686
|
-
}
|
|
687
|
-
moveSync(into_: Folder) {
|
|
688
|
-
using _ = this.lockSync()
|
|
689
|
-
const newPath = into_.join(this.name)
|
|
690
|
-
fs.renameSync(this.isAt, newPath)
|
|
691
|
-
this.pointsTo = newPath
|
|
692
|
-
}
|
|
693
|
-
async copy(into_: Folder): Promise<this> {
|
|
694
|
-
const newPath = into_.join(this.name)
|
|
695
|
-
const target = await this.target()
|
|
696
|
-
await fp.symlink(target.isAt, newPath)
|
|
697
|
-
return new SymbolicLink(newPath, false) as this
|
|
698
|
-
}
|
|
699
|
-
copySync(into_: Folder): this {
|
|
700
|
-
const newPath = into_.join(this.name)
|
|
701
|
-
const target = this.targetSync()
|
|
702
|
-
fs.symlinkSync(target.isAt, newPath)
|
|
703
|
-
return new SymbolicLink(newPath, false) as this
|
|
704
|
-
}
|
|
705
|
-
async rename(to_: string) {
|
|
706
|
-
using _ = await this.lock()
|
|
707
|
-
const newPath = this.parent().join(to_)
|
|
708
|
-
await fp.rename(this.isAt, newPath)
|
|
709
|
-
this.pointsTo = newPath
|
|
710
|
-
}
|
|
711
|
-
renameSync(to_: string) {
|
|
712
|
-
using _ = this.lockSync()
|
|
713
|
-
const newPath = this.parent().join(to_)
|
|
714
|
-
fs.renameSync(this.isAt, newPath)
|
|
715
|
-
this.pointsTo = newPath
|
|
716
|
-
}
|
|
717
728
|
|
|
718
|
-
async check(): Promise<boolean> { return (await fp.lstat(this.isAt)).isSymbolicLink() }
|
|
719
|
-
checkSync(): boolean { return fs.lstatSync(this.isAt).isSymbolicLink() }
|
|
729
|
+
async check(): Promise<boolean> { try { return (await fp.lstat(this.isAt)).isSymbolicLink() } catch { return false } }
|
|
730
|
+
checkSync(): boolean { try { return fs.lstatSync(this.isAt).isSymbolicLink() } catch { return false } }
|
|
720
731
|
|
|
721
732
|
override isSymlink(): this is SymbolicLink { return true as const }
|
|
722
733
|
override isSymbolicLink(): this is SymbolicLink { return true as const }
|
|
723
734
|
}
|
|
724
735
|
export { SymbolicLink as Symlink }
|
|
736
|
+
here().walk(r => !r.isSymlink())
|
|
725
737
|
|
|
726
738
|
|
|
727
739
|
|
|
728
740
|
export abstract class UnusableRoad extends Road {
|
|
729
741
|
override readonly mutable: boolean = false // Modification will cause system issues (e.g. deleting a device file)
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
742
|
+
async size(): Promise<0> { return 0 }
|
|
743
|
+
sizeSync(): 0 { return 0 }
|
|
744
|
+
error(): never { throw new Err(`${this.constructor.name} at '${this.isAt}' is a system-level resource thus not subject to modification.`) }
|
|
745
|
+
/** @deprecated System-level resources (UnusableRoad) can't/shouldn't be locked */
|
|
746
|
+
override lock(): never { return this.error() }
|
|
747
|
+
/** @deprecated System-level resources (UnusableRoad) can't/shouldn't be locked */
|
|
736
748
|
override lockSync(): never { return this.error() }
|
|
737
|
-
|
|
749
|
+
/** @deprecated System-level resources (UnusableRoad) can't/shouldn't be deleted */
|
|
750
|
+
override delete(): never { return this.error() }
|
|
751
|
+
/** @deprecated System-level resources (UnusableRoad) can't/shouldn't be deleted */
|
|
738
752
|
override deleteSync(): never { return this.error() }
|
|
739
|
-
|
|
753
|
+
/** @deprecated System-level resources (UnusableRoad) can't/shouldn't be moved */
|
|
754
|
+
override move(): never { return this.error() }
|
|
755
|
+
/** @deprecated System-level resources (UnusableRoad) can't/shouldn't be moved */
|
|
740
756
|
override moveSync(): never { return this.error() }
|
|
741
|
-
|
|
757
|
+
/** @deprecated System-level resources (UnusableRoad) can't/shouldn't be copied */
|
|
758
|
+
override copy(): never { return this.error() }
|
|
759
|
+
/** @deprecated System-level resources (UnusableRoad) can't/shouldn't be copied */
|
|
742
760
|
override copySync(): never { return this.error() }
|
|
743
|
-
|
|
761
|
+
/** @deprecated System-level resources (UnusableRoad) can't/shouldn't be renamed */
|
|
762
|
+
override rename(): never { return this.error() }
|
|
763
|
+
/** @deprecated System-level resources (UnusableRoad) can't/shouldn't be renamed */
|
|
744
764
|
override renameSync(): never { return this.error() }
|
|
745
|
-
|
|
765
|
+
|
|
746
766
|
override isUnusable(): this is UnusableRoad { return true as const }
|
|
747
767
|
}
|
|
748
768
|
export class BlockDevice extends UnusableRoad {
|
|
749
|
-
async check(): Promise<boolean> { return (await fp.lstat(this.isAt)).isBlockDevice() }
|
|
750
|
-
checkSync(): boolean { return fs.lstatSync(this.isAt).isBlockDevice() }
|
|
769
|
+
async check(): Promise<boolean> { try { return (await fp.lstat(this.isAt)).isBlockDevice() } catch { return false } }
|
|
770
|
+
checkSync(): boolean { try { return fs.lstatSync(this.isAt).isBlockDevice() } catch { return false } }
|
|
751
771
|
override isBlockDevice(): this is BlockDevice { return true as const }
|
|
752
772
|
}
|
|
753
773
|
export class CharacterDevice extends UnusableRoad {
|
|
754
|
-
async check(): Promise<boolean> { return (await fp.lstat(this.isAt)).isCharacterDevice() }
|
|
755
|
-
checkSync(): boolean { return fs.lstatSync(this.isAt).isCharacterDevice() }
|
|
774
|
+
async check(): Promise<boolean> { try { return (await fp.lstat(this.isAt)).isCharacterDevice() } catch { return false } }
|
|
775
|
+
checkSync(): boolean { try { return fs.lstatSync(this.isAt).isCharacterDevice() } catch { return false } }
|
|
756
776
|
override isCharacterDevice(): this is CharacterDevice { return true as const }
|
|
757
777
|
}
|
|
758
778
|
export class Fifo extends UnusableRoad {
|
|
759
|
-
async check(): Promise<boolean> { return (await fp.lstat(this.isAt)).isFIFO() }
|
|
760
|
-
checkSync(): boolean { return fs.lstatSync(this.isAt).isFIFO() }
|
|
779
|
+
async check(): Promise<boolean> { try { return (await fp.lstat(this.isAt)).isFIFO() } catch { return false } }
|
|
780
|
+
checkSync(): boolean { try { return fs.lstatSync(this.isAt).isFIFO() } catch { return false } }
|
|
761
781
|
override isFifo(): this is Fifo { return true as const }
|
|
762
782
|
}
|
|
763
783
|
export class Socket extends UnusableRoad {
|
|
764
|
-
async check(): Promise<boolean> { return (await fp.lstat(this.isAt)).isSocket() }
|
|
765
|
-
checkSync(): boolean { return fs.lstatSync(this.isAt).isSocket() }
|
|
784
|
+
async check(): Promise<boolean> { try { return (await fp.lstat(this.isAt)).isSocket() } catch { return false } }
|
|
785
|
+
checkSync(): boolean { try { return fs.lstatSync(this.isAt).isSocket() } catch { return false } }
|
|
766
786
|
override isSocket(): this is Socket { return true as const }
|
|
767
787
|
}
|
|
768
788
|
|
|
769
789
|
|
|
770
790
|
|
|
791
|
+
export async function Temp<T extends Road>(createable_: { create: (at: string) => Promise<T> }): Promise<T & Disposable & AsyncDisposable> {
|
|
792
|
+
let t = await createable_.create(tmp().join(`instrumentality@${cr.randomUUID()}`))
|
|
793
|
+
return Object.assign(t, {
|
|
794
|
+
[Symbol.dispose]() { try { t.deleteSync() } catch {} toDelete?.delete(t.isAt); finalizer?.unregister(t) },
|
|
795
|
+
async [Symbol.asyncDispose]() { try { await t.delete() } catch {} toDelete?.delete(t.isAt); finalizer?.unregister(t) }
|
|
796
|
+
})
|
|
797
|
+
}
|
|
798
|
+
export function TempSync<T extends Road>(createable_: { createSync: (at: string) => T }): T & Disposable & AsyncDisposable {
|
|
799
|
+
let t = createable_.createSync(tmp().join(`instrumentality@${cr.randomUUID()}`))
|
|
800
|
+
return Object.assign(t, {
|
|
801
|
+
[Symbol.dispose]() { try { t.deleteSync() } catch {} toDelete?.delete(t.isAt); finalizer?.unregister(t) },
|
|
802
|
+
async [Symbol.asyncDispose]() { try { await t.delete() } catch {} toDelete?.delete(t.isAt); finalizer?.unregister(t) }
|
|
803
|
+
})
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
|
|
771
807
|
let finalizer: FinalizationRegistry<string> | null = null
|
|
772
808
|
let toDelete: Set<string> | null = null
|
|
773
809
|
let exitHandlerRegistered: boolean | null = null
|
|
@@ -796,12 +832,20 @@ export function registerToCleanup(self_: Road) {
|
|
|
796
832
|
}
|
|
797
833
|
|
|
798
834
|
|
|
799
|
-
export function
|
|
800
|
-
let t = createable_.
|
|
835
|
+
export async function AutoTemp<T extends Road>(createable_: { mk: (at: string) => Promise<T> }): Promise<T & Disposable & AsyncDisposable> {
|
|
836
|
+
let t = await createable_.mk(tmp().join(`instrumentality@${cr.randomUUID()}`))
|
|
837
|
+
registerToCleanup(t)
|
|
838
|
+
return Object.freeze(Object.assign(t, {
|
|
839
|
+
[Symbol.dispose]() { try { t.deleteSync() } catch {} toDelete?.delete(t.isAt); finalizer?.unregister(t) },
|
|
840
|
+
async [Symbol.asyncDispose]() { try { await t.delete() } catch {} toDelete?.delete(t.isAt); finalizer?.unregister(t) }
|
|
841
|
+
}))
|
|
842
|
+
}
|
|
843
|
+
export function AutoTempSync<T extends Road>(createable_: { mk: (at: string) => T }, autoCleanup_: boolean): T & Disposable & AsyncDisposable {
|
|
844
|
+
let t = createable_.mk(tmp().join(`instrumentality@${cr.randomUUID()}`))
|
|
801
845
|
if (autoCleanup_)
|
|
802
846
|
registerToCleanup(t)
|
|
803
847
|
return Object.freeze(Object.assign(t, {
|
|
804
848
|
[Symbol.dispose]() { try { t.deleteSync() } catch {} toDelete?.delete(t.isAt); finalizer?.unregister(t) },
|
|
805
849
|
async [Symbol.asyncDispose]() { try { await t.delete() } catch {} toDelete?.delete(t.isAt); finalizer?.unregister(t) }
|
|
806
850
|
}))
|
|
807
|
-
}
|
|
851
|
+
}
|