1
1
use std:: { collections:: HashMap , fs} ;
2
2
3
+ use regex:: { Captures , Regex } ;
3
4
use roxmltree:: Node ;
4
5
5
- #[ derive( Debug ) ]
6
6
pub struct GodotXmlDocs {
7
7
class_fn_desc : HashMap < ( String , String ) , String > ,
8
+ regexes : Regexes ,
8
9
}
9
10
10
11
impl GodotXmlDocs {
@@ -16,6 +17,7 @@ impl GodotXmlDocs {
16
17
17
18
let mut docs = GodotXmlDocs {
18
19
class_fn_desc : HashMap :: default ( ) ,
20
+ regexes : Regexes :: new ( ) ,
19
21
} ;
20
22
21
23
for entry in entries {
@@ -126,27 +128,157 @@ impl GodotXmlDocs {
126
128
127
129
self . class_fn_desc . insert (
128
130
( class. into ( ) , method. into ( ) ) ,
129
- Self :: reformat_as_rustdoc ( doc) ,
131
+ Self :: reformat_as_rustdoc ( & self . regexes , doc) ,
130
132
) ;
131
133
}
132
134
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
+
133
157
/// 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
+ )
138
172
} else {
139
- ""
173
+ godot_doc
140
174
} ;
141
175
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
145
179
. replace ( "[codeblock]" , "```gdscript" )
146
180
. replace ( "[/codeblock]" , "```" )
181
+ . replace ( "[code]" , "`" )
182
+ . replace ( "[/code]" , "`" )
147
183
. 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
+ } ) ;
149
217
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
+ }
151
283
}
152
284
}
0 commit comments