commander 12.0.0 → 13.0.0-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/Readme.md +15 -29
- package/esm.mjs +1 -1
- package/lib/argument.js +9 -5
- package/lib/command.js +509 -223
- package/lib/error.js +0 -4
- package/lib/help.js +336 -84
- package/lib/option.js +40 -19
- package/lib/suggestSimilar.js +5 -4
- package/package.json +23 -21
- package/typings/index.d.ts +174 -44
package/lib/error.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* CommanderError class
|
|
3
|
-
* @class
|
|
4
3
|
*/
|
|
5
4
|
class CommanderError extends Error {
|
|
6
5
|
/**
|
|
@@ -8,7 +7,6 @@ class CommanderError extends Error {
|
|
|
8
7
|
* @param {number} exitCode suggested exit code which could be used with process.exit
|
|
9
8
|
* @param {string} code an id string representing the error
|
|
10
9
|
* @param {string} message human-readable description of the error
|
|
11
|
-
* @constructor
|
|
12
10
|
*/
|
|
13
11
|
constructor(exitCode, code, message) {
|
|
14
12
|
super(message);
|
|
@@ -23,13 +21,11 @@ class CommanderError extends Error {
|
|
|
23
21
|
|
|
24
22
|
/**
|
|
25
23
|
* InvalidArgumentError class
|
|
26
|
-
* @class
|
|
27
24
|
*/
|
|
28
25
|
class InvalidArgumentError extends CommanderError {
|
|
29
26
|
/**
|
|
30
27
|
* Constructs the InvalidArgumentError class
|
|
31
28
|
* @param {string} [message] explanation of why argument is invalid
|
|
32
|
-
* @constructor
|
|
33
29
|
*/
|
|
34
30
|
constructor(message) {
|
|
35
31
|
super(1, 'commander.invalidArgument', message);
|
package/lib/help.js
CHANGED
|
@@ -12,11 +12,24 @@ const { humanReadableArgName } = require('./argument.js');
|
|
|
12
12
|
class Help {
|
|
13
13
|
constructor() {
|
|
14
14
|
this.helpWidth = undefined;
|
|
15
|
+
this.minWidthToWrap = 40;
|
|
15
16
|
this.sortSubcommands = false;
|
|
16
17
|
this.sortOptions = false;
|
|
17
18
|
this.showGlobalOptions = false;
|
|
18
19
|
}
|
|
19
20
|
|
|
21
|
+
/**
|
|
22
|
+
* prepareContext is called by Commander after applying overrides from `Command.configureHelp()`
|
|
23
|
+
* and just before calling `formatHelp()`.
|
|
24
|
+
*
|
|
25
|
+
* Commander just uses the helpWidth and the rest is provided for optional use by more complex subclasses.
|
|
26
|
+
*
|
|
27
|
+
* @param {{ error?: boolean, helpWidth?: number, outputHasColors?: boolean }} contextOptions
|
|
28
|
+
*/
|
|
29
|
+
prepareContext(contextOptions) {
|
|
30
|
+
this.helpWidth = this.helpWidth ?? contextOptions.helpWidth ?? 80;
|
|
31
|
+
}
|
|
32
|
+
|
|
20
33
|
/**
|
|
21
34
|
* Get an array of the visible subcommands. Includes a placeholder for the implicit help command, if there is one.
|
|
22
35
|
*
|
|
@@ -25,14 +38,14 @@ class Help {
|
|
|
25
38
|
*/
|
|
26
39
|
|
|
27
40
|
visibleCommands(cmd) {
|
|
28
|
-
const visibleCommands = cmd.commands.filter(cmd => !cmd._hidden);
|
|
41
|
+
const visibleCommands = cmd.commands.filter((cmd) => !cmd._hidden);
|
|
29
42
|
const helpCommand = cmd._getHelpCommand();
|
|
30
43
|
if (helpCommand && !helpCommand._hidden) {
|
|
31
44
|
visibleCommands.push(helpCommand);
|
|
32
45
|
}
|
|
33
46
|
if (this.sortSubcommands) {
|
|
34
47
|
visibleCommands.sort((a, b) => {
|
|
35
|
-
// @ts-ignore: overloaded return type
|
|
48
|
+
// @ts-ignore: because overloaded return type
|
|
36
49
|
return a.name().localeCompare(b.name());
|
|
37
50
|
});
|
|
38
51
|
}
|
|
@@ -44,12 +57,14 @@ class Help {
|
|
|
44
57
|
*
|
|
45
58
|
* @param {Option} a
|
|
46
59
|
* @param {Option} b
|
|
47
|
-
* @returns number
|
|
60
|
+
* @returns {number}
|
|
48
61
|
*/
|
|
49
62
|
compareOptions(a, b) {
|
|
50
63
|
const getSortKey = (option) => {
|
|
51
64
|
// WYSIWYG for order displayed in help. Short used for comparison if present. No special handling for negated.
|
|
52
|
-
return option.short
|
|
65
|
+
return option.short
|
|
66
|
+
? option.short.replace(/^-/, '')
|
|
67
|
+
: option.long.replace(/^--/, '');
|
|
53
68
|
};
|
|
54
69
|
return getSortKey(a).localeCompare(getSortKey(b));
|
|
55
70
|
}
|
|
@@ -72,9 +87,13 @@ class Help {
|
|
|
72
87
|
if (!removeShort && !removeLong) {
|
|
73
88
|
visibleOptions.push(helpOption); // no changes needed
|
|
74
89
|
} else if (helpOption.long && !removeLong) {
|
|
75
|
-
visibleOptions.push(
|
|
90
|
+
visibleOptions.push(
|
|
91
|
+
cmd.createOption(helpOption.long, helpOption.description),
|
|
92
|
+
);
|
|
76
93
|
} else if (helpOption.short && !removeShort) {
|
|
77
|
-
visibleOptions.push(
|
|
94
|
+
visibleOptions.push(
|
|
95
|
+
cmd.createOption(helpOption.short, helpOption.description),
|
|
96
|
+
);
|
|
78
97
|
}
|
|
79
98
|
}
|
|
80
99
|
if (this.sortOptions) {
|
|
@@ -94,8 +113,14 @@ class Help {
|
|
|
94
113
|
if (!this.showGlobalOptions) return [];
|
|
95
114
|
|
|
96
115
|
const globalOptions = [];
|
|
97
|
-
for (
|
|
98
|
-
|
|
116
|
+
for (
|
|
117
|
+
let ancestorCmd = cmd.parent;
|
|
118
|
+
ancestorCmd;
|
|
119
|
+
ancestorCmd = ancestorCmd.parent
|
|
120
|
+
) {
|
|
121
|
+
const visibleOptions = ancestorCmd.options.filter(
|
|
122
|
+
(option) => !option.hidden,
|
|
123
|
+
);
|
|
99
124
|
globalOptions.push(...visibleOptions);
|
|
100
125
|
}
|
|
101
126
|
if (this.sortOptions) {
|
|
@@ -114,13 +139,14 @@ class Help {
|
|
|
114
139
|
visibleArguments(cmd) {
|
|
115
140
|
// Side effect! Apply the legacy descriptions before the arguments are displayed.
|
|
116
141
|
if (cmd._argsDescription) {
|
|
117
|
-
cmd.registeredArguments.forEach(argument => {
|
|
118
|
-
argument.description =
|
|
142
|
+
cmd.registeredArguments.forEach((argument) => {
|
|
143
|
+
argument.description =
|
|
144
|
+
argument.description || cmd._argsDescription[argument.name()] || '';
|
|
119
145
|
});
|
|
120
146
|
}
|
|
121
147
|
|
|
122
148
|
// If there are any arguments with a description then return all the arguments.
|
|
123
|
-
if (cmd.registeredArguments.find(argument => argument.description)) {
|
|
149
|
+
if (cmd.registeredArguments.find((argument) => argument.description)) {
|
|
124
150
|
return cmd.registeredArguments;
|
|
125
151
|
}
|
|
126
152
|
return [];
|
|
@@ -135,11 +161,15 @@ class Help {
|
|
|
135
161
|
|
|
136
162
|
subcommandTerm(cmd) {
|
|
137
163
|
// Legacy. Ignores custom usage string, and nested commands.
|
|
138
|
-
const args = cmd.registeredArguments
|
|
139
|
-
|
|
164
|
+
const args = cmd.registeredArguments
|
|
165
|
+
.map((arg) => humanReadableArgName(arg))
|
|
166
|
+
.join(' ');
|
|
167
|
+
return (
|
|
168
|
+
cmd._name +
|
|
140
169
|
(cmd._aliases[0] ? '|' + cmd._aliases[0] : '') +
|
|
141
170
|
(cmd.options.length ? ' [options]' : '') + // simplistic check for non-help option
|
|
142
|
-
(args ? ' ' + args : '')
|
|
171
|
+
(args ? ' ' + args : '')
|
|
172
|
+
);
|
|
143
173
|
}
|
|
144
174
|
|
|
145
175
|
/**
|
|
@@ -174,7 +204,12 @@ class Help {
|
|
|
174
204
|
|
|
175
205
|
longestSubcommandTermLength(cmd, helper) {
|
|
176
206
|
return helper.visibleCommands(cmd).reduce((max, command) => {
|
|
177
|
-
return Math.max(
|
|
207
|
+
return Math.max(
|
|
208
|
+
max,
|
|
209
|
+
this.displayWidth(
|
|
210
|
+
helper.styleSubcommandTerm(helper.subcommandTerm(command)),
|
|
211
|
+
),
|
|
212
|
+
);
|
|
178
213
|
}, 0);
|
|
179
214
|
}
|
|
180
215
|
|
|
@@ -188,7 +223,10 @@ class Help {
|
|
|
188
223
|
|
|
189
224
|
longestOptionTermLength(cmd, helper) {
|
|
190
225
|
return helper.visibleOptions(cmd).reduce((max, option) => {
|
|
191
|
-
return Math.max(
|
|
226
|
+
return Math.max(
|
|
227
|
+
max,
|
|
228
|
+
this.displayWidth(helper.styleOptionTerm(helper.optionTerm(option))),
|
|
229
|
+
);
|
|
192
230
|
}, 0);
|
|
193
231
|
}
|
|
194
232
|
|
|
@@ -202,7 +240,10 @@ class Help {
|
|
|
202
240
|
|
|
203
241
|
longestGlobalOptionTermLength(cmd, helper) {
|
|
204
242
|
return helper.visibleGlobalOptions(cmd).reduce((max, option) => {
|
|
205
|
-
return Math.max(
|
|
243
|
+
return Math.max(
|
|
244
|
+
max,
|
|
245
|
+
this.displayWidth(helper.styleOptionTerm(helper.optionTerm(option))),
|
|
246
|
+
);
|
|
206
247
|
}, 0);
|
|
207
248
|
}
|
|
208
249
|
|
|
@@ -216,7 +257,12 @@ class Help {
|
|
|
216
257
|
|
|
217
258
|
longestArgumentTermLength(cmd, helper) {
|
|
218
259
|
return helper.visibleArguments(cmd).reduce((max, argument) => {
|
|
219
|
-
return Math.max(
|
|
260
|
+
return Math.max(
|
|
261
|
+
max,
|
|
262
|
+
this.displayWidth(
|
|
263
|
+
helper.styleArgumentTerm(helper.argumentTerm(argument)),
|
|
264
|
+
),
|
|
265
|
+
);
|
|
220
266
|
}, 0);
|
|
221
267
|
}
|
|
222
268
|
|
|
@@ -234,7 +280,11 @@ class Help {
|
|
|
234
280
|
cmdName = cmdName + '|' + cmd._aliases[0];
|
|
235
281
|
}
|
|
236
282
|
let ancestorCmdNames = '';
|
|
237
|
-
for (
|
|
283
|
+
for (
|
|
284
|
+
let ancestorCmd = cmd.parent;
|
|
285
|
+
ancestorCmd;
|
|
286
|
+
ancestorCmd = ancestorCmd.parent
|
|
287
|
+
) {
|
|
238
288
|
ancestorCmdNames = ancestorCmd.name() + ' ' + ancestorCmdNames;
|
|
239
289
|
}
|
|
240
290
|
return ancestorCmdNames + cmdName + ' ' + cmd.usage();
|
|
@@ -248,7 +298,7 @@ class Help {
|
|
|
248
298
|
*/
|
|
249
299
|
|
|
250
300
|
commandDescription(cmd) {
|
|
251
|
-
// @ts-ignore: overloaded return type
|
|
301
|
+
// @ts-ignore: because overloaded return type
|
|
252
302
|
return cmd.description();
|
|
253
303
|
}
|
|
254
304
|
|
|
@@ -261,7 +311,7 @@ class Help {
|
|
|
261
311
|
*/
|
|
262
312
|
|
|
263
313
|
subcommandDescription(cmd) {
|
|
264
|
-
// @ts-ignore: overloaded return type
|
|
314
|
+
// @ts-ignore: because overloaded return type
|
|
265
315
|
return cmd.summary() || cmd.description();
|
|
266
316
|
}
|
|
267
317
|
|
|
@@ -278,15 +328,20 @@ class Help {
|
|
|
278
328
|
if (option.argChoices) {
|
|
279
329
|
extraInfo.push(
|
|
280
330
|
// use stringify to match the display of the default value
|
|
281
|
-
`choices: ${option.argChoices.map((choice) => JSON.stringify(choice)).join(', ')}
|
|
331
|
+
`choices: ${option.argChoices.map((choice) => JSON.stringify(choice)).join(', ')}`,
|
|
332
|
+
);
|
|
282
333
|
}
|
|
283
334
|
if (option.defaultValue !== undefined) {
|
|
284
335
|
// default for boolean and negated more for programmer than end user,
|
|
285
336
|
// but show true/false for boolean option as may be for hand-rolled env or config processing.
|
|
286
|
-
const showDefault =
|
|
337
|
+
const showDefault =
|
|
338
|
+
option.required ||
|
|
339
|
+
option.optional ||
|
|
287
340
|
(option.isBoolean() && typeof option.defaultValue === 'boolean');
|
|
288
341
|
if (showDefault) {
|
|
289
|
-
extraInfo.push(
|
|
342
|
+
extraInfo.push(
|
|
343
|
+
`default: ${option.defaultValueDescription || JSON.stringify(option.defaultValue)}`,
|
|
344
|
+
);
|
|
290
345
|
}
|
|
291
346
|
}
|
|
292
347
|
// preset for boolean and negated are more for programmer than end user
|
|
@@ -315,17 +370,20 @@ class Help {
|
|
|
315
370
|
if (argument.argChoices) {
|
|
316
371
|
extraInfo.push(
|
|
317
372
|
// use stringify to match the display of the default value
|
|
318
|
-
`choices: ${argument.argChoices.map((choice) => JSON.stringify(choice)).join(', ')}
|
|
373
|
+
`choices: ${argument.argChoices.map((choice) => JSON.stringify(choice)).join(', ')}`,
|
|
374
|
+
);
|
|
319
375
|
}
|
|
320
376
|
if (argument.defaultValue !== undefined) {
|
|
321
|
-
extraInfo.push(
|
|
377
|
+
extraInfo.push(
|
|
378
|
+
`default: ${argument.defaultValueDescription || JSON.stringify(argument.defaultValue)}`,
|
|
379
|
+
);
|
|
322
380
|
}
|
|
323
381
|
if (extraInfo.length > 0) {
|
|
324
|
-
const
|
|
382
|
+
const extraDescription = `(${extraInfo.join(', ')})`;
|
|
325
383
|
if (argument.description) {
|
|
326
|
-
return `${argument.description} ${
|
|
384
|
+
return `${argument.description} ${extraDescription}`;
|
|
327
385
|
}
|
|
328
|
-
return
|
|
386
|
+
return extraDescription;
|
|
329
387
|
}
|
|
330
388
|
return argument.description;
|
|
331
389
|
}
|
|
@@ -340,65 +398,177 @@ class Help {
|
|
|
340
398
|
|
|
341
399
|
formatHelp(cmd, helper) {
|
|
342
400
|
const termWidth = helper.padWidth(cmd, helper);
|
|
343
|
-
const helpWidth = helper.helpWidth
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
if (description) {
|
|
348
|
-
const fullText = `${term.padEnd(termWidth + itemSeparatorWidth)}${description}`;
|
|
349
|
-
return helper.wrap(fullText, helpWidth - itemIndentWidth, termWidth + itemSeparatorWidth);
|
|
350
|
-
}
|
|
351
|
-
return term;
|
|
352
|
-
}
|
|
353
|
-
function formatList(textArray) {
|
|
354
|
-
return textArray.join('\n').replace(/^/gm, ' '.repeat(itemIndentWidth));
|
|
401
|
+
const helpWidth = helper.helpWidth ?? 80; // in case prepareContext() was not called
|
|
402
|
+
|
|
403
|
+
function callFormatItem(term, description) {
|
|
404
|
+
return helper.formatItem(term, termWidth, description, helper);
|
|
355
405
|
}
|
|
356
406
|
|
|
357
407
|
// Usage
|
|
358
|
-
let output = [
|
|
408
|
+
let output = [
|
|
409
|
+
`${helper.styleTitle('Usage:')} ${helper.styleUsage(helper.commandUsage(cmd))}`,
|
|
410
|
+
'',
|
|
411
|
+
];
|
|
359
412
|
|
|
360
413
|
// Description
|
|
361
414
|
const commandDescription = helper.commandDescription(cmd);
|
|
362
415
|
if (commandDescription.length > 0) {
|
|
363
|
-
output = output.concat([
|
|
416
|
+
output = output.concat([
|
|
417
|
+
helper.boxWrap(
|
|
418
|
+
helper.styleCommandDescription(commandDescription),
|
|
419
|
+
helpWidth,
|
|
420
|
+
),
|
|
421
|
+
'',
|
|
422
|
+
]);
|
|
364
423
|
}
|
|
365
424
|
|
|
366
425
|
// Arguments
|
|
367
426
|
const argumentList = helper.visibleArguments(cmd).map((argument) => {
|
|
368
|
-
return
|
|
427
|
+
return callFormatItem(
|
|
428
|
+
helper.styleArgumentTerm(helper.argumentTerm(argument)),
|
|
429
|
+
helper.styleArgumentDescription(helper.argumentDescription(argument)),
|
|
430
|
+
);
|
|
369
431
|
});
|
|
370
432
|
if (argumentList.length > 0) {
|
|
371
|
-
output = output.concat([
|
|
433
|
+
output = output.concat([
|
|
434
|
+
helper.styleTitle('Arguments:'),
|
|
435
|
+
...argumentList,
|
|
436
|
+
'',
|
|
437
|
+
]);
|
|
372
438
|
}
|
|
373
439
|
|
|
374
440
|
// Options
|
|
375
441
|
const optionList = helper.visibleOptions(cmd).map((option) => {
|
|
376
|
-
return
|
|
442
|
+
return callFormatItem(
|
|
443
|
+
helper.styleOptionTerm(helper.optionTerm(option)),
|
|
444
|
+
helper.styleOptionDescription(helper.optionDescription(option)),
|
|
445
|
+
);
|
|
377
446
|
});
|
|
378
447
|
if (optionList.length > 0) {
|
|
379
|
-
output = output.concat([
|
|
448
|
+
output = output.concat([
|
|
449
|
+
helper.styleTitle('Options:'),
|
|
450
|
+
...optionList,
|
|
451
|
+
'',
|
|
452
|
+
]);
|
|
380
453
|
}
|
|
381
454
|
|
|
382
|
-
if (
|
|
383
|
-
const globalOptionList = helper
|
|
384
|
-
|
|
385
|
-
|
|
455
|
+
if (helper.showGlobalOptions) {
|
|
456
|
+
const globalOptionList = helper
|
|
457
|
+
.visibleGlobalOptions(cmd)
|
|
458
|
+
.map((option) => {
|
|
459
|
+
return callFormatItem(
|
|
460
|
+
helper.styleOptionTerm(helper.optionTerm(option)),
|
|
461
|
+
helper.styleOptionDescription(helper.optionDescription(option)),
|
|
462
|
+
);
|
|
463
|
+
});
|
|
386
464
|
if (globalOptionList.length > 0) {
|
|
387
|
-
output = output.concat([
|
|
465
|
+
output = output.concat([
|
|
466
|
+
helper.styleTitle('Global Options:'),
|
|
467
|
+
...globalOptionList,
|
|
468
|
+
'',
|
|
469
|
+
]);
|
|
388
470
|
}
|
|
389
471
|
}
|
|
390
472
|
|
|
391
473
|
// Commands
|
|
392
474
|
const commandList = helper.visibleCommands(cmd).map((cmd) => {
|
|
393
|
-
return
|
|
475
|
+
return callFormatItem(
|
|
476
|
+
helper.styleSubcommandTerm(helper.subcommandTerm(cmd)),
|
|
477
|
+
helper.styleSubcommandDescription(helper.subcommandDescription(cmd)),
|
|
478
|
+
);
|
|
394
479
|
});
|
|
395
480
|
if (commandList.length > 0) {
|
|
396
|
-
output = output.concat([
|
|
481
|
+
output = output.concat([
|
|
482
|
+
helper.styleTitle('Commands:'),
|
|
483
|
+
...commandList,
|
|
484
|
+
'',
|
|
485
|
+
]);
|
|
397
486
|
}
|
|
398
487
|
|
|
399
488
|
return output.join('\n');
|
|
400
489
|
}
|
|
401
490
|
|
|
491
|
+
/**
|
|
492
|
+
* Return display width of string, ignoring ANSI escape sequences. Used in padding and wrapping calculations.
|
|
493
|
+
*
|
|
494
|
+
* @param {string} str
|
|
495
|
+
* @returns {number}
|
|
496
|
+
*/
|
|
497
|
+
displayWidth(str) {
|
|
498
|
+
return stripColor(str).length;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Style the title for displaying in the help. Called with 'Usage:', 'Options:', etc.
|
|
503
|
+
*
|
|
504
|
+
* @param {string} str
|
|
505
|
+
* @returns {string}
|
|
506
|
+
*/
|
|
507
|
+
styleTitle(str) {
|
|
508
|
+
return str;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
styleUsage(str) {
|
|
512
|
+
// Usage has lots of parts the user might like to color separately! Assume default usage string which is formed like:
|
|
513
|
+
// command subcommand [options] [command] <foo> [bar]
|
|
514
|
+
return str
|
|
515
|
+
.split(' ')
|
|
516
|
+
.map((word) => {
|
|
517
|
+
if (word === '[options]') return this.styleOptionText(word);
|
|
518
|
+
if (word === '[command]') return this.styleSubcommandText(word);
|
|
519
|
+
if (word[0] === '[' || word[0] === '<')
|
|
520
|
+
return this.styleArgumentText(word);
|
|
521
|
+
return this.styleCommandText(word); // Restrict to initial words?
|
|
522
|
+
})
|
|
523
|
+
.join(' ');
|
|
524
|
+
}
|
|
525
|
+
styleCommandDescription(str) {
|
|
526
|
+
return this.styleDescriptionText(str);
|
|
527
|
+
}
|
|
528
|
+
styleOptionDescription(str) {
|
|
529
|
+
return this.styleDescriptionText(str);
|
|
530
|
+
}
|
|
531
|
+
styleSubcommandDescription(str) {
|
|
532
|
+
return this.styleDescriptionText(str);
|
|
533
|
+
}
|
|
534
|
+
styleArgumentDescription(str) {
|
|
535
|
+
return this.styleDescriptionText(str);
|
|
536
|
+
}
|
|
537
|
+
styleDescriptionText(str) {
|
|
538
|
+
return str;
|
|
539
|
+
}
|
|
540
|
+
styleOptionTerm(str) {
|
|
541
|
+
return this.styleOptionText(str);
|
|
542
|
+
}
|
|
543
|
+
styleSubcommandTerm(str) {
|
|
544
|
+
// This is very like usage with lots of parts! Assume default string which is formed like:
|
|
545
|
+
// subcommand [options] <foo> [bar]
|
|
546
|
+
return str
|
|
547
|
+
.split(' ')
|
|
548
|
+
.map((word) => {
|
|
549
|
+
if (word === '[options]') return this.styleOptionText(word);
|
|
550
|
+
if (word[0] === '[' || word[0] === '<')
|
|
551
|
+
return this.styleArgumentText(word);
|
|
552
|
+
return this.styleSubcommandText(word); // Restrict to initial words?
|
|
553
|
+
})
|
|
554
|
+
.join(' ');
|
|
555
|
+
}
|
|
556
|
+
styleArgumentTerm(str) {
|
|
557
|
+
return this.styleArgumentText(str);
|
|
558
|
+
}
|
|
559
|
+
styleOptionText(str) {
|
|
560
|
+
return str;
|
|
561
|
+
}
|
|
562
|
+
styleArgumentText(str) {
|
|
563
|
+
return str;
|
|
564
|
+
}
|
|
565
|
+
styleSubcommandText(str) {
|
|
566
|
+
return str;
|
|
567
|
+
}
|
|
568
|
+
styleCommandText(str) {
|
|
569
|
+
return str;
|
|
570
|
+
}
|
|
571
|
+
|
|
402
572
|
/**
|
|
403
573
|
* Calculate the pad width from the maximum term length.
|
|
404
574
|
*
|
|
@@ -412,46 +582,128 @@ class Help {
|
|
|
412
582
|
helper.longestOptionTermLength(cmd, helper),
|
|
413
583
|
helper.longestGlobalOptionTermLength(cmd, helper),
|
|
414
584
|
helper.longestSubcommandTermLength(cmd, helper),
|
|
415
|
-
helper.longestArgumentTermLength(cmd, helper)
|
|
585
|
+
helper.longestArgumentTermLength(cmd, helper),
|
|
416
586
|
);
|
|
417
587
|
}
|
|
418
588
|
|
|
419
589
|
/**
|
|
420
|
-
*
|
|
421
|
-
* Do not wrap if insufficient room for wrapping (minColumnWidth), or string is manually formatted.
|
|
590
|
+
* Detect manually wrapped and indented strings by checking for line break followed by whitespace.
|
|
422
591
|
*
|
|
423
592
|
* @param {string} str
|
|
424
|
-
* @
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
593
|
+
* @returns {boolean}
|
|
594
|
+
*/
|
|
595
|
+
preformatted(str) {
|
|
596
|
+
return /\n[^\S\r\n]/.test(str);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/**
|
|
600
|
+
* Format the "item", which consists of a term and description. Pad the term and wrap the description, indenting the following lines.
|
|
601
|
+
*
|
|
602
|
+
* So "TTT", 5, "DDD DDDD DD DDD" might be formatted for this.helpWidth=17 like so:
|
|
603
|
+
* TTT DDD DDDD
|
|
604
|
+
* DD DDD
|
|
605
|
+
*
|
|
606
|
+
* @param {string} term
|
|
607
|
+
* @param {number} termWidth
|
|
608
|
+
* @param {string} description
|
|
609
|
+
* @param {Help} helper
|
|
610
|
+
* @returns {string}
|
|
611
|
+
*/
|
|
612
|
+
formatItem(term, termWidth, description, helper) {
|
|
613
|
+
const itemIndent = 2;
|
|
614
|
+
const itemIndentStr = ' '.repeat(itemIndent);
|
|
615
|
+
if (!description) return itemIndentStr + term;
|
|
616
|
+
|
|
617
|
+
// Pad the term out to a consistent width, so descriptions are aligned.
|
|
618
|
+
const paddedTerm = term.padEnd(
|
|
619
|
+
termWidth + term.length - helper.displayWidth(term),
|
|
620
|
+
);
|
|
621
|
+
|
|
622
|
+
// Format the description.
|
|
623
|
+
const spacerWidth = 2; // between term and description
|
|
624
|
+
const helpWidth = this.helpWidth ?? 80; // in case prepareContext() was not called
|
|
625
|
+
const remainingWidth = helpWidth - termWidth - spacerWidth - itemIndent;
|
|
626
|
+
let formattedDescription;
|
|
627
|
+
if (
|
|
628
|
+
remainingWidth < this.minWidthToWrap ||
|
|
629
|
+
helper.preformatted(description)
|
|
630
|
+
) {
|
|
631
|
+
formattedDescription = description;
|
|
632
|
+
} else {
|
|
633
|
+
const wrappedDescription = helper.boxWrap(description, remainingWidth);
|
|
634
|
+
formattedDescription = wrappedDescription.replace(
|
|
635
|
+
/\n/g,
|
|
636
|
+
'\n' + ' '.repeat(termWidth + spacerWidth),
|
|
637
|
+
);
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// Construct and overall indent.
|
|
641
|
+
return (
|
|
642
|
+
itemIndentStr +
|
|
643
|
+
paddedTerm +
|
|
644
|
+
' '.repeat(spacerWidth) +
|
|
645
|
+
formattedDescription.replace(/\n/g, `\n${itemIndentStr}`)
|
|
646
|
+
);
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* Wrap a string at whitespace, preserving existing line breaks.
|
|
651
|
+
* Wrapping is skipped if the width is less than `minWidthToWrap`.
|
|
428
652
|
*
|
|
653
|
+
* @param {string} str
|
|
654
|
+
* @param {number} width
|
|
655
|
+
* @returns {string}
|
|
429
656
|
*/
|
|
657
|
+
boxWrap(str, width) {
|
|
658
|
+
if (width < this.minWidthToWrap) return str;
|
|
659
|
+
|
|
660
|
+
const rawLines = str.split(/\r\n|\n/);
|
|
661
|
+
// split up text by whitespace
|
|
662
|
+
const chunkPattern = /[\s]*[^\s]+/g;
|
|
663
|
+
const wrappedLines = [];
|
|
664
|
+
rawLines.forEach((line) => {
|
|
665
|
+
const chunks = line.match(chunkPattern);
|
|
666
|
+
if (chunks === null) {
|
|
667
|
+
wrappedLines.push('');
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
430
670
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
return
|
|
451
|
-
if (line === '\n') return ''; // preserve empty lines
|
|
452
|
-
return ((i > 0) ? indentString : '') + line.trimEnd();
|
|
453
|
-
}).join('\n');
|
|
671
|
+
let sumChunks = [chunks.shift()];
|
|
672
|
+
let sumWidth = this.displayWidth(sumChunks[0]);
|
|
673
|
+
chunks.forEach((chunk) => {
|
|
674
|
+
const visibleWidth = this.displayWidth(chunk);
|
|
675
|
+
// Accumulate chunks while they fit into width.
|
|
676
|
+
if (sumWidth + visibleWidth <= width) {
|
|
677
|
+
sumChunks.push(chunk);
|
|
678
|
+
sumWidth += visibleWidth;
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
wrappedLines.push(sumChunks.join(''));
|
|
682
|
+
|
|
683
|
+
const nextChunk = chunk.trimStart(); // trim space at line break
|
|
684
|
+
sumChunks = [nextChunk];
|
|
685
|
+
sumWidth = this.displayWidth(nextChunk);
|
|
686
|
+
});
|
|
687
|
+
wrappedLines.push(sumChunks.join(''));
|
|
688
|
+
});
|
|
689
|
+
|
|
690
|
+
return wrappedLines.join('\n');
|
|
454
691
|
}
|
|
455
692
|
}
|
|
456
693
|
|
|
694
|
+
/**
|
|
695
|
+
* Strip style ANSI escape sequences from the string. In particular, SGR (Select Graphic Rendition) codes.
|
|
696
|
+
*
|
|
697
|
+
* @param {string} str
|
|
698
|
+
* @returns {string}
|
|
699
|
+
* @package
|
|
700
|
+
*/
|
|
701
|
+
|
|
702
|
+
function stripColor(str) {
|
|
703
|
+
// eslint-disable-next-line no-control-regex
|
|
704
|
+
const sgrPattern = /\x1b\[\d*(;\d*)*m/g;
|
|
705
|
+
return str.replace(sgrPattern, '');
|
|
706
|
+
}
|
|
707
|
+
|
|
457
708
|
exports.Help = Help;
|
|
709
|
+
exports.stripColor = stripColor;
|