Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .github/workflows/update-readme.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ on:

jobs:
regen-readme:
runs-on: self-hosted
runs-on: ubuntu-latest

steps:
- name: 🛎️ Checkout
Expand All @@ -21,6 +21,11 @@ jobs:
persist-credentials: true
token: ${{ secrets.GITHUB_TOKEN }}

- name: 🐍 Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.x'

- name: ⚙️ Generate TOC
run: python3 scripts/order.py

Expand Down
20 changes: 20 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Python
__pycache__/
*.py[cod]
*$py.class

# Virtual environments
venv/
.env/

# IDE
.vscode/
.idea/

# Build directories
build/
cmake-build-*/

# OS files
.DS_Store
Thumbs.db
27 changes: 16 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,24 +38,24 @@
<thead>
<tr>
<th style="padding:4px 8px; text-align:center; font-weight:bold; border:1px solid #ddd;">A–I</th>
<th style="padding:4px 8px; text-align:center; font-weight:bold; border:1px solid #ddd;">I–R</th>
<th style="padding:4px 8px; text-align:center; font-weight:bold; border:1px solid #ddd;">J–R</th>
<th style="padding:4px 8px; text-align:center; font-weight:bold; border:1px solid #ddd;">S–W</th>
</tr>
</thead>
<tbody>
<tr>
<td style="padding:4px 8px; text-align:center; border:1px solid #ddd;"><a href="#atari-games">Atari-Games</a></td>
<td style="padding:4px 8px; text-align:center; border:1px solid #ddd;"><a href="#introspective">introspective</a></td>
<td style="padding:4px 8px; text-align:center; border:1px solid #ddd;"><a href="#js-compiler">JS-compiler</a></td>
<td style="padding:4px 8px; text-align:center; border:1px solid #ddd;"><a href="#sfml-multithread-verlet">SFML Multithread verlet</a></td>
</tr>
<tr>
<td style="padding:4px 8px; text-align:center; border:1px solid #ddd;"><a href="#beneficial-c-programming">Beneficial-C programming</a></td>
<td style="padding:4px 8px; text-align:center; border:1px solid #ddd;"><a href="#js-compiler">JS-compiler</a></td>
<td style="padding:4px 8px; text-align:center; border:1px solid #ddd;"><a href="#json-converter">Json Converter</a></td>
<td style="padding:4px 8px; text-align:center; border:1px solid #ddd;"><a href="#simple-code">Simple-Code</a></td>
</tr>
<tr>
<td style="padding:4px 8px; text-align:center; border:1px solid #ddd;"><a href="#chess-3-d">Chess 3 D</a></td>
<td style="padding:4px 8px; text-align:center; border:1px solid #ddd;"><a href="#json-converter">Json Converter</a></td>
<td style="padding:4px 8px; text-align:center; border:1px solid #ddd;"><a href="#kalman-filtering-simulation">Kalman-Filtering-Simulation</a></td>
<td style="padding:4px 8px; text-align:center; border:1px solid #ddd;"><a href="#simple-turso">simple turso</a></td>
</tr>
<tr>
Expand All @@ -80,39 +80,44 @@
</tr>
<tr>
<td style="padding:4px 8px; text-align:center; border:1px solid #ddd;"><a href="#define-evil">define evil</a></td>
<td style="padding:4px 8px; text-align:center; border:1px solid #ddd;"><a href="#ofx-async">ofx Async</a></td>
<td style="padding:4px 8px; text-align:center; border:1px solid #ddd;"><a href="#naive-circuit-simulator">Naive Circuit Simulator</a></td>
<td style="padding:4px 8px; text-align:center; border:1px solid #ddd;"><a href="#terminal-file-manager">Terminal File Manager</a></td>
</tr>
<tr>
<td style="padding:4px 8px; text-align:center; border:1px solid #ddd;"><a href="#design-patterns">Design-Patterns</a></td>
<td style="padding:4px 8px; text-align:center; border:1px solid #ddd;"><a href="#open-gl-examples">Open GL examples</a></td>
<td style="padding:4px 8px; text-align:center; border:1px solid #ddd;"><a href="#ofx-async">ofx Async</a></td>
<td style="padding:4px 8px; text-align:center; border:1px solid #ddd;"><a href="#thread-pool">Thread Pool</a></td>
</tr>
<tr>
<td style="padding:4px 8px; text-align:center; border:1px solid #ddd;"><a href="#dump-c">Dump C++</a></td>
<td style="padding:4px 8px; text-align:center; border:1px solid #ddd;"><a href="#pfad">PFAD</a></td>
<td style="padding:4px 8px; text-align:center; border:1px solid #ddd;"><a href="#open-gl-examples">Open GL examples</a></td>
<td style="padding:4px 8px; text-align:center; border:1px solid #ddd;"><a href="#tokenizer-tok">Tokenizer tok</a></td>
</tr>
<tr>
<td style="padding:4px 8px; text-align:center; border:1px solid #ddd;"><a href="#file-lock-socket">File Lock Socket</a></td>
<td style="padding:4px 8px; text-align:center; border:1px solid #ddd;"><a href="#pipes-screen-saver">Pipes Screen Saver</a></td>
<td style="padding:4px 8px; text-align:center; border:1px solid #ddd;"><a href="#pfad">PFAD</a></td>
<td style="padding:4px 8px; text-align:center; border:1px solid #ddd;"><a href="#trigonometric-functions">Trigonometric-Functions</a></td>
</tr>
<tr>
<td style="padding:4px 8px; text-align:center; border:1px solid #ddd;"><a href="#gen-xpassword">Gen XPassword</a></td>
<td style="padding:4px 8px; text-align:center; border:1px solid #ddd;"><a href="#pong">POng</a></td>
<td style="padding:4px 8px; text-align:center; border:1px solid #ddd;"><a href="#pipes-screen-saver">Pipes Screen Saver</a></td>
<td style="padding:4px 8px; text-align:center; border:1px solid #ddd;"><a href="#vite-fa-config">Vite FA config</a></td>
</tr>
<tr>
<td style="padding:4px 8px; text-align:center; border:1px solid #ddd;"><a href="#hangman">Hangman</a></td>
<td style="padding:4px 8px; text-align:center; border:1px solid #ddd;"><a href="#random-utility-tools">Random Utility tools</a></td>
<td style="padding:4px 8px; text-align:center; border:1px solid #ddd;"><a href="#pong">POng</a></td>
<td style="padding:4px 8px; text-align:center; border:1px solid #ddd;"><a href="#volumetric-clouds">volumetric-clouds</a></td>
</tr>
<tr>
<td style="padding:4px 8px; text-align:center; border:1px solid #ddd;"><a href="#inject-hook">Inject Hook</a></td>
<td style="padding:4px 8px; text-align:center; border:1px solid #ddd;"><a href="#regular-expression-convertors">Regular Expression Convertors</a></td>
<td style="padding:4px 8px; text-align:center; border:1px solid #ddd;"><a href="#random-utility-tools">Random Utility tools</a></td>
<td style="padding:4px 8px; text-align:center; border:1px solid #ddd;"><a href="#word-proc">Word Proc</a></td>
</tr>
<tr>
<td style="padding:4px 8px; text-align:center; border:1px solid #ddd;"><a href="#introspective">introspective</a></td>
<td style="padding:4px 8px; text-align:center; border:1px solid #ddd;"><a href="#regular-expression-convertors">Regular Expression Convertors</a></td>
<td style="padding:4px 8px; text-align:center; border:1px solid #ddd;"></td>
</tr>
</tbody>
</table>
<!-- TOC END -->
Expand Down
205 changes: 134 additions & 71 deletions scripts/order.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,40 @@
#!/usr/bin/env python3
"""
README Table of Contents Generator

This script automatically generates and updates the Table of Contents (TOC)
in the README.md file based on project directories in the repository.
"""
import os
import sys
import math
import re
import urllib.parse
from pathlib import Path
from itertools import zip_longest

