Passed
Push — master ( e7326f...65e227 )
by Sergii
01:22
created

template/publish.js   F

Complexity

Total Complexity 151
Complexity/F 3.28

Size

Lines of Code 895
Function Count 46

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 0
c 1
b 0
f 1
nc 2359296
dl 0
loc 895
rs 2.1818
noi 3
wmc 151
mnd 4
bc 126
fnc 46
bpm 2.7391
cpm 3.2826

How to fix   Complexity   

Complexity

Complex classes like template/publish.js often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
"use strict";
2
3
/**
4
 * @module template/publish
5
 * @type {*}
6
 */
7
/*global env: true */
8
9
var template = require('jsdoc/template'),
10
  doop = require('jsdoc/util/doop'),
11
  fs = require('jsdoc/fs'),
12
  _ = require('underscore'),
13
  path = require('jsdoc/path'),
14
15
  taffy = require('taffydb').taffy,
16
  handle = require('jsdoc/util/error').handle,
17
  helper = require('jsdoc/util/templateHelper'),
18
  moment = require("moment"),
19
  htmlsafe = helper.htmlsafe,
20
  sanitizeHtml = require('sanitize-html'),
21
  linkto = helper.linkto,
22
  resolveAuthorLinks = helper.resolveAuthorLinks,
23
  scopeToPunc = helper.scopeToPunc,
24
  hasOwnProp = Object.prototype.hasOwnProperty,
25
  conf = env.conf.templates || {},
26
  data,
27
  view,
28
  outdir = env.opts.destination,
29
  searchEnabled = conf.search !== false;
