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