template.js 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. var fs = require('jsdoc/fs');
  2. var path = require('jsdoc/path');
  3. var helper = require('jsdoc/util/templateHelper');
  4. var markdown = require('jsdoc/util/markdown').getParser();
  5. var hbs = require('./handlebarsHelper');
  6. var lunr = require('./lunrHelper');
  7. var processor = require('./postProcessor');
  8. var sanitizeHtml = require('sanitize-html');
  9. var extend = require('extend');
  10. var moment = require('moment');
  11. var kinds = exports.kinds = {
  12. custom: ['readme', 'global', 'source', 'tutorial', 'list'],
  13. pages: ['readme', 'global', 'source', 'tutorial', 'list', 'class', 'external', 'mixin', 'module', 'namespace', 'interface'],
  14. symbols: ['tutorial', 'class', 'external', 'event', 'mixin', 'module', 'namespace', 'interface', 'member', 'function', 'constant', 'typedef'],
  15. global: ['member', 'function', 'constant', 'typedef']
  16. };
  17. var options = exports.options = extend({
  18. includeDate: true,
  19. dateFormat: "Do MMM YYYY",
  20. systemName: "FooDoc",
  21. systemSummary: "A Bootstrap and Handlebars based template for JSDoc3.",
  22. systemLogo: "",
  23. systemColor: "",
  24. navMembers: [],
  25. footer: "",
  26. copyright: "FooDoc Copyright © 2016 The contributors to the JSDoc3 and FooDoc projects.",
  27. linenums: true,
  28. collapseSymbols: true,
  29. inverseNav: true,
  30. inlineNav: false,
  31. outputSourceFiles: true,
  32. sourceRootPath: null,
  33. disablePackagePath: true,
  34. outputSourcePath: false,
  35. showTableOfContents: true,
  36. showAccessFilter: true,
  37. analytics: null,
  38. methodHeadingReturns: true,
  39. sort: "linenum, longname, version, since",
  40. search: true,
  41. favicon: null,
  42. stylesheets: [],
  43. scripts: []
  44. }, env.conf.templates || {});
  45. if (!options.navMembers.length){
  46. options.navMembers = [
  47. {"kind": "class", "title": "Classes", "summary": "All documented classes."},
  48. {"kind": "external", "title": "Externals", "summary": "All documented external members."},
  49. {"kind": "global", "title": "Globals", "summary": "All documented globals."},
  50. {"kind": "mixin", "title": "Mixins", "summary": "All documented mixins."},
  51. {"kind": "interface", "title": "Interfaces", "summary": "All documented interfaces."},
  52. {"kind": "module", "title": "Modules", "summary": "All documented modules."},
  53. {"kind": "namespace", "title": "Namespaces", "summary": "All documented namespaces."},
  54. {"kind": "tutorial", "title": "Tutorials", "summary": "All available tutorials."}
  55. ];
  56. }
  57. var faviconTypes = {
  58. '.ico': 'image/x-icon',
  59. '.png': 'image/png',
  60. '.jpg': 'image/jpeg',
  61. '.jpeg': 'image/jpeg',
  62. '.gif': 'image/gif'
  63. };
  64. var config = exports.config = {
  65. debug: false,
  66. raw: env.opts,
  67. version: env.version.number,
  68. date: moment().format(options.dateFormat),
  69. faviconType: options.favicon ? faviconTypes[path.extname(options.favicon)] : null,
  70. dir: {
  71. root: null,
  72. tmpl: null,
  73. static: null,
  74. output: env.opts.destination,
  75. images: path.join( env.opts.destination, 'img' ),
  76. tutorials: env.opts.tutorials
  77. }
  78. };
  79. var raw = exports.raw = {
  80. data: null,
  81. opts: {},
  82. tutorials: []
  83. };
  84. var configured = false;
  85. exports.configure = function(taffyData, opts, tutorials){
  86. raw.data = helper.prune(taffyData);
  87. raw.opts = opts;
  88. raw.tutorials = tutorials;
  89. config.dir.root = opts.templates;
  90. config.dir.tmpl = path.join(opts.template, 'tmpl');
  91. config.dir.static = path.join(opts.template, 'static');
  92. configured = true;
  93. return config;
  94. };
  95. var navbar = exports.navbar = {};
  96. exports.postProcess = function(){
  97. processor.registerReadme();
  98. processor.registerModules();
  99. processor.registerGlobals();
  100. processor.registerDoclets();
  101. processor.registerSources();
  102. processor.registerTutorials();
  103. processor.registerLists();
  104. processor.process();
  105. processor.buildNavbar(navbar);
  106. };
  107. exports.publish = function(){
  108. generateStaticFiles();
  109. generateDocs();
  110. lunr.writeFilesSync(true);
  111. };
  112. exports.sanitize = function(html){
  113. if (typeof html !== 'string') return;
  114. return sanitizeHtml(html, {allowedTags: [], allowedAttributes: []}).replace(/\s+/g, ' ').trim();
  115. };
  116. /**
  117. * @summary Find items in the current TaffyDB that match the specified key-value pairs.
  118. * @param {Object|function} spec - An object of key-value pairs to match against (e.g. `{longname:"foo"}`), or a function that returns `true` if a value matches.
  119. * @returns {Array.<Object>} The matching items.
  120. * @example {@caption The following shows supplying an object to perform a by example search against the data.}
  121. * var foo = helper.find({longname:"foo"}).first(); // get the first doclet with a longname of `foo`
  122. * var children = helper.find({memberof:"foo"}); // get all doclets which belong to the `foo` doclet.
  123. */
  124. var find = exports.find = function (spec, sort) {
  125. if (!configured) return [];
  126. return sort ? raw.data(spec).order(sort).get() : raw.data(spec).get();
  127. };
  128. var linkto = exports.linkto = function(longname, linkText){
  129. var text = (linkText || longname)+'';
  130. // if no linkText was supplied lookup the longname and use the `linkText` property for the doclet.
  131. if (typeof linkText !== 'string'){
  132. var found = find({longname: longname});
  133. if (found.length && found[0].kind){
  134. var doclet = found[0];
  135. text = doclet.linkText || text;
  136. if (doclet.kind === 'tutorial'){
  137. return helper.toTutorial(doclet.longname, text, { tag: 'em', classname: 'disabled' });
  138. }
  139. }
  140. }
  141. return helper.linkto(longname, text);
  142. };
  143. var getPages = exports.getPages = function(sort){
  144. var members = {};
  145. kinds.pages.forEach(function(kind){
  146. members[kind] = find({kind: kind}, sort);
  147. });
  148. return members;
  149. };
  150. var generateStaticFiles = exports.generateStaticFiles = function(){
  151. // first copy all files within the templates 'static' directory to the output directory
  152. var files = fs.ls(config.dir.static, 3);
  153. files.forEach(function(fileName){
  154. var toDir = fs.toDir( fileName.replace( config.dir.static, config.dir.output ) );
  155. fs.mkPath( toDir );
  156. fs.copyFileSync( fileName, toDir );
  157. });
  158. // then copy the systemLogo file if one was supplied and update the option with the output file path
  159. if (options.systemLogo){
  160. var stats = fs.lstatSync(options.systemLogo);
  161. if (stats.isFile()){
  162. fs.mkPath(config.dir.images);
  163. fs.copyFileSync(options.systemLogo, config.dir.images);
  164. options.systemLogo = 'img/'+path.basename(options.systemLogo);
  165. }
  166. }
  167. // same for the favicon
  168. if (options.favicon){
  169. var stats = fs.lstatSync(options.favicon);
  170. if (stats.isFile()){
  171. fs.copyFileSync(options.favicon, env.opts.destination, 'favicon' + path.extname(options.favicon));
  172. options.favicon = 'favicon' + path.extname(options.favicon);
  173. }
  174. }
  175. // then copy all user supplied files
  176. var userFiles;
  177. if (options.default && (userFiles = options.default.staticFiles)) {
  178. // The canonical property name is `include`. We accept `paths` for backwards compatibility with a bug in JSDoc 3.2.x.
  179. var paths = userFiles.include || userFiles.paths || [];
  180. var filter = new (require('jsdoc/src/filter')).Filter(userFiles);
  181. var scanner = new (require('jsdoc/src/scanner')).Scanner();
  182. paths.forEach(function(filePath) {
  183. var files = scanner.scan([filePath], 10, filter);
  184. files.forEach(function(fileName) {
  185. var from = fs.toDir(filePath);
  186. var toDir = fs.toDir( fileName.replace(from, config.dir.output) );
  187. fs.mkPath(toDir);
  188. fs.copyFileSync(fileName, toDir);
  189. });
  190. });
  191. }
  192. };
  193. var generateDocs = exports.generateDocs = function(){
  194. var pages = getPages();
  195. Object.keys(pages).forEach(function(kind){
  196. if (pages[kind].length){
  197. pages[kind].forEach(function(doclet){
  198. var filename = doclet.kind === 'tutorial' ? helper.tutorialToUrl(doclet.longname) : helper.longnameToUrl[doclet.longname];
  199. if (!filename) return;
  200. if (doclet.kind === 'list' && !doclet.members.length) return;
  201. var output = path.join(config.dir.output, filename),
  202. html = hbs.render(doclet, doclet.kind !== 'source');
  203. if (html === null) return;
  204. fs.writeFileSync(output, html, 'utf8');
  205. if (doclet.kind !== 'source'){
  206. lunr.add(doclet, html);
  207. }
  208. });
  209. }
  210. });
  211. };
  212. var hasNavMember = exports.hasNavMember = function(kind){
  213. return options.navMembers.findIndex(function(member){
  214. return member.kind == kind;
  215. }) != -1;
  216. };
  217. exports.createCrumbs = function(doclet){
  218. var crumbs = [];
  219. if (doclet.kind === 'readme' || doclet.kind === 'source') return crumbs;
  220. crumbs.push(linkto("index", "Home"));
  221. if (doclet.kind !== 'list' && doclet.kind !== 'global' && hasNavMember(doclet.kind)){
  222. crumbs.push(linkto("list:"+doclet.kind));
  223. }
  224. if (doclet.kind === 'tutorial'){
  225. helper.getAncestors(raw.data, doclet).forEach(function(ancestor){
  226. crumbs.push(linkto(ancestor.longname));
  227. });
  228. crumbs.push(doclet.title || doclet.name);
  229. } else {
  230. crumbs.push(doclet.ancestors.join('') + doclet.name);
  231. }
  232. return crumbs;
  233. };