TmpMenu/scripts/doc_gen.py
2024-02-24 11:10:58 +01:00

725 lines
22 KiB
Python

import os
from enum import Enum
src_folder = "../src/"
lua_api_comment_identifier = "lua api"
lua_api_comment_separator = ":"
tables = {}
classes = {}
functions = {}
class DocKind(Enum):
Table = "table"
Class = "class"
Field = "field"
Constructor = "constructor"
Function = "function"
Tabs = "tabs"
Infraction = "infraction"
class Table:
def __init__(self, name, fields, functions, description):
self.name = name.strip()
self.fields = fields
self.functions = functions
self.description = description
def __str__(self):
s = f"# Table: {self.name}\n"
s += "\n"
if len(self.description) > 0:
s += f"{self.description}\n"
s += "\n"
if len(self.fields) > 0:
s += f"## Fields ({len(self.fields)})\n"
s += "\n"
self.check_for_duplicate_fields_names()
for field in self.fields:
s += field.print_markdown()
if len(self.functions) > 0:
s += f"## Functions ({len(self.functions)})\n"
s += "\n"
for func in self.functions:
s += func.print_markdown(f"{self.name}.")
s += "\n"
return s
def check_for_duplicate_fields_names(self):
seen = set()
duplicates = [x for x in self.fields if x.name in seen or seen.add(x.name)]
if len(duplicates) > 0:
print("Error while building lua doc. Duplicate field names:")
for dup in duplicates:
print(dup)
exit(1)
class Class:
def __init__(self, name, inheritance, fields, constructors, functions, description):
self.name = name.strip()
self.inheritance = inheritance
self.fields = fields
self.constructors = constructors
self.functions = functions
self.description = description
def __str__(self):
s = f"# Class: {self.name}\n"
s += "\n"
if len(self.inheritance) > 0:
inherited_class_names = ", ".join(self.inheritance)
s += f"## Inherit from {len(self.inheritance)} class: {inherited_class_names}\n"
s += "\n"
if len(self.description) > 0:
s += f"{self.description}\n"
s += "\n"
if len(self.fields) > 0:
s += f"## Fields ({len(self.fields)})\n"
s += "\n"
self.check_for_duplicate_fields_names()
for field in self.fields:
s += field.print_markdown()
if len(self.constructors) > 0:
s += f"## Constructors ({len(self.constructors)})\n"
s += "\n"
for ctor in self.constructors:
s += ctor.print_markdown()
if len(self.functions) > 0:
s += f"## Functions ({len(self.functions)})\n"
s += "\n"
for func in self.functions:
s += func.print_markdown(f"{self.name}:")
s += "\n"
return s
def check_for_duplicate_fields_names(self):
seen = set()
duplicates = [x for x in self.fields if x.name in seen or seen.add(x.name)]
if len(duplicates) > 0:
print("Error while building lua doc. Duplicate field names:")
for dup in duplicates:
print(dup)
exit(1)
class Field:
def __init__(self, name, type_, description):
self.name = name.strip()
self.type_ = type_.strip()
self.description = description
def __str__(self):
s = f"Field: {self.name}\n"
s += f"Type: {self.type_}\n"
s += f"Description: {self.description.strip()}\n"
return s
def print_markdown(self):
s = ""
s += f"### `{self.name}`\n"
s += "\n"
if len(self.description) > 0:
s += f"{self.description}\n"
s += "\n"
if self.type_ is not None and len(self.type_) > 0:
s += f"- Type: `{self.type_}`\n"
s += "\n"
return s
class Constructor:
def __init__(self, parent, parameters, description):
self.parent = parent
self.parameters = parameters
self.description = description
def print_markdown(self):
s = ""
parameters_str = ", ".join(p.name for p in self.parameters)
s += f"### `new({parameters_str})`\n"
s += "\n"
if len(self.description) > 0:
s += f"{self.description}\n"
s += "\n"
if len(self.parameters) > 0:
s += f"- **Parameters:**\n"
for param in self.parameters:
s += f" - `{param.name}` ({param.type_})"
if len(param.description) > 0:
s += f": {param.description}\n"
else:
s += f"\n"
s += "\n"
s += f"**Example Usage:**\n"
s += "```lua\n"
s += f"myInstance = {self.parent.name}:new({parameters_str})\n"
s += "```\n"
s += "\n"
return s
class Parameter:
def __init__(self, name, type_, description):
self.name = name.strip()
self.type_ = type_.strip()
self.description = description
def __str__(self):
s = f"Parameter: {self.name}\n"
s += f"Type: {self.type_}\n"
s += f"Description: {self.description.strip()}\n"
return s
class Function:
def __init__(
self, name, parent, parameters, return_type, return_description, description
):
self.name = name.strip()
self.parent = parent
self.parameters = parameters
self.return_type = return_type
self.return_description = return_description
self.description = description
def __str__(self):
s = f"Function: {self.name}\n"
type_name = str(type(self.parent)).split(".")[1][:-2]
s += f"Parent: {self.parent.name} ({type_name})\n"
s += f"Parameters: {len(self.parameters)}\n"
i = 1
for param in self.parameters:
s += f"Parameter {i}\n"
s += str(param) + "\n"
i += 1
s += f"Return Type: {self.return_type}\n"
s += f"Return Description: {self.return_description}\n"
s += f"Description: {self.description}\n"
return s
def print_markdown(self, prefix):
s = ""
parameters_str = ", ".join(p.name for p in self.parameters)
s += f"### `{self.name}({parameters_str})`\n"
s += "\n"
if len(self.description) > 0:
s += f"{self.description}\n"
s += "\n"
if len(self.parameters) > 0:
s += f"- **Parameters:**\n"
for param in self.parameters:
s += f" - `{param.name}` ({param.type_})"
if len(param.description) > 0:
s += f": {param.description}\n"
else:
s += f"\n"
s += "\n"
if self.return_type is not None and len(self.return_type) > 0:
s += f"- **Returns:**\n"
s += f" - `{self.return_type}`: {self.return_description}\n"
s += "\n"
s += f"**Example Usage:**\n"
s += "```lua\n"
if self.return_type is not None and len(self.return_type) > 0:
s += self.return_type + " = "
if "Global Table" in prefix:
prefix = ""
s += f"{prefix}{self.name}({parameters_str})\n"
s += "```\n"
s += "\n"
return s
def make_table(table_name):
if table_name not in tables:
tables[table_name] = Table(table_name, [], [], "")
cur_table = tables[table_name]
return cur_table
def make_class(class_name):
if class_name not in classes:
classes[class_name] = Class(class_name, [], [], [], [], "")
cur_class = classes[class_name]
return cur_class
def is_comment_a_lua_api_doc_comment(text_lower):
return (
lua_api_comment_identifier in text_lower
and lua_api_comment_separator in text_lower
and "//" in text_lower
)
def parse_lua_api_doc(folder_path):
for root, dirs, files in os.walk(folder_path):
for file_name in files:
if os.path.splitext(file_name)[1].startswith((".c", ".h")):
file_path = os.path.join(root, file_name)
with open(file_path, "r") as file:
doc_kind = None
cur_table = None
cur_class = None
cur_function = None
cur_field = None
cur_constructor = None
for line in file:
line = line.strip()
line_lower = line.lower()
if is_comment_a_lua_api_doc_comment(line_lower):
doc_kind = DocKind(
line.split(lua_api_comment_separator, 1)[1]
.strip()
.lower()
)
if (
doc_kind is not DocKind.Tabs
and doc_kind is not DocKind.Infraction
):
continue
if doc_kind is not None and "//" in line:
match doc_kind:
case DocKind.Table:
cur_table = parse_table_doc(
cur_table, line, line_lower
)
case DocKind.Class:
cur_class = parse_class_doc(
cur_class, line, line_lower
)
case DocKind.Function:
(
cur_function,
cur_table,
cur_class,
) = parse_function_doc(
cur_function,
cur_table,
cur_class,
line,
line_lower,
)
case DocKind.Field:
(cur_field, cur_table, cur_class) = parse_field_doc(
cur_field,
cur_table,
cur_class,
line,
line_lower,
)
case DocKind.Constructor:
(
cur_constructor,
cur_class,
) = parse_constructor_doc(
cur_constructor,
cur_class,
line,
line_lower,
)
case DocKind.Tabs:
parse_tabs_doc(file)
case DocKind.Infraction:
parse_infraction_doc(file)
case _:
# print("unsupported doc kind: " + str(doc_kind))
pass
else:
doc_kind = None
def parse_table_doc(cur_table, line, line_lower):
if is_lua_doc_comment_startswith(line_lower, "name"):
table_name = line.split(lua_api_comment_separator, 1)[1].strip()
cur_table = make_table(table_name)
else:
if len(cur_table.description) != 0:
cur_table.description += "\n"
cur_table.description += sanitize_description(line)
return cur_table
def parse_class_doc(cur_class, line, line_lower):
if is_lua_doc_comment_startswith(line_lower, "name"):
class_name = line.split(lua_api_comment_separator, 1)[1].strip()
cur_class = make_class(class_name)
elif is_lua_doc_comment_startswith(line_lower, "inherit"):
inherited_class_name = line.split(lua_api_comment_separator, 1)[1].strip()
cur_class.inheritance.append(inherited_class_name)
else:
if len(cur_class.description) != 0:
cur_class.description += "\n"
cur_class.description += sanitize_description(line)
return cur_class
def parse_function_doc(cur_function, cur_table, cur_class, line, line_lower):
if (
is_lua_doc_comment_startswith(line_lower, "table")
and lua_api_comment_separator in line_lower
):
table_name = line.split(lua_api_comment_separator, 1)[1].strip()
cur_table = make_table(table_name)
cur_function = Function("Didnt get name yet", cur_table, [], None, "", "")
cur_table.functions.append(cur_function)
elif (
is_lua_doc_comment_startswith(line_lower, "class")
and lua_api_comment_separator in line_lower
):
class_name = line.split(lua_api_comment_separator, 1)[1].strip()
cur_class = make_class(class_name)
cur_function = Function("Didnt get name yet", cur_class, [], None, "", "")
cur_class.functions.append(cur_function)
elif (
is_lua_doc_comment_startswith(line_lower, "name")
and lua_api_comment_separator in line_lower
):
function_name = line.split(lua_api_comment_separator, 1)[1].strip()
cur_function.name = function_name
if function_name not in functions:
functions[function_name] = cur_function
elif (
is_lua_doc_comment_startswith(line_lower, "param")
and lua_api_comment_separator in line_lower
):
parameter = make_parameter_from_doc_line(line)
cur_function.parameters.append(parameter)
elif (
is_lua_doc_comment_startswith(line_lower, "return")
and lua_api_comment_separator in line_lower
):
return_info = line.split(lua_api_comment_separator, 2)
try:
cur_function.return_type = return_info[1].strip()
cur_function.return_description = return_info[2].strip()
except IndexError:
pass
else:
if len(cur_function.description) != 0:
cur_function.description += "\n"
cur_function.description += sanitize_description(line)
return cur_function, cur_table, cur_class
def parse_field_doc(cur_field, cur_table, cur_class, line, line_lower):
if (
is_lua_doc_comment_startswith(line_lower, "table")
and lua_api_comment_separator in line_lower
):
table_name = line.split(lua_api_comment_separator, 1)[1].strip()
cur_table = make_table(table_name)
cur_field = Field("Didnt get name yet", "", "")
cur_table.fields.append(cur_field)
elif (
is_lua_doc_comment_startswith(line_lower, "class")
and lua_api_comment_separator in line_lower
):
class_name = line.split(lua_api_comment_separator, 1)[1].strip()
cur_class = make_class(class_name)
cur_field = Field("Didnt get name yet", "", "")
cur_class.fields.append(cur_field)
elif (
is_lua_doc_comment_startswith(line_lower, "field")
and lua_api_comment_separator in line_lower
):
field_info = line.split(lua_api_comment_separator, 2)
cur_field.name = field_info[1].strip()
cur_field.type_ = field_info[2].strip()
else:
if len(cur_field.description) != 0:
cur_field.description += "\n"
if line.startswith("// "):
line = line[3:]
cur_field.description += sanitize_description(line)
return cur_field, cur_table, cur_class
def parse_constructor_doc(cur_constructor, cur_class, line, line_lower):
if (
is_lua_doc_comment_startswith(line_lower, "class")
and lua_api_comment_separator in line_lower
):
class_name = line.split(lua_api_comment_separator, 1)[1].strip()
cur_class = make_class(class_name)
cur_constructor = Constructor(cur_class, [], "")
cur_class.constructors.append(cur_constructor)
elif (
is_lua_doc_comment_startswith(line_lower, "param")
and lua_api_comment_separator in line_lower
):
parameter = make_parameter_from_doc_line(line)
cur_constructor.parameters.append(parameter)
else:
if len(cur_constructor.description) != 0:
cur_constructor.description += "\n"
cur_constructor.description += sanitize_description(line)
return cur_constructor, cur_class
tabs_enum = []
def parse_tabs_doc(file):
start_parsing = False
for line in file:
if "enum class" in line.lower():
start_parsing = True
continue
if start_parsing:
if "};" in line.lower():
return
if "{" == line.lower().strip():
continue
if "//" in line.lower():
continue
if "" == line.lower().strip():
continue
else:
tabs_enum.append(line.replace(",", "").strip())
infraction_enum = []
def parse_infraction_doc(file):
start_parsing = False
for line in file:
if "enum class" in line.lower():
start_parsing = True
continue
if start_parsing:
if "};" in line.lower():
return
if "{" == line.lower().strip():
continue
if "//" in line.lower():
continue
if "" == line.lower().strip():
continue
else:
infraction_enum.append(line.replace(",", "").strip())
def make_parameter_from_doc_line(line):
param_info = line.split(lua_api_comment_separator, 3)[1:]
param_name = param_type = param_desc = ""
try:
param_name = param_info[0].strip()
param_type = param_info[1].strip()
param_desc = param_info[2].strip()
except IndexError:
pass
return Parameter(param_name, param_type, param_desc)
def sanitize_description(line):
if line.startswith("// ") and line[3] != " ":
line = line[3:]
if line.startswith("//"):
line = line[2:]
return line.rstrip()
def is_lua_doc_comment_startswith(line_lower, starts_with_text):
return line_lower.replace("//", "").strip().startswith(starts_with_text)
parse_lua_api_doc(src_folder)
try:
os.makedirs("../docs/lua/tables/")
except:
pass
for table_name, table in tables.items():
file_name = f"../docs/lua/tables/{table_name}.md"
if os.path.exists(file_name):
os.remove(file_name)
f = open(file_name, "ba")
f.write(bytes(str(table), "UTF8"))
f.close()
tabs_file_name = f"../docs/lua/tabs.md"
if os.path.exists(tabs_file_name):
os.remove(tabs_file_name)
f = open(tabs_file_name, "a")
f.write(
"""# Tabs
All the tabs from the menu are listed below, used as parameter for adding gui elements to them.
**Example Usage:**
```lua
missionsTab = gui.get_tab("GUI_TAB_MISSIONS")
missionsTab:add_button("Click me", function ()
log.info("You clicked!")
end)
```
For a complete list of available gui functions, please refer to the tab class documentation and the gui table documentation.
"""
)
f.write(f"## Tab Count: {len(tabs_enum)}\n\n")
# Minus the first, because it's the `NONE` tab, minus the last one because it's for runtime defined tabs.
for i in range(1, len(tabs_enum) - 1):
f.write("### `GUI_TAB_" + tabs_enum[i] + "`\n")
f.close()
infraction_file_name = f"../docs/lua/infraction.md"
if os.path.exists(infraction_file_name):
os.remove(infraction_file_name)
f = open(infraction_file_name, "a")
f.write(
"""# Infraction
All the infraction from the menu are listed below, used as parameter for adding an infraction to a given player, for flagging them as modder.
**Example Usage:**
```lua
network.flag_player_as_modder(player_index, infraction.CUSTOM_REASON, "My custom reason on why the player is flagged as a modder")
```
"""
)
f.write(f"## Infraction Count: {len(infraction_enum)}\n\n")
for i in range(0, len(infraction_enum)):
f.write("### `" + infraction_enum[i] + "`\n")
f.close()
try:
os.makedirs("../docs/lua/classes/")
except:
pass
for class_name, class_ in classes.items():
file_name = f"../docs/lua/classes/{class_name}.md"
if os.path.exists(file_name):
os.remove(file_name)
f = open(file_name, "ba")
f.write(bytes(str(class_), "UTF8"))
f.close()
commands_file_name = f"../docs/lua/commands.md"
if os.path.exists(commands_file_name):
os.remove(commands_file_name)
f = open(commands_file_name, "a")
f.write(
"""# Commands
All the current commands from the menu are listed below.
**Example Usage through Lua:**
```lua
command.call("spawn", {joaat("adder")})
command.call_player(somePlayerIndex, "spawn", {joaat("adder")})
```
For a complete list of available command functions, please refer to the command table documentation.
"""
)
commands = []
with open("../docs/lua/commands_dump.txt", "r") as file:
for line in file:
cmd = line.split("|", 1)[1].strip().split("|")
commands.append(cmd)
f.write(f"## Command Count: {len(commands)}\n\n")
for cmd in commands:
name = cmd[0]
label = cmd[1]
desc = cmd[2]
arg_count = cmd[3]
f.write(f"### {name}\n")
f.write(f"{desc}\n")
f.write(f"Arg Count: {arg_count}\n")
f.write("\n")
f.close()