mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-08 17:48:46 -10:00
I added a pretty generic container structure for the scripting documentation when adding JSON output. However, I did not use that new structure for the HTML output since that was just too much work. This moves the HTML output over to the new structure so now the scripting API handling code is independent from how the resulting documentation is generated. I also moved that code out of the main scripting code which removes quite a lot of clutter in those files. This is mostly a straight port of the old code with no significant changes (except the changes to make it use the new data).
500 lines
12 KiB
C++
500 lines
12 KiB
C++
#include "doc_html.h"
|
|
|
|
namespace scripting {
|
|
namespace {
|
|
|
|
void ade_output_toc(FILE* fp, const std::unique_ptr<DocumentationElement>& 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("<dd>", fp);
|
|
|
|
if (el->name.empty()) {
|
|
fprintf(fp, "<a href=\"#%s\">%s", el->shortName.c_str(), el->shortName.c_str());
|
|
} else {
|
|
fprintf(fp, "<a href=\"#%s\">%s", el->name.c_str(), el->name.c_str());
|
|
if (!el->shortName.empty())
|
|
fprintf(fp, " (%s)", el->shortName.c_str());
|
|
}
|
|
fputs("</a>", fp);
|
|
|
|
if (!el->description.empty())
|
|
fprintf(fp, " - %s\n", el->description.c_str());
|
|
|
|
fputs("</dd>\n", fp);
|
|
}
|
|
|
|
void OutputToc(FILE* fp, const ScriptingDocumentation& doc)
|
|
{
|
|
//***TOC: Libraries
|
|
fputs("<dt><b>Libraries</b></dt>\n", fp);
|
|
for (const auto& el : doc.elements) {
|
|
if (el->type != ElementType::Library) {
|
|
continue;
|
|
}
|
|
|
|
ade_output_toc(fp, el);
|
|
}
|
|
|
|
//***TOC: Objects
|
|
fputs("<dt><b>Types</b></dt>\n", fp);
|
|
for (const auto& el : doc.elements) {
|
|
if (el->type != ElementType::Class) {
|
|
continue;
|
|
}
|
|
|
|
ade_output_toc(fp, el);
|
|
}
|
|
|
|
//***TOC: Enumerations
|
|
fputs("<dt><b><a href=\"#Enumerations\">Enumerations</a></b></dt>", fp);
|
|
|
|
//***End TOC
|
|
fputs("</dl><br/><br/>", fp);
|
|
}
|
|
|
|
SCP_string element_name(const std::unique_ptr<DocumentationElement>& 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, "<a href=\"#%s\">%s</a>", 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());
|
|
}
|
|
}
|
|
}
|
|
|
|
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, " <i>%s</i>", arg.name.c_str());
|
|
|
|
if (!arg.def_val.empty()) {
|
|
fprintf(fp, " = <i>%s</i>", arg.def_val.c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
if (optional) {
|
|
fputs("]", fp);
|
|
}
|
|
} else {
|
|
fprintf(fp, "<i>%s</i>", overload.simple.c_str());
|
|
}
|
|
}
|
|
|
|
void OutputElement(FILE* fp,
|
|
const std::unique_ptr<DocumentationElement>& 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("<dd><dl>", fp);
|
|
|
|
switch (el->type) {
|
|
case ElementType::Library:
|
|
case ElementType::Class: {
|
|
//***Name (ShortName)
|
|
if (parent == nullptr) {
|
|
fprintf(fp, "<dt id=\"%s\">", element_name(el).c_str());
|
|
}
|
|
if (el->name.empty()) {
|
|
if (el->type == ElementType::Class) {
|
|
const auto classEl = static_cast<DocumentationElementClass*>(el.get());
|
|
if (classEl->superClass.empty())
|
|
fprintf(fp, "<h2>%s</h2>\n", classEl->shortName.c_str());
|
|
else {
|
|
fprintf(fp,
|
|
"<h2>%s:<a href=\"#%s\">%s</a></h2>\n",
|
|
classEl->superClass.c_str(),
|
|
classEl->superClass.c_str(),
|
|
classEl->superClass.c_str());
|
|
}
|
|
} else {
|
|
fprintf(fp, "<h2>%s</h2>\n", el->shortName.c_str());
|
|
}
|
|
} else {
|
|
fprintf(fp, "<h2>%s", el->name.c_str());
|
|
|
|
if (!el->shortName.empty())
|
|
fprintf(fp, " (%s)", el->shortName.c_str());
|
|
|
|
fputs("</h2>\n", fp);
|
|
|
|
if (el->type == ElementType::Class) {
|
|
const auto classEl = static_cast<DocumentationElementClass*>(el.get());
|
|
|
|
if (!classEl->superClass.empty()) {
|
|
fprintf(fp,
|
|
":<a href=\"#%s\">%s</a>",
|
|
classEl->superClass.c_str(),
|
|
classEl->superClass.c_str());
|
|
}
|
|
}
|
|
}
|
|
fputs("</dt>\n", fp);
|
|
|
|
//***Description
|
|
if (!el->description.empty()) {
|
|
fprintf(fp, "<dd>%s</dd>\n", el->description.c_str());
|
|
}
|
|
break;
|
|
}
|
|
case ElementType::Operator:
|
|
case ElementType::Function: {
|
|
const auto funcEl = static_cast<DocumentationElementFunction*>(el.get());
|
|
|
|
//***Name(ShortName)(Arguments)
|
|
for (const auto& overload : funcEl->overloads) {
|
|
fputs("<dt>", fp);
|
|
|
|
fputs("<i>", fp);
|
|
if (!funcEl->returnType.isEmpty())
|
|
ade_output_type_link(fp, funcEl->returnType);
|
|
else
|
|
fputs("nil", fp);
|
|
fputs(" </i>", fp);
|
|
|
|
const string_conv* op = nullptr;
|
|
if (el->name.empty()) {
|
|
fprintf(fp, "<b>%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, "<b>%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, "<b>%s%s", parent->name.c_str(), op->dest);
|
|
} else
|
|
fprintf(fp, "<b>%s", op->dest);
|
|
}
|
|
}
|
|
|
|
if (op == nullptr) {
|
|
fputs("(</b>", fp);
|
|
output_argument_list(fp, overload);
|
|
fputs("<b>)</b>\n", fp);
|
|
} else {
|
|
fputs("</b> ", fp);
|
|
output_argument_list(fp, overload);
|
|
}
|
|
fputs("</dt>\n", fp);
|
|
}
|
|
|
|
//***Description
|
|
if (!el->description.empty()) {
|
|
fprintf(fp, "<dd>%s</dd>\n", el->description.c_str());
|
|
}
|
|
|
|
if (el->deprecationVersion.isValid()) {
|
|
if (!el->deprecationMessage.empty()) {
|
|
fprintf(fp,
|
|
"<dd><b>Deprecated starting with version %d.%d.%d:</b> %s</dd>\n",
|
|
el->deprecationVersion.major,
|
|
el->deprecationVersion.minor,
|
|
el->deprecationVersion.build,
|
|
el->deprecationMessage.c_str());
|
|
} else {
|
|
fprintf(fp,
|
|
"<dd><b>Deprecated starting with version %d.%d.%d.</b></dd>\n",
|
|
el->deprecationVersion.major,
|
|
el->deprecationVersion.minor,
|
|
el->deprecationVersion.build);
|
|
}
|
|
}
|
|
|
|
//***Result: ReturnDescription
|
|
if (!funcEl->returnDocumentation.empty()) {
|
|
fprintf(fp, "<dd><b>Returns:</b> %s</dd>\n", funcEl->returnDocumentation.c_str());
|
|
} else {
|
|
fputs("<dd><b>Returns:</b> Nothing</dd>\n", fp);
|
|
}
|
|
break;
|
|
}
|
|
case ElementType::Property: {
|
|
const auto propEl = static_cast<DocumentationElementProperty*>(el.get());
|
|
|
|
//***Type Name(ShortName)
|
|
fputs("<dt>\n", fp);
|
|
fputs("<i>", fp);
|
|
ade_output_type_link(fp, propEl->getterType);
|
|
fputs("</i> ", fp);
|
|
|
|
if (propEl->name.empty()) {
|
|
fprintf(fp, "<b>%s</b>\n", propEl->shortName.c_str());
|
|
} else {
|
|
fprintf(fp, "<b>%s", propEl->name.c_str());
|
|
if (!propEl->shortName.empty())
|
|
fputs(propEl->shortName.c_str(), fp);
|
|
fputs("</b>\n", fp);
|
|
}
|
|
if (!propEl->setterType.empty())
|
|
fprintf(fp, " = <i>%s</i>", propEl->setterType.c_str());
|
|
fputs("</dt>\n", fp);
|
|
|
|
//***Description
|
|
if (!propEl->description.empty())
|
|
fprintf(fp, "<dd>%s</dd>\n", propEl->description.c_str());
|
|
|
|
//***Also settable with: Arguments
|
|
if (!propEl->returnDocumentation.empty())
|
|
fprintf(fp, "<dd><b>Value:</b> %s</b></dd>\n", propEl->returnDocumentation.c_str());
|
|
break;
|
|
}
|
|
default:
|
|
Warning(LOCATION, "Unknown type '%d' passed to ade_table_entry::OutputMeta", static_cast<int>(el->type));
|
|
break;
|
|
}
|
|
}
|
|
|
|
fputs("<dd><dl>\n", fp);
|
|
for (const auto& child : el->children) {
|
|
if (parent == nullptr || child->name != "__indexer")
|
|
OutputElement(fp, child, el.get(), parent);
|
|
}
|
|
fputs("</dl></dd>\n", fp);
|
|
|
|
if (!skip_this)
|
|
fputs("<br></dl></dd>\n", fp);
|
|
}
|
|
|
|
void OutputLuaMeta(FILE* fp, const ScriptingDocumentation& doc)
|
|
{
|
|
OutputToc(fp, doc);
|
|
|
|
SCP_vector<ade_table_entry*> table_entries;
|
|
|
|
//***Everything
|
|
fputs("<dl>\n", fp);
|
|
for (const auto& el : doc.elements) {
|
|
OutputElement(fp, el, nullptr, nullptr);
|
|
}
|
|
fputs("</dl>\n", fp);
|
|
}
|
|
|
|
template <typename Container>
|
|
static void output_hook_variable_list(FILE* fp, const Container& vars)
|
|
{
|
|
fputs("<dl>", fp);
|
|
for (const auto& param : vars) {
|
|
fputs("<dt>", fp);
|
|
ade_output_type_link(fp, param.type);
|
|
fprintf(fp, " <i>%s</i></dt>", param.name);
|
|
fprintf(fp, "<dd><b>Description:</b> %s</dt>", param.description);
|
|
}
|
|
fputs("</dl>", 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,
|
|
"<html>\n<head>\n\t<title>Script Output - FSO v%s (%s)</title>\n</head>\n",
|
|
FS_VERSION_FULL,
|
|
doc.name.c_str());
|
|
fputs("<body>", fp);
|
|
fprintf(fp, "\t<h1>Script Output - FSO v%s (%s)</h1>\n", FS_VERSION_FULL, doc.name.c_str());
|
|
|
|
// Scripting languages links
|
|
fputs("<dl>", fp);
|
|
|
|
//***Hooks
|
|
fputs("<dt><h2>Conditional Hooks</h2></dt>", fp);
|
|
fputs("<dd><dl>", fp);
|
|
|
|
// Conditions
|
|
fputs("<dt><b>Conditions</b></dt>", fp);
|
|
for (const auto& conditions : doc.conditions) {
|
|
fprintf(fp, "<dd>%s</dd>", conditions.c_str());
|
|
}
|
|
|
|
// Actions
|
|
fputs("<dt><b>Actions</b></dt>", fp);
|
|
fputs("<dd><dl>", fp);
|
|
for (const auto& hook : doc.actions) {
|
|
fprintf(fp, "<dt><b>%s</b></dt>", hook.name.c_str());
|
|
if (!hook.description.empty()) {
|
|
fprintf(fp, "<dd><b>Description:</b> %s", hook.description.c_str());
|
|
// Only write this for hooks with descriptions since this information is useless for legacy hooks
|
|
if (hook.overridable) {
|
|
fputs("<br><i>This hook is overridable</i>", fp);
|
|
} else {
|
|
fputs("<br><i>This hook is <b>not</b> overridable</i>", fp);
|
|
}
|
|
if (!hook.parameters.empty()) {
|
|
fputs("<br><b>Hook Variables:</b>", fp);
|
|
output_hook_variable_list(fp, hook.parameters);
|
|
}
|
|
fputs("</dd>", fp);
|
|
}
|
|
}
|
|
fputs("</dl></dd>", fp);
|
|
|
|
fputs("<dt><b>Global Hook Variables:</b> (set globally independent of a specific hook)</dt>", fp);
|
|
output_hook_variable_list(fp, doc.globalVariables);
|
|
fputs("</dl></dd><br />", fp);
|
|
|
|
// Languages
|
|
fputs("<dl>", fp);
|
|
//***Version info
|
|
fprintf(fp, "<dd>Lua Version: %s</dd>\n", LUA_RELEASE);
|
|
|
|
fputs("<dd>", fp);
|
|
|
|
OutputLuaMeta(fp, doc);
|
|
|
|
fputs("</dd>", fp);
|
|
|
|
//***Enumerations
|
|
fprintf(fp, "<dt id=\"Enumerations\"><h2>Enumerations</h2></dt>");
|
|
for (const auto& enumeration : doc.enumerations) {
|
|
// Cyborg17 -- Omit the deprecated flag
|
|
if (enumeration.name == "VM_EXTERNAL_CAMERA_LOCKED") {
|
|
continue;
|
|
}
|
|
// WMC - For now, print to the file without the description.
|
|
fprintf(fp, "<dd><b>%s</b></dd>", enumeration.name.c_str());
|
|
}
|
|
fputs("</dl></body></html>", fp);
|
|
|
|
fclose(fp);
|
|
}
|
|
|
|
} // namespace scripting
|