UNPKG

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