Skip to content

Commit dc63f92

Browse files
committed
Merge branch 'master' of https://github.com/ElunaLuaEngine/Eluna
2 parents 6d4685b + d6623fd commit dc63f92

9 files changed

Lines changed: 940 additions & 798 deletions

File tree

docs/ElunaDoc/__main__.py

Lines changed: 169 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,126 +1,223 @@
11
import os
22
import shutil
33
import typing
4-
from jinja2 import Environment, FileSystemLoader
5-
from typedecorator import params, returns
6-
from ElunaDoc.parser import ClassParser, MethodDoc
74
import glob
85
import time
6+
import re
7+
from jinja2 import Environment, FileSystemLoader
8+
from typedecorator import params, returns
9+
10+
from ElunaDoc.parser import ClassParser
911

1012

1113
@returns([(str, typing.IO)])
1214
@params(search_path=str)
1315
def find_class_files(search_path):
14-
"""Find and open all files containing Eluna class methods in `search_path`.
15-
16-
:param search_path: the path to search for Eluna methods in
17-
:return: a list of all files containing Eluna methods, and the name of their respective classes
18-
"""
19-
# Get the current working dir and switch to the search path.
16+
"""Find and open all files containing Eluna class methods in `search_path`."""
2017
old_dir = os.getcwd()
2118
os.chdir(search_path)
22-
# Search for all files ending in "Methods.h", and exclude BigIntMethods.h
23-
method_file_names = [file_name for file_name in glob.glob('*Methods.h') if file_name != 'BigIntMethods.h']
24-
# Open each file.
25-
method_files = [open(file_name, 'r') for file_name in method_file_names]
26-
# Go back to where we were before.
19+
method_file_names = [file_name for file_name in glob.glob("*Methods.h") if file_name != "BigIntMethods.h"]
20+
method_files = [open(file_name, "r", encoding="utf-8") for file_name in method_file_names]
2721
os.chdir(old_dir)
2822
return method_files
2923

3024

3125
def make_renderer(template_path, link_parser_factory):
3226
"""Return a function that can be used to render Jinja2 templates from the `template_path` directory."""
33-
34-
# Set up jinja2 environment to load templates from the templates folder.
3527
env = Environment(loader=FileSystemLoader(template_path))
3628

37-
3829
def inner(template_name, output_path, level, **kwargs):
39-
env.filters['parse_links'], env.filters['parse_data_type'] = link_parser_factory(level)
30+
env.filters["parse_links"], env.filters["parse_data_type"] = link_parser_factory(level)
4031
template = env.get_template(template_name)
4132
static = make_static(level)
4233
root = make_root(level)
4334

44-
with open('build/' + output_path, 'w') as out:
35+
with open("build/" + output_path, "w", encoding="utf-8") as out:
4536
out.write(template.render(level=level, static=static, root=root, **kwargs))
4637

4738
return inner
4839

4940

5041
def make_static(level):
51-
return lambda file_name: ('../' * level) + 'static/' + file_name
42+
return lambda file_name: ("../" * level) + "static/" + file_name
5243

5344

5445
def make_root(level):
55-
return lambda file_name: ('../' * level) + file_name
46+
return lambda file_name: ("../" * level) + file_name
47+
5648

49+
# ---------------- Hooks.h parsing (events.<category>.<name>) ----------------
5750

