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