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