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 if (!thing) 1533 // else {} 1534 continue; 1535 1536 Element dl; 1537 foreach(child; thing.children) { 1538 if(mi.isPrivate && !child.isExplicitlyNonPrivate) 1539 continue; 1540 1541 if (!child.docsShouldBeOutputted) 1542 continue; 1543 1544 if(dl is null) { 1545 if(firstMitd) { 1546 auto h2 = content.addChild("h2", "Mixed In Members"); 1547 h2.id = "mixed-in-members"; 1548 firstMitd = false; 1549 } 1550 1551 //mi.name 1552 1553 string sp; 1554 MyOutputRange or = MyOutputRange(&sp); 1555 mi.getSimplifiedPrototype(or); 1556 auto h3 = content.addChild("h3", Html("From " ~ sp)); 1557 1558 dl = content.addChild("dl").addClass("member-list native"); 1559 } 1560 handleChildDecl(dl, child); 1561 1562 if(!minimalDescent) 1563 writeHtml(child, forReal, gzip, headerTitle, headerLinks, true); 1564 } 1565 } 1566 } 1567 1568 auto irList = decl.inheritsFrom; 1569 if(irList.length) { 1570 auto h2 = content.addChild("h2", "Inherited Members"); 1571 h2.id = "inherited-members"; 1572 1573 bool hasAnyListing = false; 1574 1575 foreach(ir; irList) { 1576 if(ir.decl is null) continue; 1577 auto h3 = content.addChild("h3", "From " ~ ir.decl.name); 1578 h3.id = "inherited-from-" ~ ir.decl.fullyQualifiedName; 1579 auto dl = content.addChild("dl").addClass("member-list inherited"); 1580 bool hadListing = false; 1581 foreach(child; ir.decl.children) { 1582 if(!child.docsShouldBeOutputted) 1583 continue; 1584 if(!child.isConstructor()) { 1585 handleChildDecl(dl, child); 1586 hadListing = true; 1587 hasAnyListing = true; 1588 } 1589 } 1590 1591 if(!hadListing) { 1592 h3.removeFromTree(); 1593 dl.removeFromTree(); 1594 } 1595 } 1596 1597 if(!hasAnyListing) 1598 h2.removeFromTree(); 1599 } 1600 1601 decl.addSupplementalData(content); 1602 1603 s = null; 1604 1605 if(auto fd = cast(FunctionDeclaration) decl.getAstNode()) 1606 comment.writeDetails(output, fd, decl.getProcessedUnittests()); 1607 else if(auto fd = cast(Constructor) decl.getAstNode()) 1608 comment.writeDetails(output, fd, decl.getProcessedUnittests()); 1609 else if(auto fd = cast(TemplateDeclaration) decl.getAstNode()) 1610 comment.writeDetails(output, fd, decl.getProcessedUnittests()); 1611 else if(auto fd = cast(EponymousTemplateDeclaration) decl.getAstNode()) 1612 comment.writeDetails(output, fd, decl.getProcessedUnittests()); 1613 else if(auto fd = cast(StructDeclaration) decl.getAstNode()) 1614 comment.writeDetails(output, fd, decl.getProcessedUnittests()); 1615 else if(auto fd = cast(ClassDeclaration) decl.getAstNode()) 1616 comment.writeDetails(output, fd, decl.getProcessedUnittests()); 1617 else if(auto fd = cast(AliasDecl) decl) { 1618 if(fd.initializer) 1619 comment.writeDetails(output, fd.initializer, decl.getProcessedUnittests()); 1620 else 1621 comment.writeDetails(output, decl, decl.getProcessedUnittests()); 1622 } else { 1623 //import std.stdio; writeln(decl.getAstNode); 1624 comment.writeDetails(output, decl, decl.getProcessedUnittests()); 1625 } 1626 1627 content.addChild("div", Html(s)); 1628 1629 if(forReal) { 1630 auto nav = document.requireElementById("page-nav"); 1631 1632 Decl[] navArray; 1633 string[string] inNavArray; 1634 if(decl.parent) { 1635 auto iterate = decl.parent.children; 1636 1637 if(!decl.isModule) { 1638 if(auto emc = decl.parent.eponymousModuleChild()) { 1639 // we are an only child of a module, show the module's nav instead 1640 if(decl.parent.parent !is null) 1641 iterate = decl.parent.parent.children; 1642 } 1643 } 1644 1645 foreach(child; iterate) { 1646 if(cast(ImportDecl) child) continue; // do not document public imports here, they belong only on the inside 1647 if(child.docsShouldBeOutputted) { 1648 // strip overloads from sidebar 1649 if(child.name !in inNavArray) { 1650 navArray ~= child; 1651 inNavArray[child.name] = ""; 1652 } 1653 } 1654 } 1655 } else { 1656 /+ commented pending removal 1657 // this is for building the module nav when doing an incremental 1658 // rebuild. It loads the index.xml made with the special option below. 1659 static bool attemptedXmlLoad; 1660 static ModuleDecl[] indexedModules; 1661 if(!attemptedXmlLoad) { 1662 import std.file; 1663 if(std.file.exists("index.xml")) { 1664 auto idx = new XmlDocument(readText("index.xml")); 1665 foreach(d; idx.querySelectorAll("listing > decl")) 1666 indexedModules ~= new ModuleDecl(d.requireSelector("name").innerText); 1667 } 1668 attemptedXmlLoad = true; 1669 } 1670 1671 auto tm = cast(ModuleDecl) decl; 1672 if(tm !is null) 1673 foreach(im; indexedModules) 1674 if(im.packageName == tm.packageName) 1675 navArray ~= im; 1676 +/ 1677 } 1678 1679 { 1680 auto p = decl.parent; 1681 while(p) { 1682 // cut off package names that would be repeated 1683 auto name = (p.isModule && p.parent) ? lastDotOnly(p.name) : p.name; 1684 if(name == "index" && p.fakeDecl) 1685 break; 1686 nav.prependChild(new TextNode(" ")); 1687 nav.prependChild(Element.make("a", name, p.link(true))).addClass("parent"); 1688 p = p.parent; 1689 } 1690 } 1691 1692 import std.algorithm; 1693 1694 sort!sorter(navArray); 1695 1696 Element list; 1697 1698 string lastType; 1699 foreach(item; navArray) { 1700 if(item.declarationType != lastType) { 1701 nav.addChild("span", pluralize(item.declarationType)).addClass("type-separator"); 1702 list = nav.addChild("ul"); 1703 lastType = item.declarationType; 1704 } 1705 1706 string name; 1707 if(item.isArticle) { 1708 auto mod = cast(ModuleDecl) item; 1709 name = mod.justDocsTitle; 1710 } else { 1711 // cut off package names that would be repeated 1712 name = (item.isModule && item.parent) ? lastDotOnly(item.name) : item.name; 1713 } 1714 auto n = list.addChild("li").addChild("a", name, item.link).addClass(item.declarationType.replace(" ", "-")); 1715 if(item.name == decl.name || name == decl.name) 1716 n.addClass("current"); 1717 } 1718 1719 if(justDocs) { 1720 if(auto d = document.querySelector("#details")) 1721 d.removeFromTree; 1722 } 1723 1724 auto toc = Element.make("div"); 1725 toc.id = "table-of-contents"; 1726 auto current = toc; 1727 int lastLevel; 1728 tree: foreach(header; document.root.tree) { 1729 int level; 1730 switch(header.tagName) { 1731 case "h2": 1732 level = 2; 1733 break; 1734 case "h3": 1735 level = 3; 1736 break; 1737 case "h4": 1738 level = 4; 1739 break; 1740 case "h5:": 1741 level = 5; 1742 break; 1743 case "h6": 1744 level = 6; 1745 break; 1746 default: break; 1747 } 1748 1749 if(level == 0) continue; 1750 1751 bool addToIt = true; 1752 if(header.hasClass("hide-from-toc")) 1753 addToIt = false; 1754 1755 Element addTo; 1756 if(addToIt) { 1757 auto parentCheck = header; 1758 while(parentCheck) { 1759 if(parentCheck.hasClass("adrdox-sample")) 1760 continue tree; 1761 parentCheck = parentCheck.parentNode; 1762 } 1763 1764 if(level > lastLevel) { 1765 current = current.addChild("ol"); 1766 current.addClass("heading-level-" ~ to!string(level)); 1767 } else if(level < lastLevel) { 1768 while(current && !current.hasClass("heading-level-" ~ to!string(level))) 1769 current = current.parentNode; 1770 if(current is null) { 1771 import std.stdio; 1772 writeln("WARNING: TOC broken on " ~ decl.name); 1773 goto skip_toc; 1774 } 1775 assert(current !is null); 1776 } 1777 1778 lastLevel = level; 1779 addTo = current; 1780 if(addTo.tagName != "ol") 1781 addTo = addTo.parentNode; 1782 } 1783 1784 if(!header.hasAttribute("id")) 1785 header.attrs.id = toId(header.innerText); 1786 if(header.querySelector(" > *") is null) { 1787 auto selfLink = Element.make("a", header.innerText, "#" ~ header.attrs.id); 1788 selfLink.addClass("header-anchor"); 1789 header.innerHTML = selfLink.toString(); 1790 } 1791 1792 if(addToIt) 1793 addTo.addChild("li", Element.make("a", header.innerText, "#" ~ header.attrs.id)); 1794 } 1795 1796 if(auto d = document.querySelector("#more-link")) { 1797 if(document.querySelectorAll(".user-header:not(.hide-from-toc)").length > 2) 1798 d.replaceWith(toc); 1799 } 1800 1801 skip_toc: {} 1802 1803 if(auto a = document.querySelector(".annotated-prototype")) 1804 outer: foreach(c; a.querySelectorAll(".parameters-list")) { 1805 auto p = c.parentNode; 1806 while(p) { 1807 if(p.hasClass("lambda-expression")) 1808 continue outer; 1809 p = p.parentNode; 1810 } 1811 c.addClass("toplevel"); 1812 } 1813 1814 // for line numbering 1815 foreach(pre; document.querySelectorAll("pre.highlighted, pre.block-code[data-language!=\"\"]")) { 1816 addLineNumbering(pre); 1817 } 1818 1819 string overloadLink; 1820 string declLink = decl.link(true, &overloadLink); 1821 1822 if(declLink == ".html") 1823 return document; 1824 1825 if(usePseudoFiles) { 1826 pseudoFiles[declLink] = document.toString(); 1827 if(overloadLink.length && overloadLink != ".html") 1828 pseudoFiles[overloadLink] = redirectToOverloadHtml(declLink); 1829 } else { 1830 writeFile(outputFilePath(declLink), document.toString(), gzip); 1831 if(overloadLink.length && overloadLink != ".html") 1832 writeFile(outputFilePath(overloadLink), redirectToOverloadHtml(declLink), gzip); 1833 } 1834 1835 import std.stdio; 1836 writeln("WRITTEN TO ", declLink); 1837 } 1838 1839 return document; 1840 } 1841 1842 string redirectToOverloadHtml(string what) { 1843 return `<html class="overload-redirect"><script>location.href = '`~what~`';</script> <a href="`~what~`">Continue to overload</a></html>`; 1844 } 1845 1846 void addLineNumbering(Element pre, bool id = false) { 1847 if(pre.hasClass("with-line-wrappers")) 1848 return; 1849 string html; 1850 int count; 1851 foreach(idx, line; pre.innerHTML.splitLines) { 1852 auto num = to!string(idx + 1); 1853 auto href = "L"~num; 1854 if(id) 1855 html ~= "<a class=\"br\""~(id ? " id=\""~href~"\"" : "")~" href=\"#"~href~"\">"~num~" </a>"; 1856 else 1857 html ~= "<span class=\"br\">"~num~" </span>"; 1858 html ~= line; 1859 html ~= "\n"; 1860 count++; 1861 } 1862 if(count < 55) 1863 return; // no point cluttering the display with the sample is so small you can eyeball it instantly anyway 1864 pre.innerHTML = html.stripRight; 1865 pre.addClass("with-line-wrappers"); 1866 1867 if(count >= 10000) 1868 pre.addClass("ten-thousand-lines"); 1869 else if(count >= 1000) 1870 pre.addClass("thousand-lines"); 1871 } 1872 1873 string lastDotOnly(string s) { 1874 auto idx = s.lastIndexOf("."); 1875 if(idx == -1) return s; 1876 return s[idx + 1 .. $]; 1877 } 1878 1879 struct InheritanceResult { 1880 Decl decl; // may be null 1881 string plainText; 1882 //const(BaseClass) ast; 1883 } 1884 1885 Decl[] declsByUda(string uda, Decl start = null) { 1886 if(start is null) { 1887 assert(0); // cross-module search not implemented here 1888 } 1889 1890 Decl[] list; 1891 1892 if(start.hasUda(uda)) 1893 list ~= start; 1894 1895 foreach(child; start.children) 1896 list ~= declsByUda(uda, child); 1897 1898 return list; 1899 1900 } 1901 1902 abstract class Decl { 1903 private int databaseId; 1904 1905 bool fakeDecl = false; 1906 bool alreadyGenerated = false; 1907 abstract string name(); 1908 abstract string comment(); 1909 abstract string rawComment(); 1910 abstract string declarationType(); 1911 abstract const(ASTNode) getAstNode(); 1912 abstract int lineNumber(); 1913 1914 //abstract string sourceCode(); 1915 1916 abstract void getAnnotatedPrototype(MyOutputRange); 1917 abstract void getSimplifiedPrototype(MyOutputRange); 1918 1919 final string externNote() { 1920 bool hadABody; 1921 if(auto f = cast(FunctionDecl) this) { 1922 if(f.astNode && f.astNode.functionBody) 1923 hadABody = f.astNode.functionBody.hadABody; 1924 } 1925 1926 if(hadABody) 1927 return ". Be warned that the author may not have intended to support it."; 1928 1929 switch(externWhat) { 1930 case "C": 1931 case "C++": 1932 case "Windows": 1933 case "Objective-C": 1934 return " but is binding to " ~ externWhat ~ ". You might be able to learn more by searching the web for its name."; 1935 case "System": 1936 return " but is binding to an external library. You might be able to learn more by searching the web for its name."; 1937 case null: 1938 default: 1939 return "."; 1940 } 1941 } 1942 1943 DocComment parsedDocComment_; 1944 final @property DocComment parsedDocComment() { 1945 if(parsedDocComment_ is DocComment.init) { 1946 parsedDocComment_ = parseDocumentationComment(this.rawComment().length ? this.comment() : "/++\n$(UNDOCUMENTED Undocumented in source"~externNote~")\n+/", this); 1947 } 1948 return parsedDocComment_; 1949 } 1950 1951 void getAggregatePrototype(MyOutputRange r) { 1952 getSimplifiedPrototype(r); 1953 r.put(";"); 1954 } 1955 1956 /* virtual */ void addSupplementalData(Element) {} 1957 1958 // why is this needed?!?!?!?!? 1959 override int opCmp(Object o) { 1960 return cast(int)cast(void*)this - cast(int)cast(void*)o; 1961 } 1962 1963 Decl parentModule() { 1964 auto p = this; 1965 while(p) { 1966 if(p.isModule()) 1967 return p; 1968 p = p.parent; 1969 } 1970 assert(0); 1971 } 1972 1973 Decl previousSibling() { 1974 if(parent is null) 1975 return null; 1976 1977 Decl prev; 1978 foreach(child; parent.children) { 1979 if(child is this) 1980 return prev; 1981 prev = child; 1982 } 1983 1984 return null; 1985 } 1986 1987 bool isDocumented() { 1988 // this shouldn't be needed anymore cuz the recursive check below does a better job 1989 //if(this.isModule) 1990 //return true; // allow undocumented modules because then it will at least descend into documented children 1991 1992 // skip modules with "internal" because they are usually not meant 1993 // to be publicly documented anyway 1994 { 1995 auto mod = this.parentModule.name; 1996 if(mod.indexOf(".internal") != -1 && !documentInternal) 1997 return false; 1998 } 1999 2000 if(documentUndocumented) 2001 return true; 2002 2003 if(this.rawComment.length) // hack 2004 return this.rawComment.length > 0; // cool, not a hack 2005 2006 // if it has any documented children, we want to pretend this is documented too 2007 // since then it will be possible to navigate to it 2008 foreach(child; children) 2009 if(child.docsShouldBeOutputted()) 2010 return true; 2011 2012 // what follows is all filthy hack 2013 // the C bindings in druntime are not documented, but 2014 // we want them to show up. So I'm gonna hack it. 2015 2016 /* 2017 auto mod = this.parentModule.name; 2018 if(mod.startsWith("core")) 2019 return true; 2020 */ 2021 return false; 2022 } 2023 2024 bool isStatic() { 2025 foreach (a; attributes) { 2026 if(a.attr && a.attr.attribute.type == tok!"static") 2027 return true; 2028 // gshared also implies static (though note that shared does not!) 2029 if(a.attr && a.attr.attribute.type == tok!"__gshared") 2030 return true; 2031 } 2032 2033 return false; 2034 } 2035 2036 bool isPrivate() { 2037 IdType protection; 2038 foreach (a; attributes) { 2039 if (a.attr && isProtection(a.attr.attribute.type)) 2040 protection = a.attr.attribute.type; 2041 } 2042 2043 return protection == tok!"private"; 2044 } 2045 2046 bool isExplicitlyNonPrivate() { 2047 IdType protection; 2048 bool hadOne; 2049 foreach (a; attributes) { 2050 if (a.attr && isProtection(a.attr.attribute.type)) { 2051 protection = a.attr.attribute.type; 2052 hadOne = true; 2053 } 2054 } 2055 2056 return hadOne && protection != tok!"private" && protection != tok!"package"; 2057 2058 } 2059 2060 string externWhat() { 2061 LinkageAttribute attr; 2062 foreach (a; attributes) { 2063 if(a.attr && a.attr.linkageAttribute) 2064 attr = cast() a.attr.linkageAttribute; 2065 } 2066 2067 if(attr is null) 2068 return null; 2069 auto text = attr.identifier.text; 2070 if(text == "Objective") 2071 text = "Objective-C"; 2072 else 2073 text = text ~ (attr.hasPlusPlus ? "++" : ""); 2074 2075 return text; 2076 } 2077 2078 bool docsShouldBeOutputted() { 2079 if(this.rawComment.indexOf("$(NEVER_DOCUMENT)") != -1) 2080 return false; 2081 if((!this.isPrivate || writePrivateDocs) && this.isDocumented) 2082 return true; 2083 else if(this.rawComment.indexOf("$(ALWAYS_DOCUMENT)") != -1) 2084 return true; 2085 return false; 2086 } 2087 2088 final bool hasUda(string name) { 2089 foreach(a; attributes) { 2090 if(a.attr && a.attr.atAttribute && a.attr.atAttribute.identifier.text == name) 2091 return true; 2092 if(a.attr && a.attr.atAttribute && a.attr.atAttribute.argumentList) 2093 foreach(at; a.attr.atAttribute.argumentList.items) { 2094 if(auto e = cast(UnaryExpression) at) 2095 if(auto pe = e.primaryExpression) 2096 if(auto i = pe.identifierOrTemplateInstance) 2097 if(i.identifier.text == name) 2098 return true; 2099 } 2100 2101 } 2102 return false; 2103 } 2104 2105 // FIXME: isFinal and isVirtual 2106 // FIXME: it would be nice to inherit documentation from interfaces too. 2107 2108 bool isProperty() { 2109 return hasUda("property"); // property isn't actually a UDA, but adrdox doesn't care. 2110 } 2111 2112 bool isDeprecated() { 2113 foreach(a; attributes) { 2114 if(a.attr && a.attr.deprecated_) 2115 return true; 2116 } 2117 return false; 2118 } 2119 2120 bool isAggregateMember() { 2121 return parent ? !parent.isModule : false; // FIXME? 2122 } 2123 2124 // does NOT look for aliased overload sets, just ones right in this scope 2125 // includes this in the return (plus eponymous check). Check if overloaded with .length > 1 2126 Decl[] getImmediateDocumentedOverloads() { 2127 Decl[] ret; 2128 2129 if(this.parent !is null) { 2130 foreach(child; this.parent.children) { 2131 if(((cast(ImportDecl) child) is null) && child.name == this.name && child.docsShouldBeOutputted()) 2132 ret ~= child; 2133 } 2134 if(auto t = cast(TemplateDecl) this.parent) 2135 if(this is t.eponymousMember) { 2136 foreach(i; t.getImmediateDocumentedOverloads()) 2137 if(i !is t) 2138 ret ~= i; 2139 } 2140 } 2141 2142 return ret; 2143 } 2144 2145 Decl[] getDittos() { 2146 if(this.parent is null) 2147 return null; 2148 2149 size_t lastNonDitto; 2150 2151 foreach(idx, child; this.parent.children) { 2152 if(!child.isDitto()) 2153 lastNonDitto = idx; 2154 if(child is this) { 2155 break; 2156 } 2157 } 2158 2159 size_t stop = lastNonDitto; 2160 foreach(idx, child; this.parent.children[lastNonDitto + 1 .. $]) 2161 if(child.isDitto()) 2162 stop = idx + lastNonDitto + 1 + 1; // one +1 is offset of begin, other is to make sure it is inclusive 2163 else 2164 break; 2165 2166 return this.parent.children[lastNonDitto .. stop]; 2167 } 2168 2169 Decl eponymousModuleChild() { 2170 if(!this.isModule) 2171 return null; 2172 2173 auto name = this.name(); 2174 auto dot = name.lastIndexOf("."); 2175 name = name[dot + 1 .. $]; 2176 2177 Decl emc; 2178 foreach(child; this.children) { 2179 if(cast(ImportDecl) child) 2180 continue; 2181 if(emc !is null) 2182 return null; // not only child 2183 emc = child; 2184 } 2185 2186 // only if there is only the one child AND they have the same name does it count 2187 if(emc !is null && emc.name == name) 2188 return emc; 2189 return null; 2190 } 2191 2192 string link(bool forFile = false, string* masterOverloadName = null) { 2193 auto linkTo = this; 2194 if(!forFile) { 2195 if(auto emc = this.eponymousModuleChild()) { 2196 linkTo = emc; 2197 } 2198 } 2199 2200 auto n = linkTo.fullyQualifiedName(); 2201 2202 auto overloads = linkTo.getImmediateDocumentedOverloads(); 2203 if(overloads.length > 1) { 2204 int number = 1; 2205 int goodNumber; 2206 foreach(overload; overloads) { 2207 if(overload is this) { 2208 goodNumber = number; 2209 break; 2210 } 2211 number++; 2212 } 2213 2214 if(goodNumber) 2215 number = goodNumber; 2216 else 2217 number = 1; 2218 2219 if(masterOverloadName !is null) 2220 *masterOverloadName = n.idup; 2221 2222 import std.conv : text; 2223 n ~= text(".", number); 2224 } 2225 2226 n ~= ".html"; 2227 2228 if(masterOverloadName !is null) 2229 *masterOverloadName ~= ".html"; 2230 2231 if(!forFile) { 2232 string d = getDirectoryForPackage(linkTo.fullyQualifiedName()); 2233 if(d.length) { 2234 n = d ~ n; 2235 if(masterOverloadName !is null) 2236 *masterOverloadName = d ~ *masterOverloadName; 2237 } 2238 } 2239 2240 return n.handleCaseSensitivity(); 2241 } 2242 2243 string[] parentNameList() { 2244 string[] fqn = [name()]; 2245 auto p = parent; 2246 while(p) { 2247 fqn = p.name() ~ fqn; 2248 p = p.parent; 2249 } 2250 return fqn; 2251 2252 } 2253 2254 string fullyQualifiedName() { 2255 string fqn = name(); 2256 if(isModule) 2257 return fqn; 2258 auto p = parent; 2259 while(p) { 2260 fqn = p.name() ~ "." ~ fqn; 2261 if(p.isModule) 2262 break; // do NOT want package names in here 2263 p = p.parent; 2264 } 2265 return fqn; 2266 } 2267 2268 final InheritanceResult[] inheritsFrom() { 2269 if(!inheritsFromProcessed) 2270 foreach(ref i; _inheritsFrom) 2271 if(this.parent && i.plainText.length) { 2272 i.decl = this.parent.lookupName(i.plainText); 2273 } 2274 inheritsFromProcessed = true; 2275 return _inheritsFrom; 2276 } 2277 InheritanceResult[] _inheritsFrom; 2278 bool inheritsFromProcessed = false; 2279 2280 Decl[string] nameTable; 2281 bool nameTableBuilt; 2282 Decl[string] buildNameTable(string[] excludeModules = null) { 2283 if(!nameTableBuilt) { 2284 lookup: foreach(mod; this.importedModules) { 2285 if(!mod.publicImport) 2286 continue; 2287 if(auto modDeclPtr = mod.name in modulesByName) { 2288 auto modDecl = *modDeclPtr; 2289 2290 foreach(imod; excludeModules) 2291 if(imod == modDeclPtr.name) 2292 break lookup; 2293 2294 auto tbl = modDecl.buildNameTable(excludeModules ~ this.parentModule.name); 2295 foreach(k, v; tbl) 2296 nameTable[k] = v; 2297 } 2298 } 2299 2300 foreach(child; children) 2301 nameTable[child.name] = child; 2302 2303 nameTableBuilt = true; 2304 } 2305 return nameTable; 2306 } 2307 2308 // the excludeModules is meant to prevent circular lookups 2309 Decl lookupName(string name, bool lookUp = true, string[] excludeModules = null) { 2310 if(importedModules.length == 0 || importedModules[$-1].name != "object") 2311 addImport("object", false); 2312 2313 if(name.length == 0) 2314 return null; 2315 string originalFullName = name; 2316 auto subject = this; 2317 if(name[0] == '.') { 2318 // global scope operator 2319 while(subject && !subject.isModule) 2320 subject = subject.parent; 2321 name = name[1 .. $]; 2322 originalFullName = originalFullName[1 .. $]; 2323 2324 } 2325 2326 auto firstDotIdx = name.indexOf("."); 2327 if(firstDotIdx != -1) { 2328 subject = subject.lookupName(name[0 .. firstDotIdx]); 2329 name = name[firstDotIdx + 1 .. $]; 2330 } 2331 2332 if(subject) 2333 while(subject) { 2334 2335 auto table = subject.buildNameTable(); 2336 if(name in table) 2337 return table[name]; 2338 2339 if(lookUp) 2340 // at the top level, we also need to check private imports 2341 lookup: foreach(mod; subject.importedModules) { 2342 if(mod.publicImport) 2343 continue; // handled by the name table 2344 auto lookupInsideModule = originalFullName; 2345 if(auto modDeclPtr = mod.name in modulesByName) { 2346 auto modDecl = *modDeclPtr; 2347 2348 foreach(imod; excludeModules) 2349 if(imod == modDeclPtr.name) 2350 break lookup; 2351 //import std.stdio; writeln(modDecl.name, " ", lookupInsideModule); 2352 auto located = modDecl.lookupName(lookupInsideModule, false, excludeModules ~ this.parentModule.name); 2353 if(located !is null) 2354 return located; 2355 } 2356 } 2357 2358 if(!lookUp || subject.isModule) 2359 subject = null; 2360 else 2361 subject = subject.parent; 2362 } 2363 else { 2364 // FIXME? 2365 // fully qualified name from this module 2366 subject = this; 2367 if(originalFullName.startsWith(this.parentModule.name ~ ".")) { 2368 // came from here! 2369 auto located = this.parentModule.lookupName(originalFullName[this.parentModule.name.length + 1 .. $]); 2370 if(located !is null) 2371 return located; 2372 } else 2373 while(subject !is null) { 2374 foreach(mod; subject.importedModules) { 2375 if(originalFullName.startsWith(mod.name ~ ".")) { 2376 // fully qualified name from this module 2377 auto lookupInsideModule = originalFullName[mod.name.length + 1 .. $]; 2378 if(auto modDeclPtr = mod.name in modulesByName) { 2379 auto modDecl = *modDeclPtr; 2380 auto located = modDecl.lookupName(lookupInsideModule, mod.publicImport); 2381 if(located !is null) 2382 return located; 2383 } 2384 } 2385 } 2386 2387 if(lookUp && subject.isModule) 2388 subject = null; 2389 else 2390 subject = subject.parent; 2391 } 2392 } 2393 2394 return null; 2395 } 2396 2397 final Decl lookupName(const IdentifierOrTemplateInstance ic, bool lookUp = true) { 2398 auto subject = this; 2399 if(ic.templateInstance) 2400 return null; // FIXME 2401 2402 return lookupName(ic.identifier.text, lookUp); 2403 } 2404 2405 2406 final Decl lookupName(const IdentifierChain ic) { 2407 auto subject = this; 2408 assert(ic.identifiers.length); 2409 2410 // FIXME: leading dot? 2411 foreach(idx, ident; ic.identifiers) { 2412 subject = subject.lookupName(ident.text, idx == 0); 2413 if(subject is null) return null; 2414 } 2415 return subject; 2416 } 2417 2418 final Decl lookupName(const IdentifierOrTemplateChain ic) { 2419 auto subject = this; 2420 assert(ic.identifiersOrTemplateInstances.length); 2421 2422 // FIXME: leading dot? 2423 foreach(idx, ident; ic.identifiersOrTemplateInstances) { 2424 subject = subject.lookupName(ident, idx == 0); 2425 if(subject is null) return null; 2426 } 2427 return subject; 2428 } 2429 2430 final Decl lookupName(const Symbol ic) { 2431 // FIXME dot 2432 return lookupName(ic.identifierOrTemplateChain); 2433 } 2434 2435 2436 Decl parent; 2437 Decl[] children; 2438 2439 void writeTemplateConstraint(MyOutputRange output); 2440 2441 const(VersionOrAttribute)[] attributes; 2442 2443 void addChild(Decl decl) { 2444 decl.parent = this; 2445 children ~= decl; 2446 } 2447 2448 struct ImportedModule { 2449 string name; 2450 bool publicImport; 2451 } 2452 ImportedModule[] importedModules; 2453 void addImport(string moduleName, bool isPublic) { 2454 importedModules ~= ImportedModule(moduleName, isPublic); 2455 } 2456 2457 struct Unittest { 2458 const(dparse.ast.Unittest) ut; 2459 string code; 2460 string comment; 2461 } 2462 2463 Unittest[] unittests; 2464 2465 void addUnittest(const(dparse.ast.Unittest) ut, const(ubyte)[] code, string comment) { 2466 int slicePoint = 0; 2467 foreach(idx, b; code) { 2468 if(b == ' ' || b == '\t' || b == '\r') 2469 slicePoint++; 2470 else if(b == '\n') { 2471 slicePoint++; 2472 break; 2473 } else { 2474 slicePoint = 0; 2475 break; 2476 } 2477 } 2478 code = code[slicePoint .. $]; 2479 unittests ~= Unittest(ut, unittestCodeToString(code), comment); 2480 } 2481 2482 string unittestCodeToString(const(ubyte)[] code) { 2483 auto excludeString = cast(const(ubyte[])) "// exclude from docs"; 2484 bool replacementMade; 2485 2486 import std.algorithm.searching; 2487 2488 auto idx = code.countUntil(excludeString); 2489 while(idx != -1) { 2490 int before = cast(int) idx; 2491 int after = cast(int) idx; 2492 while(before > 0 && code[before] != '\n') 2493 before--; 2494 while(after < code.length && code[after] != '\n') 2495 after++; 2496 2497 code = code[0 .. before] ~ code[after .. $]; 2498 replacementMade = true; 2499 idx = code.countUntil(excludeString); 2500 } 2501 2502 if(!replacementMade) 2503 return (cast(char[]) code).idup; // needs to be unique 2504 else 2505 return cast(string) code; // already copied above, so it is unique 2506 } 2507 2508 struct ProcessedUnittest { 2509 string code; 2510 string comment; 2511 bool embedded; 2512 } 2513 2514 bool _unittestsProcessed; 2515 ProcessedUnittest[] _processedUnittests; 2516 2517 ProcessedUnittest[] getProcessedUnittests() { 2518 if(_unittestsProcessed) 2519 return _processedUnittests; 2520 2521 _unittestsProcessed = true; 2522 2523 // source, comment 2524 ProcessedUnittest[] ret; 2525 2526 Decl start = this; 2527 if(isDitto()) { 2528 foreach(child; this.parent.children) { 2529 if(child is this) 2530 break; 2531 if(!child.isDitto()) 2532 start = child; 2533 } 2534 2535 } 2536 2537 bool started = false; 2538 if(this.parent) 2539 foreach(child; this.parent.children) { 2540 if(started) { 2541 if(!child.isDitto()) 2542 break; 2543 } else { 2544 if(child is start) 2545 started = true; 2546 } 2547 2548 if(started) 2549 foreach(test; child.unittests) 2550 if(test.comment.length) 2551 ret ~= ProcessedUnittest(test.code, test.comment); 2552 } 2553 else 2554 foreach(test; this.unittests) 2555 if(test.comment.length) 2556 ret ~= ProcessedUnittest(test.code, test.comment); 2557 _processedUnittests = ret; 2558 return ret; 2559 } 2560 2561 override string toString() { 2562 string s; 2563 s ~= super.toString() ~ " " ~ this.name(); 2564 foreach(child; children) { 2565 s ~= "\n"; 2566 auto p = parent; 2567 while(p) { 2568 s ~= "\t"; 2569 p = p.parent; 2570 } 2571 s ~= child.toString(); 2572 } 2573 return s; 2574 } 2575 2576 abstract bool isDitto(); 2577 bool isModule() { return false; } 2578 bool isArticle() { return false; } 2579 bool isConstructor() { return false; } 2580 2581 bool aliasThisPresent; 2582 Token aliasThisToken; 2583 string aliasThisComment; 2584 2585 Decl aliasThis() { 2586 if(!aliasThisPresent) 2587 return null; 2588 else 2589 return lookupName(aliasThisToken.text, false); 2590 } 2591 2592 DestructorDecl destructor() { 2593 foreach(child; children) 2594 if(auto dd = cast(DestructorDecl) child) 2595 return dd; 2596 return null; 2597 } 2598 2599 PostblitDecl postblit() { 2600 foreach(child; children) 2601 if(auto dd = cast(PostblitDecl) child) 2602 return dd; 2603 return null; 2604 } 2605 2606 2607 abstract bool isDisabled(); 2608 2609 ConstructorDecl disabledDefaultConstructor() { 2610 foreach(child; children) 2611 if(child.isConstructor() && child.isDisabled()) { 2612 auto ctor = cast(ConstructorDecl) child; 2613 if(ctor.astNode.parameters || ctor.astNode.parameters.parameters.length == 0) 2614 return ctor; 2615 } 2616 return null; 2617 } 2618 } 2619 2620 class ModuleDecl : Decl { 2621 mixin CtorFrom!Module defaultMixins; 2622 2623 string justDocsTitle; 2624 2625 override bool isModule() { return true; } 2626 override bool isArticle() { return justDocsTitle.length > 0; } 2627 2628 override bool docsShouldBeOutputted() { 2629 if(this.justDocsTitle !is null) 2630 return true; 2631 return super.docsShouldBeOutputted(); 2632 } 2633 2634 override string declarationType() { 2635 return isArticle() ? "Article" : "module"; 2636 } 2637 2638 version(none) 2639 override void getSimplifiedPrototype(MyOutputRange r) { 2640 if(isArticle()) 2641 r.put(justDocsTitle); 2642 else 2643 defaultMixins.getSimplifiedPrototype(r); 2644 } 2645 2646 ubyte[] originalSource; 2647 2648 string packageName() { 2649 auto it = this.name(); 2650 auto idx = it.lastIndexOf("."); 2651 if(idx == -1) 2652 return null; 2653 return it[0 .. idx]; 2654 } 2655 } 2656 2657 class AliasDecl : Decl { 2658 mixin CtorFrom!AliasDeclaration; 2659 2660 this(const(AliasDeclaration) ad, const(VersionOrAttribute)[] attributes) { 2661 this.attributes = attributes; 2662 this.astNode = ad; 2663 this.initializer = null; 2664 // deal with the type and initializer list and storage classes 2665 } 2666 2667 const(AliasInitializer) initializer; 2668 2669 this(const(AliasDeclaration) ad, const(AliasInitializer) init, const(VersionOrAttribute)[] attributes) { 2670 this.attributes = attributes; 2671 this.astNode = ad; 2672 this.initializer = init; 2673 // deal with init 2674 } 2675 2676 override string name() { 2677 if(initializer is null) 2678 return toText(astNode.identifierList); 2679 else 2680 return initializer.name.text; 2681 } 2682 2683 override void getAnnotatedPrototype(MyOutputRange output) { 2684 void cool() { 2685 output.putTag("<div class=\"declaration-prototype\">"); 2686 if(parent !is null && !parent.isModule) { 2687 output.putTag("<div class=\"parent-prototype\">"); 2688 parent.getSimplifiedPrototype(output); 2689 output.putTag("</div><div>"); 2690 getPrototype(output, true); 2691 output.putTag("</div>"); 2692 } else { 2693 getPrototype(output, true); 2694 } 2695 output.putTag("</div>"); 2696 } 2697 2698 writeOverloads!cool(this, output); 2699 } 2700 2701 override void getSimplifiedPrototype(MyOutputRange output) { 2702 getPrototype(output, false); 2703 } 2704 2705 void getPrototype(MyOutputRange output, bool link) { 2706 // FIXME: storage classes? 2707 2708 if(link) { 2709 auto f = new MyFormatter!(typeof(output))(output, this); 2710 writeAttributes(f, output, this.attributes); 2711 } 2712 2713 output.putTag("<span class=\"builtin-type\">alias</span> "); 2714 2715 output.putTag("<span class=\"name\">"); 2716 output.put(name); 2717 output.putTag("</span>"); 2718 2719 if(initializer && initializer.templateParameters) { 2720 output.putTag(toHtml(initializer.templateParameters).source); 2721 } 2722 2723 output.put(" = "); 2724 2725 if(initializer) { 2726 if(link) 2727 output.putTag(toLinkedHtml(initializer.type, this).source); 2728 else 2729 output.putTag(toHtml(initializer.type).source); 2730 } 2731 2732 if(astNode.type) { 2733 if(link) { 2734 auto t = toText(astNode.type); 2735 auto decl = lookupName(t); 2736 if(decl is null) 2737 goto nulldecl; 2738 output.putTag(getReferenceLink(t, decl).toString); 2739 } else { 2740 nulldecl: 2741 output.putTag(toHtml(astNode.type).source); 2742 } 2743 } 2744 } 2745 } 2746 2747 class VariableDecl : Decl { 2748 mixin CtorFrom!VariableDeclaration mixinmagic; 2749 2750 const(Declarator) declarator; 2751 this(const(Declarator) declarator, const(VariableDeclaration) astNode, const(VersionOrAttribute)[] attributes) { 2752 this.astNode = astNode; 2753 this.declarator = declarator; 2754 this.attributes = attributes; 2755 this.ident = Token.init; 2756 this.initializer = null; 2757 2758 foreach(a; astNode.attributes) 2759 this.attributes ~= new VersionOrAttribute(a); 2760 filterDuplicateAttributes(); 2761 } 2762 2763 const(Token) ident; 2764 const(Initializer) initializer; 2765 this(const(VariableDeclaration) astNode, const(Token) ident, const(Initializer) initializer, const(VersionOrAttribute)[] attributes, bool isEnum) { 2766 this.declarator = null; 2767 this.attributes = attributes; 2768 this.astNode = astNode; 2769 this.ident = ident; 2770 this.isEnum = isEnum; 2771 this.initializer = initializer; 2772 2773 foreach(a; astNode.attributes) 2774 this.attributes ~= new VersionOrAttribute(a); 2775 filterDuplicateAttributes(); 2776 } 2777 2778 void filterDuplicateAttributes() { 2779 const(VersionOrAttribute)[] filtered; 2780 foreach(idx, a; attributes) { 2781 bool isdup; 2782 foreach(b; attributes[idx + 1 .. $]) { 2783 if(a is b) 2784 continue; 2785 2786 if(cast(FakeAttribute) a || cast(FakeAttribute) b) 2787 continue; 2788 2789 if(a.attr is b.attr) 2790 isdup = true; 2791 else if(toText(a.attr) == toText(b.attr)) 2792 isdup = true; 2793 } 2794 if(!isdup) 2795 filtered ~= a; 2796 } 2797 2798 this.attributes = filtered; 2799 } 2800 2801 bool isEnum; 2802 2803 override string name() { 2804 if(declarator) 2805 return declarator.name.text; 2806 else 2807 return ident.text; 2808 } 2809 2810 override bool isDitto() { 2811 if(declarator && declarator.comment is null) { 2812 foreach (idx, const Declarator d; astNode.declarators) { 2813 if(d.comment !is null) { 2814 break; 2815 } 2816 if(d is declarator && idx) 2817 return true; 2818 } 2819 } 2820 2821 return mixinmagic.isDitto(); 2822 } 2823 2824 override string rawComment() { 2825 string it = astNode.comment; 2826 auto additional = (declarator ? declarator.comment : astNode.autoDeclaration.comment); 2827 2828 if(additional != it) 2829 it ~= additional; 2830 return it; 2831 } 2832 2833 override void getAnnotatedPrototype(MyOutputRange outputFinal) { 2834 string t; 2835 MyOutputRange output = MyOutputRange(&t); 2836 2837 void piece() { 2838 output.putTag("<div class=\"declaration-prototype\">"); 2839 if(parent !is null && !parent.isModule) { 2840 output.putTag("<div class=\"parent-prototype\">"); 2841 parent.getSimplifiedPrototype(output); 2842 output.putTag("</div><div>"); 2843 auto f = new MyFormatter!(typeof(output))(output); 2844 writeAttributes(f, output, attributes); 2845 getSimplifiedPrototypeInternal(output, true); 2846 output.putTag("</div>"); 2847 } else { 2848 auto f = new MyFormatter!(typeof(output))(output); 2849 writeAttributes(f, output, attributes); 2850 getSimplifiedPrototypeInternal(output, true); 2851 } 2852 output.putTag("</div>"); 2853 } 2854 2855 2856 writeOverloads!piece(this, output); 2857 2858 outputFinal.putTag(linkUpHtml(t, this)); 2859 } 2860 2861 override void getSimplifiedPrototype(MyOutputRange output) { 2862 getSimplifiedPrototypeInternal(output, false); 2863 } 2864 2865 final void getSimplifiedPrototypeInternal(MyOutputRange output, bool link) { 2866 foreach(sc; astNode.storageClasses) { 2867 output.putTag(toHtml(sc).source); 2868 output.put(" "); 2869 } 2870 2871 if(astNode.type) { 2872 if(link) { 2873 auto html = toHtml(astNode.type).source; 2874 auto txt = toText(astNode.type); 2875 2876 auto typeDecl = lookupName(txt); 2877 if(typeDecl is null || !typeDecl.docsShouldBeOutputted) 2878 goto plain; 2879 2880 output.putTag("<a title=\""~typeDecl.fullyQualifiedName~"\" href=\""~typeDecl.link~"\">" ~ html ~ "</a>"); 2881 } else { 2882 plain: 2883 output.putTag(toHtml(astNode.type).source); 2884 } 2885 } else 2886 output.putTag("<span class=\"builtin-type\">"~(isEnum ? "enum" : "auto")~"</span>"); 2887 2888 output.put(" "); 2889 2890 output.putTag("<span class=\"name\">"); 2891 output.put(name); 2892 output.putTag("</span>"); 2893 2894 if(declarator && declarator.templateParameters) 2895 output.putTag(toHtml(declarator.templateParameters).source); 2896 2897 if(link) { 2898 if(initializer !is null) { 2899 output.put(" = "); 2900 output.putTag(toHtml(initializer).source); 2901 } 2902 } 2903 output.put(";"); 2904 } 2905 2906 override void getAggregatePrototype(MyOutputRange output) { 2907 auto f = new MyFormatter!(typeof(output))(output); 2908 writeAttributes(f, output, attributes); 2909 getSimplifiedPrototypeInternal(output, false); 2910 } 2911 2912 override string declarationType() { 2913 return (isStatic() ? "static variable" : (isEnum ? "manifest constant" : "variable")); 2914 } 2915 } 2916 2917 2918 class FunctionDecl : Decl { 2919 mixin CtorFrom!FunctionDeclaration; 2920 override void getAnnotatedPrototype(MyOutputRange output) { 2921 doFunctionDec(this, output); 2922 } 2923 2924 override Decl lookupName(string name, bool lookUp = true, string[] excludeModules = null) { 2925 // is it a param or template param? If so, return that. 2926 2927 foreach(param; astNode.parameters.parameters) { 2928 if (param.name.type != tok!"") 2929 if(param.name.text == name) { 2930 return null; // it is local, but we don't have a decl.. 2931 } 2932 } 2933 if(astNode.templateParameters && astNode.templateParameters.templateParameterList && astNode.templateParameters.templateParameterList.items) 2934 foreach(param; astNode.templateParameters.templateParameterList.items) { 2935 auto paramName = ""; 2936 2937 if(param.templateTypeParameter) 2938 paramName = param.templateTypeParameter.identifier.text; 2939 else if(param.templateValueParameter) 2940 paramName = param.templateValueParameter.identifier.text; 2941 else if(param.templateAliasParameter) 2942 paramName = param.templateAliasParameter.identifier.text; 2943 else if(param.templateTupleParameter) 2944 paramName = param.templateTupleParameter.identifier.text; 2945 2946 if(paramName.length && paramName == name) { 2947 return null; // it is local, but we don't have a decl.. 2948 } 2949 } 2950 2951 if(lookUp) 2952 return super.lookupName(name, lookUp, excludeModules); 2953 else 2954 return null; 2955 } 2956 2957 override string declarationType() { 2958 return isProperty() ? "property" : (isStatic() ? "static function" : "function"); 2959 } 2960 2961 override void getAggregatePrototype(MyOutputRange output) { 2962 bool hadAttrs; 2963 foreach(attr; attributes) 2964 if(auto cfa = cast(ConditionFakeAttribute) attr) { 2965 if(!hadAttrs) { 2966 output.putTag("<div class=\"conditional-compilation-attributes\">"); 2967 hadAttrs = true; 2968 } 2969 output.putTag(cfa.toHTML); 2970 output.putTag("<br />\n"); 2971 } 2972 if(hadAttrs) 2973 output.putTag("</div>"); 2974 2975 if(isStatic()) { 2976 output.putTag("<span class=\"storage-class\">static</span> "); 2977 } 2978 2979 getSimplifiedPrototype(output); 2980 output.put(";"); 2981 } 2982 2983 override void getSimplifiedPrototype(MyOutputRange output) { 2984 foreach(sc; astNode.storageClasses) { 2985 output.putTag(toHtml(sc).source); 2986 output.put(" "); 2987 } 2988 2989 if(isProperty() && (paramCount == 0 || paramCount == 1 || (paramCount == 2 && !isAggregateMember))) { 2990 if((paramCount == 1 && isAggregateMember()) || (paramCount == 2 && !isAggregateMember())) { 2991 // setter 2992 output.putTag(toHtml(astNode.parameters.parameters[0].type).source); 2993 output.put(" "); 2994 output.putTag("<span class=\"name\">"); 2995 output.put(name); 2996 output.putTag("</span>"); 2997 2998 output.put(" [@property setter]"); 2999 } else { 3000 // getter 3001 putSimplfiedReturnValue(output, astNode); 3002 output.put(" "); 3003 output.putTag("<span class=\"name\">"); 3004 output.put(name); 3005 output.putTag("</span>"); 3006 3007 output.put(" [@property getter]"); 3008 } 3009 } else { 3010 putSimplfiedReturnValue(output, astNode); 3011 output.put(" "); 3012 output.putTag("<span class=\"name\">"); 3013 output.put(name); 3014 output.putTag("</span>"); 3015 putSimplfiedArgs(output, astNode); 3016 } 3017 } 3018 3019 int paramCount() { 3020 return cast(int) astNode.parameters.parameters.length; 3021 } 3022 } 3023 3024 class ConstructorDecl : Decl { 3025 mixin CtorFrom!Constructor; 3026 3027 override void getAnnotatedPrototype(MyOutputRange output) { 3028 doFunctionDec(this, output); 3029 } 3030 3031 override void getSimplifiedPrototype(MyOutputRange output) { 3032 output.putTag("<span class=\"lang-feature name\">"); 3033 output.put("this"); 3034 output.putTag("</span>"); 3035 putSimplfiedArgs(output, astNode); 3036 } 3037 3038 override bool isConstructor() { return true; } 3039 } 3040 3041 class DestructorDecl : Decl { 3042 mixin CtorFrom!Destructor; 3043 3044 override void getSimplifiedPrototype(MyOutputRange output) { 3045 output.putTag("<span class=\"lang-feature name\">"); 3046 output.put("~this"); 3047 output.putTag("</span>"); 3048 output.put("()"); 3049 } 3050 } 3051 3052 class PostblitDecl : Decl { 3053 mixin CtorFrom!Postblit; 3054 3055 override void getSimplifiedPrototype(MyOutputRange output) { 3056 if(isDisabled) { 3057 output.putTag("<span class=\"builtin-type\">"); 3058 output.put("@disable"); 3059 output.putTag("</span>"); 3060 output.put(" "); 3061 } 3062 output.putTag("<span class=\"lang-feature name\">"); 3063 output.put("this(this)"); 3064 output.putTag("</span>"); 3065 } 3066 } 3067 3068 class ImportDecl : Decl { 3069 mixin CtorFrom!ImportDeclaration; 3070 3071 bool isPublic; 3072 string newName; 3073 string oldName; 3074 3075 override string link(bool forFile = false, string* useless = null) { 3076 string d; 3077 if(!forFile) { 3078 d = getDirectoryForPackage(oldName); 3079 } 3080 return d ~ oldName ~ ".html"; 3081 } 3082 3083 // I also want to document undocumented public imports, since they also spam up the namespace 3084 override bool docsShouldBeOutputted() { 3085 return isPublic; 3086 } 3087 3088 override string name() { 3089 return newName.length ? newName : oldName; 3090 } 3091 3092 override string declarationType() { 3093 return "import"; 3094 } 3095 3096 override void getSimplifiedPrototype(MyOutputRange output) { 3097 if(isPublic) 3098 output.putTag("<span class=\"builtin-type\">public</span> "); 3099 output.putTag(toHtml(astNode).source); 3100 } 3101 3102 } 3103 3104 class MixedInTemplateDecl : Decl { 3105 mixin CtorFrom!TemplateMixinExpression; 3106 3107 override string declarationType() { 3108 return "mixin"; 3109 } 3110 3111 override void getSimplifiedPrototype(MyOutputRange output) { 3112 output.putTag(toHtml(astNode).source); 3113 } 3114 } 3115 3116 class StructDecl : Decl { 3117 mixin CtorFrom!StructDeclaration; 3118 override void getAnnotatedPrototype(MyOutputRange output) { 3119 annotatedPrototype(this, output); 3120 } 3121 3122 } 3123 3124 class UnionDecl : Decl { 3125 mixin CtorFrom!UnionDeclaration; 3126 3127 override void getAnnotatedPrototype(MyOutputRange output) { 3128 annotatedPrototype(this, output); 3129 } 3130 } 3131 3132 class ClassDecl : Decl { 3133 mixin CtorFrom!ClassDeclaration; 3134 3135 override void getAnnotatedPrototype(MyOutputRange output) { 3136 annotatedPrototype(this, output); 3137 } 3138 } 3139 3140 class InterfaceDecl : Decl { 3141 mixin CtorFrom!InterfaceDeclaration; 3142 override void getAnnotatedPrototype(MyOutputRange output) { 3143 annotatedPrototype(this, output); 3144 } 3145 } 3146 3147 class TemplateDecl : Decl { 3148 mixin CtorFrom!TemplateDeclaration; 3149 3150 Decl eponymousMember() { 3151 foreach(child; this.children) 3152 if(child.name == this.name) 3153 return child; 3154 return null; 3155 } 3156 3157 override void getAnnotatedPrototype(MyOutputRange output) { 3158 annotatedPrototype(this, output); 3159 } 3160 } 3161 3162 class EponymousTemplateDecl : Decl { 3163 mixin CtorFrom!EponymousTemplateDeclaration; 3164 3165 /* 3166 Decl eponymousMember() { 3167 foreach(child; this.children) 3168 if(child.name == this.name) 3169 return child; 3170 return null; 3171 } 3172 */ 3173 3174 override string declarationType() { 3175 return "enum"; 3176 } 3177 3178 override void getAnnotatedPrototype(MyOutputRange output) { 3179 annotatedPrototype(this, output); 3180 } 3181 } 3182 3183 3184 class MixinTemplateDecl : Decl { 3185 mixin CtorFrom!TemplateDeclaration; // MixinTemplateDeclaration does nothing interesting except this.. 3186 3187 override void getAnnotatedPrototype(MyOutputRange output) { 3188 annotatedPrototype(this, output); 3189 } 3190 3191 override string declarationType() { 3192 return "mixin template"; 3193 } 3194 } 3195 3196 class EnumDecl : Decl { 3197 mixin CtorFrom!EnumDeclaration; 3198 3199 override void addSupplementalData(Element content) { 3200 doEnumDecl(this, content); 3201 } 3202 } 3203 3204 class AnonymousEnumDecl : Decl { 3205 mixin CtorFrom!AnonymousEnumDeclaration; 3206 3207 override string name() { 3208 assert(astNode.members.length > 0); 3209 auto name = astNode.members[0].name.text; 3210 return name; 3211 } 3212 3213 override void addSupplementalData(Element content) { 3214 doEnumDecl(this, content); 3215 } 3216 3217 override string declarationType() { 3218 return "enum"; 3219 } 3220 } 3221 3222 mixin template CtorFrom(T) { 3223 const(T) astNode; 3224 3225 static if(!is(T == VariableDeclaration) && !is(T == AliasDeclaration)) 3226 this(const(T) astNode, const(VersionOrAttribute)[] attributes) { 3227 this.astNode = astNode; 3228 this.attributes = attributes; 3229 3230 static if(is(typeof(astNode.memberFunctionAttributes))) { 3231 foreach(a; astNode.memberFunctionAttributes) 3232 if(a !is null) 3233 this.attributes ~= new MemberFakeAttribute(a); 3234 } 3235 3236 static if(is(typeof(astNode) == const(ClassDeclaration)) || is(typeof(astNode) == const(InterfaceDeclaration))) { 3237 if(astNode.baseClassList) 3238 foreach(idx, baseClass; astNode.baseClassList.items) { 3239 auto bc = toText(baseClass); 3240 InheritanceResult ir = InheritanceResult(null, bc); 3241 _inheritsFrom ~= ir; 3242 } 3243 } 3244 } 3245 3246 static if(is(T == Module)) { 3247 // this is so I can load this from the index... kinda a hack 3248 // it should only be used in limited circumstances 3249 private string _name; 3250 private this(string name) { 3251 this._name = name; 3252 this.astNode = null; 3253 } 3254 } 3255 3256 override const(T) getAstNode() { return astNode; } 3257 override int lineNumber() { 3258 static if(__traits(compiles, astNode.name.line)) 3259 return cast(int) astNode.name.line; 3260 else static if(__traits(compiles, astNode.line)) 3261 return cast(int) astNode.line; 3262 else static if(__traits(compiles, astNode.declarators[0].name.line)) { 3263 if(astNode.declarators.length) 3264 return cast(int) astNode.declarators[0].name.line; 3265 } else static if(is(typeof(astNode) == const(Module))) { 3266 return 0; 3267 } else static assert(0, typeof(astNode).stringof); 3268 return 0; 3269 } 3270 3271 override void writeTemplateConstraint(MyOutputRange output) { 3272 static if(__traits(compiles, astNode.constraint)) { 3273 if(astNode.constraint) { 3274 auto f = new MyFormatter!(typeof(output))(output); 3275 output.putTag("<div class=\"template-constraint\">"); 3276 f.format(astNode.constraint); 3277 output.putTag("</div>"); 3278 } 3279 } 3280 } 3281 3282 override string name() { 3283 static if(is(T == Constructor)) 3284 return "this"; 3285 else static if(is(T == Destructor)) 3286 return "~this"; 3287 else static if(is(T == Postblit)) 3288 return "this(this)"; 3289 else static if(is(T == Module)) 3290 return _name is null ? .format(astNode.moduleDeclaration.moduleName) : _name; 3291 else static if(is(T == AnonymousEnumDeclaration)) 3292 { assert(0); } // overridden above 3293 else static if(is(T == AliasDeclaration)) 3294 { assert(0); } // overridden above 3295 else static if(is(T == VariableDeclaration)) 3296 {assert(0);} // not compiled, overridden above 3297 else static if(is(T == ImportDeclaration)) 3298 {assert(0);} // not compiled, overridden above 3299 else static if(is(T == MixinTemplateDeclaration)) { 3300 return astNode.templateDeclaration.name.text; 3301 } else static if(is(T == StructDeclaration) || is(T == UnionDeclaration)) 3302 if(astNode.name.text.length) 3303 return astNode.name.text; 3304 else 3305 return "__anonymous"; 3306 else static if(is(T == TemplateMixinExpression)) { 3307 return astNode.identifier.text.length ? astNode.identifier.text : "__anonymous"; 3308 } else 3309 return astNode.name.text; 3310 } 3311 3312 override string comment() { 3313 static if(is(T == Module)) 3314 return astNode.moduleDeclaration.comment; 3315 else { 3316 if(isDitto()) { 3317 auto ps = previousSibling; 3318 while(ps && ps.rawComment.length == 0) 3319 ps = ps.previousSibling; 3320 return ps ? ps.comment : this.rawComment(); 3321 } else 3322 return this.rawComment(); 3323 } 3324 } 3325 3326 override void getAnnotatedPrototype(MyOutputRange) {} 3327 override void getSimplifiedPrototype(MyOutputRange output) { 3328 output.putTag("<span class=\"builtin-type\">"); 3329 output.put(declarationType()); 3330 output.putTag("</span>"); 3331 output.put(" "); 3332 3333 output.putTag("<span class=\"name\">"); 3334 output.put(this.name); 3335 output.putTag("</span>"); 3336 3337 static if(__traits(compiles, astNode.templateParameters)) { 3338 if(astNode.templateParameters) { 3339 output.putTag("<span class=\"template-params\">"); 3340 output.put(toText(astNode.templateParameters)); 3341 output.putTag("</span>"); 3342 } 3343 } 3344 } 3345 override string declarationType() { 3346 import std.string:toLower; 3347 return toLower(typeof(this).stringof[0 .. $-4]); 3348 } 3349 3350 override bool isDitto() { 3351 static if(is(T == Module)) 3352 return false; 3353 else { 3354 import std.string; 3355 auto l = strip(toLower(preprocessComment(this.rawComment, this))); 3356 if(l.length && l[$-1] == '.') 3357 l = l[0 .. $-1]; 3358 return l == "ditto"; 3359 } 3360 } 3361 3362 override string rawComment() { 3363 static if(is(T == Module)) 3364 return astNode.moduleDeclaration.comment; 3365 else static if(is(T == MixinTemplateDeclaration)) 3366 return astNode.templateDeclaration.comment; 3367 else 3368 return astNode.comment; 3369 } 3370 3371 override bool isDisabled() { 3372 foreach(attribute; attributes) 3373 if(attribute.attr && attribute.attr.atAttribute && attribute.attr.atAttribute.identifier.text == "disable") 3374 return true; 3375 static if(__traits(compiles, astNode.memberFunctionAttributes)) 3376 foreach(attribute; astNode.memberFunctionAttributes) 3377 if(attribute && attribute.atAttribute && attribute.atAttribute.identifier.text == "disable") 3378 return true; 3379 return false; 3380 } 3381 3382 } 3383 3384 private __gshared Object allClassesMutex = new Object; 3385 __gshared ClassDecl[string] allClasses; 3386 3387 class Looker : ASTVisitor { 3388 alias visit = ASTVisitor.visit; 3389 3390 const(ubyte)[] fileBytes; 3391 string originalFileName; 3392 this(const(ubyte)[] fileBytes, string fileName) { 3393 this.fileBytes = fileBytes; 3394 this.originalFileName = fileName; 3395 } 3396 3397 ModuleDecl root; 3398 3399 3400 private Decl[] stack; 3401 3402 Decl previousSibling() { 3403 auto s = stack[$-1]; 3404 if(s.children.length) 3405 return s.children[$-1]; 3406 return s; // probably a documented unittest of the module itself 3407 } 3408 3409 void visitInto(D, T)(const(T) t, bool keepAttributes = true) { 3410 auto d = new D(t, attributes[$-1]); 3411 stack[$-1].addChild(d); 3412 stack ~= d; 3413 if(!keepAttributes) 3414 pushAttributes(); 3415 t.accept(this); 3416 if(!keepAttributes) 3417 popAttributes(); 3418 stack = stack[0 .. $-1]; 3419 3420 if(specialPreprocessor == "gtk") 3421 static if(is(D == ClassDecl)) 3422 synchronized(allClassesMutex) 3423 allClasses[d.name] = d; 3424 } 3425 3426 override void visit(const Module mod) { 3427 pushAttributes(); 3428 3429 root = new ModuleDecl(mod, attributes[$-1]); 3430 stack ~= root; 3431 mod.accept(this); 3432 assert(stack.length == 1); 3433 } 3434 3435 override void visit(const FunctionDeclaration dec) { 3436 stack[$-1].addChild(new FunctionDecl(dec, attributes[$-1])); 3437 } 3438 override void visit(const Constructor dec) { 3439 stack[$-1].addChild(new ConstructorDecl(dec, attributes[$-1])); 3440 } 3441 override void visit(const TemplateMixinExpression dec) { 3442 stack[$-1].addChild(new MixedInTemplateDecl(dec, attributes[$-1])); 3443 } 3444 override void visit(const Postblit dec) { 3445 stack[$-1].addChild(new PostblitDecl(dec, attributes[$-1])); 3446 } 3447 override void visit(const Destructor dec) { 3448 stack[$-1].addChild(new DestructorDecl(dec, attributes[$-1])); 3449 } 3450 3451 override void visit(const StructDeclaration dec) { 3452 visitInto!StructDecl(dec); 3453 } 3454 override void visit(const ClassDeclaration dec) { 3455 visitInto!ClassDecl(dec); 3456 } 3457 override void visit(const UnionDeclaration dec) { 3458 visitInto!UnionDecl(dec); 3459 } 3460 override void visit(const InterfaceDeclaration dec) { 3461 visitInto!InterfaceDecl(dec); 3462 } 3463 override void visit(const TemplateDeclaration dec) { 3464 visitInto!TemplateDecl(dec); 3465 } 3466 override void visit(const EponymousTemplateDeclaration dec) { 3467 visitInto!EponymousTemplateDecl(dec); 3468 } 3469 override void visit(const MixinTemplateDeclaration dec) { 3470 visitInto!MixinTemplateDecl(dec.templateDeclaration, false); 3471 } 3472 override void visit(const EnumDeclaration dec) { 3473 visitInto!EnumDecl(dec); 3474 } 3475 override void visit(const AliasThisDeclaration dec) { 3476 stack[$-1].aliasThisPresent = true; 3477 stack[$-1].aliasThisToken = dec.identifier; 3478 stack[$-1].aliasThisComment = dec.comment; 3479 } 3480 override void visit(const AnonymousEnumDeclaration dec) { 3481 // we can't do anything with an empty anonymous enum, we need a name from somewhere 3482 if(dec.members.length) 3483 visitInto!AnonymousEnumDecl(dec); 3484 } 3485 override void visit(const VariableDeclaration dec) { 3486 if (dec.autoDeclaration) { 3487 foreach (idx, ident; dec.autoDeclaration.identifiers) { 3488 stack[$-1].addChild(new VariableDecl(dec, ident, dec.autoDeclaration.initializers[idx], attributes[$-1], dec.isEnum)); 3489 3490 } 3491 } else 3492 foreach (const Declarator d; dec.declarators) { 3493 stack[$-1].addChild(new VariableDecl(d, dec, attributes[$-1])); 3494 3495 /* 3496 if (variableDeclaration.type !is null) 3497 { 3498 auto f = new MyFormatter!(typeof(app))(app); 3499 f.format(variableDeclaration.type); 3500 } 3501 output.putTag(app.data); 3502 output.put(" "); 3503 output.put(d.name.text); 3504 3505 comment.writeDetails(output); 3506 3507 writeToParentList("variable " ~ cast(string)app.data ~ " ", name, comment.synopsis, "variable"); 3508 3509 ascendOutOf(name); 3510 */ 3511 } 3512 } 3513 override void visit(const AliasDeclaration dec) { 3514 if(dec.initializers.length) { // alias a = b 3515 foreach(init; dec.initializers) 3516 stack[$-1].addChild(new AliasDecl(dec, init, attributes[$-1])); 3517 } else { // alias b a; 3518 // might include a type... 3519 stack[$-1].addChild(new AliasDecl(dec, attributes[$-1])); 3520 } 3521 } 3522 3523 override void visit(const Unittest ut) { 3524 //import std.stdio; writeln(fileBytes.length, " ", ut.blockStatement.startLocation, " ", ut.blockStatement.endLocation); 3525 previousSibling.addUnittest( 3526 ut, 3527 fileBytes[ut.blockStatement.startLocation + 1 .. ut.blockStatement.endLocation], // trim off the opening and closing {} 3528 ut.comment 3529 ); 3530 } 3531 3532 override void visit(const ImportDeclaration id) { 3533 3534 bool isPublic = false; 3535 3536 foreach(a; attributes[$-1]) { 3537 if (a.attr && isProtection(a.attr.attribute.type)) { 3538 if(a.attr.attribute.type == tok!"public") { 3539 isPublic = true; 3540 } else { 3541 isPublic = false; 3542 } 3543 } 3544 } 3545 3546 3547 void handleSingleImport(const SingleImport si) { 3548 auto newName = si.rename.text; 3549 auto oldName = ""; 3550 foreach(idx, ident; si.identifierChain.identifiers) { 3551 if(idx) 3552 oldName ~= "."; 3553 oldName ~= ident.text; 3554 } 3555 stack[$-1].addImport(oldName, isPublic); 3556 // FIXME: handle the rest like newName for the import lookups 3557 3558 auto nid = new ImportDecl(id, attributes[$-1]); 3559 stack[$-1].addChild(nid); 3560 nid.isPublic = isPublic; 3561 nid.oldName = oldName; 3562 nid.newName = newName; 3563 } 3564 3565 3566 foreach(si; id.singleImports) { 3567 handleSingleImport(si); 3568 } 3569 3570 if(id.importBindings && id.importBindings.singleImport) 3571 handleSingleImport(id.importBindings.singleImport); // FIXME: handle bindings 3572 3573 } 3574 3575 /* 3576 override void visit(const Deprecated d) { 3577 attributes[$-1] 3578 } 3579 */ 3580 3581 override void visit(const StructBody sb) { 3582 pushAttributes(); 3583 sb.accept(this); 3584 popAttributes(); 3585 } 3586 3587 // FIXME ???? 3588 override void visit(const VersionCondition sb) { 3589 import std.conv; 3590 attributes[$-1] ~= new VersionFakeAttribute(toText(sb.token)); 3591 sb.accept(this); 3592 } 3593 3594 override void visit(const DebugCondition dc) { 3595 attributes[$-1] ~= new DebugFakeAttribute(toText(dc.identifierOrInteger)); 3596 dc.accept(this); 3597 } 3598 3599 override void visit(const StaticIfCondition sic) { 3600 attributes[$-1] ~= new StaticIfFakeAttribute(toText(sic.assignExpression)); 3601 sic.accept(this); 3602 } 3603 3604 override void visit(const BlockStatement bs) { 3605 pushAttributes(); 3606 bs.accept(this); 3607 popAttributes(); 3608 } 3609 3610 override void visit(const FunctionBody bs) { 3611 // just skipping it hence the commented code below. not important to docs 3612 /* 3613 pushAttributes(); 3614 bs.accept(this); 3615 popAttributes(); 3616 */ 3617 } 3618 3619 override void visit(const ConditionalDeclaration bs) { 3620 pushAttributes(); 3621 if(attributes.length >= 2) 3622 attributes[$-1] = attributes[$-2]; // inherit from the previous scope here 3623 size_t previousConditions; 3624 if(bs.compileCondition) { 3625 previousConditions = attributes[$-1].length; 3626 bs.compileCondition.accept(this); 3627 } 3628 // WTF FIXME FIXME 3629 // http://dpldocs.info/experimental-docs/asdf.bar.html 3630 3631 if(bs.trueDeclarations) 3632 foreach(const Declaration td; bs.trueDeclarations) { 3633 visit(td); 3634 } 3635 3636 if(bs.falseDeclaration) { 3637 auto slice = attributes[$-1][previousConditions .. $]; 3638 attributes[$-1] = attributes[$-1][0 .. previousConditions]; 3639 foreach(cond; slice) 3640 attributes[$-1] ~= cond.invertedClone; 3641 visit(bs.falseDeclaration); 3642 } 3643 popAttributes(); 3644 } 3645 3646 override void visit(const Declaration dec) { 3647 auto originalAttributes = attributes[$ - 1]; 3648 foreach(a; dec.attributes) { 3649 attributes[$ - 1] ~= new VersionOrAttribute(a); 3650 } 3651 dec.accept(this); 3652 if (dec.attributeDeclaration is null) 3653 attributes[$ - 1] = originalAttributes; 3654 } 3655 3656 override void visit(const AttributeDeclaration dec) { 3657 attributes[$ - 1] ~= new VersionOrAttribute(dec.attribute); 3658 } 3659 3660 void pushAttributes() { 3661 attributes.length = attributes.length + 1; 3662 } 3663 3664 void popAttributes() { 3665 attributes = attributes[0 .. $ - 1]; 3666 } 3667 3668 const(VersionOrAttribute)[][] attributes; 3669 } 3670 3671 3672 string format(const IdentifierChain identifierChain) { 3673 string r; 3674 foreach(count, ident; identifierChain.identifiers) { 3675 if (count) r ~= ("."); 3676 r ~= (ident.text); 3677 } 3678 return r; 3679 } 3680 3681 import std.algorithm : startsWith, findSplitBefore; 3682 import std.string : strip; 3683 3684 //Decl[][string] packages; 3685 __gshared static Object modulesByNameMonitor = new Object; // intentional CTF 3686 __gshared ModuleDecl[string] modulesByName; 3687 3688 __gshared string specialPreprocessor; 3689 3690 // simplified ".gitignore" processor 3691 final class GitIgnore { 3692 string[] masks; // on each new dir, empty line is added to masks 3693 3694 void loadGlobalGitIgnore () { 3695 import std.path; 3696 import std.stdio; 3697 try { 3698 foreach (string s; File("~/.gitignore_global".expandTilde).byLineCopy) { 3699 if (isComment(s)) continue; 3700 masks ~= trim(s); 3701 } 3702 } catch (Exception e) {} // sorry 3703 try { 3704 foreach (string s; File("~/.adrdoxignore_global".expandTilde).byLineCopy) { 3705 if (isComment(s)) continue; 3706 masks ~= trim(s); 3707 } 3708 } catch (Exception e) {} // sorry 3709 } 3710 3711 void loadGitIgnore (const(char)[] dir) { 3712 import std.path; 3713 import std.stdio; 3714 masks ~= null; 3715 try { 3716 foreach (string s; File(buildPath(dir, ".gitignore").expandTilde).byLineCopy) { 3717 if (isComment(s)) continue; 3718 masks ~= trim(s); 3719 } 3720 } catch (Exception e) {} // sorry 3721 try { 3722 foreach (string s; File(buildPath(dir, ".adrdoxignore").expandTilde).byLineCopy) { 3723 if (isComment(s)) continue; 3724 masks ~= trim(s); 3725 } 3726 } catch (Exception e) {} // sorry 3727 } 3728 3729 // unload latest gitignore 3730 void unloadGitIgnore () { 3731 auto ol = masks.length; 3732 while (masks.length > 0 && masks[$-1] !is null) masks = masks[0..$-1]; 3733 if (masks.length > 0 && masks[$-1] is null) masks = masks[0..$-1]; 3734 if (masks.length != ol) { 3735 //writeln("removed ", ol-masks.length, " lines"); 3736 masks.assumeSafeAppend; //hack! 3737 } 3738 } 3739 3740 bool match (string fname) { 3741 import std.path; 3742 import std.stdio; 3743 if (masks.length == 0) return false; 3744 //writeln("gitignore checking: <", fname, ">"); 3745 3746 bool xmatch (string path, string mask) { 3747 if (mask.length == 0 || path.length == 0) return false; 3748 import std.string : indexOf; 3749 if (mask.indexOf('/') < 0) return path.baseName.globMatch(mask); 3750 int xpos = cast(int)path.length-1; 3751 while (xpos >= 0) { 3752 while (xpos > 0 && path[xpos] != '/') --xpos; 3753 if (mask[0] == '/') { 3754 if (xpos+1 < path.length && path[xpos+1..$].globMatch(mask)) return true; 3755 } else { 3756 if (path[xpos..$].globMatch(mask)) return true; 3757 } 3758 --xpos; 3759 } 3760 return false; 3761 } 3762 3763 string curname = fname.baseName; 3764 int pos = cast(int)masks.length-1; 3765 // local dir matching 3766 while (pos >= 0 && masks[pos] !is null) { 3767 //writeln(" [", masks[pos], "]"); 3768 if (xmatch(curname, masks[pos])) { 3769 //writeln(" LOCAL HIT: [", masks[pos], "]: <", curname, ">"); 3770 return true; 3771 } 3772 if (masks[pos][0] == '/' && xmatch(curname, masks[pos][1..$])) return true; 3773 --pos; 3774 } 3775 curname = fname; 3776 while (pos >= 0) { 3777 if (masks[pos] !is null) { 3778 //writeln(" [", masks[pos], "]"); 3779 if (xmatch(curname, masks[pos])) { 3780 //writeln(" HIT: [", masks[pos], "]: <", curname, ">"); 3781 return true; 3782 } 3783 } 3784 --pos; 3785 } 3786 return false; 3787 } 3788 3789 static: 3790 inout(char)[] trim (inout(char)[] s) { 3791 while (s.length > 0 && s[0] <= ' ') s = s[1..$]; 3792 while (s.length > 0 && s[$-1] <= ' ') s = s[0..$-1]; 3793 return s; 3794 } 3795 3796 bool isComment (const(char)[] s) { 3797 s = trim(s); 3798 return (s.length == 0 || s[0] == '#'); 3799 } 3800 } 3801 3802 3803 string[] scanFiles (string basedir) { 3804 import std.file : isDir; 3805 import std.path; 3806 3807 if(basedir == "-") 3808 return ["-"]; 3809 3810 string[] res; 3811 3812 auto gi = new GitIgnore(); 3813 gi.loadGlobalGitIgnore(); 3814 3815 void scanSubDir(bool checkdir=true) (string dir) { 3816 import std.file; 3817 static if (checkdir) { 3818 string d = dir; 3819 if (d.length > 1 && d[$-1] == '/') d = d[0..$-1]; 3820 3821 //import std.stdio; writeln("***************** ", dir); 3822 if(!documentTest && d.length >= 5 && d[$-5 .. $] == "/test") 3823 return; 3824 3825 if (gi.match(d)) { 3826 //writeln("DIR SKIP: <", dir, ">"); 3827 return; 3828 } 3829 } 3830 gi.loadGitIgnore(dir); 3831 scope(exit) gi.unloadGitIgnore(); 3832 foreach (DirEntry de; dirEntries(dir, SpanMode.shallow)) { 3833 try { 3834 if (de.baseName.length == 0) continue; // just in case 3835 if (de.baseName[0] == '.') continue; // skip hidden files 3836 if (de.isDir) { scanSubDir(de.name); continue; } 3837 if (!de.baseName.globMatch("*.d")) continue; 3838 if (/*de.isFile &&*/ !gi.match(de.name)) { 3839 //writeln(de.name); 3840 res ~= de.name; 3841 } 3842 } catch (Exception e) {} // some checks (like `isDir`) can throw 3843 } 3844 } 3845 3846 basedir = basedir.expandTilde.absolutePath; 3847 if (basedir.isDir) { 3848 scanSubDir!false(basedir); 3849 } else { 3850 res ~= basedir; 3851 } 3852 return res; 3853 } 3854 3855 void writeFile(string filename, string content, bool gzip) { 3856 import std.zlib; 3857 import std.file; 3858 3859 if(gzip) { 3860 auto compress = new Compress(HeaderFormat.gzip); 3861 auto data = compress.compress(content); 3862 data ~= compress.flush(); 3863 3864 std.file.write(filename ~ ".gz", data); 3865 } else { 3866 std.file.write(filename, content); 3867 } 3868 } 3869 3870 __gshared bool generatingSource; 3871 __gshared bool blogMode = false; 3872 3873 int main(string[] args) { 3874 import std.stdio; 3875 import std.path : buildPath; 3876 import std.getopt; 3877 3878 static import std.file; 3879 LexerConfig config; 3880 StringCache stringCache = StringCache(128); 3881 3882 config.stringBehavior = StringBehavior.source; 3883 config.whitespaceBehavior = WhitespaceBehavior.include; 3884 3885 ModuleDecl[] moduleDecls; 3886 ModuleDecl[] moduleDeclsGenerate; 3887 ModuleDecl[string] moduleDeclsGenerateByName; 3888 3889 bool makeHtml = true; 3890 bool makeSearchIndex = false; 3891 string postgresConnectionString = null; 3892 string postgresVersionId = null; 3893 3894 string[] preloadArgs; 3895 3896 string[] linkReferences; 3897 3898 bool annotateSource = false; 3899 3900 string locateSymbol = null; 3901 bool gzip; 3902 bool copyStandardFiles = true; 3903 string headerTitle; 3904 3905 string texMath = "latex"; 3906 3907 string[] headerLinks; 3908 HeaderLink[] headerLinksParsed; 3909 3910 bool skipExisting = false; 3911 3912 string[] globPathInput; 3913 string dataDirPath; 3914 3915 int jobs = 0; 3916 3917 bool debugPrint; 3918 3919 auto opt = getopt(args, 3920 std.getopt.config.passThrough, 3921 std.getopt.config.bundling, 3922 "load", "Load for automatic cross-referencing, but do not generate for it", &preloadArgs, 3923 "link-references", "A file defining global link references", &linkReferences, 3924 "skeleton|s", "Location of the skeleton file, change to your use case, Default: skeleton.html", &skeletonFile, 3925 "directory|o", "Output directory of the html files", &outputDirectory, 3926 "write-private-docs|p", "Include documentation for `private` members (default: false)", &writePrivateDocs, 3927 "write-internal-modules", "Include documentation for modules named `internal` (default: false)", &documentInternal, 3928 "write-test-modules", "Include documentation for files in directories called `test` (default: false)", &documentTest, 3929 "locate-symbol", "Locate a symbol in the passed file", &locateSymbol, 3930 "genHtml|h", "Generate html, default: true", &makeHtml, 3931 "genSource|u", "Generate annotated source", &annotateSource, 3932 "genSearchIndex|i", "Generate search index, default: false", &makeSearchIndex, 3933 "postgresConnectionString", "Specify the postgres database to save search index to. If specified, you must also specify postgresVersionId", &postgresConnectionString, 3934 "postgresVersionId", "Specify the version_id to associate saved terms to. If specified, you must also specify postgresConnectionString", &postgresVersionId, 3935 "gzip|z", "Gzip generated files as they are created", &gzip, 3936 "copy-standard-files", "Copy standard JS/CSS files into target directory (default: true)", ©StandardFiles, 3937 "blog-mode", "Use adrdox as a static site generator for a blog", &blogMode, 3938 "header-title", "Title to put on the page header", &headerTitle, 3939 "header-link", "Link to add to the header (text=url)", &headerLinks, 3940 "document-undocumented", "Generate documentation even for undocumented symbols", &documentUndocumented, 3941 "minimal-descent", "Performs minimal descent into generating sub-pages", &minimalDescent, 3942 "case-insensitive-filenames", "Adjust generated filenames for case-insensitive file systems", &caseInsensitiveFilenames, 3943 "skip-existing", "Skip file generation for modules where the html already exists in the output dir", &skipExisting, 3944 "tex-math", "How TeX math should be processed (latex|katex, default=latex)", &texMath, 3945 "special-preprocessor", "Run a special preprocessor on comments. Only supported right now are gtk and dwt", &specialPreprocessor, 3946 "jobs|j", "Number of generation jobs to run at once (default=dependent on number of cpu cores", &jobs, 3947 "package-path", "Path to be prefixed to links for a particular D package namespace (package_pattern=link_prefix)", &globPathInput, 3948 "debug-print", "Print debugging information", &debugPrint, 3949 "data-dir", "Path to directory containing standard files (default=detect automatically)", &dataDirPath); 3950 3951 if (opt.helpWanted || args.length == 1) { 3952 defaultGetoptPrinter("A better D documentation generator\nCopyright © Adam D. Ruppe 2016-2021\n" ~ 3953 "Syntax: " ~ args[0] ~ " /path/to/your/package\n", opt.options); 3954 return 0; 3955 } 3956 3957 PostgreSql searchDb; 3958 3959 if(postgresVersionId.length || postgresConnectionString.length) { 3960 import std.stdio; 3961 version(with_postgres) { 3962 if(postgresVersionId.length == 0) { 3963 stderr.writeln("Required command line option `postgresVersionId` not set."); 3964 return 1; 3965 } 3966 if(postgresConnectionString.length == 0) { 3967 stderr.writeln("Required command line option `postgresConnectionString` not set. It must minimally reference an existing database like \"dbname=adrdox\"."); 3968 return 1; 3969 } 3970 3971 searchDb = new PostgreSql(postgresConnectionString); 3972 3973 try { 3974 foreach(res; searchDb.query("SELECT schema_version FROM adrdox_schema")) { 3975 if(res[0] != "1") { 3976 stderr.writeln("Unsupported adrdox_schema version. Maybe update your adrdox?"); 3977 return 1; 3978 } 3979 } 3980 } catch(DatabaseException e) { 3981 // probably table not existing, let's try to create it. 3982 try { 3983 searchDb.query(import("db.sql")); 3984 } catch(Exception e) { 3985 stderr.writeln("Database schema check failed: ", e.msg); 3986 stderr.writeln("Maybe try recreating the database and/or ensuring your user has full access."); 3987 return 1; 3988 } 3989 } 3990 3991 if(postgresVersionId == "auto") { 3992 // automatically determine one based on each file name; deferred for later. 3993 // FIXME 3994 } else { 3995 bool found = false; 3996 foreach(res; searchDb.query("SELECT id FROM package_version WHERE id = ?", postgresVersionId)) 3997 found = true; 3998 3999 if(!found) { 4000 stderr.writeln("package_version ID ", postgresVersionId, " does not exist in the database"); 4001 return 1; 4002 } 4003 } 4004 4005 } else { 4006 stderr.writeln("PostgreSql support not compiled in. Recompile adrdox with -version=with_postgres and try again."); 4007 return 1; 4008 } 4009 } 4010 4011 foreach(gpi; globPathInput) { 4012 auto idx = gpi.indexOf("="); 4013 string pathGlob; 4014 string dir; 4015 if(idx != -1) { 4016 pathGlob = gpi[0 .. idx]; 4017 dir = gpi[idx + 1 .. $]; 4018 } else { 4019 pathGlob = gpi; 4020 } 4021 4022 synchronized(directoriesForPackageMonitor) 4023 directoriesForPackage[pathGlob] = dir; 4024 } 4025 4026 if (checkDataDirectory(dataDirPath)) { 4027 // use data direcotory from command-line 4028 dataDirectory = dataDirPath; 4029 } else { 4030 import std.process: environment; 4031 4032 if (dataDirPath.length > 0) { 4033 writeln("Invalid data directory given from command line: " ~ dataDirPath); 4034 } 4035 4036 // try get data directory from environment 4037 dataDirPath = environment.get("ADRDOX_DATA_DIR"); 4038 4039 if (checkDataDirectory(dataDirPath)) { 4040 // use data directory from environment 4041 dataDirectory = dataDirPath; 4042 } else { 4043 if (dataDirPath.length > 0) { 4044 writeln("Invalid data directory given from environment variable: " ~ dataDirPath); 4045 } 4046 4047 // try detect data directory automatically 4048 if (!detectDataDirectory(dataDirectory)) { 4049 throw new Exception("Unable to determine data directory."); 4050 } 4051 } 4052 } 4053 4054 generatingSource = annotateSource; 4055 4056 if (outputDirectory[$-1] != '/') 4057 outputDirectory ~= '/'; 4058 4059 if (opt.helpWanted || args.length == 1) { 4060 defaultGetoptPrinter("A better D documentation generator\nCopyright © Adam D. Ruppe 2016-2018\n" ~ 4061 "Syntax: " ~ args[0] ~ " /path/to/your/package\n", opt.options); 4062 return 0; 4063 } 4064 4065 texMathOpt = parseTexMathOpt(texMath); 4066 4067 foreach(l; headerLinks) { 4068 auto idx = l.indexOf("="); 4069 if(idx == -1) 4070 continue; 4071 4072 HeaderLink lnk; 4073 lnk.text = l[0 .. idx].strip; 4074 lnk.url = l[idx + 1 .. $].strip; 4075 4076 headerLinksParsed ~= lnk; 4077 } 4078 4079 if(locateSymbol is null) { 4080 import std.file; 4081 4082 if (!exists(skeletonFile) && findStandardFile!false("skeleton-default.html").length) 4083 copyStandardFileTo!false(skeletonFile, "skeleton-default.html"); 4084 4085 if (!exists(outputDirectory)) 4086 mkdir(outputDirectory); 4087 4088 if(copyStandardFiles) { 4089 copyStandardFileTo(outputFilePath("style.css"), "style.css"); 4090 copyStandardFileTo(outputFilePath("script.js"), "script.js"); 4091 copyStandardFileTo(outputFilePath("search-docs.js"), "search-docs.js"); 4092 4093 switch (texMathOpt) with (TexMathOpt) { 4094 case KaTeX: { 4095 import adrdox.jstex; 4096 foreach (file; filesForKaTeX) { 4097 copyStandardFileTo(outputFilePath(file), "katex/" ~ file); 4098 } 4099 break; 4100 } 4101 default: break; 4102 } 4103 } 4104 } 4105 4106 // FIXME: maybe a zeroth path just grepping for a module declaration in located files 4107 // and making a mapping of module names, package listing, and files. 4108 // cuz reading all of Phobos takes several seconds. Then they can parse it fully lazily. 4109 4110 static void generateAnnotatedSource(ModuleDecl mod, bool gzip) { 4111 import std.file; 4112 auto annotatedSourceDocument = new Document(); 4113 annotatedSourceDocument.parseUtf8(readText(skeletonFile), true, true); 4114 4115 string fixupLink(string s) { 4116 if(!s.startsWith("http") && !s.startsWith("/")) 4117 return "../" ~ s; 4118 return s; 4119 } 4120 4121 foreach(ele; annotatedSourceDocument.querySelectorAll("a, link, script[src], form")) 4122 if(ele.tagName == "link") 4123 ele.attrs.href = "../" ~ ele.attrs.href; 4124 else if(ele.tagName == "form") 4125 ele.attrs.action = "../" ~ ele.attrs.action; 4126 else if(ele.tagName == "a") 4127 ele.attrs.href = fixupLink(ele.attrs.href); 4128 else 4129 ele.attrs.src = "../" ~ ele.attrs.src; 4130 4131 auto code = Element.make("pre", Html(linkUpHtml(highlight(cast(string) mod.originalSource), mod, "../", true))).addClass("d_code highlighted"); 4132 addLineNumbering(code, true); 4133 auto content = annotatedSourceDocument.requireElementById("page-content"); 4134 content.addChild(code); 4135 4136 auto nav = annotatedSourceDocument.requireElementById("page-nav"); 4137 4138 void addDeclNav(Element nav, Decl decl) { 4139 auto li = nav.addChild("li"); 4140 if(decl.docsShouldBeOutputted) 4141 li.addChild("a", "[Docs] ", fixupLink(decl.link)).addClass("docs"); 4142 li.addChild("a", decl.name, "#L" ~ to!string(decl.lineNumber == 0 ? 1 : decl.lineNumber)); 4143 if(decl.children.length) 4144 nav = li.addChild("ul"); 4145 foreach(child; decl.children) 4146 addDeclNav(nav, child); 4147 4148 } 4149 4150 auto sn = nav.addChild("div").setAttribute("id", "source-navigation"); 4151 4152 addDeclNav(sn.addChild("div").addClass("list-holder").addChild("ul"), mod); 4153 4154 annotatedSourceDocument.title = mod.name ~ " source code"; 4155 4156 auto outputSourcePath = outputFilePath("source"); 4157 if(!usePseudoFiles && !outputSourcePath.exists) 4158 mkdir(outputSourcePath); 4159 if(usePseudoFiles) 4160 pseudoFiles["source/" ~ mod.name ~ ".d.html"] = annotatedSourceDocument.toString(); 4161 else 4162 writeFile(outputFilePath("source", mod.name ~ ".d.html"), annotatedSourceDocument.toString(), gzip); 4163 } 4164 4165 void process(string arg, bool generate) { 4166 try { 4167 if(locateSymbol is null) 4168 writeln("First pass processing ", arg); 4169 import std.file; 4170 ubyte[] b; 4171 4172 if(arg == "-") { 4173 foreach(chunk; stdin.byChunk(4096)) 4174 b ~= chunk; 4175 } else 4176 b = cast(ubyte[]) read(arg); 4177 4178 config.fileName = arg; 4179 auto tokens = getTokensForParser(b, config, &stringCache); 4180 4181 import std.path : baseName; 4182 auto m = parseModule(tokens, baseName(arg)); 4183 4184 auto sweet = new Looker(b, baseName(arg)); 4185 sweet.visit(m); 4186 4187 4188 if(debugPrint) { 4189 debugPrintAst(m); 4190 } 4191 4192 ModuleDecl existingDecl; 4193 4194 auto mod = cast(ModuleDecl) sweet.root; 4195 4196 { 4197 mod.originalSource = b; 4198 if(mod.astNode.moduleDeclaration is null) 4199 throw new Exception("you must have a module declaration for this to work on it"); 4200 4201 if(b.startsWith(cast(ubyte[])"// just docs:")) 4202 sweet.root.justDocsTitle = (cast(string) b["// just docs:".length .. $].findSplitBefore(['\n'])[0].idup).strip; 4203 4204 synchronized(modulesByNameMonitor) { 4205 if(sweet.root.name !in modulesByName) { 4206 moduleDecls ~= mod; 4207 existingDecl = mod; 4208 4209 assert(mod !is null); 4210 modulesByName[sweet.root.name] = mod; 4211 } else { 4212 existingDecl = modulesByName[sweet.root.name]; 4213 } 4214 } 4215 } 4216 4217 if(generate) { 4218 4219 if(sweet.root.name !in moduleDeclsGenerateByName) { 4220 moduleDeclsGenerateByName[sweet.root.name] = existingDecl; 4221 moduleDeclsGenerate ~= existingDecl; 4222 4223 if(generatingSource) { 4224 generateAnnotatedSource(mod, gzip); 4225 } 4226 } 4227 } 4228 4229 //packages[sweet.root.packageName] ~= sweet.root; 4230 4231 4232 } catch (Throwable t) { 4233 writeln(t.toString()); 4234 } 4235 } 4236 4237 args = args[1 .. $]; // remove program name 4238 4239 foreach(arg; linkReferences) { 4240 import std.file; 4241 loadGlobalLinkReferences(readText(arg)); 4242 } 4243 4244 string[] generateFiles; 4245 foreach (arg; args) generateFiles ~= scanFiles(arg); 4246 /* 4247 foreach(argIdx, arg; args) { 4248 if(arg != "-" && std.file.isDir(arg)) 4249 foreach(string name; std.file.dirEntries(arg, "*.d", std.file.SpanMode.breadth)) 4250 generateFiles ~= name; 4251 else 4252 generateFiles ~= arg; 4253 } 4254 */ 4255 args = generateFiles; 4256 //{ import std.stdio; foreach (fn; args) writeln(fn); } assert(0); 4257 4258 4259 // Process them all first so name-lookups have more chance of working 4260 foreach(argIdx, arg; preloadArgs) { 4261 if(std.file.isDir(arg)) { 4262 foreach(string name; std.file.dirEntries(arg, "*.d", std.file.SpanMode.breadth)) { 4263 bool g = false; 4264 if(locateSymbol is null) 4265 foreach(idx, a; args) { 4266 if(a == name) { 4267 g = true; 4268 args[idx] = args[$-1]; 4269 args = args[0 .. $-1]; 4270 break; 4271 } 4272 } 4273 4274 process(name, g); 4275 } 4276 } else { 4277 bool g = false; 4278 4279 if(locateSymbol is null) 4280 foreach(idx, a; args) { 4281 if(a == arg) { 4282 g = true; 4283 args[idx] = args[$-1]; 4284 args = args[0 .. $-1]; 4285 break; 4286 } 4287 } 4288 4289 process(arg, g); 4290 } 4291 } 4292 4293 foreach(argIdx, arg; args) { 4294 process(arg, locateSymbol is null ? true : false); 4295 } 4296 4297 if(locateSymbol !is null) { 4298 auto decl = moduleDecls[0].lookupName(locateSymbol); 4299 if(decl is null) 4300 writeln("not found ", locateSymbol); 4301 else 4302 writeln(decl.lineNumber); 4303 return 0; 4304 } 4305 4306 // create dummy packages for those not found in the source 4307 // this makes linking far more sane, without requiring package.d 4308 // everywhere (though I still strongly recommending you write them!) 4309 // I'm using for instead of foreach so I can append in the loop 4310 // and keep it going 4311 for(size_t i = 0; i < moduleDecls.length; i++ ) { 4312 auto decl = moduleDecls[i]; 4313 auto pkg = decl.packageName; 4314 if(decl.name == "index") 4315 continue; // avoid infinite recursion 4316 if(pkg is null) 4317 pkg = "index";//continue; // to create an index.html listing all top level things 4318 synchronized(modulesByNameMonitor) 4319 if(pkg !in modulesByName) { 4320 writeln("Making FAKE package for ", pkg); 4321 config.fileName = "dummy"; 4322 auto b = cast(ubyte[]) (`/++ 4323 +/ module `~pkg~`; `); 4324 auto tokens = getTokensForParser(b, config, &stringCache); 4325 auto m = parseModule(tokens, "dummy"); 4326 auto sweet = new Looker(b, "dummy"); 4327 sweet.visit(m); 4328 4329 auto mod = cast(ModuleDecl) sweet.root; 4330 4331 mod.fakeDecl = true; 4332 4333 moduleDecls ~= mod; 4334 modulesByName[pkg] = mod; 4335 4336 // only generate a fake one if the real one isn't already there 4337 // like perhaps the real one was generated before but just not loaded 4338 // this time. 4339 if(!std.file.exists(outputFilePath(mod.link))) 4340 moduleDeclsGenerate ~= mod; 4341 } 4342 } 4343 4344 // add modules to their packages, if possible 4345 foreach(decl; moduleDecls) { 4346 auto pkg = decl.packageName; 4347 if(decl.name == "index") continue; // avoid infinite recursion 4348 if(pkg.length == 0) { 4349 //continue; 4350 pkg = "index"; 4351 } 4352 synchronized(modulesByNameMonitor) 4353 if(auto a = pkg in modulesByName) { 4354 (*a).addChild(decl); 4355 } else assert(0, pkg ~ " " ~ decl.toString); // it should have make a fake package above 4356 } 4357 4358 4359 version(with_http_server) { 4360 import arsd.cgi; 4361 4362 void serveFiles(Cgi cgi) { 4363 4364 import std.file; 4365 4366 string file = cgi.requestUri; 4367 4368 auto slash = file.lastIndexOf("/"); 4369 bool wasSource = file.indexOf("source/") != -1; 4370 4371 if(slash != -1) 4372 file = file[slash + 1 .. $]; 4373 4374 if(wasSource) 4375 file = "source/" ~ file; 4376 4377 if(file == "style.css") { 4378 cgi.setResponseContentType("text/css"); 4379 cgi.write(readText(findStandardFile("style.css")), true); 4380 return; 4381 } else if(file == "script.js") { 4382 cgi.setResponseContentType("text/javascript"); 4383 cgi.write(readText(findStandardFile("script.js")), true); 4384 return; 4385 } else if(file == "search-docs.js") { 4386 cgi.setResponseContentType("text/javascript"); 4387 cgi.write(readText(findStandardFile("search-docs.js")), true); 4388 return; 4389 } else { 4390 if(file.length == 0) { 4391 if("index" !in pseudoFiles) 4392 writeHtml(modulesByName["index"], true, false, headerTitle, headerLinksParsed); 4393 cgi.write(pseudoFiles["index"], true); 4394 return; 4395 } else { 4396 auto of = file; 4397 4398 if(file !in pseudoFiles) { 4399 ModuleDecl* declPtr; 4400 file = file[0 .. $-5]; // cut off ".html" 4401 if(wasSource) { 4402 file = file["source/".length .. $]; 4403 file = file[0 .. $-2]; // cut off ".d" 4404 } 4405 while((declPtr = file in modulesByName) is null) { 4406 auto idx = file.lastIndexOf("."); 4407 if(idx == -1) 4408 break; 4409 file = file[0 .. idx]; 4410 } 4411 4412 if(declPtr !is null) { 4413 if(wasSource) { 4414 generateAnnotatedSource(*declPtr, false); 4415 } else { 4416 if(!(*declPtr).alreadyGenerated) 4417 writeHtml(*declPtr, true, false, headerTitle, headerLinksParsed); 4418 (*declPtr).alreadyGenerated = true; 4419 } 4420 } 4421 } 4422 4423 file = of; 4424 4425 if(file in pseudoFiles) 4426 cgi.write(pseudoFiles[file], true); 4427 else { 4428 cgi.setResponseStatus("404 Not Found"); 4429 cgi.write("404 " ~ file, true); 4430 } 4431 return; 4432 } 4433 } 4434 4435 cgi.setResponseStatus("404 Not Found"); 4436 cgi.write("404", true); 4437 } 4438 4439 mixin CustomCgiMain!(Cgi, serveFiles); 4440 4441 processPoolSize = 1; 4442 4443 usePseudoFiles = true; 4444 4445 writeln("\n\nListening on http port 8999...."); 4446 4447 cgiMainImpl(["server", "--port", "8999"]); 4448 return 0; 4449 } 4450 4451 import std.parallelism; 4452 if(jobs > 1) 4453 defaultPoolThreads = jobs; 4454 4455 version(linux) 4456 if(makeSearchIndex && makeHtml) { 4457 import core.sys.posix.unistd; 4458 if(fork()) { 4459 makeSearchIndex = false; // this fork focuses on html 4460 //mustWait = true; 4461 } else { 4462 makeHtml = false; // and this one does the search 4463 } 4464 } 4465 4466 if(makeHtml) { 4467 bool[string] alreadyTried; 4468 4469 void helper(size_t idx, ModuleDecl decl) { 4470 //if(decl.parent && moduleDeclsGenerate.canFind(decl.parent)) 4471 //continue; // it will be written in the list of children. actually i want to do it all here. 4472 4473 // FIXME: make search index in here if we can 4474 if(!skipExisting || !std.file.exists(outputFilePath(decl.link(true) ~ (gzip ?".gz":"")))) { 4475 if(decl.name in alreadyTried) 4476 return; 4477 alreadyTried[decl.name] = true; 4478 writeln("Generating HTML for ", decl.name); 4479 writeHtml(decl, true, gzip, headerTitle, headerLinksParsed); 4480 } 4481 4482 writeln(idx + 1, "/", moduleDeclsGenerate.length, " completed"); 4483 } 4484 4485 if(jobs == 1) 4486 foreach(idx, decl; moduleDeclsGenerate) { 4487 helper(idx, decl); 4488 } 4489 else 4490 foreach(idx, decl; parallel(moduleDeclsGenerate)) { 4491 helper(idx, decl); 4492 } 4493 } 4494 4495 if(makeSearchIndex) { 4496 4497 // we need the listing and the search index 4498 FileProxy index; 4499 int id; 4500 4501 static import std.file; 4502 4503 // write out the landing page for JS search, 4504 // see the comment in the source of that html 4505 // for more details 4506 auto searchDocsHtml = std.file.readText(findStandardFile("search-docs.html")); 4507 writeFile(outputFilePath("search-docs.html"), searchDocsHtml, gzip); 4508 4509 4510 // the search index is a HTML page containing some script 4511 // and the index XML. See the source of search-docs.js for more info. 4512 index = FileProxy(outputFilePath("search-results.html"), gzip); 4513 4514 auto skeletonDocument = new Document(); 4515 skeletonDocument.parseUtf8(std.file.readText(skeletonFile), true, true); 4516 auto skeletonText = skeletonDocument.toString(); 4517 4518 auto idx = skeletonText.indexOf("</body>"); 4519 if(idx == -1) throw new Exception("skeleton missing body element"); 4520 4521 // write out the skeleton... 4522 index.writeln(skeletonText[0 .. idx]); 4523 4524 // and then the data container for the xml 4525 index.writeln(`<script type="text/xml" id="search-index-container">`); 4526 4527 index.writeln("<adrdox>"); 4528 4529 4530 // delete the existing stuff so we do a full update in this run 4531 version(with_postgres) { 4532 if(searchDb && postgresVersionId) { 4533 searchDb.query("START TRANSACTION"); 4534 searchDb.query("DELETE FROM auto_generated_tags WHERE package_version_id = ?", postgresVersionId); 4535 } 4536 scope(exit) 4537 if(searchDb && postgresVersionId) { 4538 searchDb.query("ROLLBACK"); 4539 } 4540 } 4541 4542 4543 index.writeln("<listing>"); 4544 foreach(decl; moduleDeclsGenerate) { 4545 if(decl.fakeDecl) 4546 continue; 4547 writeln("Listing ", decl.name); 4548 4549 writeIndexXml(decl, index, id, postgresVersionId, searchDb); 4550 } 4551 index.writeln("</listing>"); 4552 4553 // also making the search index 4554 foreach(decl; moduleDeclsGenerate) { 4555 if(decl.fakeDecl) 4556 continue; 4557 writeln("Generating search for ", decl.name); 4558 4559 generateSearchIndex(decl, searchDb); 4560 } 4561 4562 writeln("Writing search..."); 4563 4564 version(with_postgres) 4565 if(searchDb) { 4566 searchDb.flushSearchDatabase(); 4567 searchDb.query("COMMIT"); 4568 } 4569 4570 index.writeln("<index>"); 4571 foreach(term, arr; searchTerms) { 4572 index.write("<term value=\""~xmlEntitiesEncode(term)~"\">"); 4573 foreach(item; arr) { 4574 index.write("<result decl=\""~to!string(item.declId)~"\" score=\""~to!string(item.score)~"\" />"); 4575 } 4576 index.writeln("</term>"); 4577 } 4578 index.writeln("</index>"); 4579 index.writeln("</adrdox>"); 4580 4581 // finish the container 4582 index.writeln("</script>"); 4583 4584 // write the script that runs the search 4585 index.writeln("<script src=\"search-docs.js\"></script>"); 4586 4587 // and close the skeleton 4588 index.writeln("</body></html>"); 4589 index.close(); 4590 } 4591 4592 4593 //import std.stdio; 4594 //writeln("press any key to continue"); 4595 //readln(); 4596 4597 return 0; 4598 } 4599 4600 struct FileProxy { 4601 import std.zlib; 4602 File f; // it will inherit File's refcounting 4603 Compress compress; // and compress is gc'd anyway so copying the ref means same object! 4604 bool gzip; 4605 4606 this(string filename, bool gzip) { 4607 f = File(filename ~ (gzip ? ".gz" : ""), gzip ? "wb" : "wt"); 4608 if(gzip) 4609 compress = new Compress(HeaderFormat.gzip); 4610 this.gzip = gzip; 4611 } 4612 4613 void writeln(string s) { 4614 if(gzip) 4615 f.rawWrite(compress.compress(s ~ "\n")); 4616 else 4617 f.writeln(s); 4618 } 4619 4620 void write(string s) { 4621 if(gzip) 4622 f.rawWrite(compress.compress(s)); 4623 else 4624 f.write(s); 4625 } 4626 4627 void close() { 4628 if(gzip) 4629 f.rawWrite(compress.flush()); 4630 f.close(); 4631 } 4632 } 4633 4634 struct SearchResult { 4635 int declId; 4636 int score; 4637 } 4638 4639 string[] splitIdentifier(string name) { 4640 string[] ret; 4641 4642 bool isUpper(dchar c) { 4643 return c >= 'A' && c <= 'Z'; 4644 } 4645 4646 bool breakOnNext; 4647 dchar lastChar; 4648 foreach(dchar ch; name) { 4649 if(ch == '_') { 4650 breakOnNext = true; 4651 continue; 4652 } 4653 if(breakOnNext || ret.length == 0 || (isUpper(ch) && !isUpper(lastChar))) { 4654 if(ret.length == 0 || ret[$-1].length) 4655 ret ~= ""; 4656 } 4657 breakOnNext = false; 4658 ret[$-1] ~= ch; 4659 lastChar = ch; 4660 } 4661 4662 return ret; 4663 } 4664 4665 SearchResult[][string] searchTerms; 4666 string searchInsertToBeFlushed; 4667 string postgresVersionIdGlobal; // total hack!!! 4668 4669 void saveSearchTerm(PostgreSql searchDb, string term, SearchResult sr, bool isDeep = false) { 4670 if(searchDb is null || !isDeep) { 4671 searchTerms[term] ~= sr; // save the things for offline xml too 4672 } 4673 version(with_postgres) { 4674 if(searchDb !is null) { 4675 if(searchInsertToBeFlushed.length > 4096) 4676 searchDb.flushSearchDatabase(); 4677 4678 if(searchInsertToBeFlushed.length) 4679 searchInsertToBeFlushed ~= ", "; 4680 searchInsertToBeFlushed ~= "('"; 4681 searchInsertToBeFlushed ~= searchDb.escape(term); 4682 searchInsertToBeFlushed ~= "', "; 4683 searchInsertToBeFlushed ~= to!string(sr.declId); 4684 searchInsertToBeFlushed ~= ", "; 4685 searchInsertToBeFlushed ~= to!string(sr.score); 4686 searchInsertToBeFlushed ~= ", "; 4687 searchInsertToBeFlushed ~= to!string(postgresVersionIdGlobal); 4688 searchInsertToBeFlushed ~= ")"; 4689 } 4690 } 4691 } 4692 4693 void flushSearchDatabase(PostgreSql searchDb) { 4694 if(searchDb is null) 4695 return; 4696 else version(with_postgres) { 4697 if(searchInsertToBeFlushed.length) { 4698 searchDb.query("INSERT INTO auto_generated_tags (tag, d_symbols_id, score, package_version_id) VALUES " ~ searchInsertToBeFlushed); 4699 4700 searchInsertToBeFlushed = searchInsertToBeFlushed[$..$]; 4701 //searchInsertToBeFlushed.assumeSafeAppend; 4702 } 4703 } 4704 } 4705 4706 void generateSearchIndex(Decl decl, PostgreSql searchDb) { 4707 /* 4708 if((*cast(void**) decl) is null) 4709 return; 4710 scope(exit) { 4711 (cast(ubyte*) decl)[0 .. typeid(decl).initializer.length] = 0; 4712 } 4713 */ 4714 4715 if(decl.databaseId == 0) 4716 return; 4717 4718 if(!decl.docsShouldBeOutputted) 4719 return; 4720 if(cast(ImportDecl) decl) 4721 return; // never write imports, it can overwrite the actual thing 4722 4723 // this needs to match the id in index.xml! 4724 const tid = decl.databaseId; 4725 4726 // FIXME: if it is undocumented in source, give it a score penalty. 4727 4728 // exact match on FQL is always a great match 4729 searchDb.saveSearchTerm(decl.fullyQualifiedName, SearchResult(tid, 50)); 4730 4731 // names like GC.free should be a solid match too 4732 4733 string partialName; 4734 if(!decl.isModule) 4735 partialName = decl.fullyQualifiedName[decl.parentModule.name.length + 1 .. $]; 4736 4737 if(partialName.length) 4738 searchDb.saveSearchTerm(partialName, SearchResult(tid, 35)); 4739 4740 if(decl.name != "this") { 4741 // exact match on specific name is worth something too 4742 searchDb.saveSearchTerm(decl.name, SearchResult(tid, 25)); 4743 4744 if(decl.isModule) { 4745 // module names like std.stdio should match stdio strongly, 4746 // and std is ok too. I will break them by dot and give diminsihing 4747 // returns. 4748 int score = 25; 4749 foreach_reverse(part; decl.name.split(".")) { 4750 searchDb.saveSearchTerm(part, SearchResult(tid, score)); 4751 score -= 10; 4752 if(score <= 0) 4753 break; 4754 } 4755 } 4756 4757 // and so is fuzzy match 4758 if(decl.name != decl.name.toLower) { 4759 searchDb.saveSearchTerm(decl.name.toLower, SearchResult(tid, 15)); 4760 } 4761 if(partialName.length && partialName != decl.name) 4762 if(partialName != partialName.toLower) 4763 searchDb.saveSearchTerm(partialName.toLower, SearchResult(tid, 20)); 4764 4765 // and so is partial word match 4766 auto splitNames = splitIdentifier(decl.name); 4767 if(splitNames.length) { 4768 foreach(name; splitNames) { 4769 searchDb.saveSearchTerm(name, SearchResult(tid, 6)); 4770 if(name != name.toLower) 4771 searchDb.saveSearchTerm(name.toLower, SearchResult(tid, 3)); 4772 } 4773 } 4774 } 4775 4776 // and we want to match parent names, though worth less. 4777 version(none) { 4778 Decl parent = decl.parent; 4779 while(parent !is null) { 4780 searchDb.saveSearchTerm(parent.name, SearchResult(tid, 5)); 4781 if(parent.name != parent.name.toLower) 4782 searchDb.saveSearchTerm(parent.name.toLower, SearchResult(tid, 2)); 4783 4784 auto splitNames = splitIdentifier(parent.name); 4785 if(splitNames.length) { 4786 foreach(name; splitNames) { 4787 searchDb.saveSearchTerm(name, SearchResult(tid, 3)); 4788 if(name != name.toLower) 4789 searchDb.saveSearchTerm(name.toLower, SearchResult(tid, 2)); 4790 } 4791 } 4792 4793 4794 parent = parent.parent; 4795 } 4796 } 4797 4798 bool deepSearch = searchDb !is null; 4799 4800 if(deepSearch) { 4801 Document document; 4802 //if(decl.fullyQualifiedName in generatedDocuments) 4803 //document = generatedDocuments[decl.fullyQualifiedName]; 4804 //else 4805 document = null;// writeHtml(decl, false, false, null, null); 4806 //assert(document !is null); 4807 4808 // FIXME: pulling this from the generated html is a bit inefficient. 4809 4810 bool[const(char)[]] wordsUsed; 4811 4812 // tags are worth a lot 4813 version(none) 4814 foreach(tag; document.querySelectorAll(".tag")) { 4815 if(tag.attrs.name in wordsUsed) continue; 4816 wordsUsed[tag.attrs.name] = true; 4817 searchDb.saveSearchTerm(tag.attrs.name, SearchResult(tid, to!int(tag.attrs.value.length ? tag.attrs.value : "0")), true); 4818 } 4819 4820 // and other names that are referenced are worth quite a bit. 4821 version(none) 4822 foreach(tag; document.querySelectorAll(".xref")) { 4823 if(tag.innerText in wordsUsed) continue; 4824 wordsUsed[tag.innerText] = true; 4825 searchDb.saveSearchTerm(tag.innerText, SearchResult(tid, tag.hasClass("parent-class") ? 10 : 5), true); 4826 } 4827 /* 4828 foreach(tag; document.querySelectorAll("a[data-ident][title]")) 4829 searchDb.saveSearchTerm(tag.dataset.ident, SearchResult(tid, 3), true); 4830 foreach(tag; document.querySelectorAll("a.hid[title]")) 4831 searchDb.saveSearchTerm(tag.innerText, SearchResult(tid, 3), true); 4832 */ 4833 4834 // and full-text search. limited to first paragraph for speed reasons, hoping it is good enough for practical purposes 4835 import ps = PorterStemmer; 4836 ps.PorterStemmer s; 4837 //foreach(tag; document.querySelectorAll(".documentation-comment.synopsis > p")){ //:first-of-type")) { 4838 //foreach(word; getWords(tag.innerText)) { 4839 foreach(word; getWords(decl.parsedDocComment.ddocSummary ~ "\n" ~ decl.parsedDocComment.synopsis)) { 4840 auto w = s.stem(word.toLower); 4841 if(w.length < 3) continue; 4842 if(w.isIrrelevant()) 4843 continue; 4844 if(w in wordsUsed) 4845 continue; 4846 wordsUsed[w] = true; 4847 searchDb.saveSearchTerm(s.stem(word.toLower).idup, SearchResult(tid, 1), true); 4848 } 4849 //} 4850 } 4851 4852 foreach(child; decl.children) 4853 generateSearchIndex(child, searchDb); 4854 } 4855 4856 bool isIrrelevant(in char[] s) { 4857 switch(s) { 4858 foreach(w; irrelevantWordList) 4859 case w: return true; 4860 default: return false; 4861 } 4862 } 4863 4864 // These are common words in English, which I'm generally 4865 // ignoring because they happen so often that they probably 4866 // aren't relevant keywords 4867 import std.meta; 4868 alias irrelevantWordList = AliasSeq!( 4869 "undocumented", 4870 "source", 4871 "intended", 4872 "author", 4873 "warned", 4874 "the", 4875 "of", 4876 "and", 4877 "a", 4878 "to", 4879 "in", 4880 "is", 4881 "you", 4882 "that", 4883 "it", 4884 "he", 4885 "was", 4886 "for", 4887 "on", 4888 "are", 4889 "as", 4890 "with", 4891 "his", 4892 "they", 4893 "I", 4894 "at", 4895 "be", 4896 "this", 4897 "have", 4898 "from", 4899 "or", 4900 "one", 4901 "had", 4902 "by", 4903 "word", 4904 "but", 4905 "not", 4906 "what", 4907 "all", 4908 "were", 4909 "we", 4910 "when", 4911 "your", 4912 "can", 4913 "said", 4914 "there", 4915 "use", 4916 "an", 4917 "each", 4918 "which", 4919 "she", 4920 "do", 4921 "how", 4922 "their", 4923 "if", 4924 "will", 4925 "up", 4926 "other", 4927 "about", 4928 "out", 4929 "many", 4930 "then", 4931 "them", 4932 "these", 4933 "so", 4934 "some", 4935 "her", 4936 "would", 4937 "make", 4938 "like", 4939 "him", 4940 "into", 4941 "time", 4942 "has", 4943 "look", 4944 "two", 4945 "more", 4946 "write", 4947 "go", 4948 "see", 4949 "number", 4950 "no", 4951 "way", 4952 "could", 4953 "people", 4954 "my", 4955 "than", 4956 "first", 4957 "water", 4958 "been", 4959 "call", 4960 "who", 4961 "its", 4962 "now", 4963 "find", 4964 "long", 4965 "down", 4966 "day", 4967 "did", 4968 "get", 4969 "come", 4970 "made", 4971 "may", 4972 "part", 4973 ); 4974 4975 string[] getWords(string text) { 4976 string[] words; 4977 string currentWord; 4978 4979 import std.uni; 4980 foreach(dchar ch; text) { 4981 if(!isAlpha(ch)) { 4982 if(currentWord.length) 4983 words ~= currentWord; 4984 currentWord = null; 4985 } else { 4986 currentWord ~= ch; 4987 } 4988 } 4989 4990 return words; 4991 } 4992 4993 import std.stdio : File; 4994 4995 int getNestingLevel(Decl decl) { 4996 int count = 0; 4997 while(decl && !decl.isModule) { 4998 decl = decl.parent; 4999 count++; 5000 } 5001 return count; 5002 } 5003 5004 void writeIndexXml(Decl decl, FileProxy index, ref int id, string postgresVersionId, PostgreSql searchDb) { 5005 //import std.stdio;writeln(decl.fullyQualifiedName, " ", decl.isPrivate, " ", decl.isDocumented); 5006 if(!decl.docsShouldBeOutputted) 5007 return; 5008 if(cast(ImportDecl) decl) 5009 return; // never write imports, it can overwrite the actual thing 5010 5011 auto cc = decl.parsedDocComment; 5012 5013 auto desc = formatDocumentationComment(cc.ddocSummary, decl); 5014 5015 .postgresVersionIdGlobal = postgresVersionId; 5016 5017 if(searchDb is null) 5018 decl.databaseId = ++id; 5019 else version(with_postgres) { 5020 5021 // 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 5022 // searchDb.query("DELETE FROM d_symbols WHERE package_version_id = ?", postgresVersionId); 5023 foreach(res; searchDb.query("SELECT id FROM d_symbols WHERE package_version_id = ? AND fully_qualified_name = ?", postgresVersionId, decl.fullyQualifiedName)) { 5024 decl.databaseId = res[0].to!int; 5025 } 5026 5027 if(decl.databaseId == 0) 5028 foreach(res; searchDb.query("INSERT INTO d_symbols 5029 (package_version_id, name, nesting_level, module_name, fully_qualified_name, url_name, summary) 5030 VALUES 5031 (?, ?, ?, ?, ?, ?, ?) 5032 RETURNING id", 5033 postgresVersionId, 5034 decl.name, 5035 getNestingLevel(decl), 5036 decl.parentModule.name, 5037 decl.fullyQualifiedName, 5038 decl.link, 5039 desc 5040 )) 5041 { 5042 decl.databaseId = res[0].to!int; 5043 } 5044 else 5045 { 5046 searchDb.query("UPDATE d_symbols 5047 SET 5048 url_name = ?, 5049 summary = ? 5050 WHERE 5051 id = ? 5052 ", decl.link, desc, decl.databaseId); 5053 } 5054 } 5055 5056 // the id needs to match the search index! 5057 index.write("<decl id=\"" ~ to!string(decl.databaseId) ~ "\" type=\""~decl.declarationType~"\">"); 5058 5059 index.write("<name>" ~ xmlEntitiesEncode(decl.name) ~ "</name>"); 5060 index.write("<desc>" ~ xmlEntitiesEncode(desc) ~ "</desc>"); 5061 index.write("<link>" ~ xmlEntitiesEncode(decl.link) ~ "</link>"); 5062 5063 foreach(child; decl.children) 5064 writeIndexXml(child, index, id, postgresVersionId, searchDb); 5065 5066 index.write("</decl>"); 5067 } 5068 5069 string pluralize(string word, int count = 2, string pluralWord = null) { 5070 if(word.length == 0) 5071 return word; 5072 5073 if(count == 1) 5074 return word; // it isn't actually plural 5075 5076 if(pluralWord !is null) 5077 return pluralWord; 5078 5079 switch(word[$ - 1]) { 5080 case 's': 5081 case 'a', 'i', 'o', 'u': 5082 return word ~ "es"; 5083 case 'f': 5084 return word[0 .. $-1] ~ "ves"; 5085 case 'y': 5086 return word[0 .. $-1] ~ "ies"; 5087 default: 5088 return word ~ "s"; 5089 } 5090 } 5091 5092 Html toLinkedHtml(T)(const T t, Decl decl) { 5093 import dparse.formatter; 5094 string s; 5095 struct Foo { 5096 void put(in char[] a) { 5097 s ~= a; 5098 } 5099 } 5100 Foo output; 5101 auto f = new MyFormatter!(typeof(output))(output); 5102 f.format(t); 5103 5104 return Html(linkUpHtml(s, decl)); 5105 } 5106 5107 string linkUpHtml(string s, Decl decl, string base = "", bool linkToSource = false) { 5108 auto document = new Document("<root>" ~ s ~ "</root>", true, true); 5109 5110 // additional cross referencing we weren't able to do at lower level 5111 foreach(ident; document.querySelectorAll("*:not(a) [data-ident]:not(:has(a)), .hid")) { 5112 // since i modify the tree in the loop, i recheck that we still match the selector 5113 if(ident.parentNode is null) 5114 continue; 5115 if(ident.tagName == "a" || (ident.parentNode && ident.parentNode.tagName == "a")) 5116 continue; 5117 string i = ident.hasAttribute("data-ident") ? ident.dataset.ident : ident.innerText; 5118 5119 auto n = ident.nextSibling; 5120 while(n && n.nodeValue == ".") { 5121 i ~= "."; 5122 auto txt = n; 5123 n = n.nextSibling; // the span, ideally 5124 if(n is null) 5125 break; 5126 if(n && (n.hasAttribute("data-ident") || n.hasClass("hid"))) { 5127 txt.removeFromTree(); 5128 i ~= n.hasAttribute("data-ident") ? n.dataset.ident : n.innerText; 5129 auto span = n; 5130 n = n.nextSibling; 5131 span.removeFromTree; 5132 } 5133 } 5134 5135 //ident.dataset.ident = i; 5136 ident.innerText = i; 5137 5138 auto found = decl.lookupName(i); 5139 string hash; 5140 5141 if(found is null) { 5142 auto lastPieceIdx = i.lastIndexOf("."); 5143 if(lastPieceIdx != -1) { 5144 found = decl.lookupName(i[0 .. lastPieceIdx]); 5145 if(found) 5146 hash = "#" ~ i[lastPieceIdx + 1 .. $]; 5147 } 5148 } 5149 5150 if(found) { 5151 auto overloads = found.getImmediateDocumentedOverloads(); 5152 if(overloads.length) 5153 found = overloads[0]; 5154 } 5155 5156 void linkToDoc() { 5157 if(found && found.docsShouldBeOutputted) { 5158 ident.attrs.title = found.fullyQualifiedName; 5159 ident.tagName = "a"; 5160 ident.href = base ~ found.link ~ hash; 5161 } 5162 } 5163 5164 if(linkToSource) { 5165 if(found && linkToSource && found.parentModule) { 5166 ident.attrs.title = found.fullyQualifiedName; 5167 ident.tagName = "a"; 5168 ident.href = found.parentModule.name ~ ".d.html#L" ~ to!string(found.lineNumber); 5169 } 5170 } else { 5171 linkToDoc(); 5172 } 5173 } 5174 5175 return document.root.innerHTML; 5176 } 5177 5178 5179 Html toHtml(T)(const T t) { 5180 import dparse.formatter; 5181 string s; 5182 struct Foo { 5183 void put(in char[] a) { 5184 s ~= a; 5185 } 5186 } 5187 Foo output; 5188 auto f = new Formatter!(typeof(output))(output); 5189 f.format(t); 5190 5191 return Html("<tt class=\"highlighted\">"~highlight(s)~"</tt>"); 5192 } 5193 5194 string toText(T)(const T t) { 5195 import dparse.formatter; 5196 string s; 5197 struct Foo { 5198 void put(in char[] a) { 5199 s ~= a; 5200 } 5201 } 5202 Foo output; 5203 auto f = new Formatter!(typeof(output))(output); 5204 f.format(t); 5205 5206 return s; 5207 } 5208 5209 string toId(string txt) { 5210 string id; 5211 bool justSawSpace; 5212 foreach(ch; txt) { 5213 if(ch < 127) { 5214 if(ch >= 'A' && ch <= 'Z') { 5215 id ~= ch + 32; 5216 } else if(ch == ' ') { 5217 if(!justSawSpace) 5218 id ~= '-'; 5219 } else { 5220 id ~= ch; 5221 } 5222 } else { 5223 id ~= ch; 5224 } 5225 justSawSpace = ch == ' '; 5226 } 5227 return id.strip; 5228 } 5229 5230 void debugPrintAst(T)(T m) { 5231 import std.stdio; 5232 import dscanner.astprinter; 5233 auto printer = new XMLPrinter; 5234 printer.visit(m); 5235 5236 writeln(new XmlDocument(printer.output).toPrettyString); 5237 } 5238 5239 5240 /* 5241 This file contains code from https://github.com/economicmodeling/harbored/ 5242 5243 Those portions are Copyright 2014 Economic Modeling Specialists, Intl., 5244 written by Brian Schott, made available under the following license: 5245 5246 Boost Software License - Version 1.0 - August 17th, 2003 5247 5248 Permission is hereby granted, free of charge, to any person or organization 5249 obtaining a copy of the software and accompanying documentation covered by 5250 this license (the "Software") to use, reproduce, display, distribute, 5251 execute, and transmit the Software, and to prepare derivative works of the 5252 Software, and to permit third-parties to whom the Software is furnished to 5253 do so, all subject to the following: 5254 5255 The copyright notices in the Software and this entire statement, including 5256 the above license grant, this restriction and the following disclaimer, 5257 must be included in all copies of the Software, in whole or in part, and 5258 all derivative works of the Software, unless such copies or derivative 5259 works are solely in the form of machine-executable object code generated by 5260 a source language processor. 5261 5262 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 5263 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 5264 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 5265 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 5266 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 5267 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 5268 DEALINGS IN THE SOFTWARE. 5269 */