58-
if __name__ == '__main__':
51+
_macro_start = re.compile(r"^\s*#define\s+([A-Z0-9_]+)_EVENTS_LIST\(X\)\s*\\\s*$")
52+
_macro_item = re.compile(r'^\s*X\(\s*([A-Z0-9_]+)\s*,\s*([0-9]+)\s*,\s*"([^"]+)"\s*\)\s*\\?\s*$')
53+
54+
# Capture: { "category", SomeEventsTable, CountOf(SomeEventsTable) },
55+
_hook_table_entry = re.compile(
56+
r'^\s*\{\s*"([^"]+)"\s*,\s*([A-Za-z0-9_]+)\s*,\s*CountOf\(\2\)\s*\}\s*,?\s*$'
57+
)
58+
59+
60+
def _table_name_to_macro_key(events_table_name: str) -> str:
61+
"""
62+
Converts 'InstanceEventsTable' -> 'instance'
63+
'GameObjectEventsTable' -> 'gameobject'
64+
'PacketEventsTable' -> 'packet'
65+
"""
66+
suffix = "EventsTable"
67+
if events_table_name.endswith(suffix):
68+
return events_table_name[: -len(suffix)].lower()
69+
return events_table_name.lower()
70+
71+
72+
def parse_macro_lists(hooks_h_path: str) -> dict[str, dict[str, dict]]:
73+
"""
74+
Return mapping per macro_category (derived from *_EVENTS_LIST macro name):
75+
{
76+
"spell": {
77+
"by_id": { 1: "on_cast", ... },
78+
"by_enum": { "SPELL_EVENT_ON_CAST": (1, "on_cast"), ... }
79+
},
80+
...
81+
}
82+
"""
83+
hooks: dict[str, dict[str, dict]] = {}
84+
current: str | None = None
85+
86+
with open(hooks_h_path, "r", encoding="utf-8") as f:
87+
for line in f:
88+
m = _macro_start.match(line)
89+
if m:
90+
current = m.group(1).lower() # e.g. SPELL -> "spell", INSTANCE -> "instance"
91+
hooks.setdefault(current, {"by_id": {}, "by_enum": {}})
92+
continue
93+
94+
if current:
95+
mi = _macro_item.match(line)
96+
if mi:
97+
enum_name = mi.group(1)
98+
id_value = int(mi.group(2))
99+
lua_name = mi.group(3)
100+
hooks[current]["by_id"][id_value] = lua_name
101+
hooks[current]["by_enum"][enum_name] = (id_value, lua_name)
102+
continue
103+
104+
# End macro block when we hit a line not continuing with '\'
105+
if not line.rstrip().endswith("\\"):
106+
current = None
107+
108+
return hooks
109+
110+
111+
def build_exported_hook_map(hooks_h_path: str) -> dict[str, dict]:
112+
"""
113+
Builds a map keyed by exported Lua category from HookTypeTable.
114+
115+
IMPORTANT: the macro key is derived from the backing EventsTable name, not the exported category.
116+
Example:
117+
{ "map", InstanceEventsTable, CountOf(InstanceEventsTable) }
118+
-> macro key = "instance" -> INSTANCE_EVENTS_LIST
119+
"""
120+
macro_lists = parse_macro_lists(hooks_h_path)
121+
122+
exported: dict[str, dict] = {}
123+
in_hook_table = False
124+
125+
with open(hooks_h_path, "r", encoding="utf-8") as f:
126+
for line in f:
127+
if "static constexpr HookStorage HookTypeTable" in line:
128+
in_hook_table = True
129+
continue
130+
if in_hook_table and "};" in line:
131+
in_hook_table = False
132+
continue
133+
if not in_hook_table:
134+
continue
135+
136+
m = _hook_table_entry.match(line)
137+
if not m:
138+
continue
139+
140+
exported_category = m.group(1) # e.g. "map"
141+
events_table_name = m.group(2) # e.g. "InstanceEventsTable"
142+
143+
macro_key = _table_name_to_macro_key(events_table_name) # e.g. "instance"
144+
table_map = macro_lists.get(macro_key)
145+
146+
if not table_map:
147+
exported[exported_category] = {"by_id": {}, "by_enum": {}}
148+
print(
149+
f"[docs] Warning: HookTypeTable exports '{exported_category}' ({events_table_name}) "
150+
f"but no matching *_EVENTS_LIST was found for key '{macro_key}'"
151+
)
152+
continue
153+
154+
exported[exported_category] = table_map
155+
156+
return exported
157+
158+
159+
# ---------------- Main ----------------
160+
161+
if __name__ == "__main__":
59162
# Recreate the build folder and copy static files over.
60-
if os.path.exists('build'):
61-
shutil.rmtree('build')
62-
os.mkdir('build')
63-
shutil.copytree('ElunaDoc/static', 'build/static')
163+
if os.path.exists("build"):
164+
shutil.rmtree("build")
165+
os.mkdir("build")
166+
shutil.copytree("ElunaDoc/static", "build/static")
167+
168+
# Load hook globals (events.<category>.<name>) from Hooks.h
169+
hooks_path = os.path.normpath(os.path.join(os.path.dirname(__file__), "..", "..", "hooks", "Hooks.h"))
170+
print(f"Loading hook globals from: {hooks_path}")
171+
exported_hooks = build_exported_hook_map(hooks_path)
64172