# Configuration
README = "README.md"
EXCLUDE = {".git", ".github", "scripts", "__pycache__"}
EXCLUDE = {".git", ".github", "scripts", "__pycache__", "wiki"}
# Override mapping: directory name -> desired header display
OVERRIDES = {
"Dump_c++": "Dump C++",
# can add any other overrides
}

# Derive a human-friendly display name for TOC entries

def display_name(name):
"""Derive a human-friendly display name for TOC entries."""
if name in OVERRIDES:
return OVERRIDES[name]
# Insert spaces before camelCase boundaries, then replace underscores
s = re.sub(r'(?<=[a-z0-9])(?=[A-Z])', ' ', name)
s = s.replace('_', ' ')
return s.strip()

# Create a GitHub-style slug for IDs

def slugify(name):
"""Create a GitHub-style slug for anchor IDs."""
base = OVERRIDES.get(name, name)
# Split camelCase and underscores
s = re.sub(r'(?<=[a-z0-9])(?=[A-Z])', ' ', base)
Expand All @@ -37,84 +47,137 @@ def slugify(name):
# Collapse spaces into hyphens
return re.sub(r"\s+", "-", s.strip())

# Convert slug back to a Title Case display without hyphens

def header_display_from_slug(slug):
"""Convert slug back to a Title Case display without hyphens."""
disp = slug.replace('-', ' ')
return disp.title()

# Compute column header ranges

def header_range(col):
"""Compute column header ranges (e.g., 'A-M')."""
if not col:
return ""
first = slugify(col[0])[0].upper()
last = slugify(col[-1])[0].upper()
return f"{first}–{last}"

# Collect project directories
projects = sorted(
[d for d in os.listdir('.')
if os.path.isdir(d)
and d not in EXCLUDE
and not d.startswith('.')],
key=str.lower
)

# Split into 3 roughly even columns
cols = 3
chunk = math.ceil(len(projects) / cols)
columns = [projects[i*chunk:(i+1)*chunk] for i in range(cols)]

