1 | const { humanReadableArgName } = require('./argument.js');
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 | class Help {
|
15 | constructor() {
|
16 | this.helpWidth = undefined;
|
17 | this.sortSubcommands = false;
|
18 | this.sortOptions = false;
|
19 | }
|
20 |
|
21 | |
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 | visibleCommands(cmd) {
|
29 | const visibleCommands = cmd.commands.filter(cmd => !cmd._hidden);
|
30 | if (cmd._hasImplicitHelpCommand()) {
|
31 |
|
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 |
|
42 | return a.name().localeCompare(b.name());
|
43 | });
|
44 | }
|
45 | return visibleCommands;
|
46 | }
|
47 |
|
48 | |
49 |
|
50 |
|
51 |
|
52 |
|
53 |
|
54 |
|
55 | visibleOptions(cmd) {
|
56 | const visibleOptions = cmd.options.filter((option) => !option.hidden);
|
57 |
|
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 |
|
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 |
|
85 |
|
86 |
|
87 |
|
88 |
|
89 |
|
90 | visibleArguments(cmd) {
|
91 |
|
92 | if (cmd._argsDescription) {
|
93 | cmd._args.forEach(argument => {
|
94 | argument.description = argument.description || cmd._argsDescription[argument.name()] || '';
|
95 | });
|
96 | }
|
97 |
|
98 |
|
99 | if (cmd._args.find(argument => argument.description)) {
|
100 | return cmd._args;
|
101 | }
|
102 | return [];
|
103 | }
|
104 |
|
105 | |
106 |
|
107 |
|
108 |
|
109 |
|
110 |
|
111 |
|
112 | subcommandTerm(cmd) {
|
113 |
|
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]' : '') +
|
118 | (args ? ' ' + args : '');
|
119 | }
|
120 |
|
121 | |
122 |
|
123 |
|
124 |
|
125 |
|
126 |
|
127 |
|
128 | optionTerm(option) {
|
129 | return option.flags;
|
130 | }
|
131 |
|
132 | |
133 |
|
134 |
|
135 |
|
136 |
|
137 |
|
138 |
|
139 | argumentTerm(argument) {
|
140 | return argument.name();
|
141 | }
|
142 |
|
143 | |
144 |
|
145 |
|
146 |
|
147 |
|
148 |
|
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 |
|
159 |
|
160 |
|
161 |
|
162 |
|
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 |
|
173 |
|
174 |
|
175 |
|
176 |
|
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 |
|
187 |
|
188 |
|
189 |
|
190 |
|
191 |
|
192 | commandUsage(cmd) {
|
193 |
|
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 |
|
207 |
|
208 |
|
209 |
|
210 |
|
211 |
|
212 | commandDescription(cmd) {
|
213 |
|
214 | return cmd.description();
|
215 | }
|
216 |
|
217 | |
218 |
|
219 |
|
220 |
|
221 |
|
222 |
|
223 |
|
224 |
|
225 | subcommandDescription(cmd) {
|
226 |
|
227 | return cmd.summary() || cmd.description();
|
228 | }
|
229 |
|
230 | |
231 |
|
232 |
|
233 |
|
234 |
|
235 |
|
236 |
|
237 | optionDescription(option) {
|
238 | const extraInfo = [];
|
239 |
|
240 | if (option.argChoices) {
|
241 | extraInfo.push(
|
242 |
|
243 | `choices: ${option.argChoices.map((choice) => JSON.stringify(choice)).join(', ')}`);
|
244 | }
|
245 | if (option.defaultValue !== undefined) {
|
246 |
|
247 |
|
248 | const showDefault = option.required || option.optional ||
|
249 | (option.isBoolean() && typeof option.defaultValue === 'boolean');
|
250 | if (showDefault) {
|
251 | extraInfo.push(`default: ${option.defaultValueDescription || JSON.stringify(option.defaultValue)}`);
|
252 | }
|
253 | }
|
254 |
|
255 | if (option.presetArg !== undefined && option.optional) {
|
256 | extraInfo.push(`preset: ${JSON.stringify(option.presetArg)}`);
|
257 | }
|
258 | if (option.envVar !== undefined) {
|
259 | extraInfo.push(`env: ${option.envVar}`);
|
260 | }
|
261 | if (extraInfo.length > 0) {
|
262 | return `${option.description} (${extraInfo.join(', ')})`;
|
263 | }
|
264 |
|
265 | return option.description;
|
266 | }
|
267 |
|
268 | |
269 |
|
270 |
|
271 |
|
272 |
|
273 |
|
274 |
|
275 | argumentDescription(argument) {
|
276 | const extraInfo = [];
|
277 | if (argument.argChoices) {
|
278 | extraInfo.push(
|
279 |
|
280 | `choices: ${argument.argChoices.map((choice) => JSON.stringify(choice)).join(', ')}`);
|
281 | }
|
282 | if (argument.defaultValue !== undefined) {
|
283 | extraInfo.push(`default: ${argument.defaultValueDescription || JSON.stringify(argument.defaultValue)}`);
|
284 | }
|
285 | if (extraInfo.length > 0) {
|
286 | const extraDescripton = `(${extraInfo.join(', ')})`;
|
287 | if (argument.description) {
|
288 | return `${argument.description} ${extraDescripton}`;
|
289 | }
|
290 | return extraDescripton;
|
291 | }
|
292 | return argument.description;
|
293 | }
|
294 |
|
295 | |
296 |
|
297 |
|
298 |
|
299 |
|
300 |
|
301 |
|
302 |
|
303 | formatHelp(cmd, helper) {
|
304 | const termWidth = helper.padWidth(cmd, helper);
|
305 | const helpWidth = helper.helpWidth || 80;
|
306 | const itemIndentWidth = 2;
|
307 | const itemSeparatorWidth = 2;
|
308 | function formatItem(term, description) {
|
309 | if (description) {
|
310 | const fullText = `${term.padEnd(termWidth + itemSeparatorWidth)}${description}`;
|
311 | return helper.wrap(fullText, helpWidth - itemIndentWidth, termWidth + itemSeparatorWidth);
|
312 | }
|
313 | return term;
|
314 | }
|
315 | function formatList(textArray) {
|
316 | return textArray.join('\n').replace(/^/gm, ' '.repeat(itemIndentWidth));
|
317 | }
|
318 |
|
319 |
|
320 | let output = [`Usage: ${helper.commandUsage(cmd)}`, ''];
|
321 |
|
322 |
|
323 | const commandDescription = helper.commandDescription(cmd);
|
324 | if (commandDescription.length > 0) {
|
325 | output = output.concat([commandDescription, '']);
|
326 | }
|
327 |
|
328 |
|
329 | const argumentList = helper.visibleArguments(cmd).map((argument) => {
|
330 | return formatItem(helper.argumentTerm(argument), helper.argumentDescription(argument));
|
331 | });
|
332 | if (argumentList.length > 0) {
|
333 | output = output.concat(['Arguments:', formatList(argumentList), '']);
|
334 | }
|
335 |
|
336 |
|
337 | const optionList = helper.visibleOptions(cmd).map((option) => {
|
338 | return formatItem(helper.optionTerm(option), helper.optionDescription(option));
|
339 | });
|
340 | if (optionList.length > 0) {
|
341 | output = output.concat(['Options:', formatList(optionList), '']);
|
342 | }
|
343 |
|
344 |
|
345 | const commandList = helper.visibleCommands(cmd).map((cmd) => {
|
346 | return formatItem(helper.subcommandTerm(cmd), helper.subcommandDescription(cmd));
|
347 | });
|
348 | if (commandList.length > 0) {
|
349 | output = output.concat(['Commands:', formatList(commandList), '']);
|
350 | }
|
351 |
|
352 | return output.join('\n');
|
353 | }
|
354 |
|
355 | |
356 |
|
357 |
|
358 |
|
359 |
|
360 |
|
361 |
|
362 |
|
363 | padWidth(cmd, helper) {
|
364 | return Math.max(
|
365 | helper.longestOptionTermLength(cmd, helper),
|
366 | helper.longestSubcommandTermLength(cmd, helper),
|
367 | helper.longestArgumentTermLength(cmd, helper)
|
368 | );
|
369 | }
|
370 |
|
371 | |
372 |
|
373 |
|
374 |
|
375 |
|
376 |
|
377 |
|
378 |
|
379 |
|
380 |
|
381 |
|
382 |
|
383 | wrap(str, width, indent, minColumnWidth = 40) {
|
384 |
|
385 |
|
386 | if (str.match(/[\n]\s+/)) return str;
|
387 |
|
388 | const columnWidth = width - indent;
|
389 | if (columnWidth < minColumnWidth) return str;
|
390 |
|
391 | const leadingStr = str.slice(0, indent);
|
392 | const columnText = str.slice(indent);
|
393 |
|
394 | const indentString = ' '.repeat(indent);
|
395 | const regex = new RegExp('.{1,' + (columnWidth - 1) + '}([\\s\u200B]|$)|[^\\s\u200B]+?([\\s\u200B]|$)', 'g');
|
396 | const lines = columnText.match(regex) || [];
|
397 | return leadingStr + lines.map((line, i) => {
|
398 | if (line.slice(-1) === '\n') {
|
399 | line = line.slice(0, line.length - 1);
|
400 | }
|
401 | return ((i > 0) ? indentString : '') + line.trimRight();
|
402 | }).join('\n');
|
403 | }
|
404 | }
|
405 |
|
406 | exports.Help = Help;
|