1 module adrdox.main; 2 3 __gshared string skeletonFile = "skeleton.html"; 4 __gshared string outputDirectory = "generated-docs"; 5 __gshared TexMathOpt texMathOpt = TexMathOpt.LaTeX; 6 7 __gshared bool writePrivateDocs = false; 8 __gshared bool documentInternal = false; 9 __gshared bool documentUndocumented = false; 10 __gshared bool minimalDescent = false; 11 12 version(linux) 13 __gshared bool caseInsensitiveFilenames = false; 14 else 15 __gshared bool caseInsensitiveFilenames = true; 16 17 18 /* 19 20 Glossary feature: little short links that lead somewhere else. 21 22 23 FIXME: it should be able to handle bom. consider core/thread.d the unittest example is offset. 24 25 26 FIXME: 27 * make sure there's a link to the source for everything 28 * search 29 * package index without needing to build everything at once 30 * version specifiers 31 * prettified constraints 32 */ 33 34 import dparse.parser; 35 import dparse.lexer; 36 import dparse.ast; 37 38 import arsd.dom; 39 import arsd.docgen.comment; 40 41 import std.algorithm :sort, canFind; 42 43 import std.string; 44 import std.conv : to; 45 46 string handleCaseSensitivity(string s) { 47 if(!caseInsensitiveFilenames) 48 return s; 49 string ugh; 50 foreach(ch; s) { 51 if(ch >= 'A' && ch <= 'Z') 52 ugh ~= "_"; 53 ugh ~= ch; 54 } 55 return ugh; 56 } 57 58 // returns empty string if file not found 59 string findStandardFile(bool dofail=true) (string stdfname) { 60 import std.file : exists, thisExePath; 61 import std.path : buildPath, dirName; 62 if (!stdfname.exists) { 63 if (stdfname.length && stdfname[0] != '/') { 64 auto path = thisExePath; 65 static foreach (i; 0..2) { 66 { 67 path = path.dirName; 68 string newname = buildPath(path, stdfname); 69 if (newname.exists) return newname; 70 } 71 } 72 } 73 static if (dofail) throw new Exception("standard file '" ~stdfname ~ "' not found!"); 74 } 75 return stdfname; 76 } 77 78 void copyStandardFileTo(bool timecheck=true) (string destname, string stdfname) { 79 import std.file; 80 if (exists(destname)) { 81 static if (timecheck) { 82 if (timeLastModified(destname) >= timeLastModified(findStandardFile(stdfname))) return; 83 } else { 84 return; 85 } 86 } 87 copy(findStandardFile(stdfname), destname); 88 } 89 90 __gshared static Object directoriesForPackageMonitor = new Object; // intentional CTFE 91 __gshared string[string] directoriesForPackage; 92 string getDirectoryForPackage(string packageName) { 93 94 if(packageName.indexOf("/") != -1) 95 return ""; // not actually a D package! 96 if(packageName.indexOf("#") != -1) 97 return ""; // not actually a D package! 98 99 string bestMatch = ""; 100 int bestMatchDots = -1; 101 102 import std.path; 103 synchronized(directoriesForPackageMonitor) 104 foreach(pkg, dir; directoriesForPackage) { 105 if(globMatch!(CaseSensitive.yes)(packageName, pkg)) { 106 int cnt; 107 foreach(ch; pkg) 108 if(ch == '.') 109 cnt++; 110 if(cnt > bestMatchDots) { 111 bestMatch = dir; 112 bestMatchDots = cnt; 113 } 114 } 115 } 116 return bestMatch; 117 } 118 119 120 // FIXME: make See Also automatically list dittos that are not overloads 121 122 enum skip_undocumented = true; 123 124 static bool sorter(Decl a, Decl b) { 125 if(a.declarationType == b.declarationType) 126 return (blogMode && a.declarationType == "Article") ? (b.name < a.name) : (a.name < b.name); 127 else if(a.declarationType == "module" || b.declarationType == "module") // always put modules up top 128 return 129 (a.declarationType == "module" ? "aaaaaa" : a.declarationType) 130 < (b.declarationType == "module" ? "aaaaaa" : b.declarationType); 131 else 132 return a.declarationType < b.declarationType; 133 } 134 135 void annotatedPrototype(T)(T decl, MyOutputRange output) { 136 static if(is(T == TemplateDecl)) { 137 auto td = cast(TemplateDecl) decl; 138 auto epony = td.eponymousMember; 139 if(epony) { 140 if(auto e = cast(FunctionDecl) epony) { 141 doFunctionDec(e, output); 142 return; 143 } 144 } 145 } 146 147 148 auto classDec = decl.astNode; 149 150 auto f = new MyFormatter!(typeof(output))(output, decl); 151 152 void writePrototype() { 153 output.putTag("<div class=\"aggregate-prototype\">"); 154 155 if(decl.parent !is null && !decl.parent.isModule) { 156 output.putTag("<div class=\"parent-prototype\">"); 157 decl.parent.getSimplifiedPrototype(output); 158 output.putTag("</div><div>"); 159 } 160 161 writeAttributes(f, output, decl.attributes); 162 163 output.putTag("<span class=\"builtin-type\">"); 164 output.put(decl.declarationType); 165 output.putTag("</span>"); 166 output.put(" "); 167 output.put(decl.name); 168 output.put(" "); 169 170 foreach(idx, ir; decl.inheritsFrom()) { 171 if(idx == 0) 172 output.put(" : "); 173 else 174 output.put(", "); 175 if(ir.decl is null) 176 output.put(ir.plainText); 177 else 178 output.putTag(`<a class="xref parent-class" href=`~ir.decl.link~`>`~ir.decl.name~`</a> `); 179 } 180 181 if(classDec.templateParameters) 182 f.format(classDec.templateParameters); 183 if(classDec.constraint) 184 f.format(classDec.constraint); 185 186 // FIXME: invariant 187 188 if(decl.children.length) { 189 output.put(" {"); 190 foreach(child; decl.children) { 191 if((child.isPrivate() && !writePrivateDocs)) 192 continue; 193 // I want to show undocumented plain data members (if not private) 194 // since they might be used as public fields or in ctors for simple 195 // structs, but I'll skip everything else undocumented. 196 if(!child.isDocumented() && (cast(VariableDecl) child) is null) 197 continue; 198 output.putTag("<div class=\"aggregate-member\">"); 199 if(child.isDocumented()) 200 output.putTag("<a href=\""~child.link~"\""); 201 child.getAggregatePrototype(output); 202 if(child.isDocumented()) 203 output.putTag("</a>"); 204 output.putTag("</div>"); 205 } 206 output.put("}"); 207 } 208 209 if(decl.parent !is null && !decl.parent.isModule) { 210 output.putTag("</div>"); 211 } 212 213 output.putTag("</div>"); 214 } 215 216 217 writeOverloads!writePrototype(decl, output); 218 } 219 220 221 void doEnumDecl(T)(T decl, Element content) 222 { 223 auto enumDec = decl.astNode; 224 static if(is(typeof(enumDec) == const(AnonymousEnumDeclaration))) { 225 if(enumDec.members.length == 0) return; 226 auto name = enumDec.members[0].name.text; 227 auto type = enumDec.baseType; 228 auto members = enumDec.members; 229 } else { 230 auto name = enumDec.name.text; 231 auto type = enumDec.type; 232 const(EnumMember)[] members; 233 if(enumDec.enumBody) 234 members = enumDec.enumBody.enumMembers; 235 } 236 237 static if(is(typeof(enumDec) == const(AnonymousEnumDeclaration))) { 238 // undocumented anonymous enums get a pass if any of 239 // their members are documented because that's what dmd does... 240 // FIXME maybe 241 242 bool foundOne = false; 243 foreach(member; members) { 244 if(member.comment.length) { 245 foundOne = true; 246 break; 247 } 248 } 249 250 if(!foundOne && skip_undocumented) 251 return; 252 } else { 253 if(enumDec.comment.length == 0 && skip_undocumented) 254 return; 255 } 256 257 /* 258 auto f = new MyFormatter!(typeof(output))(output); 259 260 if(type) { 261 output.putTag("<div class=\"base-type\">"); 262 f.format(type); 263 output.putTag("</div>"); 264 } 265 */ 266 267 content.addChild("h2", "Values").attrs.id = "values"; 268 269 auto table = content.addChild("table").addClass("enum-members"); 270 table.appendHtml("<tr><th>Value</th><th>Meaning</th></tr>"); 271 272 foreach(member; members) { 273 auto memberComment = formatDocumentationComment(preprocessComment(member.comment, decl), decl); 274 auto tr = table.addChild("tr"); 275 tr.addClass("enum-member"); 276 tr.attrs.id = member.name.text; 277 278 auto td = tr.addChild("td"); 279 280 if(member.isDisabled) { 281 td.addChild("span", "@disable").addClass("enum-disabled"); 282 td.addChild("br"); 283 } 284 285 if(member.type) { 286 td.addChild("span", toHtml(member.type)).addClass("enum-type"); 287 } 288 289 td.addChild("a", member.name.text, "#" ~ member.name.text).addClass("enum-member-name"); 290 291 if(member.assignExpression) { 292 td.addChild("span", toHtml(member.assignExpression)).addClass("enum-member-value"); 293 } 294 295 auto ea = td.addChild("div", "", "enum-attributes"); 296 foreach(attribute; member.atAttributes) { 297 ea.addChild("div", toHtml(attribute)); 298 } 299 300 // type 301 // assignExpression 302 td = tr.addChild("td"); 303 td.innerHTML = memberComment; 304 305 if(member.deprecated_) { 306 auto p = td.prependChild(Element.make("div", Element.make("span", member.deprecated_.stringLiterals.length ? "Deprecated: " : "Deprecated", "deprecated-label"))).addClass("enum-deprecated"); 307 foreach(sl; member.deprecated_.stringLiterals) 308 p.addChild("span", sl.text[1 .. $-1]); 309 } 310 311 // I might write to parent list later if I can do it all semantically inside the anonymous enum 312 // since these names are introduced into the parent scope i think it is justified to list them there 313 // maybe. 314 } 315 } 316 317 318 void doFunctionDec(T)(T decl, MyOutputRange output) 319 { 320 auto functionDec = decl.astNode; 321 322 //if(!decl.isDocumented() && skip_undocumented) 323 //return; 324 325 string[] conceptsFound; 326 327 auto f = new MyFormatter!(typeof(output))(output, decl); 328 329 /+ 330 331 auto comment = parseDocumentationComment(dittoSupport(functionDec, functionDec.comment), fullyQualifiedName ~ name); 332 333 if(auto ptr = name in overloadChain[$-1]) { 334 *ptr += 1; 335 name ~= "." ~ to!string(*ptr); 336 } else { 337 overloadChain[$-1][name] = 1; 338 overloadNodeChain[$-1] = cast() functionDec; 339 } 340 341 const(ASTNode)[] overloadsList; 342 343 if(auto overloads = overloadNodeChain[$-1] in additionalModuleInfo.overloadsOf) { 344 overloadsList = *overloads; 345 } 346 347 if(dittoChain[$-1]) 348 if(auto dittos = dittoChain[$-1] in additionalModuleInfo.dittos) { 349 auto list = *dittos; 350 outer: foreach(item; list) { 351 if(item is functionDec) 352 continue; 353 354 // already listed as a formal overload which means we 355 // don't need to list it again under see also 356 foreach(ol; overloadsList) 357 if(ol is item) 358 continue outer; 359 360 string linkHtml; 361 linkHtml ~= `<a href="`~htmlEncode(linkMapping[item])~`">` ~ htmlEncode(nameMapping[item]) ~ `</a>`; 362 363 comment.see_alsos ~= linkHtml; 364 } 365 } 366 367 descendInto(name); 368 369 output.putTag("<h1><span class=\"entity-name\">" ~ name ~ "</span> "~typeString~"</h1>"); 370 371 writeBreadcrumbs(); 372 373 output.putTag("<div class=\"function-declaration\">"); 374 375 comment.writeSynopsis(output); 376 +/ 377 378 void writeFunctionPrototype() { 379 string outputStr; 380 auto originalOutput = output; 381 382 MyOutputRange output = MyOutputRange(&outputStr); 383 auto f = new MyFormatter!(typeof(output))(output); 384 385 output.putTag("<div class=\"function-prototype\">"); 386 387 //output.putTag(`<a href="http://dpldocs.info/reading-prototypes" id="help-link">?</a>`); 388 389 if(decl.parent !is null && !decl.parent.isModule) { 390 output.putTag("<div class=\"parent-prototype\">"); 391 decl.parent.getSimplifiedPrototype(output); 392 output.putTag("</div><div>"); 393 } 394 395 writeAttributes(f, output, decl.attributes); 396 397 static if( 398 !is(typeof(functionDec) == const(Constructor)) && 399 !is(typeof(functionDec) == const(Postblit)) && 400 !is(typeof(functionDec) == const(Destructor)) 401 ) { 402 output.putTag("<div class=\"return-type\">"); 403 404 if (functionDec.hasAuto && functionDec.hasRef) 405 output.putTag(`<a class="lang-feature" href="http://dpldocs.info/auto-ref-function-return-prototype">auto ref</a> `); 406 else { 407 if (functionDec.hasAuto) 408 output.putTag(`<a class="lang-feature" href="http://dpldocs.info/auto-function-return-prototype">auto</a> `); 409 if (functionDec.hasRef) 410 output.putTag(`<a class="lang-feature" href="http://dpldocs.info/ref-function-return-prototype">ref</a> `); 411 } 412 413 if (functionDec.returnType !is null) 414 f.format(functionDec.returnType); 415 416 output.putTag("</div>"); 417 } 418 419 output.putTag("<div class=\"function-name\">"); 420 output.put(decl.name); 421 output.putTag("</div>"); 422 423 output.putTag("<div class=\"template-parameters\" data-count=\""~to!string((functionDec.templateParameters && functionDec.templateParameters.templateParameterList) ? functionDec.templateParameters.templateParameterList.items.length : 0)~"\">"); 424 if (functionDec.templateParameters !is null) 425 f.format(functionDec.templateParameters); 426 output.putTag("</div>"); 427 output.putTag("<div class=\"runtime-parameters\" data-count=\""~to!string(functionDec.parameters.parameters.length)~"\">"); 428 f.format(functionDec.parameters); 429 output.putTag("</div>"); 430 if(functionDec.constraint !is null) { 431 output.putTag("<div class=\"template-constraint\">"); 432 f.format(functionDec.constraint); 433 output.putTag("</div>"); 434 } 435 if(functionDec.functionBody !is null) { 436 // FIXME: list inherited contracts 437 output.putTag("<div class=\"function-contracts\">"); 438 import dparse.formatter; 439 auto f2 = new Formatter!(typeof(output))(output); 440 441 // I'm skipping statements cuz they ugly. but the shorter expressions aren't too bad 442 if(functionDec.functionBody.inStatement && functionDec.functionBody.inStatement.expression) { 443 output.putTag("<div class=\"in-contract\">"); 444 f2.format(functionDec.functionBody.inStatement); 445 output.putTag("</div>"); 446 } 447 if(functionDec.functionBody.outStatement) { 448 output.putTag("<div class=\"out-contract\">"); 449 f2.format(functionDec.functionBody.outStatement); 450 output.putTag("</div>"); 451 } 452 453 output.putTag("</div>"); 454 } 455 //output.put(" : "); 456 //output.put(to!string(functionDec.name.line)); 457 458 459 if(decl.parent !is null && !decl.parent.isModule) { 460 output.putTag("</div>"); 461 decl.parent.writeTemplateConstraint(output); 462 } 463 464 output.putTag("</div>"); 465 466 originalOutput.putTag(linkUpHtml(outputStr, decl)); 467 } 468 469 470 writeOverloads!writeFunctionPrototype(decl, output); 471 } 472 473 void writeOverloads(alias writePrototype, D : Decl)(D decl, ref MyOutputRange output) { 474 auto overloadsList = decl.getImmediateDocumentedOverloads(); 475 476 // I'm treating dittos similarly to overloads 477 if(overloadsList.length == 1) { 478 overloadsList = decl.getDittos(); 479 } else { 480 foreach(ditto; decl.getDittos()) { 481 if(!overloadsList.canFind(ditto)) 482 overloadsList ~= ditto; 483 } 484 } 485 if(overloadsList.length > 1) { 486 import std.conv; 487 output.putTag("<ol class=\"overloads\">"); 488 foreach(idx, item; overloadsList) { 489 assert(item.parent !is null); 490 string cn; 491 Decl epony; 492 if(auto t = cast(TemplateDecl) item) 493 epony = t.eponymousMember; 494 495 if(item !is decl && decl !is epony) 496 cn = "overload-option"; 497 else 498 cn = "active-overload-option"; 499 500 if(item.name != decl.name) 501 cn ~= " ditto-option"; 502 503 output.putTag("<li class=\""~cn~"\">"); 504 505 //if(item is decl) 506 //writeFunctionPrototype(); 507 //} else { 508 { 509 if(item !is decl) 510 output.putTag(`<a href="`~item.link~`">`); 511 512 output.putTag("<span class=\"overload-signature\">"); 513 item.getSimplifiedPrototype(output); 514 output.putTag("</span>"); 515 if(item !is decl) 516 output.putTag(`</a>`); 517 } 518 519 if(item is decl || decl is epony) 520 writePrototype(); 521 522 output.putTag("</li>"); 523 } 524 output.putTag("</ol>"); 525 } else { 526 writePrototype(); 527 } 528 } 529 530 531 void writeAttributes(F, W)(F formatter, W writer, const(VersionOrAttribute)[] attrs, bool bracket = true) 532 { 533 534 if(bracket) writer.putTag("<div class=\"attributes\">"); 535 IdType protection; 536 537 string versions; 538 539 const(VersionOrAttribute)[] remainingBuiltIns; 540 const(VersionOrAttribute)[] remainingCustoms; 541 542 foreach(a; attrs) { 543 if (a.attr && isProtection(a.attr.attribute.type)) { 544 protection = a.attr.attribute.type; 545 } else if (auto v = cast(VersionFakeAttribute) a) { 546 if(versions.length) 547 versions ~= " && "; 548 if(v.inverted) 549 versions ~= "!"; 550 versions ~= v.cond; 551 } else if(a.attr && a.attr.attribute.type == tok!"auto") { 552 // skipping auto because it is already handled as the return value 553 } else if(a.isBuiltIn) { 554 remainingBuiltIns ~= a; 555 } else { 556 remainingCustoms ~= a; 557 } 558 } 559 560 if(versions.length) { 561 writer.putTag("<div class=\"versions-container\">"); 562 writer.put("version("); 563 writer.put(versions); 564 writer.put(")"); 565 writer.putTag("</div>"); 566 } 567 568 switch (protection) 569 { 570 case tok!"private": writer.put("private "); break; 571 case tok!"package": writer.put("package "); break; 572 case tok!"protected": writer.put("protected "); break; 573 case tok!"export": writer.put("export "); break; 574 case tok!"public": // see below 575 default: 576 // I'm not printing public so this is commented intentionally 577 // public is the default state of documents so saying it outright 578 // is kinda just a waste of time IMO. 579 //writer.put("public "); 580 break; 581 } 582 583 void innards(const VersionOrAttribute a) { 584 if(auto fakeAttr = cast(const MemberFakeAttribute) a) { 585 formatter.format(fakeAttr.attr); 586 writer.put(" "); 587 } else { 588 formatter.format(a.attr); 589 writer.put(" "); 590 } 591 } 592 593 foreach (a; remainingBuiltIns) 594 innards(a); 595 foreach (a; remainingCustoms) { 596 writer.putTag("<div class=\"uda\">"); 597 innards(a); 598 writer.putTag("</div>"); 599 } 600 601 if(bracket) writer.putTag("</div>"); 602 } 603 604 class VersionOrAttribute { 605 const(Attribute) attr; 606 this(const(Attribute) attr) { 607 this.attr = attr; 608 } 609 610 bool isBuiltIn() const { 611 if(attr is null) return false; 612 if(attr.atAttribute is null) return true; // any keyword can be safely assumed... 613 614 return phelper(attr.atAttribute); 615 } 616 617 protected bool phelper(const AtAttribute at) const { 618 string txt = toText(at); 619 620 if(txt == "@(nogc)") return true; 621 if(txt == "@(disable)") return true; 622 if(txt == "@(live)") return true; 623 if(txt == "@(property)") return true; 624 625 if(txt == "@(safe)") return true; 626 if(txt == "@(system)") return true; 627 if(txt == "@(trusted)") return true; 628 629 return false; 630 } 631 632 const(VersionOrAttribute) invertedClone() const { 633 return new VersionOrAttribute(attr); 634 } 635 } 636 637 class FakeAttribute : VersionOrAttribute { 638 this() { super(null); } 639 abstract string toHTML() const; 640 } 641 642 class MemberFakeAttribute : FakeAttribute { 643 const(MemberFunctionAttribute) attr; 644 this(const(MemberFunctionAttribute) attr) { 645 this.attr = attr; 646 } 647 648 override string toHTML() const { 649 return toText(attr); 650 } 651 652 override bool isBuiltIn() const { 653 if(attr is null) return false; 654 if(attr.atAttribute is null) return true; 655 return phelper(attr.atAttribute); 656 } 657 } 658 659 class VersionFakeAttribute : FakeAttribute { 660 string cond; 661 bool inverted; 662 this(string condition, bool inverted = false) { 663 cond = condition; 664 this.inverted = inverted; 665 } 666 667 override const(VersionFakeAttribute) invertedClone() const { 668 return new VersionFakeAttribute(cond, !inverted); 669 } 670 671 override bool isBuiltIn() const { 672 return false; 673 } 674 675 override string toHTML() const { 676 auto element = Element.make("span"); 677 element.addChild("span", "version", "lang-feature"); 678 element.appendText("("); 679 if(inverted) 680 element.appendText("!"); 681 element.addChild("span", cond); 682 element.appendText(")"); 683 return element.toString; 684 } 685 } 686 687 void putSimplfiedReturnValue(MyOutputRange output, const FunctionDeclaration decl) { 688 if (decl.hasAuto && decl.hasRef) 689 output.putTag(`<span class="lang-feature">auto ref</span> `); 690 else { 691 if (decl.hasAuto) 692 output.putTag(`<span class="lang-feature">auto</span> `); 693 if (decl.hasRef) 694 output.putTag(`<span class="lang-feature">ref</span> `); 695 } 696 697 if (decl.returnType !is null) 698 output.putTag(toHtml(decl.returnType).source); 699 } 700 701 void putSimplfiedArgs(T)(MyOutputRange output, const T decl) { 702 // FIXME: do NOT show default values here 703 if(decl.parameters) { 704 output.putTag("("); 705 foreach(idx, p; decl.parameters.parameters) { 706 if(idx) 707 output.putTag(", "); 708 output.putTag(toText(p.type)); 709 output.putTag(" "); 710 output.putTag(toText(p.name)); 711 } 712 if(decl.parameters.hasVarargs) { 713 if(decl.parameters.parameters.length) 714 output.putTag(", "); 715 output.putTag("..."); 716 } 717 output.putTag(")"); 718 } 719 720 } 721 722 string specialPreprocess(string comment, Decl decl) { 723 switch(specialPreprocessor) { 724 case "dwt": 725 // translate Javadoc to adrdox 726 // @see, @exception/@throws, @param, @return 727 // @author, @version, @since, @deprecated 728 // {@link thing} 729 // one line desc is until the first <p> 730 // html tags are allowed in javadoc 731 732 // links go class#member(args) 733 // the (args) and class are optional 734 735 string parseIdentifier(ref string s, bool allowHash = false) { 736 int end = 0; 737 while(end < s.length && ( 738 (s[end] >= 'A' && s[end] <= 'Z') || 739 (s[end] >= 'a' && s[end] <= 'z') || 740 (s[end] >= '0' && s[end] <= '9') || 741 s[end] == '_' || 742 s[end] == '.' || 743 (allowHash && s[end] == '#') 744 )) 745 { 746 end++; 747 } 748 749 auto i = s[0 .. end]; 750 s = s[end .. $]; 751 return i; 752 } 753 754 755 756 // javadoc is basically html with @ stuff, so start by parsing that (presumed) tag soup 757 auto document = new Document("<root>" ~ comment ~ "</root>"); 758 759 string newComment; 760 761 string fixupJavaReference(string r) { 762 if(r.length == 0) 763 return r; 764 if(r[0] == '#') 765 r = r[1 .. $]; // local refs in adrdox need no special sigil 766 r = r.replace("#", "."); 767 auto idx = r.indexOf("("); 768 if(idx != -1) 769 r = r[0 .. idx]; 770 return r; 771 } 772 773 void translate(Element element) { 774 if(element.nodeType == NodeType.Text) { 775 foreach(line; element.nodeValue.splitLines(KeepTerminator.yes)) { 776 auto s = line.strip; 777 if(s.length && s[0] == '@') { 778 s = s[1 .. $]; 779 auto ident = parseIdentifier(s); 780 switch(ident) { 781 case "author": 782 case "deprecated": 783 case "version": 784 case "since": 785 line = ident ~ ": " ~ s ~ "\n"; 786 break; 787 case "return": 788 case "returns": 789 line = "Returns: " ~ s ~ "\n"; 790 break; 791 case "exception": 792 case "throws": 793 while(s.length && s[0] == ' ') 794 s = s[1 .. $]; 795 auto p = parseIdentifier(s); 796 797 line = "Throws: [" ~ p ~ "]" ~ s ~ "\n"; 798 break; 799 case "param": 800 while(s.length && s[0] == ' ') 801 s = s[1 .. $]; 802 auto p = parseIdentifier(s); 803 line = "Params:\n" ~ p ~ " = " ~ s ~ "\n"; 804 break; 805 case "see": 806 while(s.length && s[0] == ' ') 807 s = s[1 .. $]; 808 auto p = parseIdentifier(s, true); 809 if(p.length) 810 line = "See_Also: [" ~ fixupJavaReference(p) ~ "]" ~ "\n"; 811 else 812 line = "See_Also: " ~ s ~ "\n"; 813 break; 814 default: 815 // idk, leave it alone. 816 } 817 818 } 819 820 newComment ~= line; 821 } 822 } else { 823 if(element.tagName == "code") { 824 newComment ~= "`"; 825 // FIXME: what about ` inside code? 826 newComment ~= element.innerText; // .replace("`", "``"); 827 newComment ~= "`"; 828 } else if(element.tagName == "p") { 829 newComment ~= "\n\n"; 830 foreach(child; element.children) 831 translate(child); 832 newComment ~= "\n\n"; 833 } else if(element.tagName == "a") { 834 newComment ~= "${LINK2 " ~ element.href ~ ", " ~ element.innerText ~ "}"; 835 } else { 836 newComment ~= "${" ~ element.tagName.toUpper ~ " "; 837 foreach(child; element.children) 838 translate(child); 839 newComment ~= "}"; 840 } 841 } 842 } 843 844 foreach(child; document.root.children) 845 translate(child); 846 847 comment = newComment; 848 break; 849 case "gtk": 850 // translate gtk syntax and names to our own 851 852 string gtkObjectToDClass(string name) { 853 if(name.length == 0) 854 return null; 855 int pkgEnd = 1; 856 while(pkgEnd < name.length && !(name[pkgEnd] >= 'A' && name[pkgEnd] <= 'Z')) 857 pkgEnd++; 858 859 auto pkg = name[0 .. pkgEnd].toLower; 860 auto mod = name[pkgEnd .. $]; 861 862 auto t = pkg ~ "." ~ mod; 863 864 if(t in modulesByName) 865 return t ~ "." ~ mod; 866 if(auto c = mod in allClasses) 867 return c.fullyQualifiedName; 868 869 return null; 870 } 871 872 string trimFirstThing(string name) { 873 if(name.length == 0) 874 return null; 875 int pkgEnd = 1; 876 while(pkgEnd < name.length && !(name[pkgEnd] >= 'A' && name[pkgEnd] <= 'Z')) 877 pkgEnd++; 878 return name[pkgEnd .. $]; 879 } 880 881 string formatForDisplay(string name) { 882 auto parts = name.split("."); 883 // gtk.Application.Application.member 884 // we want to take out the repeated one - slot [1] 885 string disp; 886 if(parts.length > 2) 887 foreach(idx, part; parts) { 888 if(idx == 1) continue; 889 if(idx) { 890 disp ~= "."; 891 } 892 disp ~= part; 893 } 894 else 895 disp = name; 896 return disp; 897 } 898 899 import std.regex : regex, replaceAll, Captures; 900 // gtk references to adrdox reference; punt it to the search engine 901 string magic(Captures!string m) { 902 string s = m.hit; 903 s = s[1 .. $]; // trim off # 904 auto orig = s; 905 auto name = s; 906 907 string displayOverride; 908 909 string additional; 910 911 auto idx = s.indexOf(":"); 912 if(idx != -1) { 913 // colon means it is an attribute or a signal 914 auto property = s[idx + 1 .. $]; 915 s = s[0 .. idx]; 916 if(property.length && property[0] == ':') { 917 // is a signal 918 property = property[1 .. $]; 919 additional = ".addOn"; 920 displayOverride = property; 921 } else { 922 // is a property 923 additional = ".get"; 924 displayOverride = property; 925 } 926 bool justSawDash = true; 927 foreach(ch; property) { 928 if(justSawDash && ch >= 'a' && ch <= 'z') { 929 additional ~= cast(char) (cast(int) ch - 32); 930 } else if(ch == '-') { 931 // intentionally blank 932 } else { 933 additional ~= ch; 934 } 935 936 if(ch == '-') { 937 justSawDash = true; 938 } else { 939 justSawDash = false; 940 } 941 } 942 } else { 943 idx = s.indexOf("."); 944 if(idx != -1) { 945 // dot is either a tailing period or a Struct.field 946 if(idx == s.length - 1) 947 s = s[0 .. $ - 1]; // tailing period 948 else { 949 auto structField = s[idx + 1 .. $]; 950 s = s[0 .. idx]; 951 952 additional = "." ~ structField; // FIXME? 953 } 954 } 955 } 956 957 auto dClass = gtkObjectToDClass(s); 958 bool plural = false; 959 if(dClass is null && s.length && s[$-1] == 's') { 960 s = s[0 .. $-1]; 961 dClass = gtkObjectToDClass(s); 962 plural = true; 963 } 964 965 if(dClass !is null) 966 s = dClass; 967 968 s ~= additional; 969 970 if(displayOverride.length) 971 return "[" ~ s ~ "|"~displayOverride~"]"; 972 else 973 return "[" ~ s ~ "|"~formatForDisplay(s)~(plural ? "s" : "") ~ "]"; 974 } 975 976 // gtk function to adrdox d ref 977 string magic2(Captures!string m) { 978 if(m.hit == "main()") 979 return "`"~m.hit~"`"; // special case 980 string s = m.hit[0 .. $-2]; // trim off the () 981 auto orig = m.hit; 982 // these tend to go package_class_method_snake 983 string gtkType; 984 gtkType ~= s[0] | 32; 985 s = s[1 .. $]; 986 bool justSawUnderscore = false; 987 string dType; 988 bool firstUnderscore = true; 989 while(s.length) { 990 if(s[0] == '_') { 991 justSawUnderscore = true; 992 if(!firstUnderscore) { 993 auto dc = gtkObjectToDClass(gtkType); 994 if(dc !is null) { 995 dType = dc; 996 s = s[1 .. $]; 997 break; 998 } 999 } 1000 firstUnderscore = false; 1001 } else if(justSawUnderscore) { 1002 gtkType ~= s[0] & ~32; 1003 justSawUnderscore = false; 1004 } else 1005 gtkType ~= s[0]; 1006 1007 s = s[1 .. $]; 1008 } 1009 1010 if(dType.length) { 1011 justSawUnderscore = false; 1012 string gtkMethod = ""; 1013 while(s.length) { 1014 if(s[0] == '_') { 1015 justSawUnderscore = true; 1016 } else if(justSawUnderscore) { 1017 gtkMethod ~= s[0] & ~32; 1018 justSawUnderscore = false; 1019 } else 1020 gtkMethod ~= s[0]; 1021 1022 s = s[1 .. $]; 1023 } 1024 1025 auto dispName = dType[dType.lastIndexOf(".") + 1 .. $] ~ "." ~ gtkMethod; 1026 1027 return "[" ~ dType ~ "." ~ gtkMethod ~ "|" ~ dispName ~ "]"; 1028 } 1029 1030 return "`" ~ orig ~ "`"; 1031 } 1032 1033 // cut off spam at the end of headers 1034 comment = replaceAll(comment, regex(r"(## [A-Za-z0-9 ]+)##.*$", "gm"), "$1"); 1035 1036 // translate see also header into ddoc style as a special case 1037 comment = replaceAll(comment, regex(r"## See Also.*$", "gm"), "See_Also:\n"); 1038 1039 // name lookup 1040 comment = replaceAll!magic(comment, regex(r"#[A-Za-z0-9_:\-\.]+", "g")); 1041 // gtk params to inline code 1042 comment = replaceAll(comment, regex(r"@([A-Za-z0-9_:]+)", "g"), "`$1`"); 1043 // constants too 1044 comment = replaceAll(comment, regex(r"%([A-Za-z0-9_:]+)", "g"), "`$1`"); 1045 // and functions 1046 comment = replaceAll!magic2(comment, regex(r"([A-Za-z0-9_]+)\(\)", "g")); 1047 1048 // their code blocks 1049 comment = replace(comment, `|[<!-- language="C" -->`, "```c\n"); 1050 comment = replace(comment, `]|`, "\n```"); 1051 break; 1052 default: 1053 return comment; 1054 } 1055 1056 return comment; 1057 } 1058 1059 struct HeaderLink { 1060 string text; 1061 string url; 1062 } 1063 1064 string[string] pseudoFiles; 1065 bool usePseudoFiles = false; 1066 1067 Document writeHtml(Decl decl, bool forReal, bool gzip, string headerTitle, HeaderLink[] headerLinks, bool overrideOutput = false) { 1068 if(!decl.docsShouldBeOutputted && !overrideOutput) 1069 return null; 1070 1071 auto title = decl.name; 1072 bool justDocs = false; 1073 if(auto mod = cast(ModuleDecl) decl) { 1074 if(mod.justDocsTitle !is null) { 1075 title = mod.justDocsTitle; 1076 justDocs = true; 1077 } 1078 } 1079 1080 if(decl.parent !is null && !decl.parent.isModule) { 1081 title = decl.parent.name ~ "." ~ title; 1082 } 1083 1084 auto document = new Document(); 1085 import std.file; 1086 document.parseUtf8(readText(findStandardFile(skeletonFile)), true, true); 1087 1088 switch (texMathOpt) with (TexMathOpt) { 1089 case KaTeX: { 1090 import adrdox.jstex; 1091 prepareForKaTeX(document); 1092 break; 1093 } 1094 default: break; 1095 } 1096 1097 document.title = title ~ " (" ~ decl.fullyQualifiedName ~ ")"; 1098 1099 if(headerTitle.length) 1100 document.requireSelector("#logotype span").innerText = headerTitle; 1101 if(headerLinks.length) { 1102 auto n = document.requireSelector("#page-header nav"); 1103 foreach(l; headerLinks) 1104 if(l.text.length && l.url.length) 1105 n.addChild("a", l.text, l.url); 1106 } 1107 1108 auto content = document.requireElementById("page-content"); 1109 1110 auto comment = decl.parsedDocComment; 1111 1112 content.addChild("h1", title); 1113 1114 auto breadcrumbs = content.addChild("div").addClass("breadcrumbs"); 1115 1116 //breadcrumbs.prependChild(Element.make("a", decl.name, decl.link).addClass("current breadcrumb")); 1117 1118 { 1119 auto p = decl.parent; 1120 while(p) { 1121 if(p.fakeDecl && p.name == "index") 1122 break; 1123 // cut off package names that would be repeated 1124 auto name = (p.isModule && p.parent) ? lastDotOnly(p.name) : p.name; 1125 breadcrumbs.prependChild(new TextNode(" ")); 1126 breadcrumbs.prependChild(Element.make("a", name, p.link(true)).addClass("breadcrumb")); 1127 p = p.parent; 1128 } 1129 } 1130 1131 if(blogMode && decl.isArticle) { 1132 // FIXME: kinda a hack there 1133 auto mod = cast(ModuleDecl) decl; 1134 if(mod.name.startsWith("Blog.Posted_")) 1135 content.addChild("span", decl.name.replace("Blog.Posted_", "Posted ").replace("_", "-")).addClass("date-posted"); 1136 } 1137 1138 string s; 1139 MyOutputRange output = MyOutputRange(&s); 1140 1141 comment.writeSynopsis(output); 1142 content.addChild("div", Html(s)); 1143 1144 s = null; 1145 decl.getAnnotatedPrototype(output); 1146 content.addChild("div", Html(s), "annotated-prototype"); 1147 1148 Element lastDt; 1149 string dittoedName; 1150 string dittoedComment; 1151 1152 void handleChildDecl(Element dl, Decl child, bool enableLinking = true) { 1153 auto cc = child.parsedDocComment; 1154 string sp; 1155 MyOutputRange or = MyOutputRange(&sp); 1156 child.getSimplifiedPrototype(or); 1157 1158 auto printableName = child.name; 1159 1160 if(child.isArticle) { 1161 auto mod = cast(ModuleDecl) child; 1162 printableName = mod.justDocsTitle; 1163 } else { 1164 if(child.isModule && child.parent && child.parent.isModule) { 1165 if(printableName.startsWith(child.parent.name)) 1166 printableName = printableName[child.parent.name.length + 1 .. $]; 1167 } 1168 } 1169 1170 auto newDt = Element.make("dt", Element.make("a", printableName, child.link)); 1171 auto st = newDt.addChild("div", Html(sp)).addClass("simplified-prototype"); 1172 st.style.maxWidth = to!string(st.innerText.length * 11 / 10) ~ "ch"; 1173 1174 if(child.isDitto && child.comment == dittoedComment && lastDt !is null) { 1175 // ditto'd names don't need to be written again 1176 if(child.name == dittoedName) { 1177 foreach(ldt; lastDt.parentNode.querySelectorAll("dt .simplified-prototype")) { 1178 if(st.innerHTML == ldt.innerHTML) 1179 return; // no need to say the same thing twice 1180 } 1181 // same name, but different prototype. Cut the repetition. 1182 newDt.requireSelector("a").removeFromTree(); 1183 } 1184 lastDt.addSibling(newDt); 1185 } else { 1186 dl.addChild(newDt); 1187 auto dd = dl.addChild("dd", Html(formatDocumentationComment(enableLinking ? cc.ddocSummary : preprocessComment(child.comment, child), child))); 1188 foreach(e; dd.querySelectorAll("h1, h2, h3, h4, h5, h6")) 1189 e.stripOut; 1190 dittoedComment = child.comment; 1191 } 1192 1193 lastDt = newDt; 1194 dittoedName = child.name; 1195 } 1196 1197 Decl[] ctors; 1198 Decl[] members; 1199 ModuleDecl[] articles; 1200 Decl[] submodules; 1201 ImportDecl[] imports; 1202 1203 if(forReal) 1204 foreach(child; decl.children) { 1205 if(!child.docsShouldBeOutputted) 1206 continue; 1207 if(child.isConstructor()) 1208 ctors ~= child; 1209 else if(child.isArticle) 1210 articles ~= cast(ModuleDecl) child; 1211 else if(child.isModule) 1212 submodules ~= child; 1213 else if(cast(DestructorDecl) child) 1214 {} // intentionally blank 1215 else if(cast(PostblitDecl) child) 1216 {} // intentionally blank 1217 else if(auto c = cast(ImportDecl) child) 1218 imports ~= c; 1219 else 1220 members ~= child; 1221 } 1222 1223 if(decl.disabledDefaultConstructor) { 1224 content.addChild("h2", "Disabled Default Constructor").id = "disabled-default-constructor"; 1225 auto div = content.addChild("div"); 1226 div.addChild("p", "A disabled default is present on this object. To use it, use one of the other constructors or a factory function."); 1227 } 1228 1229 if(ctors.length) { 1230 content.addChild("h2", "Constructors").id = "constructors"; 1231 auto dl = content.addChild("dl").addClass("member-list constructors"); 1232 1233 foreach(child; ctors) { 1234 if(child is decl.disabledDefaultConstructor) 1235 continue; 1236 handleChildDecl(dl, child); 1237 if(!minimalDescent) 1238 writeHtml(child, forReal, gzip, headerTitle, headerLinks); 1239 } 1240 } 1241 1242 if(auto dtor = decl.destructor) { 1243 content.addChild("h2", "Destructor").id = "destructor"; 1244 auto dl = content.addChild("dl").addClass("member-list"); 1245 1246 if(dtor.isDocumented) 1247 handleChildDecl(dl, dtor); 1248 else 1249 content.addChild("p", "A destructor is present on this object, but not explicitly documented in the source."); 1250 //if(!minimalDescent) 1251 //writeHtml(dtor, forReal, gzip, headerTitle, headerLinks); 1252 } 1253 1254 if(auto postblit = decl.postblit) { 1255 content.addChild("h2", "Postblit").id = "postblit"; 1256 auto dl = content.addChild("dl").addClass("member-list"); 1257 1258 if(postblit.isDisabled()) 1259 content.addChild("p", "Copying this object is disabled."); 1260 1261 if(postblit.isDocumented) 1262 handleChildDecl(dl, postblit); 1263 else 1264 content.addChild("p", "A postblit is present on this object, but not explicitly documented in the source."); 1265 //if(!minimalDescent) 1266 //writeHtml(dtor, forReal, gzip, headerTitle, headerLinks); 1267 } 1268 1269 if(articles.length) { 1270 content.addChild("h2", "Articles").id = "articles"; 1271 auto dl = content.addChild("dl").addClass("member-list articles"); 1272 foreach(child; articles.sort!((a,b) => (blogMode ? (b.name < a.name) : (a.name < b.name)))) { 1273 handleChildDecl(dl, child); 1274 } 1275 } 1276 1277 if(submodules.length) { 1278 content.addChild("h2", "Modules").id = "modules"; 1279 auto dl = content.addChild("dl").addClass("member-list native"); 1280 foreach(child; submodules.sort!((a,b) => a.name < b.name)) { 1281 handleChildDecl(dl, child); 1282 1283 // i actually want submodules to be controlled on the command line too. 1284 //if(!usePseudoFiles) // with pseudofiles, we can generate child modules on demand too, so avoid recursive everything on root request 1285 //writeHtml(child, forReal, gzip, headerTitle, headerLinks); 1286 } 1287 } 1288 1289 if(auto at = decl.aliasThis) { 1290 content.addChild("h2", "Alias This").id = "alias-this"; 1291 auto div = content.addChild("div"); 1292 1293 div.addChild("a", at.name, at.link); 1294 1295 if(decl.aliasThisComment.length) { 1296 auto memberComment = formatDocumentationComment(preprocessComment(decl.aliasThisComment, decl), decl); 1297 auto dc = div.addChild("div").addClass("documentation-comment"); 1298 dc.innerHTML = memberComment; 1299 } 1300 } 1301 1302 if(imports.length) { 1303 content.addChild("h2", "Public Imports").id = "public-imports"; 1304 auto div = content.addChild("div"); 1305 1306 foreach(imp; imports) { 1307 auto dl = content.addChild("dl").addClass("member-list native"); 1308 handleChildDecl(dl, imp, false); 1309 } 1310 } 1311 1312 if(members.length) { 1313 content.addChild("h2", "Members").id = "members"; 1314 1315 void outputMemberList(Decl[] members, string header, string idPrefix, string headerPrefix) { 1316 Element dl; 1317 string lastType; 1318 foreach(child; members.sort!sorter) { 1319 if(child.declarationType != lastType) { 1320 auto hdr = content.addChild(header, headerPrefix ~ pluralize(child.declarationType).capitalize, "member-list-header hide-from-toc"); 1321 hdr.id = idPrefix ~ child.declarationType; 1322 dl = content.addChild("dl").addClass("member-list native"); 1323 lastType = child.declarationType; 1324 } 1325 1326 handleChildDecl(dl, child); 1327 1328 if(!minimalDescent) 1329 writeHtml(child, forReal, gzip, headerTitle, headerLinks); 1330 } 1331 } 1332 1333 foreach(section; comment.symbolGroupsOrder) { 1334 auto memberComment = formatDocumentationComment(preprocessComment(comment.symbolGroups[section], decl), decl); 1335 string sectionPrintable = section.replace("_", " ").capitalize; 1336 // these count as user headers to move toward TOC - section groups are user defined so it makes sense 1337 auto hdr = content.addChild("h3", sectionPrintable, "member-list-header user-header"); 1338 hdr.id = "group-" ~ section; 1339 auto dc = content.addChild("div").addClass("documentation-comment"); 1340 dc.innerHTML = memberComment; 1341 1342 if(auto hdr2 = dc.querySelector("> div:only-child > h2:first-child, > div:only-child > h3:first-child")) { 1343 hdr.innerHTML = hdr2.innerHTML; 1344 hdr2.removeFromTree; 1345 } 1346 1347 Decl[] subList; 1348 for(int i = 0; i < members.length; i++) { 1349 auto member = members[i]; 1350 if(member.parsedDocComment.group == section) { 1351 subList ~= member; 1352 members[i] = members[$-1]; 1353 members = members[0 .. $-1]; 1354 i--; 1355 } 1356 } 1357 1358 outputMemberList(subList, "h4", section ~ "-", sectionPrintable ~ " "); 1359 } 1360 1361 if(members.length) { 1362 if(comment.symbolGroupsOrder.length) { 1363 auto hdr = content.addChild("h3", "Other", "member-list-header"); 1364 hdr.id = "group-other"; 1365 outputMemberList(members, "h4", "other-", "Other "); 1366 } else { 1367 outputMemberList(members, "h3", "", ""); 1368 } 1369 } 1370 } 1371 1372 bool firstMitd = true; 1373 foreach(d; decl.children) { 1374 if(auto mi = cast(MixedInTemplateDecl) d) { 1375 if(firstMitd) { 1376 auto h2 = content.addChild("h2", "Mixed In Members"); 1377 h2.id = "mixed-in-members"; 1378 firstMitd = false; 1379 } 1380 1381 //mi.name 1382 1383 string sp; 1384 MyOutputRange or = MyOutputRange(&sp); 1385 mi.getSimplifiedPrototype(or); 1386 auto h3 = content.addChild("h3", Html("From " ~ sp)); 1387 1388 auto thing = decl.lookupName(toText(mi.astNode.mixinTemplateName)); 1389 1390 if(thing) { 1391 auto dl = content.addChild("dl").addClass("member-list native"); 1392 foreach(child; thing.children) { 1393 if(child.isDocumented) { 1394 handleChildDecl(dl, child); 1395 1396 if(!minimalDescent) 1397 writeHtml(child, forReal, gzip, headerTitle, headerLinks, true); 1398 } 1399 } 1400 } else { 1401 1402 } 1403 } 1404 } 1405 1406 auto irList = decl.inheritsFrom; 1407 if(irList.length) { 1408 auto h2 = content.addChild("h2", "Inherited Members"); 1409 h2.id = "inherited-members"; 1410 1411 bool hasAnyListing = false; 1412 1413 foreach(ir; irList) { 1414 if(ir.decl is null) continue; 1415 auto h3 = content.addChild("h3", "From " ~ ir.decl.name); 1416 h3.id = "inherited-from-" ~ ir.decl.fullyQualifiedName; 1417 auto dl = content.addChild("dl").addClass("member-list inherited"); 1418 bool hadListing = false; 1419 foreach(child; ir.decl.children) { 1420 if(!child.docsShouldBeOutputted) 1421 continue; 1422 if(!child.isConstructor()) { 1423 handleChildDecl(dl, child); 1424 hadListing = true; 1425 hasAnyListing = true; 1426 } 1427 } 1428 1429 if(!hadListing) { 1430 h3.removeFromTree(); 1431 dl.removeFromTree(); 1432 } 1433 } 1434 1435 if(!hasAnyListing) 1436 h2.removeFromTree(); 1437 } 1438 1439 1440 decl.addSupplementalData(content); 1441 1442 s = null; 1443 1444 if(auto fd = cast(FunctionDeclaration) decl.getAstNode()) 1445 comment.writeDetails(output, fd, decl.getProcessedUnittests()); 1446 else if(auto fd = cast(Constructor) decl.getAstNode()) 1447 comment.writeDetails(output, fd, decl.getProcessedUnittests()); 1448 else if(auto fd = cast(TemplateDeclaration) decl.getAstNode()) 1449 comment.writeDetails(output, fd, decl.getProcessedUnittests()); 1450 else if(auto fd = cast(EponymousTemplateDeclaration) decl.getAstNode()) 1451 comment.writeDetails(output, fd, decl.getProcessedUnittests()); 1452 else if(auto fd = cast(AliasDecl) decl) { 1453 if(fd.initializer) 1454 comment.writeDetails(output, fd.initializer, decl.getProcessedUnittests()); 1455 else 1456 comment.writeDetails(output, decl, decl.getProcessedUnittests()); 1457 } else 1458 comment.writeDetails(output, decl, decl.getProcessedUnittests()); 1459 1460 content.addChild("div", Html(s)); 1461 1462 if(forReal) { 1463 auto nav = document.requireElementById("page-nav"); 1464 1465 Decl[] navArray; 1466 string[string] inNavArray; 1467 if(decl.parent) { 1468 auto iterate = decl.parent.children; 1469 1470 if(!decl.isModule && decl.parent.isModule && decl.parent.children.length == 1) { 1471 // we are an only child of a module, show the module's nav instead 1472 if(decl.parent.parent !is null) 1473 iterate = decl.parent.parent.children; 1474 } 1475 1476 foreach(child; iterate) { 1477 if(cast(ImportDecl) child) continue; // do not document public imports here, they belong only on the inside 1478 if(child.docsShouldBeOutputted) { 1479 // strip overloads from sidebar 1480 if(child.name !in inNavArray) { 1481 navArray ~= child; 1482 inNavArray[child.name] = ""; 1483 } 1484 } 1485 } 1486 } else { 1487 /+ commented pending removal 1488 // this is for building the module nav when doing an incremental 1489 // rebuild. It loads the index.xml made with the special option below. 1490 static bool attemptedXmlLoad; 1491 static ModuleDecl[] indexedModules; 1492 if(!attemptedXmlLoad) { 1493 import std.file; 1494 if(std.file.exists("index.xml")) { 1495 auto idx = new XmlDocument(readText("index.xml")); 1496 foreach(d; idx.querySelectorAll("listing > decl")) 1497 indexedModules ~= new ModuleDecl(d.requireSelector("name").innerText); 1498 } 1499 attemptedXmlLoad = true; 1500 } 1501 1502 auto tm = cast(ModuleDecl) decl; 1503 if(tm !is null) 1504 foreach(im; indexedModules) 1505 if(im.packageName == tm.packageName) 1506 navArray ~= im; 1507 +/ 1508 } 1509 1510 { 1511 auto p = decl.parent; 1512 while(p) { 1513 // cut off package names that would be repeated 1514 auto name = (p.isModule && p.parent) ? lastDotOnly(p.name) : p.name; 1515 if(name == "index" && p.fakeDecl) 1516 break; 1517 nav.prependChild(new TextNode(" ")); 1518 nav.prependChild(Element.make("a", name, p.link(true))).addClass("parent"); 1519 p = p.parent; 1520 } 1521 } 1522 1523 import std.algorithm; 1524 1525 sort!sorter(navArray); 1526 1527 Element list; 1528 1529 string lastType; 1530 foreach(item; navArray) { 1531 if(item.declarationType != lastType) { 1532 nav.addChild("span", pluralize(item.declarationType)).addClass("type-separator"); 1533 list = nav.addChild("ul"); 1534 lastType = item.declarationType; 1535 } 1536 1537 string name; 1538 if(item.isArticle) { 1539 auto mod = cast(ModuleDecl) item; 1540 name = mod.justDocsTitle; 1541 } else { 1542 // cut off package names that would be repeated 1543 name = (item.isModule && item.parent) ? lastDotOnly(item.name) : item.name; 1544 } 1545 auto n = list.addChild("li").addChild("a", name, item.link).addClass(item.declarationType.replace(" ", "-")); 1546 if(item.name == decl.name || name == decl.name) 1547 n.addClass("current"); 1548 } 1549 1550 if(justDocs) { 1551 if(auto d = document.querySelector("#details")) 1552 d.removeFromTree; 1553 } 1554 1555 auto toc = Element.make("div"); 1556 toc.id = "table-of-contents"; 1557 auto current = toc; 1558 int lastLevel; 1559 tree: foreach(header; document.root.tree) { 1560 int level; 1561 switch(header.tagName) { 1562 case "h2": 1563 level = 2; 1564 break; 1565 case "h3": 1566 level = 3; 1567 break; 1568 case "h4": 1569 level = 4; 1570 break; 1571 case "h5:": 1572 level = 5; 1573 break; 1574 case "h6": 1575 level = 6; 1576 break; 1577 default: break; 1578 } 1579 1580 if(level == 0) continue; 1581 1582 bool addToIt = true; 1583 if(header.hasClass("hide-from-toc")) 1584 addToIt = false; 1585 1586 Element addTo; 1587 if(addToIt) { 1588 auto parentCheck = header; 1589 while(parentCheck) { 1590 if(parentCheck.hasClass("adrdox-sample")) 1591 continue tree; 1592 parentCheck = parentCheck.parentNode; 1593 } 1594 1595 if(level > lastLevel) { 1596 current = current.addChild("ol"); 1597 current.addClass("heading-level-" ~ to!string(level)); 1598 } else if(level < lastLevel) { 1599 while(current && !current.hasClass("heading-level-" ~ to!string(level))) 1600 current = current.parentNode; 1601 if(current is null) { 1602 import std.stdio; 1603 writeln("WARNING: TOC broken on " ~ decl.name); 1604 goto skip_toc; 1605 } 1606 assert(current !is null); 1607 } 1608 1609 lastLevel = level; 1610 addTo = current; 1611 if(addTo.tagName != "ol") 1612 addTo = addTo.parentNode; 1613 } 1614 1615 if(!header.hasAttribute("id")) 1616 header.attrs.id = toId(header.innerText); 1617 if(header.querySelector(" > *") is null) { 1618 auto selfLink = Element.make("a", header.innerText, "#" ~ header.attrs.id); 1619 selfLink.addClass("header-anchor"); 1620 header.innerHTML = selfLink.toString(); 1621 } 1622 1623 if(addToIt) 1624 addTo.addChild("li", Element.make("a", header.innerText, "#" ~ header.attrs.id)); 1625 } 1626 1627 if(auto d = document.querySelector("#more-link")) { 1628 if(document.querySelectorAll(".user-header:not(.hide-from-toc)").length > 2) 1629 d.replaceWith(toc); 1630 } 1631 1632 skip_toc: {} 1633 1634 if(auto a = document.querySelector(".annotated-prototype")) 1635 outer: foreach(c; a.querySelectorAll(".parameters-list")) { 1636 auto p = c.parentNode; 1637 while(p) { 1638 if(p.hasClass("lambda-expression")) 1639 continue outer; 1640 p = p.parentNode; 1641 } 1642 c.addClass("toplevel"); 1643 } 1644 1645 // for line numbering 1646 foreach(pre; document.querySelectorAll("pre.highlighted, pre.block-code[data-language!=\"\"]")) { 1647 addLineNumbering(pre); 1648 } 1649 1650 string overloadLink; 1651 string declLink = decl.link(true, &overloadLink); 1652 1653 if(usePseudoFiles) { 1654 pseudoFiles[declLink] = document.toString(); 1655 if(overloadLink.length) 1656 pseudoFiles[overloadLink] = redirectToOverloadHtml(declLink); 1657 } else { 1658 writeFile(outputDirectory ~ declLink, document.toString(), gzip); 1659 if(overloadLink.length) 1660 writeFile(outputDirectory ~ overloadLink, redirectToOverloadHtml(declLink), gzip); 1661 } 1662 1663 import std.stdio; 1664 writeln("WRITTEN TO ", declLink); 1665 } 1666 1667 return document; 1668 } 1669 1670 string redirectToOverloadHtml(string what) { 1671 return `<html class="overload-redirect"><script>location.href = '`~what~`';</script> <a href="`~what~`">Continue to overload</a></html>`; 1672 } 1673 1674 void addLineNumbering(Element pre, bool id = false) { 1675 if(pre.hasClass("with-line-wrappers")) 1676 return; 1677 string html; 1678 int count; 1679 foreach(idx, line; pre.innerHTML.splitLines) { 1680 auto num = to!string(idx + 1); 1681 auto href = "L"~num; 1682 if(id) 1683 html ~= "<a class=\"br\""~(id ? " id=\""~href~"\"" : "")~" href=\"#"~href~"\">"~num~" </a>"; 1684 else 1685 html ~= "<span class=\"br\">"~num~" </span>"; 1686 html ~= line; 1687 html ~= "\n"; 1688 count++; 1689 } 1690 if(count < 15) 1691 return; // no point cluttering the display with the sample is so small you can eyeball it instantly anyway 1692 pre.innerHTML = html.stripRight; 1693 pre.addClass("with-line-wrappers"); 1694 1695 if(count >= 10000) 1696 pre.addClass("ten-thousand-lines"); 1697 else if(count >= 1000) 1698 pre.addClass("thousand-lines"); 1699 } 1700 1701 string lastDotOnly(string s) { 1702 auto idx = s.lastIndexOf("."); 1703 if(idx == -1) return s; 1704 return s[idx + 1 .. $]; 1705 } 1706 1707 struct InheritanceResult { 1708 Decl decl; // may be null 1709 string plainText; 1710 //const(BaseClass) ast; 1711 } 1712 1713 Decl[] declsByUda(string uda, Decl start = null) { 1714 if(start is null) { 1715 assert(0); // cross-module search not implemented here 1716 } 1717 1718 Decl[] list; 1719 1720 if(start.hasUda(uda)) 1721 list ~= start; 1722 1723 foreach(child; start.children) 1724 list ~= declsByUda(uda, child); 1725 1726 return list; 1727 1728 } 1729 1730 abstract class Decl { 1731 bool fakeDecl = false; 1732 bool alreadyGenerated = false; 1733 abstract string name(); 1734 abstract string comment(); 1735 abstract string rawComment(); 1736 abstract string declarationType(); 1737 abstract const(ASTNode) getAstNode(); 1738 abstract int lineNumber(); 1739 1740 //abstract string sourceCode(); 1741 1742 abstract void getAnnotatedPrototype(MyOutputRange); 1743 abstract void getSimplifiedPrototype(MyOutputRange); 1744 1745 DocComment parsedDocComment_; 1746 final @property DocComment parsedDocComment() { 1747 if(parsedDocComment_ is DocComment.init) 1748 parsedDocComment_ = parseDocumentationComment(comment, this); 1749 return parsedDocComment_; 1750 } 1751 1752 void getAggregatePrototype(MyOutputRange r) { 1753 getSimplifiedPrototype(r); 1754 r.put(";"); 1755 } 1756 1757 /* virtual */ void addSupplementalData(Element) {} 1758 1759 // why is this needed?!?!?!?!? 1760 override int opCmp(Object o) { 1761 return cast(int)cast(void*)this - cast(int)cast(void*)o; 1762 } 1763 1764 Decl parentModule() { 1765 auto p = this; 1766 while(p) { 1767 if(p.isModule()) 1768 return p; 1769 p = p.parent; 1770 } 1771 assert(0); 1772 } 1773 1774 Decl previousSibling() { 1775 if(parent is null) 1776 return null; 1777 1778 Decl prev; 1779 foreach(child; parent.children) { 1780 if(child is this) 1781 return prev; 1782 prev = child; 1783 } 1784 1785 return null; 1786 } 1787 1788 bool isDocumented() { 1789 // this shouldn't be needed anymore cuz the recursive check below does a better job 1790 //if(this.isModule) 1791 //return true; // allow undocumented modules because then it will at least descend into documented children 1792 1793 // skip modules with "internal" because they are usually not meant 1794 // to be publicly documented anyway 1795 { 1796 auto mod = this.parentModule.name; 1797 if(mod.indexOf(".internal") != -1 && !documentInternal) 1798 return false; 1799 } 1800 1801 if(documentUndocumented) 1802 return true; 1803 1804 if(comment.length) // hack 1805 return comment.length > 0; // cool, not a hack 1806 1807 // if it has any documented children, we want to pretend this is documented too 1808 // since then it will be possible to navigate to it 1809 foreach(child; children) 1810 if(child.docsShouldBeOutputted()) 1811 return true; 1812 1813 // what follows is all filthy hack 1814 // the C bindings in druntime are not documented, but 1815 // we want them to show up. So I'm gonna hack it. 1816 1817 /* 1818 auto mod = this.parentModule.name; 1819 if(mod.startsWith("core")) 1820 return true; 1821 */ 1822 return false; 1823 } 1824 1825 bool isStatic() { 1826 foreach (a; attributes) { 1827 if(a.attr && a.attr.attribute.type == tok!"static") 1828 return true; 1829 // gshared also implies static (though note that shared does not!) 1830 if(a.attr && a.attr.attribute.type == tok!"__gshared") 1831 return true; 1832 } 1833 1834 return false; 1835 } 1836 1837 bool isPrivate() { 1838 IdType protection; 1839 foreach (a; attributes) { 1840 if (a.attr && isProtection(a.attr.attribute.type)) 1841 protection = a.attr.attribute.type; 1842 } 1843 1844 return protection == tok!"private"; 1845 } 1846 1847 bool docsShouldBeOutputted() { 1848 if((!this.isPrivate || writePrivateDocs) && this.isDocumented) 1849 return true; 1850 else if(this.comment.indexOf("$(ALWAYS_DOCUMENT)") != -1) 1851 return true; 1852 return false; 1853 } 1854 1855 final bool hasUda(string name) { 1856 foreach(a; attributes) 1857 if(a.attr && a.attr.atAttribute && a.attr.atAttribute.identifier.text == name) 1858 return true; 1859 return false; 1860 } 1861 1862 // FIXME: isFinal and isVirtual 1863 // FIXME: it would be nice to inherit documentation from interfaces too. 1864 1865 bool isProperty() { 1866 foreach (a; attributes) { 1867 if(a.attr && a.attr.atAttribute && a.attr.atAttribute.identifier.text == "property") 1868 return true; 1869 } 1870 1871 return false; 1872 } 1873 1874 bool isAggregateMember() { 1875 return parent ? !parent.isModule : false; // FIXME? 1876 } 1877 1878 // does NOT look for aliased overload sets, just ones right in this scope 1879 // includes this in the return (plus eponymous check). Check if overloaded with .length > 1 1880 Decl[] getImmediateDocumentedOverloads() { 1881 Decl[] ret; 1882 1883 if(this.parent !is null) { 1884 foreach(child; this.parent.children) { 1885 if(((cast(ImportDecl) child) is null) && child.name == this.name && child.docsShouldBeOutputted()) 1886 ret ~= child; 1887 } 1888 if(auto t = cast(TemplateDecl) this.parent) 1889 if(this is t.eponymousMember) { 1890 foreach(i; t.getImmediateDocumentedOverloads()) 1891 if(i !is t) 1892 ret ~= i; 1893 } 1894 } 1895 1896 return ret; 1897 } 1898 1899 Decl[] getDittos() { 1900 if(this.parent is null) 1901 return null; 1902 1903 size_t lastNonDitto; 1904 1905 foreach(idx, child; this.parent.children) { 1906 if(!child.isDitto()) 1907 lastNonDitto = idx; 1908 if(child is this) { 1909 break; 1910 } 1911 } 1912 1913 size_t stop = lastNonDitto; 1914 foreach(idx, child; this.parent.children[lastNonDitto + 1 .. $]) 1915 if(child.isDitto()) 1916 stop = idx + lastNonDitto + 1 + 1; // one +1 is offset of begin, other is to make sure it is inclusive 1917 else 1918 break; 1919 1920 return this.parent.children[lastNonDitto .. stop]; 1921 } 1922 1923 string link(bool forFile = false, string* masterOverloadName = null) { 1924 auto linkTo = this; 1925 if(!forFile && this.isModule && this.children.length == 1) { 1926 linkTo = this.children[0]; 1927 } 1928 1929 auto n = linkTo.fullyQualifiedName(); 1930 1931 auto overloads = linkTo.getImmediateDocumentedOverloads(); 1932 if(overloads.length > 1) { 1933 int number = 1; 1934 int goodNumber; 1935 foreach(overload; overloads) { 1936 if(overload is this) { 1937 goodNumber = number; 1938 break; 1939 } 1940 number++; 1941 } 1942 1943 if(goodNumber) 1944 number = goodNumber; 1945 else 1946 number = 1; 1947 1948 if(masterOverloadName !is null) 1949 *masterOverloadName = n.idup; 1950 1951 import std.conv : text; 1952 n ~= text(".", number); 1953 } 1954 1955 n ~= ".html"; 1956 1957 if(masterOverloadName !is null) 1958 *masterOverloadName ~= ".html"; 1959 1960 if(!forFile) { 1961 string d = getDirectoryForPackage(linkTo.fullyQualifiedName()); 1962 if(d.length) { 1963 n = d ~ n; 1964 if(masterOverloadName !is null) 1965 *masterOverloadName = d ~ *masterOverloadName; 1966 } 1967 } 1968 1969 return n.handleCaseSensitivity(); 1970 } 1971 1972 string[] parentNameList() { 1973 string[] fqn = [name()]; 1974 auto p = parent; 1975 while(p) { 1976 fqn = p.name() ~ fqn; 1977 p = p.parent; 1978 } 1979 return fqn; 1980 1981 } 1982 1983 string fullyQualifiedName() { 1984 string fqn = name(); 1985 if(isModule) 1986 return fqn; 1987 auto p = parent; 1988 while(p) { 1989 fqn = p.name() ~ "." ~ fqn; 1990 if(p.isModule) 1991 break; // do NOT want package names in here 1992 p = p.parent; 1993 } 1994 return fqn; 1995 } 1996 1997 final InheritanceResult[] inheritsFrom() { 1998 if(!inheritsFromProcessed) 1999 foreach(ref i; _inheritsFrom) 2000 if(this.parent && i.plainText.length) { 2001 i.decl = this.parent.lookupName(i.plainText); 2002 } 2003 inheritsFromProcessed = true; 2004 return _inheritsFrom; 2005 } 2006 InheritanceResult[] _inheritsFrom; 2007 bool inheritsFromProcessed = false; 2008 2009 Decl[string] nameTable; 2010 bool nameTableBuilt; 2011 Decl[string] buildNameTable(string[] excludeModules = null) { 2012 if(!nameTableBuilt) { 2013 lookup: foreach(mod; this.importedModules) { 2014 if(!mod.publicImport) 2015 continue; 2016 if(auto modDeclPtr = mod.name in modulesByName) { 2017 auto modDecl = *modDeclPtr; 2018 2019 foreach(imod; excludeModules) 2020 if(imod == modDeclPtr.name) 2021 break lookup; 2022 2023 auto tbl = modDecl.buildNameTable(excludeModules ~ this.parentModule.name); 2024 foreach(k, v; tbl) 2025 nameTable[k] = v; 2026 } 2027 } 2028 2029 foreach(child; children) 2030 nameTable[child.name] = child; 2031 2032 nameTableBuilt = true; 2033 } 2034 return nameTable; 2035 } 2036 2037 // the excludeModules is meant to prevent circular lookups 2038 Decl lookupName(string name, bool lookUp = true, string[] excludeModules = null) { 2039 if(importedModules.length == 0 || importedModules[$-1].name != "object") 2040 addImport("object", false); 2041 2042 if(name.length == 0) 2043 return null; 2044 string originalFullName = name; 2045 auto subject = this; 2046 if(name[0] == '.') { 2047 // global scope operator 2048 while(subject && !subject.isModule) 2049 subject = subject.parent; 2050 name = name[1 .. $]; 2051 originalFullName = originalFullName[1 .. $]; 2052 2053 } 2054 2055 auto firstDotIdx = name.indexOf("."); 2056 if(firstDotIdx != -1) { 2057 subject = subject.lookupName(name[0 .. firstDotIdx]); 2058 name = name[firstDotIdx + 1 .. $]; 2059 } 2060 2061 if(subject) 2062 while(subject) { 2063 2064 auto table = subject.buildNameTable(); 2065 if(name in table) 2066 return table[name]; 2067 2068 if(lookUp) 2069 // at the top level, we also need to check private imports 2070 lookup: foreach(mod; subject.importedModules) { 2071 if(mod.publicImport) 2072 continue; // handled by the name table 2073 auto lookupInsideModule = originalFullName; 2074 if(auto modDeclPtr = mod.name in modulesByName) { 2075 auto modDecl = *modDeclPtr; 2076 2077 foreach(imod; excludeModules) 2078 if(imod == modDeclPtr.name) 2079 break lookup; 2080 //import std.stdio; writeln(modDecl.name, " ", lookupInsideModule); 2081 auto located = modDecl.lookupName(lookupInsideModule, false, excludeModules ~ this.parentModule.name); 2082 if(located !is null) 2083 return located; 2084 } 2085 } 2086 2087 if(!lookUp || subject.isModule) 2088 subject = null; 2089 else 2090 subject = subject.parent; 2091 } 2092 else { 2093 // FIXME? 2094 // fully qualified name from this module 2095 subject = this; 2096 if(originalFullName.startsWith(this.parentModule.name ~ ".")) { 2097 // came from here! 2098 auto located = this.parentModule.lookupName(originalFullName[this.parentModule.name.length + 1 .. $]); 2099 if(located !is null) 2100 return located; 2101 } else 2102 while(subject !is null) { 2103 foreach(mod; subject.importedModules) { 2104 if(originalFullName.startsWith(mod.name ~ ".")) { 2105 // fully qualified name from this module 2106 auto lookupInsideModule = originalFullName[mod.name.length + 1 .. $]; 2107 if(auto modDeclPtr = mod.name in modulesByName) { 2108 auto modDecl = *modDeclPtr; 2109 auto located = modDecl.lookupName(lookupInsideModule, mod.publicImport); 2110 if(located !is null) 2111 return located; 2112 } 2113 } 2114 } 2115 2116 if(lookUp && subject.isModule) 2117 subject = null; 2118 else 2119 subject = subject.parent; 2120 } 2121 } 2122 2123 return null; 2124 } 2125 2126 final Decl lookupName(const IdentifierOrTemplateInstance ic, bool lookUp = true) { 2127 auto subject = this; 2128 if(ic.templateInstance) 2129 return null; // FIXME 2130 2131 return lookupName(ic.identifier.text, lookUp); 2132 } 2133 2134 2135 final Decl lookupName(const IdentifierChain ic) { 2136 auto subject = this; 2137 assert(ic.identifiers.length); 2138 2139 // FIXME: leading dot? 2140 foreach(idx, ident; ic.identifiers) { 2141 subject = subject.lookupName(ident.text, idx == 0); 2142 if(subject is null) return null; 2143 } 2144 return subject; 2145 } 2146 2147 final Decl lookupName(const IdentifierOrTemplateChain ic) { 2148 auto subject = this; 2149 assert(ic.identifiersOrTemplateInstances.length); 2150 2151 // FIXME: leading dot? 2152 foreach(idx, ident; ic.identifiersOrTemplateInstances) { 2153 subject = subject.lookupName(ident, idx == 0); 2154 if(subject is null) return null; 2155 } 2156 return subject; 2157 } 2158 2159 final Decl lookupName(const Symbol ic) { 2160 // FIXME dot 2161 return lookupName(ic.identifierOrTemplateChain); 2162 } 2163 2164 2165 Decl parent; 2166 Decl[] children; 2167 2168 void writeTemplateConstraint(MyOutputRange output); 2169 2170 const(VersionOrAttribute)[] attributes; 2171 2172 void addChild(Decl decl) { 2173 decl.parent = this; 2174 children ~= decl; 2175 } 2176 2177 struct ImportedModule { 2178 string name; 2179 bool publicImport; 2180 } 2181 ImportedModule[] importedModules; 2182 void addImport(string moduleName, bool isPublic) { 2183 importedModules ~= ImportedModule(moduleName, isPublic); 2184 } 2185 2186 struct Unittest { 2187 const(dparse.ast.Unittest) ut; 2188 string code; 2189 string comment; 2190 } 2191 2192 Unittest[] unittests; 2193 2194 void addUnittest(const(dparse.ast.Unittest) ut, const(ubyte)[] code, string comment) { 2195 int slicePoint = 0; 2196 foreach(idx, b; code) { 2197 if(b == ' ' || b == '\t' || b == '\r') 2198 slicePoint++; 2199 else if(b == '\n') { 2200 slicePoint++; 2201 break; 2202 } else { 2203 slicePoint = 0; 2204 break; 2205 } 2206 } 2207 code = code[slicePoint .. $]; 2208 unittests ~= Unittest(ut, unittestCodeToString(code), comment); 2209 } 2210 2211 string unittestCodeToString(const(ubyte)[] code) { 2212 auto excludeString = cast(const(ubyte[])) "// exclude from docs"; 2213 bool replacementMade; 2214 2215 import std.algorithm.searching; 2216 2217 auto idx = code.countUntil(excludeString); 2218 while(idx != -1) { 2219 int before = cast(int) idx; 2220 int after = cast(int) idx; 2221 while(before > 0 && code[before] != '\n') 2222 before--; 2223 while(after < code.length && code[after] != '\n') 2224 after++; 2225 2226 code = code[0 .. before] ~ code[after .. $]; 2227 replacementMade = true; 2228 idx = code.countUntil(excludeString); 2229 } 2230 2231 if(!replacementMade) 2232 return (cast(char[]) code).idup; // needs to be unique 2233 else 2234 return cast(string) code; // already copied above, so it is unique 2235 } 2236 2237 struct ProcessedUnittest { 2238 string code; 2239 string comment; 2240 bool embedded; 2241 } 2242 2243 bool _unittestsProcessed; 2244 ProcessedUnittest[] _processedUnittests; 2245 2246 ProcessedUnittest[] getProcessedUnittests() { 2247 if(_unittestsProcessed) 2248 return _processedUnittests; 2249 2250 _unittestsProcessed = true; 2251 2252 // source, comment 2253 ProcessedUnittest[] ret; 2254 2255 Decl start = this; 2256 if(isDitto()) { 2257 foreach(child; this.parent.children) { 2258 if(child is this) 2259 break; 2260 if(!child.isDitto()) 2261 start = child; 2262 } 2263 2264 } 2265 2266 bool started = false; 2267 if(this.parent) 2268 foreach(child; this.parent.children) { 2269 if(started) { 2270 if(!child.isDitto()) 2271 break; 2272 } else { 2273 if(child is start) 2274 started = true; 2275 } 2276 2277 if(started) 2278 foreach(test; child.unittests) 2279 if(test.comment.length) 2280 ret ~= ProcessedUnittest(test.code, test.comment); 2281 } 2282 else 2283 foreach(test; this.unittests) 2284 if(test.comment.length) 2285 ret ~= ProcessedUnittest(test.code, test.comment); 2286 _processedUnittests = ret; 2287 return ret; 2288 } 2289 2290 override string toString() { 2291 string s; 2292 s ~= super.toString() ~ " " ~ this.name(); 2293 foreach(child; children) { 2294 s ~= "\n"; 2295 auto p = parent; 2296 while(p) { 2297 s ~= "\t"; 2298 p = p.parent; 2299 } 2300 s ~= child.toString(); 2301 } 2302 return s; 2303 } 2304 2305 abstract bool isDitto(); 2306 bool isModule() { return false; } 2307 bool isArticle() { return false; } 2308 bool isConstructor() { return false; } 2309 2310 bool aliasThisPresent; 2311 Token aliasThisToken; 2312 string aliasThisComment; 2313 2314 Decl aliasThis() { 2315 if(!aliasThisPresent) 2316 return null; 2317 else 2318 return lookupName(aliasThisToken.text, false); 2319 } 2320 2321 DestructorDecl destructor() { 2322 foreach(child; children) 2323 if(auto dd = cast(DestructorDecl) child) 2324 return dd; 2325 return null; 2326 } 2327 2328 PostblitDecl postblit() { 2329 foreach(child; children) 2330 if(auto dd = cast(PostblitDecl) child) 2331 return dd; 2332 return null; 2333 } 2334 2335 2336 abstract bool isDisabled(); 2337 2338 ConstructorDecl disabledDefaultConstructor() { 2339 foreach(child; children) 2340 if(child.isConstructor() && child.isDisabled()) { 2341 auto ctor = cast(ConstructorDecl) child; 2342 if(ctor.astNode.parameters || ctor.astNode.parameters.parameters.length == 0) 2343 return ctor; 2344 } 2345 return null; 2346 } 2347 } 2348 2349 class ModuleDecl : Decl { 2350 mixin CtorFrom!Module defaultMixins; 2351 2352 string justDocsTitle; 2353 2354 override bool isModule() { return true; } 2355 override bool isArticle() { return justDocsTitle.length > 0; } 2356 2357 override string declarationType() { 2358 return isArticle() ? "Article" : "module"; 2359 } 2360 2361 version(none) 2362 override void getSimplifiedPrototype(MyOutputRange r) { 2363 if(isArticle()) 2364 r.put(justDocsTitle); 2365 else 2366 defaultMixins.getSimplifiedPrototype(r); 2367 } 2368 2369 ubyte[] originalSource; 2370 2371 string packageName() { 2372 auto it = this.name(); 2373 auto idx = it.lastIndexOf("."); 2374 if(idx == -1) 2375 return null; 2376 return it[0 .. idx]; 2377 } 2378 } 2379 2380 class AliasDecl : Decl { 2381 mixin CtorFrom!AliasDeclaration; 2382 2383 this(const(AliasDeclaration) ad, const(VersionOrAttribute)[] attributes) { 2384 this.attributes = attributes; 2385 this.astNode = ad; 2386 this.initializer = null; 2387 // deal with the type and initializer list and storage classes 2388 } 2389 2390 const(AliasInitializer) initializer; 2391 2392 this(const(AliasDeclaration) ad, const(AliasInitializer) init, const(VersionOrAttribute)[] attributes) { 2393 this.attributes = attributes; 2394 this.astNode = ad; 2395 this.initializer = init; 2396 // deal with init 2397 } 2398 2399 override string name() { 2400 if(initializer is null) 2401 return toText(astNode.identifierList); 2402 else 2403 return initializer.name.text; 2404 } 2405 2406 override void getAnnotatedPrototype(MyOutputRange output) { 2407 void cool() { 2408 output.putTag("<div class=\"declaration-prototype\">"); 2409 if(parent !is null && !parent.isModule) { 2410 output.putTag("<div class=\"parent-prototype\""); 2411 parent.getSimplifiedPrototype(output); 2412 output.putTag("</div><div>"); 2413 getPrototype(output, true); 2414 output.putTag("</div>"); 2415 } else { 2416 getPrototype(output, true); 2417 } 2418 output.putTag("</div>"); 2419 } 2420 2421 writeOverloads!cool(this, output); 2422 } 2423 2424 override void getSimplifiedPrototype(MyOutputRange output) { 2425 getPrototype(output, false); 2426 } 2427 2428 void getPrototype(MyOutputRange output, bool link) { 2429 // FIXME: storage classes? 2430 2431 if(link) { 2432 auto f = new MyFormatter!(typeof(output))(output, this); 2433 writeAttributes(f, output, this.attributes); 2434 } 2435 2436 output.putTag("<span class=\"builtin-type\">alias</span> "); 2437 2438 output.putTag("<span class=\"name\">"); 2439 output.put(name); 2440 output.putTag("</span>"); 2441 2442 if(initializer && initializer.templateParameters) { 2443 output.putTag(toHtml(initializer.templateParameters).source); 2444 } 2445 2446 output.put(" = "); 2447 2448 if(initializer) { 2449 if(link) 2450 output.putTag(toLinkedHtml(initializer.type, this).source); 2451 else 2452 output.putTag(toHtml(initializer.type).source); 2453 } 2454 2455 if(astNode.type) { 2456 if(link) { 2457 auto t = toText(astNode.type); 2458 auto decl = lookupName(t); 2459 if(decl is null) 2460 goto nulldecl; 2461 output.putTag(getReferenceLink(t, decl).toString); 2462 } else { 2463 nulldecl: 2464 output.putTag(toHtml(astNode.type).source); 2465 } 2466 } 2467 } 2468 } 2469 2470 class VariableDecl : Decl { 2471 mixin CtorFrom!VariableDeclaration; 2472 2473 const(Declarator) declarator; 2474 this(const(Declarator) declarator, const(VariableDeclaration) astNode, const(VersionOrAttribute)[] attributes) { 2475 this.astNode = astNode; 2476 this.declarator = declarator; 2477 this.attributes = attributes; 2478 this.ident = Token.init; 2479 this.initializer = null; 2480 } 2481 2482 const(Token) ident; 2483 const(Initializer) initializer; 2484 this(const(VariableDeclaration) astNode, const(Token) ident, const(Initializer) initializer, const(VersionOrAttribute)[] attributes, bool isEnum) { 2485 this.declarator = null; 2486 this.attributes = attributes; 2487 this.astNode = astNode; 2488 this.ident = ident; 2489 this.isEnum = isEnum; 2490 this.initializer = initializer; 2491 } 2492 2493 bool isEnum; 2494 2495 override string name() { 2496 if(declarator) 2497 return declarator.name.text; 2498 else 2499 return ident.text; 2500 } 2501 2502 override string rawComment() { 2503 string it = astNode.comment; 2504 auto additional = (declarator ? declarator.comment : astNode.autoDeclaration.comment); 2505 if(additional != it) 2506 it ~= additional; 2507 return it; 2508 } 2509 2510 override void getAnnotatedPrototype(MyOutputRange output) { 2511 output.putTag("<div class=\"declaration-prototype\">"); 2512 if(parent !is null && !parent.isModule) { 2513 output.putTag("<div class=\"parent-prototype\""); 2514 parent.getSimplifiedPrototype(output); 2515 output.putTag("</div><div>"); 2516 auto f = new MyFormatter!(typeof(output))(output); 2517 writeAttributes(f, output, attributes); 2518 getSimplifiedPrototypeInternal(output, true); 2519 output.putTag("</div>"); 2520 } else { 2521 auto f = new MyFormatter!(typeof(output))(output); 2522 writeAttributes(f, output, attributes); 2523 getSimplifiedPrototypeInternal(output, true); 2524 } 2525 output.putTag("</div>"); 2526 } 2527 2528 override void getSimplifiedPrototype(MyOutputRange output) { 2529 getSimplifiedPrototypeInternal(output, false); 2530 } 2531 2532 final void getSimplifiedPrototypeInternal(MyOutputRange output, bool link) { 2533 foreach(sc; astNode.storageClasses) { 2534 output.putTag(toHtml(sc).source); 2535 output.put(" "); 2536 } 2537 2538 if(astNode.type) { 2539 if(link) { 2540 auto html = toHtml(astNode.type).source; 2541 auto txt = toText(astNode.type); 2542 2543 auto typeDecl = lookupName(txt); 2544 if(typeDecl is null || !typeDecl.docsShouldBeOutputted) 2545 goto plain; 2546 2547 output.putTag("<a title=\""~typeDecl.fullyQualifiedName~"\" href=\""~typeDecl.link~"\">" ~ html ~ "</a>"); 2548 } else { 2549 plain: 2550 output.putTag(toHtml(astNode.type).source); 2551 } 2552 } else 2553 output.putTag("<span class=\"builtin-type\">"~(isEnum ? "enum" : "auto")~"</span>"); 2554 2555 output.put(" "); 2556 2557 output.putTag("<span class=\"name\">"); 2558 output.put(name); 2559 output.putTag("</span>"); 2560 2561 if(declarator && declarator.templateParameters) 2562 output.putTag(toHtml(declarator.templateParameters).source); 2563 2564 if(link) { 2565 if(initializer !is null) { 2566 output.put(" = "); 2567 output.putTag(toHtml(initializer).source); 2568 } 2569 } 2570 output.put(";"); 2571 } 2572 2573 override void getAggregatePrototype(MyOutputRange output) { 2574 auto f = new MyFormatter!(typeof(output))(output); 2575 writeAttributes(f, output, attributes); 2576 getSimplifiedPrototypeInternal(output, false); 2577 } 2578 2579 override string declarationType() { 2580 return (isStatic() ? "static variable" : (isEnum ? "manifest constant" : "variable")); 2581 } 2582 } 2583 2584 2585 class FunctionDecl : Decl { 2586 mixin CtorFrom!FunctionDeclaration; 2587 override void getAnnotatedPrototype(MyOutputRange output) { 2588 doFunctionDec(this, output); 2589 } 2590 2591 override Decl lookupName(string name, bool lookUp = true, string[] excludeModules = null) { 2592 // is it a param or template param? If so, return that. 2593 2594 foreach(param; astNode.parameters.parameters) { 2595 if (param.name.type != tok!"") 2596 if(param.name.text == name) { 2597 return null; // it is local, but we don't have a decl.. 2598 } 2599 } 2600 if(astNode.templateParameters && astNode.templateParameters.templateParameterList && astNode.templateParameters.templateParameterList.items) 2601 foreach(param; astNode.templateParameters.templateParameterList.items) { 2602 auto paramName = ""; 2603 2604 if(param.templateTypeParameter) 2605 paramName = param.templateTypeParameter.identifier.text; 2606 else if(param.templateValueParameter) 2607 paramName = param.templateValueParameter.identifier.text; 2608 else if(param.templateAliasParameter) 2609 paramName = param.templateAliasParameter.identifier.text; 2610 else if(param.templateTupleParameter) 2611 paramName = param.templateTupleParameter.identifier.text; 2612 2613 if(paramName.length && paramName == name) { 2614 return null; // it is local, but we don't have a decl.. 2615 } 2616 } 2617 2618 if(lookUp) 2619 return super.lookupName(name, lookUp, excludeModules); 2620 else 2621 return null; 2622 } 2623 2624 override string declarationType() { 2625 return isProperty() ? "property" : (isStatic() ? "static function" : "function"); 2626 } 2627 2628 override void getAggregatePrototype(MyOutputRange output) { 2629 if(isStatic()) { 2630 output.putTag("<span class=\"storage-class\">static</span> "); 2631 } 2632 2633 getSimplifiedPrototype(output); 2634 output.put(";"); 2635 } 2636 2637 override void getSimplifiedPrototype(MyOutputRange output) { 2638 foreach(sc; astNode.storageClasses) { 2639 output.putTag(toHtml(sc).source); 2640 output.put(" "); 2641 } 2642 2643 if(isProperty() && (paramCount == 0 || paramCount == 1 || (paramCount == 2 && !isAggregateMember))) { 2644 if((paramCount == 1 && isAggregateMember()) || (paramCount == 2 && !isAggregateMember())) { 2645 // setter 2646 output.putTag(toHtml(astNode.parameters.parameters[0].type).source); 2647 output.put(" "); 2648 output.putTag("<span class=\"name\">"); 2649 output.put(name); 2650 output.putTag("</span>"); 2651 2652 output.put(" [@property setter]"); 2653 } else { 2654 // getter 2655 putSimplfiedReturnValue(output, astNode); 2656 output.put(" "); 2657 output.putTag("<span class=\"name\">"); 2658 output.put(name); 2659 output.putTag("</span>"); 2660 2661 output.put(" [@property getter]"); 2662 } 2663 } else { 2664 putSimplfiedReturnValue(output, astNode); 2665 output.put(" "); 2666 output.putTag("<span class=\"name\">"); 2667 output.put(name); 2668 output.putTag("</span>"); 2669 putSimplfiedArgs(output, astNode); 2670 } 2671 } 2672 2673 int paramCount() { 2674 return cast(int) astNode.parameters.parameters.length; 2675 } 2676 } 2677 2678 class ConstructorDecl : Decl { 2679 mixin CtorFrom!Constructor; 2680 2681 override void getAnnotatedPrototype(MyOutputRange output) { 2682 doFunctionDec(this, output); 2683 } 2684 2685 override void getSimplifiedPrototype(MyOutputRange output) { 2686 output.putTag("<span class=\"lang-feature name\">"); 2687 output.put("this"); 2688 output.putTag("</span>"); 2689 putSimplfiedArgs(output, astNode); 2690 } 2691 2692 override bool isConstructor() { return true; } 2693 } 2694 2695 class DestructorDecl : Decl { 2696 mixin CtorFrom!Destructor; 2697 2698 override void getSimplifiedPrototype(MyOutputRange output) { 2699 output.putTag("<span class=\"lang-feature name\">"); 2700 output.put("~this"); 2701 output.putTag("</span>"); 2702 output.put("()"); 2703 } 2704 } 2705 2706 class PostblitDecl : Decl { 2707 mixin CtorFrom!Postblit; 2708 2709 override void getSimplifiedPrototype(MyOutputRange output) { 2710 if(isDisabled) { 2711 output.putTag("<span class=\"builtin-type\">"); 2712 output.put("@disable"); 2713 output.putTag("</span>"); 2714 output.put(" "); 2715 } 2716 output.putTag("<span class=\"lang-feature name\">"); 2717 output.put("this(this)"); 2718 output.putTag("</span>"); 2719 } 2720 } 2721 2722 class ImportDecl : Decl { 2723 mixin CtorFrom!ImportDeclaration; 2724 2725 bool isPublic; 2726 string newName; 2727 string oldName; 2728 2729 override string link(bool forFile = false, string* useless = null) { 2730 string d; 2731 if(!forFile) { 2732 d = getDirectoryForPackage(oldName); 2733 } 2734 return d ~ oldName ~ ".html"; 2735 } 2736 2737 // I also want to document undocumented public imports, since they also spam up the namespace 2738 override bool docsShouldBeOutputted() { 2739 return isPublic; 2740 } 2741 2742 override string name() { 2743 return newName.length ? newName : oldName; 2744 } 2745 2746 override string declarationType() { 2747 return "import"; 2748 } 2749 2750 override void getSimplifiedPrototype(MyOutputRange output) { 2751 if(isPublic) 2752 output.putTag("<span class=\"builtin-type\">public</span> "); 2753 output.putTag(toHtml(astNode).source); 2754 } 2755 2756 } 2757 2758 class MixedInTemplateDecl : Decl { 2759 mixin CtorFrom!TemplateMixinExpression; 2760 2761 override string declarationType() { 2762 return "mixin"; 2763 } 2764 2765 override void getSimplifiedPrototype(MyOutputRange output) { 2766 output.putTag(toHtml(astNode).source); 2767 } 2768 } 2769 2770 class StructDecl : Decl { 2771 mixin CtorFrom!StructDeclaration; 2772 override void getAnnotatedPrototype(MyOutputRange output) { 2773 annotatedPrototype(this, output); 2774 } 2775 2776 } 2777 2778 class UnionDecl : Decl { 2779 mixin CtorFrom!UnionDeclaration; 2780 2781 override void getAnnotatedPrototype(MyOutputRange output) { 2782 annotatedPrototype(this, output); 2783 } 2784 } 2785 2786 class ClassDecl : Decl { 2787 mixin CtorFrom!ClassDeclaration; 2788 2789 override void getAnnotatedPrototype(MyOutputRange output) { 2790 annotatedPrototype(this, output); 2791 } 2792 } 2793 2794 class InterfaceDecl : Decl { 2795 mixin CtorFrom!InterfaceDeclaration; 2796 override void getAnnotatedPrototype(MyOutputRange output) { 2797 annotatedPrototype(this, output); 2798 } 2799 } 2800 2801 class TemplateDecl : Decl { 2802 mixin CtorFrom!TemplateDeclaration; 2803 2804 Decl eponymousMember() { 2805 foreach(child; this.children) 2806 if(child.name == this.name) 2807 return child; 2808 return null; 2809 } 2810 2811 override void getAnnotatedPrototype(MyOutputRange output) { 2812 annotatedPrototype(this, output); 2813 } 2814 } 2815 2816 class EponymousTemplateDecl : Decl { 2817 mixin CtorFrom!EponymousTemplateDeclaration; 2818 2819 /* 2820 Decl eponymousMember() { 2821 foreach(child; this.children) 2822 if(child.name == this.name) 2823 return child; 2824 return null; 2825 } 2826 */ 2827 2828 override string declarationType() { 2829 return "enum"; 2830 } 2831 2832 override void getAnnotatedPrototype(MyOutputRange output) { 2833 annotatedPrototype(this, output); 2834 } 2835 } 2836 2837 2838 class MixinTemplateDecl : Decl { 2839 mixin CtorFrom!TemplateDeclaration; // MixinTemplateDeclaration does nothing interesting except this.. 2840 2841 override void getAnnotatedPrototype(MyOutputRange output) { 2842 annotatedPrototype(this, output); 2843 } 2844 2845 override string declarationType() { 2846 return "mixin template"; 2847 } 2848 } 2849 2850 class EnumDecl : Decl { 2851 mixin CtorFrom!EnumDeclaration; 2852 2853 override void addSupplementalData(Element content) { 2854 doEnumDecl(this, content); 2855 } 2856 } 2857 2858 class AnonymousEnumDecl : Decl { 2859 mixin CtorFrom!AnonymousEnumDeclaration; 2860 2861 override string name() { 2862 assert(astNode.members.length > 0); 2863 auto name = astNode.members[0].name.text; 2864 return name; 2865 } 2866 2867 override void addSupplementalData(Element content) { 2868 doEnumDecl(this, content); 2869 } 2870 2871 override string declarationType() { 2872 return "enum"; 2873 } 2874 } 2875 2876 mixin template CtorFrom(T) { 2877 const(T) astNode; 2878 2879 static if(!is(T == VariableDeclaration) && !is(T == AliasDeclaration)) 2880 this(const(T) astNode, const(VersionOrAttribute)[] attributes) { 2881 this.astNode = astNode; 2882 this.attributes = attributes; 2883 2884 static if(is(typeof(astNode.memberFunctionAttributes))) { 2885 foreach(a; astNode.memberFunctionAttributes) 2886 if(a !is null) 2887 this.attributes ~= new MemberFakeAttribute(a); 2888 } 2889 2890 static if(is(typeof(astNode) == const(ClassDeclaration)) || is(typeof(astNode) == const(InterfaceDeclaration))) { 2891 if(astNode.baseClassList) 2892 foreach(idx, baseClass; astNode.baseClassList.items) { 2893 auto bc = toText(baseClass); 2894 InheritanceResult ir = InheritanceResult(null, bc); 2895 _inheritsFrom ~= ir; 2896 } 2897 } 2898 } 2899 2900 static if(is(T == Module)) { 2901 // this is so I can load this from the index... kinda a hack 2902 // it should only be used in limited circumstances 2903 private string _name; 2904 private this(string name) { 2905 this._name = name; 2906 this.astNode = null; 2907 } 2908 } 2909 2910 override const(T) getAstNode() { return astNode; } 2911 override int lineNumber() { 2912 static if(__traits(compiles, astNode.name.line)) 2913 return cast(int) astNode.name.line; 2914 else static if(__traits(compiles, astNode.line)) 2915 return cast(int) astNode.line; 2916 else static if(__traits(compiles, astNode.declarators[0].name.line)) { 2917 if(astNode.declarators.length) 2918 return cast(int) astNode.declarators[0].name.line; 2919 } else static if(is(typeof(astNode) == const(Module))) { 2920 return 0; 2921 } else static assert(0, typeof(astNode).stringof); 2922 return 0; 2923 } 2924 2925 override void writeTemplateConstraint(MyOutputRange output) { 2926 static if(__traits(compiles, astNode.constraint)) { 2927 if(astNode.constraint) { 2928 auto f = new MyFormatter!(typeof(output))(output); 2929 output.putTag("<div class=\"template-constraint\">"); 2930 f.format(astNode.constraint); 2931 output.putTag("</div>"); 2932 } 2933 } 2934 } 2935 2936 override string name() { 2937 static if(is(T == Constructor)) 2938 return "this"; 2939 else static if(is(T == Destructor)) 2940 return "~this"; 2941 else static if(is(T == Postblit)) 2942 return "this(this)"; 2943 else static if(is(T == Module)) 2944 return _name is null ? .format(astNode.moduleDeclaration.moduleName) : _name; 2945 else static if(is(T == AnonymousEnumDeclaration)) 2946 { assert(0); } // overridden above 2947 else static if(is(T == AliasDeclaration)) 2948 { assert(0); } // overridden above 2949 else static if(is(T == VariableDeclaration)) 2950 {assert(0);} // not compiled, overridden above 2951 else static if(is(T == ImportDeclaration)) 2952 {assert(0);} // not compiled, overridden above 2953 else static if(is(T == MixinTemplateDeclaration)) { 2954 return astNode.templateDeclaration.name.text; 2955 } else static if(is(T == StructDeclaration) || is(T == UnionDeclaration)) 2956 if(astNode.name.text.length) 2957 return astNode.name.text; 2958 else 2959 return "__anonymous"; 2960 else static if(is(T == TemplateMixinExpression)) { 2961 return astNode.identifier.text.length ? astNode.identifier.text : "__anonymous"; 2962 } else 2963 return astNode.name.text; 2964 } 2965 2966 override string comment() { 2967 static if(is(T == Module)) 2968 return astNode.moduleDeclaration.comment; 2969 else { 2970 if(isDitto()) { 2971 auto ps = previousSibling; 2972 while(ps && ps.rawComment.length == 0) 2973 ps = ps.previousSibling; 2974 return ps ? ps.comment : rawComment(); 2975 } else 2976 return rawComment(); 2977 } 2978 } 2979 2980 override void getAnnotatedPrototype(MyOutputRange) {} 2981 override void getSimplifiedPrototype(MyOutputRange output) { 2982 output.putTag("<span class=\"builtin-type\">"); 2983 output.put(declarationType()); 2984 output.putTag("</span>"); 2985 output.put(" "); 2986 2987 output.putTag("<span class=\"name\">"); 2988 output.put(this.name); 2989 output.putTag("</span>"); 2990 2991 static if(__traits(compiles, astNode.templateParameters)) { 2992 if(astNode.templateParameters) { 2993 output.putTag("<span class=\"template-params\">"); 2994 output.put(toText(astNode.templateParameters)); 2995 output.putTag("</span>"); 2996 } 2997 } 2998 } 2999 override string declarationType() { 3000 import std.string:toLower; 3001 return toLower(typeof(this).stringof[0 .. $-4]); 3002 } 3003 3004 override bool isDitto() { 3005 static if(is(T == Module)) 3006 return false; 3007 else { 3008 import std.string; 3009 auto l = strip(toLower(preprocessComment(rawComment, this))); 3010 if(l.length && l[$-1] == '.') 3011 l = l[0 .. $-1]; 3012 return l == "ditto"; 3013 } 3014 } 3015 3016 override string rawComment() { 3017 static if(is(T == Module)) 3018 return astNode.moduleDeclaration.comment; 3019 else static if(is(T == MixinTemplateDeclaration)) 3020 return astNode.templateDeclaration.comment; 3021 else 3022 return astNode.comment; 3023 } 3024 3025 override bool isDisabled() { 3026 foreach(attribute; attributes) 3027 if(attribute.attr && attribute.attr.atAttribute && attribute.attr.atAttribute.identifier.text == "disable") 3028 return true; 3029 static if(__traits(compiles, astNode.memberFunctionAttributes)) 3030 foreach(attribute; astNode.memberFunctionAttributes) 3031 if(attribute && attribute.atAttribute && attribute.atAttribute.identifier.text == "disable") 3032 return true; 3033 return false; 3034 } 3035 3036 } 3037 3038 ClassDecl[string] allClasses; 3039 3040 class Looker : ASTVisitor { 3041 alias visit = ASTVisitor.visit; 3042 3043 const(ubyte)[] fileBytes; 3044 string originalFileName; 3045 this(const(ubyte)[] fileBytes, string fileName) { 3046 this.fileBytes = fileBytes; 3047 this.originalFileName = fileName; 3048 } 3049 3050 ModuleDecl root; 3051 3052 3053 private Decl[] stack; 3054 3055 Decl previousSibling() { 3056 auto s = stack[$-1]; 3057 if(s.children.length) 3058 return s.children[$-1]; 3059 return s; // probably a documented unittest of the module itself 3060 } 3061 3062 void visitInto(D, T)(const(T) t) { 3063 auto d = new D(t, attributes[$-1]); 3064 stack[$-1].addChild(d); 3065 stack ~= d; 3066 t.accept(this); 3067 stack = stack[0 .. $-1]; 3068 3069 static if(is(D == ClassDecl)) 3070 allClasses[d.name] = d; 3071 } 3072 3073 override void visit(const Module mod) { 3074 pushAttributes(); 3075 3076 root = new ModuleDecl(mod, attributes[$-1]); 3077 stack ~= root; 3078 mod.accept(this); 3079 assert(stack.length == 1); 3080 } 3081 3082 override void visit(const FunctionDeclaration dec) { 3083 stack[$-1].addChild(new FunctionDecl(dec, attributes[$-1])); 3084 } 3085 override void visit(const Constructor dec) { 3086 stack[$-1].addChild(new ConstructorDecl(dec, attributes[$-1])); 3087 } 3088 override void visit(const TemplateMixinExpression dec) { 3089 stack[$-1].addChild(new MixedInTemplateDecl(dec, attributes[$-1])); 3090 } 3091 override void visit(const Postblit dec) { 3092 stack[$-1].addChild(new PostblitDecl(dec, attributes[$-1])); 3093 } 3094 override void visit(const Destructor dec) { 3095 stack[$-1].addChild(new DestructorDecl(dec, attributes[$-1])); 3096 } 3097 3098 override void visit(const StructDeclaration dec) { 3099 visitInto!StructDecl(dec); 3100 } 3101 override void visit(const ClassDeclaration dec) { 3102 visitInto!ClassDecl(dec); 3103 } 3104 override void visit(const UnionDeclaration dec) { 3105 visitInto!UnionDecl(dec); 3106 } 3107 override void visit(const InterfaceDeclaration dec) { 3108 visitInto!InterfaceDecl(dec); 3109 } 3110 override void visit(const TemplateDeclaration dec) { 3111 visitInto!TemplateDecl(dec); 3112 } 3113 override void visit(const EponymousTemplateDeclaration dec) { 3114 visitInto!EponymousTemplateDecl(dec); 3115 } 3116 override void visit(const MixinTemplateDeclaration dec) { 3117 visitInto!MixinTemplateDecl(dec.templateDeclaration); 3118 } 3119 override void visit(const EnumDeclaration dec) { 3120 visitInto!EnumDecl(dec); 3121 } 3122 override void visit(const AliasThisDeclaration dec) { 3123 stack[$-1].aliasThisPresent = true; 3124 stack[$-1].aliasThisToken = dec.identifier; 3125 stack[$-1].aliasThisComment = dec.comment; 3126 } 3127 override void visit(const AnonymousEnumDeclaration dec) { 3128 // we can't do anything with an empty anonymous enum, we need a name from somewhere 3129 if(dec.members.length) 3130 visitInto!AnonymousEnumDecl(dec); 3131 } 3132 override void visit(const VariableDeclaration dec) { 3133 if (dec.autoDeclaration) { 3134 foreach (idx, ident; dec.autoDeclaration.identifiers) { 3135 stack[$-1].addChild(new VariableDecl(dec, ident, dec.autoDeclaration.initializers[idx], attributes[$-1], dec.isEnum)); 3136 3137 } 3138 } else 3139 foreach (const Declarator d; dec.declarators) { 3140 stack[$-1].addChild(new VariableDecl(d, dec, attributes[$-1])); 3141 3142 /* 3143 if (variableDeclaration.type !is null) 3144 { 3145 auto f = new MyFormatter!(typeof(app))(app); 3146 f.format(variableDeclaration.type); 3147 } 3148 output.putTag(app.data); 3149 output.put(" "); 3150 output.put(d.name.text); 3151 3152 comment.writeDetails(output); 3153 3154 writeToParentList("variable " ~ cast(string)app.data ~ " ", name, comment.synopsis, "variable"); 3155 3156 ascendOutOf(name); 3157 */ 3158 } 3159 } 3160 override void visit(const AliasDeclaration dec) { 3161 if(dec.initializers.length) { // alias a = b 3162 foreach(init; dec.initializers) 3163 stack[$-1].addChild(new AliasDecl(dec, init, attributes[$-1])); 3164 } else { // alias b a; 3165 // might include a type... 3166 stack[$-1].addChild(new AliasDecl(dec, attributes[$-1])); 3167 } 3168 } 3169 3170 override void visit(const Unittest ut) { 3171 //import std.stdio; writeln(fileBytes.length, " ", ut.blockStatement.startLocation, " ", ut.blockStatement.endLocation); 3172 previousSibling.addUnittest( 3173 ut, 3174 fileBytes[ut.blockStatement.startLocation + 1 .. ut.blockStatement.endLocation], // trim off the opening and closing {} 3175 ut.comment 3176 ); 3177 } 3178 3179 override void visit(const ImportDeclaration id) { 3180 3181 bool isPublic = false; 3182 3183 foreach(a; attributes[$-1]) { 3184 if (a.attr && isProtection(a.attr.attribute.type)) { 3185 if(a.attr.attribute.type == tok!"public") { 3186 isPublic = true; 3187 } else { 3188 isPublic = false; 3189 } 3190 } 3191 } 3192 3193 3194 void handleSingleImport(const SingleImport si) { 3195 auto newName = si.rename.text; 3196 auto oldName = ""; 3197 foreach(idx, ident; si.identifierChain.identifiers) { 3198 if(idx) 3199 oldName ~= "."; 3200 oldName ~= ident.text; 3201 } 3202 stack[$-1].addImport(oldName, isPublic); 3203 // FIXME: handle the rest like newName for the import lookups 3204 3205 auto nid = new ImportDecl(id, attributes[$-1]); 3206 stack[$-1].addChild(nid); 3207 nid.isPublic = isPublic; 3208 nid.oldName = oldName; 3209 nid.newName = newName; 3210 } 3211 3212 3213 foreach(si; id.singleImports) { 3214 handleSingleImport(si); 3215 } 3216 3217 if(id.importBindings && id.importBindings.singleImport) 3218 handleSingleImport(id.importBindings.singleImport); // FIXME: handle bindings 3219 3220 } 3221 3222 override void visit(const StructBody sb) { 3223 pushAttributes(); 3224 sb.accept(this); 3225 popAttributes(); 3226 } 3227 3228 // FIXME ???? 3229 override void visit(const VersionCondition sb) { 3230 attributes[$-1] ~= new VersionFakeAttribute(toText(sb.token)); 3231 sb.accept(this); 3232 } 3233 3234 override void visit(const BlockStatement bs) { 3235 pushAttributes(); 3236 bs.accept(this); 3237 popAttributes(); 3238 } 3239 3240 override void visit(const FunctionBody bs) { 3241 // just skipping it hence the commented code below. not important to docs 3242 /* 3243 pushAttributes(); 3244 bs.accept(this); 3245 popAttributes(); 3246 */ 3247 } 3248 3249 override void visit(const ConditionalDeclaration bs) { 3250 pushAttributes(); 3251 if(attributes.length > 2) 3252 attributes[$-1] = attributes[$-2]; // inherit from the previous scope here 3253 size_t previousConditions; 3254 if(bs.compileCondition) { 3255 previousConditions = attributes[$-1].length; 3256 bs.compileCondition.accept(this); 3257 } 3258 3259 if(bs.trueDeclarations) 3260 foreach(td; bs.trueDeclarations) 3261 td.accept(this); 3262 3263 if(bs.falseDeclaration) { 3264 auto slice = attributes[$-1][previousConditions .. $]; 3265 attributes[$-1] = attributes[$-1][0 .. previousConditions]; 3266 foreach(cond; slice) 3267 attributes[$-1] ~= cond.invertedClone; 3268 bs.falseDeclaration.accept(this); 3269 } 3270 popAttributes(); 3271 } 3272 3273 override void visit(const Declaration dec) { 3274 auto originalAttributes = attributes[$ - 1]; 3275 foreach(a; dec.attributes) 3276 attributes[$ - 1] ~= new VersionOrAttribute(a); 3277 dec.accept(this); 3278 if (dec.attributeDeclaration is null) 3279 attributes[$ - 1] = originalAttributes; 3280 } 3281 3282 override void visit(const AttributeDeclaration dec) { 3283 attributes[$ - 1] ~= new VersionOrAttribute(dec.attribute); 3284 } 3285 3286 void pushAttributes() { 3287 attributes.length = attributes.length + 1; 3288 } 3289 3290 void popAttributes() { 3291 attributes = attributes[0 .. $ - 1]; 3292 } 3293 3294 const(VersionOrAttribute)[][] attributes; 3295 } 3296 3297 3298 string format(const IdentifierChain identifierChain) { 3299 string r; 3300 foreach(count, ident; identifierChain.identifiers) { 3301 if (count) r ~= ("."); 3302 r ~= (ident.text); 3303 } 3304 return r; 3305 } 3306 3307 import std.algorithm : startsWith, findSplitBefore; 3308 import std.string : strip; 3309 3310 //Decl[][string] packages; 3311 __gshared static Object modulesByNameMonitor = new Object; // intentional CTF 3312 __gshared ModuleDecl[string] modulesByName; 3313 3314 __gshared string specialPreprocessor; 3315 3316 // simplified ".gitignore" processor 3317 final class GitIgnore { 3318 string[] masks; // on each new dir, empty line is added to masks 3319 3320 void loadGlobalGitIgnore () { 3321 import std.path; 3322 import std.stdio; 3323 try { 3324 foreach (string s; File("~/.gitignore_global".expandTilde).byLineCopy) { 3325 if (isComment(s)) continue; 3326 masks ~= trim(s); 3327 } 3328 } catch (Exception e) {} // sorry 3329 try { 3330 foreach (string s; File("~/.adrdoxignore_global".expandTilde).byLineCopy) { 3331 if (isComment(s)) continue; 3332 masks ~= trim(s); 3333 } 3334 } catch (Exception e) {} // sorry 3335 } 3336 3337 void loadGitIgnore (const(char)[] dir) { 3338 import std.path; 3339 import std.stdio; 3340 masks ~= null; 3341 try { 3342 foreach (string s; File(buildPath(dir, ".gitignore").expandTilde).byLineCopy) { 3343 if (isComment(s)) continue; 3344 masks ~= trim(s); 3345 } 3346 } catch (Exception e) {} // sorry 3347 try { 3348 foreach (string s; File(buildPath(dir, ".adrdoxignore").expandTilde).byLineCopy) { 3349 if (isComment(s)) continue; 3350 masks ~= trim(s); 3351 } 3352 } catch (Exception e) {} // sorry 3353 } 3354 3355 // unload latest gitignore 3356 void unloadGitIgnore () { 3357 auto ol = masks.length; 3358 while (masks.length > 0 && masks[$-1] !is null) masks = masks[0..$-1]; 3359 if (masks.length > 0 && masks[$-1] is null) masks = masks[0..$-1]; 3360 if (masks.length != ol) { 3361 //writeln("removed ", ol-masks.length, " lines"); 3362 masks.assumeSafeAppend; //hack! 3363 } 3364 } 3365 3366 bool match (string fname) { 3367 import std.path; 3368 import std.stdio; 3369 if (masks.length == 0) return false; 3370 //writeln("gitignore checking: <", fname, ">"); 3371 3372 bool xmatch (string path, string mask) { 3373 if (mask.length == 0 || path.length == 0) return false; 3374 import std.string : indexOf; 3375 if (mask.indexOf('/') < 0) return path.baseName.globMatch(mask); 3376 int xpos = cast(int)path.length-1; 3377 while (xpos >= 0) { 3378 while (xpos > 0 && path[xpos] != '/') --xpos; 3379 if (mask[0] == '/') { 3380 if (xpos+1 < path.length && path[xpos+1..$].globMatch(mask)) return true; 3381 } else { 3382 if (path[xpos..$].globMatch(mask)) return true; 3383 } 3384 --xpos; 3385 } 3386 return false; 3387 } 3388 3389 string curname = fname.baseName; 3390 int pos = cast(int)masks.length-1; 3391 // local dir matching 3392 while (pos >= 0 && masks[pos] !is null) { 3393 //writeln(" [", masks[pos], "]"); 3394 if (xmatch(curname, masks[pos])) { 3395 //writeln(" LOCAL HIT: [", masks[pos], "]: <", curname, ">"); 3396 return true; 3397 } 3398 if (masks[pos][0] == '/' && xmatch(curname, masks[pos][1..$])) return true; 3399 --pos; 3400 } 3401 curname = fname; 3402 while (pos >= 0) { 3403 if (masks[pos] !is null) { 3404 //writeln(" [", masks[pos], "]"); 3405 if (xmatch(curname, masks[pos])) { 3406 //writeln(" HIT: [", masks[pos], "]: <", curname, ">"); 3407 return true; 3408 } 3409 } 3410 --pos; 3411 } 3412 return false; 3413 } 3414 3415 static: 3416 inout(char)[] trim (inout(char)[] s) { 3417 while (s.length > 0 && s[0] <= ' ') s = s[1..$]; 3418 while (s.length > 0 && s[$-1] <= ' ') s = s[0..$-1]; 3419 return s; 3420 } 3421 3422 bool isComment (const(char)[] s) { 3423 s = trim(s); 3424 return (s.length == 0 || s[0] == '#'); 3425 } 3426 } 3427 3428 3429 string[] scanFiles (string basedir) { 3430 import std.file : isDir; 3431 import std.path; 3432 3433 if(basedir == "-") 3434 return ["-"]; 3435 3436 string[] res; 3437 3438 auto gi = new GitIgnore(); 3439 gi.loadGlobalGitIgnore(); 3440 3441 void scanSubDir(bool checkdir=true) (string dir) { 3442 import std.file; 3443 static if (checkdir) { 3444 string d = dir; 3445 if (d.length > 1 && d[$-1] == '/') d = d[0..$-1]; 3446 if (gi.match(d)) { 3447 //writeln("DIR SKIP: <", dir, ">"); 3448 return; 3449 } 3450 } 3451 gi.loadGitIgnore(dir); 3452 scope(exit) gi.unloadGitIgnore(); 3453 foreach (DirEntry de; dirEntries(dir, SpanMode.shallow)) { 3454 try { 3455 if (de.baseName.length == 0) continue; // just in case 3456 if (de.baseName[0] == '.') continue; // skip hidden files 3457 if (de.isDir) { scanSubDir(de.name); continue; } 3458 if (!de.baseName.globMatch("*.d")) continue; 3459 if (/*de.isFile &&*/ !gi.match(de.name)) { 3460 //writeln(de.name); 3461 res ~= de.name; 3462 } 3463 } catch (Exception e) {} // some checks (like `isDir`) can throw 3464 } 3465 } 3466 3467 basedir = basedir.expandTilde.absolutePath; 3468 if (basedir.isDir) { 3469 scanSubDir!false(basedir); 3470 } else { 3471 res ~= basedir; 3472 } 3473 return res; 3474 } 3475 3476 void writeFile(string filename, string content, bool gzip) { 3477 import std.zlib; 3478 import std.file; 3479 3480 if(gzip) { 3481 auto compress = new Compress(HeaderFormat.gzip); 3482 auto data = compress.compress(content); 3483 data ~= compress.flush(); 3484 3485 std.file.write(filename ~ ".gz", data); 3486 } else { 3487 std.file.write(filename, content); 3488 } 3489 } 3490 3491 __gshared bool generatingSource; 3492 __gshared bool blogMode = false; 3493 3494 void main(string[] args) { 3495 import std.stdio; 3496 import std.path : buildPath; 3497 import std.getopt; 3498 3499 static import std.file; 3500 LexerConfig config; 3501 StringCache stringCache = StringCache(128); 3502 3503 config.stringBehavior = StringBehavior.source; 3504 config.whitespaceBehavior = WhitespaceBehavior.include; 3505 3506 ModuleDecl[] moduleDecls; 3507 ModuleDecl[] moduleDeclsGenerate; 3508 ModuleDecl[string] moduleDeclsGenerateByName; 3509 3510 bool makeHtml = true; 3511 bool makeSearchIndex = false; 3512 3513 string[] preloadArgs; 3514 3515 string[] linkReferences; 3516 3517 bool annotateSource = false; 3518 3519 string locateSymbol = null; 3520 bool gzip; 3521 bool copyStandardFiles = true; 3522 string headerTitle; 3523 3524 string texMath = "latex"; 3525 3526 string[] headerLinks; 3527 HeaderLink[] headerLinksParsed; 3528 3529 bool skipExisting = false; 3530 3531 string[] globPathInput; 3532 3533 int jobs = 0; 3534 3535 auto opt = getopt(args, 3536 std.getopt.config.passThrough, 3537 std.getopt.config.bundling, 3538 "load", "Load for automatic cross-referencing, but do not generate for it", &preloadArgs, 3539 "link-references", "A file defining global link references", &linkReferences, 3540 "skeleton|s", "Location of the skeleton file, change to your use case, Default: skeleton.html", &skeletonFile, 3541 "directory|o", "Output directory of the html files", &outputDirectory, 3542 "write-private-docs|p", "Include documentation for `private` members (default: false)", &writePrivateDocs, 3543 "write-internal-modules", "Include documentation for modules named `internal` (default: false)", &documentInternal, 3544 "locate-symbol", "Locate a symbol in the passed file", &locateSymbol, 3545 "genHtml|h", "Generate html, default: true", &makeHtml, 3546 "genSource|u", "Generate annotated source", &annotateSource, 3547 "genSearchIndex|i", "Generate search index, default: false", &makeSearchIndex, 3548 "gzip|z", "Gzip generated files as they are created", &gzip, 3549 "copy-standard-files", "Copy standard JS/CSS files into target directory (default: true)", ©StandardFiles, 3550 "blog-mode", "Use adrdox as a static site generator for a blog", &blogMode, 3551 "header-title", "Title to put on the page header", &headerTitle, 3552 "header-link", "Link to add to the header (text=url)", &headerLinks, 3553 "document-undocumented", "Generate documentation even for undocumented symbols", &documentUndocumented, 3554 "minimal-descent", "Performs minimal descent into generating sub-pages", &minimalDescent, 3555 "case-insensitive-filenames", "Adjust generated filenames for case-insensitive file systems", &caseInsensitiveFilenames, 3556 "skip-existing", "Skip file generation for modules where the html already exists in the output dir", &skipExisting, 3557 "tex-math", "How TeX math should be processed (latex|katex, default=latex)", &texMath, 3558 "special-preprocessor", "Run a special preprocessor on comments. Only supported right now are gtk and dwt", &specialPreprocessor, 3559 "jobs|j", "Number of generation jobs to run at once (default=dependent on number of cpu cores", &jobs, 3560 "package-path", "Path to be prefixed to links for a particular D package namespace (package_pattern=link_prefix)", &globPathInput); 3561 3562 foreach(gpi; globPathInput) { 3563 auto idx = gpi.indexOf("="); 3564 string pathGlob; 3565 string dir; 3566 if(idx != -1) { 3567 pathGlob = gpi[0 .. idx]; 3568 dir = gpi[idx + 1 .. $]; 3569 } else { 3570 pathGlob = gpi; 3571 } 3572 3573 synchronized(directoriesForPackageMonitor) 3574 directoriesForPackage[pathGlob] = dir; 3575 } 3576 3577 generatingSource = annotateSource; 3578 3579 if (outputDirectory[$-1] != '/') 3580 outputDirectory ~= '/'; 3581 3582 if (opt.helpWanted || args.length == 1) { 3583 defaultGetoptPrinter("A better D documentation generator\nCopyright © Adam D. Ruppe 2016-2018\n" ~ 3584 "Syntax: " ~ args[0] ~ " /path/to/your/package\n", opt.options); 3585 return; 3586 } 3587 3588 texMathOpt = parseTexMathOpt(texMath); 3589 3590 foreach(l; headerLinks) { 3591 auto idx = l.indexOf("="); 3592 if(idx == -1) 3593 continue; 3594 3595 HeaderLink lnk; 3596 lnk.text = l[0 .. idx].strip; 3597 lnk.url = l[idx + 1 .. $].strip; 3598 3599 headerLinksParsed ~= lnk; 3600 } 3601 3602 if(locateSymbol is null) { 3603 import std.file; 3604 3605 if (!exists(skeletonFile) && findStandardFile!false("skeleton-default.html").length) 3606 copyStandardFileTo!false(skeletonFile, "skeleton-default.html"); 3607 3608 if (!exists(outputDirectory)) 3609 mkdir(outputDirectory); 3610 3611 if(copyStandardFiles) { 3612 copyStandardFileTo(outputDirectory ~ "style.css", "style.css"); 3613 copyStandardFileTo(outputDirectory ~ "script.js", "script.js"); 3614 copyStandardFileTo(outputDirectory ~ "search-docs.js", "search-docs.js"); 3615 3616 switch (texMathOpt) with (TexMathOpt) { 3617 case KaTeX: { 3618 import adrdox.jstex; 3619 foreach (file; filesForKaTeX) { 3620 copyStandardFileTo(outputDirectory ~ file, "katex/" ~ file); 3621 } 3622 break; 3623 } 3624 default: break; 3625 } 3626 } 3627 3628 /* 3629 if(!exists(skeletonFile) && exists("skeleton-default.html")) 3630 copy("skeleton-default.html", skeletonFile); 3631 3632 if(!exists(outputDirectory)) 3633 mkdir(outputDirectory); 3634 if(!exists(outputDirectory ~ "style.css") || (timeLastModified(outputDirectory ~ "style.css") < timeLastModified("style.css"))) 3635 copy("style.css", outputDirectory ~ "style.css"); 3636 if(!exists(outputDirectory ~ "script.js") || (timeLastModified(outputDirectory ~ "script.js") < timeLastModified("script.js"))) 3637 copy("script.js", outputDirectory ~ "script.js"); 3638 */ 3639 } 3640 3641 // FIXME: maybe a zeroth path just grepping for a module declaration in located files 3642 // and making a mapping of module names, package listing, and files. 3643 // cuz reading all of Phobos takes several seconds. Then they can parse it fully lazily. 3644 3645 static void generateAnnotatedSource(ModuleDecl mod, bool gzip) { 3646 import std.file; 3647 auto annotatedSourceDocument = new Document(); 3648 annotatedSourceDocument.parseUtf8(readText(skeletonFile), true, true); 3649 3650 string fixupLink(string s) { 3651 if(!s.startsWith("http") && !s.startsWith("/")) 3652 return "../" ~ s; 3653 return s; 3654 } 3655 3656 foreach(ele; annotatedSourceDocument.querySelectorAll("a, link, script[src], form")) 3657 if(ele.tagName == "link") 3658 ele.attrs.href = "../" ~ ele.attrs.href; 3659 else if(ele.tagName == "form") 3660 ele.attrs.action = "../" ~ ele.attrs.action; 3661 else if(ele.tagName == "a") 3662 ele.attrs.href = fixupLink(ele.attrs.href); 3663 else 3664 ele.attrs.src = "../" ~ ele.attrs.src; 3665 3666 auto code = Element.make("pre", Html(linkUpHtml(highlight(cast(string) mod.originalSource), mod, "../", true))).addClass("d_code highlighted"); 3667 addLineNumbering(code, true); 3668 auto content = annotatedSourceDocument.requireElementById("page-content"); 3669 content.addChild(code); 3670 3671 auto nav = annotatedSourceDocument.requireElementById("page-nav"); 3672 3673 void addDeclNav(Element nav, Decl decl) { 3674 auto li = nav.addChild("li"); 3675 if(decl.docsShouldBeOutputted) 3676 li.addChild("a", "[Docs] ", fixupLink(decl.link)).addClass("docs"); 3677 li.addChild("a", decl.name, "#L" ~ to!string(decl.lineNumber == 0 ? 1 : decl.lineNumber)); 3678 if(decl.children.length) 3679 nav = li.addChild("ul"); 3680 foreach(child; decl.children) 3681 addDeclNav(nav, child); 3682 3683 } 3684 3685 auto sn = nav.addChild("div").setAttribute("id", "source-navigation"); 3686 3687 addDeclNav(sn.addChild("div").addClass("list-holder").addChild("ul"), mod); 3688 3689 annotatedSourceDocument.title = mod.name ~ " source code"; 3690 3691 if(!usePseudoFiles && !exists(outputDirectory ~ "source")) 3692 mkdir(outputDirectory ~ "source"); 3693 if(usePseudoFiles) 3694 pseudoFiles["source/" ~ mod.name ~ ".d.html"] = annotatedSourceDocument.toString(); 3695 else 3696 writeFile(outputDirectory ~ "source/" ~ mod.name ~ ".d.html", annotatedSourceDocument.toString(), gzip); 3697 } 3698 3699 void process(string arg, bool generate) { 3700 try { 3701 if(locateSymbol is null) 3702 writeln("First pass processing ", arg); 3703 import std.file; 3704 ubyte[] b; 3705 3706 if(arg == "-") { 3707 foreach(chunk; stdin.byChunk(4096)) 3708 b ~= chunk; 3709 } else 3710 b = cast(ubyte[]) read(arg); 3711 3712 config.fileName = arg; 3713 auto tokens = getTokensForParser(b, config, &stringCache); 3714 3715 import std.path : baseName; 3716 auto m = parseModule(tokens, baseName(arg)); 3717 3718 auto sweet = new Looker(b, baseName(arg)); 3719 sweet.visit(m); 3720 3721 ModuleDecl existingDecl; 3722 3723 auto mod = cast(ModuleDecl) sweet.root; 3724 3725 { 3726 mod.originalSource = b; 3727 if(mod.astNode.moduleDeclaration is null) 3728 throw new Exception("you must have a module declaration for this to work on it"); 3729 3730 if(b.startsWith(cast(ubyte[])"// just docs:")) 3731 sweet.root.justDocsTitle = (cast(string) b["// just docs:".length .. $].findSplitBefore(['\n'])[0].idup).strip; 3732 3733 synchronized(modulesByNameMonitor) { 3734 if(sweet.root.name !in modulesByName) { 3735 moduleDecls ~= mod; 3736 existingDecl = mod; 3737 3738 assert(mod !is null); 3739 modulesByName[sweet.root.name] = mod; 3740 } else { 3741 existingDecl = modulesByName[sweet.root.name]; 3742 } 3743 } 3744 } 3745 3746 if(generate) { 3747 3748 if(sweet.root.name !in moduleDeclsGenerateByName) { 3749 moduleDeclsGenerateByName[sweet.root.name] = existingDecl; 3750 moduleDeclsGenerate ~= existingDecl; 3751 3752 if(generatingSource) { 3753 generateAnnotatedSource(mod, gzip); 3754 } 3755 } 3756 } 3757 3758 //packages[sweet.root.packageName] ~= sweet.root; 3759 3760 3761 } catch (Throwable t) { 3762 writeln(t.toString()); 3763 } 3764 } 3765 3766 args = args[1 .. $]; // remove program name 3767 3768 foreach(arg; linkReferences) { 3769 import std.file; 3770 loadGlobalLinkReferences(readText(arg)); 3771 } 3772 3773 string[] generateFiles; 3774 foreach (arg; args) generateFiles ~= scanFiles(arg); 3775 /* 3776 foreach(argIdx, arg; args) { 3777 if(arg != "-" && std.file.isDir(arg)) 3778 foreach(string name; std.file.dirEntries(arg, "*.d", std.file.SpanMode.breadth)) 3779 generateFiles ~= name; 3780 else 3781 generateFiles ~= arg; 3782 } 3783 */ 3784 args = generateFiles; 3785 //{ import std.stdio; foreach (fn; args) writeln(fn); } assert(0); 3786 3787 3788 // Process them all first so name-lookups have more chance of working 3789 foreach(argIdx, arg; preloadArgs) { 3790 if(std.file.isDir(arg)) { 3791 foreach(string name; std.file.dirEntries(arg, "*.d", std.file.SpanMode.breadth)) { 3792 bool g = false; 3793 if(locateSymbol is null) 3794 foreach(idx, a; args) { 3795 if(a == name) { 3796 g = true; 3797 args[idx] = args[$-1]; 3798 args = args[0 .. $-1]; 3799 break; 3800 } 3801 } 3802 3803 process(name, g); 3804 } 3805 } else { 3806 bool g = false; 3807 3808 if(locateSymbol is null) 3809 foreach(idx, a; args) { 3810 if(a == arg) { 3811 g = true; 3812 args[idx] = args[$-1]; 3813 args = args[0 .. $-1]; 3814 break; 3815 } 3816 } 3817 3818 process(arg, g); 3819 } 3820 } 3821 3822 foreach(argIdx, arg; args) { 3823 process(arg, locateSymbol is null ? true : false); 3824 } 3825 3826 if(locateSymbol !is null) { 3827 auto decl = moduleDecls[0].lookupName(locateSymbol); 3828 if(decl is null) 3829 writeln("not found ", locateSymbol); 3830 else 3831 writeln(decl.lineNumber); 3832 return; 3833 } 3834 3835 // create dummy packages for those not found in the source 3836 // this makes linking far more sane, without requiring package.d 3837 // everywhere (though I still strongly recommending you write them!) 3838 // I'm using for instead of foreach so I can append in the loop 3839 // and keep it going 3840 for(size_t i = 0; i < moduleDecls.length; i++ ) { 3841 auto decl = moduleDecls[i]; 3842 auto pkg = decl.packageName; 3843 if(decl.name == "index") 3844 continue; // avoid infinite recursion 3845 if(pkg is null) 3846 pkg = "index";//continue; // to create an index.html listing all top level things 3847 synchronized(modulesByNameMonitor) 3848 if(pkg !in modulesByName) { 3849 writeln("Making FAKE package for ", pkg); 3850 config.fileName = "dummy"; 3851 auto b = cast(ubyte[]) (`/++ 3852 +/ module `~pkg~`; `); 3853 auto tokens = getTokensForParser(b, config, &stringCache); 3854 auto m = parseModule(tokens, "dummy"); 3855 auto sweet = new Looker(b, "dummy"); 3856 sweet.visit(m); 3857 3858 auto mod = cast(ModuleDecl) sweet.root; 3859 3860 mod.fakeDecl = true; 3861 3862 moduleDecls ~= mod; 3863 modulesByName[pkg] = mod; 3864 3865 // only generate a fake one if the real one isn't already there 3866 // like perhaps the real one was generated before but just not loaded 3867 // this time. 3868 if(!std.file.exists(outputDirectory ~ mod.link)) 3869 moduleDeclsGenerate ~= mod; 3870 } 3871 } 3872 3873 // add modules to their packages, if possible 3874 foreach(decl; moduleDecls) { 3875 auto pkg = decl.packageName; 3876 if(decl.name == "index") continue; // avoid infinite recursion 3877 if(pkg.length == 0) { 3878 //continue; 3879 pkg = "index"; 3880 } 3881 synchronized(modulesByNameMonitor) 3882 if(auto a = pkg in modulesByName) { 3883 (*a).addChild(decl); 3884 } else assert(0, pkg ~ " " ~ decl.toString); // it should have make a fake package above 3885 } 3886 3887 3888 version(with_http_server) { 3889 import arsd.cgi; 3890 3891 void serveFiles(Cgi cgi) { 3892 3893 import std.file; 3894 3895 string file = cgi.requestUri; 3896 3897 auto slash = file.lastIndexOf("/"); 3898 bool wasSource = file.indexOf("source/") != -1; 3899 3900 if(slash != -1) 3901 file = file[slash + 1 .. $]; 3902 3903 if(wasSource) 3904 file = "source/" ~ file; 3905 3906 if(file == "style.css") { 3907 cgi.setResponseContentType("text/css"); 3908 cgi.write(readText(findStandardFile("style.css")), true); 3909 return; 3910 } else if(file == "script.js") { 3911 cgi.setResponseContentType("text/javascript"); 3912 cgi.write(readText(findStandardFile("script.js")), true); 3913 return; 3914 } else if(file == "search-docs.js") { 3915 cgi.setResponseContentType("text/javascript"); 3916 cgi.write(readText(findStandardFile("search-docs.js")), true); 3917 return; 3918 } else { 3919 if(file.length == 0) { 3920 if("index" !in pseudoFiles) 3921 writeHtml(modulesByName["index"], true, false, headerTitle, headerLinksParsed); 3922 cgi.write(pseudoFiles["index"], true); 3923 return; 3924 } else { 3925 auto of = file; 3926 3927 if(file !in pseudoFiles) { 3928 ModuleDecl* declPtr; 3929 file = file[0 .. $-5]; // cut off ".html" 3930 if(wasSource) { 3931 file = file["source/".length .. $]; 3932 file = file[0 .. $-2]; // cut off ".d" 3933 } 3934 while((declPtr = file in modulesByName) is null) { 3935 auto idx = file.lastIndexOf("."); 3936 if(idx == -1) 3937 break; 3938 file = file[0 .. idx]; 3939 } 3940 3941 if(declPtr !is null) { 3942 if(wasSource) { 3943 generateAnnotatedSource(*declPtr, false); 3944 } else { 3945 if(!(*declPtr).alreadyGenerated) 3946 writeHtml(*declPtr, true, false, headerTitle, headerLinksParsed); 3947 (*declPtr).alreadyGenerated = true; 3948 } 3949 } 3950 } 3951 3952 file = of; 3953 3954 if(file in pseudoFiles) 3955 cgi.write(pseudoFiles[file], true); 3956 else { 3957 cgi.setResponseStatus("404 Not Found"); 3958 cgi.write("404 " ~ file, true); 3959 } 3960 return; 3961 } 3962 } 3963 3964 cgi.setResponseStatus("404 Not Found"); 3965 cgi.write("404", true); 3966 } 3967 3968 mixin CustomCgiMain!(Cgi, serveFiles); 3969 3970 processPoolSize = 1; 3971 3972 usePseudoFiles = true; 3973 3974 writeln("\n\nListening on http port 8999...."); 3975 3976 cgiMainImpl(["server", "--port", "8999"]); 3977 return; 3978 } 3979 3980 import std.parallelism; 3981 if(jobs > 1) 3982 defaultPoolThreads = jobs; 3983 3984 if(makeHtml) { 3985 bool[string] alreadyTried; 3986 3987 void helper(size_t idx, ModuleDecl decl) { 3988 //if(decl.parent && moduleDeclsGenerate.canFind(decl.parent)) 3989 //continue; // it will be written in the list of children. actually i want to do it all here. 3990 3991 // FIXME: make search index in here if we can 3992 if(!skipExisting || !std.file.exists(outputDirectory ~ decl.link(true) ~ (gzip ?".gz":""))) { 3993 if(decl.name in alreadyTried) 3994 return; 3995 alreadyTried[decl.name] = true; 3996 writeln("Generating HTML for ", decl.name); 3997 writeHtml(decl, true, gzip, headerTitle, headerLinksParsed); 3998 } 3999 4000 writeln(idx + 1, "/", moduleDeclsGenerate.length, " completed"); 4001 } 4002 4003 if(jobs == 1) 4004 foreach(idx, decl; moduleDeclsGenerate) { 4005 helper(idx, decl); 4006 } 4007 else 4008 foreach(idx, decl; parallel(moduleDeclsGenerate)) { 4009 helper(idx, decl); 4010 } 4011 } 4012 4013 if(makeSearchIndex) { 4014 4015 // we need the listing and the search index 4016 FileProxy index; 4017 int id; 4018 4019 static import std.file; 4020 4021 // write out the landing page for JS search, 4022 // see the comment in the source of that html 4023 // for more details 4024 writeFile(outputDirectory ~ "search-docs.html", import("search-docs.html"), gzip); 4025 4026 4027 // the search index is a HTML page containing some script 4028 // and the index XML. See the source of search-docs.js for more info. 4029 index = FileProxy(buildPath(outputDirectory, "search-results.html"), gzip); 4030 4031 auto skeletonDocument = new Document(); 4032 skeletonDocument.parseUtf8(std.file.readText(skeletonFile), true, true); 4033 auto skeletonText = skeletonDocument.toString(); 4034 4035 auto idx = skeletonText.indexOf("</body>"); 4036 if(idx == -1) throw new Exception("skeleton missing body element"); 4037 4038 // write out the skeleton... 4039 index.writeln(skeletonText[0 .. idx]); 4040 4041 // and then the data container for the xml 4042 index.writeln(`<script type="text/xml" id="search-index-container">`); 4043 4044 index.writeln("<adrdox>"); 4045 4046 index.writeln("<listing>"); 4047 foreach(decl; moduleDeclsGenerate) { 4048 writeln("Listing ", decl.name); 4049 4050 writeIndexXml(decl, index, id); 4051 } 4052 index.writeln("</listing>"); 4053 4054 id = 0; 4055 4056 // also making the search index 4057 foreach(decl; moduleDeclsGenerate) { 4058 if(decl.fakeDecl) 4059 continue; 4060 writeln("Generating search for ", decl.name); 4061 4062 generateSearchIndex(decl, id); 4063 } 4064 4065 writeln("Writing search..."); 4066 4067 index.writeln("<index>"); 4068 foreach(term, arr; searchTerms) { 4069 index.write("<term value=\""~xmlEntitiesEncode(term)~"\">"); 4070 foreach(item; arr) { 4071 index.write("<result decl=\""~to!string(item.declId)~"\" score=\""~to!string(item.score)~"\" />"); 4072 } 4073 index.writeln("</term>"); 4074 } 4075 index.writeln("</index>"); 4076 index.writeln("</adrdox>"); 4077 4078 // finish the container 4079 index.writeln("</script>"); 4080 4081 // write the script that runs the search 4082 index.writeln("<script src=\"search-docs.js\"></script>"); 4083 4084 // and close the skeleton 4085 index.writeln("</body></html>"); 4086 index.close(); 4087 } 4088 4089 4090 //import std.stdio; 4091 //writeln("press any key to continue"); 4092 //readln(); 4093 } 4094 4095 struct FileProxy { 4096 import std.zlib; 4097 File f; // it will inherit File's refcounting 4098 Compress compress; // and compress is gc'd anyway so copying the ref means same object! 4099 bool gzip; 4100 4101 this(string filename, bool gzip) { 4102 f = File(filename ~ (gzip ? ".gz" : ""), gzip ? "wb" : "wt"); 4103 if(gzip) 4104 compress = new Compress(HeaderFormat.gzip); 4105 this.gzip = gzip; 4106 } 4107 4108 void writeln(string s) { 4109 if(gzip) 4110 f.rawWrite(compress.compress(s ~ "\n")); 4111 else 4112 f.writeln(s); 4113 } 4114 4115 void write(string s) { 4116 if(gzip) 4117 f.rawWrite(compress.compress(s)); 4118 else 4119 f.write(s); 4120 } 4121 4122 void close() { 4123 if(gzip) 4124 f.rawWrite(compress.flush()); 4125 f.close(); 4126 } 4127 } 4128 4129 struct SearchResult { 4130 int declId; 4131 int score; 4132 } 4133 4134 string[] splitIdentifier(string name) { 4135 string[] ret; 4136 4137 bool isUpper(dchar c) { 4138 return c >= 'A' && c <= 'Z'; 4139 } 4140 4141 bool breakOnNext; 4142 dchar lastChar; 4143 foreach(dchar ch; name) { 4144 if(ch == '_') { 4145 breakOnNext = true; 4146 continue; 4147 } 4148 if(breakOnNext || ret.length == 0 || (isUpper(ch) && !isUpper(lastChar))) { 4149 if(ret.length == 0 || ret[$-1].length) 4150 ret ~= ""; 4151 } 4152 breakOnNext = false; 4153 ret[$-1] ~= ch; 4154 lastChar = ch; 4155 } 4156 4157 return ret; 4158 } 4159 4160 SearchResult[][string] searchTerms; 4161 4162 void generateSearchIndex(Decl decl, ref int id) { 4163 if(!decl.docsShouldBeOutputted) 4164 return; 4165 4166 // this needs to match the id in index.xml! 4167 auto tid = ++id; 4168 4169 // exact match on FQL is always a great match 4170 searchTerms[decl.fullyQualifiedName] ~= SearchResult(tid, 50); 4171 4172 if(decl.name != "this") { 4173 // exact match on specific name is worth something too 4174 searchTerms[decl.name] ~= SearchResult(tid, 25); 4175 4176 if(decl.isModule) { 4177 // module names like std.stdio should match stdio strongly, 4178 // and std is ok too. I will break them by dot and give diminsihing 4179 // returns. 4180 int score = 25; 4181 foreach_reverse(part; decl.name.split(".")) { 4182 searchTerms[part] ~= SearchResult(tid, score); 4183 score -= 10; 4184 if(score <= 0) 4185 break; 4186 } 4187 } 4188 4189 // and so is fuzzy match 4190 if(decl.name != decl.name.toLower) 4191 searchTerms[decl.name.toLower] ~= SearchResult(tid, 15); 4192 4193 // and so is partial word match 4194 auto splitNames = splitIdentifier(decl.name); 4195 if(splitNames.length) { 4196 foreach(name; splitNames) { 4197 searchTerms[name] ~= SearchResult(tid, 6); 4198 if(name != name.toLower) 4199 searchTerms[name.toLower] ~= SearchResult(tid, 3); 4200 } 4201 } 4202 } 4203 4204 // and we want to match parent names, though worth less. 4205 Decl parent = decl.parent; 4206 while(parent !is null) { 4207 searchTerms[parent.name] ~= SearchResult(tid, 5); 4208 if(parent.name != parent.name.toLower) 4209 searchTerms[parent.name.toLower] ~= SearchResult(tid, 2); 4210 4211 auto splitNames = splitIdentifier(parent.name); 4212 if(splitNames.length) { 4213 foreach(name; splitNames) { 4214 searchTerms[name] ~= SearchResult(tid, 3); 4215 if(name != name.toLower) 4216 searchTerms[name.toLower] ~= SearchResult(tid, 2); 4217 } 4218 } 4219 4220 4221 parent = parent.parent; 4222 } 4223 4224 Document document; 4225 //if(decl.fullyQualifiedName in generatedDocuments) 4226 //document = generatedDocuments[decl.fullyQualifiedName]; 4227 //else 4228 document = writeHtml(decl, false, false, null, null); 4229 assert(document !is null); 4230 4231 // FIXME: pulling this from the generated html is a bit inefficient. 4232 4233 // tags are worth a lot 4234 foreach(tag; document.querySelectorAll(".tag")) 4235 searchTerms[tag.attrs.name] ~= SearchResult(tid, to!int(tag.attrs.value.length ? tag.attrs.value : "0")); 4236 4237 // and other names that are referenced are worth quite a bit. 4238 foreach(tag; document.querySelectorAll(".xref")) 4239 searchTerms[tag.innerText] ~= SearchResult(tid, tag.hasClass("parent-class") ? 10 : 5); 4240 foreach(tag; document.querySelectorAll("a[data-ident][title]")) 4241 searchTerms[tag.dataset.ident] ~= SearchResult(tid, 3); 4242 foreach(tag; document.querySelectorAll("a.hid[title]")) 4243 searchTerms[tag.innerText] ~= SearchResult(tid, 3); 4244 4245 // and full-text search 4246 import ps = PorterStemmer; 4247 ps.PorterStemmer s; 4248 bool[const(char)[]] wordsUsed; 4249 foreach(tag; document.querySelectorAll(".documentation-comment")) { 4250 foreach(word; getWords(tag.innerText)) { 4251 auto w = s.stem(word.toLower); 4252 if(w.isIrrelevant()) 4253 continue; 4254 if(w in wordsUsed) 4255 continue; 4256 wordsUsed[w] = true; 4257 searchTerms[s.stem(word.toLower)] ~= SearchResult(tid, 1); 4258 } 4259 } 4260 4261 foreach(child; decl.children) 4262 generateSearchIndex(child, id); 4263 } 4264 4265 bool isIrrelevant(in char[] s) { 4266 foreach(w; irrelevantWordList) 4267 if(w == s) 4268 return true; 4269 return false; 4270 } 4271 4272 // These are common words in English, which I'm generally 4273 // ignoring because they happen so often that they probably 4274 // aren't relevant keywords 4275 import std.meta; 4276 alias irrelevantWordList = AliasSeq!( 4277 "the", 4278 "of", 4279 "and", 4280 "a", 4281 "to", 4282 "in", 4283 "is", 4284 "you", 4285 "that", 4286 "it", 4287 "he", 4288 "was", 4289 "for", 4290 "on", 4291 "are", 4292 "as", 4293 "with", 4294 "his", 4295 "they", 4296 "I", 4297 "at", 4298 "be", 4299 "this", 4300 "have", 4301 "from", 4302 "or", 4303 "one", 4304 "had", 4305 "by", 4306 "word", 4307 "but", 4308 "not", 4309 "what", 4310 "all", 4311 "were", 4312 "we", 4313 "when", 4314 "your", 4315 "can", 4316 "said", 4317 "there", 4318 "use", 4319 "an", 4320 "each", 4321 "which", 4322 "she", 4323 "do", 4324 "how", 4325 "their", 4326 "if", 4327 "will", 4328 "up", 4329 "other", 4330 "about", 4331 "out", 4332 "many", 4333 "then", 4334 "them", 4335 "these", 4336 "so", 4337 "some", 4338 "her", 4339 "would", 4340 "make", 4341 "like", 4342 "him", 4343 "into", 4344 "time", 4345 "has", 4346 "look", 4347 "two", 4348 "more", 4349 "write", 4350 "go", 4351 "see", 4352 "number", 4353 "no", 4354 "way", 4355 "could", 4356 "people", 4357 "my", 4358 "than", 4359 "first", 4360 "water", 4361 "been", 4362 "call", 4363 "who", 4364 "its", 4365 "now", 4366 "find", 4367 "long", 4368 "down", 4369 "day", 4370 "did", 4371 "get", 4372 "come", 4373 "made", 4374 "may", 4375 "part", 4376 ); 4377 4378 string[] getWords(string text) { 4379 string[] words; 4380 string currentWord; 4381 4382 import std.uni; 4383 foreach(dchar ch; text) { 4384 if(!isAlpha(ch)) { 4385 if(currentWord.length) 4386 words ~= currentWord; 4387 currentWord = null; 4388 } else { 4389 currentWord ~= ch; 4390 } 4391 } 4392 4393 return words; 4394 } 4395 4396 import std.stdio : File; 4397 4398 void writeIndexXml(Decl decl, FileProxy index, ref int id) { 4399 //import std.stdio;writeln(decl.fullyQualifiedName, " ", decl.isPrivate, " ", decl.isDocumented); 4400 if(!decl.docsShouldBeOutputted) 4401 return; 4402 4403 auto cc = decl.parsedDocComment; 4404 4405 // the id needs to match the search index! 4406 index.write("<decl id=\"" ~ to!string(++id) ~ "\" type=\""~decl.declarationType~"\">"); 4407 4408 index.write("<name>" ~ xmlEntitiesEncode(decl.name) ~ "</name>"); 4409 index.write("<desc>" ~ xmlEntitiesEncode(formatDocumentationComment(cc.ddocSummary, decl)) ~ "</desc>"); 4410 index.write("<link>" ~ xmlEntitiesEncode(decl.link) ~ "</link>"); 4411 4412 foreach(child; decl.children) 4413 writeIndexXml(child, index, id); 4414 4415 index.write("</decl>"); 4416 } 4417 4418 string pluralize(string word, int count = 2, string pluralWord = null) { 4419 if(word.length == 0) 4420 return word; 4421 4422 if(count == 1) 4423 return word; // it isn't actually plural 4424 4425 if(pluralWord !is null) 4426 return pluralWord; 4427 4428 switch(word[$ - 1]) { 4429 case 's': 4430 case 'a', 'i', 'o', 'u': 4431 return word ~ "es"; 4432 case 'f': 4433 return word[0 .. $-1] ~ "ves"; 4434 case 'y': 4435 return word[0 .. $-1] ~ "ies"; 4436 default: 4437 return word ~ "s"; 4438 } 4439 } 4440 4441 Html toLinkedHtml(T)(const T t, Decl decl) { 4442 import dparse.formatter; 4443 string s; 4444 struct Foo { 4445 void put(in char[] a) { 4446 s ~= a; 4447 } 4448 } 4449 Foo output; 4450 auto f = new MyFormatter!(typeof(output))(output); 4451 f.format(t); 4452 4453 return Html(linkUpHtml(s, decl)); 4454 } 4455 4456 string linkUpHtml(string s, Decl decl, string base = "", bool linkToSource = false) { 4457 auto document = new Document("<root>" ~ s ~ "</root>", true, true); 4458 4459 // additional cross referencing we weren't able to do at lower level 4460 foreach(ident; document.querySelectorAll("*:not(a) [data-ident]:not(:has(a)), .hid")) { 4461 // since i modify the tree in the loop, i recheck that we still match the selector 4462 if(ident.parentNode is null) 4463 continue; 4464 if(ident.tagName == "a" || (ident.parentNode && ident.parentNode.tagName == "a")) 4465 continue; 4466 string i = ident.hasAttribute("data-ident") ? ident.dataset.ident : ident.innerText; 4467 4468 auto n = ident.nextSibling; 4469 while(n && n.nodeValue == ".") { 4470 i ~= "."; 4471 auto txt = n; 4472 n = n.nextSibling; // the span, ideally 4473 if(n is null) 4474 break; 4475 if(n && (n.hasAttribute("data-ident") || n.hasClass("hid"))) { 4476 txt.removeFromTree(); 4477 i ~= n.hasAttribute("data-ident") ? n.dataset.ident : n.innerText; 4478 auto span = n; 4479 n = n.nextSibling; 4480 span.removeFromTree; 4481 } 4482 } 4483 4484 //ident.dataset.ident = i; 4485 ident.innerText = i; 4486 4487 auto found = decl.lookupName(i); 4488 string hash; 4489 4490 if(found is null) { 4491 auto lastPieceIdx = i.lastIndexOf("."); 4492 if(lastPieceIdx != -1) { 4493 found = decl.lookupName(i[0 .. lastPieceIdx]); 4494 if(found) 4495 hash = "#" ~ i[lastPieceIdx + 1 .. $]; 4496 } 4497 } 4498 4499 if(found) { 4500 auto overloads = found.getImmediateDocumentedOverloads(); 4501 if(overloads.length) 4502 found = overloads[0]; 4503 } 4504 4505 void linkToDoc() { 4506 if(found && found.docsShouldBeOutputted) { 4507 ident.attrs.title = found.fullyQualifiedName; 4508 ident.tagName = "a"; 4509 ident.href = base ~ found.link ~ hash; 4510 } 4511 } 4512 4513 if(linkToSource) { 4514 if(found && linkToSource && found.parentModule) { 4515 ident.attrs.title = found.fullyQualifiedName; 4516 ident.tagName = "a"; 4517 ident.href = found.parentModule.name ~ ".d.html#L" ~ to!string(found.lineNumber); 4518 } 4519 } else { 4520 linkToDoc(); 4521 } 4522 } 4523 4524 return document.root.innerHTML; 4525 } 4526 4527 4528 Html toHtml(T)(const T t) { 4529 import dparse.formatter; 4530 string s; 4531 struct Foo { 4532 void put(in char[] a) { 4533 s ~= a; 4534 } 4535 } 4536 Foo output; 4537 auto f = new Formatter!(typeof(output))(output); 4538 f.format(t); 4539 4540 return Html("<tt class=\"highlighted\">"~highlight(s)~"</tt>"); 4541 } 4542 4543 string toText(T)(const T t) { 4544 import dparse.formatter; 4545 string s; 4546 struct Foo { 4547 void put(in char[] a) { 4548 s ~= a; 4549 } 4550 } 4551 Foo output; 4552 auto f = new Formatter!(typeof(output))(output); 4553 f.format(t); 4554 4555 return s; 4556 } 4557 4558 string toId(string txt) { 4559 string id; 4560 bool justSawSpace; 4561 foreach(ch; txt) { 4562 if(ch < 127) { 4563 if(ch >= 'A' && ch <= 'Z') { 4564 id ~= ch + 32; 4565 } else if(ch == ' ') { 4566 if(!justSawSpace) 4567 id ~= '-'; 4568 } else { 4569 id ~= ch; 4570 } 4571 } else { 4572 id ~= ch; 4573 } 4574 justSawSpace = ch == ' '; 4575 } 4576 return id.strip; 4577 } 4578 4579 4580 4581 /* 4582 This file contains code from https://github.com/economicmodeling/harbored/ 4583 4584 Those portions are Copyright 2014 Economic Modeling Specialists, Intl., 4585 written by Brian Schott, made available under the following license: 4586 4587 Boost Software License - Version 1.0 - August 17th, 2003 4588 4589 Permission is hereby granted, free of charge, to any person or organization 4590 obtaining a copy of the software and accompanying documentation covered by 4591 this license (the "Software") to use, reproduce, display, distribute, 4592 execute, and transmit the Software, and to prepare derivative works of the 4593 Software, and to permit third-parties to whom the Software is furnished to 4594 do so, all subject to the following: 4595 4596 The copyright notices in the Software and this entire statement, including 4597 the above license grant, this restriction and the following disclaimer, 4598 must be included in all copies of the Software, in whole or in part, and 4599 all derivative works of the Software, unless such copies or derivative 4600 works are solely in the form of machine-executable object code generated by 4601 a source language processor. 4602 4603 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 4604 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 4605 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 4606 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 4607 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 4608 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 4609 DEALINGS IN THE SOFTWARE. 4610 */