65173
# Load up all files with methods we need to parse.
66-
# Hard-coded to the TC files for now. Will have to add core support later on.
67-
print('Finding Eluna method files...')
68-
class_files = find_class_files('../methods/TrinityCore/')
174+
print("Finding Eluna method files...")
175+
class_files = find_class_files("../methods/TrinityCore/")
69176

70177
# Parse all the method files.
71178
classes = []
72179
for f in class_files:
73-
print(f'Parsing file {f.name}...')
74-
classes.append(ClassParser.parse_file(f))
180+
print(f"Parsing file {f.name}...")
181+
classes.append(ClassParser.parse_file(f, exported_hooks=exported_hooks))
75182
f.close()
76183

77184
# Sort the classes so they are in the correct order in lists.
78185
classes.sort(key=lambda c: c.name)
79186

80187
def make_parsers(level):
81-
"""Returns a function that parses content for refs to other classes, methods, or enums,
82-
and automatically inserts the correct link.
83-
"""
84-
# Make lists of all class names and method names.
188+
"""Returns filters that parse refs to other classes/methods/enums and insert links."""
85189
class_names = []
86190
method_names = []
87191

88192
for class_ in classes:
89-
class_names.append('[' + class_.name + ']')
90-
193+
class_names.append("[" + class_.name + "]")
91194
for method in class_.methods:
92-
method_names.append('[' + class_.name + ':' + method.name + ']')
195+
method_names.append("[" + class_.name + ":" + method.name + "]")
93196

94197
def link_parser(content):
95-
# Replace all occurrencies of &Class:Function and then &Class with a link to given func or class
96-
198+
# Replace [Class:Method] and [Class] with links
97199
for name in method_names:
98-
# Take the [] off the front of the method's name.
99200
full_name = name[1:-1]
100-
# Split "Class:Method" into "Class" and "Method".
101-
class_name, method_name = full_name.split(':')
102-
url = '{}{}/{}.html'.format(('../' * level), class_name, method_name)
103-
# Replace occurrencies of &Class:Method with the url created
201+
class_name, method_name = full_name.split(":")
202+
url = "{}{}/{}.html".format(("../" * level), class_name, method_name)
104203
content = content.replace(name, '<a class="fn" href="{}">{}</a>'.format(url, full_name))
105204

106205
for name in class_names:
107-
# Take the [] off the front of the class's name.
108206
class_name = name[1:-1]
109-
url = '{}{}/index.html'.format(('../' * level), class_name)
110-
# Replace occurrencies of &Class:Method with the url created
207+
url = "{}{}/index.html".format(("../" * level), class_name)
111208
content = content.replace(name, '<a class="mod" href="{}">{}</a>'.format(url, class_name))
112209

113210
return content
114211

