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