1 /** 2 * D Documentation Generator 3 * Copyright: © 2014 Economic Modeling Specialists, Intl. 4 * Authors: Brian Schott 5 * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt Boost License 1.0) 6 */ 7 module visitor; 8 9 import ddoc.comments; 10 import std.algorithm; 11 import dparse.ast; 12 import dparse.formatter; 13 import dparse.lexer; 14 import std.file; 15 import std.path; 16 import std.stdio; 17 import std.typecons; 18 import unittest_preprocessor; 19 20 21 /** 22 * Generates documentation for a module. 23 */ 24 class DocVisitor : ASTVisitor 25 { 26 /** 27 * Params: 28 * outputDirectory = The directory where files will be written 29 * macros = Macro definitions used in processing documentation comments 30 * searchIndex = A file where the search information will be written 31 * unitTestMapping = The mapping of declaration addresses to their 32 * documentation unittests 33 * fileBytes = The source code of the module as a byte array. 34 */ 35 this(string outputDirectory, string[string] macros, File searchIndex, 36 TestRange[][size_t] unitTestMapping, const(ubyte[]) fileBytes) 37 { 38 this.outputDirectory = outputDirectory; 39 this.macros = macros; 40 this.searchIndex = searchIndex; 41 this.unitTestMapping = unitTestMapping; 42 this.fileBytes = fileBytes; 43 } 44 45 override void visit(const Module mod) 46 { 47 import std.array : array; 48 import std.algorithm : map; 49 import std.path : dirName; 50 import std.range : chain, only, join; 51 import std.file : mkdirRecurse; 52 import std.conv : to; 53 54 if (mod.moduleDeclaration is null) 55 return; 56 pushAttributes(); 57 stack = cast(string[]) mod.moduleDeclaration.moduleName.identifiers.map!(a => a.text).array; 58 baseLength = stack.length; 59 moduleFileBase = chain(only(outputDirectory), stack).buildPath; 60 if (!exists(moduleFileBase.dirName())) 61 moduleFileBase.dirName().mkdirRecurse(); 62 File output = File(moduleFileBase ~ ".html", "w"); 63 64 location = output.name; 65 moduleName = to!string(stack.join(".")); 66 writeHeader(output, moduleName, baseLength - 1); 67 68 writeBreadcrumbs(output); 69 70 if (mod.moduleDeclaration.comment !is null) 71 readAndWriteComment(output, mod.moduleDeclaration.comment, macros, 72 prevComments, null, getUnittestDocTuple(mod.moduleDeclaration)); 73 74 memberStack.length = 1; 75 76 mod.accept(this); 77 78 memberStack[$ - 1].write(output, outputDirectory); 79 80 output.writeln(HTML_END); 81 output.close(); 82 } 83 84 override void visit(const EnumDeclaration ed) 85 { 86 enum formattingCode = q{ 87 f.write("enum ", ad.name.text); 88 if (ad.type !is null) 89 { 90 f.write(" : "); 91 formatter.format(ad.type); 92 } 93 }; 94 visitAggregateDeclaration!(formattingCode, "enums")(ed); 95 } 96 97 override void visit(const EnumMember member) 98 { 99 if (member.comment is null) 100 return; 101 string summary = readAndWriteComment(File.init, member.comment, macros, 102 prevComments, null, getUnittestDocTuple(member)); 103 memberStack[$ - 1].values ~= Item("#", member.name.text, summary); 104 } 105 106 override void visit(const ClassDeclaration cd) 107 { 108 enum formattingCode = q{ 109 f.write("class ", ad.name.text); 110 if (ad.baseClassList !is null) 111 formatter.format(ad.baseClassList); 112 if (ad.templateParameters !is null) 113 formatter.format(ad.templateParameters); 114 if (ad.constraint !is null) 115 formatter.format(ad.constraint); 116 }; 117 visitAggregateDeclaration!(formattingCode, "classes")(cd); 118 } 119 120 override void visit(const TemplateDeclaration td) 121 { 122 enum formattingCode = q{ 123 f.write("template ", ad.name.text); 124 if (ad.templateParameters !is null) 125 formatter.format(ad.templateParameters); 126 if (ad.constraint) 127 formatter.format(ad.constraint); 128 }; 129 visitAggregateDeclaration!(formattingCode, "templates")(td); 130 } 131 132 override void visit(const StructDeclaration sd) 133 { 134 enum formattingCode = q{ 135 f.write("struct ", ad.name.text); 136 if (ad.templateParameters) 137 formatter.format(ad.templateParameters); 138 if (ad.constraint) 139 formatter.format(ad.constraint); 140 }; 141 visitAggregateDeclaration!(formattingCode, "structs")(sd); 142 } 143 144 override void visit(const InterfaceDeclaration id) 145 { 146 enum formattingCode = q{ 147 if (ad.baseClassList !is null) 148 formatter.format(ad.baseClassList); 149 if (ad.templateParameters !is null) 150 formatter.format(ad.templateParameters); 151 if (ad.constraint !is null) 152 formatter.format(ad.constraint); 153 }; 154 visitAggregateDeclaration!(formattingCode, "interfaces")(id); 155 } 156 157 override void visit(const AliasDeclaration ad) 158 { 159 import std.path : dirSeparator; 160 if (ad.comment is null) 161 return; 162 bool first; 163 if (ad.identifierList !is null) 164 { 165 foreach (name; ad.identifierList.identifiers) 166 { 167 File f = pushSymbol(name.text, first); 168 scope(exit) popSymbol(f); 169 writeBreadcrumbs(f); 170 string type = writeAliasType(f, name.text, ad.type); 171 string summary = readAndWriteComment(f, ad.comment, macros, prevComments); 172 memberStack[$ - 2].aliases ~= Item(f.name, name.text, summary, type); 173 } 174 } 175 else foreach (initializer; ad.initializers) 176 { 177 File f = pushSymbol(initializer.name.text, first); 178 scope(exit) popSymbol(f); 179 writeBreadcrumbs(f); 180 string type = writeAliasType(f, initializer.name.text, initializer.type); 181 string summary = readAndWriteComment(f, ad.comment, macros, prevComments); 182 memberStack[$ - 2].aliases ~= Item(f.name, initializer.name.text, summary, type); 183 } 184 } 185 186 override void visit(const VariableDeclaration vd) 187 { 188 bool first; 189 foreach (const Declarator dec; vd.declarators) 190 { 191 if (dec.comment is null) 192 continue; 193 File f = pushSymbol(dec.name.text, first); 194 scope(exit) popSymbol(f); 195 writeBreadcrumbs(f); 196 string summary = readAndWriteComment(f, dec.comment, macros, prevComments); 197 memberStack[$ - 2].variables ~= Item(f.name, dec.name.text, summary, formatNode(vd.type)); 198 } 199 if (vd.comment !is null && vd.autoDeclaration !is null) foreach (ident; vd.autoDeclaration.identifiers) 200 { 201 File f = pushSymbol(ident.text, first); 202 scope(exit) popSymbol(f); 203 writeBreadcrumbs(f); 204 string summary = readAndWriteComment(f, vd.comment, macros, prevComments); 205 string storageClass; 206 foreach (storage; vd.storageClasses) 207 { 208 if (storage !is null) 209 storageClass = str(storage.token.type); 210 } 211 auto i = Item(f.name, ident.text, 212 summary, storageClass == "enum" ? null : "auto"); 213 if (storageClass == "enum") 214 memberStack[$ - 2].enums ~= i; 215 else 216 memberStack[$ - 2].variables ~= i; 217 } 218 } 219 220 override void visit(const StructBody sb) 221 { 222 pushAttributes(); 223 sb.accept(this); 224 popAttributes(); 225 } 226 227 override void visit(const BlockStatement bs) 228 { 229 pushAttributes(); 230 bs.accept(this); 231 popAttributes(); 232 } 233 234 override void visit(const Declaration dec) 235 { 236 attributes[$ - 1] ~= dec.attributes; 237 dec.accept(this); 238 if (dec.attributeDeclaration is null) 239 attributes[$ - 1] = attributes[$ - 1][0 .. $ - dec.attributes.length]; 240 } 241 242 override void visit(const AttributeDeclaration dec) 243 { 244 attributes[$ - 1] ~= dec.attribute; 245 } 246 247 override void visit(const Constructor cons) 248 { 249 if (cons.comment is null) 250 return; 251 bool first; 252 File f = pushSymbol("this", first); 253 writeFnDocumentation(f, cons, attributes[$ - 1], first); 254 } 255 256 override void visit(const FunctionDeclaration fd) 257 { 258 if (fd.comment is null) 259 return; 260 bool first; 261 File f = pushSymbol(fd.name.text, first); 262 writeFnDocumentation(f, fd, attributes[$ - 1], first); 263 } 264 265 // Deliberately skip unittests 266 override void visit(const Unittest) {} 267 268 alias visit = ASTVisitor.visit; 269 270 /// The module name in "package.package.module" format. 271 string moduleName; 272 273 /// The path to the HTML file that was generated for the module being 274 /// processed. 275 string location; 276 277 invariant 278 { 279 foreach (c; prevComments[]) 280 { 281 assert(c.sections.length >= 2); 282 } 283 } 284 285 private: 286 287 void visitAggregateDeclaration(string formattingCode, string name, A)(const A ad) 288 { 289 bool first; 290 if (ad.comment is null) 291 return; 292 File f = pushSymbol(ad.name.text, first); 293 if (first) 294 writeBreadcrumbs(f); 295 else 296 f.writeln("<hr/>"); 297 { 298 auto writer = f.lockingTextWriter(); 299 writer.put(`<pre><code>`); 300 auto formatter = new Formatter!(File.LockingTextWriter)(writer); 301 scope(exit) formatter.sink = File.LockingTextWriter.init; 302 writeAttributes(formatter, writer, attributes[$ - 1]); 303 mixin(formattingCode); 304 writer.put("\n</code></pre>"); 305 } 306 string summary = readAndWriteComment(f, ad.comment, macros, prevComments, 307 null, getUnittestDocTuple(ad)); 308 mixin(`memberStack[$ - 2].` ~ name ~ ` ~= Item(f.name, ad.name.text, summary);`); 309 ad.accept(this); 310 memberStack[$ - 1].write(f, outputDirectory); 311 312 stack = stack[0 .. $ - 1]; 313 memberStack = memberStack[0 .. $ - 1]; 314 } 315 316 /** 317 * Params: 318 * t = The declaration. 319 * Returns: An array of tuples where the first item is the contents of the 320 * unittest block and the second item is the doc comment for the 321 * unittest block. This array may be empty. 322 */ 323 Tuple!(string, string)[] getUnittestDocTuple(T)(const T t) 324 { 325 immutable size_t index = cast(size_t) (cast(void*) t); 326 // writeln("Searching for unittest associated with ", index); 327 auto tupArray = index in unitTestMapping; 328 if (tupArray is null) 329 return []; 330 // writeln("Found a doc unit test for ", cast(size_t) &t); 331 Tuple!(string, string)[] rVal; 332 foreach (tup; *tupArray) 333 rVal ~= tuple(cast(string) fileBytes[tup[0] + 2 .. tup[1]], tup[2]); 334 return rVal; 335 } 336 337 /** 338 * 339 */ 340 void writeFnDocumentation(Fn)(File f, Fn fn, const(Attribute)[] attrs, bool first) 341 { 342 auto writer = f.lockingTextWriter(); 343 if (first) 344 writeBreadcrumbs(f); 345 else 346 writer.put("<hr/>"); 347 auto formatter = new Formatter!(File.LockingTextWriter)(writer); 348 scope(exit) formatter.sink = File.LockingTextWriter.init; 349 writer.put(`<pre><code>`); 350 writeAttributes(formatter, writer, attrs); 351 static if (__traits(hasMember, typeof(fn), "returnType")) 352 { 353 if (fn.returnType) 354 { 355 formatter.format(fn.returnType); 356 writer.put(" "); 357 } 358 formatter.format(fn.name); 359 } 360 else 361 writer.put("this"); 362 if (fn.templateParameters !is null) 363 formatter.format(fn.templateParameters); 364 if (fn.parameters !is null) 365 formatter.format(fn.parameters); 366 foreach (a; fn.memberFunctionAttributes) 367 { 368 writer.put(" "); 369 formatter.format(a); 370 } 371 if (fn.constraint) 372 { 373 writer.put(" "); 374 formatter.format(fn.constraint); 375 } 376 writer.put("\n</code></pre>"); 377 string summary = readAndWriteComment(f, fn.comment, macros, 378 prevComments, fn.functionBody, getUnittestDocTuple(fn)); 379 string fdName; 380 static if (__traits(hasMember, typeof(fn), "name")) 381 fdName = fn.name.text; 382 else 383 fdName = "this"; 384 memberStack[$ - 2].functions ~= Item(f.name, fdName, summary); 385 fn.accept(this); 386 stack = stack[0 .. $ - 1]; 387 memberStack = memberStack[0 .. $ - 1]; 388 } 389 390 /** 391 * Writes attributes to the given writer using the given formatter. 392 * Params: 393 * F = The formatter type 394 * W = The writer type 395 * formatter = The formatter instance to use 396 * writer = The writer that will be output to. 397 * attrs = The attributes to write. 398 */ 399 void writeAttributes(F, W)(F formatter, W writer, const(Attribute)[] attrs) 400 { 401 IdType protection; 402 if (attrs is null) 403 attrs = attributes[$ - 1]; 404 if (attributes.length > 0) foreach (a; attrs) 405 { 406 if (isProtection(a.attribute.type)) 407 protection = a.attribute.type; 408 } 409 switch (protection) 410 { 411 case tok!"private": writer.put("private "); break; 412 case tok!"package": writer.put("package "); break; 413 default: writer.put("public "); break; 414 } 415 if (attributes.length > 0) foreach (a; attrs) 416 { 417 if (!isProtection(a.attribute.type)) 418 { 419 formatter.format(a); 420 writer.put(" "); 421 } 422 } 423 } 424 425 /** 426 * Formats an AST node to a string 427 */ 428 static string formatNode(T)(const T t) 429 { 430 import std.array; 431 auto writer = appender!string(); 432 auto formatter = new Formatter!(typeof(writer))(writer); 433 formatter.format(t); 434 return writer.data; 435 } 436 437 /** 438 * Writes an alias' type to the given file and returns it. 439 * Params: 440 * f = The file to write to 441 * name = the name of the alias 442 * t = the aliased type 443 * Returns: A string reperesentation of the given type. 444 */ 445 static string writeAliasType(File f, string name, const Type t) 446 { 447 if (t is null) 448 return null; 449 f.write(`<pre><code>`); 450 f.write("alias ", name, " = "); 451 string formatted = formatNode(t); 452 f.write(formatted); 453 f.writeln(`</code></pre>`); 454 return formatted; 455 } 456 457 /** 458 * Writes navigation breadcrumbs in HTML format to the given file. 459 */ 460 void writeBreadcrumbs(File f) 461 { 462 import std.array : join; 463 import std.conv : to; 464 import std.range : chain, only; 465 f.writeln(`<div class="breadcrumbs">`); 466 f.writeln(`<table id="results"></table>`); 467 f.writeln(`<input type="search" id="search" placeholder="Search" onkeyup="searchSubmit(this.value, event)"/>`); 468 foreach (i; 0 .. stack.length) 469 { 470 if (i + 1 < stack.length) 471 { 472 if (i >= baseLength - 1) 473 { 474 string link = buildPath(chain(stack[0 .. baseLength - 1], 475 only(stack[baseLength - 1.. $ - 1].join(".")))); 476 // writeln(link, ".html"); 477 f.write(`<a href="`, stripLeadingDirectory(link, outputDirectory), `.html">`); 478 f.write(stack[i]); 479 f.write(`</a>.`); 480 } 481 else 482 { 483 f.write(stack[i]); 484 f.write("."); 485 } 486 } 487 else 488 f.write(stack[i]); 489 } 490 f.writeln(`</div>`); 491 } 492 493 /** 494 * Params: 495 * name = The symbol's name 496 * first = True if this is the first time that pushSymbol has been 497 * called for this name. 498 * isFunction = True if the symbol being pushed is a function, false 499 * otherwise. 500 * Returns: A file that the symbol's documentation should be written to. 501 */ 502 File pushSymbol(string name, ref bool first) 503 { 504 import std.array : array, join; 505 import std.string : format; 506 stack ~= name; 507 memberStack.length = memberStack.length + 1; 508 string classDocFileName = format("%s.%s.html", moduleFileBase, 509 join(stack[baseLength .. $], ".").array); 510 immutable string path = stripLeadingDirectory(classDocFileName, outputDirectory); 511 searchIndex.writefln(`{"%s" : "%s"},`, join(stack, ".").array, path); 512 immutable size_t i = memberStack.length - 2; 513 assert (i < memberStack.length, "%s %s".format(i, memberStack.length)); 514 auto p = classDocFileName in memberStack[i].overloadFiles; 515 first = p is null; 516 if (first) 517 { 518 first = true; 519 auto f = File(classDocFileName, "w"); 520 memberStack[i].overloadFiles[classDocFileName] = f; 521 writeHeader(f, name, baseLength - 1); 522 return f; 523 } 524 else 525 return *p; 526 } 527 528 void popSymbol(File f) 529 { 530 f.writeln(HTML_END); 531 stack = stack[0 .. $ - 1]; 532 memberStack = memberStack[0 .. $ - 1]; 533 } 534 535 void pushAttributes() 536 { 537 attributes.length = attributes.length + 1; 538 } 539 540 void popAttributes() 541 { 542 attributes = attributes[0 .. $ - 1]; 543 } 544 545 const(Attribute)[][] attributes; 546 Comment[] prevComments; 547 size_t baseLength; 548 string outputDirectory; 549 string moduleFileBase; 550 string[] stack; 551 string[string] macros; 552 Members[] memberStack; 553 File searchIndex; 554 TestRange[][size_t] unitTestMapping; 555 const(ubyte[]) fileBytes; 556 } 557 558 /** 559 * Writes HTML header information to the given file. 560 * Params: 561 * f = The file to write to 562 * title = The content of the HTML "title" element 563 * depth = The directory depth of the file. This is used for ensuring that 564 * the "base" element is correct so that links resolve properly. 565 */ 566 void writeHeader(File f, string title, size_t depth) 567 { 568 f.write(`<!DOCTYPE html> 569 <html> 570 <head> 571 <meta charset="utf-8"/> 572 <link rel="stylesheet" type="text/css" href="`); 573 foreach (i; 0 .. depth) 574 f.write("../"); 575 f.write(`style.css"/><script src="`); 576 foreach (i; 0 .. depth) 577 f.write("../"); 578 f.write(`highlight.pack.js"></script> 579 <title>`); 580 f.write(title); 581 f.writeln(`</title>`); 582 f.write(`<base href="`); 583 foreach (i; 0 .. depth) 584 f.write("../"); 585 f.write(`"/> 586 <script src="search.js"></script> 587 </head> 588 <body>`); 589 } 590 591 /** 592 * Writes a doc comment to the given file and returns the summary text. 593 * Params: 594 * f = The file to write the comment to 595 * comment = The comment to write 596 * macros = Macro definitions used in processing the comment 597 * prevComments = Previously encountered comments. This is used for handling 598 * "ditto" comments. May be null. 599 * functionBody = A function body used for writing contract information. May 600 * be null. 601 * testdocs = Pairs of unittest bodies and unittest doc comments. May be null. 602 * Returns: the summary from the given comment 603 */ 604 string readAndWriteComment(File f, string comment, ref string[string] macros, 605 ref Comment[] prevComments, const FunctionBody functionBody = null, 606 Tuple!(string, string)[] testDocs = null) 607 { 608 assert(comment !is null); 609 610 Comment c = parseComment(comment, macros); 611 assert(c.sections.length >= 2); 612 if (c.isDitto) 613 { 614 if (prevComments.length) 615 c = prevComments[$ - 1]; 616 else 617 stderr.writeln("Found 'ditto' comment without preceding comment to copy."); 618 } 619 else 620 prevComments ~= c; 621 622 if (f != File.init) 623 writeComment(f, c, functionBody); 624 string rVal = ""; 625 if (c.sections.length && c.sections[0].content !is null) 626 rVal = c.sections[0].content; 627 else 628 { 629 foreach (section; c.sections) 630 { 631 if (section.name == "Returns") 632 rVal = "Returns: " ~ section.content; 633 } 634 } 635 if (f != File.init && testDocs !is null) foreach (doc; testDocs) 636 { 637 // writeln("Writing a unittest doc comment"); 638 import std.string : outdent; 639 f.writeln(`<div class="section"><h3>Example</h3>`); 640 Comment dc = parseComment(doc[1], macros); 641 writeComment(f, dc); 642 f.writeln(`<pre><code>`, outdent(doc[0]), `</code></pre>`); 643 f.writeln(`</div>`); 644 } 645 return rVal; 646 } 647 /** 648 * Returns: the input string with its first directory removed. 649 */ 650 string stripLeadingDirectory(string s, string outputDirectory) 651 { 652 import std.path : pathSplitter, buildPath; 653 654 auto r1 = pathSplitter(s); 655 auto r2 = pathSplitter(outputDirectory); 656 while (!r1.empty && !r2.empty && r1.front == r2.front) 657 { 658 r1.popFront(); 659 r2.popFront(); 660 } 661 return buildPath(r1); 662 } 663 664 /// 665 unittest 666 { 667 import std.stdio : stderr; 668 669 immutable a = stripLeadingDirectory("foo/bar/baz/", "foo"); 670 assert(a == "bar/baz", a); 671 immutable b = stripLeadingDirectory("/home/user/project/docs", "/home/user/project/docs"); 672 assert(b == "", b); 673 immutable c = stripLeadingDirectory("/home/user/project/docs/file.name.html", 674 "/home/user/project/docs"); 675 assert(c == "file.name.html", c); 676 } 677 678 private: 679 680 void writeComment(File f, Comment comment, const FunctionBody functionBody = null) 681 in 682 { 683 assert(comment.sections.length >= 2); 684 } 685 body 686 { 687 foreach (i; 0 .. 2) 688 { 689 if (comment.sections[i].content is null) 690 continue; 691 f.writeln(`<div class="section">`); 692 f.writeln(comment.sections[i].content); 693 f.writeln(`</div>`); 694 } 695 if (functionBody !is null) 696 writeContracts(f, functionBody.inStatement, functionBody.outStatement); 697 foreach (section; comment.sections[2 .. $]) 698 { 699 if (section.name == "Macros") 700 continue; 701 f.writeln(`<div class="section">`); 702 if (section.name != "Summary" && section.name != "Description") 703 { 704 f.write("<h3>"); 705 f.write(prettySectionName(section.name)); 706 f.writeln("</h3>"); 707 } 708 if (section.name == "Params") 709 { 710 f.writeln(`<table class="params">`); 711 foreach (kv; section.mapping) 712 { 713 f.write(`<tr class="param"><td class="paramName">`); 714 f.write(kv[0]); 715 f.write(`</td><td class="paramDoc">`); 716 f.write(kv[1]); 717 f.writeln("</td></tr>"); 718 } 719 f.write("</table>"); 720 } 721 else 722 { 723 f.writeln(section.content); 724 } 725 f.writeln(`</div>`); 726 } 727 } 728 729 void writeContracts(File f, const InStatement inStatement, 730 const OutStatement outStatement) 731 { 732 if (inStatement is null && outStatement is null) 733 return; 734 f.write(`<div class="section"><h3>Contracts</h3><pre><code>`); 735 auto formatter = new Formatter!(File.LockingTextWriter)(f.lockingTextWriter()); 736 scope(exit) formatter.sink = File.LockingTextWriter.init; 737 if (inStatement !is null) 738 { 739 formatter.format(inStatement); 740 if (outStatement !is null) 741 f.writeln(); 742 } 743 if (outStatement !is null) 744 formatter.format(outStatement); 745 f.writeln("</code></pre></div>"); 746 } 747 748 string prettySectionName(string sectionName) 749 { 750 switch (sectionName) 751 { 752 case "See_also": return "See Also"; 753 case "Params": return "Parameters"; 754 default: return sectionName; 755 } 756 } 757 758 enum HTML_END = ` 759 <script>hljs.initHighlightingOnLoad();</script> 760 </body> 761 </html>`; 762 763 struct Item 764 { 765 string url; 766 string name; 767 string summary; 768 string type; 769 770 void write(File f, string outputDirectory) 771 { 772 f.write(`<tr><td>`); 773 if (url == "#") 774 f.write(name, `</td>`); 775 else 776 f.write(`<a href="`, stripLeadingDirectory(url, outputDirectory), `">`, name, `</a></td>`); 777 if (type is null) 778 f.write(`<td></td><td>`, summary ,`</td></tr>`); 779 else 780 f.write(`<td><pre><code>`, type, `</code></pre></td><td>`, summary ,`</td></tr>`); 781 } 782 } 783 784 struct Members 785 { 786 File[string] overloadFiles; 787 Item[] aliases; 788 Item[] classes; 789 Item[] enums; 790 Item[] functions; 791 Item[] interfaces; 792 Item[] structs; 793 Item[] templates; 794 Item[] values; 795 Item[] variables; 796 797 void write(File f, string outputDirectory) 798 { 799 if (aliases.length == 0 && classes.length == 0 && enums.length == 0 800 && functions.length == 0 && interfaces.length == 0 801 && structs.length == 0 && templates.length == 0 && values.length == 0 802 && variables.length == 0) 803 { 804 return; 805 } 806 f.writeln(`<div class="section">`); 807 if (enums.length > 0) 808 write(f, enums, "Enums", outputDirectory); 809 if (aliases.length > 0) 810 write(f, aliases, "Aliases", outputDirectory); 811 if (variables.length > 0) 812 write(f, variables, "Variables", outputDirectory); 813 if (functions.length > 0) 814 write(f, functions, "Functions", outputDirectory); 815 if (structs.length > 0) 816 write(f, structs, "Structs", outputDirectory); 817 if (interfaces.length > 0) 818 write(f, interfaces, "Interfaces", outputDirectory); 819 if (classes.length > 0) 820 write(f, classes, "Classes", outputDirectory); 821 if (templates.length > 0) 822 write(f, templates, "Templates", outputDirectory); 823 if (values.length > 0) 824 write(f, values, "Values", outputDirectory); 825 f.writeln(`</div>`); 826 foreach (f; overloadFiles) 827 { 828 f.writeln(HTML_END); 829 f.close(); 830 } 831 } 832 833 private: 834 835 void write(File f, Item[] items, string name, string outputDirectory) 836 { 837 f.writeln(`<h3>`, name, `</h3>`); 838 f.writeln(`<table>`); 839 foreach (i; items) 840 i.write(f, outputDirectory); 841 f.writeln(`</table>`); 842 } 843 }