11use std:: { collections:: HashMap , fs} ;
22
3+ use regex:: { Captures , Regex } ;
34use roxmltree:: Node ;
45
5- #[ derive( Debug ) ]
66pub struct GodotXmlDocs {
77 class_fn_desc : HashMap < ( String , String ) , String > ,
8+ regexes : Regexes ,
89}
910
1011impl GodotXmlDocs {
@@ -16,6 +17,7 @@ impl GodotXmlDocs {
1617
1718 let mut docs = GodotXmlDocs {
1819 class_fn_desc : HashMap :: default ( ) ,
20+ regexes : Regexes :: new ( ) ,
1921 } ;
2022
2123 for entry in entries {
@@ -126,27 +128,157 @@ impl GodotXmlDocs {
126128
127129 self . class_fn_desc . insert (
128130 ( class. into ( ) , method. into ( ) ) ,
129- Self :: reformat_as_rustdoc ( doc) ,
131+ Self :: reformat_as_rustdoc ( & self . regexes , doc) ,
130132 ) ;
131133 }
132134
135+ // For types that godot-rust names differently than Godot
136+ fn translate_type ( godot_type : & str ) -> & str {
137+ // Note: there is some code duplication with Ty::from_src() in api.rs
138+ match godot_type {
139+ "String" => "GodotString" ,
140+ "Error" => "GodotError" ,
141+ "RID" => "Rid" ,
142+ "AABB" => "Aabb" ,
143+ "Array" => "VariantArray" ,
144+ "PoolByteArray" => "ByteArray" ,
145+ "PoolStringArray" => "StringArray" ,
146+ "PoolVector2Array" => "Vector2Array" ,
147+ "PoolVector3Array" => "Vector3Array" ,
148+ "PoolColorArray" => "ColorArray" ,
149+ "PoolIntArray" => "Int32Array" ,
150+ "PoolRealArray" => "Float32Array" ,
151+ "G6DOFJointAxisParam" => "G6dofJointAxisParam" ,
152+ "G6DOFJointAxisFlag" => "G6dofJointAxisFlag" ,
153+ _ => godot_type,
154+ }
155+ }
156+
133157 /// Takes the Godot documentation markup and transforms it to Rustdoc.
134- /// Very basic approach with limitations, but already helps readability quite a bit.
135- fn reformat_as_rustdoc ( godot_doc : String ) -> String {
136- let gdscript_note = if godot_doc. contains ( "[codeblock]" ) {
137- "_Sample code is GDScript unless otherwise noted._\n \n "
158+ /// Replaces BBCode syntax with Rustdoc/Markdown equivalents and implements working intra-doc links.
159+ fn reformat_as_rustdoc ( re : & Regexes , godot_doc : String ) -> String {
160+ // Note: there are still a few unsupported cases, such as:
161+ // * OK and ERR_CANT_CREATE (corresponding Result.Ok() and GodotError.ERR_CANT_CREATE)
162+ // * "indexed properties" which are not also exposed as getters, e.g. `gravity_point` in
163+ // https://docs.godotengine.org/en/stable/classes/class_area2d.html#properties.
164+ // This needs to be implemented first: https://github.com/godot-rust/godot-rust/issues/689
165+
166+ // Info for GDScript blocks
167+ let godot_doc = if godot_doc. contains ( "[codeblock]" ) {
168+ format ! (
169+ "_Sample code is GDScript unless otherwise noted._\n \n {}" ,
170+ godot_doc
171+ )
138172 } else {
139- ""
173+ godot_doc
140174 } ;
141175
142- let translated = godot_doc
143- . replace ( "[code]" , "`" )
144- . replace ( "[/code]" , "`" )
176+ // Before any regex replacement, do verbatim replacements
177+ // Note: maybe some can be expressed as regex, but if text- replace does the job reliably enough, it's even faster
178+ let godot_doc = godot_doc
145179 . replace ( "[codeblock]" , "```gdscript" )
146180 . replace ( "[/codeblock]" , "```" )
181+ . replace ( "[code]" , "`" )
182+ . replace ( "[/code]" , "`" )
147183 . replace ( "[b]" , "**" )
148- . replace ( "[/b]" , "**" ) ;
184+ . replace ( "[/b]" , "**" )
185+ . replace ( "[i]" , "_" )
186+ . replace ( "[/i]" , "_" ) ;
187+
188+ // URLs
189+ let godot_doc = re. url . replace_all ( & godot_doc, |c : & Captures | {
190+ let url = & c[ 1 ] ;
191+ let text = & c[ 2 ] ;
192+
193+ if text. is_empty ( ) {
194+ format ! ( "<{url}>" , url = url)
195+ } else {
196+ format ! ( "[{text}]({url})" , text = text, url = url)
197+ }
198+ } ) ;
199+
200+ // [Type::member] style
201+ let godot_doc = re. class_member . replace_all ( & godot_doc, |c : & Captures | {
202+ let godot_ty = & c[ 2 ] ;
203+ let rust_ty = Self :: translate_type ( godot_ty) ;
204+
205+ format ! (
206+ "[`{godot_ty}.{member}`][{rust_ty}::{member}]" ,
207+ godot_ty = godot_ty,
208+ rust_ty = rust_ty,
209+ member = & c[ 3 ]
210+ )
211+ } ) ;
212+
213+ // [member] style
214+ let godot_doc = re. self_member . replace_all ( & godot_doc, |c : & Captures | {
215+ format ! ( "[`{member}`][Self::{member}]" , member = & c[ 2 ] )
216+ } ) ;
149217
150- format ! ( "{}{}" , gdscript_note, translated)
218+ // `member` style (no link)
219+ let godot_doc = re. no_link . replace_all ( & godot_doc, |c : & Captures | {
220+ format ! ( "`{member}`" , member = & c[ 1 ] )
221+ } ) ;
222+
223+ // [Type] style
224+ let godot_doc = re. class . replace_all ( & godot_doc, |c : & Captures | {
225+ let godot_ty = & c[ 2 ] ;
226+ let rust_ty = Self :: translate_type ( godot_ty) ;
227+
228+ format ! (
229+ "[`{godot_ty}`][{rust_ty}]" ,
230+ godot_ty = godot_ty,
231+ rust_ty = rust_ty
232+ )
233+ } ) ;
234+
235+ godot_doc. to_string ( )
236+ }
237+ }
238+
239+ // Holds several compiled regexes to reuse across classes
240+ // could also use 'lazy_regex' crate, but standard 'regex' has better IDE support and works well enough
241+ struct Regexes {
242+ url : Regex ,
243+ no_link : Regex ,
244+ class : Regex ,
245+ self_member : Regex ,
246+ class_member : Regex ,
247+ }
248+
249+ impl Regexes {
250+ fn new ( ) -> Self {
251+ Self {
252+ // Covers:
253+ // * [url=U]text[/url]
254+ // * [url=U][/url]
255+ url : Regex :: new ( "\\ [url=(.+?)](.*?)\\ [/url]" ) . unwrap ( ) ,
256+
257+ // Covers:
258+ // * [code]C[/code]
259+ // * [signal C]
260+ // Must run before others, as [code] will itself match the link syntax
261+ no_link : Regex :: new ( "\\ [signal ([A-Za-z0-9_]+?)]" ) . unwrap ( ) ,
262+
263+ // Covers:
264+ // * [C]
265+ // * [enum C]
266+ class : Regex :: new ( "\\ [(enum )?([A-Za-z0-9_]+?)]" ) . unwrap ( ) ,
267+
268+ // Covers:
269+ // * [member M]
270+ // * [method M]
271+ // * [constant M]
272+ self_member : Regex :: new ( "\\ [(member|method|constant) ([A-Za-z0-9_]+?)]" ) . unwrap ( ) ,
273+
274+ // Covers:
275+ // * [member C.M]
276+ // * [method C.M]
277+ // * [constant C.M]
278+ class_member : Regex :: new (
279+ "\\ [(member|method|constant) ([A-Za-z0-9_]+?)\\ .([A-Za-z0-9_]+?)]" ,
280+ )
281+ . unwrap ( ) ,
282+ }
151283 }
152284}
0 commit comments