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