30
31
var globalUrl = helper.getUniqueFilename('global');
32
var indexUrl = helper.getUniqueFilename('index');
33
34
var navOptions = {
35
  includeDate: conf.includeDate !== false,
36
  logoFile: conf.logoFile,
37
  systemName: conf.systemName || "Documentation",
38
  navType: conf.navType || "vertical",
39
  footer: conf.footer || "",
40
  copyright: conf.copyright || "",
41
  theme: conf.theme || "simplex",
42
  syntaxTheme: conf.syntaxTheme || "default",
43
  linenums: conf.linenums,
44
  collapseSymbols: conf.collapseSymbols || false,
45
  inverseNav: conf.inverseNav,
46
  outputSourceFiles: conf.outputSourceFiles === true,
47
  sourceRootPath: conf.sourceRootPath,
48
  disablePackagePath: conf.disablePackagePath,
49
  outputSourcePath: conf.outputSourcePath,
50
  dateFormat: conf.dateFormat,
51
  analytics: conf.analytics || null,
52
  methodHeadingReturns: conf.methodHeadingReturns === true,
53
  sort: conf.sort,
54
  search: searchEnabled
55
};
56
var searchableDocuments = {};
57
58
var navigationMaster = {
59
  index: {
60
    title: navOptions.systemName,
61
    link: indexUrl,
62
    members: []
63
  },
64
  namespace: {
65
    title: "Namespaces",
66
    link: helper.getUniqueFilename("namespaces.list"),
67
    members: []
68
  },
69
  module: {
70
    title: "Modules",
71
    link: helper.getUniqueFilename("modules.list"),
72
    members: []
73
  },
74
  class: {
75
    title: "Classes",
76
    link: helper.getUniqueFilename('classes.list'),
77
    members: []
78
  },
79
80
  mixin: {
81
    title: "Mixins",
82
    link: helper.getUniqueFilename("mixins.list"),
83
    members: []
84
  },
85
  event: {
86
    title: "Events",
87
    link: helper.getUniqueFilename("events.list"),
88
    members: []
89
  },
90
  interface: {
91
    title: "Interfaces",
92
    link: helper.getUniqueFilename("interfaces.list"),
93
    members: []
94
  },
95
  tutorial: {
96
    title: "Tutorials",
97
    link: helper.getUniqueFilename("tutorials.list"),
98
    members: []
99
  },
100
  global: {
101
    title: "Global",
102
    link: globalUrl,
103
    members: []
104
105
  },
106
  external: {
107
    title: "Externals",
108
    link: helper.getUniqueFilename("externals.list"),
109
    members: []
110
  }
111
};
112
113
function find(spec) {
114
  return helper.find(data, spec);
115
}
116
117
function tutoriallink(tutorial) {
118
  return helper.toTutorial(tutorial, null, {
119
    tag: 'em',
120
    classname: 'disabled',
121
    prefix: 'Tutorial: '
122
  });
123
}
124
125
function getAncestorLinks(doclet) {
126
  return helper.getAncestorLinks(data, doclet);
127
}
128
129
function hashToLink(doclet, hash) {
130
  if (!/^(#.+)/.test(hash)) {
131
    return hash;
132
  }
133
134
  var url = helper.createLink(doclet);
135
136
  url = url.replace(/(#.+|$)/, hash);
137
  return '<a href="' + url + '">' + hash + '</a>';
138
}
139
140
function needsSignature(doclet) {
141
  var needsSig = false;
142
143
  // function and class definitions always get a signature
144
  if (doclet.kind === 'function' || doclet.kind === 'class') {
145
    needsSig = true;
146
  }
147
  // typedefs that contain functions get a signature, too
148
  else if (doclet.kind === 'typedef' && doclet.type && doclet.type.names &&
149
    doclet.type.names.length) {
150
    for (var i = 0, l = doclet.type.names.length; i < l; i++) {
151
      if (doclet.type.names[i].toLowerCase() === 'function') {
152
        needsSig = true;
153
        break;
154
      }
155
    }
156
  }
157
158
  return needsSig;
159
}
160
161
function addSignatureParams(f) {
162
  var optionalClass = 'optional';
163
  var params = helper.getSignatureParams(f, optionalClass);
164
165
  f.signature = (f.signature || '') + '(';
166
167
  for (var i = 0, l = params.length; i < l; i++) {
168
    var element = params[i];
169
    var seperator = (i > 0) ? ', ' : '';
170
171
    if (!new RegExp("class=[\"|']"+optionalClass+"[\"|']").test(element)) {
172
      f.signature += seperator + element;
173
    } else {
174
      var regExp = new RegExp("<span class=[\"|']"+optionalClass+"[\"|']>(.*?)<\\/span>", "i");
175
      f.signature += element.replace(regExp, " $`["+seperator+"$1$']");
176
    }
177
178
  }
179
180
  f.signature += ')';
181
}
182
183
function addSignatureReturns(f) {
184
  if (navOptions.methodHeadingReturns) {
185
    var returnTypes = helper.getSignatureReturns(f);
186
187
    f.signature = '<span class="signature">' + (f.signature || '') + '</span>' + '<span class="type-signature">' + (returnTypes.length ? ' &rarr; {' + returnTypes.join('|') + '}' : '') + '</span>';
188
  }
189
  else {
190
    f.signature = f.signature || '';
191
  }
192
}
193
194
function addSignatureTypes(f) {
195
  var types = helper.getSignatureTypes(f);
196
197
  f.signature = (f.signature || '') + '<span class="type-signature">' + (types.length ? ' :' + types.join('|') : '') + '</span>';
198
}
199
200
function addAttribs(f) {
201
  var attribs = helper.getAttribs(f);
202
203
  f.attribs = '<span class="type-signature">' + htmlsafe(attribs.length ? '<' + attribs.join(', ') + '> ' : '') + '</span>';
204
}
205
206
function shortenPaths(files, commonPrefix) {
207
  //	// always use forward slashes
208
  //	var regexp = new RegExp( '\\\\', 'g' );
209
  //
210
  //	var prefix = commonPrefix.toLowerCase().replace( regexp, "/" );
211
  //
212
  //	Object.keys( files ).forEach( function ( file ) {
213
  //		files[file].shortened = files[file]
214
  //			.resolved
215
  //			.toLowerCase()
216
  //			.replace( regexp, '/' )
217
  //			.replace( prefix, '' );
218
  //	} );
219
220
  Object.keys(files).forEach(function(file) {
221
    files[file].shortened = files[file].resolved.replace(commonPrefix, '')
222
    // always use forward slashes
223
    .replace(/\\/g, '/');
224
  });
225
226
227
  return files;
228
}
229
230
function getPathFromDoclet(doclet) {
231
  if (!doclet.meta) {
232
    return;
233
  }
234
235
  return path.normalize(doclet.meta.path && doclet.meta.path !== 'null' ?
236
    doclet.meta.path + '/' + doclet.meta.filename :
237
    doclet.meta.filename);
238
}
239
240
function searchData(html) {
241
  var startOfContent = html.indexOf("<div class=\"container\">");
242
  if (startOfContent > 0) {
243
    var startOfSecondContent = html.indexOf("<div class=\"container\">", startOfContent + 2);
244
    if (startOfSecondContent > 0) {
245
      startOfContent = startOfSecondContent;
246
    }
247
    html = html.slice(startOfContent);
248
  }
249
  var endOfContent = html.indexOf("<span class=\"copyright\">");
250
  if (endOfContent > 0) {
251
    html = html.substring(0, endOfContent);
252
  }
253
  var stripped = sanitizeHtml(html, {allowedTags: [], allowedAttributes: []});
254
  stripped = stripped.replace(/\s+/g, ' ');
255
  return stripped;
256
}
257
258
function generate(docType, title, docs, filename, resolveLinks) {
259
  resolveLinks = resolveLinks === false ? false : true;
260
261
  var docData = {
262
    title: title,
263
    docs: docs,
264
    docType: docType
265
  };
266
267
  var outpath = path.join(outdir, filename),
268
    html = view.render('container.tmpl', docData);
269
270
  if (resolveLinks) {
271
    html = helper.resolveLinks(html); // turn {@link foo} into <a href="foodoc.html">foo</a>
272
  }
273
274
  if (searchEnabled) {
275
    searchableDocuments[filename] = {
276
      "id": filename,
277
      "title": title,
278
      "body": searchData(html)
279
    };
280
  }
281
282
  fs.writeFileSync(outpath, html, 'utf8');
283
}
284
285
function generateSourceFiles(sourceFiles) {
286
  Object.keys(sourceFiles).forEach(function(file) {
287
    var source;
288
    // links are keyed to the shortened path in each doclet's `meta.shortpath` property
289
    var sourceOutfile = helper.getUniqueFilename(sourceFiles[file].shortened);
290
    helper.registerLink(sourceFiles[file].shortened, sourceOutfile);
291
292
    try {
293
      source = {
294
        kind: 'source',
295
        code: helper.htmlsafe(fs.readFileSync(sourceFiles[file].resolved, 'utf8'))
296
      };
297
    } catch (e) {
298
      handle(e);
299
    }
300
301
    generate('source', 'Source: ' + sourceFiles[file].shortened, [source], sourceOutfile,
302
      false);
303
  });
304
}
305
306
/**
307
 * Look for classes or functions with the same name as modules (which indicates that the module
308
 * exports only that class or function), then attach the classes or functions to the `module`
309
 * property of the appropriate module doclets. The name of each class or function is also updated
310
 * for display purposes. This function mutates the original arrays.
311
 *
312
 * @private
313
 * @param {Array.<module:jsdoc/doclet.Doclet>} doclets - The array of classes and functions to
314
 * check.
315
 * @param {Array.<module:jsdoc/doclet.Doclet>} modules - The array of module doclets to search.
316
 */
317
function attachModuleSymbols(doclets, modules) {
318
  var symbols = {};
319
320
  // build a lookup table
321
  doclets.forEach(function(symbol) {
322
    symbols[symbol.longname] = symbols[symbol.longname] || [];
323
    symbols[symbol.longname].push(symbol);
324
  });
325
326
  return modules.map(function(module) {
327
    if (symbols[module.longname]) {
328
      module.modules = symbols[module.longname]
329
      // Only show symbols that have a description. Make an exception for classes, because
330
      // we want to show the constructor-signature heading no matter what.
331
      .filter(function(symbol) {
332
        return symbol.description || symbol.kind === 'class';
333
      })
334
        .map(function(symbol) {
335
          symbol = doop(symbol);
336
337
          if (symbol.kind === 'class' || symbol.kind === 'function') {
338
            symbol.name = symbol.name.replace('module:', '(require("') + '"))';
339
          }
340
341
          return symbol;
342
        });
343
    }
344
  });
345
}
346
347
/**
348
 * Create the navigation sidebar.
349
 * @param {object} members The members that will be used to create the sidebar.
350
 * @param {array<object>} members.classes
351
 * @param {array<object>} members.externals
352
 * @param {array<object>} members.globals
353
 * @param {array<object>} members.mixins
354
 * @param {array<object>} members.interfaces
355
 * @param {array<object>} members.modules
356
 * @param {array<object>} members.namespaces
357
 * @param {array<object>} members.tutorials
358
 * @param {array<object>} members.events
359
 * @return {string} The HTML for the navigation sidebar.
360
 */
361
function buildNav(members) {
362
363
  var seen = {};
364
  var nav = navigationMaster;
365
  if (members.modules.length) {
366
367
    members.modules.forEach(function(m) {
368
      if (!hasOwnProp.call(seen, m.longname)) {
369
370
        nav.module.members.push(linkto(m.longname, m.longname.replace("module:", "")));
371
      }
372
      seen[m.longname] = true;
373
    });
374
  }
375
376
  if (members.externals.length) {
377
378
    members.externals.forEach(function(e) {
379
      if (!hasOwnProp.call(seen, e.longname)) {
380
381
        nav.external.members.push(linkto(e.longname, e.name.replace(/(^"|"$)/g, '')));
382
      }
383
      seen[e.longname] = true;
384
    });
385
  }
386
387
  if (members.classes.length) {
388
389
    members.classes.forEach(function(c) {
390
      if (!hasOwnProp.call(seen, c.longname)) {
391
392
        nav.class.members.push(linkto(c.longname, c.longname.replace("module:", "")));
393
      }
394
      seen[c.longname] = true;
395
    });
396
397
  }
398
399
  if (members.events.length) {
400
401
    members.events.forEach(function(e) {
402
      if (!hasOwnProp.call(seen, e.longname)) {
403
404
        nav.event.members.push(linkto(e.longname, e.longname.replace("module:", "")));
405
      }
406
      seen[e.longname] = true;
407
    });
408
409
  }
410
411
  if (members.namespaces.length) {
412
413
    members.namespaces.forEach(function(n) {
414
      if (!hasOwnProp.call(seen, n.longname)) {
415
416
        nav.namespace.members.push(linkto(n.longname, n.longname.replace("module:", "")));
417
      }
418
      seen[n.longname] = true;
419
    });
420
421
  }
422
423
  if (members.mixins.length) {
424
425
    members.mixins.forEach(function(m) {
426
      if (!hasOwnProp.call(seen, m.longname)) {
427
428
        nav.mixin.members.push(linkto(m.longname, m.longname.replace("module:", "")));
429
      }
430
      seen[m.longname] = true;
431
    });
432
433
  }
434
435
  if (members.interfaces && members.interfaces.length) {
436
437
    members.interfaces.forEach(function(m) {
438
      if (!hasOwnProp.call(seen, m.longname)) {
439
440
        nav.interface.members.push(linkto(m.longname, m.longname.replace("module:", "")));
441
      }
442
      seen[m.longname] = true;
443
    });
444
445
  }
446
447
  if (members.tutorials.length) {
448
449
    members.tutorials.forEach(function(t) {
450
451
      nav.tutorial.members.push(tutoriallink(t.name));
452
    });
453
454
  }
455
456
  if (members.globals.length) {
457
    members.globals.forEach(function(g) {
458
      if (g.kind !== 'typedef' && !hasOwnProp.call(seen, g.longname)) {
459
460
        nav.global.members.push(linkto(g.longname, g.longname.replace("module:", "")));
461
      }
462
      seen[g.longname] = true;
463
    });
464
465
    // even if there are no links, provide a link to the global page.
466
    if (nav.global.members.length === 0) {
467
      nav.global.members.push(linkto("global", "Global"));
468
    }
469
  }
470
471
  var topLevelNav = [];
472
  _.each(nav, function(entry, name) {
473
    if (entry.members.length > 0 && name !== "index") {
474
      topLevelNav.push({
475
        title: entry.title,
476
        link: entry.link,
477
        members: entry.members
478
      });
479
    }
480
  });
481
  nav.topLevelNav = topLevelNav;
482
}
483
484
/**
485
 @param {TAFFY} taffyData See <http://taffydb.com/>.
486
 @param {object} opts
487
 @param {Tutorial} tutorials
488
 */
489
exports.publish = function(taffyData, opts, tutorials) {
490
  data = taffyData;
491
492
  conf['default'] = conf['default'] || {};
493
494
  var templatePath = opts.template;
495
  view = new template.Template(templatePath + '/tmpl');
496
497
  // claim some special filenames in advance, so the All-Powerful Overseer of Filename Uniqueness
498
  // doesn't try to hand them out later
499
  //	var indexUrl = helper.getUniqueFilename( 'index' );
500
  // don't call registerLink() on this one! 'index' is also a valid longname
501
502
  //	var globalUrl = helper.getUniqueFilename( 'global' );
503
  helper.registerLink('global', globalUrl);
504
505
  // set up templating
506
  // set up templating
507
  view.layout = conf['default'].layoutFile ?
508
    path.getResourcePath(path.dirname(conf['default'].layoutFile),
509
    path.basename(conf['default'].layoutFile) ) : 'layout.tmpl';
510
511
  // set up tutorials for helper
512
  helper.setTutorials(tutorials);
513
514
  data = helper.prune(data);
515
516
  var sortOption = navOptions.sort === undefined ? opts.sort : navOptions.sort;
517
  sortOption = sortOption === undefined ? true : sortOption;
518
  sortOption = sortOption === true ? 'longname, version, since' : sortOption;
519
  if (sortOption) {
520
    data.sort(sortOption);
521
  }
522
  helper.addEventListeners(data);
523
524
  var sourceFiles = {};
525
  var sourceFilePaths = [];
526
  data().each(function(doclet) {
527
    doclet.attribs = '';
528
529
    if (doclet.examples) {
530
      doclet.examples = doclet.examples.map(function(example) {
531
        var caption, lang;
532
533
        // allow using a markdown parser on the examples captions (surrounded by useless HTML p tags)
534
        if (example.match(/^\s*(<p>)?<caption>([\s\S]+?)<\/caption>(\s*)([\s\S]+?)(<\/p>)?$/i)) {
535
          caption = RegExp.$2;
536
          example = RegExp.$4 + (RegExp.$1 ? '' : RegExp.$5);
537
        }
538
539
        var lang = /{@lang (.*?)}/.exec(example);
540
541
        if (lang && lang[1]) {
542
          example = example.replace(lang[0], "");
543
          lang = lang[1];
544
545
        } else {
546
          lang = null;
547
        }
548
549
        return {
550
          caption: caption || '',
551
          code: example,
552
          lang: lang || "javascript"
553
        };
554
      });
555
    }
556
    if (doclet.see) {
557
      doclet.see.forEach(function(seeItem, i) {
558
        doclet.see[i] = hashToLink(doclet, seeItem);
559
      });
560
    }
561
562
    // build a list of source files
563
    var sourcePath;
564
    if (doclet.meta) {
565
      sourcePath = getPathFromDoclet(doclet);
566
      sourceFiles[sourcePath] = {
567
        resolved: sourcePath,
568
        shortened: null
569
      };
570
571
      //Check to see if the array of source file paths already contains
572
      // the source path, if not then add it
573
      if (sourceFilePaths.indexOf(sourcePath) === -1) {
574
          sourceFilePaths.push(sourcePath)
575
      }
576
    }
577
  });
578
579
  // update outdir if necessary, then create outdir
580
  var packageInfo = (find({
581
    kind: 'package'
582
  }) || [])[0];
583
  if (navOptions.disablePackagePath !== true && packageInfo && packageInfo.name) {
584
    if (packageInfo.version) {
585
      outdir = path.join(outdir, packageInfo.name, packageInfo.version);
586
    } else {
587
      outdir = path.join(outdir, packageInfo.name);
588
    }
589
  }
590
  fs.mkPath(outdir);
591
592
	// copy the template's static files to outdir
593
	var fromDir = path.join( templatePath, 'static' );
594
	var staticFiles = fs.ls( fromDir, 3 );
595
596
	staticFiles.forEach( function ( fileName ) {
597
		var toDir = fs.toDir( fileName.replace( fromDir, outdir ) );
598
		fs.mkPath( toDir );
599
		fs.copyFileSync( fileName, toDir );
600
	} );
601
602
    // copy user-specified static files to outdir
603
    var staticFilePaths;
604
    var staticFileFilter;
605
    var staticFileScanner;
606
    if (conf.default.staticFiles) {
607
        // The canonical property name is `include`. We accept `paths` for backwards compatibility
608
        // with a bug in JSDoc 3.2.x.
609
        staticFilePaths = conf.default.staticFiles.include ||
610
            conf.default.staticFiles.paths ||
611
            [];
612
        staticFileFilter = new (require('jsdoc/src/filter')).Filter(conf.default.staticFiles);
613
        staticFileScanner = new (require('jsdoc/src/scanner')).Scanner();
614
615
        staticFilePaths.forEach(function(filePath) {
616
            var extraStaticFiles = staticFileScanner.scan([filePath], 10, staticFileFilter);
617
618
            extraStaticFiles.forEach(function(fileName) {
619
                var sourcePath = fs.toDir(filePath);
620
                var toDir = fs.toDir( fileName.replace(sourcePath, outdir) );
621
                fs.mkPath(toDir);
622
                fs.copyFileSync(fileName, toDir);
623
            });
624
        });
625
    }
626
627
  if (sourceFilePaths.length) {
628
    var payload = navOptions.sourceRootPath;
629
    if (!payload) {
630
      payload = path.commonPrefix(sourceFilePaths);
631
    }
632
    sourceFiles = shortenPaths(sourceFiles, payload);
633
  }
634
  data().each(function(doclet) {
635
    var url = helper.createLink(doclet);
636
    helper.registerLink(doclet.longname, url);
637
638
    // add a shortened version of the full path
639
    var docletPath;
640
    if (doclet.meta) {
641
      docletPath = getPathFromDoclet(doclet);
642
      if (!_.isEmpty(sourceFiles[docletPath])) {
643
        docletPath = sourceFiles[docletPath].shortened;
644
        if (docletPath) {
645
          doclet.meta.shortpath = docletPath;
646
        }
647
      }
648
    }
649
  });
650
651
  data().each(function(doclet) {
652
    var url = helper.longnameToUrl[doclet.longname];
653
654
    if (url.indexOf('#') > -1) {
655
      doclet.id = helper.longnameToUrl[doclet.longname].split(/#/).pop();
656
    } else {
657
      doclet.id = doclet.name;
658
    }
659
660
    if (needsSignature(doclet)) {
661
      addSignatureParams(doclet);
662
      addSignatureReturns(doclet);
663
      addAttribs(doclet);
664
    }
665
  });
666
667
  // do this after the urls have all been generated
668
  data().each(function(doclet) {
669
    doclet.ancestors = getAncestorLinks(doclet);
670
671
    if (doclet.kind === 'member') {
672
      addSignatureTypes(doclet);
673
      addAttribs(doclet);
674
    }
675
676
    if (doclet.kind === 'constant') {
677
      addSignatureTypes(doclet);
678
      addAttribs(doclet);
679
      doclet.kind = 'member';
680
    }
681
  });
682
683
  var members = helper.getMembers(data);
684
  members.tutorials = tutorials.children;
685
686
  // add template helpers
687
  view.find = find;
688
  view.linkto = linkto;
689
  view.resolveAuthorLinks = resolveAuthorLinks;
690
  view.tutoriallink = tutoriallink;
691
  view.htmlsafe = htmlsafe;
692
  view.moment = moment;
693
694
  // once for all
695
  buildNav(members);
696
  view.nav = navigationMaster;
697
  view.navOptions = navOptions;
698
  attachModuleSymbols(find({
699
      kind: ['class', 'function'],
700
      longname: {
701
        left: 'module:'
702
      }
703
    }),
704
    members.modules);
705
706
  // only output pretty-printed source files if requested; do this before generating any other
707
  // pages, so the other pages can link to the source files
708
  if (navOptions.outputSourceFiles) {
709
    generateSourceFiles(sourceFiles);
710
  }
711
712
  if (members.globals.length) {
713
    generate('global', 'Global', [{
714
      kind: 'globalobj'
715
    }], globalUrl);
716
  }
717
718
  // some browsers can't make the dropdown work
719
  if (view.nav.module && view.nav.module.members.length) {
720
    generate('module', view.nav.module.title, [{
721
      kind: 'sectionIndex',
722
      contents: view.nav.module
723
    }], navigationMaster.module.link);
724
  }
725
726
  if (view.nav.class && view.nav.class.members.length) {
727
    generate('class', view.nav.class.title, [{
728
      kind: 'sectionIndex',
729
      contents: view.nav.class
730
    }], navigationMaster.class.link);
731
  }
732
733
  if (view.nav.namespace && view.nav.namespace.members.length) {
734
    generate('namespace', view.nav.namespace.title, [{
735
      kind: 'sectionIndex',
736
      contents: view.nav.namespace
737
    }], navigationMaster.namespace.link);
738
  }
739
740
  if (view.nav.mixin && view.nav.mixin.members.length) {
741
    generate('mixin', view.nav.mixin.title, [{
742
      kind: 'sectionIndex',
743
      contents: view.nav.mixin
744
    }], navigationMaster.mixin.link);
745
  }
746
747
  if (view.nav.interface && view.nav.interface.members.length) {
748
    generate('interface', view.nav.interface.title, [{
749
      kind: 'sectionIndex',
750
      contents: view.nav.interface
751
    }], navigationMaster.interface.link);
752
  }
753
754
  if (view.nav.external && view.nav.external.members.length) {
755
    generate('external', view.nav.external.title, [{
756
      kind: 'sectionIndex',
757
      contents: view.nav.external
758
    }], navigationMaster.external.link);
759
  }
760
761
  if (view.nav.tutorial && view.nav.tutorial.members.length) {
762
    generate('tutorial', view.nav.tutorial.title, [{
763
      kind: 'sectionIndex',
764
      contents: view.nav.tutorial
765
    }], navigationMaster.tutorial.link);
766
  }
767
768
  // index page displays information from package.json and lists files
769
  var files = find({
770
      kind: 'file'
771
    }),
772
    packages = find({
773
      kind: 'package'
774
    });
775
776
  generate('index', 'Index',
777
    packages.concat(
778
      [{
779
        kind: 'mainpage',
780
        readme: opts.readme,
781
        longname: (opts.mainpagetitle) ? opts.mainpagetitle : 'Main Page'
782
      }]
783
    ).concat(files),
784
    indexUrl);
785
786
  // set up the lists that we'll use to generate pages
787
  var classes = taffy(members.classes);
788
  var modules = taffy(members.modules);
789
  var namespaces = taffy(members.namespaces);
790
  var mixins = taffy(members.mixins);
791
  var interfaces = taffy(members.interfaces);
792
  var externals = taffy(members.externals);
793
794
  for (var longname in helper.longnameToUrl) {
795
    if (hasOwnProp.call(helper.longnameToUrl, longname)) {
796
      var myClasses = helper.find(classes, {
797
        longname: longname
798
      });
799
      if (myClasses.length) {
800
        generate('class', 'Class: ' + myClasses[0].name, myClasses, helper.longnameToUrl[longname]);
801
      }
802
803
      var myModules = helper.find(modules, {
804
        longname: longname
805
      });
806
      if (myModules.length) {
807
        generate('module', 'Module: ' + myModules[0].name, myModules, helper.longnameToUrl[longname]);
808
      }
809
810
      var myNamespaces = helper.find(namespaces, {
811
        longname: longname
812
      });
813
      if (myNamespaces.length) {
814
        generate('namespace', 'Namespace: ' + myNamespaces[0].name, myNamespaces, helper.longnameToUrl[longname]);
815
      }
816
817
      var myMixins = helper.find(mixins, {
818
        longname: longname
819
      });
820
      if (myMixins.length) {
821
        generate('mixin', 'Mixin: ' + myMixins[0].name, myMixins, helper.longnameToUrl[longname]);
822
      }
823
824
      var myInterfaces = helper.find(interfaces, {
825
        longname: longname
826
      });
827
      if (myInterfaces.length) {
828
        generate('interface', 'Interface: ' + myInterfaces[0].name, myInterfaces, helper.longnameToUrl[longname]);
829
      }
830
831
      var myExternals = helper.find(externals, {
832
        longname: longname
833
      });
834
      if (myExternals.length) {
835
        generate('external', 'External: ' + myExternals[0].name, myExternals, helper.longnameToUrl[longname]);
836
      }
837
    }
838
  }
839
840
  // TODO: move the tutorial functions to templateHelper.js
841
  function generateTutorial(title, tutorial, filename) {
842
    var tutorialData = {
843
      title: title,
844
      header: tutorial.title,
845
      content: tutorial.parse(),
846
      children: tutorial.children,
847
      docs: null
848
    };
849
850
    var tutorialPath = path.join(outdir, filename),
851
      html = view.render('tutorial.tmpl', tutorialData);
852
853
    // yes, you can use {@link} in tutorials too!
854
    html = helper.resolveLinks(html); // turn {@link foo} into <a href="foodoc.html">foo</a>
855
856
    if (searchEnabled) {
857
      searchableDocuments[filename] = {
858
        "id": filename,
859
        "title": title,
860
        "body": searchData(html)
861
      };
862
    }
863
864
    fs.writeFileSync(tutorialPath, html, 'utf8');
865
  }
866
867
  // tutorials can have only one parent so there is no risk for loops
868
  function saveChildren(node) {
869
    node.children.forEach(function(child) {
870
      generateTutorial('Tutorial: ' + child.title, child, helper.tutorialToUrl(child.name));
871
      saveChildren(child);
872
    });
873
  }
874
875
  function generateQuickTextSearch(templatePath, searchableDocuments, navOptions) {
876
      var data = {
877
          searchableDocuments: JSON.stringify(searchableDocuments),
878
          navOptions: navOptions
879
      };
880
881
      var tmplString = fs.readFileSync(templatePath + "/quicksearch.tmpl").toString(),
882
            tmpl = _.template(tmplString);
883
884
      var html = tmpl(data),
885
            outpath = path.join(outdir, "quicksearch.html");
886
887
      fs.writeFileSync(outpath, html, "utf8");
888
  }
889
890
  saveChildren(tutorials);
891
892
  if (searchEnabled) {
893
      generateQuickTextSearch(templatePath + '/tmpl', searchableDocuments, navOptions);
894
  }
895
};
896