115212
# Links to the "Programming in Lua" documentation for each Lua type.
116213
lua_type_documentation = {
117-
'nil': 'http://www.lua.org/pil/2.1.html',
118-
'boolean': 'http://www.lua.org/pil/2.2.html',
119-
'number': 'http://www.lua.org/pil/2.3.html',
120-
'string': 'http://www.lua.org/pil/2.4.html',
121-
'table': 'http://www.lua.org/pil/2.5.html',
122-
'function': 'http://www.lua.org/pil/2.6.html',
123-
'...': 'http://www.lua.org/pil/5.2.html',
214+
"nil": "http://www.lua.org/pil/2.1.html",
215+
"boolean": "http://www.lua.org/pil/2.2.html",
216+
"number": "http://www.lua.org/pil/2.3.html",
217+
"string": "http://www.lua.org/pil/2.4.html",
218+
"table": "http://www.lua.org/pil/2.5.html",
219+
"function": "http://www.lua.org/pil/2.6.html",
220+
"...": "http://www.lua.org/pil/5.2.html",
124221
}
125222

126223
def data_type_parser(content):
@@ -132,44 +229,44 @@ def data_type_parser(content):
132229
# Otherwise try to build a link to the proper page.
133230
if content in class_names:
134231
class_name = content[1:-1]
135-
url = '{}{}/index.html'.format(('../' * level), class_name)
232+
url = "{}{}/index.html".format(("../" * level), class_name)
136233
return '<strong><a class="mod" href="{}">{}</a></strong>'.format(url, class_name)
137234

138235
# Case for enums to direct to a search on github
139236
enum_name = content[1:-1]
140-
url = 'https://github.com/ElunaLuaEngine/ElunaTrinityWotlk/search?l=cpp&q=%22enum+{}%22&type=Code&utf8=%E2%9C%93'.format(enum_name)
237+
url = (
238+
'https://github.com/ElunaLuaEngine/ElunaTrinityWotlk/search?l=cpp&q=%22enum+{}%22'
239+
"&type=Code&utf8=%E2%9C%93"
240+
).format(enum_name)
141241
return '<strong><a href="{}">{}</a></strong>'.format(url, enum_name)
142242

143-
# By default we just return the name without the [] around it
144-
return content[1:-1]
145-
146243
return link_parser, data_type_parser
147244

148245
# Create the render function with the template path and parser maker.
149-
render = make_renderer('ElunaDoc/templates', make_parsers)
246+
render = make_renderer("ElunaDoc/templates", make_parsers)
150247

151248
# Render the index.
152-
render('index.html', 'index.html', level=0, classes=classes)
249+
render("index.html", "index.html", level=0, classes=classes)
153250
# Render the search index.
154-
render('search-index.js', 'search-index.js', level=0, classes=classes)
251+
render("search-index.js", "search-index.js", level=0, classes=classes)
155252
# Render the date.
156-
render('date.js', 'date.js', level=0, currdate=time.strftime("%d/%m/%Y"))
253+
render("date.js", "date.js", level=0, currdate=time.strftime("%d/%m/%Y"))
157254

158255
for class_ in classes:
159-
print(f'Rendering pages for class {class_.name}...')
256+
print(f"Rendering pages for class {class_.name}...")
160257

161258
# Make a folder for the class.
162-
os.mkdir('build/' + class_.name)
163-
index_path = '{}/index.html'.format(class_.name)
164-
sidebar_path = '{}/sidebar.js'.format(class_.name)
259+
os.mkdir("build/" + class_.name)
260+
index_path = "{}/index.html".format(class_.name)
261+
sidebar_path = "{}/sidebar.js".format(class_.name)
165262

166263
# Render the class's index page.
167-
render('class.html', index_path, level=1, classes=classes, current_class=class_)
264+
render("class.html", index_path, level=1, classes=classes, current_class=class_)
168265

169266
# Render the class's sidebar script.
170-
render('sidebar.js', sidebar_path, level=1, classes=classes, current_class=class_)
267+
render("sidebar.js", sidebar_path, level=1, classes=classes, current_class=class_)
171268

172269
# Render each method's page.
173270
for method in class_.methods:
174-
method_path = '{}/{}.html'.format(class_.name, method.name)
175-
render('method.html', method_path, level=1, current_class=class_, current_method=method)
271+
method_path = "{}/{}.html".format(class_.name, method.name)
272+
render("method.html", method_path, level=1, current_class=class_, current_method=method)

0 commit comments

Comments
 (0)