#!/usr/bin/python3 import subprocess import re import os import argparse from collections import defaultdict from enum import Enum from glob import glob class CheckType(Enum): NEW_FAMILY = 1 MODIFIED_FAMILY = 2 MODIFIED_FAMILY_METADATA = 3 DESIGNER = 4 class CheckToken(Enum): NEW_FONT = 1 DESIGNER = 2 FONT_FAMILY = 3 MODIFIED_FONTS = 4 MODIFIED_METADATA = 5 def _parse_git_diff(diff_info: str) -> list[tuple[str, str]]: """ ''' A ofl/mavenpro/MavenPro[wght].ttf M ofl/mavenpro/METADATA.pb ''' -> [ ("A", "ofl/mavenpro/MavenPro[wght].ttf"), ("M", "ofl/mavenpro/METADATA.pb") ] """ parsed = re.findall(r"([A|M|D])(\t)(.*)", diff_info) return [(s, p) for s, _, p in parsed] def directory_check_types(branch="origin/main"): git_diff_text = subprocess.run( ["git", "diff", branch, "--name-status"], capture_output=True ).stdout.decode("utf-8") git_diff = _parse_git_diff(git_diff_text) # Tokenize each directory git diff has reported directories_to_check = defaultdict(set) for state, path in git_diff: dirpath = ( os.path.dirname(path) if path not in ("to_sandbox.txt", "to_production.txt") else path ) dir_tokens = directories_to_check[dirpath] if path.startswith("catalog"): dir_tokens.add(CheckToken.DESIGNER) if path.startswith(("ofl", "ufl", "apache")): dir_tokens.add(CheckToken.FONT_FAMILY) if path.endswith((".ttf", ".otf")) and state == "A": dir_tokens.add(CheckToken.NEW_FONT) if path.endswith((".ttf", ".otf")) and (state == "M" or state == "D"): dir_tokens.add(CheckToken.MODIFIED_FONTS) if path.endswith((".txt", ".pb", ".html")) and state == "M": dir_tokens.add(CheckToken.MODIFIED_METADATA) # Set each directory's check type results = [] for path, tokens in directories_to_check.items(): if CheckToken.FONT_FAMILY in tokens: if CheckToken.MODIFIED_FONTS in tokens: results.append((path, CheckType.MODIFIED_FAMILY)) elif CheckToken.MODIFIED_METADATA in tokens: results.append((path, CheckType.MODIFIED_FAMILY_METADATA)) else: results.append((path, CheckType.NEW_FAMILY)) if CheckToken.DESIGNER in tokens: results.append((path, CheckType.DESIGNER)) return results def main(): parser = argparse.ArgumentParser() parser.add_argument( "--branch", default="origin/main", help="branch to compare current head against" ) parser.add_argument( "--render", action="store_true", help="Check rendering of families only" ) parser.add_argument("--pr-number", help="PR to output fontbakery report to") parser.add_argument("--pr-url-body", default="https://www.github.com/google/fonts/pull/%s") args = parser.parse_args() profile_test_file = os.path.join(os.path.dirname(__file__), "test_profiles.py") for directory, check_type in directory_check_types(args.branch): out = os.path.join("out", os.path.basename(directory)) fonts = glob(os.path.join(directory, "*.ttf")) qa_cmd_prefix = ["gftools", "qa", "-f"] + fonts + ["-o", out] if args.pr_number: if not args.pr_url_body.endswith("/"): args.pr_url_body += "/" url = "%s%s" % (args.pr_url_body, args.pr_number) qa_cmd_prefix += ["--out-url", url] if args.render and check_type == CheckType.NEW_FAMILY: print(f"Rendering new family: {directory}") subprocess.run(qa_cmd_prefix + ["-gfb", "--render", "--imgs"]) elif args.render and check_type == CheckType.MODIFIED_FAMILY: print(f"Rendering modified family: {directory}") subprocess.run(qa_cmd_prefix + ["-gfb", "--render", "--imgs"]) # we only want args.render to do the above two conditions elif args.render: continue elif check_type == CheckType.NEW_FAMILY: print(f"Checking new family: {directory}") subprocess.run(qa_cmd_prefix + ["--fontbakery"]) elif check_type == CheckType.MODIFIED_FAMILY: print(f"Checking modified family: {directory}") subprocess.run(qa_cmd_prefix + ["-gfb", "--fontbakery", "--diffenator"]) elif check_type == CheckType.MODIFIED_FAMILY_METADATA: print(f"Checking modified family metadata: {directory}") subprocess.run(qa_cmd_prefix + ["--fontbakery", "-o", out]) elif check_type == CheckType.DESIGNER: print(f"Checking designer profile: {directory}") subprocess.run(["pytest", profile_test_file, directory]) else: print(f"Skipping directory {directory}") if __name__ == "__main__": main()