#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,
"\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, "\tScript 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