text: #!/usr/bin/python3
  
  import json
  import warnings
  from pathlib import Path
  
  from brownie._config import CONFIG
  from brownie.exceptions import BrownieConfigWarning
  from brownie.network.state import TxHistory
  from brownie.project import get_loaded_projects
  from brownie.utils import color
  
  COVERAGE_COLORS = [(0.8, "bright red"), (0.9, "bright yellow"), (1, "bright green")]
  
  
  def _save_coverage_report(build, coverage_eval, report_path):
      # Saves a test coverage report for viewing in the GUI
      report = {
          "highlights": _get_highlights(build, coverage_eval),
          "coverage": _get_totals(build, coverage_eval),
          "sha1": {},  # TODO
      }
      report = json.loads(json.dumps(report, default=sorted))
      report_path = Path(report_path).absolute()
      if report_path.is_dir():
          report_path = report_path.joinpath("coverage.json")
      with report_path.open("w") as fp:
          json.dump(report, fp, sort_keys=True, indent=2)
      print(f"\nCoverage report saved at {report_path}")
      print("View the report using the Brownie GUI")
      return report_path
  
  
  def _load_report_exclude_data(settings):
      exclude_paths = []
      if settings["exclude_paths"]:
          exclude = settings["exclude_paths"]
          if not isinstance(exclude, list):
              exclude = [exclude]
          for glob_str in exclude:
              if Path(glob_str).is_absolute():
                  base_path = Path(glob_str).root
              else:
                  base_path = Path(".")
              try:
                  exclude_paths.extend([i.as_posix() for i in base_path.glob(glob_str)])
              except Exception:
                  warnings.warn(
                      "Invalid glob pattern in config exclude settings: '{glob_str}'",
                      BrownieConfigWarning,
                  )
  
      exclude_contracts = []
      if settings["exclude_contracts"]:
          exclude_contracts = settings["exclude_contracts"]
          if not isinstance(exclude_contracts, list):
              exclude_contracts = [exclude_contracts]
  
      return exclude_paths, exclude_contracts
  
  
  def _build_gas_profile_output():
      # Formats gas profile report that may be printed to the console
      exclude_paths, exclude_contracts = _load_report_exclude_data(CONFIG.settings["reports"])
      try:
          project = get_loaded_projects()[0]
      except IndexError:
          project = None
  
      gas = TxHistory().gas_profile
      sorted_gas = sorted(gas.items())
      grouped_by_contract = {}
      padding = {}
  
      lines = [""]
  
      for full_name, values in sorted_gas:
          contract, function = full_name.split(".", 1)
  
          try:
              if project._sources.get_source_path(contract) in exclude_paths:
                  continue
          except (AttributeError, KeyError):
              pass
          if contract in exclude_contracts:
              continue
  
          # calculate padding to get table-like formatting
          padding["fn"] = max(padding.get("fn", 0), len(str(function)))
          for k, v in values.items():
              padding[k] = max(padding.get(k, 0), len(str(v)))
  
          # group functions with payload by contract name
          if contract in grouped_by_contract.keys():
              grouped_by_contract[contract][function] = values
          else:
              grouped_by_contract[contract] = {function: values}
  
      for contract, functions in grouped_by_contract.items():
          lines.append(f"{color('bright magenta')}{contract}{color} <Contract>")
          sorted_functions = dict(
              sorted(functions.items(), key=lambda value: value[1]["avg"], reverse=True)
          )
          for ix, (fn_name, values) in enumerate(sorted_functions.items()):
              prefix = "\u2514\u2500" if ix == len(functions) - 1 else "\u251c\u2500"
              fn_name = fn_name.ljust(padding["fn"])
              values["avg"] = int(values["avg"])
              values = {k: str(v).rjust(padding[k]) for k, v in values.items()}
              lines.append(
                  f"   {prefix} {fn_name} -  avg: {values['avg']}"
                  f"  low: {values['low']}  high: {values['high']}"
              )
  
      return lines + [""]
  
  
  def _build_coverage_output(coverage_eval):
      # Formats a coverage evaluation report that may be printed to the console
  
      exclude_paths, exclude_contracts = _load_report_exclude_data(CONFIG.settings["reports"])
      all_totals = [
          (i, _get_totals(i._build, coverage_eval, exclude_contracts)) for i in get_loaded_projects()
      ]
      all_totals = [i for i in all_totals if i[1]]
      lines = []
  
      for project, totals in all_totals:
  
          if len(all_totals) > 1:
              lines.append(f"\n======== {color('bright magenta')}{project._name}{color} ========")
  
          for contract_name in sorted(totals):
              if project._sources.get_source_path(contract_name) in exclude_paths:
                  continue
  
              pct = _pct(
                  totals[contract_name]["totals"]["statements"],
                  totals[contract_name]["totals"]["branches"],
              )
              lines.append(
                  f"\n  contract: {color('bright magenta')}{contract_name}{color}"
                  f" - {_cov_color(pct)}{pct:.1%}{color}"
              )
  
              cov = totals[contract_name]
              results = []
              for fn_name, statement_cov in cov["statements"].items():
                  branch_cov = cov["branches"][fn_name] if fn_name in cov["branches"] else (0, 0, 0)
                  pct = _pct(statement_cov, branch_cov)
                  results.append((fn_name, pct))
  
              for fn_name, pct in sorted(results, key=lambda k: (-k[1], k[0])):
                  lines.append(f"    {fn_name} - {_cov_color(pct)}{pct:.1%}{color}")
  
      return lines
  
  
  def _cov_color(pct):
      return color(next(i[1] for i in COVERAGE_COLORS if pct <= i[0]))
  
  
  def _pct(statement, branch):
      pct = statement[0] / (statement[1] or 1)
      if branch[-1]:
          pct = (pct + (branch[0] + branch[1]) / (branch[2] * 2)) / 2
      return pct
  
  
  def _get_totals(build, coverage_eval, exclude_contracts=None):
      # Returns a modified coverage eval dict showing counts and totals for each function.
  
      if exclude_contracts is None:
          exclude_contracts = []
      coverage_eval = _split_by_fn(build, coverage_eval)
      results = dict(
          (
              i,
              {
                  "statements": {},
                  "totals": {"statements": 0, "branches": [0, 0]},
                  "branches": {"true": {}, "false": {}},
              },
          )
          for i in coverage_eval
      )
      for contract_name in coverage_eval:
          if contract_name in exclude_contracts:
              del results[contract_name]
              continue
          try:
              coverage_map = build.get(contract_name)["coverageMap"]
          except KeyError:
              del results[contract_name]
              continue
  
          r = results[contract_name]
          r["statements"], r["totals"]["statements"] = _statement_totals(
              coverage_eval[contract_name], coverage_map["statements"], exclude_contracts
          )
          r["branches"], r["totals"]["branches"] = _branch_totals(
              coverage_eval[contract_name], coverage_map["branches"], exclude_contracts
          )
  
      return results
  
  
  def _split_by_fn(build, coverage_eval):
      # Splits a coverage eval dict so that coverage indexes are stored by function.
      results = dict(
          (i, {"statements": {}, "branches": {"true": {}, "false": {}}}) for i in coverage_eval
      )
      for name in coverage_eval:
          try:
              map_ = build.get(name)["coverageMap"]
              results[name] = dict((k, _split(v, map_, k)) for k, v in coverage_eval[name].items())
          except KeyError:
              pass
      return results
  
  
  def _split(coverage_eval, coverage_map, key):
      results = {}
      for fn, map_ in coverage_map["statements"][key].items():
          results[fn] = [[i for i in map_ if int(i) in coverage_eval[0]], [], []]
      for fn, map_ in coverage_map["branches"][key].items():
          results[fn][1] = [i for i in map_ if int(i) in coverage_eval[1]]
          results[fn][2] = [i for i in map_ if int(i) in coverage_eval[2]]
      return results
  
  
  def _statement_totals(coverage_eval, coverage_map, exclude_contracts):
      result = {}
      count, total = 0, 0
      for path, fn in [(k, x) for k, v in coverage_eval.items() for x in v]:
          if fn.split(".")[0] in exclude_contracts:
              continue
          count += len(coverage_eval[path][fn][0])
          total += len(coverage_map[path][fn])
          result[fn] = (len(coverage_eval[path][fn][0]), len(coverage_map[path][fn]))
      return result, (count, total)
  
  
  def _branch_totals(coverage_eval, coverage_map, exclude_contracts):
      result = {}
      final = [0, 0, 0]
      for path, fn in [(k, x) for k, v in coverage_map.items() for x in v]:
          if fn.split(".")[0] in exclude_contracts:
              continue
          if path not in coverage_eval:
              true, false = 0, 0
          else:
              true = len(coverage_eval[path][fn][2])
              false = len(coverage_eval[path][fn][1])
          total = len(coverage_map[path][fn])
          result[fn] = (true, false, total)
          for i in range(3):
              final[i] += result[fn][i]
      return result, final
  
  
  def _get_highlights(build, coverage_eval):
      # Returns a highlight map formatted for display in the GUI.
      results = {"statements": {}, "branches": {}}
      for name, eval_ in coverage_eval.items():
          try:
              coverage_map = build.get(name)["coverageMap"]
          except KeyError:
              continue
  
          results["statements"][name] = _statement_highlights(eval_, coverage_map["statements"])
          results["branches"][name] = _branch_highlights(eval_, coverage_map["branches"])
  
      return results
  
  
  def _statement_highlights(coverage_eval, coverage_map):
      results = dict((i, []) for i in coverage_map)
      for path, fn in [(k, x) for k, v in coverage_map.items() for x in v]:
          results[path].extend(
              [
                  list(offset) + [_statement_color(i, coverage_eval, path), ""]
                  for i, offset in coverage_map[path][fn].items()
              ]
          )
      return results
  
  
  def _statement_color(i, coverage_eval, path):
      if path not in coverage_eval or int(i) not in coverage_eval[path][0]:
          return "red"
      return "green"
  
  
  def _branch_highlights(coverage_eval, coverage_map):
      results = dict((i, []) for i in coverage_map)
      for path, fn in [(k, x) for k, v in coverage_map.items() for x in v]:
          results[path].extend(
              [
                  list(offset[:2]) + [_branch_color(int(i), coverage_eval, path, offset[2]), ""]
                  for i, offset in coverage_map[path][fn].items()
              ]
          )
      return results
  
  
  def _branch_color(i, coverage_eval, path, jump):
      if path not in coverage_eval:
          return "red"
      if i in coverage_eval[path][2]:
          if i in coverage_eval[path][1]:
              return "green"
          return "yellow" if jump else "orange"
      if i in coverage_eval[path][1]:
          return "orange" if jump else "yellow"
      return "red"
  