add @jsdoc/cli and @jsdoc/util modules

`@jsdoc/cli` deals with command-line arguments, and `@jsdoc/util` casts values to appropriate types.
This commit is contained in:
Jeff Williams 2019-01-26 12:02:55 -08:00
parent 4badaef5fd
commit 25c37411d9
30 changed files with 821 additions and 1083 deletions

4
cli.js
View File

@ -47,7 +47,7 @@ module.exports = (() => {
// TODO: docs
cli.loadConfig = () => {
const _ = require('lodash');
const args = require('jsdoc/opts/args');
const args = require('@jsdoc/cli').args;
let conf;
const config = require('@jsdoc/config');
@ -166,7 +166,7 @@ module.exports = (() => {
// TODO: docs
cli.printHelp = () => {
cli.printVersion();
console.log( `\n${require('jsdoc/opts/args').help()}\n` );
console.log( `\n${require('@jsdoc/cli').help}\n` );
console.log('Visit http://usejsdoc.org for more information.');
return Promise.resolve(0);

View File

@ -1,301 +0,0 @@
/**
* 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;

View File

@ -1,82 +0,0 @@
/**
* @module jsdoc/opts/args
* @requires jsdoc/opts/argparser
*/
const ArgParser = require('jsdoc/opts/argparser');
const cast = require('jsdoc/util/cast').cast;
const querystring = require('querystring');
let ourOptions;
const argParser = new ArgParser();
const hasOwnProp = Object.prototype.hasOwnProperty;
function parseQuery(str) {
return cast( querystring.parse(str) );
}
/* eslint-disable no-multi-spaces */
argParser.addOption('a', 'access', true, 'Only display symbols with the given access: "package", public", "protected", "private" or "undefined", or "all" for all access levels. Default: all except "private"', true);
argParser.addOption('c', 'configure', true, 'The path to the configuration file. Default: path/to/jsdoc/conf.json');
argParser.addOption('d', 'destination', true, 'The path to the output folder. Default: ./out/');
argParser.addOption('', 'debug', false, 'Log information for debugging JSDoc.');
argParser.addOption('e', 'encoding', true, 'Assume this encoding when reading all source files. Default: utf8');
argParser.addOption('h', 'help', false, 'Print this message and quit.');
argParser.addOption('', 'match', true, 'When running tests, only use specs whose names contain <value>.', true);
argParser.addOption('', 'nocolor', false, 'When running tests, do not use color in console output.');
argParser.addOption('p', 'private', false, 'Display symbols marked with the @private tag. Equivalent to "--access all". Default: false');
argParser.addOption('P', 'package', true, 'The path to the project\'s package file. Default: path/to/sourcefiles/package.json');
argParser.addOption('', 'pedantic', false, 'Treat errors as fatal errors, and treat warnings as errors. Default: false');
argParser.addOption('q', 'query', true, 'A query string to parse and store in jsdoc.env.opts.query. Example: foo=bar&baz=true', false, parseQuery);
argParser.addOption('r', 'recurse', false, 'Recurse into subdirectories when scanning for source files and tutorials.');
argParser.addOption('R', 'readme', true, 'The path to the project\'s README file. Default: path/to/sourcefiles/README.md');
argParser.addOption('t', 'template', true, 'The path to the template to use. Default: path/to/jsdoc/templates/default');
argParser.addOption('T', 'test', false, 'Run all tests and quit.');
argParser.addOption('u', 'tutorials', true, 'Directory in which JSDoc should search for tutorials.');
argParser.addOption('v', 'version', false, 'Display the version number and quit.');
argParser.addOption('', 'verbose', false, 'Log detailed information to the console as JSDoc runs.');
argParser.addOption('X', 'explain', false, 'Dump all found doclet internals to console and quit.');
/* eslint-enable no-multi-spaces */
// Options that are no longer supported and should be ignored
argParser.addIgnoredOption('l', 'lenient'); // removed in JSDoc 3.3.0
/**
* Set the options for this app.
* @throws {Error} Illegal arguments will throw errors.
* @param {string|String[]} args The command line arguments for this app.
*/
exports.parse = (args = []) => {
if (typeof args === 'string' || args.constructor === String) {
args = String(args).split(/\s+/g);
}
ourOptions = argParser.parse(args);
return ourOptions;
};
/**
* Retrieve help message for options.
*/
exports.help = () => argParser.help();
/**
* Get a named option.
* @variation name
* @param {string} name The name of the option.
* @return {string} The value associated with the given name.
*//**
* Get all the options for this app.
* @return {Object} A collection of key/values representing all the options.
*/
exports.get = name => {
if (typeof name === 'undefined') {
return ourOptions;
}
else if ( hasOwnProp.call(ourOptions, name) ) {
return ourOptions[name];
}
return undefined;
};

View File

@ -1,6 +1,6 @@
// TODO: docs
/** @module jsdoc/src/astnode */
const cast = require('jsdoc/util/cast').cast;
const cast = require('@jsdoc/util').cast;
const env = require('jsdoc/env');
const name = require('jsdoc/name');
const Syntax = require('@jsdoc/syntax');

View File

@ -1,14 +1,12 @@
/**
* @module jsdoc/tag/type
*/
const cast = require('@jsdoc/util').cast;
const catharsis = require('catharsis');
const jsdoc = {
name: require('jsdoc/name'),
tag: {
inline: require('jsdoc/tag/inline')
},
util: {
cast: require('jsdoc/util/cast')
}
};
const util = require('util');
@ -161,7 +159,7 @@ function parseName(tagInfo) {
// like 'foo=bar' or 'foo = bar'
if ( /^(.+?)\s*=\s*(.+)$/.test(tagInfo.name) ) {
tagInfo.name = RegExp.$1;
tagInfo.defaultvalue = jsdoc.util.cast.cast(RegExp.$2);
tagInfo.defaultvalue = cast(RegExp.$2);
}
}

193
package-lock.json generated
View File

@ -127,6 +127,97 @@
"to-fast-properties": "^2.0.0"
}
},
"@jsdoc/cli": {
"version": "file:packages/jsdoc-cli",
"requires": {
"@jsdoc/util": "file:packages/jsdoc-util",
"lodash": "^4.17.11",
"yargs-parser": "^11.1.1"
},
"dependencies": {
"ansi-regex": {
"version": "3.0.0",
"bundled": true
},
"camelcase": {
"version": "5.0.0",
"bundled": true
},
"cliui": {
"version": "4.1.0",
"bundled": true,
"requires": {
"string-width": "^2.1.1",
"strip-ansi": "^4.0.0",
"wrap-ansi": "^2.0.0"
}
},
"find-up": {
"version": "3.0.0",
"bundled": true,
"requires": {
"locate-path": "^3.0.0"
}
},
"invert-kv": {
"version": "2.0.0",
"bundled": true
},
"lcid": {
"version": "2.0.0",
"bundled": true,
"requires": {
"invert-kv": "^2.0.0"
}
},
"os-locale": {
"version": "3.1.0",
"bundled": true,
"requires": {
"execa": "^1.0.0",
"lcid": "^2.0.0",
"mem": "^4.0.0"
}
},
"strip-ansi": {
"version": "4.0.0",
"bundled": true,
"requires": {
"ansi-regex": "^3.0.0"
}
},
"which-module": {
"version": "2.0.0",
"bundled": true
},
"yargs": {
"version": "12.0.5",
"bundled": true,
"requires": {
"cliui": "^4.0.0",
"decamelize": "^1.2.0",
"find-up": "^3.0.0",
"get-caller-file": "^1.0.1",
"os-locale": "^3.0.0",
"require-directory": "^2.1.1",
"require-main-filename": "^1.0.1",
"set-blocking": "^2.0.0",
"string-width": "^2.0.0",
"which-module": "^2.0.0",
"y18n": "^3.2.1 || ^4.0.0",
"yargs-parser": "^11.1.1"
}
},
"yargs-parser": {
"version": "11.1.1",
"bundled": true,
"requires": {
"camelcase": "^5.0.0",
"decamelize": "^1.2.0"
}
}
}
},
"@jsdoc/config": {
"version": "file:packages/jsdoc-config",
"requires": {
@ -260,6 +351,9 @@
"@jsdoc/template-silent": {
"version": "file:packages/jsdoc-template-silent"
},
"@jsdoc/util": {
"version": "file:packages/jsdoc-util"
},
"@lerna/add": {
"version": "3.10.6",
"resolved": "https://registry.npmjs.org/@lerna/add/-/add-3.10.6.tgz",
@ -1338,8 +1432,7 @@
"ansi-regex": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
"dev": true
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
},
"ansi-styles": {
"version": "3.2.1",
@ -2186,8 +2279,7 @@
"code-point-at": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
"integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
"dev": true
"integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c="
},
"collection-map": {
"version": "1.0.0",
@ -2653,7 +2745,6 @@
"version": "6.0.5",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
"integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
"dev": true,
"requires": {
"nice-try": "^1.0.4",
"path-key": "^2.0.1",
@ -2728,8 +2819,7 @@
"decamelize": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
"dev": true
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA="
},
"decamelize-keys": {
"version": "1.1.0",
@ -3037,7 +3127,6 @@
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz",
"integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==",
"dev": true,
"requires": {
"once": "^1.4.0"
},
@ -3046,7 +3135,6 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"dev": true,
"requires": {
"wrappy": "1"
}
@ -3287,7 +3375,6 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz",
"integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==",
"dev": true,
"requires": {
"cross-spawn": "^6.0.0",
"get-stream": "^4.0.0",
@ -4387,8 +4474,7 @@
"get-caller-file": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz",
"integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==",
"dev": true
"integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w=="
},
"get-pkg-repo": {
"version": "1.4.0",
@ -4501,7 +4587,6 @@
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
"integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==",
"dev": true,
"requires": {
"pump": "^3.0.0"
},
@ -4510,7 +4595,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
"integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
"dev": true,
"requires": {
"end-of-stream": "^1.1.0",
"once": "^1.3.1"
@ -5346,8 +5430,7 @@
"is-fullwidth-code-point": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
"dev": true
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8="
},
"is-glob": {
"version": "4.0.0",
@ -5423,8 +5506,7 @@
"is-stream": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
"integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=",
"dev": true
"integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ="
},
"is-subset": {
"version": "0.1.1",
@ -5483,8 +5565,7 @@
"isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
"dev": true
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
},
"isobject": {
"version": "3.0.1",
@ -6002,7 +6083,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
"integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
"dev": true,
"requires": {
"p-locate": "^3.0.0",
"path-exists": "^3.0.0"
@ -6011,8 +6091,7 @@
"path-exists": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
"integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
"dev": true
"integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU="
}
}
},
@ -6143,7 +6222,6 @@
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz",
"integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==",
"dev": true,
"requires": {
"p-defer": "^1.0.0"
}
@ -6212,7 +6290,6 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/mem/-/mem-4.0.0.tgz",
"integrity": "sha512-WQxG/5xYc3tMbYLXoXPm81ET2WDULiU5FxbuIoNbJqLOOI8zehXFdZuiUEgfdrU2mVB1pxBZUGlYORSrpuJreA==",
"dev": true,
"requires": {
"map-age-cleaner": "^0.1.1",
"mimic-fn": "^1.0.0",
@ -6402,8 +6479,7 @@
"mimic-fn": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz",
"integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==",
"dev": true
"integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ=="
},
"minimatch": {
"version": "3.0.4",
@ -6618,8 +6694,7 @@
"nice-try": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
"integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
"dev": true
"integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ=="
},
"node-fetch-npm": {
"version": "2.0.2",
@ -6807,7 +6882,6 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
"integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=",
"dev": true,
"requires": {
"path-key": "^2.0.0"
}
@ -6827,8 +6901,7 @@
"number-is-nan": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
"integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
"dev": true
"integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0="
},
"nyc": {
"version": "13.1.0",
@ -8084,7 +8157,6 @@
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz",
"integrity": "sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA=",
"dev": true,
"requires": {
"wrappy": "1"
}
@ -8211,26 +8283,22 @@
"p-defer": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz",
"integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=",
"dev": true
"integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww="
},
"p-finally": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
"integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=",
"dev": true
"integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4="
},
"p-is-promise": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz",
"integrity": "sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4=",
"dev": true
"integrity": "sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4="
},
"p-limit": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.1.0.tgz",
"integrity": "sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g==",
"dev": true,
"requires": {
"p-try": "^2.0.0"
}
@ -8239,7 +8307,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
"integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
"dev": true,
"requires": {
"p-limit": "^2.0.0"
}
@ -8274,8 +8341,7 @@
"p-try": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz",
"integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==",
"dev": true
"integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ=="
},
"p-waterfall": {
"version": "1.0.0",
@ -8459,8 +8525,7 @@
"path-key": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
"integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=",
"dev": true
"integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A="
},
"path-parse": {
"version": "1.0.6",
@ -9008,14 +9073,12 @@
"require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
"dev": true
"integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I="
},
"require-main-filename": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz",
"integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=",
"dev": true
"integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE="
},
"requizzle": {
"version": "0.2.1",
@ -9171,8 +9234,7 @@
"semver": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz",
"integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==",
"dev": true
"integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg=="
},
"semver-greatest-satisfied-range": {
"version": "1.1.0",
@ -9186,8 +9248,7 @@
"set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
"dev": true
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
},
"set-value": {
"version": "2.0.0",
@ -9216,7 +9277,6 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
"integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
"dev": true,
"requires": {
"shebang-regex": "^1.0.0"
}
@ -9224,8 +9284,7 @@
"shebang-regex": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
"integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
"dev": true
"integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM="
},
"sigmund": {
"version": "1.0.1",
@ -9236,8 +9295,7 @@
"signal-exit": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
"integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=",
"dev": true
"integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0="
},
"slash": {
"version": "1.0.0",
@ -9578,7 +9636,6 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
"integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
"dev": true,
"requires": {
"is-fullwidth-code-point": "^2.0.0",
"strip-ansi": "^4.0.0"
@ -9587,14 +9644,12 @@
"ansi-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
"dev": true
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg="
},
"strip-ansi": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
"dev": true,
"requires": {
"ansi-regex": "^3.0.0"
}
@ -9620,7 +9675,6 @@
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"dev": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@ -9637,8 +9691,7 @@
"strip-eof": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
"integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=",
"dev": true
"integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8="
},
"strip-indent": {
"version": "2.0.0",
@ -10439,7 +10492,6 @@
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
"integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
"dev": true,
"requires": {
"isexe": "^2.0.0"
}
@ -10469,7 +10521,6 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
"integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
"dev": true,
"requires": {
"string-width": "^1.0.1",
"strip-ansi": "^3.0.1"
@ -10479,7 +10530,6 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
"integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
"dev": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@ -10488,7 +10538,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
"dev": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@ -10500,8 +10549,7 @@
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"dev": true
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
},
"write": {
"version": "0.2.1",
@ -10569,8 +10617,7 @@
"y18n": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz",
"integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=",
"dev": true
"integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE="
},
"yallist": {
"version": "2.1.2",

View File

@ -14,11 +14,13 @@
},
"dependencies": {
"@babel/parser": "~7.2.3",
"@jsdoc/cli": "file:packages/jsdoc-cli",
"@jsdoc/config": "file:packages/jsdoc-config",
"@jsdoc/logger": "file:packages/jsdoc-logger",
"@jsdoc/syntax": "file:packages/jsdoc-syntax",
"@jsdoc/template-original": "file:packages/jsdoc-template-original",
"@jsdoc/template-silent": "file:packages/jsdoc-template-silent",
"@jsdoc/util": "file:packages/jsdoc-util",
"bluebird": "~3.5.0",
"catharsis": "~0.8.9",
"escape-string-regexp": "~1.0.5",

View File

@ -0,0 +1,11 @@
# @jsdoc/cli
The code that powers JSDoc's command-line interface.
## Installing the package
Using npm:
```shell
npm install --save @jsdoc/cli
```

View File

@ -0,0 +1,9 @@
/**
* @module @jsdoc/cli
*/
const args = require('./lib/args');
const help = require('./lib/help');
exports.args = args;
exports.help = help;

View File

@ -0,0 +1,176 @@
/**
* @module @jsdoc/cli/lib/args
*/
const _ = require('lodash');
const cast = require('@jsdoc/util').cast;
const querystring = require('querystring');
const yargs = require('yargs-parser');
const ARGS = exports.ARGS = {
access: {
alias: 'a',
array: true,
choices: ['all', 'package', 'private', 'protected', 'public', 'undefined'],
defaultDescription: 'All except `private`',
description: 'Document only symbols with the specified access level.',
requiresArg: true
},
configure: {
alias: 'c',
description: 'The configuration file to use.',
normalize: true,
requiresArg: true
},
debug: {
boolean: true,
description: 'Log information to help with debugging.'
},
destination: {
alias: 'd',
default: './out',
description: 'The output directory.',
normalize: true,
requiresArg: true
},
encoding: {
alias: 'e',
default: 'utf8',
description: 'The encoding to assume when reading source files.',
requiresArg: true
},
explain: {
alias: 'X',
boolean: true,
description: 'Print the parse results to the console and exit.'
},
help: {
alias: 'h',
boolean: true,
description: 'Print help information and exit.'
},
match: {
description: 'Run only tests whose names contain this value.',
requiresArg: true
},
package: {
alias: 'P',
description: 'The path to the `package.json` file to use.',
normalize: true,
requiresArg: true
},
pedantic: {
boolean: true,
description: 'Treat errors as fatal errors, and treat warnings as errors.'
},
private: {
alias: 'p',
boolean: true,
description: 'Document private symbols (equivalent to `--access all`).'
},
query: {
alias: 'q',
coerce: ((str) => cast(querystring.parse(str))),
description: 'A query string to parse and store (for example, `foo=bar&baz=true`).',
requiresArg: true
},
readme: {
alias: 'R',
boolean: true,
description: 'The `README` file to include in the documentation.',
normalize: true,
requiresArg: true
},
recurse: {
alias: 'r',
boolean: true,
description: 'Recurse into subdirectories to find source files.'
},
template: {
alias: 't',
description: 'The template package to use.',
requiresArg: true
},
test: {
alias: 'T',
boolean: true,
description: 'Run all tests and exit.'
},
tutorials: {
alias: 'u',
description: 'The directory to search for tutorials.',
normalize: true,
requiresArg: true
},
verbose: {
boolean: true,
description: 'Log detailed information to the console.'
},
version: {
alias: 'v',
boolean: true,
description: 'Display the version number and exit.'
}
};
function validateValue(arg, choices, values) {
for (let value of values) {
if (!choices.includes(value)) {
throw new Error(`The argument --${arg} accepts only these values: ${choices}`);
}
}
}
const YARGS_OPTS = (() => {
const opts = {
alias: {},
array: [],
boolean: [],
coerce: {},
narg: {},
normalize: []
};
Object.keys(ARGS).forEach(arg => {
const value = ARGS[arg];
if (value.alias) {
opts.alias[arg] = [value.alias];
}
if (value.array) {
opts.array.push(arg);
}
if (value.boolean) {
opts.boolean.push(arg);
}
if (value.coerce) {
opts.coerce[arg] = value.coerce;
}
if (value.normalize) {
opts.normalize.push(arg);
}
if (value.requiresArg) {
opts.narg[arg] = 1;
}
});
return opts;
})();
exports.parse = (args => {
const knownArgs = Object.keys(ARGS);
const parsed = yargs(args, YARGS_OPTS);
knownArgs.forEach(arg => {
if (parsed[arg] && ARGS[arg].choices) {
validateValue(arg, ARGS[arg].choices, parsed[arg]);
}
});
return _.pick(parsed, knownArgs.concat(['_']));
});

View File

@ -0,0 +1,115 @@
/**
* @module @jsdoc/cli/lib/help
*/
const ARGS = require('./args').ARGS;
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}) => {
max = Math.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;
}
module.exports = (() => {
const options = {
names: [],
descriptions: []
};
Object.keys(ARGS)
.sort()
.forEach(arg => {
const opts = ARGS[arg];
let description = '';
let name = '';
if (opts.alias) {
name += `-${opts.alias}, `;
}
name += `--${arg}`;
if (opts.requiresArg) {
name += ' <value>';
}
description += opts.description;
if (opts.array) {
description += ' Can be specified more than once.';
}
if (opts.choices) {
description += ` Accepts these values: ${opts.choices.join(', ')}`;
}
options.names.push(name);
options.descriptions.push(description);
});
return `Options:\n${formatHelpInfo(options).join('\n')}`;
})();

35
packages/jsdoc-cli/package-lock.json generated Normal file
View File

@ -0,0 +1,35 @@
{
"name": "@jsdoc/cli",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@jsdoc/util": {
"version": "file:../jsdoc-util"
},
"camelcase": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz",
"integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA=="
},
"decamelize": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA="
},
"lodash": {
"version": "4.17.11",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg=="
},
"yargs-parser": {
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz",
"integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==",
"requires": {
"camelcase": "^5.0.0",
"decamelize": "^1.2.0"
}
}
}
}

