def main(specification=None, values=None): # this won't be used in check-googlefonts add_spec_arg = False if specification is None: specification = get_spec() add_spec_arg = True argument_parser = ArgumentParser(specification, spec_arg=add_spec_arg) args = argument_parser.parse_args() runner = runner_factory(specification, args.fonts, explicit_tests=args.checkid, custom_order=args.order, values=values) # the most verbose loglevel wins loglevel = min(args.loglevels) if args.loglevels else DEFAULT_LOG_LEVEL tr = TerminalReporter(runner=runner, is_async=False, print_progress=not args.no_progress, test_threshold=loglevel, log_threshold=args.loglevel_messages or loglevel, usecolor=not args.no_colors, collect_results_by=args.gather_by) reporters = [tr.receive] if args.json: sr = SerializeReporter(runner=runner, collect_results_by=args.gather_by) reporters.append(sr.receive) distribute_generator(runner.run(), reporters) if args.json: import json json.dump(sr.getdoc(), args.json, sort_keys=True, indent=4)
def main(specification=None, values=None): # specification can be injected by e.g. check-googlefonts injects it's own spec add_spec_arg = False if specification is None: specification = get_spec() add_spec_arg = True argument_parser, values_keys = ArgumentParser(specification, spec_arg=add_spec_arg) args = argument_parser.parse_args() if args.list_checks: for section_name, section in specification._sections.items(): checks = section.list_checks() sys.exit("Available checks on {} are:\n{}".format( section_name, "\n".join(checks))) values_ = {} if values is not None: values_.update(values) # values_keys are returned by specification.setup_argparse # these are keys for custom arguments required by the spec. if values_keys: for key in values_keys: if hasattr(args, key): values_[key] = getattr(args, key) runner = runner_factory(specification, explicit_checks=args.checkid, custom_order=args.order, values=values_) # the most verbose loglevel wins loglevel = min(args.loglevels) if args.loglevels else DEFAULT_LOG_LEVEL tr = TerminalReporter(runner=runner, is_async=False, print_progress=not args.no_progress, check_threshold=loglevel, log_threshold=args.loglevel_messages or loglevel, usecolor=not args.no_colors, collect_results_by=args.gather_by) reporters = [tr.receive] if args.json: sr = SerializeReporter(runner=runner, collect_results_by=args.gather_by) reporters.append(sr.receive) distribute_generator(runner.run(), reporters) if args.json: import json json.dump(sr.getdoc(), args.json, sort_keys=True, indent=4)
def doit(args): logger = args.logger htmlfile = args.html if args.ttfaudit: # Special action to compare checks in profile against check_list values audit(args.fonts, logger) # args.fonts used as output file name for audit return # Process list of fonts supplied, expanding wildcards using glob if needed fonts = [] fontstype = None for pattern in args.fonts: for fullpath in glob.glob(pattern): ftype = fullpath.lower().rsplit(".", 1)[-1] if ftype == "otf": ftype = "ttf" if ftype not in ("ttf", "ufo"): logger.log( "Fonts must be OpenType or UFO - " + fullpath + " invalid", "S") if fontstype is None: fontstype = ftype else: if ftype != fontstype: logger.log( "All fonts must be of the same type - both UFO and ttf/otf fonts supplied", "S") fonts.append(fullpath) if fonts == []: logger.log( "No files match the filespec provided for fonts: " + str(args.fonts), "S") # Create the profile object if args.profile: proname = args.profile else: if fontstype == "ttf": proname = "silfont.fbtests.ttfchecks" else: logger.log("UFO fonts not yet supported", "S") try: module = get_module(proname) except Exception as e: logger.log("Failed to import profile: " + proname + "\n" + str(e), "S") profile = get_module_profile(module) psfcheck_list = module.psfcheck_list # Create the runner and reporter objetcs, then run the tests runner = CheckRunner(profile, values={"fonts": fonts}) sr = SerializeReporter( runner=runner ) # This produces results from all the tests in sr.getdoc for later analysis reporters = [sr.receive] if htmlfile: hr = HTMLReporter(runner=runner, loglevels=[SKIP]) reporters.append(hr.receive) distribute_generator(runner.run(), reporters) # Process the results results = sr.getdoc() sections = results["sections"] checks = {} maxname = 11 somedebug = False overrides = {} tempoverrides = False for section in sections: secchecks = section["checks"] for check in secchecks: checkid = check["key"][1][17:-1] fontfile = check[ "filename"] if "filename" in check else "Family-wide" path, fontname = os.path.split(fontfile) if fontname not in checks: checks[fontname] = { "ERROR": [], "FAIL": [], "WARN": [], "INFO": [], "SKIP": [], "PASS": [], "DEBUG": [] } if len(fontname) > maxname: maxname = len(fontname) status = check["logs"][0]["status"] if checkid in psfcheck_list: # Look for status overrides (changetype, temp) = ("temp_change_status", True) if "temp_change_status" in psfcheck_list[checkid]\ else ("change_status", False) if changetype in psfcheck_list[checkid]: change_status = psfcheck_list[checkid][changetype] if status in change_status: reason = change_status[ "reason"] if "reason" in change_status else None overrides[fontname + ", " + checkid] = (status + " to " + change_status[status], temp, reason) if temp: tempoverrides = True status = change_status[status] checks[fontname][status].append(check) if status == "DEBUG": somedebug = True if htmlfile: logger.log("Writing results to " + htmlfile, "P") with open(htmlfile, 'w') as hfile: hfile.write(hr.get_html()) fbstats = ["ERROR", "FAIL", "WARN", "INFO", "SKIP", "PASS"] psflevels = ["E", "E", "W", "I", "I", "V"] if somedebug: # Only have debug column if some debug statuses are present fbstats.append("DEBUG") psflevels.append("W") wrapper = TextWrapper(width=120, initial_indent=" ", subsequent_indent=" ") errorcnt = 0 failcnt = 0 summarymess = "Check status summary:\n" summarymess += "{:{pad}}ERROR FAIL WARN INFO SKIP PASS".format( "", pad=maxname + 4) if somedebug: summarymess += " DEBUG" fontlist = list(sorted(x for x in checks if x != "Family-wide")) + [ "Family-wide" ] # Sort with Family-wide last for fontname in fontlist: summarymess += "\n {:{pad}}".format(fontname, pad=maxname) for i, status in enumerate(fbstats): psflevel = psflevels[i] checklist = checks[fontname][status] cnt = len(checklist) if cnt > 0 or status != "DEBUG": summarymess += "{:6d}".format(cnt) # Suppress 0 for DEBUG if cnt: if status == "ERROR": errorcnt += cnt if status == "FAIL": failcnt += cnt messparts = [ "Checks with status {} for {}".format(status, fontname) ] for check in checklist: messparts.append(" > {}".format(check["key"][1][17:-1])) messparts += wrapper.wrap(check["logs"][0]["message"]) logger.log("\n".join(messparts), psflevel) if overrides != {}: summarymess += "\n Note: " + str( len(overrides) ) + " Fontbakery statuses were overridden - see log file for details" if tempoverrides: summarymess += "\n ******** Some of the overrides were temporary overrides ********" logger.log(summarymess, "P") if overrides != {}: for oname in overrides: override = overrides[oname] mess = "Status override for " + oname + ": " + override[0] if override[1]: mess += " (Temporary override)" logger.log(mess, "W") if override[2] is not None: logger.log("Override reason: " + override[2], "I") if errorcnt + failcnt > 0: mess = str( failcnt) + " test(s) gave a status of FAIL" if failcnt > 0 else "" if errorcnt > 0: if failcnt > 0: mess += "\n " mess += str(errorcnt) + " test(s) gave a status of ERROR which means they failed to execute properly." \ "\n " \ " ERROR probably indicates a software issue rather than font issue" logger.log(mess, "S")
def main(profile=None, values=None): # profile can be injected by e.g. check-googlefonts injects it's own profile add_profile_arg = False if profile is None: profile = get_profile() add_profile_arg = True argument_parser, values_keys = ArgumentParser(profile, profile_arg=add_profile_arg) args = argument_parser.parse_args() if args.list_checks: if args.loglevels == [PASS]: # if verbose: from fontbakery.constants import WHITE_STR, CYAN_STR, BLUE_STR for section in profile._sections.values(): print(WHITE_STR.format("\nSection:") + " " + section.name) for check in section._checks: print( CYAN_STR.format(check.id) + "\n" + BLUE_STR.format(f'"{check.description}"') + "\n") else: for section_name, section in profile._sections.items(): for check in section._checks: print(check.id) sys.exit() values_ = {} if values is not None: values_.update(values) # values_keys are returned by profile.setup_argparse # these are keys for custom arguments required by the profile. if values_keys: for key in values_keys: if hasattr(args, key): values_[key] = getattr(args, key) try: runner = CheckRunner(profile, values=values_, custom_order=args.order, explicit_checks=args.checkid, exclude_checks=args.exclude_checkid) except ValueValidationError as e: print(e) argument_parser.print_usage() sys.exit(1) # The default Windows Terminal just displays the escape codes. The argument # parser above therefore has these options disabled. if sys.platform == "win32": args.no_progress = True args.no_colors = True # the most verbose loglevel wins loglevel = min(args.loglevels) if args.loglevels else DEFAULT_LOG_LEVEL tr = TerminalReporter(runner=runner, is_async=False , print_progress=not args.no_progress , check_threshold=loglevel , log_threshold=args.loglevel_messages or loglevel , usecolor=not args.no_colors , collect_results_by=args.gather_by , skip_status_report=None if args.show_sections\ else (STARTSECTION, ENDSECTION) ) reporters = [tr.receive] if args.json: sr = SerializeReporter(runner=runner, collect_results_by=args.gather_by) reporters.append(sr.receive) if args.ghmarkdown: mdr = GHMarkdownReporter(loglevels=args.loglevels, runner=runner, collect_results_by=args.gather_by) reporters.append(mdr.receive) if args.html: hr = HTMLReporter(loglevels=args.loglevels, runner=runner, collect_results_by=args.gather_by) reporters.append(hr.receive) distribute_generator(runner.run(), reporters) if args.json: import json json.dump(sr.getdoc(), args.json, sort_keys=True, indent=4) print("A report in JSON format has been" " saved to '{}'".format(args.json.name)) if args.ghmarkdown: args.ghmarkdown.write(mdr.get_markdown()) print("A report in GitHub Markdown format which can be useful\n" " for posting issues on a GitHub issue tracker has been\n" " saved to '{}'".format(args.ghmarkdown.name)) if args.html: args.html.write(hr.get_html()) print(f"A report in HTML format has been saved to '{args.html.name}'") # Fail and error let the command fail return 1 if tr.worst_check_status in (ERROR, FAIL) else 0
def main(profile=None, values=None): # profile can be injected by e.g. check-googlefonts injects it's own profile add_profile_arg = False if profile is None: profile = get_profile() add_profile_arg = True argument_parser, values_keys = ArgumentParser(profile, profile_arg=add_profile_arg) args = argument_parser.parse_args() # The default Windows Terminal just displays the escape codes. The argument # parser above therefore has these options disabled. if sys.platform == "win32": args.no_progress = True args.no_colors = True from fontbakery.constants import NO_COLORS_THEME, DARK_THEME, LIGHT_THEME if args.no_colors: theme = NO_COLORS_THEME else: if args.light_theme: theme = LIGHT_THEME elif args.dark_theme: theme = DARK_THEME elif sys.platform == "darwin": # The vast majority of MacOS users seem to use a light-background on the text terminal theme = LIGHT_THEME else: # For orther systems like GNU+Linux and Windows, a dark terminal seems to be more common. theme = DARK_THEME if args.list_checks: if args.loglevels == [PASS]: # if verbose: for section in profile._sections.values(): print(theme["list-checks: section"]("\nSection:") + " " + section.name) for check in section._checks: print(theme["list-checks: check-id"](check.id) + "\n" + theme["list-checks: description"] (f'"{check.description}"') + "\n") else: for section_name, section in profile._sections.items(): for check in section._checks: print(check.id) sys.exit() values_ = {} if values is not None: values_.update(values) # values_keys are returned by profile.setup_argparse # these are keys for custom arguments required by the profile. if values_keys: for key in values_keys: if hasattr(args, key): values_[key] = getattr(args, key) runner_kwds = dict(values=values_, custom_order=args.order, explicit_checks=args.checkid, exclude_checks=args.exclude_checkid) try: runner = CheckRunner(profile, **runner_kwds) except ValueValidationError as e: print(e) argument_parser.print_usage() sys.exit(1) is_async = args.multiprocessing != 0 # the most verbose loglevel wins loglevel = min(args.loglevels) if args.loglevels else DEFAULT_LOG_LEVEL tr = TerminalReporter(runner=runner, is_async=is_async , print_progress=not args.no_progress , succinct=args.succinct , check_threshold=loglevel , log_threshold=args.loglevel_messages or loglevel , theme=theme , collect_results_by=args.gather_by , skip_status_report=None if args.show_sections \ else (SECTIONSUMMARY, ) ) reporters = [tr.receive] if args.json: sr = SerializeReporter(runner=runner, collect_results_by=args.gather_by) reporters.append(sr.receive) if args.ghmarkdown: mdr = GHMarkdownReporter(loglevels=args.loglevels, runner=runner, collect_results_by=args.gather_by) reporters.append(mdr.receive) if args.html: hr = HTMLReporter(loglevels=args.loglevels, runner=runner, collect_results_by=args.gather_by) reporters.append(hr.receive) if args.multiprocessing == 0: status_generator = runner.run() else: status_generator = multiprocessing_runner(args.multiprocessing, runner, runner_kwds) distribute_generator(status_generator, reporters) if args.json: import json json.dump(sr.getdoc(), args.json, sort_keys=True, indent=4) print(f'A report in JSON format has been' f' saved to "{args.json.name}"') if args.ghmarkdown: args.ghmarkdown.write(mdr.get_markdown()) print(f'A report in GitHub Markdown format which can be useful\n' f' for posting issues on a GitHub issue tracker has been\n' f' saved to "{args.ghmarkdown.name}"') if args.html: args.html.write(hr.get_html()) print(f'A report in HTML format has been saved to "{args.html.name}"') # Fail and error let the command fail return 1 if tr.worst_check_status in (ERROR, FAIL) else 0
def main(specification=None, values=None): # specification can be injected by e.g. check-googlefonts injects it's own spec add_spec_arg = False if specification is None: specification = get_spec() add_spec_arg = True argument_parser, values_keys = ArgumentParser(specification, spec_arg=add_spec_arg) args = argument_parser.parse_args() if args.list_checks: print('Available checks') for section_name, section in specification._sections.items(): checks = section.list_checks() message = "# {}:\n {}".format(section_name, "\n ".join(checks)) print(message) sys.exit() values_ = {} if values is not None: values_.update(values) # values_keys are returned by specification.setup_argparse # these are keys for custom arguments required by the spec. if values_keys: for key in values_keys: if hasattr(args, key): values_[key] = getattr(args, key) try: runner = runner_factory(specification, explicit_checks=args.checkid, exclude_checks=args.exclude_checkid, custom_order=args.order, values=values_) except ValueValidationError as e: print(e) argument_parser.print_usage() sys.exit(1) # The default Windows Terminal just displays the escape codes. The argument # parser above therefore has these options disabled. if sys.platform == "win32": args.no_progress = True args.no_colors = True # the most verbose loglevel wins loglevel = min(args.loglevels) if args.loglevels else DEFAULT_LOG_LEVEL tr = TerminalReporter(runner=runner, is_async=False , print_progress=not args.no_progress , check_threshold=loglevel , log_threshold=args.loglevel_messages or loglevel , usecolor=not args.no_colors , collect_results_by=args.gather_by , skip_status_report=None if args.show_sections\ else (STARTSECTION, ENDSECTION) ) reporters = [tr.receive] if args.json: sr = SerializeReporter(runner=runner, collect_results_by=args.gather_by) reporters.append(sr.receive) if args.ghmarkdown: mdr = GHMarkdownReporter(loglevels=args.loglevels, runner=runner, collect_results_by=args.gather_by) reporters.append(mdr.receive) distribute_generator(runner.run(), reporters) if args.json: import json json.dump(sr.getdoc(), args.json, sort_keys=True, indent=4) if args.ghmarkdown: args.ghmarkdown.write(mdr.get_markdown()) # Fail and error let the command fail return 1 if tr.worst_check_status in (ERROR, FAIL) else 0
logging.warning("Skipping '{}' as it does not seem " "to be valid TrueType font file.".format(fullpath)) return fonts_to_check if __name__ == '__main__': args = parser.parse_args() values = dict(fonts=get_fonts(args.arg_filepaths)) runner = TestRunner(specification, values , explicit_tests=args.checkid , custom_order=args.order ) # the most verbose loglevel wins loglevel = min(args.loglevels) if args.loglevels else DEFAULT_LOG_LEVEL tr = TerminalReporter(runner=runner, is_async=False , print_progress=not args.no_progress , test_threshold=loglevel , log_threshold=args.loglevel_messages or loglevel , usecolor=not args.no_colors , collect_results_by=args.gather_by ) reporters = [tr.receive] if args.json: sr = SerializeReporter(runner=runner, collect_results_by=args.gather_by) reporters.append(sr.receive) distribute_generator(runner.run(), reporters) if args.json: import json json.dump(sr.getdoc(), args.json, sort_keys=True, indent=4)