Convert from Homebrew Formulae to Casks for font installation

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>
This commit is contained in:
Matt Troutman 2026-03-07 21:38:59 -06:00
parent cb1918c30f
commit 76743cdc4d
No known key found for this signature in database
273 changed files with 2509 additions and 10048 deletions

View file

@ -1,105 +1,25 @@
"""Verify that each font's formula install logic would copy the right files (simulated install)."""
import shutil
import tempfile
from pathlib import Path
"""Verify that each font's cask references valid font files that exist."""
import re
import pytest
from tests.conftest import (
FONT_FILES_DIR,
FORMULA_DIR,
WEB_EXTENSIONS,
get_font_dir_names,
)
def _run_install_simulation(font_name: str, font_dir: Path, prefix: Path, formula_name: str) -> None:
"""
Simulate the formula's install: copy ttf/otf/web/other_files to prefix
as the formula would (same layout as homebrew-fonts/font_files/<font_name>/).
"""
# Layout formula expects: homebrew-fonts/font_files/<font_name>/{ttf,otf,web,other_files}
# We use font_dir directly (same layout).
(prefix / "fonts").mkdir(parents=True)
(prefix / "fonts/truetype").mkdir(parents=True)
(prefix / "fonts/opentype").mkdir(parents=True)
(prefix / "fonts/webfonts").mkdir(parents=True)
(prefix / formula_name).mkdir(parents=True)
for f in (font_dir / "ttf").glob("*.ttf"):
shutil.copy2(f, prefix / "fonts/truetype" / f.name)
for f in (font_dir / "otf").glob("*.otf"):
shutil.copy2(f, prefix / "fonts/opentype" / f.name)
web_dir = font_dir / "web"
for f in web_dir.iterdir():
if f.is_file() and f.suffix.lower() in WEB_EXTENSIONS:
shutil.copy2(f, prefix / "fonts/webfonts" / f.name)
other_src = font_dir / "other_files"
if other_src.exists():
for f in other_src.iterdir():
dest = prefix / formula_name / f.name
if f.is_dir():
shutil.copytree(f, dest, dirs_exist_ok=True)
else:
shutil.copy2(f, dest)
from tests.conftest import CASKS_DIR, FONT_FILES_DIR, get_font_dir_names
@pytest.mark.parametrize("font_name", get_font_dir_names())
def test_install_simulation_places_files(font_name):
"""Simulate install for this font and assert expected files are present under prefix."""
font_dir = FONT_FILES_DIR / font_name
formula_name = font_name.replace("font-", "", 1)
def test_cask_font_artifacts_are_installable(font_name):
"""Each font artifact in the cask points to a real TTF or OTF file."""
cask_path = CASKS_DIR / f"{font_name}.rb"
if not cask_path.exists():
pytest.skip(f"No cask for {font_name} (no TTF/OTF files)")
with tempfile.TemporaryDirectory() as tmp:
prefix = Path(tmp) / "prefix"
prefix.mkdir()
_run_install_simulation(font_name, font_dir, prefix, formula_name)
content = cask_path.read_text()
paths = re.findall(r'^\s*font\s+"([^"]+)"', content, re.MULTILINE)
assert paths, f"{font_name}: cask has no font artifacts"
# Same assertions as the formula's test block: at least one expected path has content
ttf_files = list((prefix / "fonts/truetype").glob("*.ttf"))
otf_files = list((prefix / "fonts/opentype").glob("*.otf"))
web_files = list((prefix / "fonts/webfonts").iterdir()) if (prefix / "fonts/webfonts").exists() else []
other_dir = prefix / formula_name
has_other = other_dir.exists() and any(other_dir.iterdir())
has_ttf = len(ttf_files) > 0
has_otf = len(otf_files) > 0
has_web = len(web_files) > 0
assert has_ttf or has_otf or has_web or has_other, (
f"{font_name}: after simulated install, no files in share/fonts/truetype, "
f"share/fonts/opentype, share/fonts/webfonts, or share/{formula_name}"
)
@pytest.mark.parametrize("font_name", get_font_dir_names())
def test_install_simulation_file_counts_match_source(font_name):
"""After simulated install, number of installed font files matches font_files/."""
font_dir = FONT_FILES_DIR / font_name
formula_name = font_name.replace("font-", "", 1)
with tempfile.TemporaryDirectory() as tmp:
prefix = Path(tmp) / "prefix"
prefix.mkdir()
_run_install_simulation(font_name, font_dir, prefix, formula_name)
ttf_src = len(list((font_dir / "ttf").glob("*.ttf")))
otf_src = len(list((font_dir / "otf").glob("*.otf")))
web_src = sum(
1 for f in (font_dir / "web").iterdir()
if f.is_file() and f.suffix.lower() in WEB_EXTENSIONS
)
ttf_installed = len(list((prefix / "fonts/truetype").glob("*.ttf")))
otf_installed = len(list((prefix / "fonts/opentype").glob("*.otf")))
web_installed = len(list((prefix / "fonts/webfonts").iterdir()))
assert ttf_src == ttf_installed, (
f"{font_name}: TTF count mismatch: source={ttf_src}, installed={ttf_installed}"
)
assert otf_src == otf_installed, (
f"{font_name}: OTF count mismatch: source={otf_src}, installed={otf_installed}"
)
assert web_src == web_installed, (
f"{font_name}: web font count mismatch: source={web_src}, installed={web_installed}"
for rel_path in paths:
full_path = FONT_FILES_DIR.parent / rel_path
assert full_path.is_file(), f"{font_name}: missing font file: {rel_path}"
assert full_path.suffix.lower() in (".ttf", ".otf", ".ttc"), (
f"{font_name}: unexpected font type: {full_path.suffix}"
)