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 }