View File

@ -0,0 +1,27 @@
{
"name": "@jsdoc/cli",
"version": "1.0.0",
"description": "The code that powers JSDoc's command-line interface.",
"main": "index.js",
"repository": {
"type": "git",
"url": "https://github.com/jsdoc3/jsdoc"
},
"keywords": [
"jsdoc"
],
"author": {
"name": "Jeff Williams",
"email": "jeffrey.l.williams@gmail.com"
},
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/jsdoc3/jsdoc/issues"
},
"homepage": "https://github.com/jsdoc3/jsdoc#readme",
"dependencies": {
"@jsdoc/util": "file:../jsdoc-util",
"lodash": "^4.17.11",
"yargs-parser": "^11.1.1"
}
}

View File

@ -0,0 +1,31 @@
const cli = require('../../index');
describe('@jsdoc/cli', () => {
it('is an object', () => {
expect(cli).toBeObject();
});
it('has an args object', () => {
expect(cli.args).toBeObject();
});
it('has a help string', () => {
expect(cli.help).toBeString();
});
describe('args', () => {
it('is ./lib/args', () => {
const args = require('../../lib/args');
expect(cli.args).toBe(args);
});
});
describe('help', () => {
it('is ./lib/help', () => {
const help = require('../../lib/help');
expect(cli.help).toBe(help);
});
});
});

