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