#include "doc_html.h" #include "scripting/api/objs/enums.h" #include namespace scripting { namespace { SCP_string link_enum_group_references(const SCP_string& text) { SCP_string out; SCP_string token; for (size_t i = 0; i <= text.size(); ++i) { const auto ch = (i < text.size()) ? text[i] : ' '; if (std::isalnum(static_cast(ch)) || ch == '_' || ch == '*') { token.push_back(ch); continue; } if (!token.empty()) { SCP_string lookup = token; while (!lookup.empty() && (lookup.back() == '*' || lookup.back() == ',' || lookup.back() == '.' || lookup.back() == ')' || lookup.back() == ']')) { lookup.pop_back(); } auto group = api::get_enum_group_info(lookup.c_str()); if (group) { out += "id; out += "\">"; out += token; out += ""; } else { out += token; } token.clear(); } if (i < text.size()) { out.push_back(ch); } } return out; } void ade_output_toc(FILE* fp, const std::unique_ptr& el) { Assert(fp != nullptr); Assert(el != nullptr); // WMC - sanity checking if (el->name.empty() && el->shortName.empty()) { Warning(LOCATION, "Found ade_table_entry with no name or shortname"); return; } fputs("
", fp); if (el->name.empty()) { fprintf(fp, "%s", el->shortName.c_str(), el->shortName.c_str()); } else { fprintf(fp, "%s", el->name.c_str(), el->name.c_str()); if (!el->shortName.empty()) fprintf(fp, " (%s)", el->shortName.c_str()); } fputs("", fp); if (!el->description.empty()) { auto description = link_enum_group_references(el->description); fprintf(fp, " - %s\n", description.c_str()); } fputs("
\n", fp); } void OutputToc(FILE* fp, const ScriptingDocumentation& doc) { //***TOC: Libraries fputs("
Libraries
\n", fp); for (const auto& el : doc.elements) { if (el->type != ElementType::Library) { continue; } ade_output_toc(fp, el); } //***TOC: Objects fputs("
Types
\n", fp); for (const auto& el : doc.elements) { if (el->type != ElementType::Class) { continue; } ade_output_toc(fp, el); } //***TOC: Enumerations fputs("
Enumerations
", fp); //***End TOC fputs("

