Spaces:
Runtime error
Runtime error
/** | |
* 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 | |
**/ | |
; | |
var sprintf = require('sprintf-js').sprintf; | |
// Constants | |
var 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; | |
}; | |