mirror of
https://github.com/jsdoc/jsdoc.git
synced 2025-12-08 19:46:11 +00:00
302 lines
9.0 KiB
JavaScript
302 lines
9.0 KiB
JavaScript
/**
|
|
* Parse the command line arguments.
|
|
* @module jsdoc/opts/argparser
|
|
*/
|
|
const _ = require('lodash');
|
|
|
|
const hasOwnProp = Object.prototype.hasOwnProperty;
|
|
|
|
function padLeft(str, length) {
|
|
return str.padStart(str.length + length);
|
|
}
|
|
|
|
function padRight(str, length) {
|
|
return str.padEnd(str.length + length);
|
|
}
|
|
|
|
function findMaxLength(arr) {
|
|
let max = 0;
|
|
|
|
arr.forEach(({length}) => {
|
|
if (length > max) {
|
|
max = length;
|
|
}
|
|
});
|
|
|
|
return max;
|
|
}
|
|
|
|
function concatWithMaxLength(items, maxLength) {
|
|
let result = '';
|
|
|
|
// to prevent endless loops, always use the first item, regardless of length
|
|
result += items.shift();
|
|
|
|
while (items.length && (result.length + items[0].length < maxLength)) {
|
|
result += ` ${items.shift()}`;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// we want to format names and descriptions like this:
|
|
// | -f, --foo Very long description very long description very long |
|
|
// | description very long description. |
|
|
function formatHelpInfo({names, descriptions}) {
|
|
const MARGIN_LENGTH = 4;
|
|
const results = [];
|
|
|
|
const maxLength = process.stdout.columns;
|
|
const maxNameLength = findMaxLength(names);
|
|
const wrapDescriptionAt = maxLength - (MARGIN_LENGTH * 3) - maxNameLength;
|
|
|
|
// build the string for each option
|
|
names.forEach((name, i) => {
|
|
let result;
|
|
let partialDescription;
|
|
let words;
|
|
|
|
// add a left margin to the name
|
|
result = padLeft(names[i], MARGIN_LENGTH);
|
|
// and a right margin, with extra padding so the descriptions line up with one another
|
|
result = padRight(result, maxNameLength - names[i].length + MARGIN_LENGTH);
|
|
|
|
// split the description on spaces
|
|
words = descriptions[i].split(' ');
|
|
// add as much of the description as we can fit on the first line
|
|
result += concatWithMaxLength(words, wrapDescriptionAt);
|
|
// if there's anything left, keep going until we've consumed the description
|
|
while (words.length) {
|
|
partialDescription = padLeft('', maxNameLength + (MARGIN_LENGTH * 2));
|
|
partialDescription += concatWithMaxLength(words, wrapDescriptionAt);
|
|
result += `\n${partialDescription}`;
|
|
}
|
|
|
|
results.push(result);
|
|
});
|
|
|
|
return results;
|
|
}
|
|
|
|
/**
|
|
* A parser to interpret the key-value pairs entered on the command line.
|
|
*
|
|
* @alias module:jsdoc/opts/argparser
|
|
*/
|
|
class ArgParser {
|
|
/**
|
|
* Create an instance of the parser.
|
|
*/
|
|
constructor() {
|
|
this._options = [];
|
|
this._shortNameIndex = {};
|
|
this._longNameIndex = {};
|
|
}
|
|
|
|
_getOptionByShortName(name) {
|
|
if (hasOwnProp.call(this._shortNameIndex, name)) {
|
|
return this._options[this._shortNameIndex[name]];
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
_getOptionByLongName(name) {
|
|
if (hasOwnProp.call(this._longNameIndex, name)) {
|
|
return this._options[this._longNameIndex[name]];
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
_addOption(option) {
|
|
let currentIndex;
|
|
|
|
const longName = option.longName;
|
|
const shortName = option.shortName;
|
|
|
|
this._options.push(option);
|
|
currentIndex = this._options.length - 1;
|
|
|
|
if (shortName) {
|
|
this._shortNameIndex[shortName] = currentIndex;
|
|
}
|
|
if (longName) {
|
|
this._longNameIndex[longName] = currentIndex;
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Provide information about a legal option.
|
|
*
|
|
* @param {character} shortName - The short name of the option, entered like: -T.
|
|
* @param {string} longName - The equivalent long name of the option, entered like: --test.
|
|
* @param {boolean} hasValue - Does this option require a value? Like: -t templatename
|
|
* @param {string} helpText - A brief description of the option.
|
|
* @param {boolean} [canHaveMultiple=false] - Set to `true` if the option can be provided more
|
|
* than once.
|
|
* @param {function} [coercer] - A function to coerce the given value to a specific type.
|
|
* @return {this}
|
|
* @example
|
|
* myParser.addOption('t', 'template', true, 'The path to the template.');
|
|
* myParser.addOption('h', 'help', false, 'Show the help message.');
|
|
*/
|
|
addOption(shortName, longName, hasValue, helpText, canHaveMultiple = false, coercer) {
|
|
return this._addOption({
|
|
shortName: shortName,
|
|
longName: longName,
|
|
hasValue: hasValue,
|
|
helpText: helpText,
|
|
canHaveMultiple: canHaveMultiple,
|
|
coercer: coercer
|
|
});
|
|
}
|
|
|
|
// TODO: refactor addOption to accept objects, then get rid of this method
|
|
/**
|
|
* Provide information about an option that should not cause an error if present, but that is always
|
|
* ignored (for example, an option that was used in previous versions but is no longer supported).
|
|
*
|
|
* @private
|
|
* @param {string} shortName - The short name of the option with a leading hyphen (for example,
|
|
* `-v`).
|
|
* @param {string} longName - The long name of the option with two leading hyphens (for example,
|
|
* `--version`).
|
|
*/
|
|
addIgnoredOption(shortName, longName) {
|
|
return this._addOption({
|
|
shortName: shortName,
|
|
longName: longName,
|
|
ignore: true
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Generate a summary of all the options with corresponding help text.
|
|
* @returns {string}
|
|
*/
|
|
help() {
|
|
const options = {
|
|
names: [],
|
|
descriptions: []
|
|
};
|
|
|
|
this._options.forEach(option => {
|
|
let name = '';
|
|
|
|
// don't show ignored options
|
|
if (option.ignore) {
|
|
return;
|
|
}
|
|
|
|
if (option.shortName) {
|
|
name += `-${option.shortName}${option.longName ? ', ' : ''}`;
|
|
}
|
|
|
|
if (option.longName) {
|
|
name += `--${option.longName}`;
|
|
}
|
|
|
|
if (option.hasValue) {
|
|
name += ' <value>';
|
|
}
|
|
|
|
options.names.push(name);
|
|
options.descriptions.push(option.helpText);
|
|
});
|
|
|
|
return `Options:\n${formatHelpInfo(options).join('\n')}`;
|
|
}
|
|
|
|
/**
|
|
* Get the options.
|
|
* @param {Array.<string>} args An array, like ['-x', 'hello']
|
|
* @param {Object} [defaults={}] An optional collection of default values.
|
|
* @returns {Object} The keys will be the longNames, or the shortName if no longName is defined for
|
|
* that option. The values will be the values provided, or `true` if the option accepts no value.
|
|
*/
|
|
parse(args, defaults) {
|
|
let arg;
|
|
let next;
|
|
let option;
|
|
const result = (defaults && _.defaults({}, defaults)) || {};
|
|
let shortName;
|
|
let longName;
|
|
let name;
|
|
let value;
|
|
|
|
result._ = [];
|
|
for (let i = 0, l = args.length; i < l; i++) {
|
|
arg = String(args[i]);
|
|
next = (i < l - 1) ? String(args[i + 1]) : null;
|
|
shortName = null;
|
|
value = null;
|
|
|
|
// like -t
|
|
if (arg.charAt(0) === '-') {
|
|
// like --template
|
|
if (arg.charAt(1) === '-') {
|
|
name = longName = arg.slice(2);
|
|
option = this._getOptionByLongName(longName);
|
|
}
|
|
else {
|
|
name = shortName = arg.slice(1);
|
|
option = this._getOptionByShortName(shortName);
|
|
}
|
|
|
|
if (option === null) {
|
|
throw new Error(`Unknown command-line option "${name}".`);
|
|
}
|
|
|
|
if (option.hasValue) {
|
|
value = next;
|
|
i++;
|
|
|
|
if (value === null || value.charAt(0) === '-') {
|
|
throw new Error(`The command-line option "${name}" requires a value.`);
|
|
}
|
|
}
|
|
else {
|
|
value = true;
|
|
}
|
|
|
|
// skip ignored options now that we've consumed the option text
|
|
if (option.ignore) {
|
|
continue;
|
|
}
|
|
|
|
if (option.longName && shortName) {
|
|
name = option.longName;
|
|
}
|
|
|
|
if (typeof option.coercer === 'function') {
|
|
value = option.coercer(value);
|
|
}
|
|
|
|
// Allow for multiple options of the same type to be present
|
|
if (option.canHaveMultiple && hasOwnProp.call(result, name)) {
|
|
const val = result[name];
|
|
|
|
if (val instanceof Array) {
|
|
val.push(value);
|
|
} else {
|
|
result[name] = [val, value];
|
|
}
|
|
}
|
|
else {
|
|
result[name] = value;
|
|
}
|
|
}
|
|
else {
|
|
result._.push(arg);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
}
|
|
|
|
module.exports = ArgParser;
|