publish.js 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828
  1. /*global env: true */
  2. 'use strict';
  3. var doop = require('jsdoc/util/doop');
  4. var fs = require('jsdoc/fs');
  5. var helper = require('jsdoc/util/templateHelper');
  6. var logger = require('jsdoc/util/logger');
  7. var path = require('jsdoc/path');
  8. var taffy = require('@jsdoc/salty').taffy;
  9. var template = require('jsdoc/template');
  10. var util = require('util');
  11. var htmlsafe = helper.htmlsafe;
  12. var linkto = helper.linkto;
  13. var resolveAuthorLinks = helper.resolveAuthorLinks;
  14. var scopeToPunc = helper.scopeToPunc;
  15. var hasOwnProp = Object.prototype.hasOwnProperty;
  16. var data;
  17. var view;
  18. var outdir = path.normalize(env.opts.destination);
  19. function copyFile(source, target, cb) {
  20. var cbCalled = false;
  21. var rd = fs.createReadStream(source);
  22. rd.on("error", function(err) {
  23. done(err);
  24. });
  25. var wr = fs.createWriteStream(target);
  26. wr.on("error", function(err) {
  27. done(err);
  28. });
  29. wr.on("close", function(ex) {
  30. done();
  31. });
  32. rd.pipe(wr);
  33. function done(err) {
  34. if (!cbCalled) {
  35. cb(err);
  36. cbCalled = true;
  37. }
  38. }
  39. }
  40. function find(spec) {
  41. return helper.find(data, spec);
  42. }
  43. function tutoriallink(tutorial) {
  44. return helper.toTutorial(tutorial, null, { tag: 'em', classname: 'disabled', prefix: 'Tutorial: ' });
  45. }
  46. function getAncestorLinks(doclet) {
  47. return helper.getAncestorLinks(data, doclet);
  48. }
  49. function hashToLink(doclet, hash) {
  50. if ( !/^(#.+)/.test(hash) ) { return hash; }
  51. var url = helper.createLink(doclet);
  52. url = url.replace(/(#.+|$)/, hash);
  53. return '<a href="' + url + '">' + hash + '</a>';
  54. }
  55. function needsSignature(doclet) {
  56. var needsSig = false;
  57. // function and class definitions always get a signature
  58. if (doclet.kind === 'function' || doclet.kind === 'class' && !doclet.hideconstructor) {
  59. needsSig = true;
  60. }
  61. // typedefs that contain functions get a signature, too
  62. else if (doclet.kind === 'typedef' && doclet.type && doclet.type.names &&
  63. doclet.type.names.length) {
  64. for (var i = 0, l = doclet.type.names.length; i < l; i++) {
  65. if (doclet.type.names[i].toLowerCase() === 'function') {
  66. needsSig = true;
  67. break;
  68. }
  69. }
  70. }
  71. // and namespaces that are functions get a signature (but finding them is a
  72. // bit messy)
  73. else if (doclet.kind === 'namespace' && doclet.meta && doclet.meta.code &&
  74. doclet.meta.code.type && doclet.meta.code.type.match(/[Ff]unction/)) {
  75. needsSig = true;
  76. }
  77. return needsSig;
  78. }
  79. function getSignatureAttributes(item) {
  80. var attributes = [];
  81. if (item.optional) {
  82. attributes.push('opt');
  83. }
  84. if (item.nullable === true) {
  85. attributes.push('nullable');
  86. }
  87. else if (item.nullable === false) {
  88. attributes.push('non-null');
  89. }
  90. return attributes;
  91. }
  92. function updateItemName(item) {
  93. var attributes = getSignatureAttributes(item);
  94. var itemName = item.name || '';
  95. if (item.variable) {
  96. itemName = '&hellip;' + itemName;
  97. }
  98. if (attributes && attributes.length) {
  99. itemName = util.format( '%s<span class="signature-attributes">%s</span>', itemName,
  100. attributes.join(', ') );
  101. }
  102. return itemName;
  103. }
  104. function addParamAttributes(params) {
  105. return params.filter(function(param) {
  106. return param.name && param.name.indexOf('.') === -1;
  107. }).map(updateItemName);
  108. }
  109. function buildItemTypeStrings(item) {
  110. var types = [];
  111. if (item && item.type && item.type.names) {
  112. item.type.names.forEach(function(name) {
  113. types.push( linkto(name, htmlsafe(name)) );
  114. });
  115. }
  116. return types;
  117. }
  118. function buildAttribsString(attribs) {
  119. var attribsString = '';
  120. if (attribs && attribs.length) {
  121. attribsString = htmlsafe( util.format('(%s) ', attribs.join(', ')) );
  122. }
  123. return attribsString;
  124. }
  125. function addNonParamAttributes(items) {
  126. var types = [];
  127. items.forEach(function(item) {
  128. types = types.concat( buildItemTypeStrings(item) );
  129. });
  130. return types;
  131. }
  132. function addSignatureParams(f) {
  133. var params = f.params ? addParamAttributes(f.params) : [];
  134. f.signature = util.format( '%s(%s)', (f.signature || ''), params.join(', ') );
  135. }
  136. function addSignatureReturns(f) {
  137. var attribs = [];
  138. var attribsString = '';
  139. var returnTypes = [];
  140. var returnTypesString = '';
  141. var source = f.yields || f.returns;
  142. // jam all the return-type attributes into an array. this could create odd results (for example,
  143. // if there are both nullable and non-nullable return types), but let's assume that most people
  144. // who use multiple @return tags aren't using Closure Compiler type annotations, and vice-versa.
  145. if (source) {
  146. source.forEach(function(item) {
  147. helper.getAttribs(item).forEach(function(attrib) {
  148. if (attribs.indexOf(attrib) === -1) {
  149. attribs.push(attrib);
  150. }
  151. });
  152. });
  153. attribsString = buildAttribsString(attribs);
  154. }
  155. if (source) {
  156. returnTypes = addNonParamAttributes(source);
  157. }
  158. if (returnTypes.length) {
  159. returnTypesString = util.format( ' &rarr; %s{%s}', attribsString, returnTypes.join('|') );
  160. }
  161. f.signature = '<span class="signature">' + (f.signature || '') + '</span>' +
  162. '<span class="type-signature">' + returnTypesString + '</span>';
  163. }
  164. function addSignatureTypes(f) {
  165. var types = f.type ? buildItemTypeStrings(f) : [];
  166. f.signature = (f.signature || '') + '<span class="type-signature">' +
  167. (types.length ? ' :' + types.join('|') : '') + '</span>';
  168. }
  169. function addAttribs(f) {
  170. var attribs = helper.getAttribs(f);
  171. var attribsString = buildAttribsString(attribs);
  172. if (attribsString && attribsString.length) {
  173. f.attribs = util.format('<span class="type-signature type-signature-' + attribsString.replace(/\(/g, '').replace(/\)/g, '').trim() +'">%s</span>', attribsString);
  174. }
  175. else {
  176. f.attribs = util.format('<span class="type-signature">%s</span>', attribsString);
  177. }
  178. }
  179. function shortenPaths(files, commonPrefix) {
  180. Object.keys(files).forEach(function(file) {
  181. files[file].shortened = files[file].resolved.replace(commonPrefix, '')
  182. // always use forward slashes
  183. .replace(/\\/g, '/');
  184. });
  185. return files;
  186. }
  187. function getPathFromDoclet(doclet) {
  188. if (!doclet.meta) {
  189. return null;
  190. }
  191. return doclet.meta.path && doclet.meta.path !== 'null' ?
  192. path.join(doclet.meta.path, doclet.meta.filename) :
  193. doclet.meta.filename;
  194. }
  195. function generate(type, title, docs, filename, resolveLinks) {
  196. resolveLinks = resolveLinks === false ? false : true;
  197. var docData = {
  198. type: type,
  199. title: title,
  200. docs: docs
  201. };
  202. var outpath = path.join(outdir, filename),
  203. html = view.render('container.tmpl', docData);
  204. if (resolveLinks) {
  205. html = helper.resolveLinks(html); // turn {@link foo} into <a href="foodoc.html">foo</a>
  206. }
  207. fs.writeFileSync(outpath, html, 'utf8');
  208. }
  209. function generateSourceFiles(sourceFiles, encoding) {
  210. encoding = encoding || 'utf8';
  211. Object.keys(sourceFiles).forEach(function(file) {
  212. var source;
  213. // links are keyed to the shortened path in each doclet's `meta.shortpath` property
  214. var sourceOutfile = helper.getUniqueFilename(sourceFiles[file].shortened);
  215. helper.registerLink(sourceFiles[file].shortened, sourceOutfile);
  216. try {
  217. source = {
  218. kind: 'source',
  219. code: helper.htmlsafe( fs.readFileSync(sourceFiles[file].resolved, encoding) )
  220. };
  221. }
  222. catch(e) {
  223. logger.error('Error while generating source file %s: %s', file, e.message);
  224. }
  225. generate('Source', sourceFiles[file].shortened, [source], sourceOutfile, false);
  226. });
  227. }
  228. /**
  229. * Look for classes or functions with the same name as modules (which indicates that the module
  230. * exports only that class or function), then attach the classes or functions to the `module`
  231. * property of the appropriate module doclets. The name of each class or function is also updated
  232. * for display purposes. This function mutates the original arrays.
  233. *
  234. * @private
  235. * @param {Array.<module:jsdoc/doclet.Doclet>} doclets - The array of classes and functions to
  236. * check.
  237. * @param {Array.<module:jsdoc/doclet.Doclet>} modules - The array of module doclets to search.
  238. */
  239. function attachModuleSymbols(doclets, modules) {
  240. var symbols = {};
  241. // build a lookup table
  242. doclets.forEach(function(symbol) {
  243. symbols[symbol.longname] = symbols[symbol.longname] || [];
  244. symbols[symbol.longname].push(symbol);
  245. });
  246. return modules.map(function(module) {
  247. if (symbols[module.longname]) {
  248. module.modules = symbols[module.longname]
  249. // Only show symbols that have a description. Make an exception for classes, because
  250. // we want to show the constructor-signature heading no matter what.
  251. .filter(function(symbol) {
  252. return symbol.description || symbol.kind === 'class';
  253. })
  254. .map(function(symbol) {
  255. symbol = doop(symbol);
  256. if (symbol.kind === 'class' || symbol.kind === 'function' && !symbol.hideconstructor) {
  257. symbol.name = symbol.name.replace('module:', '(require("') + '"))';
  258. }
  259. return symbol;
  260. });
  261. }
  262. });
  263. }
  264. function buildMemberNav(items, itemHeading, itemsSeen, linktoFn) {
  265. var nav = '';
  266. if (items && items.length) {
  267. var itemsNav = '';
  268. var docdash = env && env.conf && env.conf.docdash || {};
  269. var level = typeof docdash.navLevel === 'number' && docdash.navLevel >= 0 ?
  270. docdash.navLevel :
  271. Infinity;
  272. items.forEach(function(item) {
  273. var displayName;
  274. var methods = find({kind:'function', memberof: item.longname});
  275. var members = find({kind:'member', memberof: item.longname});
  276. var conf = env && env.conf || {};
  277. var classes = '';
  278. // show private class?
  279. if (docdash.private === false && item.access === 'private') return;
  280. // depth to show?
  281. if (item.ancestors && item.ancestors.length > level) {
  282. classes += 'level-hide';
  283. }
  284. classes = classes ? ' class="'+ classes + '"' : '';
  285. itemsNav += '<li'+ classes +'>';
  286. if ( !hasOwnProp.call(item, 'longname') ) {
  287. itemsNav += linktoFn('', item.name);
  288. } else if ( !hasOwnProp.call(itemsSeen, item.longname) ) {
  289. if (conf.templates.default.useLongnameInNav) {
  290. displayName = item.longname;
  291. } else {
  292. displayName = item.name;
  293. }
  294. itemsNav += linktoFn(item.longname, displayName.replace(/\b(module|event):/g, ''));
  295. if (docdash.static && members.find(function (m) { return m.scope === 'static'; } )) {
  296. itemsNav += "<ul class='members'>";
  297. members.forEach(function (member) {
  298. if (!member.scope === 'static') return;
  299. itemsNav += "<li data-type='member'";
  300. if(docdash.collapse)
  301. itemsNav += " style='display: none;'";
  302. itemsNav += ">";
  303. itemsNav += linkto(member.longname, member.name);
  304. itemsNav += "</li>";
  305. });
  306. itemsNav += "</ul>";
  307. }
  308. if (methods.length) {
  309. itemsNav += "<ul class='methods'>";
  310. methods.forEach(function (method) {
  311. if (docdash.static === false && method.scope === 'static') return;
  312. if (docdash.private === false && method.access === 'private') return;
  313. var navItem = '';
  314. var navItemLink = linkto(method.longname, method.name);
  315. navItem += "<li data-type='method'";
  316. if(docdash.collapse)
  317. navItem += " style='display: none;'";
  318. navItem += ">";
  319. navItem += navItemLink;
  320. navItem += "</li>";
  321. itemsNav += navItem;
  322. });
  323. itemsNav += "</ul>";
  324. }
  325. itemsSeen[item.longname] = true;
  326. }
  327. itemsNav += '</li>';
  328. });
  329. if (itemsNav !== '') {
  330. if(docdash.collapse === "top") {
  331. nav += '<h3 class="collapsed_header">' + itemHeading + '</h3><ul class="collapse_top">' + itemsNav + '</ul>';
  332. }
  333. else {
  334. nav += '<h3>' + itemHeading + '</h3><ul>' + itemsNav + '</ul>';
  335. }
  336. }
  337. }
  338. return nav;
  339. }
  340. function linktoTutorial(longName, name) {
  341. return tutoriallink(name);
  342. }
  343. function linktoExternal(longName, name) {
  344. return linkto(longName, name.replace(/(^"|"$)/g, ''));
  345. }
  346. /**
  347. * Create the navigation sidebar.
  348. * @param {object} members The members that will be used to create the sidebar.
  349. * @param {array<object>} members.classes
  350. * @param {array<object>} members.externals
  351. * @param {array<object>} members.globals
  352. * @param {array<object>} members.mixins
  353. * @param {array<object>} members.modules
  354. * @param {array<object>} members.namespaces
  355. * @param {array<object>} members.tutorials
  356. * @param {array<object>} members.events
  357. * @param {array<object>} members.interfaces
  358. * @return {string} The HTML for the navigation sidebar.
  359. */
  360. function buildNav(members) {
  361. var nav = '<h2><a href="index.html">Home</a></h2>';
  362. var seen = {};
  363. var seenTutorials = {};
  364. var docdash = env && env.conf && env.conf.docdash || {};
  365. if(docdash.menu){
  366. for(var menu in docdash.menu){
  367. nav += '<h2><a ';
  368. //add attributes
  369. for(var attr in docdash.menu[menu]){
  370. nav += attr+'="' + docdash.menu[menu][attr] + '" ';
  371. }
  372. nav += '>' + menu + '</a></h2>';
  373. }
  374. }
  375. function buildMemberNavGlobal() {
  376. var ret = "";
  377. if (members.globals.length) {
  378. var globalNav = '';
  379. members.globals.forEach(function(g) {
  380. if ( (docdash.typedefs || g.kind !== 'typedef') && !hasOwnProp.call(seen, g.longname) ) {
  381. globalNav += '<li>' + linkto(g.longname, g.name) + '</li>';
  382. }
  383. seen[g.longname] = true;
  384. });
  385. if (!globalNav) {
  386. // turn the heading into a link so you can actually get to the global page
  387. ret += '<h3>' + linkto('global', 'Global') + '</h3>';
  388. }
  389. else {
  390. if(docdash.collapse === "top") {
  391. ret += '<h3 class="collapsed_header">Global</h3><ul class="collapse_top">' + globalNav + '</ul>';
  392. }
  393. else {
  394. ret += '<h3>Global</h3><ul>' + globalNav + '</ul>';
  395. }
  396. }
  397. }
  398. return ret;
  399. }
  400. var defaultOrder = [
  401. 'Classes', 'Modules', 'Externals', 'Events', 'Namespaces', 'Mixins', 'Tutorials', 'Interfaces', 'Global'
  402. ];
  403. var order = docdash.sectionOrder || defaultOrder;
  404. var sections = {
  405. Classes: buildMemberNav(members.classes, 'Classes', seen, linkto),
  406. Modules: buildMemberNav(members.modules, 'Modules', {}, linkto),
  407. Externals: buildMemberNav(members.externals, 'Externals', seen, linktoExternal),
  408. Events: buildMemberNav(members.events, 'Events', seen, linkto),
  409. Namespaces: buildMemberNav(members.namespaces, 'Namespaces', seen, linkto),
  410. Mixins: buildMemberNav(members.mixins, 'Mixins', seen, linkto),
  411. Tutorials: buildMemberNav(members.tutorials, 'Tutorials', seenTutorials, linktoTutorial),
  412. Interfaces: buildMemberNav(members.interfaces, 'Interfaces', seen, linkto),
  413. Global: buildMemberNavGlobal()
  414. };
  415. order.forEach(member => nav += sections[member]);
  416. return nav;
  417. }
  418. /**
  419. @param {TAFFY} taffyData See <http://taffydb.com/>.
  420. @param {object} opts
  421. @param {Tutorial} tutorials
  422. */
  423. exports.publish = function(taffyData, opts, tutorials) {
  424. var docdash = env && env.conf && env.conf.docdash || {};
  425. data = taffyData;
  426. var conf = env.conf.templates || {};
  427. conf.default = conf.default || {};
  428. var templatePath = path.normalize(opts.template);
  429. view = new template.Template( path.join(templatePath, 'tmpl') );
  430. // claim some special filenames in advance, so the All-Powerful Overseer of Filename Uniqueness
  431. // doesn't try to hand them out later
  432. var indexUrl = helper.getUniqueFilename('index');
  433. // don't call registerLink() on this one! 'index' is also a valid longname
  434. var globalUrl = helper.getUniqueFilename('global');
  435. helper.registerLink('global', globalUrl);
  436. // set up templating
  437. view.layout = conf.default.layoutFile ?
  438. path.getResourcePath(path.dirname(conf.default.layoutFile),
  439. path.basename(conf.default.layoutFile) ) :
  440. 'layout.tmpl';
  441. // set up tutorials for helper
  442. helper.setTutorials(tutorials);
  443. data = helper.prune(data);
  444. docdash.sort !== false && data.sort('longname, version, since');
  445. helper.addEventListeners(data);
  446. var sourceFiles = {};
  447. var sourceFilePaths = [];
  448. data().each(function(doclet) {
  449. if(docdash.removeQuotes){
  450. if(docdash.removeQuotes === "all"){
  451. if(doclet.name){
  452. doclet.name = doclet.name.replace(/"/g, '');
  453. doclet.name = doclet.name.replace(/'/g, '');
  454. }
  455. if(doclet.longname){
  456. doclet.longname = doclet.longname.replace(/"/g, '');
  457. doclet.longname = doclet.longname.replace(/'/g, '');
  458. }
  459. }
  460. else if(docdash.removeQuotes === "trim"){
  461. if(doclet.name){
  462. doclet.name = doclet.name.replace(/^"(.*)"$/, '$1');
  463. doclet.name = doclet.name.replace(/^'(.*)'$/, '$1');
  464. }
  465. if(doclet.longname){
  466. doclet.longname = doclet.longname.replace(/^"(.*)"$/, '$1');
  467. doclet.longname = doclet.longname.replace(/^'(.*)'$/, '$1');
  468. }
  469. }
  470. }
  471. doclet.attribs = '';
  472. if (doclet.examples) {
  473. doclet.examples = doclet.examples.map(function(example) {
  474. var caption, code;
  475. if (example && example.match(/^\s*<caption>([\s\S]+?)<\/caption>(\s*[\n\r])([\s\S]+)$/i)) {
  476. caption = RegExp.$1;
  477. code = RegExp.$3;
  478. }
  479. return {
  480. caption: caption || '',
  481. code: code || example || ''
  482. };
  483. });
  484. }
  485. if (doclet.see) {
  486. doclet.see.forEach(function(seeItem, i) {
  487. doclet.see[i] = hashToLink(doclet, seeItem);
  488. });
  489. }
  490. // build a list of source files
  491. var sourcePath;
  492. if (doclet.meta) {
  493. sourcePath = getPathFromDoclet(doclet);
  494. sourceFiles[sourcePath] = {
  495. resolved: sourcePath,
  496. shortened: null
  497. };
  498. if (sourceFilePaths.indexOf(sourcePath) === -1) {
  499. sourceFilePaths.push(sourcePath);
  500. }
  501. }
  502. });
  503. // update outdir if necessary, then create outdir
  504. var packageInfo = ( find({kind: 'package'}) || [] ) [0];
  505. if (packageInfo) {
  506. var subdirs = [outdir];
  507. if (packageInfo.name) {
  508. var packageName = packageInfo.name.split('/');
  509. if (packageName.length > 1 && docdash.scopeInOutputPath !== false) {
  510. subdirs.push(packageName[0]);
  511. }
  512. if (docdash.nameInOutputPath !== false) {
  513. subdirs.push((packageName.length > 1 ? packageName[1] : packageName[0]));
  514. }
  515. }
  516. if (packageInfo.version && docdash.versionInOutputPath !== false) {
  517. subdirs.push(packageInfo.version);
  518. }
  519. if (subdirs.length > 1) {
  520. outdir = path.join.apply(null, subdirs);
  521. }
  522. }
  523. fs.mkPath(outdir);
  524. // copy the template's static files to outdir
  525. var fromDir = path.join(templatePath, 'static');
  526. var staticFiles = fs.ls(fromDir, 3);
  527. staticFiles.forEach(function(fileName) {
  528. var toDir = fs.toDir( fileName.replace(fromDir, outdir) );
  529. fs.mkPath(toDir);
  530. copyFile(fileName, path.join(toDir, path.basename(fileName)), function(err){if(err) console.err(err);});
  531. });
  532. // copy user-specified static files to outdir
  533. var staticFilePaths;
  534. var staticFileFilter;
  535. var staticFileScanner;
  536. if (conf.default.staticFiles) {
  537. // The canonical property name is `include`. We accept `paths` for backwards compatibility
  538. // with a bug in JSDoc 3.2.x.
  539. staticFilePaths = conf.default.staticFiles.include ||
  540. conf.default.staticFiles.paths ||
  541. [];
  542. staticFileFilter = new (require('jsdoc/src/filter')).Filter(conf.default.staticFiles);
  543. staticFileScanner = new (require('jsdoc/src/scanner')).Scanner();
  544. staticFilePaths.forEach(function(filePath) {
  545. var extraStaticFiles = staticFileScanner.scan([filePath], 10, staticFileFilter);
  546. extraStaticFiles.forEach(function(fileName) {
  547. var sourcePath = fs.toDir(filePath);
  548. var toDir = fs.toDir( fileName.replace(sourcePath, outdir) );
  549. fs.mkPath(toDir);
  550. copyFile(fileName, path.join(toDir, path.basename(fileName)), function(err){if(err) console.err(err);});
  551. });
  552. });
  553. }
  554. if (sourceFilePaths.length) {
  555. sourceFiles = shortenPaths( sourceFiles, path.commonPrefix(sourceFilePaths) );
  556. }
  557. data().each(function(doclet) {
  558. var url = helper.createLink(doclet);
  559. helper.registerLink(doclet.longname, url);
  560. // add a shortened version of the full path
  561. var docletPath;
  562. if (doclet.meta) {
  563. docletPath = getPathFromDoclet(doclet);
  564. docletPath = sourceFiles[docletPath].shortened;
  565. if (docletPath) {
  566. doclet.meta.shortpath = docletPath;
  567. }
  568. }
  569. });
  570. data().each(function(doclet) {
  571. var url = helper.longnameToUrl[doclet.longname];
  572. if (url.indexOf('#') > -1) {
  573. doclet.id = helper.longnameToUrl[doclet.longname].split(/#/).pop();
  574. }
  575. else {
  576. doclet.id = doclet.name;
  577. }
  578. if ( needsSignature(doclet) ) {
  579. addSignatureParams(doclet);
  580. addSignatureReturns(doclet);
  581. addAttribs(doclet);
  582. }
  583. });
  584. // do this after the urls have all been generated
  585. data().each(function(doclet) {
  586. doclet.ancestors = getAncestorLinks(doclet);
  587. if (doclet.kind === 'member') {
  588. addSignatureTypes(doclet);
  589. addAttribs(doclet);
  590. }
  591. if (doclet.kind === 'constant') {
  592. addSignatureTypes(doclet);
  593. addAttribs(doclet);
  594. doclet.kind = 'member';
  595. }
  596. });
  597. var members = helper.getMembers(data);
  598. members.tutorials = tutorials.children;
  599. // output pretty-printed source files by default
  600. var outputSourceFiles = conf.default && conf.default.outputSourceFiles !== false
  601. ? true
  602. : false;
  603. // add template helpers
  604. view.find = find;
  605. view.linkto = linkto;
  606. view.resolveAuthorLinks = resolveAuthorLinks;
  607. view.tutoriallink = tutoriallink;
  608. view.htmlsafe = htmlsafe;
  609. view.outputSourceFiles = outputSourceFiles;
  610. // once for all
  611. view.nav = buildNav(members);
  612. attachModuleSymbols( find({ longname: {left: 'module:'} }), members.modules );
  613. // generate the pretty-printed source files first so other pages can link to them
  614. if (outputSourceFiles) {
  615. generateSourceFiles(sourceFiles, opts.encoding);
  616. }
  617. if (members.globals.length) {
  618. generate('', 'Global', [{kind: 'globalobj'}], globalUrl);
  619. }
  620. // index page displays information from package.json and lists files
  621. var files = find({kind: 'file'});
  622. var packages = find({kind: 'package'});
  623. generate('', 'Home',
  624. packages.concat(
  625. [{kind: 'mainpage', readme: opts.readme, longname: (opts.mainpagetitle) ? opts.mainpagetitle : 'Main Page'}]
  626. ).concat(files),
  627. indexUrl);
  628. // common nav generation, no need for templating here, we already have full html
  629. if (docdash.commonNav) {
  630. fs.writeFileSync(path.join(outdir, 'nav.inc.html'), view.nav, 'utf8');
  631. }
  632. // set up the lists that we'll use to generate pages
  633. var classes = taffy(members.classes);
  634. var modules = taffy(members.modules);
  635. var namespaces = taffy(members.namespaces);
  636. var mixins = taffy(members.mixins);
  637. var externals = taffy(members.externals);
  638. var interfaces = taffy(members.interfaces);
  639. Object.keys(helper.longnameToUrl).forEach(function(longname) {
  640. var myModules = helper.find(modules, {longname: longname});
  641. if (myModules.length) {
  642. generate('Module', myModules[0].name, myModules, helper.longnameToUrl[longname]);
  643. }
  644. var myClasses = helper.find(classes, {longname: longname});
  645. if (myClasses.length) {
  646. generate('Class', myClasses[0].name, myClasses, helper.longnameToUrl[longname]);
  647. }
  648. var myNamespaces = helper.find(namespaces, {longname: longname});
  649. if (myNamespaces.length) {
  650. generate('Namespace', myNamespaces[0].name, myNamespaces, helper.longnameToUrl[longname]);
  651. }
  652. var myMixins = helper.find(mixins, {longname: longname});
  653. if (myMixins.length) {
  654. generate('Mixin', myMixins[0].name, myMixins, helper.longnameToUrl[longname]);
  655. }
  656. var myExternals = helper.find(externals, {longname: longname});
  657. if (myExternals.length) {
  658. generate('External', myExternals[0].name, myExternals, helper.longnameToUrl[longname]);
  659. }
  660. var myInterfaces = helper.find(interfaces, {longname: longname});
  661. if (myInterfaces.length) {
  662. generate('Interface', myInterfaces[0].name, myInterfaces, helper.longnameToUrl[longname]);
  663. }
  664. });
  665. // TODO: move the tutorial functions to templateHelper.js
  666. function generateTutorial(title, tutorial, filename) {
  667. var tutorialData = {
  668. title: title,
  669. header: tutorial.title,
  670. content: tutorial.parse(),
  671. children: tutorial.children
  672. };
  673. var tutorialPath = path.join(outdir, filename);
  674. var html = view.render('tutorial.tmpl', tutorialData);
  675. // yes, you can use {@link} in tutorials too!
  676. html = helper.resolveLinks(html); // turn {@link foo} into <a href="foodoc.html">foo</a>
  677. fs.writeFileSync(tutorialPath, html, 'utf8');
  678. }
  679. // tutorials can have only one parent so there is no risk for loops
  680. function saveChildren(node) {
  681. node.children.forEach(function(child) {
  682. generateTutorial('Tutorial: ' + child.title, child, helper.tutorialToUrl(child.name));
  683. saveChildren(child);
  684. });
  685. }
  686. saveChildren(tutorials);
  687. };