View File

@ -0,0 +1,99 @@
const args = require('../../../lib/args');
const ARGS = args.ARGS;
describe('@jsdoc/cli/lib/args', () => {
it('exists', () => {
expect(args).toBeObject();
});
it('has an `ARGS` object', () => {
expect(args.ARGS).toBeObject();
});
it('has a `parse` function', () => {
expect(args.parse).toBeFunction();
});
describe('ARGS', () => {
// No need to test this object directly.
});
describe('parse', () => {
it('accepts an array and returns an object', () => {
const parsed = args.parse([]);
expect(parsed).toBeObject();
});
it('parses all known options', () => {
const argv = [];
Object.keys(ARGS).forEach(arg => {
argv.push(`--${arg}`);
if (ARGS[arg].requiresArg) {
argv.push(ARGS[arg].choices ? ARGS[arg].choices[0] : `value-for-${arg}`);
}
});
const parsed = args.parse(argv);
expect(Object.keys(parsed).sort())
.toEqual(Object.keys(ARGS).concat(['_']).sort());
});
it('puts extra arguments in the `_` array', () => {
const parsed = args.parse(['foo.js']);
expect(parsed._).toEqual(['foo.js']);
});
it('ignores unknown options', () => {
const parsed = args.parse(['--foo']);
expect(parsed.foo).toBeUndefined();
});
describe('--access', () => {
it('is returned as an array even when specified only once', () => {
const parsed = args.parse(['--access', 'public']);
expect(parsed.access).toEqual(['public']);
});
it('accepts the argument more than once', () => {
const parsed = args.parse([
'--access',
'protected',
'--access',
'public'
]);
expect(parsed.access.sort()).toEqual(['protected', 'public']);
});
it('throws on unknown values', () => {
function badArg() {
return args.parse(['--access', 'ibility']);
}
expect(badArg).toThrowError();
});
});
describe('--query', () => {
it('is returned as an object', () => {
const parsed = args.parse(['--query', 'foo=bar']);
expect(parsed.query).toEqual({foo: 'bar'});
});
it('coerces values to the appropriate types', () => {
const parsed = args.parse(['--query', 'foo=true&bar=17']);
expect(parsed.query.foo).toBeTrue();
expect(parsed.query.bar).toBe(17);
});
});
});
});

