import arsd.dom; import std.file; import std.algorithm; import std.range; import std.exception : enforce; static import std.string; class Symbol { string type; string name; string parentName; string fullName; Element d_decl; Html summary; string[] tags; string primaryTag() { if(tags.length) return tags[0]; return "Uncategorized"; } Symbol parent; Symbol[] children; Symbol[] getTree() { Symbol[] tree; tree ~= this; foreach(child; children) tree ~= child.getTree(); return tree; } } void main(string[] args) { string arg = "stdio.html"; if(args.length > 1) arg = args[1]; auto document = new Document(readText(arg)); Symbol[] symbols; Symbol currentParent; // Get our info together... foreach(e; document.getElementsBySelector(".d_decl > .ddoc_psymbol")) { // .ddoc_psymbol")) { Symbol s = new Symbol; s.type = getType(e.parentNode); s.d_decl = e.parentNode; s.name = e.innerText; s.summary = getSummary(e); s.tags = getTags(e); auto parent = getParentTerm(e); // we're really interested just in *if* it has a parent if(parent is null) { currentParent = s; symbols ~= s; s.fullName = s.name; } else { assert(currentParent !is null); s.parent = currentParent; s.fullName = s.parent.name ~ "." ~ s.name; currentParent.children ~= s; } } fixupAnchors(symbols); foreach(e; document.getElementsBySelector(".d_inlinecode > a[name]")) { // Why are these even outputted by ddoc? e.parentNode.removeChild(e); } // We want the table to be right before the main content auto where = document.requireSelector("dl"); foreach(i, type; ["class / struct", "enum", "function"]) { auto n = where.parentNode.insertBefore(where, summaryTableFrom(document, array(filter!((a) { if(i == 0) return a.type == "class" || a.type == "struct"; else return a.type == type; })(symbols)), type)); if(i == 0) n.id = "quick-reference"; } Symbol[] allSymbols; foreach(s; symbols) allSymbols ~= s.getTree(); // This does tables for all the child members too foreach(element; document.getElementsBySelector("dd > dl")) { auto d_decl = element.parentNode.previousSibling; while(d_decl !is null && !d_decl.hasClass("d_decl")) { auto e = d_decl.querySelector("> .d_decl"); if(e) { d_decl = e; break; } d_decl = d_decl.previousSibling; } enforce(d_decl !is null); // we must have a parent is Symbol symbol; foreach(s; allSymbols) // lol linear search if(s.d_decl is d_decl) { symbol = s; break; } enforce(symbol !is null, d_decl.toString()); // should have parsed info too if(symbol.type == "enum") continue; // enums are usually short enough that adding the table for them just waste space element.parentNode.insertBefore(element, summaryTableFrom(document, symbol.children, "Member")); } // HACK for working on my computer foreach(e; document.getElementsBySelector("link[rel=stylesheet]")) { if(!startsWith(e.href, "http")) e.href = "http://dlang.org/css/style.css"; } // HACK to add some custom style document.requireSelector("head").addChild("style", " .book { margin-top: 3em; margin-bottom: 3em; width: 100%; }"); // don't need this anymore auto quickindex = document.getElementById("quickindex"); if(quickindex !is null) quickindex.removeFromTree; // finally, write out the changed file std.file.write("std_" ~ arg, document.toString()); } Element summaryTableFrom(Document document, Symbol[] symbols, string type) { if(symbols.length == 0) return document.createTextNode(" "); // don't output a table with nothing // First, we want a sorting by primary tag (the first one to appear) // Then, that result is sorted by name. This lets us put them out beautifully bool mysorter(Symbol a, Symbol b) { int diff = std.string.cmp(a.primaryTag(), b.primaryTag()); if(diff == 0) diff = std.string.cmp(a.name, b.name); return diff < 0; } auto table = cast(Table) document.createElement("table"); table.addClass("book"); table.caption = std.string.capitalize(type) ~ " Quick Reference"; auto thead = table.addChild("thead"); auto thead_row = thead.addChild("tr"); thead_row.addChild("th", std.string.capitalize(type) ~ " Name"); thead_row.addChild("th", "Description"); string currentHeader; string lastSymbol; int categories = 0; foreach(s; sort!(mysorter)(symbols)) { // the header..... if(currentHeader != s.primaryTag()) { table.appendRow(table.th(s.primaryTag()) .setAttribute("colspan", "2")).setAttribute("class", "leadingrow"); currentHeader = s.primaryTag(); categories++; } if(s.fullName == lastSymbol) continue; // skip overloads and other duplicates table.appendRow(new Link("#" ~ s.fullName, s.name), s.summary); lastSymbol = s.fullName; } // these shouldn't be up here! foreach(a; table.getElementsBySelector("a[name], a[id]")) { a.parentNode.removeChild(a); } if(categories == 1) // let's not bother differentiating the tags if there's only one foreach(a; table.getElementsBySelector(".leadingrow")) a.removeFromTree; return table; } // This is needed because member anchors get repeated in stock ddoc void fixupAnchors(Symbol[] symbols) { foreach(symbol; symbols) { foreach(e; symbol.d_decl.getElementsBySelector("> a[name]")) { e.name = symbol.fullName; // the full name is unambiguous. But, what about function overloads? } fixupAnchors(symbol.children); } } string[] getTags(Element e) { auto dd = getAssociatedDefinition(e); string[] tags; foreach(a; dd.getElementsBySelector("a.tag[href]")) { tags ~= a.innerText; } return tags; } Html getSummary(Element e) { // The summary is in the next sibling dd after the parent dt. // The summary extends from the first node up until the first paragraph tag. auto dd = enforce(getAssociatedDefinition(e)); string html; foreach(child; dd.childNodes) { if(child.tagName == "p") break; html ~= child.toString(); } return Html(html); } string getType(Element d_decl) { //
struct File; // we want "struct" from there - the first text node's contents. auto txt = cast(TextNode) d_decl.firstChild; if(txt is null) return "unknown"; auto it = std.string.strip(txt.contents); switch(it) { default: return "function"; case "class", "struct", "enum": return it; } assert(0); // never reached } Element getParentTerm(Element e) { e = e.getParent("dd"); if(e is null) return null; // doesn't have a parent return enforce(e.previousSibling("dt")); } Element getAssociatedDefinition(Element e) { e = enforce(e.getParent("dt")); return enforce(e.nextSibling("dd")); }