Add UnrealDocGenerator tool and UE API skill

- ue_parser.py: position-based UE C++ header parser
- ue_markdown.py: compact agent-optimised Markdown renderer
- generate.py: two-pass CLI (parse-all → type index → render-all)
- samples/: representative UE headers (GeomUtils, AIController, GameplayTagsManager)
- .claude/skills/ue-api/: Claude Code skill for querying UE docs + source headers
- CLAUDE.md: architecture notes, usage, critical gotchas

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-24 06:55:05 -05:00
commit 93ca33c36a
12 changed files with 4387 additions and 0 deletions

135
generate.py Normal file
View File

@@ -0,0 +1,135 @@
#!/usr/bin/env python3
"""
generate.py — CLI for UnrealDocGenerator.
Usage:
python generate.py <input> <output_dir>
<input> can be a single .h file or a directory (processed recursively).
Two-pass pipeline:
Pass 1 — parse every header, build a corpus-wide type index
Pass 2 — render each header with cross-reference links injected
"""
import sys
import os
import re
from pathlib import Path
from ue_parser import parse_header, ParsedHeader
from ue_markdown import render_header
# ---------------------------------------------------------------------------
# Type index
# ---------------------------------------------------------------------------
def build_type_index(parsed_list: list[tuple[Path, ParsedHeader]],
input_base: Path) -> dict[str, str]:
"""
Returns {TypeName: md_path_relative_to_docs_root} for every
class, struct, enum, and delegate in the corpus.
"""
index: dict[str, str] = {}
for h, parsed in parsed_list:
try:
rel = h.relative_to(input_base)
except ValueError:
rel = Path(h.name)
md_rel = str(rel.with_suffix('.md'))
for ci in parsed.classes:
index[ci.name] = md_rel
for ei in parsed.enums:
index[ei.name] = md_rel
for di in parsed.delegates:
index[di.name] = md_rel
# namespace names are not types — skip
return index
# ---------------------------------------------------------------------------
# Type index file
# ---------------------------------------------------------------------------
def write_type_index(type_index: dict[str, str], output_dir: Path) -> None:
"""
Write docs/type-index.txt — compact TypeName: path/to/File.md lookup.
One entry per line, alphabetically sorted. Agents can grep this file
to resolve a type name to its documentation path.
"""
_valid_name = re.compile(r'^[A-Za-z_][A-Za-z0-9_]*$')
lines = sorted(f"{name}: {path}" for name, path in type_index.items()
if _valid_name.match(name))
out = output_dir / "type-index.txt"
out.write_text('\n'.join(lines) + '\n', encoding='utf-8')
print(f"Written {out}")
# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
def main():
if len(sys.argv) < 3:
print("Usage: python generate.py <input> <output_dir>", file=sys.stderr)
sys.exit(1)
input_arg = Path(sys.argv[1])
output_dir = Path(sys.argv[2])
output_dir.mkdir(parents=True, exist_ok=True)
# Collect input files
if input_arg.is_file():
headers = [input_arg]
input_base = input_arg.parent
elif input_arg.is_dir():
headers = sorted(input_arg.rglob('*.h'))
input_base = input_arg
else:
print(f"Error: {input_arg} is not a file or directory", file=sys.stderr)
sys.exit(1)
if not headers:
print("No .h files found.", file=sys.stderr)
sys.exit(1)
# --- Pass 1: parse all ---
parsed_list: list[tuple[Path, ParsedHeader]] = []
for h in headers:
print(f"Parsing {h} ...")
try:
parsed = parse_header(str(h))
parsed_list.append((h, parsed))
except Exception as exc:
print(f" ERROR parsing {h}: {exc}", file=sys.stderr)
# --- Build corpus-wide type index ---
type_index = build_type_index(parsed_list, input_base)
# --- Pass 2: render all ---
success = 0
for h, parsed in parsed_list:
print(f"Rendering {h} ...")
try:
rel = h.relative_to(input_base)
except ValueError:
rel = Path(h.name)
current_md = str(rel.with_suffix('.md'))
out_path = output_dir / current_md
out_path.parent.mkdir(parents=True, exist_ok=True)
try:
md = render_header(parsed, type_index=type_index, current_md=current_md)
out_path.write_text(md, encoding='utf-8')
success += 1
except Exception as exc:
print(f" ERROR rendering {h}: {exc}", file=sys.stderr)
write_type_index(type_index, output_dir)
print(f"\nGenerated {success}/{len(parsed_list)} files + type-index.txt in {output_dir}/")
if __name__ == '__main__':
main()