View File

@ -0,0 +1,14 @@
const ARGS = require('../../../lib/args').ARGS;
const help = require('../../../lib/help');
describe('@jsdoc/cli/lib/help', () => {
it('is a string', () => {
expect(help).toBeString();
});
it('covers all known command-line arguments', () => {
Object.keys(ARGS).forEach(arg => {
expect(help.includes(arg)).toBeTrue();
});
});
});

View File

@ -4,7 +4,7 @@
const _ = require('lodash');
const cosmiconfig = require('cosmiconfig');
const defaults = require('./defaults');
const defaults = require('./lib/defaults');
const stripBom = require('strip-bom');
const stripJsonComments = require('strip-json-comments');
@ -44,6 +44,8 @@ const explorer = cosmiconfig(MODULE_NAME, {
]
});
exports.defaults = defaults;
exports.loadSync = (filepath) => {
let loaded;

View File

@ -1,12 +1,13 @@
/**
* The default configuration settings for JSDoc.
*
* @module @jsdoc/config/defaults
* @module @jsdoc/config/lib/defaults
*/
module.exports = {
// TODO(hegemonic): integrate CLI options with other options
opts: {
access: ['package', 'protected', 'public', 'undefined'],
destination: './out',
encoding: 'utf8'
},

View File

@ -1,7 +1,7 @@
describe('@jsdoc/config', () => {
const mockFs = require('mock-fs');
const config = require('../../index');
const defaults = require('../../defaults');
const defaults = require('../../lib/defaults');
afterEach(() => mockFs.restore());
@ -9,10 +9,20 @@ describe('@jsdoc/config', () => {
expect(config).toBeObject();
});
it('has a defaults object', () => {
expect(config.defaults).toBeObject();
});
it('has a loadSync method', () => {
expect(config.loadSync).toBeFunction();
});
describe('defaults', () => {
it('is ./lib/defaults', () => {
expect(config.defaults).toBe(defaults);
});
});
describe('loadSync', () => {
it('returns an object with `config` and `filepath` properties', () => {
mockFs({

View File

@ -1,6 +1,6 @@
const defaults = require('../../defaults');
const defaults = require('../../../lib/defaults');
describe('@jsdoc/config/defaults', () => {
describe('@jsdoc/config/lib/defaults', () => {
it('exists', () => {
expect(defaults).toBeObject();
});

View File

@ -0,0 +1,11 @@
# @jsdoc/util
Utility modules for JSDoc.
## Installing the package
Using npm:
```shell
npm install --save @jsdoc/util
```

View File

@ -0,0 +1,7 @@
/**
* @module @jsdoc/util
*/
const cast = require('./lib/cast');
exports.cast = cast;

View File

@ -1,7 +1,7 @@
/**
* Module to convert values between various JavaScript types.
* @module
* @private
*
* @module @jsdoc/util/lib/cast
*/
/**
@ -46,7 +46,7 @@ function castString(str) {
number = parseInt(str, 10);
}
if ( String(number) === str && !isNaN(number) ) {
if (String(number) === str && !isNaN(number)) {
result = number;
}
else {
@ -65,14 +65,13 @@ function castString(str) {
* If an object or array is passed to this method, the object or array's values will be recursively
* converted to the appropriate types. The original object or array is not modified.
*
* @private
* @param {(string|Object|Array)} item - The item whose type will be converted.
* @return {(string|number|boolean|Object|Array)} The converted value.
*/
exports.cast = function cast(item) {
const cast = module.exports = (item => {
let result;
if ( Array.isArray(item) ) {
if (Array.isArray(item)) {
result = [];
for (let i = 0, l = item.length; i < l; i++) {
result[i] = cast(item[i]);
@ -92,4 +91,4 @@ exports.cast = function cast(item) {
}
return result;
};
});

5
packages/jsdoc-util/package-lock.json generated Normal file
View File

@ -0,0 +1,5 @@
{
"name": "@jsdoc/util",
"version": "1.0.0",
"lockfileVersion": 1
}

View File

@ -0,0 +1,22 @@
{
"name": "@jsdoc/util",
"version": "1.0.0",
"description": "Utility modules for JSDoc.",
"main": "index.js",
"repository": {
"type": "git",
"url": "https://github.com/jsdoc3/jsdoc"
},
"keywords": [
"jsdoc"
],
"author": {
"name": "Jeff Williams",
"email": "jeffrey.l.williams@gmail.com"
},
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/jsdoc3/jsdoc/issues"
},
"homepage": "https://github.com/jsdoc3/jsdoc#readme"
}

View File

@ -0,0 +1,19 @@
const util = require('../../index');
describe('@jsdoc/util', () => {
it('is an object', () => {
expect(util).toBeObject();
});
it('has a cast method', () => {
expect(util.cast).toBeFunction();
});
describe('cast', () => {
it('is ./lib/cast', () => {
const cast = require('../../lib/cast');
expect(util.cast).toBe(cast);
});
});
});

View File

@ -0,0 +1,89 @@
const cast = require('../../../lib/cast');
describe('@jsdoc/util/lib/cast', () => {
it('exists', () => {
expect(cast).toBeFunction();
});
it('does not modify values that are not strings, objects, or arrays', () => {
const result = cast(8);
expect(result).toBe(8);
});
it('does not modify strings that are not boolean-ish or number-ish', () => {
const result = cast('hello world');
expect(result).toBe('hello world');
});
it('casts boolean-ish values to booleans', () => {
const truthish = cast('true');
const falsish = cast('false');
expect(truthish).toBeTrue();
expect(falsish).toBeFalse();
});
it('casts null-ish values to null', () => {
const nullish = cast('null');
expect(nullish).toBeNull();
});
it('casts undefined-ish values to undefined', () => {
const undefinedish = cast('undefined');
expect(undefinedish).toBeUndefined();
});
it('casts positive number-ish values to numbers', () => {
const positive = cast('17.35');
expect(positive).toBe(17.35);
});
it('casts negative number-ish values to numbers', () => {
const negative = cast('-17.35');
expect(negative).toBe(-17.35);
});
it('casts NaN-ish values to NaN', () => {
const nan = cast('NaN');
expect(nan).toBeNaN();
});
it('converts values in an object', () => {
const result = cast({ foo: 'true' });
expect(result).toEqual({ foo: true });
});
it('converts values in nested objects', () => {
const result = cast({
foo: {
bar: 'true'
}
});
expect(result).toEqual({
foo: {
bar: true
}
});
});
it('converts values in an array', () => {
const result = cast(['true', '17.35']);
expect(result).toEqual([true, 17.35]);
});
it('converts values in a nested array', () => {
const result = cast(['true', ['17.35']]);
expect(result).toEqual([true, [17.35]]);
});
});

View File

@ -1,168 +0,0 @@
describe('jsdoc/opts/argparser', () => {
const ArgParser = require('jsdoc/opts/argparser');
let argParser;
let ourOptions;
function trueFalse(v) {
let r = false;
if (v) {
if (v === 'true') { r = true; }
else if (v === 'false') { r = false; }
else { v = Boolean(r); }
}
return r;
}
beforeEach(() => {
argParser = new ArgParser()
.addOption('s', 'strict', true, 'Throw error on invalid input.', false, trueFalse)
.addOption('n', 'name', true, 'The name of the project.', false);
ourOptions = argParser.parse(['-s', 'true', '-n', 'true']);
});
it('should exist', () => {
expect(ArgParser).toBeDefined();
});
it('should be a constructor', () => {
expect(typeof ArgParser).toBe('function');
expect(new ArgParser() instanceof ArgParser).toBe(true);
});
describe('ArgParser', () => {
it('should provide an "addIgnoredOption" method', () => {
expect(argParser.addIgnoredOption).toBeDefined();
expect(typeof argParser.addIgnoredOption).toBe('function');
});
it('should provide an "addOption" method', () => {
expect(argParser.addOption).toBeDefined();
expect(typeof argParser.addOption).toBe('function');
});
it('should provide a "help" method', () => {
expect(argParser.help).toBeDefined();
expect(typeof argParser.help).toBe('function');
});
it('should provide a "parse" method', () => {
expect(argParser.parse).toBeDefined();
expect(typeof argParser.parse).toBe('function');
});
describe('addIgnoredOption', () => {
it('should be chainable', () => {
expect(argParser.addIgnoredOption({})).toBe(argParser);
});
});
describe('addOption', () => {
it('should be chainable', () => {
expect(argParser.addOption('a', null, false, 'Option')).toBe(argParser);
});
});
describe('help', () => {
const columns = process.stdout.columns;
beforeEach(() => {
process.stdout.columns = 80;
});
afterEach(() => {
process.stdout.columns = columns;
});
it('should format the help text correctly', () => {
const helpText = [
'Options:',
' --noshortname Just a long name.',
' -o <value> Only a short name.',
' -s, --supercalifragilisticexpialidocious If you say it loud enough,',
" you'll always sound",
' precocious.'
].join('\n');
argParser = new ArgParser()
.addIgnoredOption('m', 'meh', false, 'Ignore me.')
.addOption(null, 'noshortname', false, 'Just a long name.')
.addOption('o', null, true, 'Only a short name.')
.addOption('s', 'supercalifragilisticexpialidocious', false,
"If you say it loud enough, you'll always sound precocious.");
expect(argParser.help()).toBe(helpText);
});
});
describe('parse', () => {
it('should return an object with information about the options', () => {
expect(typeof ourOptions).toBe('object');
expect(ourOptions.strict).toBe(true);
expect(ourOptions.name).toBe('true');
});
it('should merge the defaults into the command-line options', () => {
const defaults = {
strict: false,
name: 'Hello world!'
};
ourOptions = argParser.parse(['-s', true], defaults);
expect(ourOptions.strict).toBe(true);
expect(ourOptions.name).toBe(defaults.name);
});
it('should recognize options that can be used more than once', () => {
argParser.addOption(null, 'multi', true, '', true);
ourOptions = argParser.parse(['--multi', 'value1', '--multi', 'value2',
'--multi', 'value3']);
expect(Array.isArray(ourOptions.multi)).toBe(true);
expect(ourOptions.multi.length).toBe(3);
expect(ourOptions.multi[0]).toBe('value1');
expect(ourOptions.multi[1]).toBe('value2');
expect(ourOptions.multi[2]).toBe('value3');
});
it('should throw an error if an unrecognized short option is used', () => {
function badShortOption() {
argParser.parse(['-w']);
}
expect(badShortOption).toThrow();
});
it('should throw an error if an unrecognized long option is used', () => {
function badLongOption() {
argParser.parse(['--whatever']);
}
expect(badLongOption).toThrow();
});
it('should throw an error if a required value is missing', () => {
function missingValue() {
argParser.parse(['--requires-value']);
}
argParser.addOption(null, 'requires-value', true, '');
expect(missingValue).toThrow();
});
it('should coerce a true value if a coercer is provided', () => {
expect(ourOptions.strict).toBeDefined();
expect(ourOptions.strict).toBe(true);
});
it('should coerce a string value if no coercer is provided', () => {
expect(ourOptions.name).toBeDefined();
expect(ourOptions.name).toBe('true');
});
});
});
});

View File

@ -1,344 +0,0 @@
describe('jsdoc/opts/args', () => {
const args = require('jsdoc/opts/args');
const querystring = require('querystring');
it('should exist', () => {
expect(args).toBeDefined();
expect(typeof args).toBe('object');
});
it('should export a "parse" function', () => {
expect(args.parse).toBeDefined();
expect(typeof args.parse).toBe('function');
});
it('should export a "help" function', () => {
expect(args.help).toBeDefined();
expect(typeof args.help).toBe('function');
});
it('should export a "get" function', () => {
expect(args.get).toBeDefined();
expect(typeof args.get).toBe('function');
});
describe('parse', () => {
it('should accept a "-t" option and return an object with a "template" property', () => {
args.parse(['-t', 'mytemplate']);
const r = args.get();
expect(r.template).toBe('mytemplate');
});
it('should accept a "--template" option and return an object with a "template" property', () => {
args.parse(['--template', 'mytemplate']);
const r = args.get();
expect(r.template).toBe('mytemplate');
});
it('should accept a "-c" option with a JSON file and return an object with a "configure" property', () => {
args.parse(['-c', 'myconf.json']);
const r = args.get();
expect(r.configure).toBe('myconf.json');
});
it('should accept a "-c" option with a JS file and return an object with a "configure" property', () => {
args.parse(['-c', 'myconf.js']);
const r = args.get();
expect(r.configure).toBe('myconf.js');
});
it('should accept a "--configure" option with a JSON file and return an object with a "configure" property', () => {
args.parse(['--configure', 'myconf.json']);
const r = args.get();
expect(r.configure).toBe('myconf.json');
});
it('should accept a "--configure" option with a JS file and return an object with a "configure" property', () => {
args.parse(['--configure', 'myconf.js']);
const r = args.get();
expect(r.configure).toBe('myconf.js');
});
it('should accept an "-e" option and return an object with a "encoding" property', () => {
args.parse(['-e', 'ascii']);
const r = args.get();
expect(r.encoding).toBe('ascii');
});
it('should accept an "--encoding" option and return an object with an "encoding" property', () => {
args.parse(['--encoding', 'ascii']);
const r = args.get();
expect(r.encoding).toBe('ascii');
});
it('should accept a "-T" option and return an object with a "test" property', () => {
args.parse(['-T']);
const r = args.get();
expect(r.test).toBe(true);
});
it('should accept a "--test" option and return an object with a "test" property', () => {
args.parse(['--test']);
const r = args.get();
expect(r.test).toBe(true);
});
it('should accept a "-d" option and return an object with a "destination" property', () => {
args.parse(['-d', 'mydestination']);
const r = args.get();
expect(r.destination).toBe('mydestination');
});
it('should accept a "--destination" option and return an object with a "destination" property', () => {
args.parse(['--destination', 'mydestination']);
const r = args.get();
expect(r.destination).toBe('mydestination');
});
it('should accept a "-p" option and return an object with a "private" property', () => {
args.parse(['-p']);
const r = args.get();
expect(r.private).toBe(true);
});
it('should accept a "--private" option and return an object with a "private" property', () => {
args.parse(['--private']);
const r = args.get();
expect(r.private).toBe(true);
});
it('should accept a "-a" option and return an object with an "access" property', () => {
args.parse(['-a', 'public']);
const r = args.get();
expect(r.access).toBe('public');
});
it('should accept a "--access" option and return an object with an "access" property', () => {
args.parse(['--access', 'public']);
const r = args.get();
expect(r.access).toBe('public');
});
it('should accept multiple "--access" options and return an object with an "access" property', () => {
args.parse(['--access', 'public', '--access', 'protected']);
const r = args.get();
expect(r.access).toContain('public');
expect(r.access).toContain('protected');
});
it('should accept a "-r" option and return an object with a "recurse" property', () => {
args.parse(['-r']);
const r = args.get();
expect(r.recurse).toBe(true);
});
it('should accept a "--recurse" option and return an object with a "recurse" property', () => {
args.parse(['--recurse']);
const r = args.get();
expect(r.recurse).toBe(true);
});
it('should accept a "-l" option and ignore it', () => {
args.parse(['-l']);
const r = args.get();
expect(r.lenient).not.toBeDefined();
});
it('should accept a "--lenient" option and ignore it', () => {
args.parse(['--lenient']);
const r = args.get();
expect(r.lenient).not.toBeDefined();
});
it('should accept a "-h" option and return an object with a "help" property', () => {
args.parse(['-h']);
const r = args.get();
expect(r.help).toBe(true);
});
it('should accept a "--help" option and return an object with a "help" property', () => {
args.parse(['--help']);
const r = args.get();
expect(r.help).toBe(true);
});
it('should accept an "-X" option and return an object with an "explain" property', () => {
args.parse(['-X']);
const r = args.get();
expect(r.explain).toBe(true);
});
it('should accept an "--explain" option and return an object with an "explain" property', () => {
args.parse(['--explain']);
const r = args.get();
expect(r.explain).toBe(true);
});
it('should accept a "-q" option and return an object with a "query" property', () => {
args.parse(['-q', 'foo=bar&fab=baz']);
const r = args.get();
expect(r.query).toEqual({
foo: 'bar',
fab: 'baz'
});
});
it('should accept a "--query" option and return an object with a "query" property', () => {
args.parse(['--query', 'foo=bar&fab=baz']);
const r = args.get();
expect(r.query).toEqual({
foo: 'bar',
fab: 'baz'
});
});
it('should use type coercion on the "query" property so it has real booleans and numbers', () => {
const obj = {
foo: 'fab',
bar: true,
baz: false,
qux: [1, -97]
};
let r;
args.parse(['-q', querystring.stringify(obj)]);
r = args.get();
expect(r.query).toEqual(obj);
});
it('should accept a "-u" option and return an object with a "tutorials" property', () => {
args.parse(['-u', 'mytutorials']);
const r = args.get();
expect(r.tutorials).toBe('mytutorials');
});
it('should accept a "--tutorials" option and return an object with a "tutorials" property', () => {
args.parse(['--tutorials', 'mytutorials']);
const r = args.get();
expect(r.tutorials).toBe('mytutorials');
});
it('should accept a "--debug" option and return an object with a "debug" property', () => {
args.parse(['--debug']);
const r = args.get();
expect(r.debug).toBe(true);
});
it('should accept a "--verbose" option and return an object with a "verbose" property', () => {
args.parse(['--verbose']);
const r = args.get();
expect(r.verbose).toBe(true);
});
it('should accept a "--pedantic" option and return an object with a "pedantic" property', () => {
args.parse(['--pedantic']);
const r = args.get();
expect(r.pedantic).toBe(true);
});
it('should accept a "--match" option and return an object with a "match" property', () => {
args.parse(['--match', '.*tag']);
const r = args.get();
expect(r.match).toBe('.*tag');
});
it('should accept multiple "--match" options and return an object with a "match" property', () => {
args.parse(['--match', '.*tag', '--match', 'parser']);
const r = args.get();
expect(r.match).toEqual(['.*tag', 'parser']);
});
it('should accept a "--nocolor" option and return an object with a "nocolor" property', () => {
args.parse(['--nocolor']);
const r = args.get();
expect(r.nocolor).toBe(true);
});
it('should accept a "-P" option and return an object with a "package" property', () => {
args.parse(['-P', 'path/to/package/file.json']);
const r = args.get();
expect(r.package).toBe('path/to/package/file.json');
});
it('should accept a "--package" option and return an object with a "package" property', () => {
args.parse(['--package', 'path/to/package/file.json']);
const r = args.get();
expect(r.package).toBe('path/to/package/file.json');
});
it('should accept a "-R" option and return an object with a "readme" property', () => {
args.parse(['-R', 'path/to/readme/file.md']);
const r = args.get();
expect(r.readme).toBe('path/to/readme/file.md');
});
it('should accept a "--readme" option and return an object with a "readme" property', () => {
args.parse(['--readme', 'path/to/readme/file.md']);
const r = args.get();
expect(r.readme).toBe('path/to/readme/file.md');
});
it('should accept a "-v" option and return an object with a "version" property', () => {
args.parse(['-v']);
const r = args.get();
expect(r.version).toBe(true);
});
it('should accept a "--version" option and return an object with a "version" property', () => {
args.parse(['--version']);
const r = args.get();
expect(r.version).toBe(true);
});
it('should accept a naked option (with no "-") and return an object with a "_" property', () => {
args.parse(['myfile1', 'myfile2']);
const r = args.get();
expect(r._).toEqual(['myfile1', 'myfile2']);
});
// TODO: tests for args that must have values
});
});

View File

@ -1,96 +0,0 @@
describe('jsdoc/util/cast', () => {
const cast = require('jsdoc/util/cast');
it('should exist', () => {
expect(typeof cast).toBe('object');
});
it('should export a "cast" method', () => {
expect(typeof cast.cast).toBe('function');
});
describe('cast', () => {
it('should not modify values that are not strings, objects, or arrays', () => {
const result = cast.cast(8);
expect(result).toBe(8);
});
it('should not modify strings that are not boolean-ish or number-ish', () => {
const result = cast.cast('hello world');
expect(result).toBe('hello world');
});
it('should cast boolean-ish values to booleans', () => {
const truthish = cast.cast('true');
const falsish = cast.cast('false');
expect(truthish).toBe(true);
expect(falsish).toBe(false);
});
it('should cast null-ish values to null', () => {
const nullish = cast.cast('null');
expect(nullish).toBe(null);
});
it('should cast undefined-ish values to undefined', () => {
const undefinedish = cast.cast('undefined');
expect(undefinedish).toBeUndefined();
});
it('should cast positive number-ish values to numbers', () => {
const positive = cast.cast('17.35');
expect(positive).toBe(17.35);
});
it('should cast negative number-ish values to numbers', () => {
const negative = cast.cast('-17.35');
expect(negative).toBe(-17.35);
});
it('should cast NaN-ish values to NaN', () => {
const nan = cast.cast('NaN');
expect(typeof nan).toBe('number');
expect(isNaN(nan)).toBe(true);
});
it('should convert values in an object', () => {
const result = cast.cast({ foo: 'true' });
expect(result).toEqual({ foo: true });
});
it('should convert values in nested objects', () => {
const result = cast.cast({
foo: {
bar: 'true'
}
});
expect(result).toEqual({
foo: {
bar: true
}
});
});
it('should convert values in an array', () => {
const result = cast.cast(['true', '17.35']);
expect(result).toEqual([true, 17.35]);
});
it('should convert values in a nested array', () => {
const result = cast.cast(['true', ['17.35']]);
expect(result).toEqual([true, [17.35]]);
});
});
});