instrumentality 0.0.2 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/base.d.ts +30 -47
- package/dist/base.d.ts.map +1 -1
- package/dist/base.js +29 -44
- package/dist/dom.d.ts +19 -28
- package/dist/dom.d.ts.map +1 -1
- package/dist/dom.js +26 -41
- package/dist/road.d.ts +219 -179
- package/dist/road.d.ts.map +1 -1
- package/dist/road.js +450 -476
- package/package.json +1 -1
- package/src/base.ts +33 -51
- package/src/dom.ts +29 -44
- package/src/road.ts +502 -529
package/dist/road.js
CHANGED
|
@@ -5,24 +5,22 @@ import * as fp from "node:fs/promises";
|
|
|
5
5
|
import * as ph from "node:path";
|
|
6
6
|
import * as os from "node:os";
|
|
7
7
|
import * as cr from "node:crypto";
|
|
8
|
-
import * as sp from "node:stream/promises";
|
|
9
8
|
import { on } from "node:events";
|
|
10
9
|
import * as bs from "./base.js";
|
|
11
|
-
/**
|
|
12
|
-
* Subclass of {@link bs.InsErr} that represents an error thrown from this specific module of the library
|
|
13
|
-
*/
|
|
10
|
+
/** Subclass of {@link bs.InsErr} that represents an error thrown from this specific module of the library */
|
|
14
11
|
export class RdErr extends bs.InsErr {
|
|
15
12
|
name = "Instrumentality-Road-Error";
|
|
16
13
|
}
|
|
14
|
+
export { RdErr as RoadError };
|
|
17
15
|
/**
|
|
18
|
-
* Returns the constructor function corresponding to the file mode.
|
|
16
|
+
* Returns the constructor function corresponding to the file mode (statmode).
|
|
19
17
|
*
|
|
20
|
-
* @param
|
|
21
|
-
* @returns The constructor function corresponding to the
|
|
18
|
+
* @param statmode_ The file mode to check.
|
|
19
|
+
* @returns The constructor function corresponding to the road type (e.g., {@link File}, {@link Folder}, etc.).
|
|
22
20
|
* @throws If the file mode is unknown, throws a {@link RdErr}.
|
|
23
21
|
*/
|
|
24
|
-
export function
|
|
25
|
-
switch (
|
|
22
|
+
export function resolveMode(statmode_) {
|
|
23
|
+
switch (statmode_ & fsc.S_IFMT) {
|
|
26
24
|
case fsc.S_IFREG: return File;
|
|
27
25
|
case fsc.S_IFDIR: return Folder;
|
|
28
26
|
case fsc.S_IFBLK: return BlockDevice;
|
|
@@ -30,183 +28,127 @@ export function modeCtor(statmode) {
|
|
|
30
28
|
case fsc.S_IFLNK: return SymbolicLink;
|
|
31
29
|
case fsc.S_IFIFO: return Fifo;
|
|
32
30
|
case fsc.S_IFSOCK: return Socket;
|
|
33
|
-
default: throw new RdErr(`Unknown mode type ${
|
|
31
|
+
default: throw new RdErr(`Unknown mode type ${statmode_} (statmode is most likely corrupted)`);
|
|
34
32
|
}
|
|
35
33
|
}
|
|
36
34
|
/**
|
|
37
|
-
* Creates
|
|
35
|
+
* Creates the appropriate subclass of {@link Road} based on the file mode of the specified path.
|
|
38
36
|
*
|
|
39
|
-
* @param
|
|
40
|
-
* @returns A new instance of
|
|
41
|
-
* @throws If
|
|
37
|
+
* @param path_ The path to follow.
|
|
38
|
+
* @returns A new instance of {@link Road}.
|
|
39
|
+
* @throws If {@link fp.lstat}/{@link fs.lstatSync} fails to retrieved the status of {@link path_}.
|
|
42
40
|
*/
|
|
43
|
-
export function
|
|
44
|
-
|
|
45
|
-
return new (modeCtor(fs.lstatSync(lookFor).mode))(lookFor, false);
|
|
41
|
+
export async function factory(path_) {
|
|
42
|
+
return new (resolveMode((await fp.lstat(path_)).mode))(path_, false);
|
|
46
43
|
}
|
|
47
|
-
/**
|
|
48
|
-
export
|
|
49
|
-
|
|
50
|
-
return new (modeCtor((await fp.lstat(lookFor)).mode))(lookFor, false);
|
|
44
|
+
/** Sync version of {@link factory}. */
|
|
45
|
+
export function factorySync(path_) {
|
|
46
|
+
return new (resolveMode(fs.lstatSync(path_).mode))(path_, false);
|
|
51
47
|
}
|
|
52
48
|
/**
|
|
53
|
-
* A map that keeps track of locked roads to prevent concurrent modifications.
|
|
49
|
+
* A map that keeps track of locked roads to prevent concurrent modifications.
|
|
50
|
+
*
|
|
51
|
+
* @key The absolute path of the road that is currently locked.
|
|
52
|
+
* @value A promise that resolves when the lock on the road is released.
|
|
54
53
|
*
|
|
55
|
-
* @remarks
|
|
56
|
-
*
|
|
54
|
+
* @remarks The map is initialized lazily when the first lock is created to minimize import-time side effects.
|
|
55
|
+
* It's generally meant for read-only purposes. It's not advised to modify this map directly if there are built-in mechanisms that do the job for you as well.
|
|
57
56
|
*/
|
|
58
|
-
let lockedRoads = null;
|
|
57
|
+
export let lockedRoads = null;
|
|
59
58
|
/**
|
|
60
|
-
*
|
|
59
|
+
* Road is an OOP, pointer-like representation of an entry in the local file system. It wraps around the Node.js fs module and provides a more convenient access to it.
|
|
60
|
+
* It's meant to help the user mentally model an entry and help them reason about it, as well as provide a more convenient API and guardrails for common operations.
|
|
61
61
|
*
|
|
62
|
-
* @
|
|
63
|
-
*
|
|
62
|
+
* @remarks This is by no means a one-to-one mapping of the underlying file system (after initialization).
|
|
63
|
+
* It is more like a memory representation, similar to a pointer in low-level programming languages; other processes might mess with the underlying entry. There are methods to check for consistency, but they are not guaranteed to be foolproof.
|
|
64
64
|
*/
|
|
65
|
-
export function lockFor(roadOrPath) {
|
|
66
|
-
return lockedRoads?.get(roadOrPath.toString());
|
|
67
|
-
}
|
|
68
65
|
export class Road {
|
|
69
66
|
/** The absolute path to the file or directory that this Road instance represents.
|
|
70
|
-
* Intentionally made protected to prevent external modification, as changing this value could lead to inconsistencies and unexpected behavior. */
|
|
67
|
+
* @remarks Intentionally made protected to prevent external modification, as changing this value could lead to inconsistencies and unexpected behavior. */
|
|
71
68
|
pointsTo;
|
|
72
69
|
/** Indicates whether the file or directory represented by this Road instance can be modified.
|
|
73
70
|
* Changing this value does not affect the actual file system permissions, but rather serves as a safeguard within the application to prevent accidental modifications. */
|
|
74
71
|
mutable = true;
|
|
75
72
|
// Quick accessors
|
|
76
|
-
/**
|
|
73
|
+
/** Copy of the absolute path. */
|
|
77
74
|
get isAt() { return this.pointsTo; }
|
|
78
|
-
/**
|
|
79
|
-
get name() { return
|
|
80
|
-
/**
|
|
75
|
+
/** Name of the road without the path (including extensions). */
|
|
76
|
+
get name() { return this.isAt.slice(this.isAt.lastIndexOf(ph.sep) + 1); }
|
|
77
|
+
/** The amount of path segments in the absolute path to the file or directory represented by this Road instance, minus one (i.e., the depth of the path in the file system hierarchy). */
|
|
78
|
+
get depth() { return this.isAt.split(ph.sep).length - 1; }
|
|
79
|
+
/** Same as {@link isAt} but for compatibility with external APIs. */
|
|
81
80
|
toString() { return this.isAt; }
|
|
82
|
-
/** Returns the OS file type of the file or directory.
|
|
83
|
-
* Return value (OS type) and the type of this instance are not guaranteed to be the same, as the file system may have changed since this instance was created. */
|
|
84
|
-
typeSync() { return (modeCtor(fs.lstatSync(this.isAt).mode)); }
|
|
85
|
-
/** Async version of {@link typeSync}. */
|
|
86
|
-
async type() { return (modeCtor((await fp.lstat(this.isAt)).mode)); }
|
|
87
81
|
/**
|
|
88
|
-
*
|
|
82
|
+
* Creates a new instance of the Road class.
|
|
89
83
|
*
|
|
90
|
-
* @param
|
|
91
|
-
* @param
|
|
92
|
-
* This can be skipped for performance reasons if the type is known to be correct, but it is recommended to keep it enabled for safety.
|
|
93
|
-
* @throws If the specified path does not exist, throws a fs.{@link Error}.
|
|
94
|
-
* @throws If the type of the file or directory at the specified path does not match the type of this instance, throws a {@link RdErr}. Useful for subclasses.
|
|
95
|
-
*/
|
|
96
|
-
constructor(lookFor, typeCheck) {
|
|
97
|
-
this.pointsTo = ph.resolve(lookFor);
|
|
98
|
-
if (typeCheck && !(this instanceof this.typeSync())) // `this` directly refers to the subclass
|
|
99
|
-
throw new RdErr(`Type missmatch: Path '${this.isAt}' is not of constructed type ${this.constructor.name}.`);
|
|
100
|
-
}
|
|
101
|
-
/**
|
|
102
|
-
* Verifies that the file or directory represented by this Road instance exists, is of the same type as this instance, and (optionally) is writable.
|
|
84
|
+
* @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}.
|
|
85
|
+
* @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.
|
|
103
86
|
*
|
|
104
|
-
* @
|
|
105
|
-
* @returns Result of the verification.
|
|
87
|
+
* @throws If {@link typeCheck_} is true and the path does not correspond to the expected type of road, a {@link RdErr} will be thrown.
|
|
106
88
|
*/
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
112
|
-
catch {
|
|
113
|
-
return false;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
/** Sync version of {@link verify}. */
|
|
117
|
-
verifySync(expectMode, typeCheck) {
|
|
118
|
-
try {
|
|
119
|
-
fs.accessSync(this.isAt, fsc.F_OK | expectMode);
|
|
120
|
-
return typeCheck || this instanceof this.typeSync();
|
|
121
|
-
}
|
|
122
|
-
catch {
|
|
123
|
-
return false;
|
|
124
|
-
}
|
|
89
|
+
constructor(path_, typeCheck_) {
|
|
90
|
+
this.pointsTo = ph.resolve(path_);
|
|
91
|
+
if (typeCheck_ && !this.checkSync())
|
|
92
|
+
throw new RdErr(`Type mismatch: '${this.isAt}'`);
|
|
125
93
|
}
|
|
126
94
|
/**
|
|
127
|
-
*
|
|
95
|
+
* 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.
|
|
96
|
+
*
|
|
97
|
+
* @param cb_ A callback function that will be called when the lock is released. This is used internally to manage the lock state.
|
|
98
|
+
* @returns An object with a dispose method that releases the lock when called.
|
|
99
|
+
* @throws If the road is immutable, a {@link RdErr} will be thrown.
|
|
128
100
|
*
|
|
129
|
-
* @
|
|
130
|
-
*
|
|
101
|
+
* @remarks This method MUST be used with a `using` statement to ensure that the lock is released properly. Failing to do so may result in deadlocks or other concurrency issues.
|
|
102
|
+
* 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 (to which the `using` statement is a convenient way to do so).
|
|
131
103
|
*/
|
|
132
|
-
async
|
|
133
|
-
if (!await this.verify(fsc.R_OK | fsc.W_OK, true))
|
|
134
|
-
throw new RdErr(`Road to '${this.isAt}' (${this.constructor.name}) isn't the same as during construction, can't modify (OS type: ${fs.existsSync(this.isAt) ? this.typeSync().name : 'nonexistent'})`);
|
|
104
|
+
async lock(cb_ = () => { }) {
|
|
135
105
|
if (!this.mutable)
|
|
136
|
-
throw new RdErr(`
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
await lockFor(lockedPath);
|
|
142
|
-
lockedRoads.set(lockedPath, new Promise(res => releaseLock = res));
|
|
106
|
+
throw new RdErr(`Road to '${this.isAt}' is immutable.`);
|
|
107
|
+
lockedRoads ??= new Map();
|
|
108
|
+
const isAt = this.isAt;
|
|
109
|
+
await lockedRoads.get(isAt); // will skip if `undefined` (no lock)
|
|
110
|
+
lockedRoads.set(isAt, new Promise(res => cb_ = res));
|
|
143
111
|
return {
|
|
144
112
|
[Symbol.dispose]() {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
lockedRoads.delete(lockedPath);
|
|
113
|
+
cb_();
|
|
114
|
+
lockedRoads.delete(isAt);
|
|
148
115
|
},
|
|
149
116
|
async [Symbol.asyncDispose]() {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
lockedRoads.delete(lockedPath);
|
|
117
|
+
cb_();
|
|
118
|
+
lockedRoads.delete(isAt);
|
|
153
119
|
}
|
|
154
120
|
};
|
|
155
121
|
}
|
|
156
122
|
/**
|
|
157
|
-
* Sync version of {@link
|
|
158
|
-
*
|
|
159
|
-
* @remarks This sync version will throw if the road is already locked by another operation.
|
|
123
|
+
* Sync version of {@link lock}.
|
|
124
|
+
* @throws Also throws a {@link RdErr} if the road is currently locked by another operation as it cannot wait for the lock to be released in a synchronous context.
|
|
160
125
|
*/
|
|
161
|
-
|
|
162
|
-
if (!this.verifySync(fsc.R_OK | fsc.W_OK, true))
|
|
163
|
-
throw new RdErr(`Road to '${this.isAt}' (${this.constructor.name}) isn't the same as during construction, can't modify (OS type: ${fs.existsSync(this.isAt) ? this.typeSync().name : 'nonexistent'})`);
|
|
126
|
+
lockSync(cb_ = () => { }) {
|
|
164
127
|
if (!this.mutable)
|
|
165
|
-
throw new RdErr(`
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
let releaseLock = () => { };
|
|
172
|
-
lockedRoads.set(lockedPath, new Promise(res => releaseLock = res));
|
|
128
|
+
throw new RdErr(`Road to '${this.isAt}' is immutable.`);
|
|
129
|
+
lockedRoads ??= new Map();
|
|
130
|
+
const isAt = this.isAt;
|
|
131
|
+
if (lockedRoads.has(isAt))
|
|
132
|
+
throw new RdErr(`Road to '${this.isAt}' is currently locked by another operation.`);
|
|
133
|
+
lockedRoads.set(isAt, new Promise(res => cb_ = res));
|
|
173
134
|
return {
|
|
174
135
|
[Symbol.dispose]() {
|
|
175
|
-
lockedRoads.delete(
|
|
176
|
-
|
|
177
|
-
releaseLock = () => { throw new RdErr("Lock already released, can't dispose"); };
|
|
136
|
+
lockedRoads.delete(isAt);
|
|
137
|
+
cb_();
|
|
178
138
|
},
|
|
179
139
|
async [Symbol.asyncDispose]() {
|
|
180
|
-
lockedRoads.delete(
|
|
181
|
-
|
|
182
|
-
releaseLock = () => { throw new RdErr("Lock already released, can't dispose"); };
|
|
140
|
+
lockedRoads.delete(isAt);
|
|
141
|
+
cb_();
|
|
183
142
|
}
|
|
184
143
|
};
|
|
185
144
|
}
|
|
145
|
+
/** @returns An instance of {@link Folder} representing the parent directory of the current road. */
|
|
146
|
+
parent() { return new Folder(ph.dirname(this.isAt), false); }
|
|
186
147
|
/**
|
|
187
|
-
*
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
async exists() { return this.verify(fsc.F_OK, true); }
|
|
191
|
-
/**
|
|
192
|
-
* @returns The file system stats for the file or directory.
|
|
193
|
-
* @see {@link fs.lstatSync}
|
|
194
|
-
*/
|
|
195
|
-
statsSync() { return fs.lstatSync(this.isAt); }
|
|
196
|
-
/**
|
|
197
|
-
* @returns The file system stats for the file or directory.
|
|
198
|
-
* @see {@link fs.promises.lstat}
|
|
199
|
-
*/
|
|
200
|
-
async stats() {
|
|
201
|
-
return fp.lstat(this.isAt);
|
|
202
|
-
}
|
|
203
|
-
/**
|
|
204
|
-
* @returns The amount of path segments in the absolute path to the file or directory represented by this Road instance, minus one (i.e., the depth of the file or directory in the file system hierarchy).
|
|
205
|
-
* @remarks As subclasses of {@link Road} require all paths to be absolute/normalized and valid, this method is guaranteed to return a non-negative integer.
|
|
148
|
+
* 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.
|
|
149
|
+
*
|
|
150
|
+
* @yields Each ancestor folder as a {@link Folder} instance.
|
|
206
151
|
*/
|
|
207
|
-
depth() { return this.isAt.split(ph.sep).length - 1; }
|
|
208
|
-
/** @returns The parent folder of the file or directory represented by this Road instance. */
|
|
209
|
-
parent() { return new Folder(ph.dirname(this.isAt), false); }
|
|
210
152
|
*ancestorsIt() {
|
|
211
153
|
let current = this.parent();
|
|
212
154
|
let parent = current.parent();
|
|
@@ -216,28 +158,50 @@ export class Road {
|
|
|
216
158
|
parent = current.parent();
|
|
217
159
|
}
|
|
218
160
|
}
|
|
161
|
+
/** @returns An array of {@link Folder} instances representing the ancestors of the current road. */
|
|
219
162
|
ancestors() { return [...this.ancestorsIt()]; }
|
|
220
|
-
|
|
163
|
+
/**
|
|
164
|
+
* Watches the current entry for changes and resolves when the entry becomes accessible (i.e., exists and can be accessed).
|
|
165
|
+
*
|
|
166
|
+
* @param abs An {@link AbortSignal} that stops the watching process when aborted.
|
|
167
|
+
* @param expectMode The expected access mode for the entry, defaults to {@link fsc.F_OK} (existence check).
|
|
168
|
+
* @param cb_ An optional callback function that will be called with any errors encountered while checking for accessibility.
|
|
169
|
+
* @see {@link fp.access} on how the check for accessibility is performed.
|
|
170
|
+
* @see {@link fs.watch} for more information on how the watching process works.
|
|
171
|
+
* @see {@link on} for more information on how the event listener is set up.
|
|
172
|
+
*/
|
|
173
|
+
async untilAccessible(abs, expectMode = fsc.F_OK, cb_) {
|
|
221
174
|
const watcher = fs.watch(this.isAt);
|
|
222
175
|
try {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
for await (let _ of on(watcher, 'change', { signal: abs }))
|
|
227
|
-
if (await this.verify(mode, true))
|
|
176
|
+
for await (let _ of on(watcher, 'change', { signal: abs })) {
|
|
177
|
+
try {
|
|
178
|
+
await fp.access(this.isAt, expectMode);
|
|
228
179
|
return;
|
|
229
|
-
|
|
230
|
-
|
|
180
|
+
}
|
|
181
|
+
catch (err) {
|
|
182
|
+
await cb_?.(err);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
catch (e) {
|
|
187
|
+
throw e;
|
|
231
188
|
}
|
|
232
189
|
finally {
|
|
233
190
|
watcher.close();
|
|
234
191
|
}
|
|
235
192
|
}
|
|
236
|
-
|
|
193
|
+
/**
|
|
194
|
+
* Watches the current entry for changes and resolves when the entry is changed.
|
|
195
|
+
*
|
|
196
|
+
* @param abs_ An {@link AbortSignal} that stops the watching process when aborted.
|
|
197
|
+
* @param cb_ An optional callback function that will be called when the entry is changed.
|
|
198
|
+
* @returns The return value of the callback function, or null if no callback is provided.
|
|
199
|
+
*/
|
|
200
|
+
async onChange(abs_, cb_) {
|
|
237
201
|
const watcher = fs.watch(this.isAt);
|
|
238
202
|
try {
|
|
239
|
-
for await (let _ of on(watcher, 'change', { signal:
|
|
240
|
-
return await
|
|
203
|
+
for await (let _ of on(watcher, 'change', { signal: abs_ }))
|
|
204
|
+
return await cb_?.() ?? null;
|
|
241
205
|
return null;
|
|
242
206
|
}
|
|
243
207
|
catch (e) {
|
|
@@ -247,285 +211,295 @@ export class Road {
|
|
|
247
211
|
watcher.close();
|
|
248
212
|
}
|
|
249
213
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
}
|
|
214
|
+
/** @returns The result of {@link fp.lstat} for the current entry. */
|
|
215
|
+
lstat() { return fp.lstat(this.isAt); }
|
|
216
|
+
/** @returns The result of {@link fs.lstatSync} for the current entry. */
|
|
217
|
+
lstatSync() { return fs.lstatSync(this.isAt); }
|
|
218
|
+
/** @returns The result of {@link fp.stat} for the current entry. */
|
|
219
|
+
stat() { return fp.stat(this.isAt); }
|
|
220
|
+
/** @returns The result of {@link fs.statSync} for the current entry. */
|
|
221
|
+
statSync() { return fs.statSync(this.isAt); }
|
|
222
|
+
/** Type narrowing for {@link File} (similar to `instanceof` without unnecessary runtime checks). */
|
|
223
|
+
isFile() { return false; }
|
|
224
|
+
/** Type narrowing for {@link Folder} (similar to `instanceof` without unnecessary runtime checks). */
|
|
225
|
+
isDir() { return false; }
|
|
226
|
+
/** Type narrowing for {@link Folder} (similar to `instanceof` without unnecessary runtime checks). */
|
|
227
|
+
isFolder() { return false; }
|
|
228
|
+
/** Type narrowing for {@link Folder} (similar to `instanceof` without unnecessary runtime checks). */
|
|
229
|
+
isDirectory() { return false; }
|
|
230
|
+
/** Type narrowing for {@link Folder} (similar to `instanceof` without unnecessary runtime checks). */
|
|
231
|
+
isDict() { return false; }
|
|
232
|
+
/** Type narrowing for {@link Folder} (similar to `instanceof` without unnecessary runtime checks). */
|
|
233
|
+
isDictionary() { return false; }
|
|
234
|
+
/** Type narrowing for {@link SymbolicLink} (similar to `instanceof` without unnecessary runtime checks). */
|
|
235
|
+
isSymlink() { return false; }
|
|
236
|
+
/** Type narrowing for {@link SymbolicLink} (similar to `instanceof` without unnecessary runtime checks). */
|
|
237
|
+
isSymbolicLink() { return false; }
|
|
238
|
+
/** Type narrowing for {@link UnusableRoad} (similar to `instanceof` without unnecessary runtime checks). */
|
|
239
|
+
isUnusable() { return false; }
|
|
240
|
+
/** Type narrowing for {@link BlockDevice} (similar to `instanceof` without unnecessary runtime checks). */
|
|
241
|
+
isBlockDevice() { return false; }
|
|
242
|
+
/** Type narrowing for {@link CharacterDevice} (similar to `instanceof` without unnecessary runtime checks). */
|
|
243
|
+
isCharacterDevice() { return false; }
|
|
244
|
+
/** Type narrowing for {@link Fifo} (similar to `instanceof` without unnecessary runtime checks). */
|
|
245
|
+
isFifo() { return false; }
|
|
246
|
+
/** Type narrowing for {@link Socket} (similar to `instanceof` without unnecessary runtime checks). */
|
|
247
|
+
isSocket() { return false; }
|
|
276
248
|
}
|
|
249
|
+
/** Subclass of {@link Road} that represents a file. */
|
|
277
250
|
export class File extends Road {
|
|
278
|
-
|
|
279
|
-
get noExt() { return ph.basename(this.isAt, this.ext); }
|
|
280
|
-
static createSync(at) {
|
|
251
|
+
static async create(at_) {
|
|
281
252
|
try {
|
|
282
|
-
|
|
253
|
+
await fp.access(at_, fsc.W_OK);
|
|
283
254
|
}
|
|
284
255
|
catch {
|
|
285
|
-
|
|
256
|
+
await fp.writeFile(at_, "");
|
|
286
257
|
}
|
|
287
|
-
return new File(
|
|
258
|
+
return new File(at_, true);
|
|
288
259
|
}
|
|
289
|
-
static
|
|
260
|
+
static createSync(at_) {
|
|
290
261
|
try {
|
|
291
|
-
|
|
262
|
+
fs.accessSync(at_, fs.constants.W_OK);
|
|
292
263
|
}
|
|
293
264
|
catch {
|
|
294
|
-
|
|
265
|
+
fs.writeFileSync(at_, "");
|
|
295
266
|
}
|
|
296
|
-
return new File(
|
|
267
|
+
return new File(at_, true);
|
|
297
268
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
269
|
+
get ext() { return ph.extname(this.isAt); }
|
|
270
|
+
get noExt() { return ph.basename(this.isAt, this.ext); }
|
|
271
|
+
async read(encoding_, flag_) {
|
|
272
|
+
if (encoding_)
|
|
273
|
+
return fp.readFile(this.isAt, { encoding: encoding_, flag: flag_ });
|
|
301
274
|
else
|
|
302
|
-
return
|
|
275
|
+
return fp.readFile(this.isAt);
|
|
303
276
|
}
|
|
304
|
-
|
|
305
|
-
if (
|
|
306
|
-
return
|
|
277
|
+
readSync(encoding_, flag_) {
|
|
278
|
+
if (encoding_)
|
|
279
|
+
return fs.readFileSync(this.isAt, { encoding: encoding_, flag: flag_ });
|
|
307
280
|
else
|
|
308
|
-
return
|
|
281
|
+
return fs.readFileSync(this.isAt);
|
|
309
282
|
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
const fd = fs.openSync(this.isAt, flags, mode);
|
|
283
|
+
async *itBuff(chunkSize_ = 64 * 1024, flags_ = 'r', mode_) {
|
|
284
|
+
const fd = await fp.open(this.isAt, flags_, mode_);
|
|
313
285
|
try {
|
|
314
|
-
const buffer = Buffer.alloc(
|
|
286
|
+
const buffer = Buffer.alloc(chunkSize_);
|
|
315
287
|
let bytesRead;
|
|
316
288
|
do {
|
|
317
|
-
|
|
289
|
+
const readResult = await fd.read(buffer, 0, chunkSize_, null);
|
|
290
|
+
bytesRead = readResult.bytesRead;
|
|
318
291
|
if (bytesRead > 0)
|
|
319
292
|
yield buffer.subarray(0, bytesRead);
|
|
320
|
-
} while (bytesRead ===
|
|
293
|
+
} while (bytesRead === chunkSize_);
|
|
321
294
|
}
|
|
322
295
|
finally {
|
|
323
|
-
|
|
296
|
+
await fd.close();
|
|
324
297
|
}
|
|
325
298
|
}
|
|
326
|
-
|
|
327
|
-
const fd =
|
|
299
|
+
*itBuffSync(chunkSize_ = 64 * 1024, flags_ = 'r', mode_) {
|
|
300
|
+
const fd = fs.openSync(this.isAt, flags_, mode_);
|
|
328
301
|
try {
|
|
329
|
-
const buffer = Buffer.alloc(
|
|
302
|
+
const buffer = Buffer.alloc(chunkSize_);
|
|
330
303
|
let bytesRead;
|
|
331
304
|
do {
|
|
332
|
-
|
|
333
|
-
bytesRead = readResult.bytesRead;
|
|
305
|
+
bytesRead = fs.readSync(fd, buffer, 0, chunkSize_, null);
|
|
334
306
|
if (bytesRead > 0)
|
|
335
307
|
yield buffer.subarray(0, bytesRead);
|
|
336
|
-
} while (bytesRead ===
|
|
337
|
-
}
|
|
338
|
-
finally {
|
|
339
|
-
await fd.close();
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
async *itLines(options = { encoding: 'utf-8' }) {
|
|
343
|
-
const readStream = fs.createReadStream(this.isAt, options);
|
|
344
|
-
const rlInterface = rl.createInterface({ input: readStream, crlfDelay: Infinity });
|
|
345
|
-
try {
|
|
346
|
-
for await (const line of rlInterface)
|
|
347
|
-
yield line;
|
|
308
|
+
} while (bytesRead === chunkSize_);
|
|
348
309
|
}
|
|
349
310
|
finally {
|
|
350
|
-
|
|
351
|
-
readStream.destroy();
|
|
311
|
+
fs.closeSync(fd);
|
|
352
312
|
}
|
|
353
313
|
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
hash.update(chunk);
|
|
358
|
-
return encoding ? hash.digest(encoding) : hash.digest();
|
|
359
|
-
}
|
|
360
|
-
async computeHash(algorithm = "sha256", options, encoding) {
|
|
361
|
-
const hash = cr.createHash(algorithm, options);
|
|
362
|
-
for await (const chunk of this.itBuff())
|
|
363
|
-
hash.update(chunk);
|
|
364
|
-
return encoding ? hash.digest(encoding) : hash.digest();
|
|
365
|
-
}
|
|
366
|
-
async streamHash(algorithm = "sha256", options, encoding) {
|
|
367
|
-
const hash = cr.createHash(algorithm, options);
|
|
368
|
-
await sp.pipeline(fs.createReadStream(this.isAt), hash);
|
|
369
|
-
return encoding ? hash.digest(encoding) : hash.digest();
|
|
370
|
-
}
|
|
371
|
-
writeSync(data, options) {
|
|
372
|
-
using _ = this.initChangeSync();
|
|
373
|
-
fs.writeFileSync(this.isAt, data, options);
|
|
314
|
+
async write(data_, options_) {
|
|
315
|
+
using _ = await this.lock();
|
|
316
|
+
await fp.writeFile(this.isAt, data_, options_);
|
|
374
317
|
}
|
|
375
|
-
|
|
376
|
-
using _ =
|
|
377
|
-
|
|
318
|
+
writeSync(data_, options_) {
|
|
319
|
+
using _ = this.lockSync();
|
|
320
|
+
fs.writeFileSync(this.isAt, data_, options_);
|
|
378
321
|
}
|
|
379
|
-
|
|
380
|
-
using _ = this.
|
|
381
|
-
|
|
322
|
+
async append(data_, options_) {
|
|
323
|
+
using _ = await this.lock();
|
|
324
|
+
await fp.appendFile(this.isAt, data_, options_);
|
|
382
325
|
}
|
|
383
|
-
|
|
384
|
-
using _ =
|
|
385
|
-
|
|
326
|
+
appendSync(data_, options_) {
|
|
327
|
+
using _ = this.lockSync();
|
|
328
|
+
fs.appendFileSync(this.isAt, data_, options_);
|
|
386
329
|
}
|
|
387
|
-
async
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
else if ((await fp.lstat(this.isAt)).size !== (await fp.lstat(other.isAt)).size)
|
|
391
|
-
return false;
|
|
392
|
-
const thisIter = this.itBuff();
|
|
393
|
-
const otherIter = other.itBuff();
|
|
394
|
-
while (true) {
|
|
395
|
-
const [a, b] = await Promise.all([thisIter.next(), otherIter.next()]);
|
|
396
|
-
if (a.done && b.done)
|
|
397
|
-
return true;
|
|
398
|
-
if (a.done !== b.done)
|
|
399
|
-
return false;
|
|
400
|
-
if (!a.value.equals(b.value))
|
|
401
|
-
return false;
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
sameAsSync(other) {
|
|
405
|
-
if (this.isAt === other.isAt)
|
|
406
|
-
return true;
|
|
407
|
-
else if (fs.statSync(this.isAt).size !== fs.statSync(other.isAt).size)
|
|
408
|
-
return false;
|
|
409
|
-
const thisIter = this.itBuffSync();
|
|
410
|
-
const otherIter = other.itBuffSync();
|
|
411
|
-
while (true) {
|
|
412
|
-
const a = thisIter.next();
|
|
413
|
-
const b = otherIter.next();
|
|
414
|
-
if (a.done && b.done)
|
|
415
|
-
return true;
|
|
416
|
-
if (a.done !== b.done)
|
|
417
|
-
return false;
|
|
418
|
-
if (!a.value.equals(b.value))
|
|
419
|
-
return false;
|
|
420
|
-
}
|
|
330
|
+
async delete() {
|
|
331
|
+
using _ = await this.lock();
|
|
332
|
+
await fp.rm(this.isAt, { force: true });
|
|
421
333
|
}
|
|
422
334
|
deleteSync() {
|
|
423
|
-
using _ = this.
|
|
335
|
+
using _ = this.lockSync();
|
|
424
336
|
fs.rmSync(this.isAt, { force: true });
|
|
425
337
|
}
|
|
426
|
-
async
|
|
427
|
-
using _ = await this.
|
|
428
|
-
|
|
338
|
+
async move(into_) {
|
|
339
|
+
using _ = await this.lock();
|
|
340
|
+
const newPath = into_.join(this.name);
|
|
341
|
+
await fp.rename(this.isAt, newPath);
|
|
342
|
+
this.pointsTo = newPath;
|
|
429
343
|
}
|
|
430
|
-
moveSync(
|
|
431
|
-
using _ = this.
|
|
432
|
-
const newPath =
|
|
344
|
+
moveSync(into_) {
|
|
345
|
+
using _ = this.lockSync();
|
|
346
|
+
const newPath = into_.join(this.name);
|
|
433
347
|
fs.renameSync(this.isAt, newPath);
|
|
434
348
|
this.pointsTo = newPath;
|
|
435
349
|
}
|
|
436
|
-
async
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
this.pointsTo = newPath;
|
|
350
|
+
async copy(into_) {
|
|
351
|
+
const newPath = into_.join(this.name);
|
|
352
|
+
await fp.copyFile(this.isAt, newPath);
|
|
353
|
+
return new File(newPath, false);
|
|
441
354
|
}
|
|
442
|
-
copySync(
|
|
443
|
-
const newPath =
|
|
355
|
+
copySync(into_) {
|
|
356
|
+
const newPath = into_.join(this.name);
|
|
444
357
|
fs.copyFileSync(this.isAt, newPath);
|
|
445
358
|
return new File(newPath, false);
|
|
446
359
|
}
|
|
447
|
-
async
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
360
|
+
async rename(to_) {
|
|
361
|
+
using _ = await this.lock();
|
|
362
|
+
const newPath = this.parent().join(to_);
|
|
363
|
+
await fp.rename(this.isAt, newPath);
|
|
364
|
+
this.pointsTo = newPath;
|
|
451
365
|
}
|
|
452
|
-
renameSync(
|
|
453
|
-
using _ = this.
|
|
454
|
-
const newPath = this.parent().join(
|
|
366
|
+
renameSync(to_) {
|
|
367
|
+
using _ = this.lockSync();
|
|
368
|
+
const newPath = this.parent().join(to_);
|
|
455
369
|
fs.renameSync(this.isAt, newPath);
|
|
456
370
|
this.pointsTo = newPath;
|
|
457
371
|
}
|
|
458
|
-
async
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
372
|
+
async check() { return (await fp.lstat(this.isAt)).isFile(); }
|
|
373
|
+
checkSync() { return fs.lstatSync(this.isAt).isFile(); }
|
|
374
|
+
isFile() { return true; }
|
|
375
|
+
}
|
|
376
|
+
export async function computeHash(f_, algorithm_ = "sha256", options_, encoding_) {
|
|
377
|
+
const hash = cr.createHash(algorithm_, options_);
|
|
378
|
+
for await (const chunk of f_.itBuff())
|
|
379
|
+
hash.update(chunk);
|
|
380
|
+
return encoding_ ? hash.digest(encoding_) : hash.digest();
|
|
381
|
+
}
|
|
382
|
+
export function computeHashSync(f_, algorithm_ = "sha256", options_, encoding_) {
|
|
383
|
+
const hash = cr.createHash(algorithm_, options_);
|
|
384
|
+
for (const chunk of f_.itBuffSync())
|
|
385
|
+
hash.update(chunk);
|
|
386
|
+
return encoding_ ? hash.digest(encoding_) : hash.digest();
|
|
387
|
+
}
|
|
388
|
+
export async function streamHash(f_, algorithm_ = "sha256", options_, encoding_) {
|
|
389
|
+
const hash = cr.createHash(algorithm_, options_);
|
|
390
|
+
for await (const chunk of f_.itBuff())
|
|
391
|
+
hash.update(chunk);
|
|
392
|
+
return encoding_ ? hash.digest(encoding_) : hash.digest();
|
|
393
|
+
}
|
|
394
|
+
export function streamHashSync(f_, algorithm_ = "sha256", options_, encoding_) {
|
|
395
|
+
const hash = cr.createHash(algorithm_, options_);
|
|
396
|
+
for (const chunk of f_.itBuffSync())
|
|
397
|
+
hash.update(chunk);
|
|
398
|
+
return encoding_ ? hash.digest(encoding_) : hash.digest();
|
|
399
|
+
}
|
|
400
|
+
export async function* itLines(f_, options_ = { encoding: 'utf-8' }) {
|
|
401
|
+
const readStream = fs.createReadStream(f_.isAt, options_);
|
|
402
|
+
const rlInterface = rl.createInterface({ input: readStream, crlfDelay: Infinity });
|
|
403
|
+
try {
|
|
404
|
+
for await (const line of rlInterface)
|
|
405
|
+
yield line;
|
|
463
406
|
}
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
407
|
+
finally {
|
|
408
|
+
rlInterface.close();
|
|
409
|
+
readStream.destroy();
|
|
467
410
|
}
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
411
|
+
}
|
|
412
|
+
export async function fileSameAs(f1_, f2_) {
|
|
413
|
+
if (f1_.isAt === f2_.isAt)
|
|
414
|
+
return true;
|
|
415
|
+
else if ((await fp.lstat(f1_.isAt)).size !== (await fp.lstat(f2_.isAt)).size)
|
|
416
|
+
return false;
|
|
417
|
+
const iter1 = f1_.itBuff();
|
|
418
|
+
const iter2 = f2_.itBuff();
|
|
419
|
+
while (true) {
|
|
420
|
+
const [a, b] = await Promise.all([iter1.next(), iter2.next()]);
|
|
421
|
+
if (a.done && b.done)
|
|
422
|
+
return true;
|
|
423
|
+
if (a.done !== b.done)
|
|
424
|
+
return false;
|
|
425
|
+
if (!a.value.equals(b.value))
|
|
426
|
+
return false;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
export function fileSameAsSync(f1_, f2_) {
|
|
430
|
+
if (f1_.isAt === f2_.isAt)
|
|
431
|
+
return true;
|
|
432
|
+
else if (fs.statSync(f1_.isAt).size !== fs.statSync(f2_.isAt).size)
|
|
433
|
+
return false;
|
|
434
|
+
const iter1 = f1_.itBuffSync();
|
|
435
|
+
const iter2 = f2_.itBuffSync();
|
|
436
|
+
while (true) {
|
|
437
|
+
const a = iter1.next();
|
|
438
|
+
const b = iter2.next();
|
|
439
|
+
if (a.done && b.done)
|
|
440
|
+
return true;
|
|
441
|
+
if (a.done !== b.done)
|
|
442
|
+
return false;
|
|
443
|
+
if (!a.value.equals(b.value))
|
|
444
|
+
return false;
|
|
471
445
|
}
|
|
472
446
|
}
|
|
473
|
-
export function entry() { return new File(process.argv[1], false); }
|
|
474
447
|
export class Folder extends Road {
|
|
475
|
-
static async create(
|
|
448
|
+
static async create(at_) {
|
|
476
449
|
try {
|
|
477
|
-
await fp.access(
|
|
450
|
+
await fp.access(at_, fsc.W_OK);
|
|
478
451
|
}
|
|
479
452
|
catch {
|
|
480
|
-
await fp.mkdir(
|
|
453
|
+
await fp.mkdir(at_, { recursive: true });
|
|
481
454
|
}
|
|
482
|
-
return new Folder(
|
|
455
|
+
return new Folder(at_, false);
|
|
483
456
|
}
|
|
484
|
-
static createSync(
|
|
457
|
+
static createSync(at_) {
|
|
485
458
|
try {
|
|
486
|
-
fs.accessSync(
|
|
459
|
+
fs.accessSync(at_, fsc.W_OK);
|
|
487
460
|
}
|
|
488
461
|
catch {
|
|
489
|
-
fs.mkdirSync(
|
|
462
|
+
fs.mkdirSync(at_, { recursive: true });
|
|
490
463
|
}
|
|
491
|
-
return new Folder(
|
|
464
|
+
return new Folder(at_, false);
|
|
492
465
|
}
|
|
493
|
-
join(...
|
|
494
|
-
return ph.join(this.isAt, ...
|
|
466
|
+
join(...paths_) {
|
|
467
|
+
return ph.join(this.isAt, ...paths_);
|
|
495
468
|
}
|
|
496
|
-
*
|
|
497
|
-
for (const
|
|
498
|
-
const road =
|
|
499
|
-
if (!
|
|
469
|
+
async *it(expectedType_) {
|
|
470
|
+
for (const entryName of await fp.readdir(this.isAt)) {
|
|
471
|
+
const road = await factory(this.join(entryName));
|
|
472
|
+
if (!expectedType_ || road instanceof expectedType_)
|
|
500
473
|
yield road;
|
|
501
474
|
}
|
|
502
475
|
}
|
|
503
|
-
|
|
504
|
-
for (const entry of
|
|
505
|
-
const road =
|
|
506
|
-
if (!
|
|
476
|
+
*itSync(expectedType_) {
|
|
477
|
+
for (const entry of fs.readdirSync(this.isAt)) {
|
|
478
|
+
const road = factorySync(this.join(entry));
|
|
479
|
+
if (!expectedType_ || road instanceof expectedType_)
|
|
507
480
|
yield road;
|
|
508
481
|
}
|
|
509
482
|
}
|
|
510
|
-
|
|
511
|
-
const entries = fs.readdirSync(this.isAt).map(entry => factorySync(this.join(entry)));
|
|
512
|
-
if (!expectedType)
|
|
513
|
-
return entries;
|
|
514
|
-
return entries.filter(entry => entry instanceof expectedType);
|
|
515
|
-
}
|
|
516
|
-
async list(_expectedType) {
|
|
483
|
+
async list(expectedType_) {
|
|
517
484
|
const entries = (await fp.readdir(this.isAt)).map(async (entry) => factory(this.join(entry)));
|
|
518
485
|
const resolvedEntries = await Promise.all(entries);
|
|
519
|
-
if (!
|
|
486
|
+
if (!expectedType_)
|
|
520
487
|
return resolvedEntries;
|
|
521
|
-
return resolvedEntries.filter(entry => entry instanceof
|
|
488
|
+
return resolvedEntries.filter(entry => entry instanceof expectedType_);
|
|
522
489
|
}
|
|
523
|
-
|
|
490
|
+
listSync(expectedType_) {
|
|
491
|
+
const entries = fs.readdirSync(this.isAt).map(entry => factorySync(this.join(entry)));
|
|
492
|
+
if (!expectedType_)
|
|
493
|
+
return entries;
|
|
494
|
+
return entries.filter(entry => entry instanceof expectedType_);
|
|
495
|
+
}
|
|
496
|
+
async find(name_, expectedType_) {
|
|
524
497
|
try {
|
|
525
|
-
|
|
526
|
-
|
|
498
|
+
await fp.access(this.join(name_), fs.constants.F_OK);
|
|
499
|
+
const found = await factory(this.join(name_));
|
|
500
|
+
if (!expectedType_)
|
|
527
501
|
return found;
|
|
528
|
-
if (found instanceof
|
|
502
|
+
if (found instanceof expectedType_)
|
|
529
503
|
return found;
|
|
530
504
|
return null;
|
|
531
505
|
}
|
|
@@ -533,13 +507,12 @@ export class Folder extends Road {
|
|
|
533
507
|
return null;
|
|
534
508
|
}
|
|
535
509
|
}
|
|
536
|
-
|
|
510
|
+
findSync(name_, expectedType_) {
|
|
537
511
|
try {
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
if (!_expectedType)
|
|
512
|
+
const found = factorySync(this.join(name_));
|
|
513
|
+
if (!expectedType_)
|
|
541
514
|
return found;
|
|
542
|
-
if (found instanceof
|
|
515
|
+
if (found instanceof expectedType_)
|
|
543
516
|
return found;
|
|
544
517
|
return null;
|
|
545
518
|
}
|
|
@@ -547,66 +520,65 @@ export class Folder extends Road {
|
|
|
547
520
|
return null;
|
|
548
521
|
}
|
|
549
522
|
}
|
|
550
|
-
|
|
551
|
-
const newPath = this.join(
|
|
552
|
-
|
|
553
|
-
return
|
|
554
|
-
}
|
|
555
|
-
async add(name, createable) {
|
|
556
|
-
const newPath = this.join(name);
|
|
557
|
-
await createable.create(newPath);
|
|
558
|
-
return factory(newPath);
|
|
523
|
+
async add(name_, createable_) {
|
|
524
|
+
const newPath = this.join(name_);
|
|
525
|
+
await createable_.create(newPath);
|
|
526
|
+
return (await factory(newPath));
|
|
559
527
|
}
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
528
|
+
addSync(name_, createable_) {
|
|
529
|
+
const newPath = this.join(name_);
|
|
530
|
+
createable_.createSync(newPath);
|
|
531
|
+
return factorySync(newPath);
|
|
563
532
|
}
|
|
564
|
-
async delete(
|
|
565
|
-
using _ = await this.
|
|
566
|
-
await fp.rm(this.isAt,
|
|
533
|
+
async delete(options_ = { recursive: true }) {
|
|
534
|
+
using _ = await this.lock();
|
|
535
|
+
await fp.rm(this.isAt, options_);
|
|
567
536
|
}
|
|
568
|
-
|
|
569
|
-
using _ = this.
|
|
570
|
-
|
|
571
|
-
fs.renameSync(this.isAt, newPath);
|
|
572
|
-
this.pointsTo = newPath;
|
|
537
|
+
deleteSync(options_ = { recursive: true }) {
|
|
538
|
+
using _ = this.lockSync();
|
|
539
|
+
fs.rmSync(this.isAt, options_);
|
|
573
540
|
}
|
|
574
|
-
async move(
|
|
575
|
-
using _ = await this.
|
|
576
|
-
const newPath =
|
|
541
|
+
async move(into_) {
|
|
542
|
+
using _ = await this.lock();
|
|
543
|
+
const newPath = into_.join(this.name);
|
|
577
544
|
await fp.rename(this.isAt, newPath);
|
|
578
545
|
this.pointsTo = newPath;
|
|
579
546
|
}
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
547
|
+
moveSync(into_) {
|
|
548
|
+
using _ = this.lockSync();
|
|
549
|
+
const newPath = into_.join(this.name);
|
|
550
|
+
fs.renameSync(this.isAt, newPath);
|
|
551
|
+
this.pointsTo = newPath;
|
|
584
552
|
}
|
|
585
|
-
async copy(
|
|
586
|
-
const newPath =
|
|
553
|
+
async copy(into_) {
|
|
554
|
+
const newPath = into_.join(this.name);
|
|
587
555
|
await fp.cp(this.isAt, newPath, { recursive: true });
|
|
588
556
|
return new Folder(newPath, false);
|
|
589
557
|
}
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
this.pointsTo = newPath;
|
|
558
|
+
copySync(into_) {
|
|
559
|
+
const newPath = into_.join(this.name);
|
|
560
|
+
fs.cpSync(this.isAt, newPath, { recursive: true });
|
|
561
|
+
return new Folder(newPath, false);
|
|
595
562
|
}
|
|
596
|
-
async rename(
|
|
597
|
-
using _ = await this.
|
|
598
|
-
const newPath = this.parent().join(
|
|
563
|
+
async rename(to_) {
|
|
564
|
+
using _ = await this.lock();
|
|
565
|
+
const newPath = this.parent().join(to_);
|
|
599
566
|
await fp.rename(this.isAt, newPath);
|
|
600
567
|
this.pointsTo = newPath;
|
|
601
568
|
}
|
|
602
|
-
|
|
603
|
-
using _ = this.
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
using _ = await this.initChange();
|
|
608
|
-
await fp.mkdir(this.isAt, { recursive: true });
|
|
569
|
+
renameSync(to_) {
|
|
570
|
+
using _ = this.lockSync();
|
|
571
|
+
const newPath = this.parent().join(to_);
|
|
572
|
+
fs.renameSync(this.isAt, newPath);
|
|
573
|
+
this.pointsTo = newPath;
|
|
609
574
|
}
|
|
575
|
+
async check() { return (await fp.lstat(this.isAt)).isDirectory(); }
|
|
576
|
+
checkSync() { return fs.lstatSync(this.isAt).isDirectory(); }
|
|
577
|
+
isFolder() { return true; }
|
|
578
|
+
isDir() { return true; }
|
|
579
|
+
isDirectory() { return true; }
|
|
580
|
+
isDict() { return true; }
|
|
581
|
+
isDictionary() { return true; }
|
|
610
582
|
}
|
|
611
583
|
export function sysRoot() { return new Folder(ph.parse(process.cwd()).root, false); }
|
|
612
584
|
export function home() { return new Folder(os.homedir(), false); }
|
|
@@ -614,131 +586,134 @@ export function tmp() { return new Folder(os.tmpdir(), false); }
|
|
|
614
586
|
export function here() { return new Folder(process.cwd(), false); }
|
|
615
587
|
export { Folder as Dir, Folder as Directory, Folder as Dict, Folder as Dictionary };
|
|
616
588
|
export class SymbolicLink extends Road {
|
|
617
|
-
static async create(
|
|
589
|
+
static async create(at_, target_) {
|
|
618
590
|
try {
|
|
619
|
-
await fp.access(
|
|
591
|
+
await fp.access(at_, fs.constants.F_OK);
|
|
620
592
|
}
|
|
621
593
|
catch {
|
|
622
|
-
await fp.symlink(
|
|
594
|
+
await fp.symlink(target_.toString(), at_);
|
|
623
595
|
}
|
|
624
|
-
return new SymbolicLink(
|
|
596
|
+
return new SymbolicLink(at_, false);
|
|
625
597
|
}
|
|
626
|
-
static createSync(
|
|
598
|
+
static createSync(at_, target_) {
|
|
627
599
|
try {
|
|
628
|
-
fs.accessSync(
|
|
600
|
+
fs.accessSync(at_, fs.constants.F_OK);
|
|
629
601
|
}
|
|
630
602
|
catch {
|
|
631
|
-
fs.symlinkSync(
|
|
603
|
+
fs.symlinkSync(target_.toString(), at_);
|
|
632
604
|
}
|
|
633
|
-
return new SymbolicLink(
|
|
634
|
-
}
|
|
635
|
-
targetSync() {
|
|
636
|
-
return factorySync(ph.resolve(ph.dirname(this.isAt), fs.readlinkSync(this.isAt)));
|
|
605
|
+
return new SymbolicLink(at_, false);
|
|
637
606
|
}
|
|
638
607
|
async target() {
|
|
639
608
|
return factory(ph.resolve(ph.dirname(this.isAt), await fp.readlink(this.isAt)));
|
|
640
609
|
}
|
|
641
|
-
|
|
642
|
-
this.
|
|
643
|
-
fs.symlinkSync(_newTarget.isAt, this.isAt);
|
|
610
|
+
targetSync() {
|
|
611
|
+
return factorySync(ph.resolve(ph.dirname(this.isAt), fs.readlinkSync(this.isAt)));
|
|
644
612
|
}
|
|
645
|
-
async retarget(
|
|
613
|
+
async retarget(to_) {
|
|
646
614
|
await this.delete();
|
|
647
|
-
return fp.symlink(
|
|
615
|
+
return fp.symlink(to_.isAt, this.isAt);
|
|
648
616
|
}
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
fs.
|
|
617
|
+
retargetSync(to_) {
|
|
618
|
+
this.deleteSync();
|
|
619
|
+
fs.symlinkSync(to_.isAt, this.isAt);
|
|
652
620
|
}
|
|
653
621
|
async delete() {
|
|
654
|
-
using _ = await this.
|
|
622
|
+
using _ = await this.lock();
|
|
655
623
|
await fp.unlink(this.isAt);
|
|
656
624
|
}
|
|
657
|
-
|
|
658
|
-
using _ = this.
|
|
659
|
-
|
|
660
|
-
fs.renameSync(this.isAt, newPath);
|
|
661
|
-
this.pointsTo = newPath;
|
|
625
|
+
deleteSync() {
|
|
626
|
+
using _ = this.lockSync();
|
|
627
|
+
fs.unlinkSync(this.isAt);
|
|
662
628
|
}
|
|
663
|
-
async move(
|
|
664
|
-
using _ = await this.
|
|
665
|
-
const newPath =
|
|
629
|
+
async move(into_) {
|
|
630
|
+
using _ = await this.lock();
|
|
631
|
+
const newPath = into_.join(this.name);
|
|
666
632
|
await fp.rename(this.isAt, newPath);
|
|
667
633
|
this.pointsTo = newPath;
|
|
668
634
|
}
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
const
|
|
672
|
-
fs.
|
|
673
|
-
|
|
635
|
+
moveSync(into_) {
|
|
636
|
+
using _ = this.lockSync();
|
|
637
|
+
const newPath = into_.join(this.name);
|
|
638
|
+
fs.renameSync(this.isAt, newPath);
|
|
639
|
+
this.pointsTo = newPath;
|
|
674
640
|
}
|
|
675
|
-
async copy(
|
|
676
|
-
const newPath =
|
|
641
|
+
async copy(into_) {
|
|
642
|
+
const newPath = into_.join(this.name);
|
|
677
643
|
const target = await this.target();
|
|
678
644
|
await fp.symlink(target.isAt, newPath);
|
|
679
645
|
return new SymbolicLink(newPath, false);
|
|
680
646
|
}
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
const
|
|
684
|
-
fs.
|
|
685
|
-
|
|
647
|
+
copySync(into_) {
|
|
648
|
+
const newPath = into_.join(this.name);
|
|
649
|
+
const target = this.targetSync();
|
|
650
|
+
fs.symlinkSync(target.isAt, newPath);
|
|
651
|
+
return new SymbolicLink(newPath, false);
|
|
686
652
|
}
|
|
687
|
-
async rename(
|
|
688
|
-
using _ = await this.
|
|
689
|
-
const newPath = this.parent().join(
|
|
653
|
+
async rename(to_) {
|
|
654
|
+
using _ = await this.lock();
|
|
655
|
+
const newPath = this.parent().join(to_);
|
|
690
656
|
await fp.rename(this.isAt, newPath);
|
|
691
657
|
this.pointsTo = newPath;
|
|
692
658
|
}
|
|
693
|
-
|
|
694
|
-
using _ = this.
|
|
695
|
-
const
|
|
696
|
-
fs.
|
|
697
|
-
|
|
698
|
-
async resurrect() {
|
|
699
|
-
using _ = await this.initChange();
|
|
700
|
-
const target = await this.target();
|
|
701
|
-
await fp.symlink(target.isAt, this.isAt);
|
|
659
|
+
renameSync(to_) {
|
|
660
|
+
using _ = this.lockSync();
|
|
661
|
+
const newPath = this.parent().join(to_);
|
|
662
|
+
fs.renameSync(this.isAt, newPath);
|
|
663
|
+
this.pointsTo = newPath;
|
|
702
664
|
}
|
|
665
|
+
async check() { return (await fp.lstat(this.isAt)).isSymbolicLink(); }
|
|
666
|
+
checkSync() { return fs.lstatSync(this.isAt).isSymbolicLink(); }
|
|
667
|
+
isSymlink() { return true; }
|
|
668
|
+
isSymbolicLink() { return true; }
|
|
703
669
|
}
|
|
704
670
|
export { SymbolicLink as Symlink };
|
|
705
671
|
export class UnusableRoad extends Road {
|
|
706
|
-
mutable = false; // Modification
|
|
707
|
-
constructor(
|
|
708
|
-
super(
|
|
672
|
+
mutable = false; // Modification will cause system issues (e.g. deleting a device file)
|
|
673
|
+
constructor(...args_) {
|
|
674
|
+
super(...args_);
|
|
709
675
|
Object.freeze(this);
|
|
710
676
|
}
|
|
711
|
-
error() { throw new RdErr(`${this.constructor.name} at '${this.isAt}' is a system-level resource thus
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
deleteSync() { return this.error(); }
|
|
677
|
+
error() { throw new RdErr(`${this.constructor.name} at '${this.isAt}' is a system-level resource thus not subject to modification.`); }
|
|
678
|
+
async lock() { return this.error(); }
|
|
679
|
+
lockSync() { return this.error(); }
|
|
715
680
|
async delete() { return this.error(); }
|
|
716
|
-
|
|
681
|
+
deleteSync() { return this.error(); }
|
|
717
682
|
async move() { return this.error(); }
|
|
718
|
-
|
|
683
|
+
moveSync() { return this.error(); }
|
|
719
684
|
async copy() { return this.error(); }
|
|
720
|
-
|
|
685
|
+
copySync() { return this.error(); }
|
|
721
686
|
async rename() { return this.error(); }
|
|
722
|
-
|
|
723
|
-
|
|
687
|
+
renameSync() { return this.error(); }
|
|
688
|
+
isUnusable() { return true; }
|
|
724
689
|
}
|
|
725
690
|
export class BlockDevice extends UnusableRoad {
|
|
691
|
+
async check() { return (await fp.lstat(this.isAt)).isBlockDevice(); }
|
|
692
|
+
checkSync() { return fs.lstatSync(this.isAt).isBlockDevice(); }
|
|
693
|
+
isBlockDevice() { return true; }
|
|
726
694
|
}
|
|
727
695
|
export class CharacterDevice extends UnusableRoad {
|
|
696
|
+
async check() { return (await fp.lstat(this.isAt)).isCharacterDevice(); }
|
|
697
|
+
checkSync() { return fs.lstatSync(this.isAt).isCharacterDevice(); }
|
|
698
|
+
isCharacterDevice() { return true; }
|
|
728
699
|
}
|
|
729
700
|
export class Fifo extends UnusableRoad {
|
|
701
|
+
async check() { return (await fp.lstat(this.isAt)).isFIFO(); }
|
|
702
|
+
checkSync() { return fs.lstatSync(this.isAt).isFIFO(); }
|
|
703
|
+
isFifo() { return true; }
|
|
730
704
|
}
|
|
731
705
|
export class Socket extends UnusableRoad {
|
|
706
|
+
async check() { return (await fp.lstat(this.isAt)).isSocket(); }
|
|
707
|
+
checkSync() { return fs.lstatSync(this.isAt).isSocket(); }
|
|
708
|
+
isSocket() { return true; }
|
|
732
709
|
}
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
let exitHandlerRegistered =
|
|
710
|
+
let finalizer = null;
|
|
711
|
+
let toDelete = null;
|
|
712
|
+
let exitHandlerRegistered = null;
|
|
736
713
|
/**
|
|
737
714
|
* Forcefully cleans up all files and folders registered for cleanup on exit.
|
|
738
|
-
*
|
|
739
|
-
* @remarks This function is not recommended to be called manually, as it will delete all files and folders registered for cleanup on exit, which may lead to data loss if called at the wrong time. This function is intended to be called automatically when the process exits.
|
|
740
715
|
*/
|
|
741
|
-
|
|
716
|
+
function forceCleanupToDelete() {
|
|
742
717
|
for (const path of toDelete ?? [])
|
|
743
718
|
try {
|
|
744
719
|
fs.rmSync(path, { force: true, recursive: true });
|
|
@@ -751,24 +726,22 @@ export function forceCleanupToDelete() {
|
|
|
751
726
|
process.off('exit', forceCleanupToDelete);
|
|
752
727
|
exitHandlerRegistered = false;
|
|
753
728
|
}
|
|
754
|
-
export function registerToCleanup(
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
if (!toDelete)
|
|
761
|
-
toDelete = new Set();
|
|
729
|
+
export function registerToCleanup(self_) {
|
|
730
|
+
finalizer ??= new FinalizationRegistry(p => { try {
|
|
731
|
+
fs.rmSync(p, { force: true, recursive: true });
|
|
732
|
+
}
|
|
733
|
+
catch { } ; toDelete?.delete(p); });
|
|
734
|
+
toDelete ??= new Set();
|
|
762
735
|
if (!exitHandlerRegistered) {
|
|
763
736
|
process.once('exit', forceCleanupToDelete);
|
|
764
737
|
exitHandlerRegistered = true;
|
|
765
738
|
}
|
|
766
|
-
toDelete.add(
|
|
767
|
-
finalizer.register(
|
|
739
|
+
toDelete.add(self_.isAt);
|
|
740
|
+
finalizer.register(self_, self_.isAt, self_);
|
|
768
741
|
}
|
|
769
|
-
export function Temp(
|
|
770
|
-
|
|
771
|
-
if (
|
|
742
|
+
export function Temp(createable_, autoCleanup_) {
|
|
743
|
+
let t = createable_.createSync(tmp().join(`instrumentality@${cr.randomUUID()}`));
|
|
744
|
+
if (autoCleanup_)
|
|
772
745
|
registerToCleanup(t);
|
|
773
746
|
return Object.freeze(Object.assign(t, {
|
|
774
747
|
[Symbol.dispose]() { try {
|
|
@@ -781,3 +754,4 @@ export function Temp(createable, autoCleanup) {
|
|
|
781
754
|
catch { } toDelete?.delete(t.isAt); finalizer?.unregister(t); }
|
|
782
755
|
}));
|
|
783
756
|
}
|
|
757
|
+
11;
|