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 }