react / wstein / node_modules / jest-cli / node_modules / istanbul / node_modules / js-yaml / node_modules / argparse / lib / help / formatter.js
80728 views/**1* class HelpFormatter2*3* Formatter for generating usage messages and argument help strings. Only the4* name of this class is considered a public API. All the methods provided by5* the class are considered an implementation detail.6*7* Do not call in your code, use this class only for inherits your own forvatter8*9* ToDo add [additonal formatters][1]10*11* [1]:http://docs.python.org/dev/library/argparse.html#formatter-class12**/13'use strict';1415var _ = require('lodash');16var sprintf = require('sprintf-js').sprintf;1718// Constants19var $$ = require('../const');202122/*:nodoc:* internal23* new Support(parent, heding)24* - parent (object): parent section25* - heading (string): header string26*27**/28function Section(parent, heading) {29this._parent = parent;30this._heading = heading;31this._items = [];32}3334/*:nodoc:* internal35* Section#addItem(callback) -> Void36* - callback (array): tuple with function and args37*38* Add function for single element39**/40Section.prototype.addItem = function (callback) {41this._items.push(callback);42};4344/*:nodoc:* internal45* Section#formatHelp(formatter) -> string46* - formatter (HelpFormatter): current formatter47*48* Form help section string49*50**/51Section.prototype.formatHelp = function (formatter) {52var itemHelp, heading;5354// format the indented section55if (!!this._parent) {56formatter._indent();57}5859itemHelp = this._items.map(function (item) {60var obj, func, args;6162obj = formatter;63func = item[0];64args = item[1];65return func.apply(obj, args);66});67itemHelp = formatter._joinParts(itemHelp);6869if (!!this._parent) {70formatter._dedent();71}7273// return nothing if the section was empty74if (!itemHelp) {75return '';76}7778// add the heading if the section was non-empty79heading = '';80if (!!this._heading && this._heading !== $$.SUPPRESS) {81var currentIndent = formatter.currentIndent;82heading = _.repeat(' ', currentIndent) + this._heading + ':' + $$.EOL;83}8485// join the section-initialize newline, the heading and the help86return formatter._joinParts([$$.EOL, heading, itemHelp, $$.EOL]);87};8889/**90* new HelpFormatter(options)91*92* #### Options:93* - `prog`: program name94* - `indentIncriment`: indent step, default value 295* - `maxHelpPosition`: max help position, default value = 2496* - `width`: line width97*98**/99var HelpFormatter = module.exports = function HelpFormatter(options) {100options = options || {};101102this._prog = options.prog;103104this._maxHelpPosition = options.maxHelpPosition || 24;105this._width = (options.width || ((process.env.COLUMNS || 80) - 2));106107this._currentIndent = 0;108this._indentIncriment = options.indentIncriment || 2;109this._level = 0;110this._actionMaxLength = 0;111112this._rootSection = new Section(null);113this._currentSection = this._rootSection;114115this._whitespaceMatcher = new RegExp('\\s+', 'g');116this._longBreakMatcher = new RegExp($$.EOL + $$.EOL + $$.EOL + '+', 'g');117};118119HelpFormatter.prototype._indent = function () {120this._currentIndent += this._indentIncriment;121this._level += 1;122};123124HelpFormatter.prototype._dedent = function () {125this._currentIndent -= this._indentIncriment;126this._level -= 1;127if (this._currentIndent < 0) {128throw new Error('Indent decreased below 0.');129}130};131132HelpFormatter.prototype._addItem = function (func, args) {133this._currentSection.addItem([func, args]);134};135136//137// Message building methods138//139140/**141* HelpFormatter#startSection(heading) -> Void142* - heading (string): header string143*144* Start new help section145*146* See alse [code example][1]147*148* ##### Example149*150* formatter.startSection(actionGroup.title);151* formatter.addText(actionGroup.description);152* formatter.addArguments(actionGroup._groupActions);153* formatter.endSection();154*155**/156HelpFormatter.prototype.startSection = function (heading) {157this._indent();158var section = new Section(this._currentSection, heading);159var func = section.formatHelp.bind(section);160this._addItem(func, [this]);161this._currentSection = section;162};163164/**165* HelpFormatter#endSection -> Void166*167* End help section168*169* ##### Example170*171* formatter.startSection(actionGroup.title);172* formatter.addText(actionGroup.description);173* formatter.addArguments(actionGroup._groupActions);174* formatter.endSection();175**/176HelpFormatter.prototype.endSection = function () {177this._currentSection = this._currentSection._parent;178this._dedent();179};180181/**182* HelpFormatter#addText(text) -> Void183* - text (string): plain text184*185* Add plain text into current section186*187* ##### Example188*189* formatter.startSection(actionGroup.title);190* formatter.addText(actionGroup.description);191* formatter.addArguments(actionGroup._groupActions);192* formatter.endSection();193*194**/195HelpFormatter.prototype.addText = function (text) {196if (!!text && text !== $$.SUPPRESS) {197this._addItem(this._formatText, [text]);198}199};200201/**202* HelpFormatter#addUsage(usage, actions, groups, prefix) -> Void203* - usage (string): usage text204* - actions (array): actions list205* - groups (array): groups list206* - prefix (string): usage prefix207*208* Add usage data into current section209*210* ##### Example211*212* formatter.addUsage(this.usage, this._actions, []);213* return formatter.formatHelp();214*215**/216HelpFormatter.prototype.addUsage = function (usage, actions, groups, prefix) {217if (usage !== $$.SUPPRESS) {218this._addItem(this._formatUsage, [usage, actions, groups, prefix]);219}220};221222/**223* HelpFormatter#addArgument(action) -> Void224* - action (object): action225*226* Add argument into current section227*228* Single variant of [[HelpFormatter#addArguments]]229**/230HelpFormatter.prototype.addArgument = function (action) {231if (action.help !== $$.SUPPRESS) {232var self = this;233234// find all invocations235var invocations = [this._formatActionInvocation(action)];236var invocationLength = invocations[0].length;237238var actionLength;239240if (!!action._getSubactions) {241this._indent();242action._getSubactions().forEach(function (subaction) {243244var invocationNew = self._formatActionInvocation(subaction);245invocations.push(invocationNew);246invocationLength = Math.max(invocationLength, invocationNew.length);247248});249this._dedent();250}251252// update the maximum item length253actionLength = invocationLength + this._currentIndent;254this._actionMaxLength = Math.max(this._actionMaxLength, actionLength);255256// add the item to the list257this._addItem(this._formatAction, [action]);258}259};260261/**262* HelpFormatter#addArguments(actions) -> Void263* - actions (array): actions list264*265* Mass add arguments into current section266*267* ##### Example268*269* formatter.startSection(actionGroup.title);270* formatter.addText(actionGroup.description);271* formatter.addArguments(actionGroup._groupActions);272* formatter.endSection();273*274**/275HelpFormatter.prototype.addArguments = function (actions) {276var self = this;277actions.forEach(function (action) {278self.addArgument(action);279});280};281282//283// Help-formatting methods284//285286/**287* HelpFormatter#formatHelp -> string288*289* Format help290*291* ##### Example292*293* formatter.addText(this.epilog);294* return formatter.formatHelp();295*296**/297HelpFormatter.prototype.formatHelp = function () {298var help = this._rootSection.formatHelp(this);299if (help) {300help = help.replace(this._longBreakMatcher, $$.EOL + $$.EOL);301help = _.trim(help, $$.EOL) + $$.EOL;302}303return help;304};305306HelpFormatter.prototype._joinParts = function (partStrings) {307return partStrings.filter(function (part) {308return (!!part && part !== $$.SUPPRESS);309}).join('');310};311312HelpFormatter.prototype._formatUsage = function (usage, actions, groups, prefix) {313if (!prefix && !_.isString(prefix)) {314prefix = 'usage: ';315}316317actions = actions || [];318groups = groups || [];319320321// if usage is specified, use that322if (usage) {323usage = sprintf(usage, {prog: this._prog});324325// if no optionals or positionals are available, usage is just prog326} else if (!usage && actions.length === 0) {327usage = this._prog;328329// if optionals and positionals are available, calculate usage330} else if (!usage) {331var prog = this._prog;332var optionals = [];333var positionals = [];334var actionUsage;335var textWidth;336337// split optionals from positionals338actions.forEach(function (action) {339if (action.isOptional()) {340optionals.push(action);341} else {342positionals.push(action);343}344});345346// build full usage string347actionUsage = this._formatActionsUsage([].concat(optionals, positionals), groups);348usage = [prog, actionUsage].join(' ');349350// wrap the usage parts if it's too long351textWidth = this._width - this._currentIndent;352if ((prefix.length + usage.length) > textWidth) {353354// break usage into wrappable parts355var regexpPart = new RegExp('\\(.*?\\)+|\\[.*?\\]+|\\S+', 'g');356var optionalUsage = this._formatActionsUsage(optionals, groups);357var positionalUsage = this._formatActionsUsage(positionals, groups);358359360var optionalParts = optionalUsage.match(regexpPart);361var positionalParts = positionalUsage.match(regexpPart) || [];362363if (optionalParts.join(' ') !== optionalUsage) {364throw new Error('assert "optionalParts.join(\' \') === optionalUsage"');365}366if (positionalParts.join(' ') !== positionalUsage) {367throw new Error('assert "positionalParts.join(\' \') === positionalUsage"');368}369370// helper for wrapping lines371var _getLines = function (parts, indent, prefix) {372var lines = [];373var line = [];374375var lineLength = !!prefix ? prefix.length - 1: indent.length - 1;376377parts.forEach(function (part) {378if (lineLength + 1 + part.length > textWidth) {379lines.push(indent + line.join(' '));380line = [];381lineLength = indent.length - 1;382}383line.push(part);384lineLength += part.length + 1;385});386387if (line) {388lines.push(indent + line.join(' '));389}390if (prefix) {391lines[0] = lines[0].substr(indent.length);392}393return lines;394};395396var lines, indent, parts;397// if prog is short, follow it with optionals or positionals398if (prefix.length + prog.length <= 0.75 * textWidth) {399indent = _.repeat(' ', (prefix.length + prog.length + 1));400if (optionalParts) {401lines = [].concat(402_getLines([prog].concat(optionalParts), indent, prefix),403_getLines(positionalParts, indent)404);405} else if (positionalParts) {406lines = _getLines([prog].concat(positionalParts), indent, prefix);407} else {408lines = [prog];409}410411// if prog is long, put it on its own line412} else {413indent = _.repeat(' ', prefix.length);414parts = optionalParts + positionalParts;415lines = _getLines(parts, indent);416if (lines.length > 1) {417lines = [].concat(418_getLines(optionalParts, indent),419_getLines(positionalParts, indent)420);421}422lines = [prog] + lines;423}424// join lines into usage425usage = lines.join($$.EOL);426}427}428429// prefix with 'usage:'430return prefix + usage + $$.EOL + $$.EOL;431};432433HelpFormatter.prototype._formatActionsUsage = function (actions, groups) {434// find group indices and identify actions in groups435var groupActions = [];436var inserts = [];437var self = this;438439groups.forEach(function (group) {440var end;441var i;442443var start = actions.indexOf(group._groupActions[0]);444if (start >= 0) {445end = start + group._groupActions.length;446447//if (actions.slice(start, end) === group._groupActions) {448if (_.isEqual(actions.slice(start, end), group._groupActions)) {449group._groupActions.forEach(function (action) {450groupActions.push(action);451});452453if (!group.required) {454if (!!inserts[start]) {455inserts[start] += ' [';456}457else {458inserts[start] = '[';459}460inserts[end] = ']';461} else {462if (!!inserts[start]) {463inserts[start] += ' (';464}465else {466inserts[start] = '(';467}468inserts[end] = ')';469}470for (i = start + 1; i < end; i += 1) {471inserts[i] = '|';472}473}474}475});476477// collect all actions format strings478var parts = [];479480actions.forEach(function (action, actionIndex) {481var part;482var optionString;483var argsDefault;484var argsString;485486// suppressed arguments are marked with None487// remove | separators for suppressed arguments488if (action.help === $$.SUPPRESS) {489parts.push(null);490if (inserts[actionIndex] === '|') {491inserts.splice(actionIndex, actionIndex);492} else if (inserts[actionIndex + 1] === '|') {493inserts.splice(actionIndex + 1, actionIndex + 1);494}495496// produce all arg strings497} else if (!action.isOptional()) {498part = self._formatArgs(action, action.dest);499500// if it's in a group, strip the outer []501if (groupActions.indexOf(action) >= 0) {502if (part[0] === '[' && part[part.length - 1] === ']') {503part = part.slice(1, -1);504}505}506// add the action string to the list507parts.push(part);508509// produce the first way to invoke the option in brackets510} else {511optionString = action.optionStrings[0];512513// if the Optional doesn't take a value, format is: -s or --long514if (action.nargs === 0) {515part = '' + optionString;516517// if the Optional takes a value, format is: -s ARGS or --long ARGS518} else {519argsDefault = action.dest.toUpperCase();520argsString = self._formatArgs(action, argsDefault);521part = optionString + ' ' + argsString;522}523// make it look optional if it's not required or in a group524if (!action.required && groupActions.indexOf(action) < 0) {525part = '[' + part + ']';526}527// add the action string to the list528parts.push(part);529}530});531532// insert things at the necessary indices533for (var i = inserts.length - 1; i >= 0; --i) {534if (inserts[i] !== null) {535parts.splice(i, 0, inserts[i]);536}537}538539// join all the action items with spaces540var text = parts.filter(function (part) {541return !!part;542}).join(' ');543544// clean up separators for mutually exclusive groups545text = text.replace(/([\[(]) /g, '$1'); // remove spaces546text = text.replace(/ ([\])])/g, '$1');547text = text.replace(/\[ *\]/g, ''); // remove empty groups548text = text.replace(/\( *\)/g, '');549text = text.replace(/\(([^|]*)\)/g, '$1'); // remove () from single action groups550551text = _.trim(text);552553// return the text554return text;555};556557HelpFormatter.prototype._formatText = function (text) {558text = sprintf(text, {prog: this._prog});559var textWidth = this._width - this._currentIndent;560var indentIncriment = _.repeat(' ', this._currentIndent);561return this._fillText(text, textWidth, indentIncriment) + $$.EOL + $$.EOL;562};563564HelpFormatter.prototype._formatAction = function (action) {565var self = this;566567var helpText;568var helpLines;569var parts;570var indentFirst;571572// determine the required width and the entry label573var helpPosition = Math.min(this._actionMaxLength + 2, this._maxHelpPosition);574var helpWidth = this._width - helpPosition;575var actionWidth = helpPosition - this._currentIndent - 2;576var actionHeader = this._formatActionInvocation(action);577578// no help; start on same line and add a final newline579if (!action.help) {580actionHeader = _.repeat(' ', this._currentIndent) + actionHeader + $$.EOL;581582// short action name; start on the same line and pad two spaces583} else if (actionHeader.length <= actionWidth) {584actionHeader = _.repeat(' ', this._currentIndent) +585actionHeader +586' ' +587_.repeat(' ', actionWidth - actionHeader.length);588indentFirst = 0;589590// long action name; start on the next line591} else {592actionHeader = _.repeat(' ', this._currentIndent) + actionHeader + $$.EOL;593indentFirst = helpPosition;594}595596// collect the pieces of the action help597parts = [actionHeader];598599// if there was help for the action, add lines of help text600if (!!action.help) {601helpText = this._expandHelp(action);602helpLines = this._splitLines(helpText, helpWidth);603parts.push(_.repeat(' ', indentFirst) + helpLines[0] + $$.EOL);604helpLines.slice(1).forEach(function (line) {605parts.push(_.repeat(' ', helpPosition) + line + $$.EOL);606});607608// or add a newline if the description doesn't end with one609} else if (actionHeader.charAt(actionHeader.length - 1) !== $$.EOL) {610parts.push($$.EOL);611}612// if there are any sub-actions, add their help as well613if (!!action._getSubactions) {614this._indent();615action._getSubactions().forEach(function (subaction) {616parts.push(self._formatAction(subaction));617});618this._dedent();619}620// return a single string621return this._joinParts(parts);622};623624HelpFormatter.prototype._formatActionInvocation = function (action) {625if (!action.isOptional()) {626var format_func = this._metavarFormatter(action, action.dest);627var metavars = format_func(1);628return metavars[0];629} else {630var parts = [];631var argsDefault;632var argsString;633634// if the Optional doesn't take a value, format is: -s, --long635if (action.nargs === 0) {636parts = parts.concat(action.optionStrings);637638// if the Optional takes a value, format is: -s ARGS, --long ARGS639} else {640argsDefault = action.dest.toUpperCase();641argsString = this._formatArgs(action, argsDefault);642action.optionStrings.forEach(function (optionString) {643parts.push(optionString + ' ' + argsString);644});645}646return parts.join(', ');647}648};649650HelpFormatter.prototype._metavarFormatter = function (action, metavarDefault) {651var result;652653if (!!action.metavar || action.metavar === '') {654result = action.metavar;655} else if (!!action.choices) {656var choices = action.choices;657658if (_.isString(choices)) {659choices = choices.split('').join(', ');660} else if (_.isArray(choices)) {661choices = choices.join(',');662}663else664{665choices = _.keys(choices).join(',');666}667result = '{' + choices + '}';668} else {669result = metavarDefault;670}671672return function (size) {673if (Array.isArray(result)) {674return result;675} else {676var metavars = [];677for (var i = 0; i < size; i += 1) {678metavars.push(result);679}680return metavars;681}682};683};684685HelpFormatter.prototype._formatArgs = function (action, metavarDefault) {686var result;687var metavars;688689var buildMetavar = this._metavarFormatter(action, metavarDefault);690691switch (action.nargs) {692case undefined:693case null:694metavars = buildMetavar(1);695result = '' + metavars[0];696break;697case $$.OPTIONAL:698metavars = buildMetavar(1);699result = '[' + metavars[0] + ']';700break;701case $$.ZERO_OR_MORE:702metavars = buildMetavar(2);703result = '[' + metavars[0] + ' [' + metavars[1] + ' ...]]';704break;705case $$.ONE_OR_MORE:706metavars = buildMetavar(2);707result = '' + metavars[0] + ' [' + metavars[1] + ' ...]';708break;709case $$.REMAINDER:710result = '...';711break;712case $$.PARSER:713metavars = buildMetavar(1);714result = metavars[0] + ' ...';715break;716default:717metavars = buildMetavar(action.nargs);718result = metavars.join(' ');719}720return result;721};722723HelpFormatter.prototype._expandHelp = function (action) {724var params = { prog: this._prog };725726Object.keys(action).forEach(function (actionProperty) {727var actionValue = action[actionProperty];728729if (actionValue !== $$.SUPPRESS) {730params[actionProperty] = actionValue;731}732});733734if (!!params.choices) {735if (_.isString(params.choices)) {736params.choices = params.choices.split('').join(', ');737}738else if (_.isArray(params.choices)) {739params.choices = params.choices.join(', ');740}741else {742params.choices = _.keys(params.choices).join(', ');743}744}745746return sprintf(this._getHelpString(action), params);747};748749HelpFormatter.prototype._splitLines = function (text, width) {750var lines = [];751var delimiters = [" ", ".", ",", "!", "?"];752var re = new RegExp('[' + delimiters.join('') + '][^' + delimiters.join('') + ']*$');753754text = text.replace(/[\n\|\t]/g, ' ');755756text = _.trim(text);757text = text.replace(this._whitespaceMatcher, ' ');758759// Wraps the single paragraph in text (a string) so every line760// is at most width characters long.761text.split($$.EOL).forEach(function (line) {762if (width >= line.length) {763lines.push(line);764return;765}766767var wrapStart = 0;768var wrapEnd = width;769var delimiterIndex = 0;770while (wrapEnd <= line.length) {771if (wrapEnd !== line.length && delimiters.indexOf(line[wrapEnd] < -1)) {772delimiterIndex = (re.exec(line.substring(wrapStart, wrapEnd)) || {}).index;773wrapEnd = wrapStart + delimiterIndex + 1;774}775lines.push(line.substring(wrapStart, wrapEnd));776wrapStart = wrapEnd;777wrapEnd += width;778}779if (wrapStart < line.length) {780lines.push(line.substring(wrapStart, wrapEnd));781}782});783784return lines;785};786787HelpFormatter.prototype._fillText = function (text, width, indent) {788var lines = this._splitLines(text, width);789lines = lines.map(function (line) {790return indent + line;791});792return lines.join($$.EOL);793};794795HelpFormatter.prototype._getHelpString = function (action) {796return action.help;797};798799800