diff --git a/core/doc_data.cpp b/core/doc_data.cpp index 5e09e560d56d..2d909f5335ea 100644 --- a/core/doc_data.cpp +++ b/core/doc_data.cpp @@ -165,6 +165,7 @@ void DocData::method_doc_from_methodinfo(DocData::MethodDoc &p_method, const Met void DocData::constant_doc_from_variant(DocData::ConstantDoc &p_const, const StringName &p_name, const Variant &p_value, const String &p_desc) { p_const.name = p_name; p_const.value = p_value; + p_const.is_value_valid = (p_value.get_type() != Variant::OBJECT); p_const.description = p_desc; } diff --git a/editor/doc_tools.cpp b/editor/doc_tools.cpp index 7c9736413da4..e65d9da5629d 100644 --- a/editor/doc_tools.cpp +++ b/editor/doc_tools.cpp @@ -1403,7 +1403,7 @@ static void _write_method_doc(Ref f, const String &p_name, Vector"); + _write_string(f, 3, ""); } if (m.errors_returned.size() > 0) { for (int j = 0; j < m.errors_returned.size(); j++) { @@ -1420,9 +1420,9 @@ static void _write_method_doc(Ref f, const String &p_name, Vector"); + _write_string(f, 3, ""); } else { - _write_string(f, 3, ""); + _write_string(f, 3, ""); } } @@ -1437,7 +1437,7 @@ static void _write_method_doc(Ref f, const String &p_name, Vector &p_class_path) { +Error DocTools::save_classes(const String &p_default_path, const HashMap &p_class_path, bool p_include_xml_schema) { for (KeyValue &E : class_list) { DocData::ClassDoc &c = E.value; @@ -1449,16 +1449,16 @@ Error DocTools::save_classes(const String &p_default_path, const HashMap f = FileAccess::open(save_file, FileAccess::WRITE, &err); ERR_CONTINUE_MSG(err != OK, "Can't write doc file: " + save_file + "."); _write_string(f, 0, ""); - String header = ")", - schema_path); + if (p_include_xml_schema) { + // Reference the XML schema so editors can provide error checking. + // Modules are nested deep, so change the path to reference the same schema everywhere. + const String schema_path = save_path.find("modules/") != -1 ? "../../../doc/class.xsd" : "../class.xsd"; + header += vformat( + R"( xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="%s")", + schema_path); + } + header += ">"; _write_string(f, 0, header); _write_string(f, 1, ""); @@ -1518,9 +1521,9 @@ Error DocTools::save_classes(const String &p_default_path, const HashMap"); + _write_string(f, 2, ""); } else { - _write_string(f, 2, ""); + _write_string(f, 2, ""); _write_string(f, 3, _translate_doc_string(p.description).strip_edges().xml_escape()); _write_string(f, 2, ""); } @@ -1546,12 +1549,12 @@ Error DocTools::save_classes(const String &p_default_path, const HashMap"); + _write_string(f, 2, ""); } else { - _write_string(f, 2, ""); + _write_string(f, 2, ""); } } else { - _write_string(f, 2, ""); + _write_string(f, 2, ""); } } else { if (!k.enumeration.is_empty()) { diff --git a/editor/doc_tools.h b/editor/doc_tools.h index 08efae31efc3..2d4a45bda008 100644 --- a/editor/doc_tools.h +++ b/editor/doc_tools.h @@ -47,7 +47,7 @@ public: bool has_doc(const String &p_class_name); void generate(bool p_basic_types = false); Error load_classes(const String &p_dir); - Error save_classes(const String &p_default_path, const HashMap &p_class_path); + Error save_classes(const String &p_default_path, const HashMap &p_class_path, bool p_include_xml_schema = true); Error _load(Ref parser); Error load_compressed(const uint8_t *p_data, int p_compressed_size, int p_uncompressed_size); diff --git a/main/main.cpp b/main/main.cpp index 0a6c7d5f9701..43495bdf398c 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -112,6 +112,10 @@ #include "modules/mono/editor/bindings_generator.h" #endif +#ifdef MODULE_GDSCRIPT_ENABLED +#include "modules/gdscript/gdscript.h" +#endif + /* Static members */ // Singletons @@ -247,6 +251,31 @@ static String get_full_version_string() { return String(VERSION_FULL_BUILD) + hash; } +#if defined(TOOLS_ENABLED) && defined(MODULE_GDSCRIPT_ENABLED) +static Vector get_files_with_extension(const String &p_root, const String &p_extension) { + Vector paths; + + Ref dir = DirAccess::open(p_root); + if (dir.is_valid()) { + dir->list_dir_begin(); + String fn = dir->get_next(); + while (!fn.is_empty()) { + if (!dir->current_is_hidden() && fn != "." && fn != "..") { + if (dir->current_is_dir()) { + paths.append_array(get_files_with_extension(p_root.path_join(fn), p_extension)); + } else if (fn.get_extension() == p_extension) { + paths.append(p_root.path_join(fn)); + } + } + fn = dir->get_next(); + } + dir->list_dir_end(); + } + + return paths; +} +#endif + // FIXME: Could maybe be moved to have less code in main.cpp. void initialize_physics() { /// 3D Physics Server @@ -455,6 +484,9 @@ void Main::print_help(const char *p_binary) { #endif // DISABLE_DEPRECATED OS::get_singleton()->print(" --doctool [] Dump the engine API reference to the given (defaults to current dir) in XML format, merging if existing files are found.\n"); OS::get_singleton()->print(" --no-docbase Disallow dumping the base types (used with --doctool).\n"); +#ifdef MODULE_GDSCRIPT_ENABLED + OS::get_singleton()->print(" --gdscript-docs Rather than dumping the engine API, generate API reference from the inline documentation in the GDScript files found in (used with --doctool).\n"); +#endif OS::get_singleton()->print(" --build-solutions Build the scripting solutions (e.g. for C# projects). Implies --editor and requires a valid project to edit.\n"); OS::get_singleton()->print(" --dump-gdextension-interface Generate GDExtension header file 'gdextension_interface.h' in the current folder. This file is the base file required to implement a GDExtension.\n"); OS::get_singleton()->print(" --dump-extension-api Generate JSON dump of the Godot API for GDExtension bindings named 'extension_api.json' in the current folder.\n"); @@ -2461,6 +2493,9 @@ bool Main::start() { String _export_preset; bool export_debug = false; bool export_pack_only = false; +#ifdef MODULE_GDSCRIPT_ENABLED + String gdscript_docs_path; +#endif #ifndef DISABLE_DEPRECATED bool converting_project = false; bool validating_converting_project = false; @@ -2521,6 +2556,10 @@ bool Main::start() { doc_tool_path = "."; parsed_pair = false; } +#ifdef MODULE_GDSCRIPT_ENABLED + } else if (args[i] == "--gdscript-docs") { + gdscript_docs_path = args[i + 1]; +#endif } else if (args[i] == "--export-release") { editor = true; //needs editor _export_preset = args[i + 1]; @@ -2552,6 +2591,38 @@ bool Main::start() { uint64_t minimum_time_msec = GLOBAL_DEF(PropertyInfo(Variant::INT, "application/boot_splash/minimum_display_time", PROPERTY_HINT_RANGE, "0,100,1,or_greater,suffix:ms"), 0); #ifdef TOOLS_ENABLED +#ifdef MODULE_GDSCRIPT_ENABLED + if (!doc_tool_path.is_empty() && !gdscript_docs_path.is_empty()) { + DocTools docs; + Error err; + + Vector paths = get_files_with_extension(gdscript_docs_path, "gd"); + ERR_FAIL_COND_V_MSG(paths.size() == 0, false, "Couldn't find any GDScript files under the given directory: " + gdscript_docs_path); + + for (const String &path : paths) { + Ref gdscript = ResourceLoader::load(path); + for (const DocData::ClassDoc &class_doc : gdscript->get_documentation()) { + docs.add_doc(class_doc); + } + } + + if (doc_tool_path == ".") { + doc_tool_path = "./docs"; + } + + Ref da = DirAccess::create_for_path(doc_tool_path); + err = da->make_dir_recursive(doc_tool_path); + ERR_FAIL_COND_V_MSG(err != OK, false, "Error: Can't create GDScript docs directory: " + doc_tool_path + ": " + itos(err)); + + HashMap doc_data_classes; + err = docs.save_classes(doc_tool_path, doc_data_classes, false); + ERR_FAIL_COND_V_MSG(err != OK, false, "Error saving GDScript docs:" + itos(err)); + + OS::get_singleton()->set_exit_code(EXIT_SUCCESS); + return false; + } +#endif // MODULE_GDSCRIPT_ENABLED + if (!doc_tool_path.is_empty()) { // Needed to instance editor-only classes for their default values Engine::get_singleton()->set_editor_hint(true); diff --git a/modules/gdscript/editor/gdscript_docgen.cpp b/modules/gdscript/editor/gdscript_docgen.cpp index 451af996ecba..ce64d7974771 100644 --- a/modules/gdscript/editor/gdscript_docgen.cpp +++ b/modules/gdscript/editor/gdscript_docgen.cpp @@ -239,6 +239,7 @@ void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_c DocData::ConstantDoc const_doc; const_doc.name = val.identifier->name; const_doc.value = String(Variant(val.value)); + const_doc.is_value_valid = true; const_doc.description = val.doc_description; const_doc.enumeration = name;