Skip to content

Commit c340573

Browse files
committed
Add header TOC generation
Fix Issue 18202 - Show TOC overview in the dlang specification pages
1 parent 1389172 commit c340573

File tree

1 file changed

+121
-1
lines changed

1 file changed

+121
-1
lines changed

ddoc.d

Lines changed: 121 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ Example usage:
1414
1515
Author: Sebastian Wilzbach
1616
*/
17-
import std.stdio;
17+
import std.algorithm, std.array, std.ascii, std.conv, std.file, std.functional,
18+
std.meta, std.path, std.range, std.string, std.typecons;
19+
import std.stdio : writeln, writefln;
1820

1921
struct Config
2022
{
@@ -45,6 +47,10 @@ int main(string[] args)
4547

4648
import std.file : readText;
4749
auto text = args[$ - 1].readText;
50+
51+
// transform and extend the ddoc page
52+
text = genHeader(text);
53+
4854
return compile(text, args[1 .. $ - 1]);
4955
}
5056

@@ -58,3 +64,117 @@ auto compile(R)(R buffer, string[] arguments)
5864
pipes.stdin.close;
5965
return wait(pipes.pid);
6066
}
67+
68+
// replaces the content of a DDoc macro call
69+
auto updateDdocTag(string fileText, string ddocKey, string newContent)
70+
{
71+
auto pos = fileText.representation.countUntil(ddocKey);
72+
if (pos < 0)
73+
return fileText;
74+
const ddocStartLength = ddocKey.representation.until('(', No.openRight).count;
75+
auto len = fileText[pos .. $].representation.drop(ddocStartLength).untilClosingParentheses.walkLength;
76+
return fileText.replace(fileText[pos .. pos + len + ddocStartLength + 1], newContent);
77+
}
78+
79+
// a range until the next ')', nested () are ignored
80+
auto untilClosingParentheses(R)(R rs)
81+
{
82+
return rs.cumulativeFold!((count, r){
83+
switch(r)
84+
{
85+
case '(':
86+
count++;
87+
break;
88+
case ')':
89+
count--;
90+
break;
91+
default:
92+
}
93+
return count;
94+
})(1).zip(rs).until!(e => e[0] == 0).map!(e => e[1]);
95+
}
96+
97+
unittest
98+
{
99+
import std.algorithm.comparison : equal;
100+
assert("aa $(foo $(bar)foobar)".untilClosingParentheses.equal("aa $(foo $(bar)foobar)"));
101+
assert("$(FOO a, b, $(ARGS e, f)))".untilClosingParentheses.equal("$(FOO a, b, $(ARGS e, f))"));
102+
}
103+
104+
// parse the ddoc file for H2 and H3 items
105+
// H3 items are listed as subitems
106+
auto parseToc(string text)
107+
{
108+
alias TocEntry = Tuple!(string, "id", string, "name");
109+
alias TocTopEntry = Tuple!(TocEntry, "main", TocEntry[], "children");
110+
TocTopEntry[] toc;
111+
112+
bool isH2 = true;
113+
void append(string id, string name)
114+
{
115+
auto entry = TocEntry(id, name);
116+
if (isH2)
117+
toc ~= TocTopEntry(entry, null);
118+
else
119+
toc.back.children ~= entry;
120+
}
121+
while (!text.empty)
122+
{
123+
enum needles = AliasSeq!("$(H2 ", "$(SECTION2 ", "$(H3", "$(SECTION3");
124+
auto res = text.find(needles);
125+
if (res[0].empty)
126+
break;
127+
128+
isH2 = res[1] <= 2;
129+
text = res[0].drop(needles.only[res[1] - 1].length);
130+
text.skipOver!isWhite;
131+
132+
enum gname = "$(GNAME ";
133+
enum lNameNeedles = AliasSeq!("$(LNAME2", "$(LEGACY_LNAME2");
134+
if (text.startsWith(gname))
135+
{
136+
auto name = text.drop(gname.length).untilClosingParentheses.to!string.strip;
137+
append(name, name);
138+
}
139+
else if (auto idx = text.startsWith(lNameNeedles))
140+
{
141+
auto arr = text.drop(lNameNeedles.only[idx - 1].length).splitter(",");
142+
if (idx == 2)
143+
arr.popFront;
144+
append(arr.front.strip, arr.dropOne.joiner(",").untilClosingParentheses.to!string.strip);
145+
}
146+
}
147+
return toc;
148+
}
149+
150+
// Ddoc splits arguments by commas
151+
auto escapeDdoc(string s)
152+
{
153+
return s.replace(",", "$(COMMA)");
154+
}
155+
156+
// generated a SPEC_HEADERNAV_TOC Ddoc macro with the parsed H2/H3 entries
157+
auto genHeader(string fileText)
158+
{
159+
enum ddocKey = "$(SPEC_HEADERNAV_TOC";
160+
auto newContent = ddocKey ~ "\n";
161+
enum indent = " ";
162+
foreach (entry; fileText.parseToc)
163+
{
164+
if (entry.children)
165+
{
166+
newContent ~= "%s$(SPEC_HEADERNAV_SUBITEMS %s, %s,\n".format(indent, entry.main.id, entry.main.name.escapeDdoc);
167+
foreach (child; entry.children)
168+
newContent ~= "%s$(SPEC_HEADERNAV_ITEM %s, %s)\n".format(indent.repeat(2).joiner, child.id, child.name.escapeDdoc);
169+
newContent ~= indent;
170+
newContent ~= ")\n";
171+
}
172+
else
173+
{
174+
newContent ~= "%s$(SPEC_HEADERNAV_ITEM %s, %s)\n".format(indent, entry.main.id, entry.main.name.escapeDdoc);
175+
}
176+
}
177+
newContent ~= ")";
178+
return updateDdocTag(fileText, ddocKey, newContent);
179+
}
180+

0 commit comments

Comments
 (0)