11from __future__ import annotations
22from pathlib import Path
3- from typing import Dict , List , Tuple
3+ from typing import Dict , List , Tuple , Optional
4+ from datetime import datetime
45from .models import ScriptDoc
56from .parser import parse_gd_script
67from .render import render_script_markdown , render_function_markdown
78from .indexer import build_index_for_docs , compute_reference_links_for_function
8- from .utils import slug , rel_href
9+ from .utils import slug , rel_href , split_brief_details
910
1011def write_docs (
1112 src : Path ,
@@ -107,17 +108,141 @@ def write_docs(
107108 path .write_text (md , encoding = "utf-8" )
108109
109110 if make_index and not single_file :
110- index_lines = ["# Index" , "" ]
111- for d , _ , path in rendered :
112- rel = path .relative_to (out )
113- index_lines .append (f"- [{ d .class_name or d .path .stem } ]({ rel .as_posix ()} )" )
114- if split_functions :
115- class_basename = d .class_name or d .path .stem
116- func_root = rel .parent / class_basename / "functions"
117- real_func_root = out / func_root
118- if real_func_root .exists ():
119- for m in [m for m in d .members if m .kind == "func" ]:
120- f = real_func_root / f"{ slug (m .name )} .md"
121- if f .exists ():
122- index_lines .append (f" - [{ m .name } ]({ (rel .parent / class_basename / 'functions' / f .name ).as_posix ()} )" )
123- (out / "INDEX.md" ).write_text ("\n " .join (index_lines ) + "\n " , encoding = "utf-8" )
111+ write_index (out = out , rendered = rendered , split_functions = split_functions )
112+
113+ def write_index (
114+ out : Path ,
115+ rendered : list [tuple ["ScriptDoc" , str , Path ]],
116+ split_functions : bool ,
117+ ) -> None :
118+ """
119+ Write a structured index under ``out/_index/``.
120+ Args:
121+ out: Output directory where docs were written.
122+ rendered: List of tuples (ScriptDoc, rendered_markdown, class_md_path).
123+ split_functions: Whether function pages were split out.
124+ """
125+ index_dir = out / "_index"
126+ index_dir .mkdir (parents = True , exist_ok = True )
127+
128+ class_rows = []
129+ for d , _ , class_md_path in rendered :
130+ title = d .class_name or d .path .stem
131+ rel_class_md = class_md_path .relative_to (out )
132+ parent_rel_dir = rel_class_md .parent
133+ extends = d .extends or ""
134+ counts = {
135+ "func" : sum (1 for m in d .members if m .kind == "func" ),
136+ "var" : sum (1 for m in d .members if m .kind == "var" ),
137+ "const" :sum (1 for m in d .members if m .kind == "const" ),
138+ "signal" :sum (1 for m in d .members if m .kind == "signal" ),
139+ "enum" : sum (1 for m in d .members if m .kind == "enum" ),
140+ }
141+ class_rows .append ((title , rel_class_md , parent_rel_dir , extends , counts ))
142+
143+ by_folder_lines = ["# Classes by Folder" , "" ]
144+ from collections import defaultdict
145+ classes_by_folder : dict [str , list [tuple [str , Path , str , dict ]]] = defaultdict (list )
146+ for title , rel_md , parent_dir , extends , counts in class_rows :
147+ classes_by_folder [parent_dir .as_posix ()].append ((title , rel_md , extends , counts ))
148+ for folder in sorted (classes_by_folder .keys (), key = lambda s : (s .count ("/" ), s )):
149+ depth = 0 if folder == "." else folder .count ("/" ) + (0 if not folder else 1 )
150+ header_prefix = "#" * max (2 , min (6 , depth + 2 ))
151+ folder_label = folder if folder and folder != "." else "(root)"
152+ by_folder_lines .append (f"{ header_prefix } { folder_label } " )
153+ by_folder_lines .append ("" )
154+ for title , rel_md , extends , counts in sorted (classes_by_folder [folder ], key = lambda r : r [0 ].lower ()):
155+ href = rel_href (rel_md , start_rel = Path ("_index" )) # file lives at _index/by-folder.md
156+ summary = f"{ counts ['func' ]} funcs · { counts ['var' ]} vars · { counts ['signal' ]} signals · { counts ['const' ]} consts · { counts ['enum' ]} enums"
157+ ext = f" — *inherits* `{ extends } `" if extends else ""
158+ by_folder_lines .append (f"- [{ title } ]({ href } ) — { summary } { ext } " )
159+ by_folder_lines .append ("" )
160+
161+ (index_dir / "by-folder.md" ).write_text ("\n " .join (by_folder_lines ).rstrip () + "\n " , encoding = "utf-8" )
162+
163+ classes_lines = ["# Classes A–Z" , "" ]
164+ buckets : dict [str , list [tuple [str , Path , str , dict ]]] = defaultdict (list )
165+ for title , rel_md , _ , extends , counts in class_rows :
166+ first = title [0 ].upper () if title else "#"
167+ if not ("A" <= first <= "Z" ):
168+ first = "#"
169+ buckets [first ].append ((title , rel_md , extends , counts ))
170+
171+ letters = [c for c in "ABCDEFGHIJKLMNOPQRSTUVWXYZ" ] + ["#" ]
172+ classes_lines .append ("**Jump to:** " + " · " .join (f"[{ L } ](#{ L .lower ()} )" for L in letters ))
173+ classes_lines .append ("" )
174+
175+ for L in letters :
176+ if L not in buckets :
177+ continue
178+ classes_lines .append (f"## { L } " )
179+ classes_lines .append ("" )
180+ for title , rel_md , extends , counts in sorted (buckets [L ], key = lambda r : r [0 ].lower ()):
181+ href = rel_href (rel_md , start_rel = Path ("_index" ))
182+ summary = f"{ counts ['func' ]} funcs · { counts ['var' ]} vars · { counts ['signal' ]} signals"
183+ ext = f" — *inherits* `{ extends } `" if extends else ""
184+ classes_lines .append (f"- [{ title } ]({ href } ) — { summary } { ext } " )
185+ classes_lines .append ("" )
186+
187+ (index_dir / "classes.md" ).write_text ("\n " .join (classes_lines ).rstrip () + "\n " , encoding = "utf-8" )
188+
189+ if split_functions :
190+ funcs_lines = ["# Functions A–Z" , "" ]
191+ fn_buckets : dict [str , list [tuple [str , str , Path , Optional [str ]]]] = defaultdict (list )
192+ for d , _ , class_md_path in rendered :
193+ class_title = d .class_name or d .path .stem
194+ class_rel = class_md_path .relative_to (out )
195+ functions_dir = class_rel .parent / class_title / "functions"
196+ for m in (mm for mm in d .members if mm .kind == "func" ):
197+ fpath = functions_dir / f"{ slug (m .name )} .md"
198+ real_path = out / fpath
199+ if not real_path .exists ():
200+ continue
201+ first = (m .name [0 ].upper () if m .name else "#" )
202+ if not ("A" <= first <= "Z" ):
203+ first = "#"
204+ brief = None
205+ if m .doc and m .doc .markdown :
206+ b , _ = split_brief_details (m .doc .markdown )
207+ brief = b or None
208+ fn_buckets [first ].append ((class_title , m .name , fpath , brief ))
209+
210+ funcs_lines .append ("**Jump to:** " + " · " .join (f"[{ L } ](#{ L .lower ()} )" for L in letters ))
211+ funcs_lines .append ("" )
212+
213+ for L in letters :
214+ if L not in fn_buckets :
215+ continue
216+ funcs_lines .append (f"## { L } " )
217+ funcs_lines .append ("" )
218+ for class_title , fname , rel_fn_md , brief in sorted (fn_buckets [L ], key = lambda r : (r [1 ].lower (), r [0 ].lower ())):
219+ href = rel_href (rel_fn_md , start_rel = Path ("_index" ))
220+ title = f"{ class_title } ::{ fname } "
221+ line = f"- [{ title } ]({ href } )"
222+ if brief :
223+ line += f" — { brief } "
224+ funcs_lines .append (line )
225+ funcs_lines .append ("" )
226+ (index_dir / "functions.md" ).write_text ("\n " .join (funcs_lines ).rstrip () + "\n " , encoding = "utf-8" )
227+
228+ now = datetime .now ().strftime ("%Y-%m-%d %H:%M" )
229+ main = [
230+ "# Game API Reference" ,
231+ "" ,
232+ f"*Generated on:* { now } " ,
233+ "" ,
234+ "### Navigation" ,
235+ "" ,
236+ "- **By Folder:** [_index/by-folder.md](_index/by-folder.md)" ,
237+ "- **Classes A–Z:** [_index/classes.md](_index/classes.md)" ,
238+ ]
239+ if split_functions :
240+ main .append ("- **Functions A–Z:** [_index/functions.md](_index/functions.md)" )
241+ main += [
242+ "" ,
243+ "### Tips" ,
244+ "" ,
245+ "- Use your viewer’s search (e.g. GitHub’s file search or browser **Ctrl/⌘+F**)." ,
246+ "- Each class page has a summary and detailed sections (functions, signals, etc.)." ,
247+ ]
248+ (out / "INDEX.md" ).write_text ("\n " .join (main ).rstrip () + "\n " , encoding = "utf-8" )
0 commit comments