예제 #1
0
    def __call__(self, values, condition_overrides={}):
        from fontTools.ttLib import TTFont
        if isinstance(values, str):
            values = {'font': values, 'fonts': [values]}
        elif isinstance(values, TTFont):
            values = {
                'font': values.reader.file.name,
                'fonts': [values.reader.file.name],
                'ttFont': values,
                'ttFonts': [values]
            }
        elif isinstance(values, list):
            if isinstance(values[0], str):
                values = {'fonts': values}
            elif isinstance(values[0], TTFont):
                values = {
                    'fonts': [v.reader.file.name for v in values],
                    'ttFonts': values
                }

        self.runner = CheckRunner(
            self.profile, values,
            Configuration(explicit_checks=[self.check_id]))
        for check_identity in self.runner.order:
            _, check, _ = check_identity
            if check.id != self.check_id:
                continue
            self.check_identity = check_identity
            self.check_section, self.check, self.check_iterargs = check_identity
            break
        if self.check_identity is None:
            raise KeyError(f'Check with id "{self.check_id}" not found.')

        self._args = self._get_args(condition_overrides)
        return list(self.runner._exec_check(self.check, self._args))
예제 #2
0
def multiprocessing_worker(jobs_queue, results_queue, profile_module_locator,
                           runner_kwds):
    profile = get_profile_from_module_locator(profile_module_locator)
    runner = CheckRunner(profile, **runner_kwds)
    reporter = WorkerToQueueReporter(results_queue,
                                     profile=profile,
                                     runner=runner,
                                     ticks_to_flush=5)

    next_check_gen = _worker_jobs_generator(jobs_queue, profile, reporter)
    runner.run_externally_controlled(reporter.receive, next_check_gen)
예제 #3
0
def runner_factory( specification
                  , explicit_checks=None
                  , custom_order=None
                  , values=None):
  """ Convenience CheckRunner factory. """
  return CheckRunner( specification, values
                    , explicit_checks=explicit_checks
                    , custom_order=custom_order
                    )
예제 #4
0
 def start(self):
     profile = get_module_profile(
         get_module("fontbakery.profiles." + self.profilename))
     print(self.checks)
     runner = CheckRunner(profile,
                          values={"fonts": self.paths},
                          config={
                              "custom_order": None,
                              "explicit_checks": self.checks,
                              "exclude_checks": None
                          })
     print("Log levels: ", self.loglevels)
     hr = HTMLReporter(runner=runner, loglevels=self.loglevels)
     ghmd = GHMarkdownReporter(runner=runner, loglevels=self.loglevels)
     prog = ProgressReporter(self.progressStatus, runner=runner)
     reporters = [hr.receive, prog.receive, ghmd.receive]
     status_generator = runner.run()
     print("Starting distribute_generator")
     distribute_generator(status_generator, reporters)
     print("Done with distribute_generator")
     self.signalStatus.emit(hr.get_html(), ghmd.get_markdown())
예제 #5
0
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")
예제 #6
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()

    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
예제 #7
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()

  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
예제 #8
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

    theme = get_theme(args)
    # the most verbose loglevel wins
    loglevel = min(args.loglevels) if args.loglevels else DEFAULT_LOG_LEVEL

    if args.list_checks:
        list_checks(profile, theme, verbose=loglevel > DEFAULT_LOG_LEVEL)

    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)

    if args.configfile:
        configuration = Configuration.from_config_file(args.configfile)
    else:
        configuration = Configuration()

    # Command line args overrides config, but only if given
    configuration.maybe_override(
        Configuration(custom_order=args.order,
                      explicit_checks=args.checkid,
                      exclude_checks=args.exclude_checkid))
    runner_kwds = dict(values=values_, config=configuration)
    try:
        runner = CheckRunner(profile, **runner_kwds)
    except ValueValidationError as e:
        print(e)
        argument_parser.print_usage()
        sys.exit(1)

    is_async = args.multiprocessing != 0

    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]
    if "reporters" not in args:
        args.reporters = []

    for reporter_class, output_file in args.reporters:
        reporters.append(
            reporter_class(loglevels=args.loglevels,
                           runner=runner,
                           collect_results_by=args.gather_by,
                           output_file=output_file))

    if args.multiprocessing == 0:
        status_generator = runner.run()
    else:
        status_generator = multiprocessing_runner(args.multiprocessing, runner,
                                                  runner_kwds)

    distribute_generator(status_generator,
                         [reporter.receive for reporter in reporters])

    for reporter in reporters:
        reporter.write()

    # Fail and error let the command fail
    return 1 if tr.worst_check_status in (ERROR, FAIL) else 0
