mirror of
https://github.com/cporter202/coreclaw-api-directory.git
synced 2026-08-03 21:15:24 +02:00
Redesign API directory experience
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import csv
|
||||
import re
|
||||
import sys
|
||||
import xml.etree.ElementTree as ET
|
||||
from collections import Counter
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
CSV_PATH = ROOT / "data" / "workers.csv"
|
||||
README_PATH = ROOT / "README.md"
|
||||
BANNER_PATH = ROOT / "assets" / "coreclaw-api-directory-banner.svg"
|
||||
|
||||
REQUIRED_FIELDS = {
|
||||
"title",
|
||||
"path",
|
||||
"url",
|
||||
"category",
|
||||
"source",
|
||||
"platform",
|
||||
"author",
|
||||
"description",
|
||||
}
|
||||
AFFILIATE_URL = re.compile(
|
||||
r"^https://www\.coreclaw\.com/[^/?]+/[^/?]+\?fpr=chris69$"
|
||||
)
|
||||
|
||||
|
||||
def fail(message: str) -> None:
|
||||
print(f"ERROR: {message}", file=sys.stderr)
|
||||
raise SystemExit(1)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
if not CSV_PATH.is_file():
|
||||
fail(f"missing {CSV_PATH.relative_to(ROOT)}")
|
||||
if not README_PATH.is_file():
|
||||
fail("missing README.md")
|
||||
if not BANNER_PATH.is_file():
|
||||
fail(f"missing {BANNER_PATH.relative_to(ROOT)}")
|
||||
|
||||
with CSV_PATH.open(encoding="utf-8", newline="") as handle:
|
||||
reader = csv.DictReader(handle)
|
||||
fields = set(reader.fieldnames or [])
|
||||
missing_fields = REQUIRED_FIELDS - fields
|
||||
if missing_fields:
|
||||
fail(f"CSV is missing fields: {', '.join(sorted(missing_fields))}")
|
||||
rows = list(reader)
|
||||
|
||||
if not rows:
|
||||
fail("catalog is empty")
|
||||
|
||||
paths = [row["path"].strip() for row in rows]
|
||||
duplicate_paths = sorted(path for path, count in Counter(paths).items() if count > 1)
|
||||
if duplicate_paths:
|
||||
fail(f"duplicate Worker paths: {', '.join(duplicate_paths)}")
|
||||
|
||||
for index, row in enumerate(rows, start=2):
|
||||
empty = sorted(field for field in REQUIRED_FIELDS if not row[field].strip())
|
||||
if empty:
|
||||
fail(f"CSV row {index} has empty fields: {', '.join(empty)}")
|
||||
if not AFFILIATE_URL.fullmatch(row["url"].strip()):
|
||||
fail(f"CSV row {index} has an invalid affiliate URL: {row['url']}")
|
||||
|
||||
readme = README_PATH.read_text(encoding="utf-8")
|
||||
missing_from_readme = [path for path in paths if path not in readme]
|
||||
if missing_from_readme:
|
||||
fail(f"README is missing {len(missing_from_readme)} Worker paths")
|
||||
|
||||
try:
|
||||
root = ET.parse(BANNER_PATH).getroot()
|
||||
except ET.ParseError as error:
|
||||
fail(f"banner SVG is invalid: {error}")
|
||||
if not root.tag.endswith("svg"):
|
||||
fail("banner asset is not an SVG document")
|
||||
|
||||
category_counts = Counter(row["category"].strip() for row in rows)
|
||||
source_count = len({row["source"].strip() for row in rows})
|
||||
print(
|
||||
f"Catalog valid: {len(rows)} Workers, "
|
||||
f"{len(category_counts)} categories, {source_count} sources."
|
||||
)
|
||||
for category, count in sorted(category_counts.items()):
|
||||
print(f" {count:>3} {category}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user