Skip to content

Commit 209a79b

Browse files
committed
Add meta/ directory with JSON metadata for each tool
- Created docs_to_json.py script to convert *.docs.md files to JSON - Each tool now has a meta/<slug>.json file with description and commit hash - Updated build_colophon.py, gather_links.py, build_by_month.py to read from meta/ JSON files - Updated write_docs.py to also generate meta/ JSON files when creating docs haiku.json intentionally omitted to test rebuild capability
1 parent 64d210f commit 209a79b

172 files changed

Lines changed: 838 additions & 77 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

build_by_month.py

Lines changed: 10 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -31,44 +31,19 @@ def _get_first_n_words(text: str, n: int = 15) -> str:
3131
return " ".join(words[:n]) + "..."
3232

3333

34-
def _extract_summary(docs_path: Path, word_limit: int = 30) -> str:
35-
"""Extract the first paragraph of the docs file, limited to word_limit words."""
36-
if not docs_path.exists():
34+
def _extract_summary(meta_path: Path, word_limit: int = 30) -> str:
35+
"""Extract the description from the meta JSON file, limited to word_limit words."""
36+
if not meta_path.exists():
3737
return ""
3838

3939
try:
40-
content = docs_path.read_text("utf-8").strip()
41-
except OSError:
40+
import json
41+
data = json.load(meta_path.open("r", encoding="utf-8"))
42+
description = data.get("description", "")
43+
return _get_first_n_words(description, word_limit)
44+
except (OSError, json.JSONDecodeError):
4245
return ""
4346

44-
# Remove HTML comments
45-
if "<!--" in content:
46-
content = content.split("<!--", 1)[0]
47-
48-
# Strip any markdown heading lines first
49-
content_lines = [
50-
line for line in content.splitlines()
51-
if not line.lstrip().startswith("# ")
52-
and not line.lstrip().startswith("## ")
53-
and not line.lstrip().startswith("### ")
54-
and not line.lstrip().startswith("#### ")
55-
and not line.lstrip().startswith("##### ")
56-
and not line.lstrip().startswith("###### ")
57-
]
58-
59-
# Get first paragraph
60-
lines = []
61-
for line in content_lines:
62-
stripped = line.strip()
63-
if not stripped:
64-
if lines:
65-
break
66-
continue
67-
lines.append(stripped)
68-
69-
paragraph = " ".join(lines)
70-
return _get_first_n_words(paragraph, word_limit)
71-
7247

7348
def _load_gathered_links() -> dict:
7449
if not GATHERED_LINKS_PATH.exists():
@@ -105,8 +80,8 @@ def build_by_month() -> None:
10580

10681
# Get the docs summary
10782
slug = page_name.replace(".html", "")
108-
docs_path = Path(f"{slug}.docs.md")
109-
summary = _extract_summary(docs_path)
83+
meta_path = Path("meta") / f"{slug}.json"
84+
summary = _extract_summary(meta_path)
11085

