postProcessor.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. var fs = require('jsdoc/fs');
  2. var path = require('jsdoc/path');
  3. var helper = require('jsdoc/util/templateHelper');
  4. var doop = require('jsdoc/util/doop');
  5. var handle = require('jsdoc/util/error').handle;
  6. var template = require('./template');
  7. var doc = require('./docletHelper');
  8. var glob = require('glob');
  9. var extend = require('extend');
  10. exports.process = function(){
  11. template.raw.data.sort(template.options.sort);
  12. template.raw.data().each(function(doclet){
  13. doclet.ancestors = helper.getAncestorLinks(template.raw.data, doclet);
  14. doclet.linkText = doc.getLinkText(doclet);
  15. doclet.summary = doc.getSummary(doclet);
  16. doclet.params = doc.getParamsOrProps(doclet, 'params');
  17. doc.checkParamsOrProps(doclet, 'params');
  18. doclet.properties = doc.getParamsOrProps(doclet, 'properties');
  19. doc.checkParamsOrProps(doclet, 'properties');
  20. doclet.attribs = doc.getAttribs(doclet);
  21. doclet.signature = doc.getSignature(doclet);
  22. doclet.examples = doc.getExamples(doclet);
  23. doclet.fires = doc.getFires(doclet);
  24. doclet.requires = doc.getRequires(doclet);
  25. doclet.pageTitle = doc.getPageTitle(doclet, true);
  26. doclet.pageTitleHTML = doc.getPageTitle(doclet, false);
  27. doclet.symbolTitle = doc.getSymbolTitle(doclet, true);
  28. doclet.symbolTitleHTML = doc.getSymbolTitle(doclet, false);
  29. doclet.primaryTitle = doc.getPrimaryTitle(doclet, true);
  30. doclet.primaryTitleHTML = doc.getPrimaryTitle(doclet, false);
  31. doclet.listTitle = doc.getListTitle(doclet, true);
  32. doclet.listTitleHTML = doc.getListTitle(doclet, false);
  33. doclet.hasDetails = doc.hasDetails(doclet);
  34. doclet.inherited = doc.isInherited(doclet);
  35. doclet.access = typeof doclet.access == 'string' ? doclet.access : "public";
  36. // todo: maybe expose this as an additional option, basically switches the description to a summary if no summary was supplied and the descriptions text is shorter than 120 characters
  37. //if (!doclet.summary.length && doclet.description && template.sanitize(doclet.description).length <= 120){
  38. // doclet.summary = doclet.description;
  39. // doclet.description = '';
  40. //}
  41. if (typeof doclet.showTableOfContents != 'boolean'){
  42. doclet.showTableOfContents = template.options.showTableOfContents;
  43. }
  44. });
  45. template.raw.data().each(function(doclet){
  46. doclet.symbols = doc.getSymbols(doclet);
  47. doclet.showAccessFilter = doc.getShowAccessFilter(doclet);
  48. });
  49. };
  50. var hasOwnProp = Object.prototype.hasOwnProperty;
  51. function getFilename(longname) {
  52. var fileUrl;
  53. if ( hasOwnProp.call(helper.longnameToUrl, longname) ) {
  54. fileUrl = helper.longnameToUrl[longname];
  55. } else {
  56. fileUrl = helper.getUniqueFilename(longname);
  57. helper.registerLink(longname, fileUrl);
  58. }
  59. return fileUrl;
  60. }
  61. /**
  62. * @summary Registers the given doclet creating and registering a link for it and appending an id value to use when rendering in HTML.
  63. * @param doclet - The doclet to register.
  64. */
  65. var registerDoclet = exports.registerDoclet = function (doclet) {
  66. var url;
  67. if (template.kinds.custom.indexOf(doclet.kind) !== -1) {
  68. url = helper.getUniqueFilename(doclet.longname);
  69. } else if (doclet.kind === 'event') {
  70. url = getFilename(doclet.memberof || helper.globalName);
  71. if ( (doclet.name !== doclet.longname) || (doclet.scope === helper.globalName) ) {
  72. url += '#' + (helper.scopeToPunc[doclet.scope] === '#' ? '' : helper.scopeToPunc[doclet.scope]) + 'event:' + doclet.name.replace(/(^"|"$)/g, "");
  73. }
  74. } else {
  75. url = helper.createLink(doclet);
  76. }
  77. helper.registerLink(doclet.longname, url);
  78. if (helper.longnameToUrl[doclet.longname].indexOf('#') > -1) {
  79. doclet.id = helper.longnameToUrl[doclet.longname].split(/#/).pop();
  80. } else {
  81. doclet.id = doclet.name;
  82. }
  83. };
  84. /**
  85. * @summary Registers the README.md file as the `readme` doclet.
  86. * @description This not only registers the doclet but also the link associated with it, reserving it prior to registering any other links.
  87. *
  88. * The doclet is built up using the `systemName` and `systemSummary` options as the values for the associated `name` and `summary` properties of a doclet. The `longname` is simply the filename which is `index.html` and the `contents` is the markdown parsed `README.md` supplied as a source to jsdoc.
  89. * @example {@caption The following shows the basic structure of the generated doclet.}
  90. * {
  91. * kind: "readme",
  92. * longname: "index.html",
  93. * name: "<option:systemName>",
  94. * summary: "<option:systemSummary>",
  95. * contents: "<markdown:README.md>"
  96. * }
  97. */
  98. exports.registerReadme = function () {
  99. var doclet = {
  100. kind: 'readme',
  101. longname: 'index',
  102. name: template.options.systemName,
  103. summary: template.options.systemSummary,
  104. contents: template.raw.opts.readme
  105. };
  106. registerDoclet(doclet);
  107. template.raw.data.insert(doclet);
  108. };
  109. exports.registerGlobals = function(){
  110. var options = template.options.navMembers.find(function(member){ return member.kind === "global"; });
  111. if (!options){
  112. options = {
  113. title: "Globals",
  114. summary: "All documented globals."
  115. };
  116. }
  117. var doclet = {
  118. kind: 'global',
  119. longname: helper.globalName,
  120. name: options.title,
  121. summary: options.summary,
  122. members: template.find({
  123. kind: template.kinds.global,
  124. memberof: { isUndefined: true }
  125. }, "longname, version, since"),
  126. showTableOfContents: true,
  127. showAccessFilter: false
  128. };
  129. registerDoclet(doclet);
  130. template.raw.data.insert(doclet);
  131. };
  132. exports.registerListeners = function(){
  133. helper.addEventListeners(template.raw.data);
  134. };
  135. /**
  136. * @summary Registers all default doclets.
  137. */
  138. exports.registerDoclets = function(){
  139. template.raw.data().each(function(doclet){
  140. if (template.kinds.custom.indexOf(doclet.kind) === -1) {
  141. registerDoclet(doclet);
  142. }
  143. });
  144. };
  145. exports.registerModules = function(){
  146. var mapping = {};
  147. template.raw.data({
  148. kind: ['class', 'function'],
  149. longname: {
  150. left: 'module:'
  151. }
  152. }).each(function(doclet){
  153. var longname = doclet.longname;
  154. mapping[longname] = mapping[longname] || [];
  155. mapping[longname].push(doclet);
  156. });
  157. template.raw.data({kind: 'module'}).each(function(module){
  158. if (mapping[module.longname]){
  159. mapping[module.longname].filter(function(doclet){
  160. return doclet.summary || doclet.description || doclet.kind === 'class';
  161. }).forEach(function(doclet){
  162. doclet.exported = true;
  163. doclet.memberof = module.longname;
  164. var scope = helper.scopeToPunc[doclet.scope] || '>';
  165. doclet.longname = module.longname + scope + doclet.name;
  166. });
  167. }
  168. });
  169. };
  170. /**
  171. * @summary Registers all source files as a `source` doclet.
  172. * @description This method takes into account the `sourceRootPath` option and will remove this value from the file path when generating the `longname` value for the doclet. If this option is not supplied the default behavior is to find the common prefix of all file paths and trim that.
  173. */
  174. exports.registerSources = function () {
  175. var sourceFilePaths = [];
  176. // iterate all default doclets and build the source file path if the file meta exists and add it to the doclet as meta.filepath
  177. template.raw.data().each(function (doclet) {
  178. if (template.kinds.custom.indexOf(doclet.kind) === -1) {
  179. if (doclet.meta) {
  180. var src = doclet.meta.path && doclet.meta.path !== 'null'
  181. ? doclet.meta.path + '/' + doclet.meta.filename
  182. : doclet.meta.filename;
  183. doclet.linenum = doclet.meta.lineno; // add the linenum to the base doclet so we can sort by it
  184. doclet.meta.filepath = path.normalize(src);
  185. // push the full source file path into the array if it doesn't exist
  186. if (sourceFilePaths.indexOf(doclet.meta.filepath) === -1) {
  187. sourceFilePaths.push(doclet.meta.filepath);
  188. }
  189. }
  190. }
  191. });
  192. var pathToLongname = {};
  193. if (sourceFilePaths.length) {
  194. var sources = [], root = template.options.sourceRootPath;
  195. if (!root) root = path.commonPrefix(sourceFilePaths);
  196. sourceFilePaths.forEach(function (filePath) {
  197. var short = filePath.replace(root, '').replace(/\\/g, '/');
  198. var doclet = {kind: "source", longname: short, name: short, code: "", showTableOfContents: false};
  199. try {
  200. doclet.code = helper.htmlsafe(fs.readFileSync(filePath, 'utf8'));
  201. pathToLongname[filePath] = short;
  202. registerDoclet(doclet);
  203. sources.push(doclet);
  204. } catch (e) {
  205. handle(e);
  206. }
  207. });
  208. template.raw.data.insert(sources);
  209. }
  210. // now that the sources are registered go back and update the default doclets so they contain the relevant source properties
  211. template.raw.data().each(function (doclet) {
  212. // only update default kinds with the source file info
  213. if (template.kinds.custom.indexOf(doclet.kind) === -1) {
  214. // if there is a registered source file for this doclet
  215. if (doclet.meta && doclet.meta.filepath && pathToLongname[doclet.meta.filepath]) {
  216. // build up the plain as well as html text source properties
  217. doclet.source = pathToLongname[doclet.meta.filepath];
  218. doclet.sourcelink = helper.linkto(doclet.source);
  219. if (template.options.linenums) {
  220. doclet.sourcelink += ', ' + helper.linkto(doclet.source, 'line ' + doclet.meta.lineno, null, 'line-' + doclet.meta.lineno);
  221. }
  222. }
  223. doclet.hasSource = !!(doclet.source && (template.options.outputSourceFiles || template.options.outputSourcePath));
  224. }
  225. });
  226. };
  227. var flattenTutorialConfig = function(obj, base){
  228. if (!base) base = {};
  229. Object.keys(obj).forEach(function(key){
  230. var config = obj[key];
  231. base[key] = config;
  232. if (config && config.children && !config.children.length){
  233. flattenTutorialConfig(config.children, base);
  234. }
  235. });
  236. return base;
  237. };
  238. var getTutorialToConfig = function(){
  239. if (!template.config.dir.tutorials) return {};
  240. var files = glob.sync('*.json', {cwd: template.config.dir.tutorials}), json = {};
  241. files.forEach(function(file){
  242. var raw = fs.readFileSync(path.join(template.config.dir.tutorials, file), "utf8");
  243. extend(true, json, JSON.parse(raw));
  244. });
  245. return flattenTutorialConfig(json);
  246. };
  247. var processTutorial = function(tutorial, tutorials, tutorialToConfig){
  248. if (!tutorials) tutorials = [];
  249. tutorial.children.forEach(function(child) {
  250. child.kind = 'tutorial';
  251. child.longname = child.name;
  252. child.title = child.title || child.name;
  253. child.memberof = tutorial.name;
  254. child.contents = child.parse();
  255. child.showTableOfContents = template.options.showTableOfContents;
  256. var config;
  257. if (config = tutorialToConfig[child.longname]){
  258. if (config.summary){
  259. child.summary = config.summary;
  260. }
  261. if (config.description){
  262. child.description = config.description;
  263. }
  264. if (typeof config.showTableOfContents === 'boolean'){
  265. child.showTableOfContents = config.showTableOfContents;
  266. }
  267. }
  268. tutorials.push(child);
  269. processTutorial(child, tutorials, tutorialToConfig);
  270. });
  271. return tutorials;
  272. };
  273. exports.registerTutorials = function(){
  274. helper.setTutorials(template.raw.tutorials);
  275. var tutorialToConfig = getTutorialToConfig();
  276. var tutorials = processTutorial(template.raw.tutorials, false, tutorialToConfig);
  277. tutorials.forEach(function(tutorial){
  278. template.raw.data.insert(tutorial);
  279. });
  280. };
  281. exports.registerLists = function(){
  282. template.options.navMembers.forEach(function(member){
  283. // the global kind is register just after the index to reserve it's name so don't do it again
  284. if (member.kind == "global") return;
  285. var doclet = {
  286. kind: 'list',
  287. longname: "list:" + member.kind,
  288. for: member.kind,
  289. name: member.title,
  290. summary: member.summary,
  291. showTableOfContents: true,
  292. showAccessFilter: false,
  293. members: template.find({kind: member.kind}, "longname, version, since")
  294. };
  295. if (member.kind == 'tutorial'){
  296. doclet.members = doclet.members.filter(function(m){ return !m.memberof; });
  297. }
  298. registerDoclet(doclet);
  299. template.raw.data.insert(doclet);
  300. });
  301. };
  302. exports.buildNavbar = function(navbar){
  303. navbar.index = {
  304. kind: 'readme',
  305. link: helper.longnameToUrl['index'],
  306. title: template.options.systemName,
  307. summary: template.options.systemSummary,
  308. members: []
  309. };
  310. navbar.topLevel = template.find({kind:['list','global']}, "longname, name").filter(function(doclet){
  311. return doclet.members.length > 0 && (doclet.kind == 'list' || template.hasNavMember(doclet.kind));
  312. }).map(function(doclet){
  313. return {
  314. title: doclet.name,
  315. summary: doclet.summary,
  316. link: helper.longnameToUrl[doclet.longname],
  317. members: doclet.members.map(function(member){
  318. return template.linkto(member.longname);
  319. })
  320. };
  321. });
  322. };