@nejs/basic-extensions 2.16.0 → 2.18.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/bin/repl.basics.js +554 -0
- package/dist/@nejs/{basic-extensions.bundle.2.16.0.js → basic-extensions.bundle.2.18.0.js} +5 -5
- package/dist/@nejs/{basic-extensions.bundle.2.16.0.js.map → basic-extensions.bundle.2.18.0.js.map} +3 -3
- package/dist/cjs/utils/descriptor.utils.js +16 -4
- package/dist/cjs/utils/descriptor.utils.js.map +1 -1
- package/dist/mjs/utils/descriptor.utils.js +16 -4
- package/dist/mjs/utils/descriptor.utils.js.map +1 -1
- package/package.json +2 -2
- package/repl.bootstrap.js +38 -262
- package/repl.history +5 -0
- package/src/utils/descriptor.utils.js +18 -4
|
@@ -0,0 +1,554 @@
|
|
|
1
|
+
const repl = await import('node:repl');
|
|
2
|
+
const fs = await import('node:fs');
|
|
3
|
+
const project = JSON.parse(String(fs.readFileSync('./package.json')));
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Default prompt character used in the REPL.
|
|
7
|
+
*
|
|
8
|
+
* @constant {string} kDefaultPrompt
|
|
9
|
+
* @default
|
|
10
|
+
* @example
|
|
11
|
+
* console.log(kDefaultPrompt) // Output: 'λ'
|
|
12
|
+
*/
|
|
13
|
+
const kDefaultPrompt = 'λ'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Formats the given text in red color for terminal output.
|
|
17
|
+
*
|
|
18
|
+
* This function uses ANSI escape codes to colorize text. The default
|
|
19
|
+
* additional character appended to the text is a space.
|
|
20
|
+
*
|
|
21
|
+
* @function
|
|
22
|
+
* @param {string} text - The text to be colorized.
|
|
23
|
+
* @param {string} [plus=' '] - An optional string to append after the text.
|
|
24
|
+
* @returns {string} The colorized text with the appended string.
|
|
25
|
+
* @example
|
|
26
|
+
* console.log(red('Error')) // Output: '\x1b[31mError\x1b[1;39m\x1b[22m '
|
|
27
|
+
*/
|
|
28
|
+
const red = (text, plus = ' ') =>
|
|
29
|
+
`\x1b[31m${text}\x1b[1;39m\x1b[22m${plus}`
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Formats the given text in green color for terminal output.
|
|
33
|
+
*
|
|
34
|
+
* This function uses ANSI escape codes to colorize text. The default
|
|
35
|
+
* additional character appended to the text is a space.
|
|
36
|
+
*
|
|
37
|
+
* @function
|
|
38
|
+
* @param {string} text - The text to be colorized.
|
|
39
|
+
* @param {string} [plus=' '] - An optional string to append after the text.
|
|
40
|
+
* @returns {string} The colorized text with the appended string.
|
|
41
|
+
* @example
|
|
42
|
+
* console.log(green('Success')) // Output: '\x1b[32mSuccess\x1b[1;39m\x1b[22m '
|
|
43
|
+
*/
|
|
44
|
+
const green = (text, plus = ' ') =>
|
|
45
|
+
`\x1b[32m${text}\x1b[1;39m\x1b[22m${plus}`
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Default options for configuring the REPL (Read-Eval-Print Loop) environment.
|
|
49
|
+
*
|
|
50
|
+
* This object provides a set of default configurations that can be used to
|
|
51
|
+
* initialize a REPL session. Each property in this object can be overridden
|
|
52
|
+
* by providing a custom options object when creating a REPL instance.
|
|
53
|
+
*
|
|
54
|
+
* @constant {Object} replOpts
|
|
55
|
+
* @property {Function|undefined} about - A function to display information
|
|
56
|
+
* about the REPL or project. Defaults to undefined.
|
|
57
|
+
* @property {boolean} allowInvocation - Determines if invocation of commands
|
|
58
|
+
* is allowed. Defaults to true.
|
|
59
|
+
* @property {boolean} allowDefaultCommands - Indicates if default commands
|
|
60
|
+
* like 'clear' and 'about' should be available. Defaults to true.
|
|
61
|
+
* @property {Array} commands - An array of custom command definitions to be
|
|
62
|
+
* added to the REPL. Defaults to an empty array.
|
|
63
|
+
* @property {Object} exports - An object containing variables or functions
|
|
64
|
+
* to be exported to the REPL context. Defaults to an empty object.
|
|
65
|
+
* @property {Function} onReady - A callback function that is executed when
|
|
66
|
+
* the REPL is ready. Defaults to an empty function.
|
|
67
|
+
* @property {string} prompt - The prompt string displayed in the REPL.
|
|
68
|
+
* Defaults to the value of `kDefaultPrompt`.
|
|
69
|
+
* @property {boolean} useGlobal - Specifies whether the REPL should use the
|
|
70
|
+
* global context. Defaults to true.
|
|
71
|
+
* @property {Object} replOpts - Additional options to be passed to the REPL
|
|
72
|
+
* server. Defaults to an empty object.
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* // Creating a REPL with default options
|
|
76
|
+
* const replInstance = createRepl()
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* // Overriding default options
|
|
80
|
+
* const customRepl = createRepl({
|
|
81
|
+
* prompt: '> ',
|
|
82
|
+
* allowDefaultCommands: false
|
|
83
|
+
* })
|
|
84
|
+
*/
|
|
85
|
+
const replOpts = {
|
|
86
|
+
about: undefined,
|
|
87
|
+
allowDefaultCommands: true,
|
|
88
|
+
commands: [],
|
|
89
|
+
exports: {},
|
|
90
|
+
onReady: () => { },
|
|
91
|
+
prompt: kDefaultPrompt,
|
|
92
|
+
useGlobal: true,
|
|
93
|
+
replOpts: {},
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Creates a REPL (Read-Eval-Print Loop) instance with customizable options.
|
|
98
|
+
*
|
|
99
|
+
* This function initializes a REPL environment using the provided options,
|
|
100
|
+
* allowing for the execution of commands and scripts in a dynamic context.
|
|
101
|
+
* It supports custom commands, prompt customization, and context exports.
|
|
102
|
+
*
|
|
103
|
+
* @function createRepl
|
|
104
|
+
* @param {Object} [options] - Configuration options for the REPL instance.
|
|
105
|
+
* @param {Function} [options.about] - Function to display information about
|
|
106
|
+
* the REPL or project.
|
|
107
|
+
* @param {boolean} [options.allowDefaultCommands=true] - Indicates if default
|
|
108
|
+
* commands like 'clear' and 'about' should be available.
|
|
109
|
+
* @param {Array} [options.commands] - Custom command definitions to be added
|
|
110
|
+
* to the REPL.
|
|
111
|
+
* @param {Object} [options.exports] - Variables or functions to be exported
|
|
112
|
+
* to the REPL context.
|
|
113
|
+
* @param {Function} [options.onReady] - Callback executed when the REPL is
|
|
114
|
+
* ready.
|
|
115
|
+
* @param {string} [options.prompt=kDefaultPrompt] - The prompt string
|
|
116
|
+
* displayed in the REPL.
|
|
117
|
+
* @param {boolean} [options.useGlobal=true] - Specifies whether the REPL
|
|
118
|
+
* should use the global context.
|
|
119
|
+
* @param {Object} [options.replOpts] - Additional options for the REPL server.
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* // Creating a REPL with default options
|
|
123
|
+
* const replInstance = createRepl()
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* // Overriding default options
|
|
127
|
+
* const customRepl = createRepl({
|
|
128
|
+
* prompt: '> ',
|
|
129
|
+
* allowDefaultCommands: false
|
|
130
|
+
* })
|
|
131
|
+
*/
|
|
132
|
+
export function createRepl(options) {
|
|
133
|
+
options = {
|
|
134
|
+
...replOpts,
|
|
135
|
+
...((options && typeof options === 'object' && options) || {})
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const prompt = green(options?.prompt ?? kDefaultPrompt)
|
|
139
|
+
const replServer = new repl.REPLServer({
|
|
140
|
+
useGlobal: options?.useGlobal ?? false,
|
|
141
|
+
prompt: options?.prompt ?? kDefaultPrompt,
|
|
142
|
+
...(options?.replOpts ?? {})
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
const aboutFn = options?.about ?? defaultAbout.bind(replServer)
|
|
146
|
+
const state = {
|
|
147
|
+
allowInvocation: true
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const clearFn = (displayPrompt = true) => {
|
|
151
|
+
clear(state)
|
|
152
|
+
|
|
153
|
+
if (displayPrompt)
|
|
154
|
+
replServer.displayPrompt()
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
let commands = [
|
|
158
|
+
...(options?.allowDefaultCommands === false ? [] : [
|
|
159
|
+
['cls', { action: () => clearFn(false), help: 'Clears the screen' }],
|
|
160
|
+
['clear', { action: () => clearFn(false), help: 'Clears the screen' }],
|
|
161
|
+
['about', { action: aboutFn, help: 'Shows info about this project' }],
|
|
162
|
+
['state', {
|
|
163
|
+
action() {
|
|
164
|
+
printStateString(options?.exports ?? replServer.context, state)
|
|
165
|
+
},
|
|
166
|
+
help: 'Generates state about this REPL context'
|
|
167
|
+
}]
|
|
168
|
+
]),
|
|
169
|
+
...(Array.isArray(options?.commands) ? options.commands : [])
|
|
170
|
+
]
|
|
171
|
+
|
|
172
|
+
for (const [command, options] of commands) {
|
|
173
|
+
const { action, help, overridable } = options
|
|
174
|
+
|
|
175
|
+
replServer.defineCommand(command, { action, help })
|
|
176
|
+
|
|
177
|
+
if (overridable !== false) {
|
|
178
|
+
overridableGlobal(replServer, command, action)
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
Object.assign(replServer.context,
|
|
183
|
+
options?.exports ?? {},
|
|
184
|
+
{
|
|
185
|
+
[Symbol.for('repl.prompt')]: prompt,
|
|
186
|
+
replServer
|
|
187
|
+
},
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
Object.defineProperty(replServer, '_initialPrompt', {
|
|
191
|
+
get() {
|
|
192
|
+
const _prompt = replServer.context[Symbol.for('repl.prompt')]
|
|
193
|
+
const isRed = !globalThis?._
|
|
194
|
+
|
|
195
|
+
return isRed ? red(_prompt) : green(_prompt)
|
|
196
|
+
}
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
replServer.setupHistory('repl.history', function(err, repl) {
|
|
200
|
+
clearFn(false)
|
|
201
|
+
aboutFn(false)
|
|
202
|
+
options?.onReady?.call(replServer)
|
|
203
|
+
replServer.displayPrompt()
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
return replServer
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Displays information about the current project in the REPL.
|
|
211
|
+
*
|
|
212
|
+
* This function outputs the project's name, version, description, and author
|
|
213
|
+
* to the console using ANSI escape codes for color formatting. It attempts to
|
|
214
|
+
* display the REPL prompt after printing the information.
|
|
215
|
+
*
|
|
216
|
+
* The function uses optional chaining to check if `this` context has a
|
|
217
|
+
* `displayPrompt` method. If not, it defaults to using the `replServer`
|
|
218
|
+
* instance to display the prompt.
|
|
219
|
+
*
|
|
220
|
+
* @function defaultAbout
|
|
221
|
+
* @example
|
|
222
|
+
* // Outputs project information in the REPL
|
|
223
|
+
* defaultAbout()
|
|
224
|
+
*/
|
|
225
|
+
function defaultAbout(displayPrompt = true) {
|
|
226
|
+
console.log(`\x1b[32m${project.name}\x1b[39m v\x1b[1m${project.version}\x1b[22m`);
|
|
227
|
+
console.log(`\x1b[3m${project.description}\x1b[23m`);
|
|
228
|
+
console.log(`Written by \x1b[34m${project.author ?? 'Jane Doe'}\x1b[39m.`);
|
|
229
|
+
|
|
230
|
+
if (displayPrompt) {
|
|
231
|
+
console.log('')
|
|
232
|
+
return this?.displayPrompt() ?? replServer.displayPrompt();
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Clears the terminal screen if invocation is allowed.
|
|
238
|
+
*
|
|
239
|
+
* This function uses ANSI escape codes to reset the cursor position and
|
|
240
|
+
* clear the terminal screen. It is typically used to refresh the display
|
|
241
|
+
* in a REPL environment.
|
|
242
|
+
*
|
|
243
|
+
* @function clear
|
|
244
|
+
* @param {boolean} [replState.allowInvocation] - A flag indicating whether the
|
|
245
|
+
* screen clearing is permitted. Defaults to true.
|
|
246
|
+
* @example
|
|
247
|
+
* // Clears the screen if invocation is allowed
|
|
248
|
+
* clear()
|
|
249
|
+
*/
|
|
250
|
+
function clear(replState) {
|
|
251
|
+
if (replState.allowInvocation) {
|
|
252
|
+
process.stdout.write('\x1b[3;0f\x1b[2J')
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Creates an overridable global property within a given context, allowing
|
|
258
|
+
* dynamic reassignment and restoration of its default behavior.
|
|
259
|
+
*
|
|
260
|
+
* This function defines a property on the specified context that can be
|
|
261
|
+
* overridden by an expression assignment. It also registers a REPL command
|
|
262
|
+
* to restore the property to its default state. The property is initially
|
|
263
|
+
* set to execute a provided action function, and upon reassignment, it
|
|
264
|
+
* stores the new value and logs a message indicating the change.
|
|
265
|
+
*
|
|
266
|
+
* @function overridableGlobal
|
|
267
|
+
* @param {Object} replServer - The REPL server instance to define commands on.
|
|
268
|
+
* @param {string} property - The name of the property to be made overridable.
|
|
269
|
+
* @param {Function} action - The default function to execute when the property
|
|
270
|
+
* is accessed before being overridden.
|
|
271
|
+
* @param {string} [changeText='Expression assignment to "@X", previous function now disabled.'] -
|
|
272
|
+
* The message to log when the property is overridden. The placeholder "@X" is
|
|
273
|
+
* replaced with the property name.
|
|
274
|
+
* @param {Object} [context=globalThis] - The context in which to define the
|
|
275
|
+
* property. Defaults to the global object.
|
|
276
|
+
*
|
|
277
|
+
* @example
|
|
278
|
+
* // Define an overridable global property 'myProp' in the REPL
|
|
279
|
+
* overridableGlobal(replServer, 'myProp', () => 'default value')
|
|
280
|
+
*/
|
|
281
|
+
function overridableGlobal(
|
|
282
|
+
replServer,
|
|
283
|
+
property,
|
|
284
|
+
action,
|
|
285
|
+
changeText = 'Expression assignment to "@X", previous function now disabled.',
|
|
286
|
+
context = globalThis,
|
|
287
|
+
) {
|
|
288
|
+
const message = changeText.replaceAll(/\@X/g, property)
|
|
289
|
+
|
|
290
|
+
let changed = false
|
|
291
|
+
let storage = undefined
|
|
292
|
+
|
|
293
|
+
const makeDescriptor = () => ({
|
|
294
|
+
get() {
|
|
295
|
+
if (changed === false) {
|
|
296
|
+
return action()
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return storage
|
|
300
|
+
},
|
|
301
|
+
set(value) {
|
|
302
|
+
if (changed === false) {
|
|
303
|
+
console.log(message)
|
|
304
|
+
changed = true
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
storage = value
|
|
308
|
+
},
|
|
309
|
+
configurable: true,
|
|
310
|
+
get enumerable() { return changed }
|
|
311
|
+
})
|
|
312
|
+
|
|
313
|
+
replServer.defineCommand(
|
|
314
|
+
`restore${property.charAt(0).toUpperCase()}${property.substring(1,property.length)}`,
|
|
315
|
+
{
|
|
316
|
+
action() {
|
|
317
|
+
changed = false
|
|
318
|
+
storage = undefined
|
|
319
|
+
|
|
320
|
+
Object.defineProperty(context, property, makeDescriptor())
|
|
321
|
+
console.log(this.help)
|
|
322
|
+
},
|
|
323
|
+
help: `Restores ${property} to default REPL custom state.`
|
|
324
|
+
}
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
Object.defineProperty(context, property, makeDescriptor())
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Generates a snapshot of the current REPL state, categorizing global objects
|
|
332
|
+
* into classes, functions, properties, symbols, and descriptors. This function
|
|
333
|
+
* is designed to capture and organize the current state for inspection or
|
|
334
|
+
* modification purposes. It temporarily disables invocation to safely enumerate
|
|
335
|
+
* global objects, capturing their descriptors and categorizing them accordingly.
|
|
336
|
+
* If invocation is already disabled, it returns the current state without
|
|
337
|
+
* modification. Skipped properties during enumeration are tracked but not
|
|
338
|
+
* processed further.
|
|
339
|
+
*
|
|
340
|
+
* @returns {Object} An object representing the current REPL state, with
|
|
341
|
+
* properties for classes, functions, properties, symbols, and descriptors
|
|
342
|
+
* (further divided into accessors and data descriptors). Each category is an
|
|
343
|
+
* object with keys as the global identifiers and values containing the key,
|
|
344
|
+
* value, and descriptor of the item.
|
|
345
|
+
*/
|
|
346
|
+
function generateState(forObject = globalThis, _state) {
|
|
347
|
+
const replState = {
|
|
348
|
+
classes: {},
|
|
349
|
+
functions: {},
|
|
350
|
+
properties: {},
|
|
351
|
+
symbols: {},
|
|
352
|
+
descriptors: {
|
|
353
|
+
accessors: {},
|
|
354
|
+
data: {},
|
|
355
|
+
},
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
if (!_state.allowInvocation) {
|
|
359
|
+
return replState;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
let skipped = [];
|
|
363
|
+
|
|
364
|
+
_state.allowInvocation = false;
|
|
365
|
+
Reflect.ownKeys(forObject).forEach(key => {
|
|
366
|
+
try {
|
|
367
|
+
const value = forObject[key];
|
|
368
|
+
const descriptor = Object.getOwnPropertyDescriptor(forObject, key);
|
|
369
|
+
|
|
370
|
+
if (String(value).startsWith('class')) {
|
|
371
|
+
replState.classes[key] = {key, value, descriptor};
|
|
372
|
+
}
|
|
373
|
+
else if (typeof value === 'function') {
|
|
374
|
+
replState.functions[key] = {key, value, descriptor};
|
|
375
|
+
}
|
|
376
|
+
else {
|
|
377
|
+
replState.properties[key] = {key, value, descriptor};
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (typeof key === 'symbol') {
|
|
381
|
+
replState.symbols[key] = { key, value, descriptor };
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (Reflect.has(descriptor, 'get') || Reflect.has(descriptor, 'set')) {
|
|
385
|
+
replState.descriptors.accessors[key] = { key, descriptor };
|
|
386
|
+
}
|
|
387
|
+
else if (Reflect.has(descriptor, 'value')) {
|
|
388
|
+
replState.descriptors.data[key] = { key, descriptor };
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
catch (ignored) {
|
|
392
|
+
skipped.push(String(key));
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
_state.allowInvocation = true;
|
|
396
|
+
|
|
397
|
+
return replState;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Prints a formatted string representation of the state of an object.
|
|
402
|
+
*
|
|
403
|
+
* This function generates a state object for the given `forObject` and
|
|
404
|
+
* prints its classes, functions, properties, and descriptors in a
|
|
405
|
+
* human-readable format. The output is styled using ANSI escape codes
|
|
406
|
+
* for terminal display.
|
|
407
|
+
*
|
|
408
|
+
* @param {Object} [forObject=globalThis] - The object to generate the
|
|
409
|
+
* state from. Defaults to the global object.
|
|
410
|
+
*
|
|
411
|
+
* @example
|
|
412
|
+
* // Prints the state of the global object
|
|
413
|
+
* printStateString()
|
|
414
|
+
*
|
|
415
|
+
* @example
|
|
416
|
+
* // Prints the state of a custom object without allowing invocation
|
|
417
|
+
* const myObject = { a: 1, b: function() {}, c: class {} }
|
|
418
|
+
* printStateString(myObject, false)
|
|
419
|
+
*/
|
|
420
|
+
function printStateString(forObject = globalThis, _state) {
|
|
421
|
+
const state = generateState(forObject, _state);
|
|
422
|
+
const b = (s) => `\x1b[1m${s}\x1b[22m`;
|
|
423
|
+
const i = (s) => `\x1b[3m${s}\x1b[23m`;
|
|
424
|
+
const j = ', ';
|
|
425
|
+
|
|
426
|
+
state.classes = [...Object.keys(state.classes)].map(k => String(k));
|
|
427
|
+
state.functions = [...Object.keys(state.functions)].map(k => String(k));
|
|
428
|
+
state.properties = [...Object.keys(state.properties)].map(k => String(k));
|
|
429
|
+
|
|
430
|
+
state.descriptors.accessors = [...Object.keys(state.descriptors.accessors)]
|
|
431
|
+
.map(k => String(k));
|
|
432
|
+
|
|
433
|
+
state.descriptors.data = [...Object.keys(state.descriptors.data)]
|
|
434
|
+
.map(k => String(k));
|
|
435
|
+
|
|
436
|
+
if (state.classes.length)
|
|
437
|
+
console.log(`${b('Classes')}\n${wrapContent(state.classes, i, j)}`);
|
|
438
|
+
|
|
439
|
+
if (state.functions.length)
|
|
440
|
+
console.log(`${b('Functions')}\n${wrapContent(state.functions, i, j)}`);
|
|
441
|
+
|
|
442
|
+
if (state.properties.length)
|
|
443
|
+
console.log(`${b('Properties')}\n${wrapContent(state.properties, i, j)}`);
|
|
444
|
+
|
|
445
|
+
if (state.descriptors.accessors.length)
|
|
446
|
+
console.log(`${b('Accessors')}\n${wrapContent(state.descriptors.accessors, i, j)}`);
|
|
447
|
+
|
|
448
|
+
console.log('')
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Formats a string or array of values into lines with specified indentation and line width.
|
|
453
|
+
* @param {string|array} input - The input string or array of strings to be formatted.
|
|
454
|
+
* @param {number} nCols - The maximum number of columns per line (default 80).
|
|
455
|
+
* @param {number} nSpaceIndents - The number of spaces for indentation (default 2).
|
|
456
|
+
* @returns {string} The formatted string.
|
|
457
|
+
*/
|
|
458
|
+
function formatValues(input, transform, nCols = 80, nSpaceIndents = 2) {
|
|
459
|
+
// Split the string into an array if input is a string
|
|
460
|
+
const values = typeof input === 'string' ? input.split(', ') : input;
|
|
461
|
+
let line = ''.padStart(nSpaceIndents, ' ');
|
|
462
|
+
let result = [];
|
|
463
|
+
|
|
464
|
+
values.forEach((value, index) => {
|
|
465
|
+
// Transform value if a transform function is supplied.
|
|
466
|
+
if (transform && typeof transform === 'function') {
|
|
467
|
+
value = transform(value);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Check if adding the next value exceeds the column limit
|
|
471
|
+
if (line.length + value.length + 2 > nCols && line.trim().length > 0) {
|
|
472
|
+
// If it does, push the line to the result and start a new line
|
|
473
|
+
result.push(line);
|
|
474
|
+
line = ''.padStart(nSpaceIndents, ' ');
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// Add the value to the line, followed by ", " if it's not the last value
|
|
478
|
+
line += value + (index < values.length - 1 ? ', ' : '');
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
// Add the last line if it's not empty
|
|
482
|
+
if (line.trim().length > 0) {
|
|
483
|
+
result.push(line);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
return result.join('\n');
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Wraps a long string or array of strings into lines with specified
|
|
491
|
+
* indentation and line width.
|
|
492
|
+
*
|
|
493
|
+
* This function processes the input by splitting it into lines, applying
|
|
494
|
+
* optional transformations, and wrapping the content to fit within a
|
|
495
|
+
* specified width. It handles ANSI escape codes to ensure accurate
|
|
496
|
+
* length calculations for terminal output.
|
|
497
|
+
*
|
|
498
|
+
* @function wrapContent
|
|
499
|
+
* @param {string|Array} longString - The input string or array of strings
|
|
500
|
+
* to be wrapped.
|
|
501
|
+
* @param {Function} [transform] - An optional function to transform each
|
|
502
|
+
* element before wrapping.
|
|
503
|
+
* @param {string} [joinOn=' '] - The string used to join elements in a line.
|
|
504
|
+
* @param {number} [indent=2] - The number of spaces for indentation.
|
|
505
|
+
* @param {number} [wrapAt=80] - The maximum line width for wrapping.
|
|
506
|
+
* @returns {string} The wrapped content as a single string with lines
|
|
507
|
+
* separated by newlines.
|
|
508
|
+
* @example
|
|
509
|
+
* // Wraps a long string with default settings
|
|
510
|
+
* const wrapped = wrapContent('This is a very long string that needs to be wrapped.')
|
|
511
|
+
* console.log(wrapped)
|
|
512
|
+
*/
|
|
513
|
+
function wrapContent(
|
|
514
|
+
longString,
|
|
515
|
+
transform,
|
|
516
|
+
joinOn = ' ',
|
|
517
|
+
indent = 2,
|
|
518
|
+
wrapAt = 80
|
|
519
|
+
) {
|
|
520
|
+
let asArray = Array.isArray(longString)
|
|
521
|
+
? longString
|
|
522
|
+
: String(longString).replaceAll(/\r\n/g, '\n').split('\n')
|
|
523
|
+
|
|
524
|
+
asArray = asArray.map(element => String(element).trim())
|
|
525
|
+
|
|
526
|
+
let lines = []
|
|
527
|
+
let maxLen = wrapAt - indent
|
|
528
|
+
let curLine = []
|
|
529
|
+
let sgrLength = (s) => s.replaceAll(/\x1b\[?\d+(;\d+)*[a-zA-Z]/g, '').length
|
|
530
|
+
|
|
531
|
+
for (let element of asArray) {
|
|
532
|
+
if (typeof transform === 'function') {
|
|
533
|
+
element = String(transform(element)).trim()
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
let curLength = sgrLength(curLine.join(joinOn))
|
|
537
|
+
let elementLength = sgrLength(String(element) + joinOn)
|
|
538
|
+
|
|
539
|
+
if (curLength + elementLength > maxLen) {
|
|
540
|
+
let leading = indent > 0 ? ' '.repeat(indent) : ''
|
|
541
|
+
lines.push(`${leading}${curLine.join(joinOn)}`)
|
|
542
|
+
curLine = []
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
curLine.push(String(element))
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
if (curLine.length) {
|
|
549
|
+
let leading = indent > 0 ? ' '.repeat(indent) : ''
|
|
550
|
+
lines.push(`${leading}${curLine.join(joinOn)}`)
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
return lines.join('\n')
|
|
554
|
+
}
|