UNPKG

14.4 kBJavaScriptView Raw
1var _ = require('lodash');
2var fs = require('fs');
3var os = require('os');
4var path = require('path');
5var semver = require('semver');
6
7/*jshint -W079 */
8var Filter = require('./filter');
9var Parser = require('./parser');
10var Worker = require('./worker');
11
12var PluginLoader = require('./plugin_loader');
13
14var FileError = require('./errors/file_error');
15var ParserError = require('./errors/parser_error');
16var WorkerError = require('./errors/worker_error');
17
18// const
19var SPECIFICATION_VERSION = '0.3.0';
20
21var defaults = {
22 excludeFilters: [],
23 includeFilters: [ '.*\\.(clj|cls|coffee|cpp|cs|dart|erl|exs?|go|groovy|ino?|java|js|jsx|litcoffee|lua|p|php?|pl|pm|py|rb|scala|ts|vue)$' ],
24
25 src: path.join(__dirname, '../example/'),
26
27 filters: {},
28 languages: {},
29 parsers: {},
30 workers: {},
31
32 lineEnding: detectLineEnding(),
33 encoding: 'utf8'
34};
35
36var app = {
37 options : {}, // see defaults
38 log : logger,
39 generator : {},
40 packageInfos: {},
41 markdownParser: false,
42 filters: {
43 apierror : './filters/api_error.js',
44 apiheader : './filters/api_header.js',
45 apiparam : './filters/api_param.js',
46 apisuccess : './filters/api_success.js'
47 },
48 languages: {
49 '.clj' : './languages/clj.js',
50 '.coffee' : './languages/coffee.js',
51 '.erl' : './languages/erl.js',
52 '.ex' : './languages/ex.js',
53 '.exs' : './languages/ex.js',
54 '.litcoffee' : './languages/coffee.js',
55 '.lua' : './languages/lua.js',
56 '.pl' : './languages/pm.js',
57 '.pm' : './languages/pm.js',
58 '.py' : './languages/py.js',
59 '.rb' : './languages/rb.js',
60 'default' : './languages/default.js'
61 },
62 parsers: {
63 api : './parsers/api.js',
64 apidefine : './parsers/api_define.js',
65 apidescription : './parsers/api_description.js',
66 apierror : './parsers/api_error.js',
67 apierrorexample : './parsers/api_error_example.js',
68 apiexample : './parsers/api_example.js',
69 apiheader : './parsers/api_header.js',
70 apiheaderexample : './parsers/api_header_example.js',
71 apigroup : './parsers/api_group.js',
72 apiname : './parsers/api_name.js',
73 apiparam : './parsers/api_param.js',
74 apiparamexample : './parsers/api_param_example.js',
75 apipermission : './parsers/api_permission.js',
76 apisuccess : './parsers/api_success.js',
77 apisuccessexample : './parsers/api_success_example.js',
78 apiuse : './parsers/api_use.js',
79 apiversion : './parsers/api_version.js',
80 apisamplerequest : './parsers/api_sample_request.js',
81 apideprecated : './parsers/api_deprecated.js'
82 },
83 workers: {
84 apierrorstructure : './workers/api_error_structure.js',
85 apierrortitle : './workers/api_error_title.js',
86 apigroup : './workers/api_group.js',
87 apiheaderstructure : './workers/api_header_structure.js',
88 apiheadertitle : './workers/api_header_title.js',
89 apiname : './workers/api_name.js',
90 apiparamtitle : './workers/api_param_title.js',
91 apipermission : './workers/api_permission.js',
92 apisamplerequest : './workers/api_sample_request.js',
93 apistructure : './workers/api_structure.js',
94 apisuccessstructure : './workers/api_success_structure.js',
95 apisuccesstitle : './workers/api_success_title.js',
96 apiuse : './workers/api_use.js'
97 },
98 hooks: {},
99 addHook: addHook,
100 hook: applyHook
101};
102
103var defaultGenerator = {
104 name : 'apidoc',
105 time : new Date(),
106 url : 'http://apidocjs.com',
107 version: '0.0.0'
108};
109
110// TODO: find abetter name for PackageInfos (-> apidoc-conf)
111var defaultPackageInfos = {
112 description: '',
113 name : '',
114 sampleUrl : false,
115 version : '0.0.0',
116 defaultVersion: '0.0.0'
117};
118
119// Simple logger interace
120var logger = {
121 debug : function() { console.log(arguments); },
122 verbose: function() { console.log(arguments); },
123 info : function() { console.log(arguments); },
124 warn : function() { console.log(arguments); },
125 error : function() { console.log(arguments); }
126};
127
128/**
129 * Return the used specification version
130 *
131 * @returns {String}
132 */
133function getSpecificationVersion() {
134 return SPECIFICATION_VERSION;
135}
136
137/**
138 * Detect and return OS specific line ending.
139 *
140 * @returns {String}
141 */
142function detectLineEnding() {
143 if ( os.platform() === 'win32' )
144 return '\r\n';
145 if ( os.platform() === 'darwin' )
146 return '\r';
147 return '\n';
148}
149
150/**
151 * Parser
152 *
153 * @param {Object} options Overwrite default options.
154 * @param {Object} logger Logger (with methods: debug, verbose, info, warn and error is necessary).
155
156 * @returns {Mixed} true = ok, but nothing todo | false = error | Object with parsed data and project-informations.
157 * {
158 * data : { ... }
159 * project: { ... }
160 * }
161 */
162function parse(options) {
163 options = _.defaults({}, options, defaults);
164
165 // extend with custom functions
166 app.filters = _.defaults({}, options.filters, app.filters);
167 app.languages = _.defaults({}, options.languages, app.languages);
168 app.parsers = _.defaults({}, options.parsers, app.parsers);
169 app.workers = _.defaults({}, options.workers, app.workers);
170 app.hooks = _.defaults({}, options.hooks, app.hooks);
171
172 // options
173 app.options = options;
174
175 // generator
176 app.generator = _.defaults({}, app.generator, defaultGenerator);
177
178 // packageInfos
179 app.packageInfos = _.defaults({}, app.packageInfos, defaultPackageInfos);
180
181 var parsedFiles = [];
182 var parsedFilenames = [];
183
184 try {
185 // Log version information
186 var filename = path.join(__dirname, '../', './package.json');
187 var packageJson = JSON.parse( fs.readFileSync( filename , 'utf8') );
188 app.log.verbose('apidoc-generator name: ' + app.generator.name);
189 app.log.verbose('apidoc-generator version: ' + app.generator.version);
190 app.log.verbose('apidoc-core version: ' + packageJson.version);
191 app.log.verbose('apidoc-spec version: ' + getSpecificationVersion());
192
193 new PluginLoader(app);
194
195 var parser = new Parser(app);
196 var worker = new Worker(app);
197 var filter = new Filter(app);
198
199 // Make them available for plugins
200 app.parser = parser;
201 app.worker = worker;
202 app.filter = filter;
203
204 // if input option for source is an array of folders,
205 // parse each folder in the order provided.
206 app.log.verbose('run parser');
207 if (options.src instanceof Array) {
208 options.src.forEach(function(folder) {
209 // Keep same options for each folder, but ensure the 'src' of options
210 // is the folder currently being processed.
211 var folderOptions = options;
212 folderOptions.src = path.join(folder, './');
213 parser.parseFiles(folderOptions, parsedFiles, parsedFilenames);
214 });
215 }
216 else {
217 // if the input option for source is a single folder, parse as usual.
218 options.src = path.join(options.src, './');
219 parser.parseFiles(options, parsedFiles, parsedFilenames);
220 }
221
222 if (parsedFiles.length > 0) {
223 // process transformations and assignments
224 app.log.verbose('run worker');
225 worker.process(parsedFiles, parsedFilenames, app.packageInfos);
226
227 // cleanup
228 app.log.verbose('run filter');
229 var blocks = filter.process(parsedFiles, parsedFilenames);
230
231 // sort by group ASC, name ASC, version DESC
232 blocks.sort(function(a, b) {
233 var nameA = a.group + a.name;
234 var nameB = b.group + b.name;
235 if (nameA === nameB) {
236 if (a.version === b.version)
237 return 0;
238 return (semver.gte(a.version, b.version)) ? -1 : 1;
239 }
240 return (nameA < nameB) ? -1 : 1;
241 });
242
243 // add apiDoc specification version
244 app.packageInfos.apidoc = SPECIFICATION_VERSION;
245
246 // add apiDoc specification version
247 app.packageInfos.generator = app.generator;
248
249 // api_data
250 var apiData = JSON.stringify(blocks, null, 2);
251 apiData = apiData.replace(/(\r\n|\n|\r)/g, app.options.lineEnding);
252
253 // api_project
254 var apiProject = JSON.stringify(app.packageInfos, null, 2);
255 apiProject = apiProject.replace(/(\r\n|\n|\r)/g, app.options.lineEnding);
256
257 return {
258 data : apiData,
259 project: apiProject
260 };
261 }
262 return true;
263 } catch(e) {
264 // display error by instance
265 var extra;
266 var meta = {};
267 if (e instanceof FileError) {
268 meta = { 'Path': e.path };
269 app.log.error(e.message, meta);
270 } else if (e instanceof ParserError) {
271 extra = e.extra;
272 if (e.source)
273 extra.unshift({ 'Source': e.source });
274 if (e.element)
275 extra.unshift({ 'Element': '@' + e.element });
276 if (e.block)
277 extra.unshift({ 'Block': e.block });
278 if (e.file)
279 extra.unshift({ 'File': e.file });
280
281 extra.forEach(function(obj) {
282 var key = Object.keys(obj)[0];
283 meta[key] = obj[key];
284 });
285
286 app.log.error(e.message, meta);
287 }
288 else if (e instanceof WorkerError) {
289 extra = e.extra;
290 if (e.definition)
291 extra.push({ 'Definition': e.definition });
292 if (e.example)
293 extra.push({ 'Example': e.example });
294 extra.unshift({ 'Element': '@' + e.element });
295 extra.unshift({ 'Block': e.block });
296 extra.unshift({ 'File': e.file });
297
298 extra.forEach(function(obj) {
299 var key = Object.keys(obj)[0];
300 meta[key] = obj[key];
301 });
302
303 app.log.error(e.message, meta);
304 }
305 else {
306 app.log.error(e.message);
307 if (e.stack)
308 app.log.debug(e.stack);
309 }
310 return false;
311 }
312}
313
314/**
315 * Set generator informations.
316 *
317 * @param {Object} [generator] Generator informations.
318 * @param {String} [generator.name] Generator name (UI-Name).
319 * @param {String} [generator.time] Time for the generated doc
320 * @param {String} [generator.version] Version (semver) of the generator, e.g. 1.2.3
321 * @param {String} [generator.url] Url to the generators homepage
322 */
323function setGeneratorInfos(generator) {
324 app.generator = generator;
325}
326
327/**
328 * Set a logger.
329 *
330 * @param {Object} logger A Logger (@see https://github.com/flatiron/winston for details)
331 * Interface:
332 * debug(msg, meta)
333 * verbose(msg, meta)
334 * info(msg, meta)
335 * warn(msg, meta)
336 * error(msg, meta)
337 */
338function setLogger(logger) {
339 app.log = logger;
340}
341
342/**
343 * Set the markdown parser.
344 *
345 * @param {Object} [markdownParser] Markdown parser.
346 */
347function setMarkdownParser(markdownParser) {
348 app.markdownParser = markdownParser;
349}
350
351/**
352 * Set package infos.
353 *
354 * @param {Object} [packageInfos] Collected from apidoc.json / package.json.
355 * @param {String} [packageInfos.name] Project name.
356 * @param {String} [packageInfos.version] Version (semver) of the project, e.g. 1.0.27
357 * @param {String} [packageInfos.description] A short description.
358 * @param {String} [packageInfos.sampleUrl] @see http://apidocjs.com/#param-api-sample-request
359 */
360function setPackageInfos(packageInfos) {
361 app.packageInfos = packageInfos;
362}
363
364/**
365 * Register a hook function.
366 *
367 * @param {String} name Name of the hook. Hook overview: https://github.com/apidoc/apidoc-core/hooks.md
368 * @param {Function} func Callback function.
369 * @param {Integer} [priority=100] Hook priority. Lower value will be executed first.
370 * Same value overwrite a previously defined hook.
371 */
372function addHook(name, func, priority) {
373 priority = priority || 100;
374
375 if ( ! app.hooks[name])
376 app.hooks[name] = [];
377
378 app.log.debug('add hook: ' + name + ' [' + priority + ']');
379
380 // Find position and overwrite same priority
381 var replace = 0;
382 var pos = 0;
383 app.hooks[name].forEach( function(entry, index) {
384 if (priority === entry.priority) {
385 pos = index;
386 replace = 1;
387 } else if (priority > entry.priority) {
388 pos = index + 1;
389 }
390 });
391
392 app.hooks[name].splice(pos, replace, {
393 func: func,
394 priority: priority
395 });
396}
397
398/**
399 * Execute a hook.
400 */
401function applyHook(name /* , ...args */) {
402 if ( ! app.hooks[name])
403 return Array.prototype.slice.call(arguments, 1, 2)[0];
404
405 var args = Array.prototype.slice.call(arguments, 1);
406 app.hooks[name].forEach( function(hook) {
407 hook.func.apply(this, args);
408 });
409 return args[0];
410}
411
412
413module.exports = {
414 getSpecificationVersion: getSpecificationVersion,
415 parse : parse,
416 setGeneratorInfos : setGeneratorInfos,
417 setLogger : setLogger,
418 setMarkdownParser : setMarkdownParser,
419 setPackageInfos : setPackageInfos
420};