Skip to content

Commit 0a56a19

Browse files
Build tools directory from metadata (#21)
- add template markers to README so the index builder can inject a generated directory - generate an alphabetical tools directory from tools.json when building index.html - style the directory list to match the existing recent tools section ------ [Codex Task](https://chatgpt.com/codex/tasks/task_e_6917741d9a608325abccfedd1e9724d4)
1 parent 7657b92 commit 0a56a19

3 files changed

Lines changed: 82 additions & 26 deletions

File tree

README.md

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,7 @@ The [colophon](https://tools.mathspp.com/colophon) lists commit messages and tra
1111

1212
## Tools index
1313

14-
### General tools
15-
16-
- [QR code generator](./qr.html)
17-
- [What time is it for me?](./what-time-is-it-for-me.html)
18-
- [Gumroad link builder](./gumroad-links.html)
19-
- [Social link preview](./social-link-preview.html)
20-
- [DMARC analyser](./dmarc-analyser.html)
21-
22-
### mathspp-specific tools
23-
24-
- [Pre-fill testimonial form](./pre-fill-testimonial.html)
14+
<!-- tools index starts -->
15+
<!-- tools index stops -->
2516

2617
<script type="module" src="homepage-search.js" data-tool-search></script>

build_index.py

Lines changed: 63 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
from __future__ import annotations
55

6+
import html
67
import json
78
from datetime import datetime
89
from pathlib import Path
@@ -93,6 +94,25 @@ def _select_recent(
9394
return selected
9495

9596

97+
def _replace_between_markers(body_html: str, start_marker: str, end_marker: str, replacement: str) -> str:
98+
if start_marker not in body_html or end_marker not in body_html:
99+
raise RuntimeError(f"Markers '{start_marker}' or '{end_marker}' not found.")
100+
101+
start_idx = body_html.find(start_marker)
102+
end_idx = body_html.find(end_marker, start_idx)
103+
104+
if start_idx == -1 or end_idx == -1 or start_idx >= end_idx:
105+
raise RuntimeError("Invalid marker positions.")
106+
107+
return (
108+
body_html[: start_idx + len(start_marker)]
109+
+ "\n"
110+
+ replacement
111+
+ "\n"
112+
+ body_html[end_idx:]
113+
)
114+
115+
96116
def _render_recent_section(recently_added: Sequence[dict], recently_updated: Sequence[dict]) -> str:
97117
def render_list(tools: Sequence[dict]) -> str:
98118
if not tools:
@@ -141,6 +161,36 @@ def render_list(tools: Sequence[dict]) -> str:
141161
return section_html
142162

143163

164+
def _render_tools_index(tools: Sequence[dict]) -> str:
165+
filtered_tools = [tool for tool in tools if tool.get("slug") != "index"]
166+
sorted_tools = sorted(
167+
filtered_tools,
168+
key=lambda tool: (tool.get("title") or tool.get("slug") or "").casefold(),
169+
)
170+
171+
if sorted_tools:
172+
items = []
173+
for tool in sorted_tools:
174+
title = tool.get("title") or tool.get("slug") or "Untitled tool"
175+
url = tool.get("url") or f"/{tool.get('slug', '')}"
176+
items.append(
177+
f" <li><a href=\"{html.escape(url)}\">{html.escape(title)}</a></li>"
178+
)
179+
list_content = "\n".join(items)
180+
else:
181+
list_content = " <li class=\"tools-directory-empty\">No tools available.</li>"
182+
183+
section_html = f"""
184+
<section class=\"surface tools-directory content-flow\">
185+
<h2>All tools</h2>
186+
<ul class=\"tools-directory-list\">
187+
{list_content}
188+
</ul>
189+
</section>
190+
"""
191+
return section_html.strip()
192+
193+
144194
def build_index() -> None:
145195
if not README_PATH.exists():
146196
raise FileNotFoundError("README.md not found")
@@ -158,22 +208,20 @@ def build_index() -> None:
158208
)
159209

160210
recent_section_html = _render_recent_section(recently_added, recently_updated)
211+
body_html = _replace_between_markers(
212+
body_html,
213+
"<!-- recently starts -->",
214+
"<!-- recently stops -->",
215+
recent_section_html,
216+
)
161217

162-
# Inject the recent section between the comment markers
163-
start_marker = '<!-- recently starts -->'
164-
end_marker = '<!-- recently stops -->'
165-
if start_marker in body_html and end_marker in body_html:
166-
# Replace content between markers
167-
start_idx = body_html.find(start_marker)
168-
end_idx = body_html.find(end_marker)
169-
if start_idx < end_idx:
170-
body_html = (
171-
body_html[:start_idx + len(start_marker)] +
172-
'\n' + recent_section_html +
173-
body_html[end_idx:]
174-
)
175-
else:
176-
raise RuntimeError("Markers not found.")
218+
tools_directory_html = _render_tools_index(tools)
219+
body_html = _replace_between_markers(
220+
body_html,
221+
"<!-- tools index starts -->",
222+
"<!-- tools index stops -->",
223+
tools_directory_html,
224+
)
177225

178226
wrapped_body = f"<article class=\"content-flow\">\n{body_html}\n</article>"
179227

styles.css

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,23 @@ legend {
303303
font-size: 0.95rem;
304304
}
305305

306+
.tools-directory-list {
307+
list-style: none;
308+
padding: 0;
309+
margin: 0;
310+
display: grid;
311+
gap: 0.55rem;
312+
}
313+
314+
.tools-directory-list a {
315+
font-weight: 600;
316+
}
317+
318+
.tools-directory-empty {
319+
color: var(--tx-2);
320+
font-size: 0.95rem;
321+
}
322+
306323
.tool-search {
307324
display: grid;
308325
gap: 0.75rem;

0 commit comments

Comments
 (0)