# Build HTML table
lines = [
'<table style="width:100%; border-collapse: collapse; border:1px solid #ddd;">',
' <thead>',
' <tr>'
]
for col in columns:
lines.append(
f' <th style="padding:4px 8px; text-align:center; font-weight:bold; border:1px solid #ddd;">{header_range(col)}</th>'

def collect_projects(base_path):
"""Collect and return sorted list of project directories."""
base = Path(base_path)
if not base.is_dir():
raise ValueError(f"Base path '{base_path}' is not a valid directory")

projects = sorted(
[d.name for d in base.iterdir()
if d.is_dir()
and d.name not in EXCLUDE
and not d.name.startswith('.')],
key=str.lower
)
lines += [' </tr>', ' </thead>', ' <tbody>']

for row in zip_longest(*columns, fillvalue=""):
lines.append(' <tr>')
for cell in row:
if cell:
slug = slugify(cell)
disp = display_name(cell)
lines.append(
f' <td style="padding:4px 8px; text-align:center; border:1px solid #ddd;"><a href="#' + slug + f'">{disp}</a></td>'
)
return projects


def build_toc_table(projects):
"""Build HTML table for the Table of Contents."""
# Split into 3 roughly even columns
cols = 3
chunk = math.ceil(len(projects) / cols) if projects else 1
columns = [projects[i*chunk:(i+1)*chunk] for i in range(cols)]

# Build HTML table
lines = [
'<table style="width:100%; border-collapse: collapse; border:1px solid #ddd;">',
' <thead>',
' <tr>'
]
for col in columns:
lines.append(
f' <th style="padding:4px 8px; text-align:center; font-weight:bold; border:1px solid #ddd;">{header_range(col)}</th>'
)
lines += [' </tr>', ' </thead>', ' <tbody>']

for row in zip_longest(*columns, fillvalue=""):
lines.append(' <tr>')
for cell in row:
if cell:
slug = slugify(cell)
disp = display_name(cell)
lines.append(
f' <td style="padding:4px 8px; text-align:center; border:1px solid #ddd;"><a href="#' + slug + f'">{disp}</a></td>'
)
else:
lines.append(
' <td style="padding:4px 8px; text-align:center; border:1px solid #ddd;"></td>'
)
lines.append(' </tr>')
lines += [' </tbody>', '</table>']
return "\n".join(lines)


def normalize_headers(content):
"""Normalize all headers to slug-based Title Case (no underscores or hyphens)."""
pattern = re.compile(r'^(### \[)([^\]]+)(\]\(\./([^\)]+)\))', re.MULTILINE)

def repl(match):
raw = match.group(4)
# Decode URL-encoded directory name
decoded = urllib.parse.unquote(raw)
# Generate slug and display
slug = slugify(decoded)
disp = header_display_from_slug(slug)
# Keep original raw in link
return f"### [{disp}](./{raw})"

return pattern.sub(repl, content)


def update_toc_in_content(content, new_toc):
"""Replace TOC region in content with new TOC."""
toc_re = re.compile(r"(<!-- TOC START -->)(.*?)(<!-- TOC END -->)", re.S)
return toc_re.sub(rf"\1\n{new_toc}\n\3", content)


def main():
"""Main entry point for the TOC generator."""
readme_path = Path(README)

# Verify README exists
if not readme_path.is_file():
print(f"Error: {README} not found in current directory", file=sys.stderr)
sys.exit(1)

try:
# Collect project directories
projects = collect_projects('.')

if not projects:
print("Warning: No project directories found", file=sys.stderr)

# Build new TOC
new_toc = build_toc_table(projects)

# Read README.md
content = readme_path.read_text(encoding="utf-8")

# Normalize headers
content = normalize_headers(content)

# Replace TOC region
updated = update_toc_in_content(content, new_toc)

# Write back if changed
if updated != content:
readme_path.write_text(updated, encoding="utf-8")
print(f"✅ {README} updated successfully")
else:
lines.append(
' <td style="padding:4px 8px; text-align:center; border:1px solid #ddd;"></td>'
)
lines.append(' </tr>')
lines += [' </tbody>', '</table>']
new_toc = "\n".join(lines)

# Read README.md
with open(README, "r", encoding="utf-8") as f:
content = f.read()

# Normalize all headers to slug-based Title Case (no underscores or hyphens)
pattern = re.compile(r'^(### \[)([^\]]+)(\]\(\./([^\)]+)\))', re.MULTILINE)
def repl(match):
raw = match.group(4)
# Decode URL-encoded directory name
decoded = urllib.parse.unquote(raw)
# Generate slug and display
slug = slugify(decoded)
disp = header_display_from_slug(slug)
# Keep original raw in link
return f"### [{disp}](./{raw})"
content = pattern.sub(repl, content)

# Replace TOC region
toc_re = re.compile(r"(<!-- TOC START -->)(.*?)(<!-- TOC END -->)", re.S)
updated = toc_re.sub(rf"\1\n{new_toc}\n\3", content)

# Write back if changed
if updated != content:
with open(README, "w", encoding="utf-8") as f:
f.write(updated)
print(f"ℹ️ {README} already up to date")

except (OSError, ValueError) as e:
print(f"Error: {e}", file=sys.stderr)
sys.exit(1)


if __name__ == "__main__":
main()
Loading