index.js 12 KB

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