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.length == 0) 34 listItem = name; 35 else 36 { 37 if (nameParts.length > 1) 38 listItem ~= nameParts[0 .. $ - 1].join(".") ~ "."; 39 listItem ~= `<a href="%s">%s</a>`.format(url, nameParts.back); 40 } 41 if (isPackage) 42 spanJS = ` onclick="show_hide('%s');"`.format(name); 43 } 44 45 /** Write the TOC item. 46 * 47 * Params: 48 * 49 * dst = Range to write to. 50 * moduleName = Name of the module/package in the documentation page of which 51 * we're writing this TOC, if we're writing module/package documentation. 52 */ 53 public void write(R)(ref R dst, string moduleName = "") 54 { 55 // Is this TOC item the module/package the current documentation page 56 // documents? 57 const isSelected = name == moduleName; 58 59 dst.put(`<li>`); 60 const css = isPackage || isSelected; 61 62 if (!css) 63 dst.put(listItem); 64 else 65 { 66 dst.put(`<span class="`); 67 if (isPackage) 68 dst.put("package"); 69 if (isSelected) 70 dst.put(" selected"); 71 dst.put(`"`); 72 if (isPackage) 73 dst.put(spanJS); 74 dst.put(`>`); 75 dst.put(listItem); 76 dst.put("</span>\n"); 77 } 78 79 if (isPackage) 80 { 81 auto moduleParts = moduleName.splitter("."); 82 const block = moduleParts.startsWith(nameParts); 83 dst.put(`<ul id="`); 84 dst.put(name); 85 dst.put(`"`); 86 if (moduleParts.startsWith(nameParts)) 87 dst.put(` style='display:block'`); 88 dst.put(">\n"); 89 items.each!(item => item.write(dst, moduleName)); 90 // End a package's list of members 91 dst.put("</ul>\n"); 92 } 93 dst.put("</li>\n"); 94 } 95 } 96 97 TocItem[] buildTree(string[] strings, string[string] links, const size_t offset = 0) 98 { 99 TocItem[] items; 100 size_t i = 0; 101 strings.sort(); 102 while (i < strings.length) 103 { 104 size_t j = i + 1; 105 auto s = strings[i][offset .. $].findSplit("."); 106 const string prefix = s[0]; 107 string suffix = s[2]; 108 TocItem item; 109 if (prefix.length != 0 && suffix.length != 0) 110 { 111 while (j < strings.length && strings[j][offset .. $].startsWith(prefix ~ s[1])) 112 j++; 113 if (i < j) 114 { 115 const o = offset + prefix.length + 1; 116 item.items = buildTree(strings[i .. j], links, o); 117 } 118 } 119 else 120 item.url = links[strings[i]]; 121 122 item.name = strings[i][0 .. item.items.empty ? $ : offset + prefix.length]; 123 124 if (items.length > 0 && items.back.name == item.name) 125 items.back.items = item.items; 126 else 127 items ~= item; 128 129 i = j; 130 } 131 foreach (ref item; items) 132 item.preCache(); 133 return items; 134 }