source: frontend/node_modules/commander/lib/help.js

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 12 days ago

Fix frontend appearance

  • Property mode set to 100644
File size: 11.6 KB
RevLine 
[9af201e]1const { humanReadableArgName } = require('./argument.js');
2
3/**
4 * TypeScript import types for JSDoc, used by Visual Studio Code IntelliSense and `npm run typescript-checkJS`
5 * https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html#import-types
6 * @typedef { import("./argument.js").Argument } Argument
7 * @typedef { import("./command.js").Command } Command
8 * @typedef { import("./option.js").Option } Option
9 */
10
11// @ts-check
12
13// Although this is a class, methods are static in style to allow override using subclass or just functions.
14class Help {
15 constructor() {
16 this.helpWidth = undefined;
17 this.sortSubcommands = false;
18 this.sortOptions = false;
19 }
20
21 /**
22 * Get an array of the visible subcommands. Includes a placeholder for the implicit help command, if there is one.
23 *
24 * @param {Command} cmd
25 * @returns {Command[]}
26 */
27
28 visibleCommands(cmd) {
29 const visibleCommands = cmd.commands.filter(cmd => !cmd._hidden);
30 if (cmd._hasImplicitHelpCommand()) {
31 // Create a command matching the implicit help command.
32 const [, helpName, helpArgs] = cmd._helpCommandnameAndArgs.match(/([^ ]+) *(.*)/);
33 const helpCommand = cmd.createCommand(helpName)
34 .helpOption(false);
35 helpCommand.description(cmd._helpCommandDescription);
36 if (helpArgs) helpCommand.arguments(helpArgs);
37 visibleCommands.push(helpCommand);
38 }
39 if (this.sortSubcommands) {
40 visibleCommands.sort((a, b) => {
41 // @ts-ignore: overloaded return type
42 return a.name().localeCompare(b.name());
43 });
44 }
45 return visibleCommands;
46 }
47
48 /**
49 * Get an array of the visible options. Includes a placeholder for the implicit help option, if there is one.
50 *
51 * @param {Command} cmd
52 * @returns {Option[]}
53 */
54
55 visibleOptions(cmd) {
56 const visibleOptions = cmd.options.filter((option) => !option.hidden);
57 // Implicit help
58 const showShortHelpFlag = cmd._hasHelpOption && cmd._helpShortFlag && !cmd._findOption(cmd._helpShortFlag);
59 const showLongHelpFlag = cmd._hasHelpOption && !cmd._findOption(cmd._helpLongFlag);
60 if (showShortHelpFlag || showLongHelpFlag) {
61 let helpOption;
62 if (!showShortHelpFlag) {
63 helpOption = cmd.createOption(cmd._helpLongFlag, cmd._helpDescription);
64 } else if (!showLongHelpFlag) {
65 helpOption = cmd.createOption(cmd._helpShortFlag, cmd._helpDescription);
66 } else {
67 helpOption = cmd.createOption(cmd._helpFlags, cmd._helpDescription);
68 }
69 visibleOptions.push(helpOption);
70 }
71 if (this.sortOptions) {
72 const getSortKey = (option) => {
73 // WYSIWYG for order displayed in help with short before long, no special handling for negated.
74 return option.short ? option.short.replace(/^-/, '') : option.long.replace(/^--/, '');
75 };
76 visibleOptions.sort((a, b) => {
77 return getSortKey(a).localeCompare(getSortKey(b));
78 });
79 }
80 return visibleOptions;
81 }
82
83 /**
84 * Get an array of the arguments if any have a description.
85 *
86 * @param {Command} cmd
87 * @returns {Argument[]}
88 */
89
90 visibleArguments(cmd) {
91 // Side effect! Apply the legacy descriptions before the arguments are displayed.
92 if (cmd._argsDescription) {
93 cmd._args.forEach(argument => {
94 argument.description = argument.description || cmd._argsDescription[argument.name()] || '';
95 });
96 }
97
98 // If there are any arguments with a description then return all the arguments.
99 if (cmd._args.find(argument => argument.description)) {
100 return cmd._args;
101 };
102 return [];
103 }
104
105 /**
106 * Get the command term to show in the list of subcommands.
107 *
108 * @param {Command} cmd
109 * @returns {string}
110 */
111
112 subcommandTerm(cmd) {
113 // Legacy. Ignores custom usage string, and nested commands.
114 const args = cmd._args.map(arg => humanReadableArgName(arg)).join(' ');
115 return cmd._name +
116 (cmd._aliases[0] ? '|' + cmd._aliases[0] : '') +
117 (cmd.options.length ? ' [options]' : '') + // simplistic check for non-help option
118 (args ? ' ' + args : '');
119 }
120
121 /**
122 * Get the option term to show in the list of options.
123 *
124 * @param {Option} option
125 * @returns {string}
126 */
127
128 optionTerm(option) {
129 return option.flags;
130 }
131
132 /**
133 * Get the argument term to show in the list of arguments.
134 *
135 * @param {Argument} argument
136 * @returns {string}
137 */
138
139 argumentTerm(argument) {
140 return argument.name();
141 }
142
143 /**
144 * Get the longest command term length.
145 *
146 * @param {Command} cmd
147 * @param {Help} helper
148 * @returns {number}
149 */
150
151 longestSubcommandTermLength(cmd, helper) {
152 return helper.visibleCommands(cmd).reduce((max, command) => {
153 return Math.max(max, helper.subcommandTerm(command).length);
154 }, 0);
155 };
156
157 /**
158 * Get the longest option term length.
159 *
160 * @param {Command} cmd
161 * @param {Help} helper
162 * @returns {number}
163 */
164
165 longestOptionTermLength(cmd, helper) {
166 return helper.visibleOptions(cmd).reduce((max, option) => {
167 return Math.max(max, helper.optionTerm(option).length);
168 }, 0);
169 };
170
171 /**
172 * Get the longest argument term length.
173 *
174 * @param {Command} cmd
175 * @param {Help} helper
176 * @returns {number}
177 */
178
179 longestArgumentTermLength(cmd, helper) {
180 return helper.visibleArguments(cmd).reduce((max, argument) => {
181 return Math.max(max, helper.argumentTerm(argument).length);
182 }, 0);
183 };
184
185 /**
186 * Get the command usage to be displayed at the top of the built-in help.
187 *
188 * @param {Command} cmd
189 * @returns {string}
190 */
191
192 commandUsage(cmd) {
193 // Usage
194 let cmdName = cmd._name;
195 if (cmd._aliases[0]) {
196 cmdName = cmdName + '|' + cmd._aliases[0];
197 }
198 let parentCmdNames = '';
199 for (let parentCmd = cmd.parent; parentCmd; parentCmd = parentCmd.parent) {
200 parentCmdNames = parentCmd.name() + ' ' + parentCmdNames;
201 }
202 return parentCmdNames + cmdName + ' ' + cmd.usage();
203 }
204
205 /**
206 * Get the description for the command.
207 *
208 * @param {Command} cmd
209 * @returns {string}
210 */
211
212 commandDescription(cmd) {
213 // @ts-ignore: overloaded return type
214 return cmd.description();
215 }
216
217 /**
218 * Get the command description to show in the list of subcommands.
219 *
220 * @param {Command} cmd
221 * @returns {string}
222 */
223
224 subcommandDescription(cmd) {
225 // @ts-ignore: overloaded return type
226 return cmd.description();
227 }
228
229 /**
230 * Get the option description to show in the list of options.
231 *
232 * @param {Option} option
233 * @return {string}
234 */
235
236 optionDescription(option) {
237 const extraInfo = [];
238 // Some of these do not make sense for negated boolean and suppress for backwards compatibility.
239
240 if (option.argChoices && !option.negate) {
241 extraInfo.push(
242 // use stringify to match the display of the default value
243 `choices: ${option.argChoices.map((choice) => JSON.stringify(choice)).join(', ')}`);
244 }
245 if (option.defaultValue !== undefined && !option.negate) {
246 extraInfo.push(`default: ${option.defaultValueDescription || JSON.stringify(option.defaultValue)}`);
247 }
248 if (option.envVar !== undefined) {
249 extraInfo.push(`env: ${option.envVar}`);
250 }
251 if (extraInfo.length > 0) {
252 return `${option.description} (${extraInfo.join(', ')})`;
253 }
254
255 return option.description;
256 };
257
258 /**
259 * Get the argument description to show in the list of arguments.
260 *
261 * @param {Argument} argument
262 * @return {string}
263 */
264
265 argumentDescription(argument) {
266 const extraInfo = [];
267 if (argument.argChoices) {
268 extraInfo.push(
269 // use stringify to match the display of the default value
270 `choices: ${argument.argChoices.map((choice) => JSON.stringify(choice)).join(', ')}`);
271 }
272 if (argument.defaultValue !== undefined) {
273 extraInfo.push(`default: ${argument.defaultValueDescription || JSON.stringify(argument.defaultValue)}`);
274 }
275 if (extraInfo.length > 0) {
276 const extraDescripton = `(${extraInfo.join(', ')})`;
277 if (argument.description) {
278 return `${argument.description} ${extraDescripton}`;
279 }
280 return extraDescripton;
281 }
282 return argument.description;
283 }
284
285 /**
286 * Generate the built-in help text.
287 *
288 * @param {Command} cmd
289 * @param {Help} helper
290 * @returns {string}
291 */
292
293 formatHelp(cmd, helper) {
294 const termWidth = helper.padWidth(cmd, helper);
295 const helpWidth = helper.helpWidth || 80;
296 const itemIndentWidth = 2;
297 const itemSeparatorWidth = 2; // between term and description
298 function formatItem(term, description) {
299 if (description) {
300 const fullText = `${term.padEnd(termWidth + itemSeparatorWidth)}${description}`;
301 return helper.wrap(fullText, helpWidth - itemIndentWidth, termWidth + itemSeparatorWidth);
302 }
303 return term;
304 };
305 function formatList(textArray) {
306 return textArray.join('\n').replace(/^/gm, ' '.repeat(itemIndentWidth));
307 }
308
309 // Usage
310 let output = [`Usage: ${helper.commandUsage(cmd)}`, ''];
311
312 // Description
313 const commandDescription = helper.commandDescription(cmd);
314 if (commandDescription.length > 0) {
315 output = output.concat([commandDescription, '']);
316 }
317
318 // Arguments
319 const argumentList = helper.visibleArguments(cmd).map((argument) => {
320 return formatItem(helper.argumentTerm(argument), helper.argumentDescription(argument));
321 });
322 if (argumentList.length > 0) {
323 output = output.concat(['Arguments:', formatList(argumentList), '']);
324 }
325
326 // Options
327 const optionList = helper.visibleOptions(cmd).map((option) => {
328 return formatItem(helper.optionTerm(option), helper.optionDescription(option));
329 });
330 if (optionList.length > 0) {
331 output = output.concat(['Options:', formatList(optionList), '']);
332 }
333
334 // Commands
335 const commandList = helper.visibleCommands(cmd).map((cmd) => {
336 return formatItem(helper.subcommandTerm(cmd), helper.subcommandDescription(cmd));
337 });
338 if (commandList.length > 0) {
339 output = output.concat(['Commands:', formatList(commandList), '']);
340 }
341
342 return output.join('\n');
343 }
344
345 /**
346 * Calculate the pad width from the maximum term length.
347 *
348 * @param {Command} cmd
349 * @param {Help} helper
350 * @returns {number}
351 */
352
353 padWidth(cmd, helper) {
354 return Math.max(
355 helper.longestOptionTermLength(cmd, helper),
356 helper.longestSubcommandTermLength(cmd, helper),
357 helper.longestArgumentTermLength(cmd, helper)
358 );
359 };
360
361 /**
362 * Wrap the given string to width characters per line, with lines after the first indented.
363 * Do not wrap if insufficient room for wrapping (minColumnWidth), or string is manually formatted.
364 *
365 * @param {string} str
366 * @param {number} width
367 * @param {number} indent
368 * @param {number} [minColumnWidth=40]
369 * @return {string}
370 *
371 */
372
373 wrap(str, width, indent, minColumnWidth = 40) {
374 // Detect manually wrapped and indented strings by searching for line breaks
375 // followed by multiple spaces/tabs.
376 if (str.match(/[\n]\s+/)) return str;
377 // Do not wrap if not enough room for a wrapped column of text (as could end up with a word per line).
378 const columnWidth = width - indent;
379 if (columnWidth < minColumnWidth) return str;
380
381 const leadingStr = str.substr(0, indent);
382 const columnText = str.substr(indent);
383
384 const indentString = ' '.repeat(indent);
385 const regex = new RegExp('.{1,' + (columnWidth - 1) + '}([\\s\u200B]|$)|[^\\s\u200B]+?([\\s\u200B]|$)', 'g');
386 const lines = columnText.match(regex) || [];
387 return leadingStr + lines.map((line, i) => {
388 if (line.slice(-1) === '\n') {
389 line = line.slice(0, line.length - 1);
390 }
391 return ((i > 0) ? indentString : '') + line.trimRight();
392 }).join('\n');
393 }
394}
395
396exports.Help = Help;
Note: See TracBrowser for help on using the repository browser.