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