instrumentality 0.0.6 → 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 +136 -92
- package/dist/road.d.ts.map +1 -1
- package/dist/road.js +441 -313
- package/package.json +5 -3
- package/src/base.ts +22 -43
- package/src/dom.ts +1 -10
- package/src/road.ts +400 -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,26 +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}. */
|
|
342
|
+
static readonly mk: typeof File.create = File.create
|
|
343
|
+
/** Synchronous version of {@link File.create}. */
|
|
283
344
|
static createSync(at_: string) {
|
|
284
345
|
try { fs.accessSync(at_, fs.constants.W_OK) }
|
|
285
346
|
catch { fs.writeFileSync(at_, "") }
|
|
286
347
|
return new File(at_, true)
|
|
287
348
|
}
|
|
349
|
+
/** Alias for {@link File.createSync}. */
|
|
350
|
+
static readonly mkSync: typeof File.createSync = File.createSync
|
|
288
351
|
|
|
352
|
+
/** The file extension of this file, including the leading dot. */
|
|
289
353
|
get ext() { return ph.extname(this.isAt) }
|
|
354
|
+
/** The file name without its extension. */
|
|
290
355
|
get noExt() { return ph.basename(this.isAt, this.ext) }
|
|
291
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
|
+
*/
|
|
292
363
|
async read(): Promise<Buffer>
|
|
293
364
|
async read(encoding_: BufferEncoding, flag_?: string): Promise<string>
|
|
294
365
|
async read(encoding_?: BufferEncoding, flag_?: string): Promise<Buffer | string> {
|
|
@@ -306,16 +377,45 @@ export class File extends Road {
|
|
|
306
377
|
return fs.readFileSync(this.isAt)
|
|
307
378
|
}
|
|
308
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
|
+
|
|
309
410
|
async *itBuff(chunkSize_: number = 64 * 1024, flags_: string | number = 'r', mode_?: fs.Mode) {
|
|
310
411
|
const fd = await fp.open(this.isAt, flags_, mode_)
|
|
311
412
|
try {
|
|
312
413
|
const buffer = Buffer.alloc(chunkSize_)
|
|
313
414
|
let bytesRead: number
|
|
314
415
|
do {
|
|
315
|
-
|
|
316
|
-
bytesRead = readResult.bytesRead
|
|
416
|
+
bytesRead = (await fd.read(buffer, 0, chunkSize_, null)).bytesRead
|
|
317
417
|
if (bytesRead > 0)
|
|
318
|
-
yield buffer.subarray(0, bytesRead)
|
|
418
|
+
yield Buffer.from(buffer.subarray(0, bytesRead))
|
|
319
419
|
} while (bytesRead === chunkSize_)
|
|
320
420
|
} finally {
|
|
321
421
|
await fd.close()
|
|
@@ -329,12 +429,81 @@ export class File extends Road {
|
|
|
329
429
|
do {
|
|
330
430
|
bytesRead = fs.readSync(fd, buffer, 0, chunkSize_, null)
|
|
331
431
|
if (bytesRead > 0)
|
|
332
|
-
yield buffer.subarray(0, bytesRead)
|
|
432
|
+
yield Buffer.from(buffer.subarray(0, bytesRead))
|
|
333
433
|
} while (bytesRead === chunkSize_)
|
|
334
434
|
} finally {
|
|
335
435
|
fs.closeSync(fd)
|
|
336
436
|
}
|
|
337
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
|
+
}
|
|
338
507
|
|
|
339
508
|
async write(data_: Buffer | string, options_?: fs.WriteFileOptions) {
|
|
340
509
|
using _ = await this.lock()
|
|
@@ -353,134 +522,16 @@ export class File extends Road {
|
|
|
353
522
|
fs.appendFileSync(this.isAt, data_, options_)
|
|
354
523
|
}
|
|
355
524
|
|
|
356
|
-
async
|
|
357
|
-
|
|
358
|
-
await fp.rm(this.isAt, { force: true })
|
|
359
|
-
}
|
|
360
|
-
deleteSync() {
|
|
361
|
-
using _ = this.lockSync()
|
|
362
|
-
fs.rmSync(this.isAt, { force: true })
|
|
363
|
-
}
|
|
364
|
-
async move(into_: Folder) {
|
|
365
|
-
using _ = await this.lock()
|
|
366
|
-
const newPath = into_.join(this.name)
|
|
367
|
-
await fp.rename(this.isAt, newPath)
|
|
368
|
-
this.pointsTo = newPath
|
|
369
|
-
}
|
|
370
|
-
moveSync(into_: Folder) {
|
|
371
|
-
using _ = this.lockSync()
|
|
372
|
-
const newPath = into_.join(this.name)
|
|
373
|
-
fs.renameSync(this.isAt, newPath)
|
|
374
|
-
this.pointsTo = newPath
|
|
375
|
-
}
|
|
376
|
-
async copy(into_: Folder): Promise<this> {
|
|
377
|
-
const newPath = into_.join(this.name)
|
|
378
|
-
await fp.copyFile(this.isAt, newPath)
|
|
379
|
-
return new File(newPath, false) as this
|
|
380
|
-
}
|
|
381
|
-
copySync(into_: Folder): this {
|
|
382
|
-
const newPath = into_.join(this.name)
|
|
383
|
-
fs.copyFileSync(this.isAt, newPath)
|
|
384
|
-
return new File(newPath, false) as this
|
|
385
|
-
}
|
|
386
|
-
async rename(to_: string) {
|
|
387
|
-
using _ = await this.lock()
|
|
388
|
-
const newPath = this.parent().join(to_)
|
|
389
|
-
await fp.rename(this.isAt, newPath)
|
|
390
|
-
this.pointsTo = newPath
|
|
391
|
-
}
|
|
392
|
-
renameSync(to_: string) {
|
|
393
|
-
using _ = this.lockSync()
|
|
394
|
-
const newPath = this.parent().join(to_)
|
|
395
|
-
fs.renameSync(this.isAt, newPath)
|
|
396
|
-
this.pointsTo = newPath
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
async check(): Promise<boolean> { return (await fp.lstat(this.isAt)).isFile() }
|
|
400
|
-
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 } }
|
|
401
527
|
|
|
402
528
|
override isFile(): this is File { return true as const }
|
|
403
529
|
}
|
|
404
530
|
|
|
405
531
|
|
|
406
|
-
// Bizarre functions for file I/O
|
|
407
|
-
export async function hash(f_: File, algorithm_?: string, options_?: cr.HashOptions): Promise<Buffer>
|
|
408
|
-
export async function hash(f_: File, algorithm_?: string, options_?: cr.HashOptions, encoding_?: BufferEncoding): Promise<string>
|
|
409
|
-
export async function hash(f_: File, algorithm_ = "sha256", options_?: cr.HashOptions, encoding_?: BufferEncoding): Promise<Buffer | string> {
|
|
410
|
-
const hash = cr.createHash(algorithm_, options_)
|
|
411
|
-
for await (const chunk of f_.itBuff())
|
|
412
|
-
hash.update(chunk)
|
|
413
|
-
return encoding_ ? hash.digest(encoding_) : hash.digest()
|
|
414
|
-
}
|
|
415
|
-
export function hashSync(f_: File, algorithm_?: string, options_?: cr.HashOptions): Buffer
|
|
416
|
-
export function hashSync(f_: File, algorithm_?: string, options_?: cr.HashOptions, encoding_?: BufferEncoding): string
|
|
417
|
-
export function hashSync(f_: File, algorithm_ = "sha256", options_?: cr.HashOptions, encoding_?: BufferEncoding): Buffer | string {
|
|
418
|
-
const hash = cr.createHash(algorithm_, options_)
|
|
419
|
-
for (const chunk of f_.itBuffSync())
|
|
420
|
-
hash.update(chunk)
|
|
421
|
-
return encoding_ ? hash.digest(encoding_) : hash.digest()
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
export async function streamHash(f_: File, algorithm_?: string, options_?: cr.HashOptions): Promise<Buffer>
|
|
425
|
-
export async function streamHash(f_: File, algorithm_?: string, options_?: cr.HashOptions, encoding_?: BufferEncoding): Promise<string>
|
|
426
|
-
export async function streamHash(f_: File, algorithm_ = "sha256", options_?: cr.HashOptions, encoding_?: BufferEncoding): Promise<Buffer | string> {
|
|
427
|
-
const hash = cr.createHash(algorithm_, options_)
|
|
428
|
-
for await (const chunk of f_.itBuff())
|
|
429
|
-
hash.update(chunk)
|
|
430
|
-
return encoding_ ? hash.digest(encoding_) : hash.digest()
|
|
431
|
-
}
|
|
432
|
-
export function streamHashSync(f_: File, algorithm_?: string, options_?: cr.HashOptions): Buffer
|
|
433
|
-
export function streamHashSync(f_: File, algorithm_?: string, options_?: cr.HashOptions, encoding_?: BufferEncoding): string
|
|
434
|
-
export function streamHashSync(f_: File, algorithm_ = "sha256", options_?: cr.HashOptions, encoding_?: BufferEncoding): Buffer | string {
|
|
435
|
-
const hash = cr.createHash(algorithm_, options_)
|
|
436
|
-
for (const chunk of f_.itBuffSync())
|
|
437
|
-
hash.update(chunk)
|
|
438
|
-
return encoding_ ? hash.digest(encoding_) : hash.digest()
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
export async function* itLines(f_: File, options_: fs.ReadStreamOptions = { encoding: 'utf-8' }) {
|
|
442
|
-
const readStream = fs.createReadStream(f_.isAt, options_)
|
|
443
|
-
const rlInterface = rl.createInterface({ input: readStream, crlfDelay: Infinity })
|
|
444
|
-
try {
|
|
445
|
-
for await (const line of rlInterface)
|
|
446
|
-
yield line
|
|
447
|
-
} finally {
|
|
448
|
-
rlInterface.close()
|
|
449
|
-
readStream.destroy()
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
export async function fileSameAs(f1_: File, f2_: File): Promise<boolean> {
|
|
454
|
-
if (f1_.isAt === f2_.isAt)
|
|
455
|
-
return true
|
|
456
|
-
else if ((await fp.lstat(f1_.isAt)).size !== (await fp.lstat(f2_.isAt)).size)
|
|
457
|
-
return false
|
|
458
|
-
const iter1 = f1_.itBuff()
|
|
459
|
-
const iter2 = f2_.itBuff()
|
|
460
|
-
while (true) {
|
|
461
|
-
const [a, b] = await Promise.all([iter1.next(), iter2.next()])
|
|
462
|
-
if (a.done && b.done) return true
|
|
463
|
-
if (a.done !== b.done) return false
|
|
464
|
-
if (!a.value!.equals(b.value!)) return false
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
export function fileSameAsSync(f1_: File, f2_: File): boolean {
|
|
468
|
-
if (f1_.isAt === f2_.isAt)
|
|
469
|
-
return true
|
|
470
|
-
else if (fs.statSync(f1_.isAt).size !== fs.statSync(f2_.isAt).size)
|
|
471
|
-
return false
|
|
472
|
-
const iter1 = f1_.itBuffSync()
|
|
473
|
-
const iter2 = f2_.itBuffSync()
|
|
474
|
-
while (true) {
|
|
475
|
-
const a = iter1.next()
|
|
476
|
-
const b = iter2.next()
|
|
477
|
-
if (a.done && b.done) return true
|
|
478
|
-
if (a.done !== b.done) return false
|
|
479
|
-
if (!a.value!.equals(b.value!)) return false
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
|
|
483
532
|
|
|
533
|
+
/** Helper type for filtering {@link Road} instances. */
|
|
534
|
+
export type filter_t<T extends Road> = ((road: Road) => road is T)
|
|
484
535
|
|
|
485
536
|
export class Folder extends Road {
|
|
486
537
|
static async create(at_: string) {
|
|
@@ -488,61 +539,88 @@ export class Folder extends Road {
|
|
|
488
539
|
catch { await fp.mkdir(at_, { recursive: true }) }
|
|
489
540
|
return new Folder(at_, false)
|
|
490
541
|
}
|
|
542
|
+
static readonly mk: typeof Folder.create = Folder.create
|
|
491
543
|
static createSync(at_: string) {
|
|
492
544
|
try { fs.accessSync(at_, fsc.W_OK) }
|
|
493
545
|
catch { fs.mkdirSync(at_, { recursive: true }) }
|
|
494
546
|
return new Folder(at_, false)
|
|
495
547
|
}
|
|
548
|
+
static readonly mkSync: typeof Folder.createSync = Folder.createSync
|
|
496
549
|
|
|
497
550
|
join(...paths_: string[]) {
|
|
498
551
|
return ph.join(this.isAt, ...paths_)
|
|
499
552
|
}
|
|
500
553
|
|
|
501
554
|
it(): AsyncIterable<Road>
|
|
502
|
-
it<T extends Road>(
|
|
503
|
-
async *it<T extends Road>(
|
|
504
|
-
for (const
|
|
505
|
-
const road =
|
|
506
|
-
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))
|
|
507
560
|
yield road
|
|
508
561
|
}
|
|
509
562
|
}
|
|
510
563
|
itSync(): Iterable<Road>
|
|
511
|
-
itSync<T extends Road>(
|
|
512
|
-
*itSync<T extends Road>(
|
|
513
|
-
for (const entry of fs.readdirSync(this.isAt)) {
|
|
514
|
-
const road =
|
|
515
|
-
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))
|
|
516
569
|
yield road
|
|
517
570
|
}
|
|
518
571
|
}
|
|
572
|
+
|
|
519
573
|
async list(): Promise<Road[]>
|
|
520
|
-
async list<T extends Road>(
|
|
521
|
-
async list<T extends Road>(
|
|
522
|
-
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))
|
|
523
577
|
const resolvedEntries = await Promise.all(entries)
|
|
524
|
-
if (!
|
|
578
|
+
if (!filter_)
|
|
525
579
|
return resolvedEntries
|
|
526
|
-
return resolvedEntries.filter(entry => entry
|
|
580
|
+
return resolvedEntries.filter(entry => filter_(entry))
|
|
527
581
|
}
|
|
528
582
|
listSync(): Road[]
|
|
529
|
-
listSync<T extends Road>(
|
|
530
|
-
listSync<T extends Road>(
|
|
531
|
-
const entries = fs.readdirSync(this.isAt).map(
|
|
532
|
-
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_)
|
|
533
587
|
return entries
|
|
534
|
-
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
|
+
}
|
|
535
614
|
}
|
|
536
615
|
|
|
537
616
|
async find(name_: string): Promise<Road | null>
|
|
538
|
-
async find<T extends Road>(name_: string,
|
|
539
|
-
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> {
|
|
540
619
|
try {
|
|
541
|
-
await fp.access(this.join(name_), fs.constants.F_OK)
|
|
542
620
|
const found = await factory(this.join(name_))
|
|
543
|
-
if (!
|
|
621
|
+
if (!expect_)
|
|
544
622
|
return found
|
|
545
|
-
if (found instanceof
|
|
623
|
+
if (found instanceof expect_)
|
|
546
624
|
return found as T
|
|
547
625
|
return null
|
|
548
626
|
} catch {
|
|
@@ -550,16 +628,16 @@ export class Folder extends Road {
|
|
|
550
628
|
}
|
|
551
629
|
}
|
|
552
630
|
findSync(name_: string): Road | null
|
|
553
|
-
findSync<T extends Road>(name_: string,
|
|
554
|
-
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 {
|
|
555
633
|
try {
|
|
556
634
|
const found = factorySync(this.join(name_))
|
|
557
|
-
if (!
|
|
635
|
+
if (!expect_)
|
|
558
636
|
return found
|
|
559
|
-
if (found instanceof
|
|
637
|
+
if (found instanceof expect_)
|
|
560
638
|
return found as T
|
|
561
639
|
return null
|
|
562
|
-
} catch {
|
|
640
|
+
} catch(e: unknown) {
|
|
563
641
|
return null
|
|
564
642
|
}
|
|
565
643
|
}
|
|
@@ -575,57 +653,25 @@ export class Folder extends Road {
|
|
|
575
653
|
return factorySync(newPath) as unknown as T
|
|
576
654
|
}
|
|
577
655
|
|
|
578
|
-
async
|
|
579
|
-
|
|
580
|
-
await
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
using _ = this.lockSync()
|
|
584
|
-
fs.rmSync(this.isAt, options_)
|
|
585
|
-
}
|
|
586
|
-
async move(into_: Folder) {
|
|
587
|
-
using _ = await this.lock()
|
|
588
|
-
const newPath = into_.join(this.name)
|
|
589
|
-
await fp.rename(this.isAt, newPath)
|
|
590
|
-
this.pointsTo = newPath
|
|
591
|
-
}
|
|
592
|
-
moveSync(into_: Folder) {
|
|
593
|
-
using _ = this.lockSync()
|
|
594
|
-
const newPath = into_.join(this.name)
|
|
595
|
-
fs.renameSync(this.isAt, newPath)
|
|
596
|
-
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
|
|
597
661
|
}
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
copySync(into_: Folder): this {
|
|
604
|
-
const newPath = into_.join(this.name)
|
|
605
|
-
fs.cpSync(this.isAt, newPath, { recursive: true })
|
|
606
|
-
return new Folder(newPath, false) as this
|
|
607
|
-
}
|
|
608
|
-
async rename(to_: string) {
|
|
609
|
-
using _ = await this.lock()
|
|
610
|
-
const newPath = this.parent().join(to_)
|
|
611
|
-
await fp.rename(this.isAt, newPath)
|
|
612
|
-
this.pointsTo = newPath
|
|
613
|
-
}
|
|
614
|
-
renameSync(to_: string) {
|
|
615
|
-
using _ = this.lockSync()
|
|
616
|
-
const newPath = this.parent().join(to_)
|
|
617
|
-
fs.renameSync(this.isAt, newPath)
|
|
618
|
-
this.pointsTo = newPath
|
|
662
|
+
sizeSync(): number {
|
|
663
|
+
let size = 0
|
|
664
|
+
for (const entry of this.itSync())
|
|
665
|
+
size += entry.sizeSync()
|
|
666
|
+
return size
|
|
619
667
|
}
|
|
620
668
|
|
|
621
|
-
async check(): Promise<boolean> { return (await fp.lstat(this.isAt)).isDirectory() }
|
|
622
|
-
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 } }
|
|
623
671
|
|
|
624
672
|
override isFolder(): this is Folder { return true as const }
|
|
625
673
|
override isDir(): this is Folder { return true as const }
|
|
626
674
|
override isDirectory(): this is Folder { return true as const }
|
|
627
|
-
override isDict(): this is Folder { return true as const }
|
|
628
|
-
override isDictionary(): this is Folder { return true as const }
|
|
629
675
|
}
|
|
630
676
|
|
|
631
677
|
|
|
@@ -643,11 +689,13 @@ export class SymbolicLink extends Road {
|
|
|
643
689
|
catch { await fp.symlink(target_.toString(), at_) }
|
|
644
690
|
return new SymbolicLink(at_, false)
|
|
645
691
|
}
|
|
692
|
+
static readonly mk: typeof SymbolicLink.create = SymbolicLink.create
|
|
646
693
|
static createSync(at_: string, target_: string | Road) {
|
|
647
694
|
try { fs.accessSync(at_, fs.constants.F_OK) }
|
|
648
695
|
catch { fs.symlinkSync(target_.toString(), at_) }
|
|
649
696
|
return new SymbolicLink(at_, false)
|
|
650
697
|
}
|
|
698
|
+
static readonly mkSync: typeof SymbolicLink.createSync = SymbolicLink.createSync
|
|
651
699
|
|
|
652
700
|
async target() {
|
|
653
701
|
return factory(ph.resolve(ph.dirname(this.isAt), await fp.readlink(this.isAt)))
|
|
@@ -656,112 +704,106 @@ export class SymbolicLink extends Road {
|
|
|
656
704
|
return factorySync(ph.resolve(ph.dirname(this.isAt), fs.readlinkSync(this.isAt)))
|
|
657
705
|
}
|
|
658
706
|
async retarget(to_: Road) {
|
|
659
|
-
await this.
|
|
660
|
-
|
|
707
|
+
using _ = await this.lock()
|
|
708
|
+
await fp.unlink(this.isAt)
|
|
709
|
+
await fp.symlink(to_.isAt, this.isAt)
|
|
661
710
|
}
|
|
662
711
|
retargetSync(to_: Road) {
|
|
663
|
-
this.
|
|
712
|
+
using _ = this.lockSync()
|
|
713
|
+
fs.unlinkSync(this.isAt)
|
|
664
714
|
fs.symlinkSync(to_.isAt, this.isAt)
|
|
665
715
|
}
|
|
666
716
|
|
|
667
|
-
async
|
|
717
|
+
async size(): Promise<number> { return (await this.lstat()).size }
|
|
718
|
+
sizeSync(): number { return this.lstatSync().size }
|
|
719
|
+
|
|
720
|
+
override async delete() {
|
|
668
721
|
using _ = await this.lock()
|
|
669
722
|
await fp.unlink(this.isAt)
|
|
670
723
|
}
|
|
671
|
-
deleteSync() {
|
|
724
|
+
override deleteSync() {
|
|
672
725
|
using _ = this.lockSync()
|
|
673
726
|
fs.unlinkSync(this.isAt)
|
|
674
727
|
}
|
|
675
|
-
async move(into_: Folder) {
|
|
676
|
-
using _ = await this.lock()
|
|
677
|
-
const newPath = into_.join(this.name)
|
|
678
|
-
await fp.rename(this.isAt, newPath)
|
|
679
|
-
this.pointsTo = newPath
|
|
680
|
-
}
|
|
681
|
-
moveSync(into_: Folder) {
|
|
682
|
-
using _ = this.lockSync()
|
|
683
|
-
const newPath = into_.join(this.name)
|
|
684
|
-
fs.renameSync(this.isAt, newPath)
|
|
685
|
-
this.pointsTo = newPath
|
|
686
|
-
}
|
|
687
|
-
async copy(into_: Folder): Promise<this> {
|
|
688
|
-
const newPath = into_.join(this.name)
|
|
689
|
-
const target = await this.target()
|
|
690
|
-
await fp.symlink(target.isAt, newPath)
|
|
691
|
-
return new SymbolicLink(newPath, false) as this
|
|
692
|
-
}
|
|
693
|
-
copySync(into_: Folder): this {
|
|
694
|
-
const newPath = into_.join(this.name)
|
|
695
|
-
const target = this.targetSync()
|
|
696
|
-
fs.symlinkSync(target.isAt, newPath)
|
|
697
|
-
return new SymbolicLink(newPath, false) as this
|
|
698
|
-
}
|
|
699
|
-
async rename(to_: string) {
|
|
700
|
-
using _ = await this.lock()
|
|
701
|
-
const newPath = this.parent().join(to_)
|
|
702
|
-
await fp.rename(this.isAt, newPath)
|
|
703
|
-
this.pointsTo = newPath
|
|
704
|
-
}
|
|
705
|
-
renameSync(to_: string) {
|
|
706
|
-
using _ = this.lockSync()
|
|
707
|
-
const newPath = this.parent().join(to_)
|
|
708
|
-
fs.renameSync(this.isAt, newPath)
|
|
709
|
-
this.pointsTo = newPath
|
|
710
|
-
}
|
|
711
728
|
|
|
712
|
-
async check(): Promise<boolean> { return (await fp.lstat(this.isAt)).isSymbolicLink() }
|
|
713
|
-
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 } }
|
|
714
731
|
|
|
715
732
|
override isSymlink(): this is SymbolicLink { return true as const }
|
|
716
733
|
override isSymbolicLink(): this is SymbolicLink { return true as const }
|
|
717
734
|
}
|
|
718
735
|
export { SymbolicLink as Symlink }
|
|
736
|
+
here().walk(r => !r.isSymlink())
|
|
719
737
|
|
|
720
738
|
|
|
721
739
|
|
|
722
740
|
export abstract class UnusableRoad extends Road {
|
|
723
741
|
override readonly mutable: boolean = false // Modification will cause system issues (e.g. deleting a device file)
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
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 */
|
|
730
748
|
override lockSync(): never { return this.error() }
|
|
731
|
-
|
|
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 */
|
|
732
752
|
override deleteSync(): never { return this.error() }
|
|
733
|
-
|
|
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 */
|
|
734
756
|
override moveSync(): never { return this.error() }
|
|
735
|
-
|
|
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 */
|
|
736
760
|
override copySync(): never { return this.error() }
|
|
737
|
-
|
|
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 */
|
|
738
764
|
override renameSync(): never { return this.error() }
|
|
739
|
-
|
|
765
|
+
|
|
740
766
|
override isUnusable(): this is UnusableRoad { return true as const }
|
|
741
767
|
}
|
|
742
768
|
export class BlockDevice extends UnusableRoad {
|
|
743
|
-
async check(): Promise<boolean> { return (await fp.lstat(this.isAt)).isBlockDevice() }
|
|
744
|
-
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 } }
|
|
745
771
|
override isBlockDevice(): this is BlockDevice { return true as const }
|
|
746
772
|
}
|
|
747
773
|
export class CharacterDevice extends UnusableRoad {
|
|
748
|
-
async check(): Promise<boolean> { return (await fp.lstat(this.isAt)).isCharacterDevice() }
|
|
749
|
-
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 } }
|
|
750
776
|
override isCharacterDevice(): this is CharacterDevice { return true as const }
|
|
751
777
|
}
|
|
752
778
|
export class Fifo extends UnusableRoad {
|
|
753
|
-
async check(): Promise<boolean> { return (await fp.lstat(this.isAt)).isFIFO() }
|
|
754
|
-
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 } }
|
|
755
781
|
override isFifo(): this is Fifo { return true as const }
|
|
756
782
|
}
|
|
757
783
|
export class Socket extends UnusableRoad {
|
|
758
|
-
async check(): Promise<boolean> { return (await fp.lstat(this.isAt)).isSocket() }
|
|
759
|
-
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 } }
|
|
760
786
|
override isSocket(): this is Socket { return true as const }
|
|
761
787
|
}
|
|
762
788
|
|
|
763
789
|
|
|
764
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
|
+
|
|
765
807
|
let finalizer: FinalizationRegistry<string> | null = null
|
|
766
808
|
let toDelete: Set<string> | null = null
|
|
767
809
|
let exitHandlerRegistered: boolean | null = null
|
|
@@ -790,12 +832,20 @@ export function registerToCleanup(self_: Road) {
|
|
|
790
832
|
}
|
|
791
833
|
|
|
792
834
|
|
|
793
|
-
export function
|
|
794
|
-
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()}`))
|
|
795
845
|
if (autoCleanup_)
|
|
796
846
|
registerToCleanup(t)
|
|
797
847
|
return Object.freeze(Object.assign(t, {
|
|
798
848
|
[Symbol.dispose]() { try { t.deleteSync() } catch {} toDelete?.delete(t.isAt); finalizer?.unregister(t) },
|
|
799
849
|
async [Symbol.asyncDispose]() { try { await t.delete() } catch {} toDelete?.delete(t.isAt); finalizer?.unregister(t) }
|
|
800
850
|
}))
|
|
801
|
-
}
|
|
851
|
+
}
|