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