@nejs/basic-extensions 2.3.0 → 2.4.0

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/package.json CHANGED
@@ -58,9 +58,9 @@
58
58
  "test": "jest"
59
59
  },
60
60
  "type": "module",
61
- "version": "2.3.0",
61
+ "version": "2.4.0",
62
62
  "dependencies": {
63
- "@nejs/extension": "^2.7.1"
63
+ "@nejs/extension": "^2.7.2"
64
64
  },
65
- "browser": "dist/@nejs/basic-extensions.bundle.2.2.1.js"
65
+ "browser": "dist/@nejs/basic-extensions.bundle.2.3.0.js"
66
66
  }
@@ -0,0 +1,248 @@
1
+ #!/usr/bin/env node --no-warnings --no-deprecations
2
+
3
+ // Import everything for playtesting.
4
+ (await import('./dist/mjs/index.js')).Controls.enableAll();
5
+
6
+ const nejsExtension = await import('@nejs/extension');
7
+
8
+ const repl = await import('node:repl');
9
+ const fs = await import('node:fs');
10
+ const project = JSON.parse(String(fs.readFileSync('./package.json')));
11
+
12
+ const options = {
13
+ useGlobal: true,
14
+ prompt: '\x1b[32mλ\x1b[1;39m\x1b[22m '
15
+ };
16
+
17
+ let allowInvocation = true;
18
+
19
+ Object.assign(global, { Patch: nejsExtension.Patch, Extension: nejsExtension.Extension });
20
+ global.replServer = new repl.REPLServer(options);
21
+
22
+ function about() {
23
+ console.log(`\x1b[32m${project.name}\x1b[39m v\x1b[1m${project.version}\x1b[22m`);
24
+ console.log(`\x1b[3m${project.description}\x1b[23m`);
25
+ console.log(`Written by \x1b[34m${project.author ?? 'Jane Doe'}\x1b[39m.\n`);
26
+ this?.displayPrompt() ?? replServer.displayPrompt();
27
+ }
28
+
29
+ const clear = () => {
30
+ if (allowInvocation) {
31
+ process.stdout.write('\x1b[0;0H\x1b[J');
32
+ return about();
33
+ }
34
+ }
35
+
36
+ clear();
37
+
38
+ replServer.defineCommand('cls', {
39
+ action: clear,
40
+ help: 'Clears the screen.'
41
+ });
42
+ replServer.defineCommand('clear', {
43
+ action: clear,
44
+ help: 'Clears the screen.'
45
+ });
46
+ replServer.defineCommand('about', {
47
+ action: about,
48
+ help: 'Shows info about this project.'
49
+ });
50
+ replServer.defineCommand('state', {
51
+ help: 'Shows stats about this REPL\'s state.',
52
+ action() {
53
+ const state = generateState();
54
+ const i = (s) => `\x1b[3m${s}\x1b[23m`;
55
+ const j = ', ';
56
+
57
+ state.classes = [...Object.keys(state.classes)].map(k => String(k));
58
+ state.functions = [...Object.keys(state.functions)].map(k => String(k));
59
+ state.properties = [...Object.keys(state.properties)].map(k => String(k));
60
+ state.descriptors.accessors = [...Object.keys(state.descriptors.accessors)].map(k => String(k));
61
+ state.descriptors.data = [...Object.keys(state.descriptors.data)].map(k => String(k));
62
+
63
+ console.log(`\x1b[1mClasses\x1b[22m\n${wrapContent(state.classes, i, j)}`);
64
+ console.log(`\x1b[1mFunctions\x1b[22m\n${wrapContent(state.functions, i, j)}`);
65
+ console.log(`\x1b[1mProperties\x1b[22m\n${wrapContent(state.properties, i, j)}`);
66
+ console.log(`\x1b[1mAccessor Descriptors\x1b[22m\n${wrapContent(state.descriptors.accessors, i, j)}`);
67
+ console.log(`\x1b[1mData Descriptors\x1b[22m\n${wrapContent(state.descriptors.data, i, j)}`);
68
+
69
+ replServer.displayPrompt();
70
+ }
71
+ });
72
+
73
+ overridableGlobal('clear', clear);
74
+ overridableGlobal('cls', clear);
75
+ overridableGlobal('state', generateState);
76
+
77
+ Object.defineProperty(replServer, '_initialPrompt', {
78
+ get() {
79
+ const isRed = !globalThis?._;
80
+ const prompt = isRed
81
+ ? '\x1b[31mλ\x1b[1;39m\x1b[22m '
82
+ : '\x1b[32mλ\x1b[1;39m\x1b[22m ';
83
+
84
+ return prompt;
85
+ }
86
+ });
87
+
88
+ function overridableGlobal(
89
+ property,
90
+ action,
91
+ changeText = 'Expression assignment to "@X", previous function now disabled.'
92
+ ) {
93
+ const message = changeText.replaceAll(/\@X/g, property);
94
+ let changed = false;
95
+ let storage = undefined;
96
+
97
+ const makeDescriptor = () => ({
98
+ get() {
99
+ if (changed === false) {
100
+ return action();
101
+ }
102
+ return storage;
103
+ },
104
+ set(value) {
105
+ if (changed === false) {
106
+ console.log(message);
107
+ changed = true;
108
+ }
109
+ storage = value;
110
+ },
111
+ configurable: true,
112
+ get enumerable() { return changed }
113
+ });
114
+
115
+ replServer.defineCommand(
116
+ `restore${property.charAt(0).toUpperCase()}${property.substring(1,property.length)}`,
117
+ {
118
+ action() {
119
+ changed = false;
120
+ storage = undefined;
121
+ Object.defineProperty(globalThis, property, makeDescriptor());
122
+ console.log(this.help);
123
+ },
124
+ help: `Restores ${property} to default REPL custom state.`,
125
+ }
126
+ );
127
+
128
+ Object.defineProperty(globalThis, property, makeDescriptor());
129
+ }
130
+
131
+ function generateState() {
132
+ const replState = {
133
+ classes: {},
134
+ functions: {},
135
+ properties: {},
136
+ descriptors: {
137
+ accessors: {},
138
+ data: {},
139
+ },
140
+ };
141
+
142
+ if (!allowInvocation) {
143
+ return replState;
144
+ }
145
+
146
+ let skipped = [];
147
+
148
+ allowInvocation = false;
149
+ Reflect.ownKeys(globalThis).forEach(key => {
150
+ try {
151
+ const value = globalThis[key];
152
+ const descriptor = Object.getOwnPropertyDescriptor(globalThis, key);
153
+
154
+ if (String(value).startsWith('class')) {
155
+ replState.classes[key] = {key, value, descriptor};
156
+ }
157
+ else if (typeof value === 'function') {
158
+ replState.functions[key] = {key, value, descriptor};
159
+ }
160
+ else {
161
+ replState.properties[key] = {key, value, descriptor};
162
+ }
163
+
164
+ if (Reflect.has(descriptor, 'get') || Reflect.has(descriptor, 'set')) {
165
+ replState.descriptors.accessors[key] = { key, descriptor };
166
+ }
167
+ else if (Reflect.has(descriptor, 'value')) {
168
+ replState.descriptors.data[key] = { key, descriptor };
169
+ }
170
+ }
171
+ catch (ignored) {
172
+ skipped.push(String(key));
173
+ }
174
+ });
175
+ allowInvocation = true;
176
+
177
+ return replState;
178
+ }
179
+
180
+ /**
181
+ * Formats a string or array of values into lines with specified indentation and line width.
182
+ * @param {string|array} input - The input string or array of strings to be formatted.
183
+ * @param {number} nCols - The maximum number of columns per line (default 80).
184
+ * @param {number} nSpaceIndents - The number of spaces for indentation (default 2).
185
+ * @returns {string} The formatted string.
186
+ */
187
+ function formatValues(input, transform, nCols = 80, nSpaceIndents = 2) {
188
+ // Split the string into an array if input is a string
189
+ const values = typeof input === 'string' ? input.split(', ') : input;
190
+ let line = ''.padStart(nSpaceIndents, ' ');
191
+ let result = [];
192
+
193
+ values.forEach((value, index) => {
194
+ // Transform value if a transform function is supplied.
195
+ if (transform && typeof transform === 'function') {
196
+ value = transform(value);
197
+ }
198
+
199
+ // Check if adding the next value exceeds the column limit
200
+ if (line.length + value.length + 2 > nCols && line.trim().length > 0) {
201
+ // If it does, push the line to the result and start a new line
202
+ result.push(line);
203
+ line = ''.padStart(nSpaceIndents, ' ');
204
+ }
205
+
206
+ // Add the value to the line, followed by ", " if it's not the last value
207
+ line += value + (index < values.length - 1 ? ', ' : '');
208
+ });
209
+
210
+ // Add the last line if it's not empty
211
+ if (line.trim().length > 0) {
212
+ result.push(line);
213
+ }
214
+
215
+ return result.join('\n');
216
+ }
217
+
218
+ function wrapContent(longString, transform, joinOn = ' ', indent = 2, wrapAt = 80) {
219
+ let asArray = Array.isArray(longString)
220
+ ? longString
221
+ : String(longString).replaceAll(/\r\n/g, '\n').split('\n');
222
+
223
+ asArray = asArray.map(element => String(element).trim());
224
+
225
+ let lines = [];
226
+ let maxLen = wrapAt - indent;
227
+ let curLine = [];
228
+ let sgrLength = (s) => s.replaceAll(/\x1b\[?\d+(;\d+)*[a-zA-Z]/g, '').length;
229
+
230
+ for (let element of asArray) {
231
+ if (typeof transform === 'function') {
232
+ element = String(transform(element)).trim();
233
+ }
234
+
235
+ let curLength = sgrLength(curLine.join(joinOn));
236
+ let elementLength = sgrLength(String(element) + joinOn);
237
+
238
+ if (curLength + elementLength > maxLen) {
239
+ let leading = indent > 0 ? ' '.repeat(indent) : '';
240
+ lines.push(`${leading}${curLine.join(joinOn)}`);
241
+ curLine = [];
242
+ }
243
+
244
+ curLine.push(String(element));
245
+ }
246
+
247
+ return lines.join('\n');
248
+ }
@@ -49,6 +49,10 @@ export class Deferred extends Promise {
49
49
  */
50
50
  #resolve = null
51
51
 
52
+ #rejected = false
53
+
54
+ #resolved = false
55
+
52
56
  /**
53
57
  * When the Deferred is settled with {@link Deferred.resolve}, the `value`
54
58
  * passed to that function will be set here as well.
@@ -145,6 +149,10 @@ export class Deferred extends Promise {
145
149
  }
146
150
  // Mark the Deferred instance as settled
147
151
  this.#settled = true
152
+
153
+ // Mark the Deferred instance as resolved
154
+ this.#resolved = true
155
+
148
156
  // Resolve the promise with the provided value
149
157
  return _resolve(value)
150
158
  }
@@ -157,6 +165,10 @@ export class Deferred extends Promise {
157
165
  }
158
166
  // Mark the Deferred instance as settled
159
167
  this.#settled = true
168
+
169
+ // Mark the Deferred as being rejected.
170
+ this.#rejected = true
171
+
160
172
  // Reject the promise with the provided reason
161
173
  return _reject(reason)
162
174
  }
@@ -184,6 +196,32 @@ export class Deferred extends Promise {
184
196
  return this.#settled
185
197
  }
186
198
 
199
+ /**
200
+ * A getter that returns a boolean indicating whether the Deferred instance
201
+ * was rejected. This property can be used to check if the Deferred has been
202
+ * settled with a rejection. It is particularly useful in scenarios where
203
+ * the resolution status of the Deferred needs to be checked without
204
+ * accessing the rejection reason or invoking any additional logic.
205
+ *
206
+ * @returns {boolean} `true` if the Deferred was rejected, otherwise `false`.
207
+ */
208
+ get wasRejected() {
209
+ return this.#rejected
210
+ }
211
+
212
+ /**
213
+ * A getter that returns a boolean indicating whether the Deferred instance
214
+ * was resolved. This property is useful for checking if the Deferred has been
215
+ * settled with a resolution, allowing for checks on the Deferred's status
216
+ * without needing to access the resolved value or trigger any additional
217
+ * logic.
218
+ *
219
+ * @returns {boolean} `true` if the Deferred was resolved, otherwise `false`.
220
+ */
221
+ get wasResolved() {
222
+ return this.#resolved
223
+ }
224
+
187
225
  /**
188
226
  * Accessor for the promise managed by this Deferred instance.
189
227
  *
@@ -223,6 +261,35 @@ export class Deferred extends Promise {
223
261
  return this.#reject(reason)
224
262
  }
225
263
 
264
+ /**
265
+ * Customizes the output of `util.inspect` on instances of Deferred when
266
+ * used in Node.js. This method is invoked by Node.js's `util.inspect`
267
+ * utility to format the inspection output of a Deferred instance.
268
+ *
269
+ * The output includes the state of the Deferred (resolved, rejected, or
270
+ * unsettled) along with the resolved value or rejection reason, if
271
+ * applicable. This provides a quick, readable status of the Deferred
272
+ * instance directly in the console or debugging tools.
273
+ *
274
+ * @param {number} depth The depth to which `util.inspect` will recurse.
275
+ * @param {object} options Formatting options provided by `util.inspect`.
276
+ * @param {function} inspect Reference to the `util.inspect` function.
277
+ * @returns {string} A formatted string representing the Deferred instance.
278
+ */
279
+ [Symbol.for('nodejs.util.inspect.custom')](depth, options, inspect) {
280
+ return [
281
+ '\x1b[1mDeferred [\x1b[22;3mPromise\x1b[23;1m]\x1b[22m ',
282
+ '{ ',
283
+ (this.settled
284
+ ? (this.wasResolved
285
+ ? `resolved with \x1b[32m${this.value}\x1b[39m`
286
+ : `rejected with \x1b[31m${this.reason?.message ?? this.reason}\x1b[39m`)
287
+ : '\x1b[33munsettled valued or reason\x1b[39m'
288
+ ),
289
+ ' }'
290
+ ].join('')
291
+ }
292
+
226
293
  /**
227
294
  * A getter for the species symbol which returns a custom DeferredPromise
228
295
  * class. This class extends from Deferred and is used to ensure that the