1""" 2Font Bakery CheckRunner is the driver of a font bakery suite of checks. 3""" 4import glob 5import logging 6import argparse 7from dataclasses import dataclass 8 9from fontbakery.callable import FontBakeryExpectedValue as ExpectedValue 10from fontbakery.profile import Profile 11from fontbakery.errors import ValueValidationError 12 13 14@dataclass 15class FileDescription: 16 name: str 17 extensions: list 18 singular: str 19 description: str 20 21 22class FontsProfile(Profile): 23 accepted_files = [ 24 FileDescription(name="fonts", 25 singular="font", 26 extensions=[".otf",".ttf"], 27 description="OpenType binary"), 28 FileDescription(name="ufos", 29 singular="ufo", 30 extensions=[".ufo"], 31 description="UFO source"), 32 FileDescription(name="designspaces", 33 singular="designspace", 34 extensions=[".designspace"], 35 description="Designspace"), 36 FileDescription(name="glyphs_files", 37 singular="glyphs_file", 38 extensions=[".glyphs"], 39 description="Glyphs source"), 40 FileDescription(name="readme_md", 41 singular="readme_md", 42 extensions=["README.md"], 43 description="Project's README markdown file"), 44 FileDescription(name="metadata_pb", 45 singular="metadata_pb", 46 extensions=["METADATA.pb"], 47 description="Project's METADATA protobuf file"), 48 ] 49 50 def setup_argparse(self, argument_parser): 51 """ 52 Set up custom arguments needed for this profile. 53 """ 54 profile = self 55 56 def get_files(pattern): 57 files_to_check = [] 58 # use glob.glob to accept *.ttf 59 # but perform a hacky fixup to workaround the square-brackets naming scheme 60 # currently in use for varfonts in google fonts... 61 if '].ttf' in pattern: 62 pattern = "*.ttf".join(pattern.split('].ttf')) 63 64 # Everything goes in for now, gets sorted in the Merge 65 for fullpath in glob.glob(pattern): 66 files_to_check.append(fullpath) 67 return files_to_check 68 69 70 class MergeAction(argparse.Action): 71 def __call__(self, parser, namespace, values, option_string=None): 72 for file_description in profile.accepted_files: 73 setattr(namespace, file_description.name, []) 74 target = [item for l in values for item in l] 75 any_accepted = False 76 for file in target: 77 accepted = False 78 for file_description in profile.accepted_files: 79 if any([file.endswith(extension) for extension in file_description.extensions]): 80 setattr(namespace, file_description.name, getattr(namespace, file_description.name) + [file]) 81 accepted = True 82 any_accepted = True 83 if not accepted: 84 logging.info(f"Skipping '{file}' as it does not" 85 f" seem to be accepted by this profile.") 86 if not any_accepted: 87 raise ValueValidationError('No applicable files found') 88 89 argument_parser.add_argument( 90 'files', 91 # To allow optional commands like "-L" to work without other input 92 # files: 93 nargs='*', 94 type=get_files, 95 action=MergeAction, 96 help='file path(s) to check. Wildcards like *.ttf are allowed.') 97 98 return tuple(x.name for x in self.accepted_files) 99 100 def get_family_checks(self): 101 family_checks = self.get_checks_by_dependencies('fonts') 102 family_checks.extend(self.get_checks_by_dependencies('ttFonts')) 103 return family_checks 104 105 @classmethod 106 def _expected_values(cls): 107 return { val.name: 108 ExpectedValue(val.name, 109 default = [], 110 description = f"A list of the {val.description} file paths to check", 111 force=True 112 ) 113 for val in cls.accepted_files 114 } 115 116 @classmethod 117 def _iterargs(cls): 118 return { val.singular: val.name for val in cls.accepted_files } 119 120 121def profile_factory(**kwds): 122 from fontbakery.profiles.shared_conditions import ttFont 123 profile = FontsProfile( 124 iterargs=FontsProfile._iterargs() 125 , conditions={ttFont.name: ttFont} 126 , derived_iterables={'ttFonts': ('ttFont', True)} 127 , expected_values=FontsProfile._expected_values() 128 , **kwds 129 ) 130 return profile 131