", fp); } SCP_string element_name(const std::unique_ptr& el) { if (!el->name.empty()) { return el->name; } return el->shortName; } void ade_output_type_link(FILE* fp, const ade_type_info& type_info) { switch (type_info.getType()) { case ade_type_info_type::Empty: fputs("nothing", fp); break; case ade_type_info_type::Simple: if (ade_is_internal_type(type_info.getIdentifier())) { fputs(type_info.getIdentifier(), fp); } else { fprintf(fp, "%s", type_info.getIdentifier(), type_info.getIdentifier()); } break; case ade_type_info_type::Tuple: { bool first = true; for (const auto& type : type_info.elements()) { if (!first) { fprintf(fp, ", "); } first = false; ade_output_type_link(fp, type); } break; } case ade_type_info_type::Array: fputs("{ ", fp); ade_output_type_link(fp, type_info.arrayType()); fputs(" ... }", fp); break; case ade_type_info_type::Map: fputs("{ ", fp); ade_output_type_link(fp, type_info.elements()[0]); fputs(" -> ", fp); ade_output_type_link(fp, type_info.elements()[1]); fputs(" ... }", fp); break; case ade_type_info_type::Iterator: fputs("iterator< ", fp); ade_output_type_link(fp, type_info.arrayType()); fputs(" >", fp); break; case ade_type_info_type::Alternative: { bool first = true; for (const auto& type : type_info.elements()) { if (!first) { fputs(" | ", fp); } first = false; ade_output_type_link(fp, type); } break; } case ade_type_info_type::Function: { const auto& elements = type_info.elements(); if (elements.size() == 1) { fputs("function() -> ", fp); } else { fputs("function(", fp); bool first = true; for (auto iter = elements.begin() + 1; iter != elements.end(); ++iter) { if (!first) { fputs(", ", fp); } first = false; ade_output_type_link(fp, *iter); fprintf(fp, " %s", iter->getName().c_str()); } fputs(") -> ", fp); } ade_output_type_link(fp, elements.front()); break; } case ade_type_info_type::Generic: { const auto& elements = type_info.elements(); ade_output_type_link(fp, elements.front()); if (elements.size() > 1) { fputs("<", fp); bool first = true; for (auto iter = elements.begin() + 1; iter != elements.end(); ++iter) { if (!first) { fputs(", ", fp); } first = false; ade_output_type_link(fp, *iter); } fputs(">", fp); } break; } case ade_type_info_type::Varargs: { ade_output_type_link(fp, type_info.elements().front()); fputs("...", fp); break; } default: UNREACHABLE("Unhandled type %d!", static_cast(type_info.getType())); break; } } void output_argument_list(FILE* fp, const DocumentationElementFunction::argument_list& overload) { if (overload.simple.empty()) { bool first = true; bool optional = false; for (const auto& arg : overload.arguments) { if (!first) { fputs(", ", fp); } first = false; if (arg.optional && !optional) { fputs("[", fp); optional = true; } ade_output_type_link(fp, arg.type); if (!arg.name.empty()) { fprintf(fp, " %s", arg.name.c_str()); if (!arg.def_val.empty()) { fprintf(fp, " = %s", link_enum_group_references(arg.def_val).c_str()); } } if (!arg.comment.empty()) { fprintf(fp, " (%s)", link_enum_group_references(arg.comment).c_str()); } } if (optional) { fputs("]", fp); } } else { fprintf(fp, "%s", link_enum_group_references(overload.simple).c_str()); } } void OutputElement(FILE* fp, const std::unique_ptr& el, const DocumentationElement* parent, const DocumentationElement* parentParent) { if (el->name.empty() && el->shortName.empty()) { Warning(LOCATION, "Found ade_table_entry with no name or shortname"); return; } bool skip_this = false; // WMC - Hack if (parent != nullptr) { for (const auto& child : el->children) { if (child->name == "__indexer") { OutputElement(fp, child, el.get(), parent); skip_this = true; break; } } } //***Begin entry if (!skip_this) { fputs("
", fp); switch (el->type) { case ElementType::Library: case ElementType::Class: { //***Name (ShortName) if (parent == nullptr) { fprintf(fp, "
", element_name(el).c_str()); } if (el->name.empty()) { if (el->type == ElementType::Class) { const auto classEl = static_cast(el.get()); if (classEl->superClass.empty()) fprintf(fp, "

%s

\n", classEl->shortName.c_str()); else { fprintf(fp, "

%s:%s

\n", classEl->superClass.c_str(), classEl->superClass.c_str(), classEl->superClass.c_str()); } } else { fprintf(fp, "

%s

\n", el->shortName.c_str()); } } else { fprintf(fp, "

%s", el->name.c_str()); if (!el->shortName.empty()) fprintf(fp, " (%s)", el->shortName.c_str()); if (el->type == ElementType::Class) { const auto classEl = static_cast(el.get()); if (!classEl->superClass.empty()) { fprintf(fp, ":%s", classEl->superClass.c_str(), classEl->superClass.c_str()); } } fputs("

