11import os
22import shutil
33import typing
4- from jinja2 import Environment , FileSystemLoader
5- from typedecorator import params , returns
6- from ElunaDoc .parser import ClassParser , MethodDoc
74import glob
85import 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 )
1315def 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
3125def 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
5041def make_static (level ):
51- return lambda file_name : (' ../' * level ) + ' static/' + file_name
42+ return lambda file_name : (" ../" * level ) + " static/" + file_name
5243
5344
5445def 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