1 module api.doc;
2 
3 import api.classes, api.methods, api.util;
4 import godotutil..string;
5 
6 import std.stdio : writeln, writefln;
7 import std..string, std.range;
8 import std.algorithm.searching;
9 import std.exception : enforce;
10 
11 import dxml.dom;
12 import dxml.util : stripIndent;
13 
14 void parseClassDoc(GodotClass c, string xml)
15 {
16 	string ddoc;
17 	auto dom = parseDOM!simpleXML(xml);
18 	
19 	enforce(dom.children.length == 1 && dom.children[0].name == "class",
20 		"Expected a single <class name=\""~c.name.d~"\"> in the doc XML");
21 	auto cDoc = dom.children[0];
22 	
23 	if(cDoc.hasChild("brief_description"))
24 	{
25 		auto bd = cDoc.child("brief_description");
26 		if(bd.childText)
27 		{
28 			ddoc ~= bd.childText;
29 			c.ddocBrief = bd.childText;
30 		}
31 	}
32 	
33 	if(cDoc.hasChild("description"))
34 	{
35 		auto fd = cDoc.child("description");
36 		if(fd.childText)
37 		{
38 			string t = fd.childText;
39 			// in case it starts with brief_description; only possible to detect if exactly the same, unfortunately
40 			t = t.chompPrefix(c.ddocBrief).strip;
41 			if(t)
42 			{
43 				if(ddoc) ddoc ~= "\n\n";
44 				ddoc ~= t;
45 			}
46 		}
47 	}
48 	
49 	/// TODO: remove any */ from inside comment and change BBCode-style stuff to ddoc macros
50 	c.ddocBrief = c.ddocBrief.godotToDdoc;
51 	c.ddoc = ddoc.godotToDdoc;
52 	
53 	if(cDoc.hasChild("methods"))
54 	{
55 		auto methods = cDoc.child("methods");
56 		foreach(mDoc; methods.children)
57 		{
58 			auto index = c.methods.countUntil!"a.name == b"(mDoc.attribute("name"));
59 			if(index != -1)
60 			{
61 				parseMethodDoc(c.methods[index], mDoc);
62 			}
63 			else writefln("No method %s.%s", c.name.godot, mDoc.attribute("name"));
64 		}
65 	}
66 	
67 	if(cDoc.hasChild("members"))
68 	{
69 		auto members = cDoc.child("members");
70 		foreach(mDoc; members.children)
71 		{
72 			auto index = c.properties.countUntil!"a.name == b"(mDoc.attribute("name"));
73 			if(index != -1)
74 			{
75 				parsePropertyDoc(c.properties[index], mDoc);
76 			}
77 			else writefln("No property %s.%s", c.name.godot, mDoc.attribute("name"));
78 		}
79 	}
80 	
81 	if(cDoc.hasChild("constants"))
82 	{
83 		auto constants = cDoc.child("constants");
84 		foreach(ceDoc; constants.children)
85 		{
86 			auto cPtr = ceDoc.attribute("name") in c.constants;
87 			if(cPtr) c.ddocConstants[ceDoc.attribute("name")] = ceDoc.childText.godotToDdoc;
88 			
89 			if(!ceDoc.attribute("enum").empty)
90 			{
91 				auto index = c.enums.countUntil!"a.name == b"(ceDoc.attribute("enum"));
92 				if(index != -1) c.enums[index].ddoc[ceDoc.attribute("name")] = ceDoc.childText.godotToDdoc;
93 			}
94 		}
95 	}
96 	
97 	/// TODO: enums
98 }
99 
100 void parseMethodDoc(GodotMethod m, DOMEntity!string mDoc)
101 {
102 	if(mDoc.hasChild("description"))
103 	{
104 		auto fd = mDoc.child("description");
105 		if(fd.childText) m.ddoc = fd.childText.godotToDdoc;
106 	}
107 }
108 
109 void parsePropertyDoc(GodotProperty p, DOMEntity!string pDoc)
110 {
111 	p.ddoc = pDoc.childText.godotToDdoc;
112 }
113 
114 
115 // ddoc util functions
116 
117 string godotToDdoc(string input)
118 {
119 	import std.algorithm, std..string, std.regex;
120 	string ret = input;
121 	
122 	// handle [tags] like Godot's doc/tools/makerst.py
123 	
124 	ret = ret.replaceAll!((Captures!string s){
125 		auto split = s[1].findSplit(".");
126 		if(split[1].empty) return "$(D "~s[1].snakeToCamel.escapeD~")";
127 		else return "$(D "~split[0].escapeType~"."~split[2].snakeToCamel.escapeD~")";
128 	})(ctRegex!(`\[(?:method|member|signal|enum) (.+?)\]`));
129 	
130 	/// TODO: not D code, can't use --- blocks. Maybe the GDScript can be parsed and converted to D...
131 	ret = ret.replaceAll!((Captures!string s) => "\n"~s[1]~"\n")
132 		(ctRegex!(`\[codeblock\]([\s\S]*?)\[/codeblock\]`));
133 	
134 	ret = ret.replaceAll(ctRegex!`\[br\][ \t]*`, "$(BR)");
135 	
136 	ret = ret.replaceAll!((Captures!string s) => "$("~s[1].toUpper~" "~s[2]~")")
137 		(ctRegex!`\[([bui])\](.*?)\[/\1\]`);
138 	
139 	ret = ret.replaceAll!((Captures!string s) => "`"~s[1]~"`")(ctRegex!`\[code\](.*?)\[/code\]`);
140 	
141 	// fallback for [Class]
142 	ret = ret.replaceAll!((Captures!string s) => "$(D "~s[1].escapeType~")")(ctRegex!`\[(.+?)\]`);
143 	
144 	return ret;
145 }
146 
147 
148 // dxml util functions
149 
150 string attribute(DOMEntity!string e, string name)
151 {
152 	auto index = e.attributes.countUntil!"a.name == b"(name);
153 	if(index == -1) return null;
154 	return e.attributes[index].value;
155 }
156 
157 bool hasChild(DOMEntity!string e, string name)
158 {
159 	return e.children.countUntil!"a.name == b"(name) != -1;
160 }
161 
162 DOMEntity!string child(DOMEntity!string e, string name)
163 {
164 	auto index = e.children.countUntil!"a.name == b"(name);
165 	assert(index != -1);
166 	return e.children[index];
167 }
168 
169 string childText(DOMEntity!string e)
170 {
171 	auto index = e.children.countUntil!"a.type == b"(EntityType.text);
172 	if(index == -1) return null;
173 	return e.children[index].text.stripIndent;
174 }
175 
176