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 }