Skip to content
Permalink
master
Switch branches/tags
Go to file
 
 
Cannot retrieve contributors at this time
module adrdox.main;
// version=with_http_server
// version=with_postgres
__gshared string dataDirectory;
__gshared string skeletonFile = "skeleton.html";
__gshared string outputDirectory = "generated-docs";
__gshared TexMathOpt texMathOpt = TexMathOpt.LaTeX;
__gshared bool writePrivateDocs = false;
__gshared bool documentInternal = false;
__gshared bool documentTest = false;
__gshared bool documentUndocumented = false;
__gshared bool minimalDescent = false;
version(linux)
__gshared bool caseInsensitiveFilenames = false;
else
__gshared bool caseInsensitiveFilenames = true;
/*
Glossary feature: little short links that lead somewhere else.
FIXME: it should be able to handle bom. consider core/thread.d the unittest example is offset.
FIXME:
* make sure there's a link to the source for everything
* search
* package index without needing to build everything at once
* version specifiers
* prettified constraints
*/
import dparse.parser;
import dparse.lexer;
import dparse.ast;
import arsd.dom;
import arsd.docgen.comment;
version(with_postgres)
import arsd.postgres;
else
private alias PostgreSql = typeof(null);
import std.algorithm :sort, canFind;
import std.string;
import std.conv : to;
string handleCaseSensitivity(string s) {
if(!caseInsensitiveFilenames)
return s;
string ugh;
foreach(ch; s) {
if(ch >= 'A' && ch <= 'Z')
ugh ~= "_";
ugh ~= ch;
}
return ugh;
}
bool checkDataDirectory(string stdpath) {
import std.file : exists;
import std.path : buildPath;
string[] stdfiles = ["script.js",
"style.css",
"search-docs.js",
"search-docs.html",
"skeleton-default.html"];
foreach (stdfile; stdfiles) {
if (!buildPath(stdpath, stdfile).exists) {
return false;
}
}
return true;
}
bool detectDataDirectory(ref string dataDir) {
import std.file : thisExePath;
import std.path : buildPath, dirName;
string exeDir = thisExePath.dirName;
string[] stdpaths = [exeDir,
exeDir.dirName,
buildPath(exeDir.dirName, "share/adrdox")];
foreach (stdpath; stdpaths) {
if (checkDataDirectory(stdpath)) {
dataDir = stdpath;
return true;
}
}
return false;
}
// returns empty string if file not found
string findStandardFile(bool dofail=true) (string stdfname) {
import std.file : exists;
import std.path : buildPath;
if (!stdfname.exists) {
if (stdfname.length && stdfname[0] != '/') {
string newname = buildPath(dataDirectory, stdfname);
if (newname.exists) return newname;
}
static if (dofail) throw new Exception("standard file '" ~stdfname ~ "' not found!");
}
return stdfname;
}
string outputFilePath(string[] names...) {
import std.path : buildPath;
names = outputDirectory ~ names;
return buildPath(names);
}
void copyStandardFileTo(bool timecheck=true) (string destname, string stdfname) {
import std.file;
if (exists(destname)) {
static if (timecheck) {
if (timeLastModified(destname) >= timeLastModified(findStandardFile(stdfname))) return;
} else {
return;
}
}
copy(findStandardFile(stdfname), destname);
}
__gshared static Object directoriesForPackageMonitor = new Object; // intentional CTFE
__gshared string[string] directoriesForPackage;
string getDirectoryForPackage(string packageName) {
if(packageName.indexOf("/") != -1)
return ""; // not actually a D package!
if(packageName.indexOf("#") != -1)
return ""; // not actually a D package!
string bestMatch = "";
int bestMatchDots = -1;
import std.path;
synchronized(directoriesForPackageMonitor)
foreach(pkg, dir; directoriesForPackage) {
if(globMatch!(CaseSensitive.yes)(packageName, pkg)) {
int cnt;
foreach(ch; pkg)
if(ch == '.')
cnt++;
if(cnt > bestMatchDots) {
bestMatch = dir;
bestMatchDots = cnt;
}
}
}
return bestMatch;
}
// FIXME: make See Also automatically list dittos that are not overloads
enum skip_undocumented = true;
static bool sorter(Decl a, Decl b) {
if(a.declarationType == b.declarationType)
return (blogMode && a.declarationType == "Article") ? (b.name < a.name) : (a.name < b.name);
else if(a.declarationType == "module" || b.declarationType == "module") // always put modules up top
return
(a.declarationType == "module" ? "aaaaaa" : a.declarationType)
< (b.declarationType == "module" ? "aaaaaa" : b.declarationType);
else
return a.declarationType < b.declarationType;
}
void annotatedPrototype(T)(T decl, MyOutputRange output) {
static if(is(T == TemplateDecl)) {
auto td = cast(TemplateDecl) decl;
auto epony = td.eponymousMember;
if(epony) {
if(auto e = cast(FunctionDecl) epony) {
doFunctionDec(e, output);
return;
}
}
}
auto classDec = decl.astNode;
auto f = new MyFormatter!(typeof(output))(output, decl);
void writePrototype() {
output.putTag("<div class=\"aggregate-prototype\">");
if(decl.parent !is null && !decl.parent.isModule) {
output.putTag("<div class=\"parent-prototype\">");
decl.parent.getSimplifiedPrototype(output);
output.putTag("</div><div>");
}
writeAttributes(f, output, decl.attributes);
output.putTag("<span class=\"builtin-type\">");
output.put(decl.declarationType);
output.putTag("</span>");
output.put(" ");
output.put(decl.name);
output.put(" ");
foreach(idx, ir; decl.inheritsFrom()) {
if(idx == 0)
output.put(" : ");
else
output.put(", ");
if(ir.decl is null)
output.put(ir.plainText);
else
output.putTag(`<a class="xref parent-class" href="`~ir.decl.link~`">`~ir.decl.name~`</a> `);
}
if(classDec.templateParameters)
f.format(classDec.templateParameters);
if(classDec.constraint)
f.format(classDec.constraint);
// FIXME: invariant
if(decl.children.length) {
output.put(" {");
foreach(child; decl.children) {
if((child.isPrivate() && !writePrivateDocs))
continue;
// I want to show undocumented plain data members (if not private)
// since they might be used as public fields or in ctors for simple
// structs, but I'll skip everything else undocumented.
if(!child.isDocumented() && (cast(VariableDecl) child) is null)
continue;
output.putTag("<div class=\"aggregate-member\">");
if(child.isDocumented())
output.putTag("<a href=\""~child.link~"\">");
child.getAggregatePrototype(output);
if(child.isDocumented())
output.putTag("</a>");
output.putTag("</div>");
}
output.put("}");
}
if(decl.parent !is null && !decl.parent.isModule) {
output.putTag("</div>");
}
output.putTag("</div>");
}
writeOverloads!writePrototype(decl, output);
}
void doEnumDecl(T)(T decl, Element content)
{
auto enumDec = decl.astNode;
static if(is(typeof(enumDec) == const(AnonymousEnumDeclaration))) {
if(enumDec.members.length == 0) return;
auto name = enumDec.members[0].name.text;
auto type = enumDec.baseType;
auto members = enumDec.members;
} else {
auto name = enumDec.name.text;
auto type = enumDec.type;
const(EnumMember)[] members;
if(enumDec.enumBody)
members = enumDec.enumBody.enumMembers;
}
static if(is(typeof(enumDec) == const(AnonymousEnumDeclaration))) {
// undocumented anonymous enums get a pass if any of
// their members are documented because that's what dmd does...
// FIXME maybe
bool foundOne = false;
foreach(member; members) {
if(member.comment.length) {
foundOne = true;
break;
}
}
if(!foundOne && skip_undocumented)
return;
} else {
if(enumDec.comment.length == 0 && skip_undocumented)
return;
}
/*
auto f = new MyFormatter!(typeof(output))(output);
if(type) {
output.putTag("<div class=\"base-type\">");
f.format(type);
output.putTag("</div>");
}
*/
Element table;
if(members.length) {
content.addChild("h2", "Values").attrs.id = "values";
table = content.addChild("table").addClass("enum-members");
table.appendHtml("<tr><th>Value</th><th>Meaning</th></tr>");
}
foreach(member; members) {
auto memberComment = formatDocumentationComment(preprocessComment(member.comment, decl), decl);
auto tr = table.addChild("tr");
tr.addClass("enum-member");
tr.attrs.id = member.name.text;
auto td = tr.addChild("td");
if(member.isDisabled) {
td.addChild("span", "@disable").addClass("enum-disabled");
td.addChild("br");
}
if(member.type) {
td.addChild("span", toHtml(member.type)).addClass("enum-type");
}
td.addChild("a", member.name.text, "#" ~ member.name.text).addClass("enum-member-name");
if(member.assignExpression) {
td.addChild("span", toHtml(member.assignExpression)).addClass("enum-member-value");
}
auto ea = td.addChild("div", "", "enum-attributes");
foreach(attribute; member.atAttributes) {
ea.addChild("div", toHtml(attribute));
}
// type
// assignExpression
td = tr.addChild("td");
td.innerHTML = memberComment;
if(member.deprecated_) {
auto p = td.prependChild(Element.make("div", Element.make("span", member.deprecated_.stringLiterals.length ? "Deprecated: " : "Deprecated", "deprecated-label"))).addClass("enum-deprecated");
foreach(sl; member.deprecated_.stringLiterals)
p.addChild("span", sl.text[1 .. $-1]);
}
// I might write to parent list later if I can do it all semantically inside the anonymous enum
// since these names are introduced into the parent scope i think it is justified to list them there
// maybe.
}
}
void doFunctionDec(T)(T decl, MyOutputRange output)
{
auto functionDec = decl.astNode;
//if(!decl.isDocumented() && skip_undocumented)
//return;
string[] conceptsFound;
auto f = new MyFormatter!(typeof(output))(output, decl);
/+
auto comment = parseDocumentationComment(dittoSupport(functionDec, functionDec.comment), fullyQualifiedName ~ name);
if(auto ptr = name in overloadChain[$-1]) {
*ptr += 1;
name ~= "." ~ to!string(*ptr);
} else {
overloadChain[$-1][name] = 1;
overloadNodeChain[$-1] = cast() functionDec;
}
const(ASTNode)[] overloadsList;
if(auto overloads = overloadNodeChain[$-1] in additionalModuleInfo.overloadsOf) {
overloadsList = *overloads;
}
if(dittoChain[$-1])
if(auto dittos = dittoChain[$-1] in additionalModuleInfo.dittos) {
auto list = *dittos;
outer: foreach(item; list) {
if(item is functionDec)
continue;
// already listed as a formal overload which means we
// don't need to list it again under see also
foreach(ol; overloadsList)
if(ol is item)
continue outer;
string linkHtml;
linkHtml ~= `<a href="`~htmlEncode(linkMapping[item])~`">` ~ htmlEncode(nameMapping[item]) ~ `</a>`;
comment.see_alsos ~= linkHtml;
}
}
descendInto(name);
output.putTag("<h1><span class=\"entity-name\">" ~ name ~ "</span> "~typeString~"</h1>");
writeBreadcrumbs();
output.putTag("<div class=\"function-declaration\">");
comment.writeSynopsis(output);
+/
void writeFunctionPrototype() {
string outputStr;
auto originalOutput = output;
MyOutputRange output = MyOutputRange(&outputStr);
auto f = new MyFormatter!(typeof(output))(output);
output.putTag("<div class=\"function-prototype\">");
//output.putTag(`<a href="http://dpldocs.info/reading-prototypes" id="help-link">?</a>`);
if(decl.parent !is null && !decl.parent.isModule) {
output.putTag("<div class=\"parent-prototype\">");
decl.parent.getSimplifiedPrototype(output);
output.putTag("</div><div>");
}
writeAttributes(f, output, decl.attributes);
static if(
!is(typeof(functionDec) == const(Constructor)) &&
!is(typeof(functionDec) == const(Postblit)) &&
!is(typeof(functionDec) == const(Destructor))
) {
output.putTag("<div class=\"return-type\">");
if (functionDec.hasAuto && functionDec.hasRef)
output.putTag(`<a class="lang-feature" href="http://dpldocs.info/auto-ref-function-return-prototype">auto ref</a> `);
else {
if (functionDec.hasAuto)
output.putTag(`<a class="lang-feature" href="http://dpldocs.info/auto-function-return-prototype">auto</a> `);
if (functionDec.hasRef)
output.putTag(`<a class="lang-feature" href="http://dpldocs.info/ref-function-return-prototype">ref</a> `);
}
if (functionDec.returnType !is null)
f.format(functionDec.returnType);
output.putTag("</div>");
}
output.putTag("<div class=\"function-name\">");
output.put(decl.name);
output.putTag("</div>");
output.putTag("<div class=\"template-parameters\" data-count=\""~to!string((functionDec.templateParameters && functionDec.templateParameters.templateParameterList) ? functionDec.templateParameters.templateParameterList.items.length : 0)~"\">");
if (functionDec.templateParameters !is null)
f.format(functionDec.templateParameters);
output.putTag("</div>");
output.putTag("<div class=\"runtime-parameters\" data-count=\""~to!string(functionDec.parameters.parameters.length)~"\">");
f.format(functionDec.parameters);
output.putTag("</div>");
if(functionDec.constraint !is null) {
output.putTag("<div class=\"template-constraint\">");
f.format(functionDec.constraint);
output.putTag("</div>");
}
if(functionDec.functionBody !is null) {
// FIXME: list inherited contracts
output.putTag("<div class=\"function-contracts\">");
import dparse.formatter;
auto f2 = new Formatter!(typeof(output))(output);
// I'm skipping statements cuz they ugly. but the shorter expressions aren't too bad
if(functionDec.functionBody.inStatement && functionDec.functionBody.inStatement.expression) {
output.putTag("<div class=\"in-contract\">");
f2.format(functionDec.functionBody.inStatement);
output.putTag("</div>");
}
if(functionDec.functionBody.outStatement) {
output.putTag("<div class=\"out-contract\">");
f2.format(functionDec.functionBody.outStatement);
output.putTag("</div>");
}
output.putTag("</div>");
}
//output.put(" : ");
//output.put(to!string(functionDec.name.line));
if(decl.parent !is null && !decl.parent.isModule) {
output.putTag("</div>");
decl.parent.writeTemplateConstraint(output);
}
output.putTag("</div>");
originalOutput.putTag(linkUpHtml(outputStr, decl));
}
writeOverloads!writeFunctionPrototype(decl, output);
}
void writeOverloads(alias writePrototype, D : Decl)(D decl, ref MyOutputRange output) {
auto overloadsList = decl.getImmediateDocumentedOverloads();
// I'm treating dittos similarly to overloads
if(overloadsList.length == 1) {
overloadsList = decl.getDittos();
} else {
foreach(ditto; decl.getDittos()) {
if(!overloadsList.canFind(ditto))
overloadsList ~= ditto;
}
}
if(overloadsList.length > 1) {
import std.conv;
output.putTag("<ol class=\"overloads\">");
foreach(idx, item; overloadsList) {
assert(item.parent !is null);
string cn;
Decl epony;
if(auto t = cast(TemplateDecl) item)
epony = t.eponymousMember;
if(item !is decl && decl !is epony)
cn = "overload-option";
else
cn = "active-overload-option";
if(item.name != decl.name)
cn ~= " ditto-option";
output.putTag("<li class=\""~cn~"\">");
//if(item is decl)
//writeFunctionPrototype();
//} else {
{
if(item !is decl)
output.putTag(`<a href="`~item.link~`">`);
output.putTag("<span class=\"overload-signature\">");
item.getSimplifiedPrototype(output);
output.putTag("</span>");
if(item !is decl)
output.putTag(`</a>`);
}
if(item is decl || decl is epony)
writePrototype();
output.putTag("</li>");
}
output.putTag("</ol>");
} else {
writePrototype();
}
}
void writeAttributes(F, W)(F formatter, W writer, const(VersionOrAttribute)[] attrs, bool bracket = true)
{
if(bracket) writer.putTag("<div class=\"attributes\">");
IdType protection;
string versions;
const(VersionOrAttribute)[] remainingBuiltIns;
const(VersionOrAttribute)[] remainingCustoms;
foreach(a; attrs) {
if (a.attr && isProtection(a.attr.attribute.type)) {
protection = a.attr.attribute.type;
} else if (auto v = cast(VersionFakeAttribute) a) {
if(versions.length)
versions ~= " && ";
if(v.inverted)
versions ~= "!";
versions ~= v.cond;
} else if(a.attr && a.attr.attribute.type == tok!"auto") {
// skipping auto because it is already handled as the return value
} else if(a.isBuiltIn) {
remainingBuiltIns ~= a;
} else {
remainingCustoms ~= a;
}
}
if(versions.length) {
writer.putTag("<div class=\"versions-container\">");
writer.put("version(");
writer.put(versions);
writer.put(")");
writer.putTag("</div>");
}
switch (protection)
{
case tok!"private": writer.put("private "); break;
case tok!"package": writer.put("package "); break;
case tok!"protected": writer.put("protected "); break;
case tok!"export": writer.put("export "); break;
case tok!"public": // see below
default:
// I'm not printing public so this is commented intentionally
// public is the default state of documents so saying it outright
// is kinda just a waste of time IMO.
//writer.put("public ");
break;
}
void innards(const VersionOrAttribute a) {
if(auto fakeAttr = cast(const MemberFakeAttribute) a) {
formatter.format(fakeAttr.attr);
writer.put(" ");
} else if(auto dbg = cast(const FakeAttribute) a) {
writer.putTag(dbg.toHTML);
} else {
if(a.attr && a.attr.deprecated_)
writer.putTag(`<span class="deprecated-decl">deprecated</span>`);
else if(a.attr)
formatter.format(a.attr);
writer.put(" ");
}
}
foreach (a; remainingBuiltIns)
innards(a);
foreach (a; remainingCustoms) {
writer.putTag("<div class=\"uda\">");
innards(a);
writer.putTag("</div>");
}
if(bracket) writer.putTag("</div>");
}
class VersionOrAttribute {
const(Attribute) attr;
this(const(Attribute) attr) {
this.attr = attr;
}
bool isBuiltIn() const {
if(attr is null) return false;
if(attr.atAttribute is null) return true; // any keyword can be safely assumed...
return phelper(attr.atAttribute);
}
protected bool phelper(const AtAttribute at) const {
string txt = toText(at);
if(txt == "@(nogc)") return true;
if(txt == "@(disable)") return true;
if(txt == "@(live)") return true;
if(txt == "@(property)") return true;
if(txt == "@(safe)") return true;
if(txt == "@(system)") return true;
if(txt == "@(trusted)") return true;
return false;
}
const(VersionOrAttribute) invertedClone() const {
return new VersionOrAttribute(attr);
}
override string toString() const {
return attr ? toText(attr) : "null";
}
}
interface ConditionFakeAttribute {
string toHTML() const;
}
class FakeAttribute : VersionOrAttribute {
this() { super(null); }
abstract string toHTML() const;
}
class MemberFakeAttribute : FakeAttribute {
const(MemberFunctionAttribute) attr;
this(const(MemberFunctionAttribute) attr) {
this.attr = attr;
}
override string toHTML() const {
return toText(attr);
}
override bool isBuiltIn() const {
if(attr is null) return false;
if(attr.atAttribute is null) return true;
return phelper(attr.atAttribute);
}
override string toString() const {
return attr ? toText(attr) : "null";
}
}
class VersionFakeAttribute : FakeAttribute, ConditionFakeAttribute {
string cond;
bool inverted;
this(string condition, bool inverted = false) {
cond = condition;
this.inverted = inverted;
}
override const(VersionFakeAttribute) invertedClone() const {
return new VersionFakeAttribute(cond, !inverted);
}
override bool isBuiltIn() const {
return false;
}
override string toHTML() const {
auto element = Element.make("span");
element.addChild("span", "version", "lang-feature");
element.appendText("(");
if(inverted)
element.appendText("!");
element.addChild("span", cond);
element.appendText(")");
return element.toString;
}
override string toString() const {
return (inverted ? "!" : "") ~ cond;
}
}
class DebugFakeAttribute : FakeAttribute, ConditionFakeAttribute {
string cond;
bool inverted;
this(string condition, bool inverted = false) {
cond = condition;
this.inverted = inverted;
}
override const(DebugFakeAttribute) invertedClone() const {
return new DebugFakeAttribute(cond, !inverted);
}
override bool isBuiltIn() const {
return false;
}
override string toHTML() const {
auto element = Element.make("span");
if(cond.length) {
element.addChild("span", "debug", "lang-feature");
element.appendText("(");
if(inverted)
element.appendText("!");
element.addChild("span", cond);
element.appendText(")");
} else {
if(inverted)
element.addChild("span", "!debug", "lang-feature");
else
element.addChild("span", "debug", "lang-feature");
}
return element.toString;
}
override string toString() const {
return (inverted ? "!" : "") ~ cond;
}
}
class StaticIfFakeAttribute : FakeAttribute, ConditionFakeAttribute {
string cond;
bool inverted;
this(string condition, bool inverted = false) {
cond = condition;
this.inverted = inverted;
}
override const(StaticIfFakeAttribute) invertedClone() const {
return new StaticIfFakeAttribute(cond, !inverted);
}
override bool isBuiltIn() const {
return false;
}
override string toHTML() const {
auto element = Element.make("span");
element.addChild("span", "static if", "lang-feature");
element.appendText("(");
if(inverted)
element.appendText("!(");
element.addChild("span", cond);
if(inverted)
element.appendText(")");
element.appendText(")");
return element.toString;
}
override string toString() const {
return (inverted ? "!" : "") ~ cond;
}
}
void putSimplfiedReturnValue(MyOutputRange output, const FunctionDeclaration decl) {
if (decl.hasAuto && decl.hasRef)
output.putTag(`<span class="lang-feature">auto ref</span> `);
else {
if (decl.hasAuto)
output.putTag(`<span class="lang-feature">auto</span> `);
if (decl.hasRef)
output.putTag(`<span class="lang-feature">ref</span> `);
}
if (decl.returnType !is null)
output.putTag(toHtml(decl.returnType).source);
}
void putSimplfiedArgs(T)(MyOutputRange output, const T decl) {
// FIXME: do NOT show default values here
if(decl.parameters) {
output.putTag("(");
foreach(idx, p; decl.parameters.parameters) {
if(idx)
output.putTag(", ");
output.putTag(toText(p.type));
output.putTag(" ");
output.putTag(toText(p.name));
}
if(decl.parameters.hasVarargs) {
if(decl.parameters.parameters.length)
output.putTag(", ");
output.putTag("...");
}
output.putTag(")");
}
}
string specialPreprocess(string comment, Decl decl) {
switch(specialPreprocessor) {
case "dwt":
// translate Javadoc to adrdox
// @see, @exception/@throws, @param, @return
// @author, @version, @since, @deprecated
// {@link thing}
// one line desc is until the first <p>
// html tags are allowed in javadoc
// links go class#member(args)
// the (args) and class are optional
string parseIdentifier(ref string s, bool allowHash = false) {
int end = 0;
while(end < s.length && (
(s[end] >= 'A' && s[end] <= 'Z') ||
(s[end] >= 'a' && s[end] <= 'z') ||
(s[end] >= '0' && s[end] <= '9') ||
s[end] == '_' ||
s[end] == '.' ||
(allowHash && s[end] == '#')
))
{
end++;
}
auto i = s[0 .. end];
s = s[end .. $];
return i;
}
// javadoc is basically html with @ stuff, so start by parsing that (presumed) tag soup
auto document = new Document("<root>" ~ comment ~ "</root>");
string newComment;
string fixupJavaReference(string r) {
if(r.length == 0)
return r;
if(r[0] == '#')
r = r[1 .. $]; // local refs in adrdox need no special sigil
r = r.replace("#", ".");
auto idx = r.indexOf("(");
if(idx != -1)
r = r[0 .. idx];
return r;
}
void translate(Element element) {
if(element.nodeType == NodeType.Text) {
foreach(line; element.nodeValue.splitLines(KeepTerminator.yes)) {
auto s = line.strip;
if(s.length && s[0] == '@') {
s = s[1 .. $];
auto ident = parseIdentifier(s);
switch(ident) {
case "author":
case "deprecated":
case "version":
case "since":
line = ident ~ ": " ~ s ~ "\n";
break;
case "return":
case "returns":
line = "Returns: " ~ s ~ "\n";
break;
case "exception":
case "throws":
while(s.length && s[0] == ' ')
s = s[1 .. $];
auto p = parseIdentifier(s);
line = "Throws: [" ~ p ~ "]" ~ s ~ "\n";
break;
case "param":
while(s.length && s[0] == ' ')
s = s[1 .. $];
auto p = parseIdentifier(s);
line = "Params:\n" ~ p ~ " = " ~ s ~ "\n";
break;
case "see":
while(s.length && s[0] == ' ')
s = s[1 .. $];
auto p = parseIdentifier(s, true);
if(p.length)
line = "See_Also: [" ~ fixupJavaReference(p) ~ "]" ~ "\n";
else
line = "See_Also: " ~ s ~ "\n";
break;
default:
// idk, leave it alone.
}
}
newComment ~= line;
}
} else {
if(element.tagName == "code") {
newComment ~= "`";
// FIXME: what about ` inside code?
newComment ~= element.innerText; // .replace("`", "``");
newComment ~= "`";
} else if(element.tagName == "p") {
newComment ~= "\n\n";
foreach(child; element.children)
translate(child);
newComment ~= "\n\n";
} else if(element.tagName == "a") {
newComment ~= "${LINK2 " ~ element.href ~ ", " ~ element.innerText ~ "}";
} else {
newComment ~= "${" ~ element.tagName.toUpper ~ " ";
foreach(child; element.children)
translate(child);
newComment ~= "}";
}
}
}
foreach(child; document.root.children)
translate(child);
comment = newComment;
break;
case "gtk":
// translate gtk syntax and names to our own
string gtkObjectToDClass(string name) {
if(name.length == 0)
return null;
int pkgEnd = 1;
while(pkgEnd < name.length && !(name[pkgEnd] >= 'A' && name[pkgEnd] <= 'Z'))
pkgEnd++;
auto pkg = name[0 .. pkgEnd].toLower;
auto mod = name[pkgEnd .. $];
auto t = pkg ~ "." ~ mod;
if(t in modulesByName)
return t ~ "." ~ mod;
synchronized(allClassesMutex)
if(auto c = mod in allClasses)
return c.fullyQualifiedName;
return null;
}
string trimFirstThing(string name) {
if(name.length == 0)
return null;
int pkgEnd = 1;
while(pkgEnd < name.length && !(name[pkgEnd] >= 'A' && name[pkgEnd] <= 'Z'))
pkgEnd++;
return name[pkgEnd .. $];
}
string formatForDisplay(string name) {
auto parts = name.split(".");
// gtk.Application.Application.member
// we want to take out the repeated one - slot [1]
string disp;
if(parts.length > 2)
foreach(idx, part; parts) {
if(idx == 1) continue;
if(idx) {
disp ~= ".";
}
disp ~= part;
}
else
disp = name;
return disp;
}
import std.regex : regex, replaceAll, Captures;
// gtk references to adrdox reference; punt it to the search engine
string magic(Captures!string m) {
string s = m.hit;
s = s[1 .. $]; // trim off #
auto orig = s;
auto name = s;
string displayOverride;
string additional;
auto idx = s.indexOf(":");
if(idx != -1) {
// colon means it is an attribute or a signal
auto property = s[idx + 1 .. $];
s = s[0 .. idx];
if(property.length && property[0] == ':') {
// is a signal
property = property[1 .. $];
additional = ".addOn";
displayOverride = property;
} else {
// is a property
additional = ".get";
displayOverride = property;
}
bool justSawDash = true;
foreach(ch; property) {
if(justSawDash && ch >= 'a' && ch <= 'z') {
additional ~= cast(char) (cast(int) ch - 32);
} else if(ch == '-') {
// intentionally blank
} else {
additional ~= ch;
}
if(ch == '-') {
justSawDash = true;
} else {
justSawDash = false;
}
}
} else {
idx = s.indexOf(".");
if(idx != -1) {
// dot is either a tailing period or a Struct.field
if(idx == s.length - 1)
s = s[0 .. $ - 1]; // tailing period
else {
auto structField = s[idx + 1 .. $];
s = s[0 .. idx];
additional = "." ~ structField; // FIXME?
}
}
}
auto dClass = gtkObjectToDClass(s);
bool plural = false;
if(dClass is null && s.length && s[$-1] == 's') {
s = s[0 .. $-1];
dClass = gtkObjectToDClass(s);
plural = true;
}
if(dClass !is null)
s = dClass;
s ~= additional;
if(displayOverride.length)
return "[" ~ s ~ "|"~displayOverride~"]";
else
return "[" ~ s ~ "|"~formatForDisplay(s)~(plural ? "s" : "") ~ "]";
}
// gtk function to adrdox d ref
string magic2(Captures!string m) {
if(m.hit == "main()")
return "`"~m.hit~"`"; // special case
string s = m.hit[0 .. $-2]; // trim off the ()
auto orig = m.hit;
// these tend to go package_class_method_snake
string gtkType;
gtkType ~= s[0] | 32;
s = s[1 .. $];
bool justSawUnderscore = false;
string dType;
bool firstUnderscore = true;
while(s.length) {
if(s[0] == '_') {
justSawUnderscore = true;
if(!firstUnderscore) {
auto dc = gtkObjectToDClass(gtkType);
if(dc !is null) {
dType = dc;
s = s[1 .. $];
break;
}
}
firstUnderscore = false;
} else if(justSawUnderscore) {
gtkType ~= s[0] & ~32;
justSawUnderscore = false;
} else
gtkType ~= s[0];
s = s[1 .. $];
}
if(dType.length) {
justSawUnderscore = false;
string gtkMethod = "";
while(s.length) {
if(s[0] == '_') {
justSawUnderscore = true;
} else if(justSawUnderscore) {
gtkMethod ~= s[0] & ~32;
justSawUnderscore = false;
} else
gtkMethod ~= s[0];
s = s[1 .. $];
}
auto dispName = dType[dType.lastIndexOf(".") + 1 .. $] ~ "." ~ gtkMethod;
return "[" ~ dType ~ "." ~ gtkMethod ~ "|" ~ dispName ~ "]";
}
return "`" ~ orig ~ "`";
}
// cut off spam at the end of headers
comment = replaceAll(comment, regex(r"(## [A-Za-z0-9 ]+)##.*$", "gm"), "$1");
// translate see also header into ddoc style as a special case
comment = replaceAll(comment, regex(r"## See Also.*$", "gm"), "See_Also:\n");
// name lookup
comment = replaceAll!magic(comment, regex(r"#[A-Za-z0-9_:\-\.]+", "g"));
// gtk params to inline code
comment = replaceAll(comment, regex(r"@([A-Za-z0-9_:]+)", "g"), "`$1`");
// constants too
comment = replaceAll(comment, regex(r"%([A-Za-z0-9_:]+)", "g"), "`$1`");
// and functions
comment = replaceAll!magic2(comment, regex(r"([A-Za-z0-9_]+)\(\)", "g"));
// their code blocks
comment = replace(comment, `|[<!-- language="C" -->`, "```c\n");
comment = replace(comment, `|[<!-- language="plain" -->`, "```\n");
comment = replace(comment, `|[`, "```\n");
comment = replace(comment, `]|`, "\n```");
break;
default:
return comment;
}
return comment;
}
struct HeaderLink {
string text;
string url;
}
string[string] pseudoFiles;
bool usePseudoFiles = false;
Document writeHtml(Decl decl, bool forReal, bool gzip, string headerTitle, HeaderLink[] headerLinks, bool overrideOutput = false) {
if(!decl.docsShouldBeOutputted && !overrideOutput)
return null;
if(cast(ImportDecl) decl)
return null; // never write imports, it can overwrite the actual thing
auto title = decl.name;
bool justDocs = false;
if(auto mod = cast(ModuleDecl) decl) {
if(mod.justDocsTitle !is null) {
title = mod.justDocsTitle;
justDocs = true;
}
}
if(decl.parent !is null && !decl.parent.isModule) {
title = decl.parent.name ~ "." ~ title;
}
auto document = new Document();
import std.file;
document.parseUtf8(readText(findStandardFile(skeletonFile)), true, true);
switch (texMathOpt) with (TexMathOpt) {
case KaTeX: {
import adrdox.jstex;
prepareForKaTeX(document);
break;
}
default: break;
}
document.title = title ~ " (" ~ decl.fullyQualifiedName ~ ")";
if(headerTitle.length)
document.requireSelector("#logotype span").innerText = headerTitle;
if(headerLinks.length) {
auto n = document.requireSelector("#page-header nav");
foreach(l; headerLinks)
if(l.text.length && l.url.length)
n.addChild("a", l.text, l.url);
}
auto content = document.requireElementById("page-content");
auto comment = decl.parsedDocComment;
content.addChild("h1", title);
auto breadcrumbs = content.addChild("div").addClass("breadcrumbs");
//breadcrumbs.prependChild(Element.make("a", decl.name, decl.link).addClass("current breadcrumb"));
{
auto p = decl.parent;
while(p) {
if(p.fakeDecl && p.name == "index")
break;
// cut off package names that would be repeated
auto name = (p.isModule && p.parent) ? lastDotOnly(p.name) : p.name;
breadcrumbs.prependChild(new TextNode(" "));
breadcrumbs.prependChild(Element.make("a", name, p.link(true)).addClass("breadcrumb"));
p = p.parent;
}
}
if(blogMode && decl.isArticle) {
// FIXME: kinda a hack there
auto mod = cast(ModuleDecl) decl;
if(mod.name.startsWith("Blog.Posted_"))
content.addChild("span", decl.name.replace("Blog.Posted_", "Posted ").replace("_", "-")).addClass("date-posted");
}
string s;
MyOutputRange output = MyOutputRange(&s);
comment.writeSynopsis(output);
content.addChild("div", Html(s));
s = null;
decl.getAnnotatedPrototype(output);
//import std.stdio; writeln(s);
//content.addChild("div", Html(linkUpHtml(s, decl)), "annotated-prototype");
content.addChild("div", Html(s), "annotated-prototype");
Element lastDt;
string dittoedName;
string dittoedComment;
void handleChildDecl(Element dl, Decl child, bool enableLinking = true) {
auto cc = child.parsedDocComment;
string sp;
MyOutputRange or = MyOutputRange(&sp);
if(child.isDeprecated)
or.putTag("<span class=\"deprecated-decl\">deprecated</span> ");
child.getSimplifiedPrototype(or);
auto printableName = child.name;
if(child.isArticle) {
auto mod = cast(ModuleDecl) child;
printableName = mod.justDocsTitle;
} else {
if(child.isModule && child.parent && child.parent.isModule) {
if(printableName.startsWith(child.parent.name))
printableName = printableName[child.parent.name.length + 1 .. $];
}
}
auto newDt = Element.make("dt", Element.make("a", printableName, child.link));
auto st = newDt.addChild("div", Html(sp)).addClass("simplified-prototype");
st.style.maxWidth = to!string(st.innerText.length * 11 / 10) ~ "ch";
if(child.isDitto && child.comment == dittoedComment && lastDt !is null) {
// ditto'd names don't need to be written again
if(child.name == dittoedName) {
foreach(ldt; lastDt.parentNode.querySelectorAll("dt .simplified-prototype")) {
if(st.innerHTML == ldt.innerHTML)
return; // no need to say the same thing twice
}
// same name, but different prototype. Cut the repetition.
newDt.requireSelector("a").removeFromTree();
}
lastDt.addSibling(newDt);
} else {
dl.addChild(newDt);
auto dd = dl.addChild("dd", Html(formatDocumentationComment(enableLinking ? cc.ddocSummary : preprocessComment(child.comment, child), child)));
foreach(e; dd.querySelectorAll("h1, h2, h3, h4, h5, h6"))
e.stripOut;
dittoedComment = child.comment;
}
lastDt = newDt;
dittoedName = child.name;
}
Decl[] ctors;
Decl[] members;
ModuleDecl[] articles;
Decl[] submodules;
ImportDecl[] imports;
if(forReal)
foreach(child; decl.children) {
if(!child.docsShouldBeOutputted)
continue;
if(child.isConstructor())
ctors ~= child;
else if(child.isArticle)
articles ~= cast(ModuleDecl) child;
else if(child.isModule)
submodules ~= child;
else if(cast(DestructorDecl) child)
{} // intentionally blank
else if(cast(PostblitDecl) child)
{} // intentionally blank
else if(auto c = cast(ImportDecl) child)
imports ~= c;
else
members ~= child;
}
if(decl.disabledDefaultConstructor) {
content.addChild("h2", "Disabled Default Constructor").id = "disabled-default-constructor";
auto div = content.addChild("div");
div.addChild("p", "A disabled default is present on this object. To use it, use one of the other constructors or a factory function.");
}
if(ctors.length) {
content.addChild("h2", "Constructors").id = "constructors";
auto dl = content.addChild("dl").addClass("member-list constructors");
foreach(child; ctors) {
if(child is decl.disabledDefaultConstructor)
continue;
handleChildDecl(dl, child);
if(!minimalDescent)
writeHtml(child, forReal, gzip, headerTitle, headerLinks);
}
}
if(auto dtor = decl.destructor) {
content.addChild("h2", "Destructor").id = "destructor";
auto dl = content.addChild("dl").addClass("member-list");
if(dtor.isDocumented)
handleChildDecl(dl, dtor);
else
content.addChild("p", "A destructor is present on this object, but not explicitly documented in the source.");
//if(!minimalDescent)
//writeHtml(dtor, forReal, gzip, headerTitle, headerLinks);
}
if(auto postblit = decl.postblit) {
content.addChild("h2", "Postblit").id = "postblit";
auto dl = content.addChild("dl").addClass("member-list");
if(postblit.isDisabled())
content.addChild("p", "Copying this object is disabled.");
if(postblit.isDocumented)
handleChildDecl(dl, postblit);
else
content.addChild("p", "A postblit is present on this object, but not explicitly documented in the source.");
//if(!minimalDescent)
//writeHtml(dtor, forReal, gzip, headerTitle, headerLinks);
}
if(articles.length) {
content.addChild("h2", "Articles").id = "articles";
auto dl = content.addChild("dl").addClass("member-list articles");
foreach(child; articles.sort!((a,b) => (blogMode ? (b.name < a.name) : (a.name < b.name)))) {
handleChildDecl(dl, child);
}
}
if(submodules.length) {
content.addChild("h2", "Modules").id = "modules";
auto dl = content.addChild("dl").addClass("member-list native");
foreach(child; submodules.sort!((a,b) => a.name < b.name)) {
handleChildDecl(dl, child);
// i actually want submodules to be controlled on the command line too.
//if(!usePseudoFiles) // with pseudofiles, we can generate child modules on demand too, so avoid recursive everything on root request
//writeHtml(child, forReal, gzip, headerTitle, headerLinks);
}
}
if(auto at = decl.aliasThis) {
content.addChild("h2", "Alias This").id = "alias-this";
auto div = content.addChild("div");
div.addChild("a", at.name, at.link);
if(decl.aliasThisComment.length) {
auto memberComment = formatDocumentationComment(preprocessComment(decl.aliasThisComment, decl), decl);
auto dc = div.addChild("div").addClass("documentation-comment");
dc.innerHTML = memberComment;
}
}
if(imports.length) {
content.addChild("h2", "Public Imports").id = "public-imports";
auto div = content.addChild("div");
foreach(imp; imports) {
auto dl = content.addChild("dl").addClass("member-list native");
handleChildDecl(dl, imp, false);
}
}
if(members.length) {
content.addChild("h2", "Members").id = "members";
void outputMemberList(Decl[] members, string header, string idPrefix, string headerPrefix) {
Element dl;
string lastType;
foreach(child; members.sort!sorter) {
if(child.declarationType != lastType) {
auto hdr = content.addChild(header, headerPrefix ~ pluralize(child.declarationType).capitalize, "member-list-header hide-from-toc");
hdr.id = idPrefix ~ child.declarationType;
dl = content.addChild("dl").addClass("member-list native");
lastType = child.declarationType;
}
handleChildDecl(dl, child);
if(!minimalDescent)
writeHtml(child, forReal, gzip, headerTitle, headerLinks);
}
}
foreach(section; comment.symbolGroupsOrder) {
auto memberComment = formatDocumentationComment(preprocessComment(comment.symbolGroups[section], decl), decl);
string sectionPrintable = section.replace("_", " ").capitalize;
// these count as user headers to move toward TOC - section groups are user defined so it makes sense
auto hdr = content.addChild("h3", sectionPrintable, "member-list-header user-header");
hdr.id = "group-" ~ section;
auto dc = content.addChild("div").addClass("documentation-comment");
dc.innerHTML = memberComment;
if(auto hdr2 = dc.querySelector("> div:only-child > h2:first-child, > div:only-child > h3:first-child")) {
hdr.innerHTML = hdr2.innerHTML;
hdr2.removeFromTree;
}
Decl[] subList;
for(int i = 0; i < members.length; i++) {
auto member = members[i];
if(member.parsedDocComment.group == section) {
subList ~= member;
members[i] = members[$-1];
members = members[0 .. $-1];
i--;
}
}
outputMemberList(subList, "h4", section ~ "-", sectionPrintable ~ " ");
}
if(members.length) {
if(comment.symbolGroupsOrder.length) {
auto hdr = content.addChild("h3", "Other", "member-list-header");
hdr.id = "group-other";
outputMemberList(members, "h4", "other-", "Other ");
} else {
outputMemberList(members, "h3", "", "");
}
}
}
bool firstMitd = true;
foreach(d; decl.children) {
if(auto mi = cast(MixedInTemplateDecl) d) {
auto thing = decl.lookupName(toText(mi.astNode.mixinTemplateName));
if (!thing)
// else {}
continue;
Element dl;
foreach(child; thing.children) {
if(mi.isPrivate && !child.isExplicitlyNonPrivate)
continue;
if (!child.docsShouldBeOutputted)
continue;
if(dl is null) {
if(firstMitd) {
auto h2 = content.addChild("h2", "Mixed In Members");
h2.id = "mixed-in-members";
firstMitd = false;
}
//mi.name
string sp;
MyOutputRange or = MyOutputRange(&sp);
mi.getSimplifiedPrototype(or);
auto h3 = content.addChild("h3", Html("From " ~ sp));
dl = content.addChild("dl").addClass("member-list native");
}
handleChildDecl(dl, child);
if(!minimalDescent)
writeHtml(child, forReal, gzip, headerTitle, headerLinks, true);
}
}
}
auto irList = decl.inheritsFrom;
if(irList.length) {
auto h2 = content.addChild("h2", "Inherited Members");
h2.id = "inherited-members";
bool hasAnyListing = false;
foreach(ir; irList) {
if(ir.decl is null) continue;
auto h3 = content.addChild("h3", "From " ~ ir.decl.name);
h3.id = "inherited-from-" ~ ir.decl.fullyQualifiedName;
auto dl = content.addChild("dl").addClass("member-list inherited");
bool hadListing = false;
foreach(child; ir.decl.children) {
if(!child.docsShouldBeOutputted)
continue;
if(!child.isConstructor()) {
handleChildDecl(dl, child);
hadListing = true;
hasAnyListing = true;
}
}
if(!hadListing) {
h3.removeFromTree();
dl.removeFromTree();
}
}
if(!hasAnyListing)
h2.removeFromTree();
}
decl.addSupplementalData(content);
s = null;
if(auto fd = cast(FunctionDeclaration) decl.getAstNode())
comment.writeDetails(output, fd, decl.getProcessedUnittests());
else if(auto fd = cast(Constructor) decl.getAstNode())
comment.writeDetails(output, fd, decl.getProcessedUnittests());
else if(auto fd = cast(TemplateDeclaration) decl.getAstNode())
comment.writeDetails(output, fd, decl.getProcessedUnittests());
else if(auto fd = cast(EponymousTemplateDeclaration) decl.getAstNode())
comment.writeDetails(output, fd, decl.getProcessedUnittests());
else if(auto fd = cast(StructDeclaration) decl.getAstNode())
comment.writeDetails(output, fd, decl.getProcessedUnittests());
else if(auto fd = cast(ClassDeclaration) decl.getAstNode())
comment.writeDetails(output, fd, decl.getProcessedUnittests());
else if(auto fd = cast(AliasDecl) decl) {
if(fd.initializer)
comment.writeDetails(output, fd.initializer, decl.getProcessedUnittests());
else
comment.writeDetails(output, decl, decl.getProcessedUnittests());
} else {
//import std.stdio; writeln(decl.getAstNode);
comment.writeDetails(output, decl, decl.getProcessedUnittests());
}
content.addChild("div", Html(s));
if(forReal) {
auto nav = document.requireElementById("page-nav");
Decl[] navArray;
string[string] inNavArray;
if(decl.parent) {
auto iterate = decl.parent.children;
if(!decl.isModule) {
if(auto emc = decl.parent.eponymousModuleChild()) {
// we are an only child of a module, show the module's nav instead
if(decl.parent.parent !is null)
iterate = decl.parent.parent.children;
}
}
foreach(child; iterate) {
if(cast(ImportDecl) child) continue; // do not document public imports here, they belong only on the inside
if(child.docsShouldBeOutputted) {
// strip overloads from sidebar
if(child.name !in inNavArray) {
navArray ~= child;
inNavArray[child.name] = "";
}
}
}
} else {
/+ commented pending removal
// this is for building the module nav when doing an incremental
// rebuild. It loads the index.xml made with the special option below.
static bool attemptedXmlLoad;
static ModuleDecl[] indexedModules;
if(!attemptedXmlLoad) {
import std.file;
if(std.file.exists("index.xml")) {
auto idx = new XmlDocument(readText("index.xml"));
foreach(d; idx.querySelectorAll("listing > decl"))
indexedModules ~= new ModuleDecl(d.requireSelector("name").innerText);
}
attemptedXmlLoad = true;
}
auto tm = cast(ModuleDecl) decl;
if(tm !is null)
foreach(im; indexedModules)
if(im.packageName == tm.packageName)
navArray ~= im;
+/
}
{
auto p = decl.parent;
while(p) {
// cut off package names that would be repeated
auto name = (p.isModule && p.parent) ? lastDotOnly(p.name) : p.name;
if(name == "index" && p.fakeDecl)
break;
nav.prependChild(new TextNode(" "));
nav.prependChild(Element.make("a", name, p.link(true))).addClass("parent");
p = p.parent;
}
}
import std.algorithm;
sort!sorter(navArray);
Element list;
string lastType;
foreach(item; navArray) {
if(item.declarationType != lastType) {
nav.addChild("span", pluralize(item.declarationType)).addClass("type-separator");
list = nav.addChild("ul");
lastType = item.declarationType;
}
string name;
if(item.isArticle) {
auto mod = cast(ModuleDecl) item;
name = mod.justDocsTitle;
} else {
// cut off package names that would be repeated
name = (item.isModule && item.parent) ? lastDotOnly(item.name) : item.name;
}
auto n = list.addChild("li").addChild("a", name, item.link).addClass(item.declarationType.replace(" ", "-"));
if(item.name == decl.name || name == decl.name)
n.addClass("current");
}
if(justDocs) {
if(auto d = document.querySelector("#details"))
d.removeFromTree;
}
auto toc = Element.make("div");
toc.id = "table-of-contents";
auto current = toc;
int lastLevel;
tree: foreach(header; document.root.tree) {
int level;
switch(header.tagName) {
case "h2":
level = 2;
break;
case "h3":
level = 3;
break;
case "h4":
level = 4;
break;
case "h5:":
level = 5;
break;
case "h6":
level = 6;
break;
default: break;
}
if(level == 0) continue;
bool addToIt = true;
if(header.hasClass("hide-from-toc"))
addToIt = false;
Element addTo;
if(addToIt) {
auto parentCheck = header;
while(parentCheck) {
if(parentCheck.hasClass("adrdox-sample"))
continue tree;
parentCheck = parentCheck.parentNode;
}
if(level > lastLevel) {
current = current.addChild("ol");
current.addClass("heading-level-" ~ to!string(level));
} else if(level < lastLevel) {
while(current && !current.hasClass("heading-level-" ~ to!string(level)))
current = current.parentNode;
if(current is null) {
import std.stdio;
writeln("WARNING: TOC broken on " ~ decl.name);
goto skip_toc;
}
assert(current !is null);
}
lastLevel = level;
addTo = current;
if(addTo.tagName != "ol")
addTo = addTo.parentNode;
}
if(!header.hasAttribute("id"))
header.attrs.id = toId(header.innerText);
if(header.querySelector(" > *") is null) {
auto selfLink = Element.make("a", header.innerText, "#" ~ header.attrs.id);
selfLink.addClass("header-anchor");
header.innerHTML = selfLink.toString();
}
if(addToIt)
addTo.addChild("li", Element.make("a", header.innerText, "#" ~ header.attrs.id));
}
if(auto d = document.querySelector("#more-link")) {
if(document.querySelectorAll(".user-header:not(.hide-from-toc)").length > 2)
d.replaceWith(toc);
}
skip_toc: {}
if(auto a = document.querySelector(".annotated-prototype"))
outer: foreach(c; a.querySelectorAll(".parameters-list")) {
auto p = c.parentNode;
while(p) {
if(p.hasClass("lambda-expression"))
continue outer;
p = p.parentNode;
}
c.addClass("toplevel");
}
// for line numbering
foreach(pre; document.querySelectorAll("pre.highlighted, pre.block-code[data-language!=\"\"]")) {
addLineNumbering(pre);
}
string overloadLink;
string declLink = decl.link(true, &overloadLink);
if(declLink == ".html")
return document;
if(usePseudoFiles) {
pseudoFiles[declLink] = document.toString();
if(overloadLink.length && overloadLink != ".html")
pseudoFiles[overloadLink] = redirectToOverloadHtml(declLink);
} else {
writeFile(outputFilePath(declLink), document.toString(), gzip);
if(overloadLink.length && overloadLink != ".html")
writeFile(outputFilePath(overloadLink), redirectToOverloadHtml(declLink), gzip);
}
import std.stdio;
writeln("WRITTEN TO ", declLink);
}
return document;
}
string redirectToOverloadHtml(string what) {
return `<html class="overload-redirect"><script>location.href = '`~what~`';</script> <a href="`~what~`">Continue to overload</a></html>`;
}
void addLineNumbering(Element pre, bool id = false) {
if(pre.hasClass("with-line-wrappers"))
return;
string html;
int count;
foreach(idx, line; pre.innerHTML.splitLines) {
auto num = to!string(idx + 1);
auto href = "L"~num;
if(id)
html ~= "<a class=\"br\""~(id ? " id=\""~href~"\"" : "")~" href=\"#"~href~"\">"~num~" </a>";
else
html ~= "<span class=\"br\">"~num~" </span>";
html ~= line;
html ~= "\n";
count++;
}
if(count < 55)
return; // no point cluttering the display with the sample is so small you can eyeball it instantly anyway
pre.innerHTML = html.stripRight;
pre.addClass("with-line-wrappers");
if(count >= 10000)
pre.addClass("ten-thousand-lines");
else if(count >= 1000)
pre.addClass("thousand-lines");
}
string lastDotOnly(string s) {
auto idx = s.lastIndexOf(".");
if(idx == -1) return s;
return s[idx + 1 .. $];
}
struct InheritanceResult {
Decl decl; // may be null
string plainText;
//const(BaseClass) ast;
}
Decl[] declsByUda(string uda, Decl start = null) {
if(start is null) {
assert(0); // cross-module search not implemented here
}
Decl[] list;
if(start.hasUda(uda))
list ~= start;
foreach(child; start.children)
list ~= declsByUda(uda, child);
return list;
}
abstract class Decl {
private int databaseId;
bool fakeDecl = false;
bool alreadyGenerated = false;
abstract string name();
abstract string comment();
abstract string rawComment();
abstract string declarationType();
abstract const(ASTNode) getAstNode();
abstract int lineNumber();
//abstract string sourceCode();
abstract void getAnnotatedPrototype(MyOutputRange);
abstract void getSimplifiedPrototype(MyOutputRange);
final string externNote() {
bool hadABody;
if(auto f = cast(FunctionDecl) this) {
if(f.astNode && f.astNode.functionBody)
hadABody = f.astNode.functionBody.hadABody;
}
if(hadABody)
return ". Be warned that the author may not have intended to support it.";
switch(externWhat) {
case "C":
case "C++":
case "Windows":
case "Objective-C":
return " but is binding to " ~ externWhat ~ ". You might be able to learn more by searching the web for its name.";
case "System":
return " but is binding to an external library. You might be able to learn more by searching the web for its name.";
case null:
default:
return ".";
}
}
DocComment parsedDocComment_;
final @property DocComment parsedDocComment() {
if(parsedDocComment_ is DocComment.init) {
parsedDocComment_ = parseDocumentationComment(this.rawComment().length ? this.comment() : "/++\n$(UNDOCUMENTED Undocumented in source"~externNote~")\n+/", this);
}
return parsedDocComment_;
}
void getAggregatePrototype(MyOutputRange r) {
getSimplifiedPrototype(r);
r.put(";");
}
/* virtual */ void addSupplementalData(Element) {}
// why is this needed?!?!?!?!?
override int opCmp(Object o) {
return cast(int)cast(void*)this - cast(int)cast(void*)o;
}
Decl parentModule() {
auto p = this;
while(p) {
if(p.isModule())
return p;
p = p.parent;
}
assert(0);
}
Decl previousSibling() {
if(parent is null)
return null;
Decl prev;
foreach(child; parent.children) {
if(child is this)
return prev;
prev = child;
}
return null;
}
bool isDocumented() {
// this shouldn't be needed anymore cuz the recursive check below does a better job
//if(this.isModule)
//return true; // allow undocumented modules because then it will at least descend into documented children
// skip modules with "internal" because they are usually not meant
// to be publicly documented anyway
{
auto mod = this.parentModule.name;
if(mod.indexOf(".internal") != -1 && !documentInternal)
return false;
}
if(documentUndocumented)
return true;
if(this.rawComment.length) // hack
return this.rawComment.length > 0; // cool, not a hack
// if it has any documented children, we want to pretend this is documented too
// since then it will be possible to navigate to it
foreach(child; children)
if(child.docsShouldBeOutputted())
return true;
// what follows is all filthy hack
// the C bindings in druntime are not documented, but
// we want them to show up. So I'm gonna hack it.
/*
auto mod = this.parentModule.name;
if(mod.startsWith("core"))
return true;
*/
return false;
}
bool isStatic() {
foreach (a; attributes) {
if(a.attr && a.attr.attribute.type == tok!"static")
return true;
// gshared also implies static (though note that shared does not!)
if(a.attr && a.attr.attribute.type == tok!"__gshared")
return true;
}
return false;
}
bool isPrivate() {
IdType protection;
foreach (a; attributes) {
if (a.attr && isProtection(a.attr.attribute.type))
protection = a.attr.attribute.type;
}
return protection == tok!"private";
}
bool isExplicitlyNonPrivate() {
IdType protection;
bool hadOne;
foreach (a; attributes) {
if (a.attr && isProtection(a.attr.attribute.type)) {
protection = a.attr.attribute.type;
hadOne = true;
}
}
return hadOne && protection != tok!"private" && protection != tok!"package";
}
string externWhat() {
LinkageAttribute attr;
foreach (a; attributes) {
if(a.attr && a.attr.linkageAttribute)
attr = cast() a.attr.linkageAttribute;
}
if(attr is null)
return null;
auto text = attr.identifier.text;
if(text == "Objective")
text = "Objective-C";
else
text = text ~ (attr.hasPlusPlus ? "++" : "");
return text;
}
bool docsShouldBeOutputted() {
if(this.rawComment.indexOf("$(NEVER_DOCUMENT)") != -1)
return false;
if((!this.isPrivate || writePrivateDocs) && this.isDocumented)
return true;
else if(this.rawComment.indexOf("$(ALWAYS_DOCUMENT)") != -1)
return true;
return false;
}
final bool hasUda(string name) {
foreach(a; attributes) {
if(a.attr && a.attr.atAttribute && a.attr.atAttribute.identifier.text == name)
return true;
if(a.attr && a.attr.atAttribute && a.attr.atAttribute.argumentList)
foreach(at; a.attr.atAttribute.argumentList.items) {
if(auto e = cast(UnaryExpression) at)
if(auto pe = e.primaryExpression)
if(auto i = pe.identifierOrTemplateInstance)
if(i.identifier.text == name)
return true;
}
}
return false;
}
// FIXME: isFinal and isVirtual
// FIXME: it would be nice to inherit documentation from interfaces too.
bool isProperty() {
return hasUda("property"); // property isn't actually a UDA, but adrdox doesn't care.
}
bool isDeprecated() {
foreach(a; attributes) {
if(a.attr && a.attr.deprecated_)
return true;
}
return false;
}
bool isAggregateMember() {
return parent ? !parent.isModule : false; // FIXME?
}
// does NOT look for aliased overload sets, just ones right in this scope
// includes this in the return (plus eponymous check). Check if overloaded with .length > 1
Decl[] getImmediateDocumentedOverloads() {
Decl[] ret;
if(this.parent !is null) {
foreach(child; this.parent.children) {
if(((cast(ImportDecl) child) is null) && child.name == this.name && child.docsShouldBeOutputted())
ret ~= child;
}
if(auto t = cast(TemplateDecl) this.parent)
if(this is t.eponymousMember) {
foreach(i; t.getImmediateDocumentedOverloads())
if(i !is t)
ret ~= i;
}
}
return ret;
}
Decl[] getDittos() {
if(this.parent is null)
return null;
size_t lastNonDitto;
foreach(idx, child; this.parent.children) {
if(!child.isDitto())
lastNonDitto = idx;
if(child is this) {
break;
}
}
size_t stop = lastNonDitto;
foreach(idx, child; this.parent.children[lastNonDitto + 1 .. $])
if(child.isDitto())
stop = idx + lastNonDitto + 1 + 1; // one +1 is offset of begin, other is to make sure it is inclusive
else
break;
return this.parent.children[lastNonDitto .. stop];
}
Decl eponymousModuleChild() {
if(!this.isModule)
return null;
auto name = this.name();
auto dot = name.lastIndexOf(".");
name = name[dot + 1 .. $];
Decl emc;
foreach(child; this.children) {
if(cast(ImportDecl) child)
continue;
if(emc !is null)
return null; // not only child
emc = child;
}
// only if there is only the one child AND they have the same name does it count
if(emc !is null && emc.name == name)
return emc;
return null;
}
string link(bool forFile = false, string* masterOverloadName = null) {
auto linkTo = this;
if(!forFile) {
if(auto emc = this.eponymousModuleChild()) {
linkTo = emc;
}
}
auto n = linkTo.fullyQualifiedName();
auto overloads = linkTo.getImmediateDocumentedOverloads();
if(overloads.length > 1) {
int number = 1;
int goodNumber;
foreach(overload; overloads) {
if(overload is this) {
goodNumber = number;
break;
}
number++;
}
if(goodNumber)
number = goodNumber;
else
number = 1;
if(masterOverloadName !is null)
*masterOverloadName = n.idup;
import std.conv : text;
n ~= text(".", number);
}
n ~= ".html";
if(masterOverloadName !is null)
*masterOverloadName ~= ".html";
if(!forFile) {
string d = getDirectoryForPackage(linkTo.fullyQualifiedName());
if(d.length) {
n = d ~ n;
if(masterOverloadName !is null)
*masterOverloadName = d ~ *masterOverloadName;
}
}
return n.handleCaseSensitivity();
}
string[] parentNameList() {
string[] fqn = [name()];
auto p = parent;
while(p) {
fqn = p.name() ~ fqn;
p = p.parent;
}
return fqn;
}
string fullyQualifiedName() {
string fqn = name();
if(isModule)
return fqn;
auto p = parent;
while(p) {
fqn = p.name() ~ "." ~ fqn;
if(p.isModule)
break; // do NOT want package names in here
p = p.parent;
}
return fqn;
}
final InheritanceResult[] inheritsFrom() {
if(!inheritsFromProcessed)
foreach(ref i; _inheritsFrom)
if(this.parent && i.plainText.length) {
i.decl = this.parent.lookupName(i.plainText);
}
inheritsFromProcessed = true;
return _inheritsFrom;
}
InheritanceResult[] _inheritsFrom;
bool inheritsFromProcessed = false;
Decl[string] nameTable;
bool nameTableBuilt;
Decl[string] buildNameTable(string[] excludeModules = null) {
if(!nameTableBuilt) {
lookup: foreach(mod; this.importedModules) {
if(!mod.publicImport)
continue;
if(auto modDeclPtr = mod.name in modulesByName) {
auto modDecl = *modDeclPtr;
foreach(imod; excludeModules)
if(imod == modDeclPtr.name)
break lookup;
auto tbl = modDecl.buildNameTable(excludeModules ~ this.parentModule.name);
foreach(k, v; tbl)
nameTable[k] = v;
}
}
foreach(child; children)
nameTable[child.name] = child;
nameTableBuilt = true;
}
return nameTable;
}
// the excludeModules is meant to prevent circular lookups
Decl lookupName(string name, bool lookUp = true, string[] excludeModules = null) {
if(importedModules.length == 0 || importedModules[$-1].name != "object")
addImport("object", false);
if(name.length == 0)
return null;
string originalFullName = name;
auto subject = this;
if(name[0] == '.') {
// global scope operator
while(subject && !subject.isModule)
subject = subject.parent;
name = name[1 .. $];
originalFullName = originalFullName[1 .. $];
}
auto firstDotIdx = name.indexOf(".");
if(firstDotIdx != -1) {
subject = subject.lookupName(name[0 .. firstDotIdx]);
name = name[firstDotIdx + 1 .. $];
}
if(subject)
while(subject) {
auto table = subject.buildNameTable();
if(name in table)
return table[name];
if(lookUp)
// at the top level, we also need to check private imports
lookup: foreach(mod; subject.importedModules) {
if(mod.publicImport)
continue; // handled by the name table
auto lookupInsideModule = originalFullName;
if(auto modDeclPtr = mod.name in modulesByName) {
auto modDecl = *modDeclPtr;
foreach(imod; excludeModules)
if(imod == modDeclPtr.name)
break lookup;
//import std.stdio; writeln(modDecl.name, " ", lookupInsideModule);
auto located = modDecl.lookupName(lookupInsideModule, false, excludeModules ~ this.parentModule.name);
if(located !is null)
return located;
}
}
if(!lookUp || subject.isModule)
subject = null;
else
subject = subject.parent;
}
else {
// FIXME?
// fully qualified name from this module
subject = this;
if(originalFullName.startsWith(this.parentModule.name ~ ".")) {
// came from here!
auto located = this.parentModule.lookupName(originalFullName[this.parentModule.name.length + 1 .. $]);
if(located !is null)
return located;
} else
while(subject !is null) {
foreach(mod; subject.importedModules) {
if(originalFullName.startsWith(mod.name ~ ".")) {
// fully qualified name from this module
auto lookupInsideModule = originalFullName[mod.name.length + 1 .. $];
if(auto modDeclPtr = mod.name in modulesByName) {
auto modDecl = *modDeclPtr;
auto located = modDecl.lookupName(lookupInsideModule, mod.publicImport);
if(located !is null)
return located;
}
}
}
if(lookUp && subject.isModule)
subject = null;
else
subject = subject.parent;
}
}
return null;
}
final Decl lookupName(const IdentifierOrTemplateInstance ic, bool lookUp = true) {
auto subject = this;
if(ic.templateInstance)
return null; // FIXME
return lookupName(ic.identifier.text, lookUp);
}
final Decl lookupName(const IdentifierChain ic) {
auto subject = this;
assert(ic.identifiers.length);
// FIXME: leading dot?
foreach(idx, ident; ic.identifiers) {
subject = subject.lookupName(ident.text, idx == 0);
if(subject is null) return null;
}
return subject;
}
final Decl lookupName(const IdentifierOrTemplateChain ic) {
auto subject = this;
assert(ic.identifiersOrTemplateInstances.length);
// FIXME: leading dot?
foreach(idx, ident; ic.identifiersOrTemplateInstances) {
subject = subject.lookupName(ident, idx == 0);
if(subject is null) return null;
}
return subject;
}
final Decl lookupName(const Symbol ic) {
// FIXME dot
return lookupName(ic.identifierOrTemplateChain);
}
Decl parent;
Decl[] children;
void writeTemplateConstraint(MyOutputRange output);
const(VersionOrAttribute)[] attributes;
void addChild(Decl decl) {
decl.parent = this;
children ~= decl;
}
struct ImportedModule {
string name;
bool publicImport;
}
ImportedModule[] importedModules;
void addImport(string moduleName, bool isPublic) {
importedModules ~= ImportedModule(moduleName, isPublic);
}
struct Unittest {
const(dparse.ast.Unittest) ut;
string code;
string comment;
}
Unittest[] unittests;
void addUnittest(const(dparse.ast.Unittest) ut, const(ubyte)[] code, string comment) {
int slicePoint = 0;
foreach(idx, b; code) {
if(b == ' ' || b == '\t' || b == '\r')
slicePoint++;
else if(b == '\n') {
slicePoint++;
break;
} else {
slicePoint = 0;
break;
}
}
code = code[slicePoint .. $];
unittests ~= Unittest(ut, unittestCodeToString(code), comment);
}
string unittestCodeToString(const(ubyte)[] code) {
auto excludeString = cast(const(ubyte[])) "// exclude from docs";
bool replacementMade;
import std.algorithm.searching;
auto idx = code.countUntil(excludeString);
while(idx != -1) {
int before = cast(int) idx;
int after = cast(int) idx;
while(before > 0 && code[before] != '\n')
before--;
while(after < code.length && code[after] != '\n')
after++;
code = code[0 .. before] ~ code[after .. $];
replacementMade = true;
idx = code.countUntil(excludeString);
}
if(!replacementMade)
return (cast(char[]) code).idup; // needs to be unique
else
return cast(string) code; // already copied above, so it is unique
}
struct ProcessedUnittest {
string code;
string comment;
bool embedded;
}
bool _unittestsProcessed;
ProcessedUnittest[] _processedUnittests;
ProcessedUnittest[] getProcessedUnittests() {
if(_unittestsProcessed)
return _processedUnittests;
_unittestsProcessed = true;
// source, comment
ProcessedUnittest[] ret;
Decl start = this;
if(isDitto()) {
foreach(child; this.parent.children) {
if(child is this)
break;
if(!child.isDitto())
start = child;
}
}
bool started = false;
if(this.parent)
foreach(child; this.parent.children) {
if(started) {
if(!child.isDitto())
break;
} else {
if(child is start)
started = true;
}
if(started)
foreach(test; child.unittests)
if(test.comment.length)
ret ~= ProcessedUnittest(test.code, test.comment);
}
else
foreach(test; this.unittests)
if(test.comment.length)
ret ~= ProcessedUnittest(test.code, test.comment);
_processedUnittests = ret;
return ret;
}
override string toString() {
string s;
s ~= super.toString() ~ " " ~ this.name();
foreach(child; children) {
s ~= "\n";
auto p = parent;
while(p) {
s ~= "\t";
p = p.parent;
}
s ~= child.toString();
}
return s;
}
abstract bool isDitto();
bool isModule() { return false; }
bool isArticle() { return false; }
bool isConstructor() { return false; }
bool aliasThisPresent;
Token aliasThisToken;
string aliasThisComment;
Decl aliasThis() {
if(!aliasThisPresent)
return null;
else
return lookupName(aliasThisToken.text, false);
}
DestructorDecl destructor() {
foreach(child; children)
if(auto dd = cast(DestructorDecl) child)
return dd;
return null;
}
PostblitDecl postblit() {
foreach(child; children)
if(auto dd = cast(PostblitDecl) child)
return dd;
return null;
}
abstract bool isDisabled();
ConstructorDecl disabledDefaultConstructor() {
foreach(child; children)
if(child.isConstructor() && child.isDisabled()) {
auto ctor = cast(ConstructorDecl) child;
if(ctor.astNode.parameters || ctor.astNode.parameters.parameters.length == 0)
return ctor;
}
return null;
}
}
class ModuleDecl : Decl {
mixin CtorFrom!Module defaultMixins;
string justDocsTitle;
override bool isModule() { return true; }
override bool isArticle() { return justDocsTitle.length > 0; }
override bool docsShouldBeOutputted() {
if(this.justDocsTitle !is null)
return true;
return super.docsShouldBeOutputted();
}
override string declarationType() {
return isArticle() ? "Article" : "module";
}
version(none)
override void getSimplifiedPrototype(MyOutputRange r) {
if(isArticle())
r.put(justDocsTitle);
else
defaultMixins.getSimplifiedPrototype(r);
}
ubyte[] originalSource;
string packageName() {
auto it = this.name();
auto idx = it.lastIndexOf(".");
if(idx == -1)
return null;
return it[0 .. idx];
}
}
class AliasDecl : Decl {
mixin CtorFrom!AliasDeclaration;
this(const(AliasDeclaration) ad, const(VersionOrAttribute)[] attributes) {
this.attributes = attributes;
this.astNode = ad;
this.initializer = null;
// deal with the type and initializer list and storage classes
}
const(AliasInitializer) initializer;
this(const(AliasDeclaration) ad, const(AliasInitializer) init, const(VersionOrAttribute)[] attributes) {
this.attributes = attributes;
this.astNode = ad;
this.initializer = init;
// deal with init
}
override string name() {
if(initializer is null)
return toText(astNode.identifierList);
else
return initializer.name.text;
}
override void getAnnotatedPrototype(MyOutputRange output) {
void cool() {
output.putTag("<div class=\"declaration-prototype\">");
if(parent !is null && !parent.isModule) {
output.putTag("<div class=\"parent-prototype\">");
parent.getSimplifiedPrototype(output);
output.putTag("</div><div>");
getPrototype(output, true);
output.putTag("</div>");
} else {
getPrototype(output, true);
}
output.putTag("</div>");
}
writeOverloads!cool(this, output);
}
override void getSimplifiedPrototype(MyOutputRange output) {
getPrototype(output, false);
}
void getPrototype(MyOutputRange output, bool link) {
// FIXME: storage classes?
if(link) {
auto f = new MyFormatter!(typeof(output))(output, this);
writeAttributes(f, output, this.attributes);
}
output.putTag("<span class=\"builtin-type\">alias</span> ");
output.putTag("<span class=\"name\">");
output.put(name);
output.putTag("</span>");
if(initializer && initializer.templateParameters) {
output.putTag(toHtml(initializer.templateParameters).source);
}
output.put(" = ");
if(initializer) {
if(link)
output.putTag(toLinkedHtml(initializer.type, this).source);
else
output.putTag(toHtml(initializer.type).source);
}
if(astNode.type) {
if(link) {
auto t = toText(astNode.type);
auto decl = lookupName(t);
if(decl is null)
goto nulldecl;
output.putTag(getReferenceLink(t, decl).toString);
} else {
nulldecl:
output.putTag(toHtml(astNode.type).source);
}
}
}
}
class VariableDecl : Decl {
mixin CtorFrom!VariableDeclaration mixinmagic;
const(Declarator) declarator;
this(const(Declarator) declarator, const(VariableDeclaration) astNode, const(VersionOrAttribute)[] attributes) {
this.astNode = astNode;
this.declarator = declarator;
this.attributes = attributes;
this.ident = Token.init;
this.initializer = null;
foreach(a; astNode.attributes)
this.attributes ~= new VersionOrAttribute(a);
filterDuplicateAttributes();
}
const(Token) ident;
const(Initializer) initializer;
this(const(VariableDeclaration) astNode, const(Token) ident, const(Initializer) initializer, const(VersionOrAttribute)[] attributes, bool isEnum) {
this.declarator = null;
this.attributes = attributes;
this.astNode = astNode;
this.ident = ident;
this.isEnum = isEnum;
this.initializer = initializer;
foreach(a; astNode.attributes)
this.attributes ~= new VersionOrAttribute(a);
filterDuplicateAttributes();
}
void filterDuplicateAttributes() {
const(VersionOrAttribute)[] filtered;
foreach(idx, a; attributes) {
bool isdup;
foreach(b; attributes[idx + 1 .. $]) {
if(a is b)
continue;
if(cast(FakeAttribute) a || cast(FakeAttribute) b)
continue;
if(a.attr is b.attr)
isdup = true;
else if(toText(a.attr) == toText(b.attr))
isdup = true;
}
if(!isdup)
filtered ~= a;
}
this.attributes = filtered;
}
bool isEnum;
override string name() {
if(declarator)
return declarator.name.text;
else
return ident.text;
}
override bool isDitto() {
if(declarator && declarator.comment is null) {
foreach (idx, const Declarator d; astNode.declarators) {
if(d.comment !is null) {
break;
}
if(d is declarator && idx)
return true;
}
}
return mixinmagic.isDitto();
}
override string rawComment() {
string it = astNode.comment;
auto additional = (declarator ? declarator.comment : astNode.autoDeclaration.comment);
if(additional != it)
it ~= additional;
return it;
}
override void getAnnotatedPrototype(MyOutputRange outputFinal) {
string t;
MyOutputRange output = MyOutputRange(&t);
void piece() {
output.putTag("<div class=\"declaration-prototype\">");
if(parent !is null && !parent.isModule) {
output.putTag("<div class=\"parent-prototype\">");
parent.getSimplifiedPrototype(output);
output.putTag("</div><div>");
auto f = new MyFormatter!(typeof(output))(output);
writeAttributes(f, output, attributes);
getSimplifiedPrototypeInternal(output, true);
output.putTag("</div>");
} else {
auto f = new MyFormatter!(typeof(output))(output);
writeAttributes(f, output, attributes);
getSimplifiedPrototypeInternal(output, true);
}
output.putTag("</div>");
}
writeOverloads!piece(this, output);
outputFinal.putTag(linkUpHtml(t, this));
}
override void getSimplifiedPrototype(MyOutputRange output) {
getSimplifiedPrototypeInternal(output, false);
}
final void getSimplifiedPrototypeInternal(MyOutputRange output, bool link) {
foreach(sc; astNode.storageClasses) {
output.putTag(toHtml(sc).source);
output.put(" ");
}
if(astNode.type) {
if(link) {
auto html = toHtml(astNode.type).source;
auto txt = toText(astNode.type);
auto typeDecl = lookupName(txt);
if(typeDecl is null || !typeDecl.docsShouldBeOutputted)
goto plain;
output.putTag("<a title=\""~typeDecl.fullyQualifiedName~"\" href=\""~typeDecl.link~"\">" ~ html ~ "</a>");
} else {
plain:
output.putTag(toHtml(astNode.type).source);
}
} else
output.putTag("<span class=\"builtin-type\">"~(isEnum ? "enum" : "auto")~"</span>");
output.put(" ");
output.putTag("<span class=\"name\">");
output.put(name);
output.putTag("</span>");
if(declarator && declarator.templateParameters)
output.putTag(toHtml(declarator.templateParameters).source);
if(link) {
if(initializer !is null) {
output.put(" = ");
output.putTag(toHtml(initializer).source);
}
}
output.put(";");
}
override void getAggregatePrototype(MyOutputRange output) {
auto f = new MyFormatter!(typeof(output))(output);
writeAttributes(f, output, attributes);
getSimplifiedPrototypeInternal(output, false);
}
override string declarationType() {
return (isStatic() ? "static variable" : (isEnum ? "manifest constant" : "variable"));
}
}
class FunctionDecl : Decl {
mixin CtorFrom!FunctionDeclaration;
override void getAnnotatedPrototype(MyOutputRange output) {
doFunctionDec(this, output);
}
override Decl lookupName(string name, bool lookUp = true, string[] excludeModules = null) {
// is it a param or template param? If so, return that.
foreach(param; astNode.parameters.parameters) {
if (param.name.type != tok!"")
if(param.name.text == name) {
return null; // it is local, but we don't have a decl..
}
}
if(astNode.templateParameters && astNode.templateParameters.templateParameterList && astNode.templateParameters.templateParameterList.items)
foreach(param; astNode.templateParameters.templateParameterList.items) {
auto paramName = "";
if(param.templateTypeParameter)
paramName = param.templateTypeParameter.identifier.text;
else if(param.templateValueParameter)
paramName = param.templateValueParameter.identifier.text;
else if(param.templateAliasParameter)
paramName = param.templateAliasParameter.identifier.text;
else if(param.templateTupleParameter)
paramName = param.templateTupleParameter.identifier.text;
if(paramName.length && paramName == name) {
return null; // it is local, but we don't have a decl..
}
}
if(lookUp)
return super.lookupName(name, lookUp, excludeModules);
else
return null;
}
override string declarationType() {
return isProperty() ? "property" : (isStatic() ? "static function" : "function");
}
override void getAggregatePrototype(MyOutputRange output) {
bool hadAttrs;
foreach(attr; attributes)
if(auto cfa = cast(ConditionFakeAttribute) attr) {
if(!hadAttrs) {
output.putTag("<div class=\"conditional-compilation-attributes\">");
hadAttrs = true;
}
output.putTag(cfa.toHTML);
output.putTag("<br />\n");
}
if(hadAttrs)
output.putTag("</div>");
if(isStatic()) {
output.putTag("<span class=\"storage-class\">static</span> ");
}
getSimplifiedPrototype(output);
output.put(";");
}
override void getSimplifiedPrototype(MyOutputRange output) {
foreach(sc; astNode.storageClasses) {
output.putTag(toHtml(sc).source);
output.put(" ");
}
if(isProperty() && (paramCount == 0 || paramCount == 1 || (paramCount == 2 && !isAggregateMember))) {
if((paramCount == 1 && isAggregateMember()) || (paramCount == 2 && !isAggregateMember())) {
// setter
output.putTag(toHtml(astNode.parameters.parameters[0].type).source);
output.put(" ");
output.putTag("<span class=\"name\">");
output.put(name);
output.putTag("</span>");
output.put(" [@property setter]");
} else {
// getter
putSimplfiedReturnValue(output, astNode);
output.put(" ");
output.putTag("<span class=\"name\">");
output.put(name);
output.putTag("</span>");
output.put(" [@property getter]");
}
} else {
putSimplfiedReturnValue(output, astNode);
output.put(" ");
output.putTag("<span class=\"name\">");
output.put(name);
output.putTag("</span>");
putSimplfiedArgs(output, astNode);
}
}
int paramCount() {
return cast(int) astNode.parameters.parameters.length;
}
}
class ConstructorDecl : Decl {
mixin CtorFrom!Constructor;
override void getAnnotatedPrototype(MyOutputRange output) {
doFunctionDec(this, output);
}
override void getSimplifiedPrototype(MyOutputRange output) {
output.putTag("<span class=\"lang-feature name\">");
output.put("this");
output.putTag("</span>");
putSimplfiedArgs(output, astNode);
}
override bool isConstructor() { return true; }
}
class DestructorDecl : Decl {
mixin CtorFrom!Destructor;
override void getSimplifiedPrototype(MyOutputRange output) {
output.putTag("<span class=\"lang-feature name\">");
output.put("~this");
output.putTag("</span>");
output.put("()");
}
}
class PostblitDecl : Decl {
mixin CtorFrom!Postblit;
override void getSimplifiedPrototype(MyOutputRange output) {
if(isDisabled) {
output.putTag("<span class=\"builtin-type\">");
output.put("@disable");
output.putTag("</span>");
output.put(" ");
}
output.putTag("<span class=\"lang-feature name\">");
output.put("this(this)");
output.putTag("</span>");
}
}
class ImportDecl : Decl {
mixin CtorFrom!ImportDeclaration;
bool isPublic;
string newName;
string oldName;
override string link(bool forFile = false, string* use