\n", fp); } fputs("
\n", fp); //***Description if (!el->description.empty()) { auto description = link_enum_group_references(el->description); fprintf(fp, "
%s
\n", description.c_str()); } break; } case ElementType::Operator: case ElementType::Function: { const auto funcEl = static_cast(el.get()); //***Name(ShortName)(Arguments) for (const auto& overload : funcEl->overloads) { fputs("
", fp); fputs("", fp); if (!funcEl->returnType.isEmpty()) ade_output_type_link(fp, funcEl->returnType); else fputs("nil", fp); fputs(" ", fp); const string_conv* op = nullptr; if (el->name.empty()) { fprintf(fp, "%s", el->shortName.c_str()); } else { op = ade_get_operator(el->name.c_str()); // WMC - Do we have an operator? if (op == nullptr) { fprintf(fp, "%s", el->name.c_str()); if (!el->shortName.empty()) { const auto op2 = ade_get_operator(el->shortName.c_str()); if (op2 == nullptr) fprintf(fp, "(%s)", el->shortName.c_str()); else fprintf(fp, "(%s)", op2->dest); } } else { // WMC - Hack if (parent != nullptr && !parent->name.empty() && parentParent != nullptr && el->name == "__indexer") { fprintf(fp, "%s%s", parent->name.c_str(), op->dest); } else fprintf(fp, "%s", op->dest); } } if (op == nullptr) { fputs("(", fp); output_argument_list(fp, overload); fputs(")\n", fp); } else { fputs(" ", fp); output_argument_list(fp, overload); } fputs("
\n", fp); } //***Description if (!el->description.empty()) { auto description = link_enum_group_references(el->description); fprintf(fp, "
%s
\n", description.c_str()); } if (el->deprecationVersion.isValid()) { if (!el->deprecationMessage.empty()) { fprintf(fp, "
Deprecated starting with version %d.%d.%d: %s
\n", el->deprecationVersion.major, el->deprecationVersion.minor, el->deprecationVersion.build, el->deprecationMessage.c_str()); } else { fprintf(fp, "
Deprecated starting with version %d.%d.%d.
\n", el->deprecationVersion.major, el->deprecationVersion.minor, el->deprecationVersion.build); } } //***Result: ReturnDescription if (!funcEl->returnDocumentation.empty()) { auto returns = link_enum_group_references(funcEl->returnDocumentation); fprintf(fp, "
Returns: %s
\n", returns.c_str()); } else { fputs("
Returns: Nothing
\n", fp); } break; } case ElementType::Property: { const auto propEl = static_cast(el.get()); //***Type Name(ShortName) fputs("
\n", fp); fputs("", fp); ade_output_type_link(fp, propEl->getterType); fputs(" ", fp); if (propEl->name.empty()) { fprintf(fp, "%s\n", propEl->shortName.c_str()); } else { fprintf(fp, "%s", propEl->name.c_str()); if (!propEl->shortName.empty()) fputs(propEl->shortName.c_str(), fp); fputs("\n", fp); } if (!propEl->setterType.isEmpty()) { fprintf(fp, " = "); ade_output_type_link(fp, propEl->setterType); } fputs("
\n", fp); //***Description if (!propEl->description.empty()) { auto description = link_enum_group_references(propEl->description); fprintf(fp, "
%s
\n", description.c_str()); } if (!propEl->returnDocumentation.empty()) { auto value = link_enum_group_references(propEl->returnDocumentation); fprintf(fp, "
Value: %s
\n", value.c_str()); } break; } default: Warning(LOCATION, "Unknown type '%d' passed to ade_table_entry::OutputMeta", static_cast(el->type)); break; } } fputs("
\n", fp); for (const auto& child : el->children) { if (parent == nullptr || child->name != "__indexer") OutputElement(fp, child, el.get(), parent); } fputs("
\n", fp); if (!skip_this) fputs("
\n", fp); } void OutputLuaMeta(FILE* fp, const ScriptingDocumentation& doc) { OutputToc(fp, doc); SCP_vector table_entries; //***Everything fputs("
\n", fp); for (const auto& el : doc.elements) { OutputElement(fp, el, nullptr, nullptr); } fputs("
\n", fp); } template static void output_hook_variable_list(FILE* fp, const Container& vars) { fputs("
", fp); for (const auto& param : vars) { fputs("
", fp); ade_output_type_link(fp, param.type); fprintf(fp, " %s
", param.name); fprintf(fp, "
Description: %s
", link_enum_group_references(param.description).c_str()); } fputs("
", fp); } template static void output_hook_conditions_list(FILE* fp, const Container& vars) { fputs("
", fp); for (const auto& param : vars) { fprintf(fp, "
%s
Description: %s
", param.first.c_str(), param.second->documentation.c_str()); } fputs("
", fp); } } // namespace void output_html_doc(const ScriptingDocumentation& doc, const SCP_string& filename) { FILE* fp = fopen(filename.c_str(), "w"); if (fp == nullptr) { return; } fprintf(fp, "\n\n\tScript Output - FSO v%s (%s)\n\n", FS_VERSION_FULL, doc.name.c_str()); fputs("", fp); fprintf(fp, "\t

