UNPKG

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