| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795 | /** * class HelpFormatter * * Formatter for generating usage messages and argument help strings. Only the * name of this class is considered a public API. All the methods provided by * the class are considered an implementation detail. * * Do not call in your code, use this class only for inherits your own forvatter * * ToDo add [additonal formatters][1] * * [1]:http://docs.python.org/dev/library/argparse.html#formatter-class **/'use strict';var sprintf = require('sprintf-js').sprintf;// Constantsvar c = require('../const');var $$ = require('../utils');/*:nodoc:* internal * new Support(parent, heding) * - parent (object): parent section * - heading (string): header string * **/function Section(parent, heading) {  this._parent = parent;  this._heading = heading;  this._items = [];}/*:nodoc:* internal * Section#addItem(callback) -> Void * - callback (array): tuple with function and args * * Add function for single element **/Section.prototype.addItem = function (callback) {  this._items.push(callback);};/*:nodoc:* internal * Section#formatHelp(formatter) -> string * - formatter (HelpFormatter): current formatter * * Form help section string * **/Section.prototype.formatHelp = function (formatter) {  var itemHelp, heading;  // format the indented section  if (this._parent) {    formatter._indent();  }  itemHelp = this._items.map(function (item) {    var obj, func, args;    obj = formatter;    func = item[0];    args = item[1];    return func.apply(obj, args);  });  itemHelp = formatter._joinParts(itemHelp);  if (this._parent) {    formatter._dedent();  }  // return nothing if the section was empty  if (!itemHelp) {    return '';  }  // add the heading if the section was non-empty  heading = '';  if (this._heading && this._heading !== c.SUPPRESS) {    var currentIndent = formatter.currentIndent;    heading = $$.repeat(' ', currentIndent) + this._heading + ':' + c.EOL;  }  // join the section-initialize newline, the heading and the help  return formatter._joinParts([ c.EOL, heading, itemHelp, c.EOL ]);};/** * new HelpFormatter(options) * * #### Options: * - `prog`: program name * - `indentIncriment`: indent step, default value 2 * - `maxHelpPosition`: max help position, default value = 24 * - `width`: line width * **/var HelpFormatter = module.exports = function HelpFormatter(options) {  options = options || {};  this._prog = options.prog;  this._maxHelpPosition = options.maxHelpPosition || 24;  this._width = (options.width || ((process.env.COLUMNS || 80) - 2));  this._currentIndent = 0;  this._indentIncriment = options.indentIncriment || 2;  this._level = 0;  this._actionMaxLength = 0;  this._rootSection = new Section(null);  this._currentSection = this._rootSection;  this._whitespaceMatcher = new RegExp('\\s+', 'g');  this._longBreakMatcher = new RegExp(c.EOL + c.EOL + c.EOL + '+', 'g');};HelpFormatter.prototype._indent = function () {  this._currentIndent += this._indentIncriment;  this._level += 1;};HelpFormatter.prototype._dedent = function () {  this._currentIndent -= this._indentIncriment;  this._level -= 1;  if (this._currentIndent < 0) {    throw new Error('Indent decreased below 0.');  }};HelpFormatter.prototype._addItem = function (func, args) {  this._currentSection.addItem([ func, args ]);};//// Message building methods///** * HelpFormatter#startSection(heading) -> Void * - heading (string): header string * * Start new help section * * See alse [code example][1] * * ##### Example * *      formatter.startSection(actionGroup.title); *      formatter.addText(actionGroup.description); *      formatter.addArguments(actionGroup._groupActions); *      formatter.endSection(); * **/HelpFormatter.prototype.startSection = function (heading) {  this._indent();  var section = new Section(this._currentSection, heading);  var func = section.formatHelp.bind(section);  this._addItem(func, [ this ]);  this._currentSection = section;};/** * HelpFormatter#endSection -> Void * * End help section * * ##### Example * *      formatter.startSection(actionGroup.title); *      formatter.addText(actionGroup.description); *      formatter.addArguments(actionGroup._groupActions); *      formatter.endSection(); **/HelpFormatter.prototype.endSection = function () {  this._currentSection = this._currentSection._parent;  this._dedent();};/** * HelpFormatter#addText(text) -> Void * - text (string): plain text * * Add plain text into current section * * ##### Example * *      formatter.startSection(actionGroup.title); *      formatter.addText(actionGroup.description); *      formatter.addArguments(actionGroup._groupActions); *      formatter.endSection(); * **/HelpFormatter.prototype.addText = function (text) {  if (text && text !== c.SUPPRESS) {    this._addItem(this._formatText, [ text ]);  }};/** * HelpFormatter#addUsage(usage, actions, groups, prefix) -> Void * - usage (string): usage text * - actions (array): actions list * - groups (array): groups list * - prefix (string): usage prefix * * Add usage data into current section * * ##### Example * *      formatter.addUsage(this.usage, this._actions, []); *      return formatter.formatHelp(); * **/HelpFormatter.prototype.addUsage = function (usage, actions, groups, prefix) {  if (usage !== c.SUPPRESS) {    this._addItem(this._formatUsage, [ usage, actions, groups, prefix ]);  }};/** * HelpFormatter#addArgument(action) -> Void * - action (object): action * * Add argument into current section * * Single variant of [[HelpFormatter#addArguments]] **/HelpFormatter.prototype.addArgument = function (action) {  if (action.help !== c.SUPPRESS) {    var self = this;    // find all invocations    var invocations = [ this._formatActionInvocation(action) ];    var invocationLength = invocations[0].length;    var actionLength;    if (action._getSubactions) {      this._indent();      action._getSubactions().forEach(function (subaction) {        var invocationNew = self._formatActionInvocation(subaction);        invocations.push(invocationNew);        invocationLength = Math.max(invocationLength, invocationNew.length);      });      this._dedent();    }    // update the maximum item length    actionLength = invocationLength + this._currentIndent;    this._actionMaxLength = Math.max(this._actionMaxLength, actionLength);    // add the item to the list    this._addItem(this._formatAction, [ action ]);  }};/** * HelpFormatter#addArguments(actions) -> Void * - actions (array): actions list * * Mass add arguments into current section * * ##### Example * *      formatter.startSection(actionGroup.title); *      formatter.addText(actionGroup.description); *      formatter.addArguments(actionGroup._groupActions); *      formatter.endSection(); * **/HelpFormatter.prototype.addArguments = function (actions) {  var self = this;  actions.forEach(function (action) {    self.addArgument(action);  });};//// Help-formatting methods///** * HelpFormatter#formatHelp -> string * * Format help * * ##### Example * *      formatter.addText(this.epilog); *      return formatter.formatHelp(); * **/HelpFormatter.prototype.formatHelp = function () {  var help = this._rootSection.formatHelp(this);  if (help) {    help = help.replace(this._longBreakMatcher, c.EOL + c.EOL);    help = $$.trimChars(help, c.EOL) + c.EOL;  }  return help;};HelpFormatter.prototype._joinParts = function (partStrings) {  return partStrings.filter(function (part) {    return (part && part !== c.SUPPRESS);  }).join('');};HelpFormatter.prototype._formatUsage = function (usage, actions, groups, prefix) {  if (!prefix && typeof prefix !== 'string') {    prefix = 'usage: ';  }  actions = actions || [];  groups = groups || [];  // if usage is specified, use that  if (usage) {    usage = sprintf(usage, { prog: this._prog });    // if no optionals or positionals are available, usage is just prog  } else if (!usage && actions.length === 0) {    usage = this._prog;    // if optionals and positionals are available, calculate usage  } else if (!usage) {    var prog = this._prog;    var optionals = [];    var positionals = [];    var actionUsage;    var textWidth;    // split optionals from positionals    actions.forEach(function (action) {      if (action.isOptional()) {        optionals.push(action);      } else {        positionals.push(action);      }    });    // build full usage string    actionUsage = this._formatActionsUsage([].concat(optionals, positionals), groups);    usage = [ prog, actionUsage ].join(' ');    // wrap the usage parts if it's too long    textWidth = this._width - this._currentIndent;    if ((prefix.length + usage.length) > textWidth) {      // break usage into wrappable parts      var regexpPart = new RegExp('\\(.*?\\)+|\\[.*?\\]+|\\S+', 'g');      var optionalUsage = this._formatActionsUsage(optionals, groups);      var positionalUsage = this._formatActionsUsage(positionals, groups);      var optionalParts = optionalUsage.match(regexpPart);      var positionalParts = positionalUsage.match(regexpPart) || [];      if (optionalParts.join(' ') !== optionalUsage) {        throw new Error('assert "optionalParts.join(\' \') === optionalUsage"');      }      if (positionalParts.join(' ') !== positionalUsage) {        throw new Error('assert "positionalParts.join(\' \') === positionalUsage"');      }      // helper for wrapping lines      /*eslint-disable func-style*/ // node 0.10 compat      var _getLines = function (parts, indent, prefix) {        var lines = [];        var line = [];        var lineLength = prefix ? prefix.length - 1 : indent.length - 1;        parts.forEach(function (part) {          if (lineLength + 1 + part.length > textWidth) {            lines.push(indent + line.join(' '));            line = [];            lineLength = indent.length - 1;          }          line.push(part);          lineLength += part.length + 1;        });        if (line) {          lines.push(indent + line.join(' '));        }        if (prefix) {          lines[0] = lines[0].substr(indent.length);        }        return lines;      };      var lines, indent, parts;      // if prog is short, follow it with optionals or positionals      if (prefix.length + prog.length <= 0.75 * textWidth) {        indent = $$.repeat(' ', (prefix.length + prog.length + 1));        if (optionalParts) {          lines = [].concat(            _getLines([ prog ].concat(optionalParts), indent, prefix),            _getLines(positionalParts, indent)          );        } else if (positionalParts) {          lines = _getLines([ prog ].concat(positionalParts), indent, prefix);        } else {          lines = [ prog ];        }        // if prog is long, put it on its own line      } else {        indent = $$.repeat(' ', prefix.length);        parts = optionalParts.concat(positionalParts);        lines = _getLines(parts, indent);        if (lines.length > 1) {          lines = [].concat(            _getLines(optionalParts, indent),            _getLines(positionalParts, indent)          );        }        lines = [ prog ].concat(lines);      }      // join lines into usage      usage = lines.join(c.EOL);    }  }  // prefix with 'usage:'  return prefix + usage + c.EOL + c.EOL;};HelpFormatter.prototype._formatActionsUsage = function (actions, groups) {  // find group indices and identify actions in groups  var groupActions = [];  var inserts = [];  var self = this;  groups.forEach(function (group) {    var end;    var i;    var start = actions.indexOf(group._groupActions[0]);    if (start >= 0) {      end = start + group._groupActions.length;      //if (actions.slice(start, end) === group._groupActions) {      if ($$.arrayEqual(actions.slice(start, end), group._groupActions)) {        group._groupActions.forEach(function (action) {          groupActions.push(action);        });        if (!group.required) {          if (inserts[start]) {            inserts[start] += ' [';          } else {            inserts[start] = '[';          }          inserts[end] = ']';        } else {          if (inserts[start]) {            inserts[start] += ' (';          } else {            inserts[start] = '(';          }          inserts[end] = ')';        }        for (i = start + 1; i < end; i += 1) {          inserts[i] = '|';        }      }    }  });  // collect all actions format strings  var parts = [];  actions.forEach(function (action, actionIndex) {    var part;    var optionString;    var argsDefault;    var argsString;    // suppressed arguments are marked with None    // remove | separators for suppressed arguments    if (action.help === c.SUPPRESS) {      parts.push(null);      if (inserts[actionIndex] === '|') {        inserts.splice(actionIndex, actionIndex);      } else if (inserts[actionIndex + 1] === '|') {        inserts.splice(actionIndex + 1, actionIndex + 1);      }      // produce all arg strings    } else if (!action.isOptional()) {      part = self._formatArgs(action, action.dest);      // if it's in a group, strip the outer []      if (groupActions.indexOf(action) >= 0) {        if (part[0] === '[' && part[part.length - 1] === ']') {          part = part.slice(1, -1);        }      }      // add the action string to the list      parts.push(part);    // produce the first way to invoke the option in brackets    } else {      optionString = action.optionStrings[0];      // if the Optional doesn't take a value, format is: -s or --long      if (action.nargs === 0) {        part = '' + optionString;      // if the Optional takes a value, format is: -s ARGS or --long ARGS      } else {        argsDefault = action.dest.toUpperCase();        argsString = self._formatArgs(action, argsDefault);        part = optionString + ' ' + argsString;      }      // make it look optional if it's not required or in a group      if (!action.required && groupActions.indexOf(action) < 0) {        part = '[' + part + ']';      }      // add the action string to the list      parts.push(part);    }  });  // insert things at the necessary indices  for (var i = inserts.length - 1; i >= 0; --i) {    if (inserts[i] !== null) {      parts.splice(i, 0, inserts[i]);    }  }  // join all the action items with spaces  var text = parts.filter(function (part) {    return !!part;  }).join(' ');  // clean up separators for mutually exclusive groups  text = text.replace(/([\[(]) /g, '$1'); // remove spaces  text = text.replace(/ ([\])])/g, '$1');  text = text.replace(/\[ *\]/g, ''); // remove empty groups  text = text.replace(/\( *\)/g, '');  text = text.replace(/\(([^|]*)\)/g, '$1'); // remove () from single action groups  text = text.trim();  // return the text  return text;};HelpFormatter.prototype._formatText = function (text) {  text = sprintf(text, { prog: this._prog });  var textWidth = this._width - this._currentIndent;  var indentIncriment = $$.repeat(' ', this._currentIndent);  return this._fillText(text, textWidth, indentIncriment) + c.EOL + c.EOL;};HelpFormatter.prototype._formatAction = function (action) {  var self = this;  var helpText;  var helpLines;  var parts;  var indentFirst;  // determine the required width and the entry label  var helpPosition = Math.min(this._actionMaxLength + 2, this._maxHelpPosition);  var helpWidth = this._width - helpPosition;  var actionWidth = helpPosition - this._currentIndent - 2;  var actionHeader = this._formatActionInvocation(action);  // no help; start on same line and add a final newline  if (!action.help) {    actionHeader = $$.repeat(' ', this._currentIndent) + actionHeader + c.EOL;  // short action name; start on the same line and pad two spaces  } else if (actionHeader.length <= actionWidth) {    actionHeader = $$.repeat(' ', this._currentIndent) +        actionHeader +        '  ' +        $$.repeat(' ', actionWidth - actionHeader.length);    indentFirst = 0;  // long action name; start on the next line  } else {    actionHeader = $$.repeat(' ', this._currentIndent) + actionHeader + c.EOL;    indentFirst = helpPosition;  }  // collect the pieces of the action help  parts = [ actionHeader ];  // if there was help for the action, add lines of help text  if (action.help) {    helpText = this._expandHelp(action);    helpLines = this._splitLines(helpText, helpWidth);    parts.push($$.repeat(' ', indentFirst) + helpLines[0] + c.EOL);    helpLines.slice(1).forEach(function (line) {      parts.push($$.repeat(' ', helpPosition) + line + c.EOL);    });  // or add a newline if the description doesn't end with one  } else if (actionHeader.charAt(actionHeader.length - 1) !== c.EOL) {    parts.push(c.EOL);  }  // if there are any sub-actions, add their help as well  if (action._getSubactions) {    this._indent();    action._getSubactions().forEach(function (subaction) {      parts.push(self._formatAction(subaction));    });    this._dedent();  }  // return a single string  return this._joinParts(parts);};HelpFormatter.prototype._formatActionInvocation = function (action) {  if (!action.isOptional()) {    var format_func = this._metavarFormatter(action, action.dest);    var metavars = format_func(1);    return metavars[0];  }  var parts = [];  var argsDefault;  var argsString;  // if the Optional doesn't take a value, format is: -s, --long  if (action.nargs === 0) {    parts = parts.concat(action.optionStrings);  // if the Optional takes a value, format is: -s ARGS, --long ARGS  } else {    argsDefault = action.dest.toUpperCase();    argsString = this._formatArgs(action, argsDefault);    action.optionStrings.forEach(function (optionString) {      parts.push(optionString + ' ' + argsString);    });  }  return parts.join(', ');};HelpFormatter.prototype._metavarFormatter = function (action, metavarDefault) {  var result;  if (action.metavar || action.metavar === '') {    result = action.metavar;  } else if (action.choices) {    var choices = action.choices;    if (typeof choices === 'string') {      choices = choices.split('').join(', ');    } else if (Array.isArray(choices)) {      choices = choices.join(',');    } else {      choices = Object.keys(choices).join(',');    }    result = '{' + choices + '}';  } else {    result = metavarDefault;  }  return function (size) {    if (Array.isArray(result)) {      return result;    }    var metavars = [];    for (var i = 0; i < size; i += 1) {      metavars.push(result);    }    return metavars;  };};HelpFormatter.prototype._formatArgs = function (action, metavarDefault) {  var result;  var metavars;  var buildMetavar = this._metavarFormatter(action, metavarDefault);  switch (action.nargs) {    /*eslint-disable no-undefined*/    case undefined:    case null:      metavars = buildMetavar(1);      result = '' + metavars[0];      break;    case c.OPTIONAL:      metavars = buildMetavar(1);      result = '[' + metavars[0] + ']';      break;    case c.ZERO_OR_MORE:      metavars = buildMetavar(2);      result = '[' + metavars[0] + ' [' + metavars[1] + ' ...]]';      break;    case c.ONE_OR_MORE:      metavars = buildMetavar(2);      result = '' + metavars[0] + ' [' + metavars[1] + ' ...]';      break;    case c.REMAINDER:      result = '...';      break;    case c.PARSER:      metavars = buildMetavar(1);      result = metavars[0] + ' ...';      break;    default:      metavars = buildMetavar(action.nargs);      result = metavars.join(' ');  }  return result;};HelpFormatter.prototype._expandHelp = function (action) {  var params = { prog: this._prog };  Object.keys(action).forEach(function (actionProperty) {    var actionValue = action[actionProperty];    if (actionValue !== c.SUPPRESS) {      params[actionProperty] = actionValue;    }  });  if (params.choices) {    if (typeof params.choices === 'string') {      params.choices = params.choices.split('').join(', ');    } else if (Array.isArray(params.choices)) {      params.choices = params.choices.join(', ');    } else {      params.choices = Object.keys(params.choices).join(', ');    }  }  return sprintf(this._getHelpString(action), params);};HelpFormatter.prototype._splitLines = function (text, width) {  var lines = [];  var delimiters = [ ' ', '.', ',', '!', '?' ];  var re = new RegExp('[' + delimiters.join('') + '][^' + delimiters.join('') + ']*$');  text = text.replace(/[\n\|\t]/g, ' ');  text = text.trim();  text = text.replace(this._whitespaceMatcher, ' ');  // Wraps the single paragraph in text (a string) so every line  // is at most width characters long.  text.split(c.EOL).forEach(function (line) {    if (width >= line.length) {      lines.push(line);      return;    }    var wrapStart = 0;    var wrapEnd = width;    var delimiterIndex = 0;    while (wrapEnd <= line.length) {      if (wrapEnd !== line.length && delimiters.indexOf(line[wrapEnd] < -1)) {        delimiterIndex = (re.exec(line.substring(wrapStart, wrapEnd)) || {}).index;        wrapEnd = wrapStart + delimiterIndex + 1;      }      lines.push(line.substring(wrapStart, wrapEnd));      wrapStart = wrapEnd;      wrapEnd += width;    }    if (wrapStart < line.length) {      lines.push(line.substring(wrapStart, wrapEnd));    }  });  return lines;};HelpFormatter.prototype._fillText = function (text, width, indent) {  var lines = this._splitLines(text, width);  lines = lines.map(function (line) {    return indent + line;  });  return lines.join(c.EOL);};HelpFormatter.prototype._getHelpString = function (action) {  return action.help;};
 |