22"""Generate GitHub Pages documentation from the toolbox package registry."""
33
44import json
5- import os
65import re
76from pathlib import Path
87
98
10- def read_toolchain_components (nix_path : Path ) -> list [str ]:
11- """Parse a toolchain default.nix to extract component package names."""
9+ def load_package_defaults (packages_dir : Path ) -> dict [str , str ]:
10+ """Load the default version for every package that has a data.json."""
11+ defaults = {}
12+ for pkg_dir in packages_dir .iterdir ():
13+ data_json = pkg_dir / "data.json"
14+ if pkg_dir .is_dir () and data_json .exists ():
15+ data = json .loads (data_json .read_text ())
16+ defaults [pkg_dir .name ] = data .get ("_meta" , {}).get ("default" , "" )
17+ return defaults
18+
19+
20+ def parse_toolchain_versions (
21+ nix_path : Path , pkg_defaults : dict [str , str ]
22+ ) -> tuple [list [str ], dict [str , list [dict ]]]:
23+ """Parse a toolchain default.nix to extract versions and their resolved components.
24+
25+ Returns (version_names, {version: [{name, version}, ...]}).
26+ """
1227 content = nix_path .read_text ()
13- return re .findall (r"toolbox\.(\w[\w-]*)\.versions" , content )
28+
29+ # Extract the default version
30+ default_match = re .search (r'default\s*=\s*"([^"]+)"' , content )
31+ default = default_match .group (1 ) if default_match else ""
32+
33+ # Split into version blocks: find each "version" = pkgs.symlinkJoin { ... };
34+ version_pattern = re .compile (
35+ r'"([^"]+)"\s*=\s*pkgs\.symlinkJoin\s*\{[^}]*paths\s*=\s*\[(.*?)\]' ,
36+ re .DOTALL ,
37+ )
38+
39+ versions = {}
40+ version_names = []
41+ for m in version_pattern .finditer (content ):
42+ ver = m .group (1 )
43+ paths_block = m .group (2 )
44+ version_names .append (ver )
45+
46+ components = []
47+ # Match toolbox.<pkg>.versions.${toolbox.<pkg>.default} (uses default)
48+ for pkg in re .findall (
49+ r"toolbox\.([\w-]+)\.versions\.\$\{toolbox\.\1\.default\}" , paths_block
50+ ):
51+ resolved = pkg_defaults .get (pkg , "?" )
52+ components .append ({"name" : pkg , "version" : resolved })
53+
54+ # Match toolbox.<pkg>.versions.<literal_version> (pinned)
55+ for pkg , ver_literal in re .findall (
56+ r'toolbox\.([\w-]+)\.versions\."?([^"\s};]+)"?' , paths_block
57+ ):
58+ # Skip if already matched as a default reference
59+ if not re .search (
60+ rf"toolbox\.{ re .escape (pkg )} \.versions\.\$\{{" , paths_block
61+ ):
62+ components .append ({"name" : pkg , "version" : ver_literal })
63+
64+ versions [ver ] = components
65+
66+ return default , version_names , versions
1467
1568
1669def main ():
@@ -19,6 +72,8 @@ def main():
1972 out_dir = repo_root / "docs" / "_site"
2073 out_dir .mkdir (parents = True , exist_ok = True )
2174
75+ pkg_defaults = load_package_defaults (packages_dir )
76+
2277 packages = []
2378 toolchains = []
2479
@@ -46,45 +101,57 @@ def main():
46101 }
47102 )
48103 else :
49- # Toolchain meta-package
50104 nix_path = pkg_dir / "default.nix"
51- components = (
52- read_toolchain_components (nix_path ) if nix_path .exists () else []
53- )
54- toolchains .append (
55- {
56- "name" : name ,
57- "components" : components ,
58- }
59- )
105+ if nix_path .exists ():
106+ default , version_names , version_map = parse_toolchain_versions (
107+ nix_path , pkg_defaults
108+ )
109+ toolchains .append (
110+ {
111+ "name" : name ,
112+ "default" : default ,
113+ "versions" : version_names ,
114+ "expansion" : version_map ,
115+ }
116+ )
60117
61118 html = render_html (packages , toolchains )
62119 (out_dir / "index.html" ).write_text (html )
63- print (f"Generated docs with { len (packages )} packages and { len (toolchains )} toolchains" )
120+ print (
121+ f"Generated docs with { len (packages )} packages and { len (toolchains )} toolchains"
122+ )
64123
65124
66125def render_html (packages : list [dict ], toolchains : list [dict ]) -> str :
67126 package_rows = ""
68127 for pkg in packages :
69128 versions_html = ", " .join (
70- f'<span class="version{ "" if v != pkg ["default" ] else " default " } ">{ v } </span>'
129+ f'<span class="version{ " default " if v == pkg ["default" ] else "" } ">{ v } </span>'
71130 for v in pkg ["versions" ]
72131 )
73132 package_rows += f""" <tr>
74133 <td class="pkg-name">{ pkg ["name" ]} </td>
75- <td><code>{ pkg ["default" ]} </code></td>
76134 <td>{ versions_html } </td>
77135 </tr>
78136"""
79137
80138 toolchain_rows = ""
81139 for tc in toolchains :
82- components_html = ", " .join (
83- f"<code>{ c } </code>" for c in tc ["components" ]
84- )
140+ versions_html = ""
141+ for ver in tc ["versions" ]:
142+ is_default = ver == tc ["default" ]
143+ components = tc ["expansion" ].get (ver , [])
144+ comp_rows = "" .join (
145+ f'<tr><td><code>{ c ["name" ]} </code></td><td>{ c ["version" ]} </td></tr>'
146+ for c in components
147+ )
148+ versions_html += f"""<details class="tc-details">
149+ <summary><span class="version{ " default" if is_default else "" } ">{ ver } </span></summary>
150+ <table class="tc-expansion"><tbody>{ comp_rows } </tbody></table>
151+ </details>"""
85152 toolchain_rows += f""" <tr>
86153 <td class="pkg-name">{ tc ["name" ]} </td>
87- <td>{ components_html } </td>
154+ <td>{ versions_html } </td>
88155 </tr>
89156"""
90157
@@ -176,6 +243,7 @@ def render_html(packages: list[dict], toolchains: list[dict]) -> str:
176243 .pkg-name {{
177244 font-weight: 600;
178245 color: var(--accent);
246+ white-space: nowrap;
179247 }}
180248 code {{
181249 background: var(--bg);
@@ -196,6 +264,35 @@ def render_html(packages: list[dict], toolchains: list[dict]) -> str:
196264 color: var(--green);
197265 font-weight: 600;
198266 }}
267+ .tc-details {{
268+ display: inline-block;
269+ margin: 0.1em;
270+ }}
271+ .tc-details summary {{
272+ cursor: pointer;
273+ list-style: none;
274+ }}
275+ .tc-details summary::-webkit-details-marker {{
276+ display: none;
277+ }}
278+ .tc-details summary .version::after {{
279+ content: " \u25b8 ";
280+ font-size: 0.7em;
281+ color: var(--text-muted);
282+ vertical-align: middle;
283+ }}
284+ .tc-details[open] summary .version::after {{
285+ content: " \u25be ";
286+ }}
287+ .tc-expansion {{
288+ margin: 0.4em 0 0.2em 0.5em;
289+ background: var(--bg);
290+ border: 1px solid var(--border);
291+ font-size: 0.85em;
292+ }}
293+ .tc-expansion td {{
294+ padding: 0.25rem 0.6rem;
295+ }}
199296 footer {{
200297 margin-top: 3rem;
201298 padding-top: 1rem;
@@ -233,8 +330,7 @@ def render_html(packages: list[dict], toolchains: list[dict]) -> str:
233330 <thead>
234331 <tr>
235332 <th>Package</th>
236- <th>Default</th>
237- <th>Available Versions</th>
333+ <th>Versions</th>
238334 </tr>
239335 </thead>
240336 <tbody>
@@ -246,7 +342,7 @@ def render_html(packages: list[dict], toolchains: list[dict]) -> str:
246342 <thead>
247343 <tr>
248344 <th>Toolchain</th>
249- <th>Included Packages </th>
345+ <th>Versions </th>
250346 </tr>
251347 </thead>
252348 <tbody>
0 commit comments