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