11186
tools_by_month[month_key].append({
11287
"filename": page_name,

build_colophon.py

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -260,29 +260,21 @@ def get_most_recent_date(page_data):
260260
</h2>
261261
</div>
262262
"""
263-
# Check for corresponding docs.md file
264-
docs_file = page_name.replace(".html", ".docs.md")
265-
if Path(docs_file).exists():
263+
# Check for corresponding meta JSON file
264+
slug = page_name.replace(".html", "")
265+
meta_file = Path("meta") / f"{slug}.json"
266+
if meta_file.exists():
266267
try:
267-
with open(docs_file, "r") as f:
268-
docs_content = f.read()
269-
# Strip any markdown heading lines first
270-
docs_lines = [
271-
line for line in docs_content.splitlines()
272-
if not line.lstrip().startswith("# ")
273-
and not line.lstrip().startswith("## ")
274-
and not line.lstrip().startswith("### ")
275-
and not line.lstrip().startswith("#### ")
276-
and not line.lstrip().startswith("##### ")
277-
and not line.lstrip().startswith("###### ")
278-
]
279-
docs_content = "\n".join(docs_lines)
280-
# Render markdown to HTML
281-
docs_html = markdown.markdown(docs_content)
282-
# Add docs above commits
283-
html_content += '<div class="docs">' + docs_html + "</div>"
268+
with open(meta_file, "r") as f:
269+
meta_data = json.load(f)
270+
description = meta_data.get("description", "")
271+
if description:
272+
# Render markdown to HTML
273+
docs_html = markdown.markdown(description)
274+
# Add docs above commits
275+
html_content += '<div class="docs">' + docs_html + "</div>"
284276
except Exception as e:
285-
print(f"Error reading {docs_file}: {e}")
277+
print(f"Error reading {meta_file}: {e}")
286278

287279
# Wrap commits in details/summary tags
288280
html_content += f"""

docs_to_json.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
#!/usr/bin/env python3
2+
"""Convert *.docs.md files to JSON files in meta/ directory."""
3+
4+
import json
5+
import re
6+
from pathlib import Path
7+
8+
9+
def extract_description(content: str) -> str:
10+
"""Extract the first paragraph (description) from docs.md content."""
11+
# Remove HTML comments
12+
if "<!--" in content:
13+
content = content.split("<!--", 1)[0]
14+
15+
# Strip any markdown heading lines
16+
content_lines = [
17+
line for line in content.splitlines()
18+
if not line.lstrip().startswith("# ")
19+
and not line.lstrip().startswith("## ")
20+
and not line.lstrip().startswith("### ")
21+
and not line.lstrip().startswith("#### ")
22+
and not line.lstrip().startswith("##### ")
23+
and not line.lstrip().startswith("###### ")
24+
]
25+
26+
# Get first paragraph
27+
lines = []
28+
for line in content_lines:
29+
stripped = line.strip()
30+
if not stripped:
31+
if lines:
32+
break
33+
continue
34+
lines.append(stripped)
35+
36+
return " ".join(lines)
37+
38+
39+
def extract_commit(content: str) -> str:
40+
"""Extract the commit hash from the HTML comment."""
41+
match = re.search(r"<!-- Generated from commit: ([a-f0-9]+) -->", content)
42+
if match:
43+
return match.group(1)
44+
return ""
45+
46+
47+
def main():
48+
# Create meta directory if it doesn't exist
49+
meta_dir = Path("meta")
50+
meta_dir.mkdir(exist_ok=True)
51+
52+
# Find all docs.md files in the current directory
53+
docs_files = sorted(Path(".").glob("*.docs.md"))
54+
55+
converted_count = 0
56+
57+
for docs_file in docs_files:
58+
# Read the content
59+
content = docs_file.read_text("utf-8")
60+
61+
# Extract description and commit
62+
description = extract_description(content)
63+
commit = extract_commit(content)
64+
65+
# Determine output filename (e.g., ai-adoption.docs.md -> meta/ai-adoption.json)
66+
slug = docs_file.stem.replace(".docs", "")
67+
output_file = meta_dir / f"{slug}.json"
68+
69+
# Create JSON object
70+
data = {
71+
"description": description,
72+
"commit": commit,
73+
}
74+
75+
# Write to file with pretty printing
76+
output_file.write_text(json.dumps(data, indent=2) + "\n", "utf-8")
77+
converted_count += 1
78+
print(f"Converted {docs_file} -> {output_file}")
79+
80+
print(f"\nConverted {converted_count} files to JSON in meta/")
81+
82+
83+
if __name__ == "__main__":
84+
main()

gather_links.py

Lines changed: 8 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -67,30 +67,17 @@ def extract_urls(text):
6767
return re.findall(url_pattern, text)
6868

6969

70-
def extract_description(docs_path: Path) -> str:
71-
"""Extract the first paragraph of the generated docs markdown file."""
72-
if not docs_path.exists():
70+
def extract_description(meta_path: Path) -> str:
71+
"""Extract the description from the meta JSON file."""
72+
if not meta_path.exists():
7373
return ""
7474

7575
try:
76-
content = docs_path.read_text("utf-8").strip()
77-
except OSError:
76+
data = json.load(meta_path.open("r", encoding="utf-8"))
77+
return data.get("description", "")
78+
except (OSError, json.JSONDecodeError):
7879
return ""
7980

80-
if "<!--" in content:
81-
content = content.split("<!--", 1)[0]
82-
83-
lines = []
84-
for line in content.splitlines():
85-
stripped = line.strip()
86-
if not stripped:
87-
if lines:
88-
break
89-
continue
90-
lines.append(stripped)
91-
92-
return " ".join(lines)
93-
9481

9582
def extract_title(html_path: Path) -> str:
9683
"""Extract the <title> from an HTML file."""
@@ -147,8 +134,8 @@ def main():
147134
if not commits:
148135
continue
149136

150-
docs_path = html_file.with_suffix(".docs.md")
151-
description = extract_description(docs_path)
137+
meta_path = Path("meta") / f"{html_file.stem}.json"
138+
description = extract_description(meta_path)
152139

153140
created_date = commits[-1]["date"] if commits else None
154141
updated_date = commits[0]["date"] if commits else None

meta/ai-adoption.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"description": "View AI adoption trends across different firm sizes by analyzing survey data on artificial intelligence usage in the workplace. This page runs a Python analysis using Pyodide to fetch employment survey data, calculate six-survey rolling averages, and generate an interactive visualization showing adoption rates by company size from November 2023 through August 2025. Download the resulting chart as PNG or SVG for further use or presentation.",
3+
"commit": "ecc4d0ed023901a9d26d99aea2b3bd34258e5241"
4+
}

meta/alt-text-extractor.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"description": "Extract alternative text and image URLs from rich text content pasted into this tool. Simply paste content from web pages, and the tool automatically detects embedded images, displays them alongside their alt text descriptions, and provides copy buttons for convenient access to both the alt text and image URLs. This utility is particularly useful for accessibility audits, content analysis, and archiving image metadata.",
3+
"commit": "e468d4b9566e27850d234e609f9d99cafbd9c78b"
4+
}

