UNPKG

11.7 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 console.log(require('jsdoc/util/dumper').dump(props.docs));
404
405 return cli;
406};
407
408cli.resolveTutorials = function() {
409 var resolver = require('jsdoc/tutorial/resolver');
410
411 if (env.opts.tutorials) {
412 resolver.load(env.opts.tutorials);
413 resolver.resolve();
414 }
415
416 return cli;
417};
418
419cli.generateDocs = function() {
420 var path = require('jsdoc/path');
421 var resolver = require('jsdoc/tutorial/resolver');
422 var taffy = require('taffydb').taffy;
423
424 var template;
425
426 env.opts.template = (function() {
427 var publish = env.opts.template || 'templates/default';
428 var templatePath = path.getResourcePath(publish);
429
430 // if we didn't find the template, keep the user-specified value so the error message is
431 // useful
432 return templatePath || env.opts.template;
433 })();
434
435 try {
436 template = require(env.opts.template + '/publish');
437 }
438 catch (e) {
439 logger.fatal('Unable to load template: ' + e.message || e);
440 }
441
442 // templates should include a publish.js file that exports a "publish" function
443 if (template.publish && typeof template.publish === 'function') {
444 var publishPromise;
445
446 logger.info('Generating output files...');
447 publishPromise = template.publish(
448 taffy(props.docs),
449 env.opts,
450 resolver.root
451 );
452
453 return Promise.resolve(publishPromise);
454 }
455 else {
456 logger.fatal(env.opts.template + ' does not export a "publish" function. Global ' +
457 '"publish" functions are no longer supported.');
458 }
459
460 return Promise.resolve();
461};
462
463// TODO: docs
464cli.exit = function(exitCode, message) {
465 if (exitCode > 0 && message) {
466 console.error(message);
467 }
468 process.on('exit', function() { process.exit(exitCode); });
469};
470
471return cli;
472})();