Formulae can't install to ~/Library/Fonts/ due to Homebrew sandboxing. Casks have a built-in `font` artifact that handles this automatically. - Replace Formula/ with Casks/ directory - Rewrite generator to produce cask files instead of formulae - Add .ttc (TrueType Collection) support - Update all tests for cask format - Update CLI and documentation - Fonts with no installable files (TTF/OTF/TTC) are skipped Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
123 lines
4.2 KiB
Python
123 lines
4.2 KiB
Python
"""add-font CLI: add or update a font in the tap, then run cleanup, cask generator, and tests."""
|
|
import re
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
|
|
def _repo_root() -> Path:
|
|
"""Repository root (directory containing font_files/ and Casks/)."""
|
|
root = Path(__file__).resolve().parent.parent
|
|
assert (root / "font_files").exists(), f"Repo root not found: {root}"
|
|
return root
|
|
|
|
|
|
def _normalize_to_font_name(name: str) -> str:
|
|
"""Return font-<name> with lowercase, spaces/hyphens normalized."""
|
|
name = name.strip().lower().replace(" ", "-")
|
|
# Collapse multiple hyphens
|
|
name = re.sub(r"-+", "-", name).strip("-")
|
|
if not name.startswith("font-"):
|
|
name = f"font-{name}"
|
|
return name
|
|
|
|
|
|
def _infer_font_name_from_path(path: Path) -> str:
|
|
"""Infer font-<name> from a folder or archive path."""
|
|
name = path.name
|
|
# Strip common suffixes
|
|
for suf in (".zip", ".tar.gz", ".tgz", ".tar"):
|
|
if name.endswith(suf):
|
|
name = name[: -len(suf)]
|
|
break
|
|
return _normalize_to_font_name(name.replace("font-", "", 1) if name.lower().startswith("font-") else name)
|
|
|
|
|
|
def _run(cmd: list[str], cwd: Path | None = None) -> int:
|
|
"""Run command; return exit code."""
|
|
r = subprocess.run(cmd, cwd=cwd or _repo_root())
|
|
return r.returncode
|
|
|
|
|
|
def main() -> None:
|
|
import argparse
|
|
|
|
parser = argparse.ArgumentParser(
|
|
description="Add or update a font in the tap: run cleanup, generate cask, run tests."
|
|
)
|
|
parser.add_argument(
|
|
"path_or_name",
|
|
help="Path to an unpacked font folder (or archive) or font name (e.g. font-acrylic-hand or acrylic-hand)",
|
|
)
|
|
parser.add_argument(
|
|
"--no-test",
|
|
action="store_true",
|
|
help="Skip running the test suite after changes",
|
|
)
|
|
parser.add_argument(
|
|
"--no-audit",
|
|
action="store_true",
|
|
help="Skip brew audit (default: run audit for touched formula)",
|
|
)
|
|
args = parser.parse_args()
|
|
root = _repo_root()
|
|
font_files_dir = root / "font_files"
|
|
script_dir = root / ".fontfoldercleanup"
|
|
|
|
path_arg = Path(args.path_or_name)
|
|
if path_arg.exists() and path_arg.is_dir():
|
|
# Add from path: copy into font_files/font-<name>
|
|
font_name = _infer_font_name_from_path(path_arg)
|
|
dest = font_files_dir / font_name
|
|
if dest.resolve() == path_arg.resolve():
|
|
# Already in place
|
|
pass
|
|
else:
|
|
if dest.exists():
|
|
print(f"Destination exists: {dest}; merging by copying new files.")
|
|
for item in path_arg.iterdir():
|
|
dest_item = dest / item.name
|
|
if item.is_dir():
|
|
if dest_item.exists():
|
|
shutil.copytree(item, dest_item, dirs_exist_ok=True)
|
|
else:
|
|
shutil.copytree(item, dest_item)
|
|
else:
|
|
shutil.copy2(item, dest_item)
|
|
else:
|
|
shutil.copytree(path_arg, dest)
|
|
print(f"Copied to {dest}")
|
|
else:
|
|
# Treat as font name
|
|
font_name = _normalize_to_font_name(args.path_or_name)
|
|
dest = font_files_dir / font_name
|
|
if not dest.is_dir():
|
|
print(f"Error: no folder at {dest}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
# Run cleanup on font_files
|
|
cleanup_script = script_dir / "cleanup_font_folders.py"
|
|
font_files_abs = str(font_files_dir.resolve())
|
|
if _run([sys.executable, str(cleanup_script), "--path", font_files_abs]) != 0:
|
|
sys.exit(1)
|
|
|
|
# Run cask generator
|
|
cask_script = script_dir / "create_homebrew_formula.py"
|
|
if _run([sys.executable, str(cask_script)]) != 0:
|
|
sys.exit(1)
|
|
|
|
if not args.no_test:
|
|
# Run pytest
|
|
if _run([sys.executable, "-m", "pytest", "tests/", "-v", "--tb=short"]) != 0:
|
|
print("Tests failed.", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
if not args.no_audit:
|
|
cask_path = root / "Casks" / f"{font_name}.rb"
|
|
if cask_path.exists():
|
|
code = _run(["brew", "audit", "--cask", str(cask_path)])
|
|
if code != 0:
|
|
print("brew audit reported issues (non-fatal).", file=sys.stderr)
|
|
|
|
print(f"Done: {font_name}")
|