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 }