cli.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472
  1. /* eslint indent: "off", no-process-exit: "off", strict: ["error", "function"] */
  2. /**
  3. * Helper methods for running JSDoc on the command line.
  4. *
  5. * A few critical notes for anyone who works on this module:
  6. *
  7. * + The module should really export an instance of `cli`, and `props` should be properties of a
  8. * `cli` instance.
  9. *
  10. * @private
  11. */
  12. module.exports = (function() {
  13. 'use strict';
  14. var app = require('jsdoc/app');
  15. var env = require('jsdoc/env');
  16. var logger = require('jsdoc/util/logger');
  17. var stripBom = require('jsdoc/util/stripbom');
  18. var stripJsonComments = require('strip-json-comments');
  19. var Promise = require('bluebird');
  20. var props = {
  21. docs: [],
  22. packageJson: null,
  23. shouldExitWithError: false,
  24. tmpdir: null
  25. };
  26. var FATAL_ERROR_MESSAGE = 'Exiting JSDoc because an error occurred. See the previous log ' +
  27. 'messages for details.';
  28. var cli = {};
  29. // TODO: docs
  30. cli.setVersionInfo = function() {
  31. var fs = require('fs');
  32. var path = require('path');
  33. // allow this to throw--something is really wrong if we can't read our own package file
  34. var info = JSON.parse( stripBom.strip(fs.readFileSync(path.join(env.dirname, 'package.json'),
  35. 'utf8')) );
  36. env.version = {
  37. number: info.version,
  38. revision: new Date( parseInt(info.revision, 10) ).toUTCString()
  39. };
  40. return cli;
  41. };
  42. // TODO: docs
  43. cli.loadConfig = function() {
  44. var _ = require('underscore');
  45. var args = require('jsdoc/opts/args');
  46. var Config = require('jsdoc/config');
  47. var config;
  48. var fs = require('jsdoc/fs');
  49. var path = require('jsdoc/path');
  50. var confPath;
  51. var isFile;
  52. var defaultOpts = {
  53. destination: './out/',
  54. encoding: 'utf8'
  55. };
  56. try {
  57. env.opts = args.parse(env.args);
  58. }
  59. catch (e) {
  60. console.error(e.message + '\n');
  61. cli.printHelp().then(function() {
  62. cli.exit(1);
  63. });
  64. }
  65. confPath = env.opts.configure || path.join(env.dirname, 'conf.json');
  66. try {
  67. isFile = fs.statSync(confPath).isFile();
  68. }
  69. catch (e) {
  70. isFile = false;
  71. }
  72. if ( !isFile && !env.opts.configure ) {
  73. confPath = path.join(env.dirname, 'conf.json.EXAMPLE');
  74. }
  75. try {
  76. switch ( path.extname(confPath) ) {
  77. case '.js':
  78. config = require( path.resolve(confPath) ) || {};
  79. break;
  80. case '.json':
  81. case '.EXAMPLE':
  82. default:
  83. config = fs.readFileSync(confPath, 'utf8');
  84. break;
  85. }
  86. env.conf = new Config(config).get();
  87. }
  88. catch (e) {
  89. cli.exit(1, 'Cannot parse the config file ' + confPath + ': ' + e + '\n' +
  90. FATAL_ERROR_MESSAGE);
  91. }
  92. // look for options on the command line, in the config file, and in the defaults, in that order
  93. env.opts = _.defaults(env.opts, env.conf.opts, defaultOpts);
  94. return cli;
  95. };
  96. // TODO: docs
  97. cli.configureLogger = function() {
  98. function recoverableError() {
  99. props.shouldExitWithError = true;
  100. }
  101. function fatalError() {
  102. cli.exit(1);
  103. }
  104. if (env.opts.debug) {
  105. logger.setLevel(logger.LEVELS.DEBUG);
  106. }
  107. else if (env.opts.verbose) {
  108. logger.setLevel(logger.LEVELS.INFO);
  109. }
  110. if (env.opts.pedantic) {
  111. logger.once('logger:warn', recoverableError);
  112. logger.once('logger:error', fatalError);
  113. }
  114. else {
  115. logger.once('logger:error', recoverableError);
  116. }
  117. logger.once('logger:fatal', fatalError);
  118. return cli;
  119. };
  120. // TODO: docs
  121. cli.logStart = function() {
  122. logger.debug( cli.getVersion() );
  123. logger.debug('Environment info: %j', {
  124. env: {
  125. conf: env.conf,
  126. opts: env.opts
  127. }
  128. });
  129. };
  130. // TODO: docs
  131. cli.logFinish = function() {
  132. var delta;
  133. var deltaSeconds;
  134. if (env.run.finish && env.run.start) {
  135. delta = env.run.finish.getTime() - env.run.start.getTime();
  136. }
  137. if (delta !== undefined) {
  138. deltaSeconds = (delta / 1000).toFixed(2);
  139. logger.info('Finished running in %s seconds.', deltaSeconds);
  140. }
  141. };
  142. // TODO: docs
  143. cli.runCommand = function(cb) {
  144. var cmd;
  145. var opts = env.opts;
  146. if (opts.help) {
  147. cmd = cli.printHelp;
  148. }
  149. else if (opts.test) {
  150. cmd = cli.runTests;
  151. }
  152. else if (opts.version) {
  153. cmd = cli.printVersion;
  154. }
  155. else {
  156. cmd = cli.main;
  157. }
  158. cmd().then(function(errorCode) {
  159. if (!errorCode && props.shouldExitWithError) {
  160. errorCode = 1;
  161. }
  162. cb(errorCode);
  163. });
  164. };
  165. // TODO: docs
  166. cli.printHelp = function() {
  167. cli.printVersion();
  168. console.log( '\n' + require('jsdoc/opts/args').help() + '\n' );
  169. console.log('Visit http://usejsdoc.org for more information.');
  170. return Promise.resolve(0);
  171. };
  172. // TODO: docs
  173. cli.runTests = function() {
  174. var path = require('jsdoc/path');
  175. var runner = Promise.promisify(require( path.join(env.dirname, 'test/runner') ));
  176. console.log('Running tests...');
  177. return runner();
  178. };
  179. // TODO: docs
  180. cli.getVersion = function() {
  181. return 'JSDoc ' + env.version.number + ' (' + env.version.revision + ')';
  182. };
  183. // TODO: docs
  184. cli.printVersion = function() {
  185. console.log( cli.getVersion() );
  186. return Promise.resolve(0);
  187. };
  188. // TODO: docs
  189. cli.main = function() {
  190. cli.scanFiles();
  191. if (env.sourceFiles.length === 0) {
  192. console.log('There are no input files to process.');
  193. return Promise.resolve(0);
  194. } else {
  195. return cli.createParser()
  196. .parseFiles()
  197. .processParseResults()
  198. .then(function() {
  199. env.run.finish = new Date();
  200. return 0;
  201. });
  202. }
  203. };
  204. function readPackageJson(filepath) {
  205. var fs = require('jsdoc/fs');
  206. try {
  207. return stripJsonComments( fs.readFileSync(filepath, 'utf8') );
  208. }
  209. catch (e) {
  210. logger.error('Unable to read the package file "%s"', filepath);
  211. return null;
  212. }
  213. }
  214. function buildSourceList() {
  215. var Readme = require('jsdoc/readme');
  216. var packageJson;
  217. var readmeHtml;
  218. var sourceFile;
  219. var sourceFiles = env.opts._ ? env.opts._.slice(0) : [];
  220. if (env.conf.source && env.conf.source.include) {
  221. sourceFiles = sourceFiles.concat(env.conf.source.include);
  222. }
  223. // load the user-specified package/README files, if any
  224. if (env.opts.package) {
  225. packageJson = readPackageJson(env.opts.package);
  226. }
  227. if (env.opts.readme) {
  228. readmeHtml = new Readme(env.opts.readme).html;
  229. }
  230. // source files named `package.json` or `README.md` get special treatment, unless the user
  231. // explicitly specified a package and/or README file
  232. for (var i = 0, l = sourceFiles.length; i < l; i++) {
  233. sourceFile = sourceFiles[i];
  234. if ( !env.opts.package && /\bpackage\.json$/i.test(sourceFile) ) {
  235. packageJson = readPackageJson(sourceFile);
  236. sourceFiles.splice(i--, 1);
  237. }
  238. if ( !env.opts.readme && /(\bREADME|\.md)$/i.test(sourceFile) ) {
  239. readmeHtml = new Readme(sourceFile).html;
  240. sourceFiles.splice(i--, 1);
  241. }
  242. }
  243. props.packageJson = packageJson;
  244. env.opts.readme = readmeHtml;
  245. return sourceFiles;
  246. }
  247. // TODO: docs
  248. cli.scanFiles = function() {
  249. var Filter = require('jsdoc/src/filter').Filter;
  250. var filter;
  251. env.opts._ = buildSourceList();
  252. // are there any files to scan and parse?
  253. if (env.conf.source && env.opts._.length) {
  254. filter = new Filter(env.conf.source);
  255. env.sourceFiles = app.jsdoc.scanner.scan(env.opts._,
  256. (env.opts.recurse ? env.conf.recurseDepth : undefined), filter);
  257. }
  258. return cli;
  259. };
  260. function resolvePluginPaths(paths) {
  261. var path = require('jsdoc/path');
  262. var pluginPaths = [];
  263. paths.forEach(function(plugin) {
  264. var basename = path.basename(plugin);
  265. var dirname = path.dirname(plugin);
  266. var pluginPath = path.getResourcePath(dirname, basename);
  267. if (!pluginPath) {
  268. logger.error('Unable to find the plugin "%s"', plugin);
  269. return;
  270. }
  271. pluginPaths.push( pluginPath );
  272. });
  273. return pluginPaths;
  274. }
  275. cli.createParser = function() {
  276. var handlers = require('jsdoc/src/handlers');
  277. var parser = require('jsdoc/src/parser');
  278. var plugins = require('jsdoc/plugins');
  279. app.jsdoc.parser = parser.createParser(env.conf.parser);
  280. if (env.conf.plugins) {
  281. env.conf.plugins = resolvePluginPaths(env.conf.plugins);
  282. plugins.installPlugins(env.conf.plugins, app.jsdoc.parser);
  283. }
  284. handlers.attachTo(app.jsdoc.parser);
  285. return cli;
  286. };
  287. cli.parseFiles = function() {
  288. var augment = require('jsdoc/augment');
  289. var borrow = require('jsdoc/borrow');
  290. var Package = require('jsdoc/package').Package;
  291. var docs;
  292. var packageDocs;
  293. props.docs = docs = app.jsdoc.parser.parse(env.sourceFiles, env.opts.encoding);
  294. // If there is no package.json, just create an empty package
  295. packageDocs = new Package(props.packageJson);
  296. packageDocs.files = env.sourceFiles || [];
  297. docs.push(packageDocs);
  298. logger.debug('Adding inherited symbols, mixins, and interface implementations...');
  299. augment.augmentAll(docs);
  300. logger.debug('Adding borrowed doclets...');
  301. borrow.resolveBorrows(docs);
  302. logger.debug('Post-processing complete.');
  303. app.jsdoc.parser.fireProcessingComplete(docs);
  304. return cli;
  305. };
  306. cli.processParseResults = function() {
  307. if (env.opts.explain) {
  308. cli.dumpParseResults();
  309. return Promise.resolve();
  310. }
  311. else {
  312. cli.resolveTutorials();
  313. return cli.generateDocs();
  314. }
  315. };
  316. cli.dumpParseResults = function() {
  317. console.log(require('jsdoc/util/dumper').dump(props.docs));
  318. return cli;
  319. };
  320. cli.resolveTutorials = function() {
  321. var resolver = require('jsdoc/tutorial/resolver');
  322. if (env.opts.tutorials) {
  323. resolver.load(env.opts.tutorials);
  324. resolver.resolve();
  325. }
  326. return cli;
  327. };
  328. cli.generateDocs = function() {
  329. var path = require('jsdoc/path');
  330. var resolver = require('jsdoc/tutorial/resolver');
  331. var taffy = require('taffydb').taffy;
  332. var template;
  333. env.opts.template = (function() {
  334. var publish = env.opts.template || 'templates/default';
  335. var templatePath = path.getResourcePath(publish);
  336. // if we didn't find the template, keep the user-specified value so the error message is
  337. // useful
  338. return templatePath || env.opts.template;
  339. })();
  340. try {
  341. template = require(env.opts.template + '/publish');
  342. }
  343. catch (e) {
  344. logger.fatal('Unable to load template: ' + e.message || e);
  345. }
  346. // templates should include a publish.js file that exports a "publish" function
  347. if (template.publish && typeof template.publish === 'function') {
  348. var publishPromise;
  349. logger.info('Generating output files...');
  350. publishPromise = template.publish(
  351. taffy(props.docs),
  352. env.opts,
  353. resolver.root
  354. );
  355. return Promise.resolve(publishPromise);
  356. }
  357. else {
  358. logger.fatal(env.opts.template + ' does not export a "publish" function. Global ' +
  359. '"publish" functions are no longer supported.');
  360. }
  361. return Promise.resolve();
  362. };
  363. // TODO: docs
  364. cli.exit = function(exitCode, message) {
  365. if (exitCode > 0 && message) {
  366. console.error(message);
  367. }
  368. process.on('exit', function() { process.exit(exitCode); });
  369. };
  370. return cli;
  371. })();