Update documentation and scripts for font addition process; introduce uv run add-font CLI command for streamlined font management. Enhance formula generation with improved class name formatting and validation steps. Remove outdated font formula files.
This commit is contained in:
parent
69d8156b09
commit
56b64d0b34
266 changed files with 1187 additions and 8145 deletions
42
tests/conftest.py
Normal file
42
tests/conftest.py
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
"""Shared fixtures and repo paths for font tap tests."""
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
# Repo root: directory containing font_files/ and Formula/
|
||||
REPO_ROOT = Path(__file__).resolve().parent.parent
|
||||
FONT_FILES_DIR = REPO_ROOT / "font_files"
|
||||
FORMULA_DIR = REPO_ROOT / "Formula"
|
||||
|
||||
REQUIRED_SUBDIRS = ("ttf", "otf", "web", "other_files")
|
||||
WEB_EXTENSIONS = (".woff", ".woff2", ".eot", ".svg")
|
||||
|
||||
|
||||
def get_font_dir_names():
|
||||
"""Return sorted list of font-* directory names in font_files/ (single source of truth)."""
|
||||
if not FONT_FILES_DIR.exists():
|
||||
return []
|
||||
return sorted(
|
||||
d.name for d in FONT_FILES_DIR.iterdir() if d.is_dir() and d.name.startswith("font-")
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def repo_root():
|
||||
return REPO_ROOT
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def font_dir_names():
|
||||
"""All font folder names (font-<name>) from font_files/."""
|
||||
names = get_font_dir_names()
|
||||
assert names, "No font-* directories found in font_files/"
|
||||
return names
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def formula_paths():
|
||||
"""All Formula/font-*.rb paths that exist."""
|
||||
if not FORMULA_DIR.exists():
|
||||
return []
|
||||
return sorted(FORMULA_DIR.glob("font-*.rb"))
|
||||
41
tests/test_font_structure.py
Normal file
41
tests/test_font_structure.py
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
"""Tests for font folder structure: required subdirs and at least one font file per font."""
|
||||
import pytest
|
||||
|
||||
from tests.conftest import FONT_FILES_DIR, REQUIRED_SUBDIRS, WEB_EXTENSIONS, get_font_dir_names
|
||||
|
||||
|
||||
# Top-level names to ignore (e.g. .DS_Store; cleanup script removes these)
|
||||
IGNORED_TOPLEVEL = {".DS_Store"}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("font_name", get_font_dir_names())
|
||||
def test_font_has_required_subdirs_only(font_name):
|
||||
"""Each font folder has exactly ttf/, otf/, web/, other_files/ and no other top-level items (except ignored)."""
|
||||
font_dir = FONT_FILES_DIR / font_name
|
||||
assert font_dir.is_dir(), f"Font dir missing: {font_dir}"
|
||||
names = [p.name for p in font_dir.iterdir() if p.name not in IGNORED_TOPLEVEL]
|
||||
for required in REQUIRED_SUBDIRS:
|
||||
assert required in names, f"{font_name}: missing subdir {required}/"
|
||||
# All must be directories (the four subdirs)
|
||||
for name in names:
|
||||
assert (font_dir / name).is_dir(), f"{font_name}: unexpected file or non-dir at top level: {name}"
|
||||
assert len(names) == 4, f"{font_name}: expected exactly 4 subdirs, got {names}"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("font_name", get_font_dir_names())
|
||||
def test_font_has_at_least_one_font_file_or_other(font_name):
|
||||
"""Each font folder has at least one font file (ttf/otf/web) or content in other_files."""
|
||||
font_dir = FONT_FILES_DIR / font_name
|
||||
has_ttf = any((font_dir / "ttf").glob("*.ttf"))
|
||||
has_otf = any((font_dir / "otf").glob("*.otf"))
|
||||
web_dir = font_dir / "web"
|
||||
has_web = any(
|
||||
f.suffix.lower() in WEB_EXTENSIONS
|
||||
for f in web_dir.glob("*")
|
||||
if f.is_file()
|
||||
)
|
||||
other_dir = font_dir / "other_files"
|
||||
has_other = any(other_dir.iterdir()) if other_dir.exists() else False
|
||||
assert has_ttf or has_otf or has_web or has_other, (
|
||||
f"{font_name}: no font files in ttf/, otf/, or web/ and no content in other_files/"
|
||||
)
|
||||
42
tests/test_formula_content.py
Normal file
42
tests/test_formula_content.py
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
"""Tests for formula content: correct paths and valid Ruby class name."""
|
||||
import re
|
||||
|
||||
import pytest
|
||||
|
||||
from tests.conftest import FONT_FILES_DIR, FORMULA_DIR, get_font_dir_names
|
||||
|
||||
|
||||
def formula_name_to_class(formula_name: str) -> str:
|
||||
"""Same logic as generator: formula name to PascalCase (no hyphens)."""
|
||||
parts = re.split(r"[-_]+", formula_name)
|
||||
return "".join(p.capitalize() for p in parts if p)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("font_name", get_font_dir_names())
|
||||
def test_formula_references_correct_font_path(font_name):
|
||||
"""Generated formula contains the correct font_files/font-<name>/ path."""
|
||||
formula_path = FORMULA_DIR / f"{font_name}.rb"
|
||||
content = formula_path.read_text()
|
||||
# Install block should reference this font path
|
||||
assert f"font_files/{font_name}/" in content, (
|
||||
f"{font_name}: formula does not reference font_files/{font_name}/"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("font_name", get_font_dir_names())
|
||||
def test_formula_class_name_valid_and_matches(font_name):
|
||||
"""Formula defines class Font<PascalCase> with no hyphens (valid Ruby)."""
|
||||
formula_path = FORMULA_DIR / f"{font_name}.rb"
|
||||
content = formula_path.read_text()
|
||||
formula_name = font_name.replace("font-", "", 1)
|
||||
expected_class = "Font" + formula_name_to_class(formula_name)
|
||||
# Class line: class FontSomething < Formula
|
||||
match = re.search(r"class\s+(Font\w+)\s+<\s+Formula", content)
|
||||
assert match, f"{font_name}: no 'class Font... < Formula' found"
|
||||
actual_class = match.group(1)
|
||||
assert "-" not in actual_class, (
|
||||
f"{font_name}: Ruby class name must not contain hyphens (got {actual_class})"
|
||||
)
|
||||
assert actual_class == expected_class, (
|
||||
f"{font_name}: expected class {expected_class}, got {actual_class}"
|
||||
)
|
||||
14
tests/test_formula_exists.py
Normal file
14
tests/test_formula_exists.py
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
"""Tests that a Formula file exists for every font."""
|
||||
import pytest
|
||||
|
||||
from tests.conftest import FORMULA_DIR, get_font_dir_names
|
||||
|
||||
|
||||
@pytest.mark.parametrize("font_name", get_font_dir_names())
|
||||
def test_formula_file_exists(font_name):
|
||||
"""Formula/font-<name>.rb exists for each font folder."""
|
||||
# font_name is e.g. "font-acrylic-hand"; formula file is font-acrylic-hand.rb
|
||||
formula_path = FORMULA_DIR / f"{font_name}.rb"
|
||||
assert formula_path.is_file(), (
|
||||
f"Missing formula for {font_name}: expected {formula_path}"
|
||||
)
|
||||
105
tests/test_install_works.py
Normal file
105
tests/test_install_works.py
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
"""Verify that each font's formula install logic would copy the right files (simulated install)."""
|
||||
import shutil
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
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-main/font_files/<font_name>/).
|
||||
"""
|
||||
# Layout formula expects: homebrew-fonts-main/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)
|
||||
|
||||
|
||||
@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)
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
prefix = Path(tmp) / "prefix"
|
||||
prefix.mkdir()
|
||||
_run_install_simulation(font_name, font_dir, prefix, formula_name)
|
||||
|
||||
# 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}"
|
||||
)
|
||||
34
tests/test_no_orphan_formulae.py
Normal file
34
tests/test_no_orphan_formulae.py
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
"""Global tests: no orphan formulae, no duplicate formula names."""
|
||||
import pytest
|
||||
|
||||
from tests.conftest import FONT_FILES_DIR, FORMULA_DIR, get_font_dir_names
|
||||
|
||||
|
||||
def test_every_formula_has_matching_font_folder():
|
||||
"""Every Formula/font-*.rb has a matching font_files/font-<name>/ directory."""
|
||||
formula_files = sorted(FORMULA_DIR.glob("font-*.rb"))
|
||||
for formula_path in formula_files:
|
||||
name = formula_path.stem # e.g. font-acrylic-hand
|
||||
font_dir = FONT_FILES_DIR / name
|
||||
assert font_dir.is_dir(), (
|
||||
f"Orphan formula: {formula_path.name} has no matching font folder {font_dir}"
|
||||
)
|
||||
|
||||
|
||||
def test_no_duplicate_formula_names():
|
||||
"""Only one formula file per font-<name> (no duplicate basenames)."""
|
||||
formula_files = list(FORMULA_DIR.glob("font-*.rb"))
|
||||
names = [p.stem for p in formula_files]
|
||||
seen = set()
|
||||
for n in names:
|
||||
assert n not in seen, f"Duplicate formula name: {n}.rb"
|
||||
seen.add(n)
|
||||
|
||||
|
||||
def test_formula_count_matches_font_count():
|
||||
"""Number of font-*.rb formulae equals number of font-* directories."""
|
||||
font_names = get_font_dir_names()
|
||||
formula_count = len(list(FORMULA_DIR.glob("font-*.rb")))
|
||||
assert formula_count == len(font_names), (
|
||||
f"Formula count ({formula_count}) != font dir count ({len(font_names)})"
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue