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