Initial commit
[yaffs-website] / node_modules / argparse / lib / help / formatter.js
1 /**
2  * class HelpFormatter
3  *
4  * Formatter for generating usage messages and argument help strings. Only the
5  * name of this class is considered a public API. All the methods provided by
6  * the class are considered an implementation detail.
7  *
8  * Do not call in your code, use this class only for inherits your own forvatter
9  *
10  * ToDo add [additonal formatters][1]
11  *
12  * [1]:http://docs.python.org/dev/library/argparse.html#formatter-class
13  **/
14 'use strict';
15
16 var sprintf = require('sprintf-js').sprintf;
17
18 // Constants
19 var c = require('../const');
20
21 var $$ = require('../utils');
22
23
24 /*:nodoc:* internal
25  * new Support(parent, heding)
26  * - parent (object): parent section
27  * - heading (string): header string
28  *
29  **/
30 function Section(parent, heading) {
31   this._parent = parent;
32   this._heading = heading;
33   this._items = [];
34 }
35
36 /*:nodoc:* internal
37  * Section#addItem(callback) -> Void
38  * - callback (array): tuple with function and args
39  *
40  * Add function for single element
41  **/
42 Section.prototype.addItem = function (callback) {
43   this._items.push(callback);
44 };
45
46 /*:nodoc:* internal
47  * Section#formatHelp(formatter) -> string
48  * - formatter (HelpFormatter): current formatter
49  *
50  * Form help section string
51  *
52  **/
53 Section.prototype.formatHelp = function (formatter) {
54   var itemHelp, heading;
55
56   // format the indented section
57   if (this._parent) {
58     formatter._indent();
59   }
60
61   itemHelp = this._items.map(function (item) {
62     var obj, func, args;
63
64     obj = formatter;
65     func = item[0];
66     args = item[1];
67     return func.apply(obj, args);
68   });
69   itemHelp = formatter._joinParts(itemHelp);
70
71   if (this._parent) {
72     formatter._dedent();
73   }
74
75   // return nothing if the section was empty
76   if (!itemHelp) {
77     return '';
78   }
79
80   // add the heading if the section was non-empty
81   heading = '';
82   if (this._heading && this._heading !== c.SUPPRESS) {
83     var currentIndent = formatter.currentIndent;
84     heading = $$.repeat(' ', currentIndent) + this._heading + ':' + c.EOL;
85   }
86
87   // join the section-initialize newline, the heading and the help
88   return formatter._joinParts([ c.EOL, heading, itemHelp, c.EOL ]);
89 };
90
91 /**
92  * new HelpFormatter(options)
93  *
94  * #### Options:
95  * - `prog`: program name
96  * - `indentIncriment`: indent step, default value 2
97  * - `maxHelpPosition`: max help position, default value = 24
98  * - `width`: line width
99  *
100  **/
101 var HelpFormatter = module.exports = function HelpFormatter(options) {
102   options = options || {};
103
104   this._prog = options.prog;
105
106   this._maxHelpPosition = options.maxHelpPosition || 24;
107   this._width = (options.width || ((process.env.COLUMNS || 80) - 2));
108
109   this._currentIndent = 0;
110   this._indentIncriment = options.indentIncriment || 2;
111   this._level = 0;
112   this._actionMaxLength = 0;
113
114   this._rootSection = new Section(null);
115   this._currentSection = this._rootSection;
116
117   this._whitespaceMatcher = new RegExp('\\s+', 'g');
118   this._longBreakMatcher = new RegExp(c.EOL + c.EOL + c.EOL + '+', 'g');
119 };
120
121 HelpFormatter.prototype._indent = function () {
122   this._currentIndent += this._indentIncriment;
123   this._level += 1;
124 };
125
126 HelpFormatter.prototype._dedent = function () {
127   this._currentIndent -= this._indentIncriment;
128   this._level -= 1;
129   if (this._currentIndent < 0) {
130     throw new Error('Indent decreased below 0.');
131   }
132 };
133
134 HelpFormatter.prototype._addItem = function (func, args) {
135   this._currentSection.addItem([ func, args ]);
136 };
137
138 //
139 // Message building methods
140 //
141
142 /**
143  * HelpFormatter#startSection(heading) -> Void
144  * - heading (string): header string
145  *
146  * Start new help section
147  *
148  * See alse [code example][1]
149  *
150  * ##### Example
151  *
152  *      formatter.startSection(actionGroup.title);
153  *      formatter.addText(actionGroup.description);
154  *      formatter.addArguments(actionGroup._groupActions);
155  *      formatter.endSection();
156  *
157  **/
158 HelpFormatter.prototype.startSection = function (heading) {
159   this._indent();
160   var section = new Section(this._currentSection, heading);
161   var func = section.formatHelp.bind(section);
162   this._addItem(func, [ this ]);
163   this._currentSection = section;
164 };
165
166 /**
167  * HelpFormatter#endSection -> Void
168  *
169  * End help section
170  *
171  * ##### Example
172  *
173  *      formatter.startSection(actionGroup.title);
174  *      formatter.addText(actionGroup.description);
175  *      formatter.addArguments(actionGroup._groupActions);
176  *      formatter.endSection();
177  **/
178 HelpFormatter.prototype.endSection = function () {
179   this._currentSection = this._currentSection._parent;
180   this._dedent();
181 };
182
183 /**
184  * HelpFormatter#addText(text) -> Void
185  * - text (string): plain text
186  *
187  * Add plain text into current section
188  *
189  * ##### Example
190  *
191  *      formatter.startSection(actionGroup.title);
192  *      formatter.addText(actionGroup.description);
193  *      formatter.addArguments(actionGroup._groupActions);
194  *      formatter.endSection();
195  *
196  **/
197 HelpFormatter.prototype.addText = function (text) {
198   if (text && text !== c.SUPPRESS) {
199     this._addItem(this._formatText, [ text ]);
200   }
201 };
202
203 /**
204  * HelpFormatter#addUsage(usage, actions, groups, prefix) -> Void
205  * - usage (string): usage text
206  * - actions (array): actions list
207  * - groups (array): groups list
208  * - prefix (string): usage prefix
209  *
210  * Add usage data into current section
211  *
212  * ##### Example
213  *
214  *      formatter.addUsage(this.usage, this._actions, []);
215  *      return formatter.formatHelp();
216  *
217  **/
218 HelpFormatter.prototype.addUsage = function (usage, actions, groups, prefix) {
219   if (usage !== c.SUPPRESS) {
220     this._addItem(this._formatUsage, [ usage, actions, groups, prefix ]);
221   }
222 };
223
224 /**
225  * HelpFormatter#addArgument(action) -> Void
226  * - action (object): action
227  *
228  * Add argument into current section
229  *
230  * Single variant of [[HelpFormatter#addArguments]]
231  **/
232 HelpFormatter.prototype.addArgument = function (action) {
233   if (action.help !== c.SUPPRESS) {
234     var self = this;
235
236     // find all invocations
237     var invocations = [ this._formatActionInvocation(action) ];
238     var invocationLength = invocations[0].length;
239
240     var actionLength;
241
242     if (action._getSubactions) {
243       this._indent();
244       action._getSubactions().forEach(function (subaction) {
245
246         var invocationNew = self._formatActionInvocation(subaction);
247         invocations.push(invocationNew);
248         invocationLength = Math.max(invocationLength, invocationNew.length);
249
250       });
251       this._dedent();
252     }
253
254     // update the maximum item length
255     actionLength = invocationLength + this._currentIndent;
256     this._actionMaxLength = Math.max(this._actionMaxLength, actionLength);
257
258     // add the item to the list
259     this._addItem(this._formatAction, [ action ]);
260   }
261 };
262
263 /**
264  * HelpFormatter#addArguments(actions) -> Void
265  * - actions (array): actions list
266  *
267  * Mass add arguments into current section
268  *
269  * ##### Example
270  *
271  *      formatter.startSection(actionGroup.title);
272  *      formatter.addText(actionGroup.description);
273  *      formatter.addArguments(actionGroup._groupActions);
274  *      formatter.endSection();
275  *
276  **/
277 HelpFormatter.prototype.addArguments = function (actions) {
278   var self = this;
279   actions.forEach(function (action) {
280     self.addArgument(action);
281   });
282 };
283
284 //
285 // Help-formatting methods
286 //
287
288 /**
289  * HelpFormatter#formatHelp -> string
290  *
291  * Format help
292  *
293  * ##### Example
294  *
295  *      formatter.addText(this.epilog);
296  *      return formatter.formatHelp();
297  *
298  **/
299 HelpFormatter.prototype.formatHelp = function () {
300   var help = this._rootSection.formatHelp(this);
301   if (help) {
302     help = help.replace(this._longBreakMatcher, c.EOL + c.EOL);
303     help = $$.trimChars(help, c.EOL) + c.EOL;
304   }
305   return help;
306 };
307
308 HelpFormatter.prototype._joinParts = function (partStrings) {
309   return partStrings.filter(function (part) {
310     return (part && part !== c.SUPPRESS);
311   }).join('');
312 };
313
314 HelpFormatter.prototype._formatUsage = function (usage, actions, groups, prefix) {
315   if (!prefix && typeof prefix !== 'string') {
316     prefix = 'usage: ';
317   }
318
319   actions = actions || [];
320   groups = groups || [];
321
322
323   // if usage is specified, use that
324   if (usage) {
325     usage = sprintf(usage, { prog: this._prog });
326
327     // if no optionals or positionals are available, usage is just prog
328   } else if (!usage && actions.length === 0) {
329     usage = this._prog;
330
331     // if optionals and positionals are available, calculate usage
332   } else if (!usage) {
333     var prog = this._prog;
334     var optionals = [];
335     var positionals = [];
336     var actionUsage;
337     var textWidth;
338
339     // split optionals from positionals
340     actions.forEach(function (action) {
341       if (action.isOptional()) {
342         optionals.push(action);
343       } else {
344         positionals.push(action);
345       }
346     });
347
348     // build full usage string
349     actionUsage = this._formatActionsUsage([].concat(optionals, positionals), groups);
350     usage = [ prog, actionUsage ].join(' ');
351
352     // wrap the usage parts if it's too long
353     textWidth = this._width - this._currentIndent;
354     if ((prefix.length + usage.length) > textWidth) {
355
356       // break usage into wrappable parts
357       var regexpPart = new RegExp('\\(.*?\\)+|\\[.*?\\]+|\\S+', 'g');
358       var optionalUsage = this._formatActionsUsage(optionals, groups);
359       var positionalUsage = this._formatActionsUsage(positionals, groups);
360
361
362       var optionalParts = optionalUsage.match(regexpPart);
363       var positionalParts = positionalUsage.match(regexpPart) || [];
364
365       if (optionalParts.join(' ') !== optionalUsage) {
366         throw new Error('assert "optionalParts.join(\' \') === optionalUsage"');
367       }
368       if (positionalParts.join(' ') !== positionalUsage) {
369         throw new Error('assert "positionalParts.join(\' \') === positionalUsage"');
370       }
371
372       // helper for wrapping lines
373       /*eslint-disable func-style*/ // node 0.10 compat
374       var _getLines = function (parts, indent, prefix) {
375         var lines = [];
376         var line = [];
377
378         var lineLength = prefix ? prefix.length - 1 : indent.length - 1;
379
380         parts.forEach(function (part) {
381           if (lineLength + 1 + part.length > textWidth) {
382             lines.push(indent + line.join(' '));
383             line = [];
384             lineLength = indent.length - 1;
385           }
386           line.push(part);
387           lineLength += part.length + 1;
388         });
389
390         if (line) {
391           lines.push(indent + line.join(' '));
392         }
393         if (prefix) {
394           lines[0] = lines[0].substr(indent.length);
395         }
396         return lines;
397       };
398
399       var lines, indent, parts;
400       // if prog is short, follow it with optionals or positionals
401       if (prefix.length + prog.length <= 0.75 * textWidth) {
402         indent = $$.repeat(' ', (prefix.length + prog.length + 1));
403         if (optionalParts) {
404           lines = [].concat(
405             _getLines([ prog ].concat(optionalParts), indent, prefix),
406             _getLines(positionalParts, indent)
407           );
408         } else if (positionalParts) {
409           lines = _getLines([ prog ].concat(positionalParts), indent, prefix);
410         } else {
411           lines = [ prog ];
412         }
413
414         // if prog is long, put it on its own line
415       } else {
416         indent = $$.repeat(' ', prefix.length);
417         parts = optionalParts + positionalParts;
418         lines = _getLines(parts, indent);
419         if (lines.length > 1) {
420           lines = [].concat(
421             _getLines(optionalParts, indent),
422             _getLines(positionalParts, indent)
423           );
424         }
425         lines = [ prog ] + lines;
426       }
427       // join lines into usage
428       usage = lines.join(c.EOL);
429     }
430   }
431
432   // prefix with 'usage:'
433   return prefix + usage + c.EOL + c.EOL;
434 };
435
436 HelpFormatter.prototype._formatActionsUsage = function (actions, groups) {
437   // find group indices and identify actions in groups
438   var groupActions = [];
439   var inserts = [];
440   var self = this;
441
442   groups.forEach(function (group) {
443     var end;
444     var i;
445
446     var start = actions.indexOf(group._groupActions[0]);
447     if (start >= 0) {
448       end = start + group._groupActions.length;
449
450       //if (actions.slice(start, end) === group._groupActions) {
451       if ($$.arrayEqual(actions.slice(start, end), group._groupActions)) {
452         group._groupActions.forEach(function (action) {
453           groupActions.push(action);
454         });
455
456         if (!group.required) {
457           if (inserts[start]) {
458             inserts[start] += ' [';
459           } else {
460             inserts[start] = '[';
461           }
462           inserts[end] = ']';
463         } else {
464           if (inserts[start]) {
465             inserts[start] += ' (';
466           } else {
467             inserts[start] = '(';
468           }
469           inserts[end] = ')';
470         }
471         for (i = start + 1; i < end; i += 1) {
472           inserts[i] = '|';
473         }
474       }
475     }
476   });
477
478   // collect all actions format strings
479   var parts = [];
480
481   actions.forEach(function (action, actionIndex) {
482     var part;
483     var optionString;
484     var argsDefault;
485     var argsString;
486
487     // suppressed arguments are marked with None
488     // remove | separators for suppressed arguments
489     if (action.help === c.SUPPRESS) {
490       parts.push(null);
491       if (inserts[actionIndex] === '|') {
492         inserts.splice(actionIndex, actionIndex);
493       } else if (inserts[actionIndex + 1] === '|') {
494         inserts.splice(actionIndex + 1, actionIndex + 1);
495       }
496
497       // produce all arg strings
498     } else if (!action.isOptional()) {
499       part = self._formatArgs(action, action.dest);
500
501       // if it's in a group, strip the outer []
502       if (groupActions.indexOf(action) >= 0) {
503         if (part[0] === '[' && part[part.length - 1] === ']') {
504           part = part.slice(1, -1);
505         }
506       }
507       // add the action string to the list
508       parts.push(part);
509
510     // produce the first way to invoke the option in brackets
511     } else {
512       optionString = action.optionStrings[0];
513
514       // if the Optional doesn't take a value, format is: -s or --long
515       if (action.nargs === 0) {
516         part = '' + optionString;
517
518       // if the Optional takes a value, format is: -s ARGS or --long ARGS
519       } else {
520         argsDefault = action.dest.toUpperCase();
521         argsString = self._formatArgs(action, argsDefault);
522         part = optionString + ' ' + argsString;
523       }
524       // make it look optional if it's not required or in a group
525       if (!action.required && groupActions.indexOf(action) < 0) {
526         part = '[' + part + ']';
527       }
528       // add the action string to the list
529       parts.push(part);
530     }
531   });
532
533   // insert things at the necessary indices
534   for (var i = inserts.length - 1; i >= 0; --i) {
535     if (inserts[i] !== null) {
536       parts.splice(i, 0, inserts[i]);
537     }
538   }
539
540   // join all the action items with spaces
541   var text = parts.filter(function (part) {
542     return !!part;
543   }).join(' ');
544
545   // clean up separators for mutually exclusive groups
546   text = text.replace(/([\[(]) /g, '$1'); // remove spaces
547   text = text.replace(/ ([\])])/g, '$1');
548   text = text.replace(/\[ *\]/g, ''); // remove empty groups
549   text = text.replace(/\( *\)/g, '');
550   text = text.replace(/\(([^|]*)\)/g, '$1'); // remove () from single action groups
551
552   text = text.trim();
553
554   // return the text
555   return text;
556 };
557
558 HelpFormatter.prototype._formatText = function (text) {
559   text = sprintf(text, { prog: this._prog });
560   var textWidth = this._width - this._currentIndent;
561   var indentIncriment = $$.repeat(' ', this._currentIndent);
562   return this._fillText(text, textWidth, indentIncriment) + c.EOL + c.EOL;
563 };
564
565 HelpFormatter.prototype._formatAction = function (action) {
566   var self = this;
567
568   var helpText;
569   var helpLines;
570   var parts;
571   var indentFirst;
572
573   // determine the required width and the entry label
574   var helpPosition = Math.min(this._actionMaxLength + 2, this._maxHelpPosition);
575   var helpWidth = this._width - helpPosition;
576   var actionWidth = helpPosition - this._currentIndent - 2;
577   var actionHeader = this._formatActionInvocation(action);
578
579   // no help; start on same line and add a final newline
580   if (!action.help) {
581     actionHeader = $$.repeat(' ', this._currentIndent) + actionHeader + c.EOL;
582
583   // short action name; start on the same line and pad two spaces
584   } else if (actionHeader.length <= actionWidth) {
585     actionHeader = $$.repeat(' ', this._currentIndent) +
586         actionHeader +
587         '  ' +
588         $$.repeat(' ', actionWidth - actionHeader.length);
589     indentFirst = 0;
590
591   // long action name; start on the next line
592   } else {
593     actionHeader = $$.repeat(' ', this._currentIndent) + actionHeader + c.EOL;
594     indentFirst = helpPosition;
595   }
596
597   // collect the pieces of the action help
598   parts = [ actionHeader ];
599
600   // if there was help for the action, add lines of help text
601   if (action.help) {
602     helpText = this._expandHelp(action);
603     helpLines = this._splitLines(helpText, helpWidth);
604     parts.push($$.repeat(' ', indentFirst) + helpLines[0] + c.EOL);
605     helpLines.slice(1).forEach(function (line) {
606       parts.push($$.repeat(' ', helpPosition) + line + c.EOL);
607     });
608
609   // or add a newline if the description doesn't end with one
610   } else if (actionHeader.charAt(actionHeader.length - 1) !== c.EOL) {
611     parts.push(c.EOL);
612   }
613   // if there are any sub-actions, add their help as well
614   if (action._getSubactions) {
615     this._indent();
616     action._getSubactions().forEach(function (subaction) {
617       parts.push(self._formatAction(subaction));
618     });
619     this._dedent();
620   }
621   // return a single string
622   return this._joinParts(parts);
623 };
624
625 HelpFormatter.prototype._formatActionInvocation = function (action) {
626   if (!action.isOptional()) {
627     var format_func = this._metavarFormatter(action, action.dest);
628     var metavars = format_func(1);
629     return metavars[0];
630   }
631
632   var parts = [];
633   var argsDefault;
634   var argsString;
635
636   // if the Optional doesn't take a value, format is: -s, --long
637   if (action.nargs === 0) {
638     parts = parts.concat(action.optionStrings);
639
640   // if the Optional takes a value, format is: -s ARGS, --long ARGS
641   } else {
642     argsDefault = action.dest.toUpperCase();
643     argsString = this._formatArgs(action, argsDefault);
644     action.optionStrings.forEach(function (optionString) {
645       parts.push(optionString + ' ' + argsString);
646     });
647   }
648   return parts.join(', ');
649 };
650
651 HelpFormatter.prototype._metavarFormatter = function (action, metavarDefault) {
652   var result;
653
654   if (action.metavar || action.metavar === '') {
655     result = action.metavar;
656   } else if (action.choices) {
657     var choices = action.choices;
658
659     if (typeof choices === 'string') {
660       choices = choices.split('').join(', ');
661     } else if (Array.isArray(choices)) {
662       choices = choices.join(',');
663     } else {
664       choices = Object.keys(choices).join(',');
665     }
666     result = '{' + choices + '}';
667   } else {
668     result = metavarDefault;
669   }
670
671   return function (size) {
672     if (Array.isArray(result)) {
673       return result;
674     }
675
676     var metavars = [];
677     for (var i = 0; i < size; i += 1) {
678       metavars.push(result);
679     }
680     return metavars;
681   };
682 };
683
684 HelpFormatter.prototype._formatArgs = function (action, metavarDefault) {
685   var result;
686   var metavars;
687
688   var buildMetavar = this._metavarFormatter(action, metavarDefault);
689
690   switch (action.nargs) {
691     /*eslint-disable no-undefined*/
692     case undefined:
693     case null:
694       metavars = buildMetavar(1);
695       result = '' + metavars[0];
696       break;
697     case c.OPTIONAL:
698       metavars = buildMetavar(1);
699       result = '[' + metavars[0] + ']';
700       break;
701     case c.ZERO_OR_MORE:
702       metavars = buildMetavar(2);
703       result = '[' + metavars[0] + ' [' + metavars[1] + ' ...]]';
704       break;
705     case c.ONE_OR_MORE:
706       metavars = buildMetavar(2);
707       result = '' + metavars[0] + ' [' + metavars[1] + ' ...]';
708       break;
709     case c.REMAINDER:
710       result = '...';
711       break;
712     case c.PARSER:
713       metavars = buildMetavar(1);
714       result = metavars[0] + ' ...';
715       break;
716     default:
717       metavars = buildMetavar(action.nargs);
718       result = metavars.join(' ');
719   }
720   return result;
721 };
722
723 HelpFormatter.prototype._expandHelp = function (action) {
724   var params = { prog: this._prog };
725
726   Object.keys(action).forEach(function (actionProperty) {
727     var actionValue = action[actionProperty];
728
729     if (actionValue !== c.SUPPRESS) {
730       params[actionProperty] = actionValue;
731     }
732   });
733
734   if (params.choices) {
735     if (typeof params.choices === 'string') {
736       params.choices = params.choices.split('').join(', ');
737     } else if (Array.isArray(params.choices)) {
738       params.choices = params.choices.join(', ');
739     } else {
740       params.choices = Object.keys(params.choices).join(', ');
741     }
742   }
743
744   return sprintf(this._getHelpString(action), params);
745 };
746
747 HelpFormatter.prototype._splitLines = function (text, width) {
748   var lines = [];
749   var delimiters = [ ' ', '.', ',', '!', '?' ];
750   var re = new RegExp('[' + delimiters.join('') + '][^' + delimiters.join('') + ']*$');
751
752   text = text.replace(/[\n\|\t]/g, ' ');
753
754   text = text.trim();
755   text = text.replace(this._whitespaceMatcher, ' ');
756
757   // Wraps the single paragraph in text (a string) so every line
758   // is at most width characters long.
759   text.split(c.EOL).forEach(function (line) {
760     if (width >= line.length) {
761       lines.push(line);
762       return;
763     }
764
765     var wrapStart = 0;
766     var wrapEnd = width;
767     var delimiterIndex = 0;
768     while (wrapEnd <= line.length) {
769       if (wrapEnd !== line.length && delimiters.indexOf(line[wrapEnd] < -1)) {
770         delimiterIndex = (re.exec(line.substring(wrapStart, wrapEnd)) || {}).index;
771         wrapEnd = wrapStart + delimiterIndex + 1;
772       }
773       lines.push(line.substring(wrapStart, wrapEnd));
774       wrapStart = wrapEnd;
775       wrapEnd += width;
776     }
777     if (wrapStart < line.length) {
778       lines.push(line.substring(wrapStart, wrapEnd));
779     }
780   });
781
782   return lines;
783 };
784
785 HelpFormatter.prototype._fillText = function (text, width, indent) {
786   var lines = this._splitLines(text, width);
787   lines = lines.map(function (line) {
788     return indent + line;
789   });
790   return lines.join(c.EOL);
791 };
792
793 HelpFormatter.prototype._getHelpString = function (action) {
794   return action.help;
795 };