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