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