meta/analytics.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"description": "Track your browsing activity and viewing patterns with this personal analytics dashboard that stores all data locally in your browser. The tool displays comprehensive statistics including total visits, unique pages visited, and time-based breakdowns through interactive charts showing visits by day or hour. A detailed table view shows your most visited pages and recent activity, with options to export your analytics data as JSON or clear all stored information at any time.",
3+
"commit": "41099d1fcf99cf91a2d2be88e96597abc776ef21"
4+
}

meta/animated-rainbow-border.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"description": "Display an animated rainbow gradient border effect around a centered box with interactive controls. The page features a dark theme with a glowing, color-shifting border that can be toggled on and off using the provided button. The animation combines gradient shifting and pulsing effects to create a dynamic, eye-catching visual presentation.",
3+
"commit": "99021c5a96c4d188e92a9341621a105e0d3600ca"
4+
}

meta/annotated-presentations.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"description": "Create annotated presentation slides with alt text and markdown notes. Upload your slide images, add accessibility descriptions and annotations with markdown support, then generate HTML output using customizable templates. The tool automatically saves your work and includes optional OCR functionality to extract text from slides.",
3+
"commit": "6c429482d2b7eb09c5e2554fe33c6157137f7bcc"
4+
}

meta/apsw-query.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"description": "Analyze and explain SQLite queries using APSW by entering SQL code and executing it in an in-browser Python environment. The tool provides detailed query analysis including execution plans, expanded SQL, and query information to help understand how SQLite processes your queries. Optional setup SQL can be run before the main query to create tables or initialize data, and parameterized queries are supported through labeled input fields.",
3+
"commit": "0af31729167e3de7f6ac73afd5e5bc03ba3b68fb"
4+
}

0 commit comments

Comments
 (0)