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