1 | /**************************************************************************/ |
2 | /* doc_tools.cpp */ |
3 | /**************************************************************************/ |
4 | /* This file is part of: */ |
5 | /* GODOT ENGINE */ |
6 | /* https://godotengine.org */ |
7 | /**************************************************************************/ |
8 | /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ |
9 | /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ |
10 | /* */ |
11 | /* Permission is hereby granted, free of charge, to any person obtaining */ |
12 | /* a copy of this software and associated documentation files (the */ |
13 | /* "Software"), to deal in the Software without restriction, including */ |
14 | /* without limitation the rights to use, copy, modify, merge, publish, */ |
15 | /* distribute, sublicense, and/or sell copies of the Software, and to */ |
16 | /* permit persons to whom the Software is furnished to do so, subject to */ |
17 | /* the following conditions: */ |
18 | /* */ |
19 | /* The above copyright notice and this permission notice shall be */ |
20 | /* included in all copies or substantial portions of the Software. */ |
21 | /* */ |
22 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ |
23 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ |
24 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ |
25 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ |
26 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ |
27 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ |
28 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ |
29 | /**************************************************************************/ |
30 | |
31 | #include "doc_tools.h" |
32 | |
33 | #include "core/config/engine.h" |
34 | #include "core/config/project_settings.h" |
35 | #include "core/core_constants.h" |
36 | #include "core/io/compression.h" |
37 | #include "core/io/dir_access.h" |
38 | #include "core/io/marshalls.h" |
39 | #include "core/io/resource_importer.h" |
40 | #include "core/object/script_language.h" |
41 | #include "core/string/translation.h" |
42 | #include "editor/editor_settings.h" |
43 | #include "editor/export/editor_export.h" |
44 | #include "scene/resources/theme.h" |
45 | #include "scene/theme/theme_db.h" |
46 | |
47 | // Used for a hack preserving Mono properties on non-Mono builds. |
48 | #include "modules/modules_enabled.gen.h" // For mono. |
49 | |
50 | static String _get_indent(const String &p_text) { |
51 | String indent; |
52 | bool has_text = false; |
53 | int line_start = 0; |
54 | |
55 | for (int i = 0; i < p_text.length(); i++) { |
56 | const char32_t c = p_text[i]; |
57 | if (c == '\n') { |
58 | line_start = i + 1; |
59 | } else if (c > 32) { |
60 | has_text = true; |
61 | indent = p_text.substr(line_start, i - line_start); |
62 | break; // Indentation of the first line that has text. |
63 | } |
64 | } |
65 | if (!has_text) { |
66 | return p_text; |
67 | } |
68 | return indent; |
69 | } |
70 | |
71 | static String _translate_doc_string(const String &p_text) { |
72 | const String indent = _get_indent(p_text); |
73 | const String message = p_text.dedent().strip_edges(); |
74 | const String translated = TranslationServer::get_singleton()->doc_translate(message, "" ); |
75 | // No need to restore stripped edges because they'll be stripped again later. |
76 | return translated.indent(indent); |
77 | } |
78 | |
79 | void DocTools::merge_from(const DocTools &p_data) { |
80 | for (KeyValue<String, DocData::ClassDoc> &E : class_list) { |
81 | DocData::ClassDoc &c = E.value; |
82 | |
83 | if (!p_data.class_list.has(c.name)) { |
84 | continue; |
85 | } |
86 | |
87 | const DocData::ClassDoc &cf = p_data.class_list[c.name]; |
88 | |
89 | c.is_deprecated = cf.is_deprecated; |
90 | c.is_experimental = cf.is_experimental; |
91 | |
92 | c.description = cf.description; |
93 | c.brief_description = cf.brief_description; |
94 | c.tutorials = cf.tutorials; |
95 | |
96 | for (int i = 0; i < c.constructors.size(); i++) { |
97 | DocData::MethodDoc &m = c.constructors.write[i]; |
98 | |
99 | for (int j = 0; j < cf.constructors.size(); j++) { |
100 | if (cf.constructors[j].name != m.name) { |
101 | continue; |
102 | } |
103 | |
104 | { |
105 | // Since constructors can repeat, we need to check the type of |
106 | // the arguments so we make sure they are different. |
107 | if (cf.constructors[j].arguments.size() != m.arguments.size()) { |
108 | continue; |
109 | } |
110 | int arg_count = cf.constructors[j].arguments.size(); |
111 | Vector<bool> arg_used; |
112 | arg_used.resize(arg_count); |
113 | for (int l = 0; l < arg_count; ++l) { |
114 | arg_used.write[l] = false; |
115 | } |
116 | // also there is no guarantee that argument ordering will match, so we |
117 | // have to check one by one so we make sure we have an exact match |
118 | for (int k = 0; k < arg_count; ++k) { |
119 | for (int l = 0; l < arg_count; ++l) { |
120 | if (cf.constructors[j].arguments[k].type == m.arguments[l].type && !arg_used[l]) { |
121 | arg_used.write[l] = true; |
122 | break; |
123 | } |
124 | } |
125 | } |
126 | bool not_the_same = false; |
127 | for (int l = 0; l < arg_count; ++l) { |
128 | if (!arg_used[l]) { // at least one of the arguments was different |
129 | not_the_same = true; |
130 | } |
131 | } |
132 | if (not_the_same) { |
133 | continue; |
134 | } |
135 | } |
136 | |
137 | const DocData::MethodDoc &mf = cf.constructors[j]; |
138 | |
139 | m.description = mf.description; |
140 | m.is_deprecated = mf.is_deprecated; |
141 | m.is_experimental = mf.is_experimental; |
142 | break; |
143 | } |
144 | } |
145 | |
146 | for (int i = 0; i < c.methods.size(); i++) { |
147 | DocData::MethodDoc &m = c.methods.write[i]; |
148 | |
149 | for (int j = 0; j < cf.methods.size(); j++) { |
150 | if (cf.methods[j].name != m.name) { |
151 | continue; |
152 | } |
153 | |
154 | const DocData::MethodDoc &mf = cf.methods[j]; |
155 | |
156 | m.description = mf.description; |
157 | m.is_deprecated = mf.is_deprecated; |
158 | m.is_experimental = mf.is_experimental; |
159 | break; |
160 | } |
161 | } |
162 | |
163 | for (int i = 0; i < c.signals.size(); i++) { |
164 | DocData::MethodDoc &m = c.signals.write[i]; |
165 | |
166 | for (int j = 0; j < cf.signals.size(); j++) { |
167 | if (cf.signals[j].name != m.name) { |
168 | continue; |
169 | } |
170 | const DocData::MethodDoc &mf = cf.signals[j]; |
171 | |
172 | m.description = mf.description; |
173 | m.is_deprecated = mf.is_deprecated; |
174 | m.is_experimental = mf.is_experimental; |
175 | break; |
176 | } |
177 | } |
178 | |
179 | for (int i = 0; i < c.constants.size(); i++) { |
180 | DocData::ConstantDoc &m = c.constants.write[i]; |
181 | |
182 | for (int j = 0; j < cf.constants.size(); j++) { |
183 | if (cf.constants[j].name != m.name) { |
184 | continue; |
185 | } |
186 | const DocData::ConstantDoc &mf = cf.constants[j]; |
187 | |
188 | m.description = mf.description; |
189 | m.is_deprecated = mf.is_deprecated; |
190 | m.is_experimental = mf.is_experimental; |
191 | break; |
192 | } |
193 | } |
194 | |
195 | for (int i = 0; i < c.annotations.size(); i++) { |
196 | DocData::MethodDoc &m = c.annotations.write[i]; |
197 | |
198 | for (int j = 0; j < cf.annotations.size(); j++) { |
199 | if (cf.annotations[j].name != m.name) { |
200 | continue; |
201 | } |
202 | const DocData::MethodDoc &mf = cf.annotations[j]; |
203 | |
204 | m.description = mf.description; |
205 | m.is_deprecated = mf.is_deprecated; |
206 | m.is_experimental = mf.is_experimental; |
207 | break; |
208 | } |
209 | } |
210 | |
211 | for (int i = 0; i < c.properties.size(); i++) { |
212 | DocData::PropertyDoc &p = c.properties.write[i]; |
213 | |
214 | for (int j = 0; j < cf.properties.size(); j++) { |
215 | if (cf.properties[j].name != p.name) { |
216 | continue; |
217 | } |
218 | const DocData::PropertyDoc &pf = cf.properties[j]; |
219 | |
220 | p.description = pf.description; |
221 | p.is_deprecated = pf.is_deprecated; |
222 | p.is_experimental = pf.is_experimental; |
223 | break; |
224 | } |
225 | } |
226 | |
227 | for (int i = 0; i < c.theme_properties.size(); i++) { |
228 | DocData::ThemeItemDoc &ti = c.theme_properties.write[i]; |
229 | |
230 | for (int j = 0; j < cf.theme_properties.size(); j++) { |
231 | if (cf.theme_properties[j].name != ti.name || cf.theme_properties[j].data_type != ti.data_type) { |
232 | continue; |
233 | } |
234 | const DocData::ThemeItemDoc &pf = cf.theme_properties[j]; |
235 | |
236 | ti.description = pf.description; |
237 | break; |
238 | } |
239 | } |
240 | |
241 | for (int i = 0; i < c.operators.size(); i++) { |
242 | DocData::MethodDoc &m = c.operators.write[i]; |
243 | |
244 | for (int j = 0; j < cf.operators.size(); j++) { |
245 | if (cf.operators[j].name != m.name) { |
246 | continue; |
247 | } |
248 | |
249 | { |
250 | // Since operators can repeat, we need to check the type of |
251 | // the arguments so we make sure they are different. |
252 | if (cf.operators[j].arguments.size() != m.arguments.size()) { |
253 | continue; |
254 | } |
255 | int arg_count = cf.operators[j].arguments.size(); |
256 | Vector<bool> arg_used; |
257 | arg_used.resize(arg_count); |
258 | for (int l = 0; l < arg_count; ++l) { |
259 | arg_used.write[l] = false; |
260 | } |
261 | // also there is no guarantee that argument ordering will match, so we |
262 | // have to check one by one so we make sure we have an exact match |
263 | for (int k = 0; k < arg_count; ++k) { |
264 | for (int l = 0; l < arg_count; ++l) { |
265 | if (cf.operators[j].arguments[k].type == m.arguments[l].type && !arg_used[l]) { |
266 | arg_used.write[l] = true; |
267 | break; |
268 | } |
269 | } |
270 | } |
271 | bool not_the_same = false; |
272 | for (int l = 0; l < arg_count; ++l) { |
273 | if (!arg_used[l]) { // at least one of the arguments was different |
274 | not_the_same = true; |
275 | } |
276 | } |
277 | if (not_the_same) { |
278 | continue; |
279 | } |
280 | } |
281 | |
282 | const DocData::MethodDoc &mf = cf.operators[j]; |
283 | |
284 | m.description = mf.description; |
285 | m.is_deprecated = mf.is_deprecated; |
286 | m.is_experimental = mf.is_experimental; |
287 | break; |
288 | } |
289 | } |
290 | |
291 | #ifndef MODULE_MONO_ENABLED |
292 | // The Mono module defines some properties that we want to keep when |
293 | // re-generating docs with a non-Mono build, to prevent pointless diffs |
294 | // (and loss of descriptions) depending on the config of the doc writer. |
295 | // We use a horrible hack to force keeping the relevant properties, |
296 | // hardcoded below. At least it's an ad hoc hack... ¯\_(ツ)_/¯ |
297 | // Don't show this to your kids. |
298 | if (c.name == "@GlobalScope" ) { |
299 | // Retrieve GodotSharp singleton. |
300 | for (int j = 0; j < cf.properties.size(); j++) { |
301 | if (cf.properties[j].name == "GodotSharp" ) { |
302 | c.properties.push_back(cf.properties[j]); |
303 | } |
304 | } |
305 | } |
306 | #endif |
307 | } |
308 | } |
309 | |
310 | void DocTools::remove_from(const DocTools &p_data) { |
311 | for (const KeyValue<String, DocData::ClassDoc> &E : p_data.class_list) { |
312 | if (class_list.has(E.key)) { |
313 | class_list.erase(E.key); |
314 | } |
315 | } |
316 | } |
317 | |
318 | void DocTools::add_doc(const DocData::ClassDoc &p_class_doc) { |
319 | ERR_FAIL_COND(p_class_doc.name.is_empty()); |
320 | class_list[p_class_doc.name] = p_class_doc; |
321 | } |
322 | |
323 | void DocTools::remove_doc(const String &p_class_name) { |
324 | ERR_FAIL_COND(p_class_name.is_empty() || !class_list.has(p_class_name)); |
325 | class_list.erase(p_class_name); |
326 | } |
327 | |
328 | bool DocTools::has_doc(const String &p_class_name) { |
329 | if (p_class_name.is_empty()) { |
330 | return false; |
331 | } |
332 | return class_list.has(p_class_name); |
333 | } |
334 | |
335 | static Variant get_documentation_default_value(const StringName &p_class_name, const StringName &p_property_name, bool &r_default_value_valid) { |
336 | Variant default_value = Variant(); |
337 | r_default_value_valid = false; |
338 | |
339 | if (ClassDB::can_instantiate(p_class_name) && !ClassDB::is_virtual(p_class_name)) { // Keep this condition in sync with ClassDB::class_get_default_property_value. |
340 | default_value = ClassDB::class_get_default_property_value(p_class_name, p_property_name, &r_default_value_valid); |
341 | } else { |
342 | // Cannot get default value of classes that can't be instantiated |
343 | List<StringName> inheriting_classes; |
344 | ClassDB::get_direct_inheriters_from_class(p_class_name, &inheriting_classes); |
345 | for (List<StringName>::Element *E2 = inheriting_classes.front(); E2; E2 = E2->next()) { |
346 | if (ClassDB::can_instantiate(E2->get())) { |
347 | default_value = ClassDB::class_get_default_property_value(E2->get(), p_property_name, &r_default_value_valid); |
348 | if (r_default_value_valid) { |
349 | break; |
350 | } |
351 | } |
352 | } |
353 | } |
354 | |
355 | return default_value; |
356 | } |
357 | |
358 | void DocTools::generate(bool p_basic_types) { |
359 | // Add ClassDB-exposed classes. |
360 | { |
361 | List<StringName> classes; |
362 | ClassDB::get_class_list(&classes); |
363 | classes.sort_custom<StringName::AlphCompare>(); |
364 | // Move ProjectSettings, so that other classes can register properties there. |
365 | classes.move_to_back(classes.find("ProjectSettings" )); |
366 | |
367 | bool skip_setter_getter_methods = true; |
368 | |
369 | // Populate documentation data for each exposed class. |
370 | while (classes.size()) { |
371 | String name = classes.front()->get(); |
372 | if (!ClassDB::is_class_exposed(name)) { |
373 | print_verbose(vformat("Class '%s' is not exposed, skipping." , name)); |
374 | classes.pop_front(); |
375 | continue; |
376 | } |
377 | |
378 | String cname = name; |
379 | // Property setters and getters do not get exposed as individual methods. |
380 | HashSet<StringName> setters_getters; |
381 | |
382 | class_list[cname] = DocData::ClassDoc(); |
383 | DocData::ClassDoc &c = class_list[cname]; |
384 | c.name = cname; |
385 | c.inherits = ClassDB::get_parent_class(name); |
386 | |
387 | List<PropertyInfo> properties; |
388 | List<PropertyInfo> own_properties; |
389 | |
390 | // Special cases for editor/project settings, and ResourceImporter classes, |
391 | // we have to rely on Object's property list to get settings and import options. |
392 | // Otherwise we just use ClassDB's property list (pure registered properties). |
393 | |
394 | bool properties_from_instance = true; // To skip `script`, etc. |
395 | bool import_option = false; // Special case for default value. |
396 | HashMap<StringName, Variant> import_options_default; |
397 | if (name == "EditorSettings" ) { |
398 | // We don't create the full blown EditorSettings (+ config file) with `create()`, |
399 | // instead we just make a local instance to get default values. |
400 | Ref<EditorSettings> edset = memnew(EditorSettings); |
401 | edset->get_property_list(&properties); |
402 | own_properties = properties; |
403 | } else if (name == "ProjectSettings" ) { |
404 | ProjectSettings::get_singleton()->get_property_list(&properties); |
405 | own_properties = properties; |
406 | } else if (ClassDB::is_parent_class(name, "ResourceImporter" ) && name != "EditorImportPlugin" && ClassDB::can_instantiate(name)) { |
407 | import_option = true; |
408 | ResourceImporter *resimp = Object::cast_to<ResourceImporter>(ClassDB::instantiate(name)); |
409 | List<ResourceImporter::ImportOption> options; |
410 | resimp->get_import_options("" , &options); |
411 | for (int i = 0; i < options.size(); i++) { |
412 | const PropertyInfo &prop = options[i].option; |
413 | properties.push_back(prop); |
414 | import_options_default[prop.name] = options[i].default_value; |
415 | } |
416 | own_properties = properties; |
417 | memdelete(resimp); |
418 | } else if (name.begins_with("EditorExportPlatform" ) && ClassDB::can_instantiate(name)) { |
419 | properties_from_instance = false; |
420 | Ref<EditorExportPlatform> platform = Object::cast_to<EditorExportPlatform>(ClassDB::instantiate(name)); |
421 | if (platform.is_valid()) { |
422 | List<EditorExportPlatform::ExportOption> options; |
423 | platform->get_export_options(&options); |
424 | for (const EditorExportPlatform::ExportOption &E : options) { |
425 | properties.push_back(E.option); |
426 | } |
427 | own_properties = properties; |
428 | } |
429 | } else { |
430 | properties_from_instance = false; |
431 | ClassDB::get_property_list(name, &properties); |
432 | ClassDB::get_property_list(name, &own_properties, true); |
433 | } |
434 | |
435 | // Sort is still needed here to handle inherited properties, even though it is done below, do not remove. |
436 | properties.sort(); |
437 | own_properties.sort(); |
438 | |
439 | List<PropertyInfo>::Element *EO = own_properties.front(); |
440 | for (const PropertyInfo &E : properties) { |
441 | bool inherited = true; |
442 | if (EO && EO->get() == E) { |
443 | inherited = false; |
444 | EO = EO->next(); |
445 | } |
446 | |
447 | if (properties_from_instance) { |
448 | if (E.name == "resource_local_to_scene" || E.name == "resource_name" || E.name == "resource_path" || E.name == "script" ) { |
449 | // Don't include spurious properties from Object property list. |
450 | continue; |
451 | } |
452 | } |
453 | |
454 | if (E.usage & PROPERTY_USAGE_GROUP || E.usage & PROPERTY_USAGE_SUBGROUP || E.usage & PROPERTY_USAGE_CATEGORY || E.usage & PROPERTY_USAGE_INTERNAL || (E.type == Variant::NIL && E.usage & PROPERTY_USAGE_ARRAY)) { |
455 | continue; |
456 | } |
457 | |
458 | DocData::PropertyDoc prop; |
459 | prop.name = E.name; |
460 | prop.overridden = inherited; |
461 | |
462 | if (inherited) { |
463 | String parent = ClassDB::get_parent_class(c.name); |
464 | while (!ClassDB::has_property(parent, prop.name, true)) { |
465 | parent = ClassDB::get_parent_class(parent); |
466 | } |
467 | prop.overrides = parent; |
468 | } |
469 | |
470 | bool default_value_valid = false; |
471 | Variant default_value; |
472 | |
473 | if (name == "ProjectSettings" ) { |
474 | // Special case for project settings, so that settings are not taken from the current project's settings |
475 | if (!ProjectSettings::get_singleton()->is_builtin_setting(E.name)) { |
476 | continue; |
477 | } |
478 | if (E.usage & PROPERTY_USAGE_EDITOR) { |
479 | if (!ProjectSettings::get_singleton()->get_ignore_value_in_docs(E.name)) { |
480 | default_value = ProjectSettings::get_singleton()->property_get_revert(E.name); |
481 | default_value_valid = true; |
482 | } |
483 | } |
484 | } else if (name == "EditorSettings" ) { |
485 | // Special case for editor settings, to prevent hardware or OS specific settings to affect the result. |
486 | } else if (import_option) { |
487 | default_value = import_options_default[E.name]; |
488 | default_value_valid = true; |
489 | } else { |
490 | default_value = get_documentation_default_value(name, E.name, default_value_valid); |
491 | if (inherited) { |
492 | bool base_default_value_valid = false; |
493 | Variant base_default_value = get_documentation_default_value(ClassDB::get_parent_class(name), E.name, base_default_value_valid); |
494 | if (!default_value_valid || !base_default_value_valid || default_value == base_default_value) { |
495 | continue; |
496 | } |
497 | } |
498 | } |
499 | |
500 | if (default_value_valid && default_value.get_type() != Variant::OBJECT) { |
501 | prop.default_value = DocData::get_default_value_string(default_value); |
502 | } |
503 | |
504 | StringName setter = ClassDB::get_property_setter(name, E.name); |
505 | StringName getter = ClassDB::get_property_getter(name, E.name); |
506 | |
507 | prop.setter = setter; |
508 | prop.getter = getter; |
509 | |
510 | bool found_type = false; |
511 | if (getter != StringName()) { |
512 | MethodBind *mb = ClassDB::get_method(name, getter); |
513 | if (mb) { |
514 | PropertyInfo retinfo = mb->get_return_info(); |
515 | |
516 | found_type = true; |
517 | if (retinfo.type == Variant::INT && retinfo.usage & (PROPERTY_USAGE_CLASS_IS_ENUM | PROPERTY_USAGE_CLASS_IS_BITFIELD)) { |
518 | prop.enumeration = retinfo.class_name; |
519 | prop.is_bitfield = retinfo.usage & PROPERTY_USAGE_CLASS_IS_BITFIELD; |
520 | prop.type = "int" ; |
521 | } else if (retinfo.class_name != StringName()) { |
522 | prop.type = retinfo.class_name; |
523 | } else if (retinfo.type == Variant::ARRAY && retinfo.hint == PROPERTY_HINT_ARRAY_TYPE) { |
524 | prop.type = retinfo.hint_string + "[]" ; |
525 | } else if (retinfo.hint == PROPERTY_HINT_RESOURCE_TYPE) { |
526 | prop.type = retinfo.hint_string; |
527 | } else if (retinfo.type == Variant::NIL && retinfo.usage & PROPERTY_USAGE_NIL_IS_VARIANT) { |
528 | prop.type = "Variant" ; |
529 | } else if (retinfo.type == Variant::NIL) { |
530 | prop.type = "void" ; |
531 | } else { |
532 | prop.type = Variant::get_type_name(retinfo.type); |
533 | } |
534 | } |
535 | |
536 | setters_getters.insert(getter); |
537 | } |
538 | |
539 | if (setter != StringName()) { |
540 | setters_getters.insert(setter); |
541 | } |
542 | |
543 | if (!found_type) { |
544 | if (E.type == Variant::OBJECT && E.hint == PROPERTY_HINT_RESOURCE_TYPE) { |
545 | prop.type = E.hint_string; |
546 | } else { |
547 | prop.type = Variant::get_type_name(E.type); |
548 | } |
549 | } |
550 | |
551 | c.properties.push_back(prop); |
552 | } |
553 | |
554 | c.properties.sort(); |
555 | |
556 | List<MethodInfo> method_list; |
557 | ClassDB::get_method_list(name, &method_list, true); |
558 | |
559 | for (const MethodInfo &E : method_list) { |
560 | if (E.name.is_empty() || (E.name[0] == '_' && !(E.flags & METHOD_FLAG_VIRTUAL))) { |
561 | continue; //hidden, don't count |
562 | } |
563 | |
564 | if (skip_setter_getter_methods && setters_getters.has(E.name)) { |
565 | // Don't skip parametric setters and getters, i.e. method which require |
566 | // one or more parameters to define what property should be set or retrieved. |
567 | // E.g. CPUParticles3D::set_param(Parameter param, float value). |
568 | if (E.arguments.size() == 0 /* getter */ || (E.arguments.size() == 1 && E.return_val.type == Variant::NIL /* setter */)) { |
569 | continue; |
570 | } |
571 | } |
572 | |
573 | DocData::MethodDoc method; |
574 | DocData::method_doc_from_methodinfo(method, E, "" ); |
575 | |
576 | Vector<Error> errs = ClassDB::get_method_error_return_values(name, E.name); |
577 | if (errs.size()) { |
578 | if (!errs.has(OK)) { |
579 | errs.insert(0, OK); |
580 | } |
581 | for (int i = 0; i < errs.size(); i++) { |
582 | if (!method.errors_returned.has(errs[i])) { |
583 | method.errors_returned.push_back(errs[i]); |
584 | } |
585 | } |
586 | } |
587 | |
588 | c.methods.push_back(method); |
589 | } |
590 | |
591 | c.methods.sort(); |
592 | |
593 | List<MethodInfo> signal_list; |
594 | ClassDB::get_signal_list(name, &signal_list, true); |
595 | |
596 | if (signal_list.size()) { |
597 | for (List<MethodInfo>::Element *EV = signal_list.front(); EV; EV = EV->next()) { |
598 | DocData::MethodDoc signal; |
599 | signal.name = EV->get().name; |
600 | for (int i = 0; i < EV->get().arguments.size(); i++) { |
601 | const PropertyInfo &arginfo = EV->get().arguments[i]; |
602 | DocData::ArgumentDoc argument; |
603 | DocData::argument_doc_from_arginfo(argument, arginfo); |
604 | |
605 | signal.arguments.push_back(argument); |
606 | } |
607 | |
608 | c.signals.push_back(signal); |
609 | } |
610 | } |
611 | |
612 | List<String> constant_list; |
613 | ClassDB::get_integer_constant_list(name, &constant_list, true); |
614 | |
615 | for (const String &E : constant_list) { |
616 | DocData::ConstantDoc constant; |
617 | constant.name = E; |
618 | constant.value = itos(ClassDB::get_integer_constant(name, E)); |
619 | constant.is_value_valid = true; |
620 | constant.enumeration = ClassDB::get_integer_constant_enum(name, E); |
621 | constant.is_bitfield = ClassDB::is_enum_bitfield(name, constant.enumeration); |
622 | c.constants.push_back(constant); |
623 | } |
624 | |
625 | // Theme items. |
626 | { |
627 | List<StringName> l; |
628 | |
629 | ThemeDB::get_singleton()->get_default_theme()->get_color_list(cname, &l); |
630 | for (const StringName &E : l) { |
631 | DocData::ThemeItemDoc tid; |
632 | tid.name = E; |
633 | tid.type = "Color" ; |
634 | tid.data_type = "color" ; |
635 | tid.default_value = DocData::get_default_value_string(ThemeDB::get_singleton()->get_default_theme()->get_color(E, cname)); |
636 | c.theme_properties.push_back(tid); |
637 | } |
638 | |
639 | l.clear(); |
640 | ThemeDB::get_singleton()->get_default_theme()->get_constant_list(cname, &l); |
641 | for (const StringName &E : l) { |
642 | DocData::ThemeItemDoc tid; |
643 | tid.name = E; |
644 | tid.type = "int" ; |
645 | tid.data_type = "constant" ; |
646 | tid.default_value = itos(ThemeDB::get_singleton()->get_default_theme()->get_constant(E, cname)); |
647 | c.theme_properties.push_back(tid); |
648 | } |
649 | |
650 | l.clear(); |
651 | ThemeDB::get_singleton()->get_default_theme()->get_font_list(cname, &l); |
652 | for (const StringName &E : l) { |
653 | DocData::ThemeItemDoc tid; |
654 | tid.name = E; |
655 | tid.type = "Font" ; |
656 | tid.data_type = "font" ; |
657 | c.theme_properties.push_back(tid); |
658 | } |
659 | |
660 | l.clear(); |
661 | ThemeDB::get_singleton()->get_default_theme()->get_font_size_list(cname, &l); |
662 | for (const StringName &E : l) { |
663 | DocData::ThemeItemDoc tid; |
664 | tid.name = E; |
665 | tid.type = "int" ; |
666 | tid.data_type = "font_size" ; |
667 | c.theme_properties.push_back(tid); |
668 | } |
669 | |
670 | l.clear(); |
671 | ThemeDB::get_singleton()->get_default_theme()->get_icon_list(cname, &l); |
672 | for (const StringName &E : l) { |
673 | DocData::ThemeItemDoc tid; |
674 | tid.name = E; |
675 | tid.type = "Texture2D" ; |
676 | tid.data_type = "icon" ; |
677 | c.theme_properties.push_back(tid); |
678 | } |
679 | |
680 | l.clear(); |
681 | ThemeDB::get_singleton()->get_default_theme()->get_stylebox_list(cname, &l); |
682 | for (const StringName &E : l) { |
683 | DocData::ThemeItemDoc tid; |
684 | tid.name = E; |
685 | tid.type = "StyleBox" ; |
686 | tid.data_type = "style" ; |
687 | c.theme_properties.push_back(tid); |
688 | } |
689 | |
690 | c.theme_properties.sort(); |
691 | } |
692 | |
693 | classes.pop_front(); |
694 | } |
695 | } |
696 | |
697 | // Add a dummy Variant entry. |
698 | { |
699 | // This allows us to document the concept of Variant even though |
700 | // it's not a ClassDB-exposed class. |
701 | class_list["Variant" ] = DocData::ClassDoc(); |
702 | class_list["Variant" ].name = "Variant" ; |
703 | } |
704 | |
705 | // If we don't want to populate basic types, break here. |
706 | if (!p_basic_types) { |
707 | return; |
708 | } |
709 | |
710 | // Add Variant data types. |
711 | for (int i = 0; i < Variant::VARIANT_MAX; i++) { |
712 | if (i == Variant::NIL) { |
713 | continue; // Not exposed outside of 'null', should not be in class list. |
714 | } |
715 | if (i == Variant::OBJECT) { |
716 | continue; // Use the core type instead. |
717 | } |
718 | |
719 | String cname = Variant::get_type_name(Variant::Type(i)); |
720 | |
721 | class_list[cname] = DocData::ClassDoc(); |
722 | DocData::ClassDoc &c = class_list[cname]; |
723 | c.name = cname; |
724 | |
725 | Callable::CallError cerror; |
726 | Variant v; |
727 | Variant::construct(Variant::Type(i), v, nullptr, 0, cerror); |
728 | |
729 | List<MethodInfo> method_list; |
730 | v.get_method_list(&method_list); |
731 | Variant::get_constructor_list(Variant::Type(i), &method_list); |
732 | |
733 | for (int j = 0; j < Variant::OP_AND; j++) { // Showing above 'and' is pretty confusing and there are a lot of variations. |
734 | for (int k = 0; k < Variant::VARIANT_MAX; k++) { |
735 | // Prevent generating for comparison with null. |
736 | if (Variant::Type(k) == Variant::NIL && (Variant::Operator(j) == Variant::OP_EQUAL || Variant::Operator(j) == Variant::OP_NOT_EQUAL)) { |
737 | continue; |
738 | } |
739 | |
740 | Variant::Type rt = Variant::get_operator_return_type(Variant::Operator(j), Variant::Type(i), Variant::Type(k)); |
741 | if (rt != Variant::NIL) { // Has operator. |
742 | // Skip String % operator as it's registered separately for each Variant arg type, |
743 | // we'll add it manually below. |
744 | if ((i == Variant::STRING || i == Variant::STRING_NAME) && Variant::Operator(j) == Variant::OP_MODULE) { |
745 | continue; |
746 | } |
747 | MethodInfo mi; |
748 | mi.name = "operator " + Variant::get_operator_name(Variant::Operator(j)); |
749 | mi.return_val.type = rt; |
750 | if (k != Variant::NIL) { |
751 | PropertyInfo arg; |
752 | arg.name = "right" ; |
753 | arg.type = Variant::Type(k); |
754 | mi.arguments.push_back(arg); |
755 | } |
756 | method_list.push_back(mi); |
757 | } |
758 | } |
759 | } |
760 | |
761 | if (i == Variant::STRING || i == Variant::STRING_NAME) { |
762 | // We skipped % operator above, and we register it manually once for Variant arg type here. |
763 | MethodInfo mi; |
764 | mi.name = "operator %" ; |
765 | mi.return_val.type = Variant::STRING; |
766 | |
767 | PropertyInfo arg; |
768 | arg.name = "right" ; |
769 | arg.type = Variant::NIL; |
770 | arg.usage = PROPERTY_USAGE_NIL_IS_VARIANT; |
771 | mi.arguments.push_back(arg); |
772 | |
773 | method_list.push_back(mi); |
774 | } |
775 | |
776 | if (Variant::is_keyed(Variant::Type(i))) { |
777 | MethodInfo mi; |
778 | mi.name = "operator []" ; |
779 | mi.return_val.type = Variant::NIL; |
780 | mi.return_val.usage = PROPERTY_USAGE_NIL_IS_VARIANT; |
781 | |
782 | PropertyInfo arg; |
783 | arg.name = "key" ; |
784 | arg.type = Variant::NIL; |
785 | arg.usage = PROPERTY_USAGE_NIL_IS_VARIANT; |
786 | mi.arguments.push_back(arg); |
787 | |
788 | method_list.push_back(mi); |
789 | } else if (Variant::has_indexing(Variant::Type(i))) { |
790 | MethodInfo mi; |
791 | mi.name = "operator []" ; |
792 | mi.return_val.type = Variant::get_indexed_element_type(Variant::Type(i)); |
793 | mi.return_val.usage = Variant::get_indexed_element_usage(Variant::Type(i)); |
794 | PropertyInfo arg; |
795 | arg.name = "index" ; |
796 | arg.type = Variant::INT; |
797 | mi.arguments.push_back(arg); |
798 | |
799 | method_list.push_back(mi); |
800 | } |
801 | |
802 | for (const MethodInfo &mi : method_list) { |
803 | DocData::MethodDoc method; |
804 | |
805 | method.name = mi.name; |
806 | |
807 | for (int j = 0; j < mi.arguments.size(); j++) { |
808 | PropertyInfo arginfo = mi.arguments[j]; |
809 | DocData::ArgumentDoc ad; |
810 | DocData::argument_doc_from_arginfo(ad, mi.arguments[j]); |
811 | ad.name = arginfo.name; |
812 | |
813 | int darg_idx = mi.default_arguments.size() - mi.arguments.size() + j; |
814 | if (darg_idx >= 0) { |
815 | ad.default_value = DocData::get_default_value_string(mi.default_arguments[darg_idx]); |
816 | } |
817 | |
818 | method.arguments.push_back(ad); |
819 | } |
820 | |
821 | DocData::return_doc_from_retinfo(method, mi.return_val); |
822 | |
823 | if (mi.flags & METHOD_FLAG_VARARG) { |
824 | if (!method.qualifiers.is_empty()) { |
825 | method.qualifiers += " " ; |
826 | } |
827 | method.qualifiers += "vararg" ; |
828 | } |
829 | |
830 | if (mi.flags & METHOD_FLAG_CONST) { |
831 | if (!method.qualifiers.is_empty()) { |
832 | method.qualifiers += " " ; |
833 | } |
834 | method.qualifiers += "const" ; |
835 | } |
836 | |
837 | if (mi.flags & METHOD_FLAG_STATIC) { |
838 | if (!method.qualifiers.is_empty()) { |
839 | method.qualifiers += " " ; |
840 | } |
841 | method.qualifiers += "static" ; |
842 | } |
843 | |
844 | if (method.name == cname) { |
845 | c.constructors.push_back(method); |
846 | } else if (method.name.begins_with("operator" )) { |
847 | c.operators.push_back(method); |
848 | } else { |
849 | c.methods.push_back(method); |
850 | } |
851 | } |
852 | |
853 | c.methods.sort(); |
854 | |
855 | List<PropertyInfo> properties; |
856 | v.get_property_list(&properties); |
857 | for (const PropertyInfo &pi : properties) { |
858 | DocData::PropertyDoc property; |
859 | property.name = pi.name; |
860 | property.type = Variant::get_type_name(pi.type); |
861 | property.default_value = DocData::get_default_value_string(v.get(pi.name)); |
862 | |
863 | c.properties.push_back(property); |
864 | } |
865 | |
866 | List<StringName> constants; |
867 | Variant::get_constants_for_type(Variant::Type(i), &constants); |
868 | |
869 | for (const StringName &E : constants) { |
870 | DocData::ConstantDoc constant; |
871 | constant.name = E; |
872 | Variant value = Variant::get_constant_value(Variant::Type(i), E); |
873 | constant.value = value.get_type() == Variant::INT ? itos(value) : value.get_construct_string().replace("\n" , " " ); |
874 | constant.is_value_valid = true; |
875 | c.constants.push_back(constant); |
876 | } |
877 | } |
878 | |
879 | // Add global API (servers, engine singletons, global constants) and Variant utility functions. |
880 | { |
881 | String cname = "@GlobalScope" ; |
882 | class_list[cname] = DocData::ClassDoc(); |
883 | DocData::ClassDoc &c = class_list[cname]; |
884 | c.name = cname; |
885 | |
886 | // Global constants. |
887 | for (int i = 0; i < CoreConstants::get_global_constant_count(); i++) { |
888 | DocData::ConstantDoc cd; |
889 | cd.name = CoreConstants::get_global_constant_name(i); |
890 | cd.is_bitfield = CoreConstants::is_global_constant_bitfield(i); |
891 | if (!CoreConstants::get_ignore_value_in_docs(i)) { |
892 | cd.value = itos(CoreConstants::get_global_constant_value(i)); |
893 | cd.is_value_valid = true; |
894 | } else { |
895 | cd.is_value_valid = false; |
896 | } |
897 | cd.enumeration = CoreConstants::get_global_constant_enum(i); |
898 | c.constants.push_back(cd); |
899 | } |
900 | |
901 | // Servers/engine singletons. |
902 | List<Engine::Singleton> singletons; |
903 | Engine::get_singleton()->get_singletons(&singletons); |
904 | |
905 | // FIXME: this is kind of hackish... |
906 | for (const Engine::Singleton &s : singletons) { |
907 | DocData::PropertyDoc pd; |
908 | if (!s.ptr) { |
909 | continue; |
910 | } |
911 | pd.name = s.name; |
912 | pd.type = s.ptr->get_class(); |
913 | while (String(ClassDB::get_parent_class(pd.type)) != "Object" ) { |
914 | pd.type = ClassDB::get_parent_class(pd.type); |
915 | } |
916 | c.properties.push_back(pd); |
917 | } |
918 | |
919 | // Variant utility functions. |
920 | List<StringName> utility_functions; |
921 | Variant::get_utility_function_list(&utility_functions); |
922 | utility_functions.sort_custom<StringName::AlphCompare>(); |
923 | for (const StringName &E : utility_functions) { |
924 | DocData::MethodDoc md; |
925 | md.name = E; |
926 | // Utility function's return type. |
927 | if (Variant::has_utility_function_return_value(E)) { |
928 | PropertyInfo pi; |
929 | pi.type = Variant::get_utility_function_return_type(E); |
930 | if (pi.type == Variant::NIL) { |
931 | pi.usage = PROPERTY_USAGE_NIL_IS_VARIANT; |
932 | } |
933 | DocData::ArgumentDoc ad; |
934 | DocData::argument_doc_from_arginfo(ad, pi); |
935 | md.return_type = ad.type; |
936 | } |
937 | |
938 | // Utility function's arguments. |
939 | if (Variant::is_utility_function_vararg(E)) { |
940 | md.qualifiers = "vararg" ; |
941 | } else { |
942 | for (int i = 0; i < Variant::get_utility_function_argument_count(E); i++) { |
943 | PropertyInfo pi; |
944 | pi.type = Variant::get_utility_function_argument_type(E, i); |
945 | pi.name = Variant::get_utility_function_argument_name(E, i); |
946 | if (pi.type == Variant::NIL) { |
947 | pi.usage = PROPERTY_USAGE_NIL_IS_VARIANT; |
948 | } |
949 | DocData::ArgumentDoc ad; |
950 | DocData::argument_doc_from_arginfo(ad, pi); |
951 | md.arguments.push_back(ad); |
952 | } |
953 | } |
954 | |
955 | c.methods.push_back(md); |
956 | } |
957 | } |
958 | |
959 | // Add scripting language built-ins. |
960 | { |
961 | // We only add a doc entry for languages which actually define any built-in |
962 | // methods, constants, or annotations. |
963 | for (int i = 0; i < ScriptServer::get_language_count(); i++) { |
964 | ScriptLanguage *lang = ScriptServer::get_language(i); |
965 | String cname = "@" + lang->get_name(); |
966 | DocData::ClassDoc c; |
967 | c.name = cname; |
968 | |
969 | // Get functions. |
970 | List<MethodInfo> minfo; |
971 | lang->get_public_functions(&minfo); |
972 | |
973 | for (const MethodInfo &mi : minfo) { |
974 | DocData::MethodDoc md; |
975 | md.name = mi.name; |
976 | |
977 | if (mi.flags & METHOD_FLAG_VARARG) { |
978 | if (!md.qualifiers.is_empty()) { |
979 | md.qualifiers += " " ; |
980 | } |
981 | md.qualifiers += "vararg" ; |
982 | } |
983 | |
984 | DocData::return_doc_from_retinfo(md, mi.return_val); |
985 | |
986 | for (int j = 0; j < mi.arguments.size(); j++) { |
987 | DocData::ArgumentDoc ad; |
988 | DocData::argument_doc_from_arginfo(ad, mi.arguments[j]); |
989 | |
990 | int darg_idx = j - (mi.arguments.size() - mi.default_arguments.size()); |
991 | if (darg_idx >= 0) { |
992 | ad.default_value = DocData::get_default_value_string(mi.default_arguments[darg_idx]); |
993 | } |
994 | |
995 | md.arguments.push_back(ad); |
996 | } |
997 | |
998 | c.methods.push_back(md); |
999 | } |
1000 | |
1001 | // Get constants. |
1002 | List<Pair<String, Variant>> cinfo; |
1003 | lang->get_public_constants(&cinfo); |
1004 | |
1005 | for (const Pair<String, Variant> &E : cinfo) { |
1006 | DocData::ConstantDoc cd; |
1007 | cd.name = E.first; |
1008 | cd.value = E.second; |
1009 | cd.is_value_valid = true; |
1010 | c.constants.push_back(cd); |
1011 | } |
1012 | |
1013 | // Get annotations. |
1014 | List<MethodInfo> ainfo; |
1015 | lang->get_public_annotations(&ainfo); |
1016 | |
1017 | for (const MethodInfo &ai : ainfo) { |
1018 | DocData::MethodDoc atd; |
1019 | atd.name = ai.name; |
1020 | |
1021 | if (ai.flags & METHOD_FLAG_VARARG) { |
1022 | if (!atd.qualifiers.is_empty()) { |
1023 | atd.qualifiers += " " ; |
1024 | } |
1025 | atd.qualifiers += "vararg" ; |
1026 | } |
1027 | |
1028 | DocData::return_doc_from_retinfo(atd, ai.return_val); |
1029 | |
1030 | for (int j = 0; j < ai.arguments.size(); j++) { |
1031 | DocData::ArgumentDoc ad; |
1032 | DocData::argument_doc_from_arginfo(ad, ai.arguments[j]); |
1033 | |
1034 | int darg_idx = j - (ai.arguments.size() - ai.default_arguments.size()); |
1035 | if (darg_idx >= 0) { |
1036 | ad.default_value = DocData::get_default_value_string(ai.default_arguments[darg_idx]); |
1037 | } |
1038 | |
1039 | atd.arguments.push_back(ad); |
1040 | } |
1041 | |
1042 | c.annotations.push_back(atd); |
1043 | } |
1044 | |
1045 | // Skip adding the lang if it doesn't expose anything (e.g. C#). |
1046 | if (c.methods.is_empty() && c.constants.is_empty() && c.annotations.is_empty()) { |
1047 | continue; |
1048 | } |
1049 | |
1050 | class_list[cname] = c; |
1051 | } |
1052 | } |
1053 | } |
1054 | |
1055 | static Error _parse_methods(Ref<XMLParser> &parser, Vector<DocData::MethodDoc> &methods) { |
1056 | String section = parser->get_node_name(); |
1057 | String element = section.substr(0, section.length() - 1); |
1058 | |
1059 | while (parser->read() == OK) { |
1060 | if (parser->get_node_type() == XMLParser::NODE_ELEMENT) { |
1061 | if (parser->get_node_name() == element) { |
1062 | DocData::MethodDoc method; |
1063 | ERR_FAIL_COND_V(!parser->has_attribute("name" ), ERR_FILE_CORRUPT); |
1064 | method.name = parser->get_named_attribute_value("name" ); |
1065 | if (parser->has_attribute("qualifiers" )) { |
1066 | method.qualifiers = parser->get_named_attribute_value("qualifiers" ); |
1067 | } |
1068 | if (parser->has_attribute("is_deprecated" )) { |
1069 | method.is_deprecated = parser->get_named_attribute_value("is_deprecated" ).to_lower() == "true" ; |
1070 | } |
1071 | if (parser->has_attribute("is_experimental" )) { |
1072 | method.is_experimental = parser->get_named_attribute_value("is_experimental" ).to_lower() == "true" ; |
1073 | } |
1074 | |
1075 | while (parser->read() == OK) { |
1076 | if (parser->get_node_type() == XMLParser::NODE_ELEMENT) { |
1077 | String name = parser->get_node_name(); |
1078 | if (name == "return" ) { |
1079 | ERR_FAIL_COND_V(!parser->has_attribute("type" ), ERR_FILE_CORRUPT); |
1080 | method.return_type = parser->get_named_attribute_value("type" ); |
1081 | if (parser->has_attribute("enum" )) { |
1082 | method.return_enum = parser->get_named_attribute_value("enum" ); |
1083 | if (parser->has_attribute("is_bitfield" )) { |
1084 | method.return_is_bitfield = parser->get_named_attribute_value("is_bitfield" ).to_lower() == "true" ; |
1085 | } |
1086 | } |
1087 | } else if (name == "returns_error" ) { |
1088 | ERR_FAIL_COND_V(!parser->has_attribute("number" ), ERR_FILE_CORRUPT); |
1089 | method.errors_returned.push_back(parser->get_named_attribute_value("number" ).to_int()); |
1090 | } else if (name == "param" ) { |
1091 | DocData::ArgumentDoc argument; |
1092 | ERR_FAIL_COND_V(!parser->has_attribute("name" ), ERR_FILE_CORRUPT); |
1093 | argument.name = parser->get_named_attribute_value("name" ); |
1094 | ERR_FAIL_COND_V(!parser->has_attribute("type" ), ERR_FILE_CORRUPT); |
1095 | argument.type = parser->get_named_attribute_value("type" ); |
1096 | if (parser->has_attribute("enum" )) { |
1097 | argument.enumeration = parser->get_named_attribute_value("enum" ); |
1098 | if (parser->has_attribute("is_bitfield" )) { |
1099 | argument.is_bitfield = parser->get_named_attribute_value("is_bitfield" ).to_lower() == "true" ; |
1100 | } |
1101 | } |
1102 | |
1103 | method.arguments.push_back(argument); |
1104 | |
1105 | } else if (name == "description" ) { |
1106 | parser->read(); |
1107 | if (parser->get_node_type() == XMLParser::NODE_TEXT) { |
1108 | method.description = parser->get_node_data(); |
1109 | } |
1110 | } |
1111 | |
1112 | } else if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END && parser->get_node_name() == element) { |
1113 | break; |
1114 | } |
1115 | } |
1116 | |
1117 | methods.push_back(method); |
1118 | |
1119 | } else { |
1120 | ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, "Invalid tag in doc file: " + parser->get_node_name() + ", expected " + element + "." ); |
1121 | } |
1122 | |
1123 | } else if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END && parser->get_node_name() == section) { |
1124 | break; |
1125 | } |
1126 | } |
1127 | |
1128 | return OK; |
1129 | } |
1130 | |
1131 | Error DocTools::load_classes(const String &p_dir) { |
1132 | Error err; |
1133 | Ref<DirAccess> da = DirAccess::open(p_dir, &err); |
1134 | if (da.is_null()) { |
1135 | return err; |
1136 | } |
1137 | |
1138 | da->list_dir_begin(); |
1139 | String path; |
1140 | path = da->get_next(); |
1141 | while (!path.is_empty()) { |
1142 | if (!da->current_is_dir() && path.ends_with("xml" )) { |
1143 | Ref<XMLParser> parser = memnew(XMLParser); |
1144 | Error err2 = parser->open(p_dir.path_join(path)); |
1145 | if (err2) { |
1146 | return err2; |
1147 | } |
1148 | |
1149 | _load(parser); |
1150 | } |
1151 | path = da->get_next(); |
1152 | } |
1153 | |
1154 | da->list_dir_end(); |
1155 | |
1156 | return OK; |
1157 | } |
1158 | |
1159 | Error DocTools::erase_classes(const String &p_dir) { |
1160 | Error err; |
1161 | Ref<DirAccess> da = DirAccess::open(p_dir, &err); |
1162 | if (da.is_null()) { |
1163 | return err; |
1164 | } |
1165 | |
1166 | List<String> to_erase; |
1167 | |
1168 | da->list_dir_begin(); |
1169 | String path; |
1170 | path = da->get_next(); |
1171 | while (!path.is_empty()) { |
1172 | if (!da->current_is_dir() && path.ends_with("xml" )) { |
1173 | to_erase.push_back(path); |
1174 | } |
1175 | path = da->get_next(); |
1176 | } |
1177 | da->list_dir_end(); |
1178 | |
1179 | while (to_erase.size()) { |
1180 | da->remove(to_erase.front()->get()); |
1181 | to_erase.pop_front(); |
1182 | } |
1183 | |
1184 | return OK; |
1185 | } |
1186 | |
1187 | Error DocTools::_load(Ref<XMLParser> parser) { |
1188 | Error err = OK; |
1189 | |
1190 | while ((err = parser->read()) == OK) { |
1191 | if (parser->get_node_type() == XMLParser::NODE_ELEMENT && parser->get_node_name() == "?xml" ) { |
1192 | parser->skip_section(); |
1193 | } |
1194 | |
1195 | if (parser->get_node_type() != XMLParser::NODE_ELEMENT) { |
1196 | continue; //no idea what this may be, but skipping anyway |
1197 | } |
1198 | |
1199 | ERR_FAIL_COND_V(parser->get_node_name() != "class" , ERR_FILE_CORRUPT); |
1200 | |
1201 | ERR_FAIL_COND_V(!parser->has_attribute("name" ), ERR_FILE_CORRUPT); |
1202 | String name = parser->get_named_attribute_value("name" ); |
1203 | class_list[name] = DocData::ClassDoc(); |
1204 | DocData::ClassDoc &c = class_list[name]; |
1205 | |
1206 | c.name = name; |
1207 | if (parser->has_attribute("inherits" )) { |
1208 | c.inherits = parser->get_named_attribute_value("inherits" ); |
1209 | } |
1210 | |
1211 | if (parser->has_attribute("is_deprecated" )) { |
1212 | c.is_deprecated = parser->get_named_attribute_value("is_deprecated" ).to_lower() == "true" ; |
1213 | } |
1214 | |
1215 | if (parser->has_attribute("is_experimental" )) { |
1216 | c.is_experimental = parser->get_named_attribute_value("is_experimental" ).to_lower() == "true" ; |
1217 | } |
1218 | |
1219 | while (parser->read() == OK) { |
1220 | if (parser->get_node_type() == XMLParser::NODE_ELEMENT) { |
1221 | String name2 = parser->get_node_name(); |
1222 | |
1223 | if (name2 == "brief_description" ) { |
1224 | parser->read(); |
1225 | if (parser->get_node_type() == XMLParser::NODE_TEXT) { |
1226 | c.brief_description = parser->get_node_data(); |
1227 | } |
1228 | |
1229 | } else if (name2 == "description" ) { |
1230 | parser->read(); |
1231 | if (parser->get_node_type() == XMLParser::NODE_TEXT) { |
1232 | c.description = parser->get_node_data(); |
1233 | } |
1234 | } else if (name2 == "tutorials" ) { |
1235 | while (parser->read() == OK) { |
1236 | if (parser->get_node_type() == XMLParser::NODE_ELEMENT) { |
1237 | String name3 = parser->get_node_name(); |
1238 | |
1239 | if (name3 == "link" ) { |
1240 | DocData::TutorialDoc tutorial; |
1241 | if (parser->has_attribute("title" )) { |
1242 | tutorial.title = parser->get_named_attribute_value("title" ); |
1243 | } |
1244 | parser->read(); |
1245 | if (parser->get_node_type() == XMLParser::NODE_TEXT) { |
1246 | tutorial.link = parser->get_node_data().strip_edges(); |
1247 | c.tutorials.push_back(tutorial); |
1248 | } |
1249 | } else { |
1250 | ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, "Invalid tag in doc file: " + name3 + "." ); |
1251 | } |
1252 | } else if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END && parser->get_node_name() == "tutorials" ) { |
1253 | break; // End of <tutorials>. |
1254 | } |
1255 | } |
1256 | } else if (name2 == "constructors" ) { |
1257 | Error err2 = _parse_methods(parser, c.constructors); |
1258 | ERR_FAIL_COND_V(err2, err2); |
1259 | } else if (name2 == "methods" ) { |
1260 | Error err2 = _parse_methods(parser, c.methods); |
1261 | ERR_FAIL_COND_V(err2, err2); |
1262 | } else if (name2 == "operators" ) { |
1263 | Error err2 = _parse_methods(parser, c.operators); |
1264 | ERR_FAIL_COND_V(err2, err2); |
1265 | } else if (name2 == "signals" ) { |
1266 | Error err2 = _parse_methods(parser, c.signals); |
1267 | ERR_FAIL_COND_V(err2, err2); |
1268 | } else if (name2 == "annotations" ) { |
1269 | Error err2 = _parse_methods(parser, c.annotations); |
1270 | ERR_FAIL_COND_V(err2, err2); |
1271 | } else if (name2 == "members" ) { |
1272 | while (parser->read() == OK) { |
1273 | if (parser->get_node_type() == XMLParser::NODE_ELEMENT) { |
1274 | String name3 = parser->get_node_name(); |
1275 | |
1276 | if (name3 == "member" ) { |
1277 | DocData::PropertyDoc prop2; |
1278 | |
1279 | ERR_FAIL_COND_V(!parser->has_attribute("name" ), ERR_FILE_CORRUPT); |
1280 | prop2.name = parser->get_named_attribute_value("name" ); |
1281 | ERR_FAIL_COND_V(!parser->has_attribute("type" ), ERR_FILE_CORRUPT); |
1282 | prop2.type = parser->get_named_attribute_value("type" ); |
1283 | if (parser->has_attribute("setter" )) { |
1284 | prop2.setter = parser->get_named_attribute_value("setter" ); |
1285 | } |
1286 | if (parser->has_attribute("getter" )) { |
1287 | prop2.getter = parser->get_named_attribute_value("getter" ); |
1288 | } |
1289 | if (parser->has_attribute("enum" )) { |
1290 | prop2.enumeration = parser->get_named_attribute_value("enum" ); |
1291 | if (parser->has_attribute("is_bitfield" )) { |
1292 | prop2.is_bitfield = parser->get_named_attribute_value("is_bitfield" ).to_lower() == "true" ; |
1293 | } |
1294 | } |
1295 | if (parser->has_attribute("is_deprecated" )) { |
1296 | prop2.is_deprecated = parser->get_named_attribute_value("is_deprecated" ).to_lower() == "true" ; |
1297 | } |
1298 | if (parser->has_attribute("is_experimental" )) { |
1299 | prop2.is_experimental = parser->get_named_attribute_value("is_experimental" ).to_lower() == "true" ; |
1300 | } |
1301 | if (!parser->is_empty()) { |
1302 | parser->read(); |
1303 | if (parser->get_node_type() == XMLParser::NODE_TEXT) { |
1304 | prop2.description = parser->get_node_data(); |
1305 | } |
1306 | } |
1307 | c.properties.push_back(prop2); |
1308 | } else { |
1309 | ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, "Invalid tag in doc file: " + name3 + "." ); |
1310 | } |
1311 | |
1312 | } else if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END && parser->get_node_name() == "members" ) { |
1313 | break; // End of <members>. |
1314 | } |
1315 | } |
1316 | |
1317 | } else if (name2 == "theme_items" ) { |
1318 | while (parser->read() == OK) { |
1319 | if (parser->get_node_type() == XMLParser::NODE_ELEMENT) { |
1320 | String name3 = parser->get_node_name(); |
1321 | |
1322 | if (name3 == "theme_item" ) { |
1323 | DocData::ThemeItemDoc prop2; |
1324 | |
1325 | ERR_FAIL_COND_V(!parser->has_attribute("name" ), ERR_FILE_CORRUPT); |
1326 | prop2.name = parser->get_named_attribute_value("name" ); |
1327 | ERR_FAIL_COND_V(!parser->has_attribute("type" ), ERR_FILE_CORRUPT); |
1328 | prop2.type = parser->get_named_attribute_value("type" ); |
1329 | ERR_FAIL_COND_V(!parser->has_attribute("data_type" ), ERR_FILE_CORRUPT); |
1330 | prop2.data_type = parser->get_named_attribute_value("data_type" ); |
1331 | if (!parser->is_empty()) { |
1332 | parser->read(); |
1333 | if (parser->get_node_type() == XMLParser::NODE_TEXT) { |
1334 | prop2.description = parser->get_node_data(); |
1335 | } |
1336 | } |
1337 | c.theme_properties.push_back(prop2); |
1338 | } else { |
1339 | ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, "Invalid tag in doc file: " + name3 + "." ); |
1340 | } |
1341 | |
1342 | } else if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END && parser->get_node_name() == "theme_items" ) { |
1343 | break; // End of <theme_items>. |
1344 | } |
1345 | } |
1346 | |
1347 | } else if (name2 == "constants" ) { |
1348 | while (parser->read() == OK) { |
1349 | if (parser->get_node_type() == XMLParser::NODE_ELEMENT) { |
1350 | String name3 = parser->get_node_name(); |
1351 | |
1352 | if (name3 == "constant" ) { |
1353 | DocData::ConstantDoc constant2; |
1354 | ERR_FAIL_COND_V(!parser->has_attribute("name" ), ERR_FILE_CORRUPT); |
1355 | constant2.name = parser->get_named_attribute_value("name" ); |
1356 | ERR_FAIL_COND_V(!parser->has_attribute("value" ), ERR_FILE_CORRUPT); |
1357 | constant2.value = parser->get_named_attribute_value("value" ); |
1358 | constant2.is_value_valid = true; |
1359 | if (parser->has_attribute("enum" )) { |
1360 | constant2.enumeration = parser->get_named_attribute_value("enum" ); |
1361 | if (parser->has_attribute("is_bitfield" )) { |
1362 | constant2.is_bitfield = parser->get_named_attribute_value("is_bitfield" ).to_lower() == "true" ; |
1363 | } |
1364 | } |
1365 | if (parser->has_attribute("is_deprecated" )) { |
1366 | constant2.is_deprecated = parser->get_named_attribute_value("is_deprecated" ).to_lower() == "true" ; |
1367 | } |
1368 | if (parser->has_attribute("is_experimental" )) { |
1369 | constant2.is_experimental = parser->get_named_attribute_value("is_experimental" ).to_lower() == "true" ; |
1370 | } |
1371 | if (!parser->is_empty()) { |
1372 | parser->read(); |
1373 | if (parser->get_node_type() == XMLParser::NODE_TEXT) { |
1374 | constant2.description = parser->get_node_data(); |
1375 | } |
1376 | } |
1377 | c.constants.push_back(constant2); |
1378 | } else { |
1379 | ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, "Invalid tag in doc file: " + name3 + "." ); |
1380 | } |
1381 | |
1382 | } else if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END && parser->get_node_name() == "constants" ) { |
1383 | break; // End of <constants>. |
1384 | } |
1385 | } |
1386 | |
1387 | } else { |
1388 | ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, "Invalid tag in doc file: " + name2 + "." ); |
1389 | } |
1390 | |
1391 | } else if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END && parser->get_node_name() == "class" ) { |
1392 | break; // End of <class>. |
1393 | } |
1394 | } |
1395 | } |
1396 | |
1397 | return OK; |
1398 | } |
1399 | |
1400 | static void _write_string(Ref<FileAccess> f, int p_tablevel, const String &p_string) { |
1401 | if (p_string.is_empty()) { |
1402 | return; |
1403 | } |
1404 | String tab = String("\t" ).repeat(p_tablevel); |
1405 | f->store_string(tab + p_string + "\n" ); |
1406 | } |
1407 | |
1408 | static void _write_method_doc(Ref<FileAccess> f, const String &p_name, Vector<DocData::MethodDoc> &p_method_docs) { |
1409 | if (!p_method_docs.is_empty()) { |
1410 | p_method_docs.sort(); |
1411 | _write_string(f, 1, "<" + p_name + "s>" ); |
1412 | for (int i = 0; i < p_method_docs.size(); i++) { |
1413 | const DocData::MethodDoc &m = p_method_docs[i]; |
1414 | |
1415 | String qualifiers; |
1416 | if (!m.qualifiers.is_empty()) { |
1417 | qualifiers += " qualifiers=\"" + m.qualifiers.xml_escape() + "\"" ; |
1418 | } |
1419 | |
1420 | String additional_attributes; |
1421 | if (m.is_deprecated) { |
1422 | additional_attributes += " is_deprecated=\"true\"" ; |
1423 | } |
1424 | if (m.is_experimental) { |
1425 | additional_attributes += " is_experimental=\"true\"" ; |
1426 | } |
1427 | |
1428 | _write_string(f, 2, "<" + p_name + " name=\"" + m.name.xml_escape() + "\"" + qualifiers + additional_attributes + ">" ); |
1429 | |
1430 | if (!m.return_type.is_empty()) { |
1431 | String enum_text; |
1432 | if (!m.return_enum.is_empty()) { |
1433 | enum_text = " enum=\"" + m.return_enum + "\"" ; |
1434 | if (m.return_is_bitfield) { |
1435 | enum_text += " is_bitfield=\"true\"" ; |
1436 | } |
1437 | } |
1438 | _write_string(f, 3, "<return type=\"" + m.return_type.xml_escape(true) + "\"" + enum_text + " />" ); |
1439 | } |
1440 | if (m.errors_returned.size() > 0) { |
1441 | for (int j = 0; j < m.errors_returned.size(); j++) { |
1442 | _write_string(f, 3, "<returns_error number=\"" + itos(m.errors_returned[j]) + "\"/>" ); |
1443 | } |
1444 | } |
1445 | |
1446 | for (int j = 0; j < m.arguments.size(); j++) { |
1447 | const DocData::ArgumentDoc &a = m.arguments[j]; |
1448 | |
1449 | String enum_text; |
1450 | if (!a.enumeration.is_empty()) { |
1451 | enum_text = " enum=\"" + a.enumeration + "\"" ; |
1452 | if (a.is_bitfield) { |
1453 | enum_text += " is_bitfield=\"true\"" ; |
1454 | } |
1455 | } |
1456 | |
1457 | if (!a.default_value.is_empty()) { |
1458 | _write_string(f, 3, "<param index=\"" + itos(j) + "\" name=\"" + a.name.xml_escape() + "\" type=\"" + a.type.xml_escape(true) + "\"" + enum_text + " default=\"" + a.default_value.xml_escape(true) + "\" />" ); |
1459 | } else { |
1460 | _write_string(f, 3, "<param index=\"" + itos(j) + "\" name=\"" + a.name.xml_escape() + "\" type=\"" + a.type.xml_escape(true) + "\"" + enum_text + " />" ); |
1461 | } |
1462 | } |
1463 | |
1464 | _write_string(f, 3, "<description>" ); |
1465 | _write_string(f, 4, _translate_doc_string(m.description).strip_edges().xml_escape()); |
1466 | _write_string(f, 3, "</description>" ); |
1467 | |
1468 | _write_string(f, 2, "</" + p_name + ">" ); |
1469 | } |
1470 | |
1471 | _write_string(f, 1, "</" + p_name + "s>" ); |
1472 | } |
1473 | } |
1474 | |
1475 | Error DocTools::save_classes(const String &p_default_path, const HashMap<String, String> &p_class_path, bool p_include_xml_schema) { |
1476 | for (KeyValue<String, DocData::ClassDoc> &E : class_list) { |
1477 | DocData::ClassDoc &c = E.value; |
1478 | |
1479 | String save_path; |
1480 | if (p_class_path.has(c.name)) { |
1481 | save_path = p_class_path[c.name]; |
1482 | } else { |
1483 | save_path = p_default_path; |
1484 | } |
1485 | |
1486 | Error err; |
1487 | String save_file = save_path.path_join(c.name.replace("\"" , "" ).replace("/" , "--" ) + ".xml" ); |
1488 | Ref<FileAccess> f = FileAccess::open(save_file, FileAccess::WRITE, &err); |
1489 | |
1490 | ERR_CONTINUE_MSG(err != OK, "Can't write doc file: " + save_file + "." ); |
1491 | |
1492 | _write_string(f, 0, "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>" ); |
1493 | |
1494 | String = "<class name=\"" + c.name.xml_escape(true) + "\"" ; |
1495 | if (!c.inherits.is_empty()) { |
1496 | header += " inherits=\"" + c.inherits.xml_escape(true) + "\"" ; |
1497 | if (c.is_deprecated) { |
1498 | header += " is_deprecated=\"true\"" ; |
1499 | } |
1500 | if (c.is_experimental) { |
1501 | header += " is_experimental=\"true\"" ; |
1502 | } |
1503 | } |
1504 | if (p_include_xml_schema) { |
1505 | // Reference the XML schema so editors can provide error checking. |
1506 | // Modules are nested deep, so change the path to reference the same schema everywhere. |
1507 | const String schema_path = save_path.find("modules/" ) != -1 ? "../../../doc/class.xsd" : "../class.xsd" ; |
1508 | header += vformat( |
1509 | R"( xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="%s")" , |
1510 | schema_path); |
1511 | } |
1512 | header += ">" ; |
1513 | _write_string(f, 0, header); |
1514 | |
1515 | _write_string(f, 1, "<brief_description>" ); |
1516 | _write_string(f, 2, _translate_doc_string(c.brief_description).strip_edges().xml_escape()); |
1517 | _write_string(f, 1, "</brief_description>" ); |
1518 | |
1519 | _write_string(f, 1, "<description>" ); |
1520 | _write_string(f, 2, _translate_doc_string(c.description).strip_edges().xml_escape()); |
1521 | _write_string(f, 1, "</description>" ); |
1522 | |
1523 | _write_string(f, 1, "<tutorials>" ); |
1524 | for (int i = 0; i < c.tutorials.size(); i++) { |
1525 | DocData::TutorialDoc tutorial = c.tutorials.get(i); |
1526 | String title_attribute = (!tutorial.title.is_empty()) ? " title=\"" + _translate_doc_string(tutorial.title).xml_escape() + "\"" : "" ; |
1527 | _write_string(f, 2, "<link" + title_attribute + ">" + tutorial.link.xml_escape() + "</link>" ); |
1528 | } |
1529 | _write_string(f, 1, "</tutorials>" ); |
1530 | |
1531 | _write_method_doc(f, "constructor" , c.constructors); |
1532 | |
1533 | _write_method_doc(f, "method" , c.methods); |
1534 | |
1535 | if (!c.properties.is_empty()) { |
1536 | _write_string(f, 1, "<members>" ); |
1537 | |
1538 | c.properties.sort(); |
1539 | |
1540 | for (int i = 0; i < c.properties.size(); i++) { |
1541 | String additional_attributes; |
1542 | if (!c.properties[i].enumeration.is_empty()) { |
1543 | additional_attributes += " enum=\"" + c.properties[i].enumeration + "\"" ; |
1544 | if (c.properties[i].is_bitfield) { |
1545 | additional_attributes += " is_bitfield=\"true\"" ; |
1546 | } |
1547 | } |
1548 | if (!c.properties[i].default_value.is_empty()) { |
1549 | additional_attributes += " default=\"" + c.properties[i].default_value.xml_escape(true) + "\"" ; |
1550 | } |
1551 | if (c.properties[i].is_deprecated) { |
1552 | additional_attributes += " is_deprecated=\"true\"" ; |
1553 | } |
1554 | if (c.properties[i].is_experimental) { |
1555 | additional_attributes += " is_experimental=\"true\"" ; |
1556 | } |
1557 | |
1558 | const DocData::PropertyDoc &p = c.properties[i]; |
1559 | |
1560 | if (c.properties[i].overridden) { |
1561 | _write_string(f, 2, "<member name=\"" + p.name + "\" type=\"" + p.type.xml_escape(true) + "\" setter=\"" + p.setter + "\" getter=\"" + p.getter + "\" overrides=\"" + p.overrides + "\"" + additional_attributes + " />" ); |
1562 | } else { |
1563 | _write_string(f, 2, "<member name=\"" + p.name + "\" type=\"" + p.type.xml_escape(true) + "\" setter=\"" + p.setter + "\" getter=\"" + p.getter + "\"" + additional_attributes + ">" ); |
1564 | _write_string(f, 3, _translate_doc_string(p.description).strip_edges().xml_escape()); |
1565 | _write_string(f, 2, "</member>" ); |
1566 | } |
1567 | } |
1568 | _write_string(f, 1, "</members>" ); |
1569 | } |
1570 | |
1571 | _write_method_doc(f, "signal" , c.signals); |
1572 | |
1573 | if (!c.constants.is_empty()) { |
1574 | _write_string(f, 1, "<constants>" ); |
1575 | for (int i = 0; i < c.constants.size(); i++) { |
1576 | const DocData::ConstantDoc &k = c.constants[i]; |
1577 | |
1578 | String additional_attributes; |
1579 | if (c.constants[i].is_deprecated) { |
1580 | additional_attributes += " is_deprecated=\"true\"" ; |
1581 | } |
1582 | if (c.constants[i].is_experimental) { |
1583 | additional_attributes += " is_experimental=\"true\"" ; |
1584 | } |
1585 | |
1586 | if (k.is_value_valid) { |
1587 | if (!k.enumeration.is_empty()) { |
1588 | if (k.is_bitfield) { |
1589 | _write_string(f, 2, "<constant name=\"" + k.name + "\" value=\"" + k.value.xml_escape(true) + "\" enum=\"" + k.enumeration + "\" is_bitfield=\"true\"" + additional_attributes + ">" ); |
1590 | } else { |
1591 | _write_string(f, 2, "<constant name=\"" + k.name + "\" value=\"" + k.value.xml_escape(true) + "\" enum=\"" + k.enumeration + "\"" + additional_attributes + ">" ); |
1592 | } |
1593 | } else { |
1594 | _write_string(f, 2, "<constant name=\"" + k.name + "\" value=\"" + k.value.xml_escape(true) + "\"" + additional_attributes + ">" ); |
1595 | } |
1596 | } else { |
1597 | if (!k.enumeration.is_empty()) { |
1598 | _write_string(f, 2, "<constant name=\"" + k.name + "\" value=\"platform-dependent\" enum=\"" + k.enumeration + "\"" + additional_attributes + ">" ); |
1599 | } else { |
1600 | _write_string(f, 2, "<constant name=\"" + k.name + "\" value=\"platform-dependent\"" + additional_attributes + ">" ); |
1601 | } |
1602 | } |
1603 | _write_string(f, 3, _translate_doc_string(k.description).strip_edges().xml_escape()); |
1604 | _write_string(f, 2, "</constant>" ); |
1605 | } |
1606 | |
1607 | _write_string(f, 1, "</constants>" ); |
1608 | } |
1609 | |
1610 | _write_method_doc(f, "annotation" , c.annotations); |
1611 | |
1612 | if (!c.theme_properties.is_empty()) { |
1613 | c.theme_properties.sort(); |
1614 | |
1615 | _write_string(f, 1, "<theme_items>" ); |
1616 | for (int i = 0; i < c.theme_properties.size(); i++) { |
1617 | const DocData::ThemeItemDoc &ti = c.theme_properties[i]; |
1618 | |
1619 | if (!ti.default_value.is_empty()) { |
1620 | _write_string(f, 2, "<theme_item name=\"" + ti.name + "\" data_type=\"" + ti.data_type + "\" type=\"" + ti.type + "\" default=\"" + ti.default_value.xml_escape(true) + "\">" ); |
1621 | } else { |
1622 | _write_string(f, 2, "<theme_item name=\"" + ti.name + "\" data_type=\"" + ti.data_type + "\" type=\"" + ti.type + "\">" ); |
1623 | } |
1624 | |
1625 | _write_string(f, 3, _translate_doc_string(ti.description).strip_edges().xml_escape()); |
1626 | |
1627 | _write_string(f, 2, "</theme_item>" ); |
1628 | } |
1629 | _write_string(f, 1, "</theme_items>" ); |
1630 | } |
1631 | |
1632 | _write_method_doc(f, "operator" , c.operators); |
1633 | |
1634 | _write_string(f, 0, "</class>" ); |
1635 | } |
1636 | |
1637 | return OK; |
1638 | } |
1639 | |
1640 | Error DocTools::load_compressed(const uint8_t *p_data, int p_compressed_size, int p_uncompressed_size) { |
1641 | Vector<uint8_t> data; |
1642 | data.resize(p_uncompressed_size); |
1643 | int ret = Compression::decompress(data.ptrw(), p_uncompressed_size, p_data, p_compressed_size, Compression::MODE_DEFLATE); |
1644 | ERR_FAIL_COND_V_MSG(ret == -1, ERR_FILE_CORRUPT, "Compressed file is corrupt." ); |
1645 | class_list.clear(); |
1646 | |
1647 | Ref<XMLParser> parser = memnew(XMLParser); |
1648 | Error err = parser->open_buffer(data); |
1649 | if (err) { |
1650 | return err; |
1651 | } |
1652 | |
1653 | _load(parser); |
1654 | |
1655 | return OK; |
1656 | } |
1657 | |