Script Output - FSO v%s (%s)

\n", FS_VERSION_FULL, doc.name.c_str()); // Scripting languages links fputs("
", fp); //***Hooks fputs("

Conditional Hooks

", fp); fputs("
", fp); // Conditions fputs("
Conditions
", fp); for (const auto& conditions : doc.conditions) { fprintf(fp, "
%s
", conditions.c_str()); } // Actions fputs("
Actions
", fp); fputs("
", fp); for (const auto& hook : doc.actions) { fprintf(fp, "
%s
", hook.name.c_str()); if (!hook.description.empty()) { fprintf(fp, "
Description: %s", hook.description.c_str()); // Only write this for hooks with descriptions since this information is useless for legacy hooks if (hook.overridable) { fputs("
This hook is overridable", fp); } else { fputs("
This hook is not overridable", fp); } if (hook.deprecation) { if (hook.deprecation->level_hook == HookDeprecationOptions::DeprecationLevel::LEVEL_ERROR) { fprintf(fp, "
Removed starting with version %d.%d.%d.\n", hook.deprecation->deprecatedSince.major, hook.deprecation->deprecatedSince.minor, hook.deprecation->deprecatedSince.build); } else if (hook.deprecation->level_override == HookDeprecationOptions::DeprecationLevel::LEVEL_ERROR) { fprintf(fp, "
Deprecated (+Override removed) starting with version %d.%d.%d.\n", hook.deprecation->deprecatedSince.major, hook.deprecation->deprecatedSince.minor, hook.deprecation->deprecatedSince.build); } else { fprintf(fp, "
Deprecated starting with version %d.%d.%d.\n", hook.deprecation->deprecatedSince.major, hook.deprecation->deprecatedSince.minor, hook.deprecation->deprecatedSince.build); } } if (!hook.parameters.empty()) { fputs("
Hook Variables:", fp); output_hook_variable_list(fp, hook.parameters); } if (!hook.conditions.empty()) { fputs("Hook-specific Conditions:", fp); output_hook_conditions_list(fp, hook.conditions); } fputs("
", fp); } } fputs("
", fp); fputs("
Global Hook Variables: (set globally independent of a specific hook)
", fp); output_hook_variable_list(fp, doc.globalVariables); fputs("

", fp); // Languages fputs("
", fp); //***Version info fprintf(fp, "
Lua Version: %s
\n", LUA_RELEASE); fputs("
", fp); OutputLuaMeta(fp, doc); fputs("
", fp); //***Options fputs("
In Game Options
", fp); fputs("
", fp); for (const auto& option : doc.options) { fprintf(fp, "
%s
", option.title.c_str()); fprintf(fp, "
Key: %s
", option.key.c_str()); fprintf(fp, "
Description: %s
", option.description.c_str()); } //***Enumerations fprintf(fp, "

Enumerations

"); SCP_unordered_map> enum_groups; SCP_vector enum_group_order; for (const auto& enumeration : doc.enumerations) { auto key = enumeration.group_id.empty() ? SCP_string("ungrouped") : enumeration.group_id; if (enum_groups.find(key) == enum_groups.end()) { enum_group_order.push_back(key); } enum_groups[key].push_back(&enumeration); } for (const auto& group_id : enum_group_order) { const auto* first_enum = enum_groups[group_id].front(); const auto& group_title = first_enum->group_title.empty() ? SCP_string("Ungrouped Enumerations") : first_enum->group_title; const auto& group_description = first_enum->group_description.empty() ? SCP_string("No group description found. Add a matching prefix entry to get_enum_group_info() in enums.cpp.") : first_enum->group_description; fprintf(fp, "

%s

", group_id.c_str(), group_title.c_str()); fprintf(fp, "
%s
", group_description.c_str()); fputs("
", fp); for (const auto* enumeration : enum_groups[group_id]) { if (!enumeration->description.empty()) { fprintf(fp, "
%s - %s
", enumeration->name.c_str(), enumeration->description.c_str()); } else { fprintf(fp, "
%s
", enumeration->name.c_str()); } } fputs("
", fp); } fputs("
", fp); fclose(fp); } } // namespace scripting