예제 #9
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
예제 #10
0
class CheckTester:
    """
    This class offers a bit of automation to aid in the implementation of
    code-tests to validade the proper behaviour of FontBakery checks.

    !!!CAUTION: this uses a lot of "private" methods and properties
    of CheckRunner, in order to make unit testing different cases simpler.

    This is not intended to run in production. However, if that is desired
    we may or may not find inspiration here on how to implement a proper
    public CheckRunner API.

    Not built for performance either!

    The idea is that we can let this class take care of computing
    the dependencies of a check for us. And we can also optionaly "fake"
    some of them in order to create useful testing scenarios for the checks.

    An initial run can be with unaltered arguments, as CheckRunner would
    produce them by itself. And subsequent calls can reuse some of them.
    """
    def __init__(self, module_or_profile, check_id):
        self.profile = module_or_profile \
                       if isinstance(module_or_profile, Profile) \
                       else get_module_profile(module_or_profile)
        self.check_id = check_id
        self.check_identity = None
        self.check_section = None
        self.check = None
        self.check_iterargs = None
        self._args = None

    def _get_args(self, condition_overrides=None):
        if condition_overrides is not None:
            for name_key, value in condition_overrides.items():
                if isinstance(name_key, str):
                    # this is a simplified form of a cache key:
                    # write the conditions directly to the iterargs of the check identity
                    used_iterargs = self.runner._filter_condition_used_iterargs(
                        name_key, self.check_iterargs)
                    key = (name_key, used_iterargs)
                else:
                    # Full control for the caller, who has to inspect how
                    # the desired key needs to be set up.
                    key = name_key
                #                                      error, value
                self.runner._cache['conditions'][key] = None, value
        args = self.runner._get_args(self.check, self.check_iterargs)
        # args that are derived iterables are generators that must be
        # converted to lists, otherwise we end up with exhausted
        # generators after their first consumption.
        for k in args:
            if self.profile.get_type(k, None) == 'derived_iterables':
                args[k] = list(args[k])
        return args

    def __getitem__(self, key):
        if key in self._args:
            return self._args[key]

        used_iterargs = self.runner._filter_condition_used_iterargs(
            key, self.check_iterargs)
        key = (key, used_iterargs)
        if key in self.runner._cache['conditions']:
            return self.runner._cache['conditions'][key][1]

    def __call__(self, values, condition_overrides={}):
        from fontTools.ttLib import TTFont
        if isinstance(values, str):
            values = {'font': values, 'fonts': [values]}
        elif isinstance(values, TTFont):
            values = {
                'font': values.reader.file.name,
                'fonts': [values.reader.file.name],
                'ttFont': values,
                'ttFonts': [values]
            }
        elif isinstance(values, list):
            if isinstance(values[0], str):
                values = {'fonts': values}
            elif isinstance(values[0], TTFont):
                values = {
                    'fonts': [v.reader.file.name for v in values],
                    'ttFonts': values
                }

        self.runner = CheckRunner(
            self.profile, values,
            Configuration(explicit_checks=[self.check_id]))
        for check_identity in self.runner.order:
            _, check, _ = check_identity
            if check.id != self.check_id:
                continue
            self.check_identity = check_identity
            self.check_section, self.check, self.check_iterargs = check_identity
            break
        if self.check_identity is None:
            raise KeyError(f'Check with id "{self.check_id}" not found.')

        self._args = self._get_args(condition_overrides)
        return list(self.runner._exec_check(self.check, self._args))
예제 #11
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 = CheckRunner(specification
                        , 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)

  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))

  # Fail and error let the command fail
  return 1 if tr.worst_check_status in (ERROR, FAIL) else 0