1 | const { humanReadableArgName } = require('./argument.js');
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 | class Help {
|
13 | constructor() {
|
14 | this.helpWidth = undefined;
|
15 | this.sortSubcommands = false;
|
16 | this.sortOptions = false;
|
17 | this.showGlobalOptions = false;
|
18 | }
|
19 |
|
20 | |
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 | visibleCommands(cmd) {
|
28 | const visibleCommands = cmd.commands.filter((cmd) => !cmd._hidden);
|
29 | const helpCommand = cmd._getHelpCommand();
|
30 | if (helpCommand && !helpCommand._hidden) {
|
31 | visibleCommands.push(helpCommand);
|
32 | }
|
33 | if (this.sortSubcommands) {
|
34 | visibleCommands.sort((a, b) => {
|
35 |
|
36 | return a.name().localeCompare(b.name());
|
37 | });
|
38 | }
|
39 | return visibleCommands;
|
40 | }
|
41 |
|
42 | |
43 |
|
44 |
|
45 |
|
46 |
|
47 |
|
48 |
|
49 | compareOptions(a, b) {
|
50 | const getSortKey = (option) => {
|
51 |
|
52 | return option.short
|
53 | ? option.short.replace(/^-/, '')
|
54 | : option.long.replace(/^--/, '');
|
55 | };
|
56 | return getSortKey(a).localeCompare(getSortKey(b));
|
57 | }
|
58 |
|
59 | |
60 |
|
61 |
|
62 |
|
63 |
|
64 |
|
65 |
|
66 | visibleOptions(cmd) {
|
67 | const visibleOptions = cmd.options.filter((option) => !option.hidden);
|
68 |
|
69 | const helpOption = cmd._getHelpOption();
|
70 | if (helpOption && !helpOption.hidden) {
|
71 |
|
72 | const removeShort = helpOption.short && cmd._findOption(helpOption.short);
|
73 | const removeLong = helpOption.long && cmd._findOption(helpOption.long);
|
74 | if (!removeShort && !removeLong) {
|
75 | visibleOptions.push(helpOption);
|
76 | } else if (helpOption.long && !removeLong) {
|
77 | visibleOptions.push(
|
78 | cmd.createOption(helpOption.long, helpOption.description),
|
79 | );
|
80 | } else if (helpOption.short && !removeShort) {
|
81 | visibleOptions.push(
|
82 | cmd.createOption(helpOption.short, helpOption.description),
|
83 | );
|
84 | }
|
85 | }
|
86 | if (this.sortOptions) {
|
87 | visibleOptions.sort(this.compareOptions);
|
88 | }
|
89 | return visibleOptions;
|
90 | }
|
91 |
|
92 | |
93 |
|
94 |
|
95 |
|
96 |
|
97 |
|
98 |
|
99 | visibleGlobalOptions(cmd) {
|
100 | if (!this.showGlobalOptions) return [];
|
101 |
|
102 | const globalOptions = [];
|
103 | for (
|
104 | let ancestorCmd = cmd.parent;
|
105 | ancestorCmd;
|
106 | ancestorCmd = ancestorCmd.parent
|
107 | ) {
|
108 | const visibleOptions = ancestorCmd.options.filter(
|
109 | (option) => !option.hidden,
|
110 | );
|
111 | globalOptions.push(...visibleOptions);
|
112 | }
|
113 | if (this.sortOptions) {
|
114 | globalOptions.sort(this.compareOptions);
|
115 | }
|
116 | return globalOptions;
|
117 | }
|
118 |
|
119 | |
120 |
|
121 |
|
122 |
|
123 |
|
124 |
|
125 |
|
126 | visibleArguments(cmd) {
|
127 |
|
128 | if (cmd._argsDescription) {
|
129 | cmd.registeredArguments.forEach((argument) => {
|
130 | argument.description =
|
131 | argument.description || cmd._argsDescription[argument.name()] || '';
|
132 | });
|
133 | }
|
134 |
|
135 |
|
136 | if (cmd.registeredArguments.find((argument) => argument.description)) {
|
137 | return cmd.registeredArguments;
|
138 | }
|
139 | return [];
|
140 | }
|
141 |
|
142 | |
143 |
|
144 |
|
145 |
|
146 |
|
147 |
|
148 |
|
149 | subcommandTerm(cmd) {
|
150 |
|
151 | const args = cmd.registeredArguments
|
152 | .map((arg) => humanReadableArgName(arg))
|
153 | .join(' ');
|
154 | return (
|
155 | cmd._name +
|
156 | (cmd._aliases[0] ? '|' + cmd._aliases[0] : '') +
|
157 | (cmd.options.length ? ' [options]' : '') +
|
158 | (args ? ' ' + args : '')
|
159 | );
|
160 | }
|
161 |
|
162 | |
163 |
|
164 |
|
165 |
|
166 |
|
167 |
|
168 |
|
169 | optionTerm(option) {
|
170 | return option.flags;
|
171 | }
|
172 |
|
173 | |
174 |
|
175 |
|
176 |
|
177 |
|
178 |
|
179 |
|
180 | argumentTerm(argument) {
|
181 | return argument.name();
|
182 | }
|
183 |
|
184 | |
185 |
|
186 |
|
187 |
|
188 |
|
189 |
|
190 |
|
191 |
|
192 | longestSubcommandTermLength(cmd, helper) {
|
193 | return helper.visibleCommands(cmd).reduce((max, command) => {
|
194 | return Math.max(max, helper.subcommandTerm(command).length);
|
195 | }, 0);
|
196 | }
|
197 |
|
198 | |
199 |
|
200 |
|
201 |
|
202 |
|
203 |
|
204 |
|
205 |
|
206 | longestOptionTermLength(cmd, helper) {
|
207 | return helper.visibleOptions(cmd).reduce((max, option) => {
|
208 | return Math.max(max, helper.optionTerm(option).length);
|
209 | }, 0);
|
210 | }
|
211 |
|
212 | |
213 |
|
214 |
|
215 |
|
216 |
|
217 |
|
218 |
|
219 |
|
220 | longestGlobalOptionTermLength(cmd, helper) {
|
221 | return helper.visibleGlobalOptions(cmd).reduce((max, option) => {
|
222 | return Math.max(max, helper.optionTerm(option).length);
|
223 | }, 0);
|
224 | }
|
225 |
|
226 | |
227 |
|
228 |
|
229 |
|
230 |
|
231 |
|
232 |
|
233 |
|
234 | longestArgumentTermLength(cmd, helper) {
|
235 | return helper.visibleArguments(cmd).reduce((max, argument) => {
|
236 | return Math.max(max, helper.argumentTerm(argument).length);
|
237 | }, 0);
|
238 | }
|
239 |
|
240 | |
241 |
|
242 |
|
243 |
|
244 |
|
245 |
|
246 |
|
247 | commandUsage(cmd) {
|
248 |
|
249 | let cmdName = cmd._name;
|
250 | if (cmd._aliases[0]) {
|
251 | cmdName = cmdName + '|' + cmd._aliases[0];
|
252 | }
|
253 | let ancestorCmdNames = '';
|
254 | for (
|
255 | let ancestorCmd = cmd.parent;
|
256 | ancestorCmd;
|
257 | ancestorCmd = ancestorCmd.parent
|
258 | ) {
|
259 | ancestorCmdNames = ancestorCmd.name() + ' ' + ancestorCmdNames;
|
260 | }
|
261 | return ancestorCmdNames + cmdName + ' ' + cmd.usage();
|
262 | }
|
263 |
|
264 | |
265 |
|
266 |
|
267 |
|
268 |
|
269 |
|
270 |
|
271 | commandDescription(cmd) {
|
272 |
|
273 | return cmd.description();
|
274 | }
|
275 |
|
276 | |
277 |
|
278 |
|
279 |
|
280 |
|
281 |
|
282 |
|
283 |
|
284 | subcommandDescription(cmd) {
|
285 |
|
286 | return cmd.summary() || cmd.description();
|
287 | }
|
288 |
|
289 | |
290 |
|
291 |
|
292 |
|
293 |
|
294 |
|
295 |
|
296 | optionDescription(option) {
|
297 | const extraInfo = [];
|
298 |
|
299 | if (option.argChoices) {
|
300 | extraInfo.push(
|
301 |
|
302 | `choices: ${option.argChoices.map((choice) => JSON.stringify(choice)).join(', ')}`,
|
303 | );
|
304 | }
|
305 | if (option.defaultValue !== undefined) {
|
306 |
|
307 |
|
308 | const showDefault =
|
309 | option.required ||
|
310 | option.optional ||
|
311 | (option.isBoolean() && typeof option.defaultValue === 'boolean');
|
312 | if (showDefault) {
|
313 | extraInfo.push(
|
314 | `default: ${option.defaultValueDescription || JSON.stringify(option.defaultValue)}`,
|
315 | );
|
316 | }
|
317 | }
|
318 |
|
319 | if (option.presetArg !== undefined && option.optional) {
|
320 | extraInfo.push(`preset: ${JSON.stringify(option.presetArg)}`);
|
321 | }
|
322 | if (option.envVar !== undefined) {
|
323 | extraInfo.push(`env: ${option.envVar}`);
|
324 | }
|
325 | if (extraInfo.length > 0) {
|
326 | return `${option.description} (${extraInfo.join(', ')})`;
|
327 | }
|
328 |
|
329 | return option.description;
|
330 | }
|
331 |
|
332 | |
333 |
|
334 |
|
335 |
|
336 |
|
337 |
|
338 |
|
339 | argumentDescription(argument) {
|
340 | const extraInfo = [];
|
341 | if (argument.argChoices) {
|
342 | extraInfo.push(
|
343 |
|
344 | `choices: ${argument.argChoices.map((choice) => JSON.stringify(choice)).join(', ')}`,
|
345 | );
|
346 | }
|
347 | if (argument.defaultValue !== undefined) {
|
348 | extraInfo.push(
|
349 | `default: ${argument.defaultValueDescription || JSON.stringify(argument.defaultValue)}`,
|
350 | );
|
351 | }
|
352 | if (extraInfo.length > 0) {
|
353 | const extraDescripton = `(${extraInfo.join(', ')})`;
|
354 | if (argument.description) {
|
355 | return `${argument.description} ${extraDescripton}`;
|
356 | }
|
357 | return extraDescripton;
|
358 | }
|
359 | return argument.description;
|
360 | }
|
361 |
|
362 | |
363 |
|
364 |
|
365 |
|
366 |
|
367 |
|
368 |
|
369 |
|
370 | formatHelp(cmd, helper) {
|
371 | const termWidth = helper.padWidth(cmd, helper);
|
372 | const helpWidth = helper.helpWidth || 80;
|
373 | const itemIndentWidth = 2;
|
374 | const itemSeparatorWidth = 2;
|
375 | function formatItem(term, description) {
|
376 | if (description) {
|
377 | const fullText = `${term.padEnd(termWidth + itemSeparatorWidth)}${description}`;
|
378 | return helper.wrap(
|
379 | fullText,
|
380 | helpWidth - itemIndentWidth,
|
381 | termWidth + itemSeparatorWidth,
|
382 | );
|
383 | }
|
384 | return term;
|
385 | }
|
386 | function formatList(textArray) {
|
387 | return textArray.join('\n').replace(/^/gm, ' '.repeat(itemIndentWidth));
|
388 | }
|
389 |
|
390 |
|
391 | let output = [`Usage: ${helper.commandUsage(cmd)}`, ''];
|
392 |
|
393 |
|
394 | const commandDescription = helper.commandDescription(cmd);
|
395 | if (commandDescription.length > 0) {
|
396 | output = output.concat([
|
397 | helper.wrap(commandDescription, helpWidth, 0),
|
398 | '',
|
399 | ]);
|
400 | }
|
401 |
|
402 |
|
403 | const argumentList = helper.visibleArguments(cmd).map((argument) => {
|
404 | return formatItem(
|
405 | helper.argumentTerm(argument),
|
406 | helper.argumentDescription(argument),
|
407 | );
|
408 | });
|
409 | if (argumentList.length > 0) {
|
410 | output = output.concat(['Arguments:', formatList(argumentList), '']);
|
411 | }
|
412 |
|
413 |
|
414 | const optionList = helper.visibleOptions(cmd).map((option) => {
|
415 | return formatItem(
|
416 | helper.optionTerm(option),
|
417 | helper.optionDescription(option),
|
418 | );
|
419 | });
|
420 | if (optionList.length > 0) {
|
421 | output = output.concat(['Options:', formatList(optionList), '']);
|
422 | }
|
423 |
|
424 | if (this.showGlobalOptions) {
|
425 | const globalOptionList = helper
|
426 | .visibleGlobalOptions(cmd)
|
427 | .map((option) => {
|
428 | return formatItem(
|
429 | helper.optionTerm(option),
|
430 | helper.optionDescription(option),
|
431 | );
|
432 | });
|
433 | if (globalOptionList.length > 0) {
|
434 | output = output.concat([
|
435 | 'Global Options:',
|
436 | formatList(globalOptionList),
|
437 | '',
|
438 | ]);
|
439 | }
|
440 | }
|
441 |
|
442 |
|
443 | const commandList = helper.visibleCommands(cmd).map((cmd) => {
|
444 | return formatItem(
|
445 | helper.subcommandTerm(cmd),
|
446 | helper.subcommandDescription(cmd),
|
447 | );
|
448 | });
|
449 | if (commandList.length > 0) {
|
450 | output = output.concat(['Commands:', formatList(commandList), '']);
|
451 | }
|
452 |
|
453 | return output.join('\n');
|
454 | }
|
455 |
|
456 | |
457 |
|
458 |
|
459 |
|
460 |
|
461 |
|
462 |
|
463 |
|
464 | padWidth(cmd, helper) {
|
465 | return Math.max(
|
466 | helper.longestOptionTermLength(cmd, helper),
|
467 | helper.longestGlobalOptionTermLength(cmd, helper),
|
468 | helper.longestSubcommandTermLength(cmd, helper),
|
469 | helper.longestArgumentTermLength(cmd, helper),
|
470 | );
|
471 | }
|
472 |
|
473 | |
474 |
|
475 |
|
476 |
|
477 |
|
478 |
|
479 |
|
480 |
|
481 |
|
482 |
|
483 |
|
484 |
|
485 | wrap(str, width, indent, minColumnWidth = 40) {
|
486 |
|
487 | const indents =
|
488 | ' \\f\\t\\v\u00a0\u1680\u2000-\u200a\u202f\u205f\u3000\ufeff';
|
489 |
|
490 | const manualIndent = new RegExp(`[\\n][${indents}]+`);
|
491 | if (str.match(manualIndent)) return str;
|
492 |
|
493 | const columnWidth = width - indent;
|
494 | if (columnWidth < minColumnWidth) return str;
|
495 |
|
496 | const leadingStr = str.slice(0, indent);
|
497 | const columnText = str.slice(indent).replace('\r\n', '\n');
|
498 | const indentString = ' '.repeat(indent);
|
499 | const zeroWidthSpace = '\u200B';
|
500 | const breaks = `\\s${zeroWidthSpace}`;
|
501 |
|
502 |
|
503 | const regex = new RegExp(
|
504 | `\n|.{1,${columnWidth - 1}}([${breaks}]|$)|[^${breaks}]+?([${breaks}]|$)`,
|
505 | 'g',
|
506 | );
|
507 | const lines = columnText.match(regex) || [];
|
508 | return (
|
509 | leadingStr +
|
510 | lines
|
511 | .map((line, i) => {
|
512 | if (line === '\n') return '';
|
513 | return (i > 0 ? indentString : '') + line.trimEnd();
|
514 | })
|
515 | .join('\n')
|
516 | );
|
517 | }
|
518 | }
|
519 |
|
520 | exports.Help = Help;
|