UNPKG

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