1 module tocbuilder; 2 3 import std.algorithm; 4 import std.array: back, empty; 5 import std.stdio; 6 import std.string: format; 7 import std.array; 8 9 struct TocItem 10 { 11 private string name; 12 private string url; 13 private TocItem[] items; 14 15 /// Computed by preCache() below /// 16 17 /// Item name split by '.' This is an optimization (redundant with e.g. name.splitter) 18 private string[] nameParts; 19 /// Is this a package item? 20 private bool isPackage; 21 /// JS for opening/closing packages. 22 private string spanJS; 23 24 /// HTML content of the list item (can be wrapped in any <li> or <span>). 25 private string listItem; 26 27 /// Precompute any values that will be frequently reused. 28 private void preCache() 29 { 30 import std.string: split; 31 nameParts = name.split("."); 32 isPackage = items.length != 0; 33 if(url is null) 34 { 35 listItem = name; 36 } 37 else 38 { 39 if(nameParts.length > 1) 40 { 41 listItem ~= nameParts[0 .. $ - 1].join(".") ~ "."; 42 } 43 listItem ~= `<a href="%s">%s</a>`.format(url, nameParts.back); 44 } 45 } 46 47 /** Write the TOC item. 48 * 49 * Params: 50 * 51 * dst = Range to write to. 52 * moduleName = Name of the module/package in the documentation page of which 53 * we're writing this TOC, if we're writing module/package documentation. 54 */ 55 public void write(R)(ref R dst, string moduleName = "") 56 { 57 // Is this TOC item the module/package the current documentation page 58 // documents? 59 const isSelected = name == moduleName; 60 61 dst.put(`<li>`); 62 const css = isPackage || isSelected; 63 64 if(!css) 65 { 66 dst.put(listItem); 67 } 68 else 69 { 70 dst.put(`<span class="`); 71 if(isPackage) { dst.put("package"); } 72 if(isSelected) { dst.put(" selected"); } 73 dst.put(`"`); 74 if(isPackage) { dst.put(spanJS); } 75 dst.put(`>`); 76 dst.put(listItem); 77 dst.put("</span>\n"); 78 } 79 80 if (isPackage) 81 { 82 auto moduleParts = moduleName.splitter("."); 83 const block = moduleParts.startsWith(nameParts); 84 dst.put(`<ul id="`); 85 dst.put(name); 86 dst.put(`"`); 87 if (moduleParts.startsWith(nameParts)) 88 { 89 dst.put(` style='display:block; margin-top: 0.5em;'`); 90 } 91 dst.put(">\n"); 92 93 foreach (item; items) 94 item.write(dst, moduleName); 95 // End a package's list of members 96 dst.put("</ul>\n"); 97 } 98 dst.put("</li>\n"); 99 } 100 } 101 102 TocItem[] buildTree(string[] strings, string[string] links, const size_t offset = 0) 103 { 104 TocItem[] items; 105 size_t i = 0; 106 strings.sort(); 107 while (i < strings.length) 108 { 109 size_t j = i + 1; 110 auto s = strings[i][offset .. $].findSplit("."); 111 const string prefix = s[0]; 112 string suffix = s[2]; 113 TocItem item; 114 if (prefix.length != 0 && suffix.length != 0) 115 { 116 while (j < strings.length && strings[j][offset .. $].startsWith(prefix ~ s[1])) 117 j++; 118 if (i < j) 119 { 120 size_t o = offset + prefix.length + 1; 121 item.items = buildTree(strings[i .. j], links, o); 122 } 123 } 124 else 125 item.url = links[strings[i]]; 126 127 item.name = strings[i][0 .. item.items.empty ? $ : offset + prefix.length]; 128 129 if(items.length > 0 && items.back.name == item.name) 130 { 131 items.back.items = item.items; 132 } 133 else 134 { 135 items ~= item; 136 } 137 138 i = j; 139 } 140 foreach(ref item; items) 141 { 142 item.preCache(); 143 } 144 return items; 145 }