-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgenerate_source_cache.py
More file actions
165 lines (136 loc) · 6.01 KB
/
Copy pathgenerate_source_cache.py
File metadata and controls
165 lines (136 loc) · 6.01 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
"""
generate_source_cache.py — Index bytewars C# scripts into .cache/source-index.json.
Usage:
python generate_source_cache.py [--bytewars-root PATH]
Default bytewars root: d:\\ags-ai\\accelbyte-unity-bytewars-game
"""
from __future__ import annotations
import argparse
import json
import re
import sys
from datetime import datetime, timezone
from pathlib import Path
SCRIPT_DIR = Path(__file__).parent
DEFAULT_BYTEWARS_ROOT = SCRIPT_DIR.parent / "accelbyte-unity-bytewars-game"
EXAMPLE_VIEWS_DIR = SCRIPT_DIR / "data" / "example-views"
CACHE_DIR = SCRIPT_DIR / ".cache"
OUTPUT_FILE = CACHE_DIR / "source-index.json"
# class <Name> : <Base> — handles optional generic params and multiple interfaces
_CLASS_RE = re.compile(
r"(?:public|internal|protected)\s+(?:abstract\s+|sealed\s+|partial\s+)*class\s+"
r"(?P<name>[A-Za-z_][A-Za-z0-9_<>]*)"
r"(?:\s*:\s*(?P<bases>[^\n{]+))?",
re.MULTILINE,
)
# public <ReturnType> <MethodName>(...)
_METHOD_RE = re.compile(
r"public\s+(?:static\s+|virtual\s+|override\s+|async\s+)*"
r"(?:[A-Za-z_][A-Za-z0-9_<>\[\]?,\s]*)\s+"
r"(?P<name>[A-Za-z_][A-Za-z0-9_]*)\s*\(",
re.MULTILINE,
)
# Keyword → feature tag mapping (order matters — first match wins)
FEATURE_KEYWORDS: list[tuple[str, list[str]]] = [
("login", ["login", "auth", "signin", "loginwith", "loginmenu", "starterMenu"]),
("auth", ["authentication", "credential", "token", "session", "iam"]),
("leaderboard", ["leaderboard", "ranking", "rank", "score", "leaderboardentry"]),
("friends", ["friend", "social", "friendlist", "friendship"]),
("lobby", ["lobby", "presence", "status", "notification"]),
("party", ["party", "invite", "matchlobby"]),
("matchmaking", ["matchmaking", "matchmaker", "queue", "matching", "imatchmaking"]),
("user_profile", ["userprofile", "profile", "displayname", "avatar"]),
("achievements", ["achievement"]),
("cloud_save", ["cloudsave", "cloudstorage", "savegame"]),
("store", ["store", "shop", "purchase", "entitlement", "item"]),
("wallet", ["wallet", "currency", "balance", "credit"]),
("notifications", ["notification", "inbox", "alert"]),
("ui", ["menu", "canvas", "hud", "panel", "widget", "controller", "ui", "screen"]),
("multiplayer", ["multiplayer", "online", "netcode", "server", "client", "network"]),
("tutorial", ["tutorial", "module", "tutorialmodule"]),
("gameplay", ["ingame", "gameplay", "player", "game"]),
]
def _feature_tags(class_name: str, method_names: list[str]) -> list[str]:
text = (class_name + " " + " ".join(method_names)).lower()
text_nospace = re.sub(r"[^a-z0-9]", "", text)
tags: list[str] = []
for tag, keywords in FEATURE_KEYWORDS:
for kw in keywords:
kw_lower = kw.lower()
if kw_lower in text or kw_lower in text_nospace:
if tag not in tags:
tags.append(tag)
break
return tags or ["general"]
def _describe(class_name: str, base_class: str | None, tags: list[str]) -> str:
base_str = f" ({base_class})" if base_class else ""
tag_str = ", ".join(tags) if tags else "general"
return f"{class_name}{base_str} — {tag_str} component"
def index_file(cs_path: Path, source_root: Path) -> list[dict]:
try:
text = cs_path.read_text(encoding="utf-8", errors="replace")
except Exception:
return []
uri = cs_path.relative_to(source_root).as_posix()
entries = []
for m in _CLASS_RE.finditer(text):
class_name = m.group("name")
# Skip generic type parameters in name
class_name = class_name.split("<")[0]
bases_raw = m.group("bases") or ""
# First non-generic base type
base_class: str | None = None
if bases_raw.strip():
first = bases_raw.strip().split(",")[0].strip()
base_class = first.split("<")[0].strip() or None
method_names = [mm.group("name") for mm in _METHOD_RE.finditer(text)]
tags = _feature_tags(class_name, method_names)
entries.append({
"uri": uri,
"absolute_path": str(cs_path),
"class_name": class_name,
"base_class": base_class,
"feature_tags": tags,
"public_methods": method_names[:20], # cap for readability
"description": _describe(class_name, base_class, tags),
})
return entries
def generate(bytewars_root: Path) -> dict:
scripts_dir = bytewars_root / "Assets" / "Scripts"
if not scripts_dir.exists():
print(f"ERROR: Scripts dir not found: {scripts_dir}", file=sys.stderr)
sys.exit(1)
components = []
cs_files = sorted(scripts_dir.rglob("*.cs"))
print(f"Indexing {len(cs_files)} C# files from {scripts_dir}...")
for cs_path in cs_files:
components.extend(index_file(cs_path, bytewars_root))
# Also index example views (real SDK call examples authored in this repo)
if EXAMPLE_VIEWS_DIR.exists():
example_files = sorted(EXAMPLE_VIEWS_DIR.rglob("*.cs"))
print(f"Indexing {len(example_files)} example view files from {EXAMPLE_VIEWS_DIR}...")
for cs_path in example_files:
components.extend(index_file(cs_path, EXAMPLE_VIEWS_DIR))
else:
print(f"Note: example-views dir not found at {EXAMPLE_VIEWS_DIR}, skipping.")
return {
"generated_at": datetime.now(timezone.utc).isoformat(),
"source_root": str(bytewars_root),
"component_count": len(components),
"components": components,
}
def main():
parser = argparse.ArgumentParser(description="Generate Unity source index cache.")
parser.add_argument(
"--bytewars-root",
type=Path,
default=DEFAULT_BYTEWARS_ROOT,
help="Path to the accelbyte-unity-bytewars-game directory",
)
args = parser.parse_args()
CACHE_DIR.mkdir(exist_ok=True)
index = generate(args.bytewars_root)
OUTPUT_FILE.write_text(json.dumps(index, indent=2), encoding="utf-8")
print(f"Wrote {index['component_count']} components to {OUTPUT_FILE}")
if __name__ == "__main__":
main()