text: #!/usr/bin/env python3
  #===- symcov-report-server.py - Coverage Reports HTTP Serve --*- python -*--===#
  #
  #                     The LLVM Compiler Infrastructure
  #
  # This file is distributed under the University of Illinois Open Source
  # License. See LICENSE.TXT for details.
  #
  #===------------------------------------------------------------------------===#
  '''(EXPERIMENTAL) HTTP server to browse coverage reports from .symcov files.
  
  Coverage reports for big binaries are too huge, generating them statically
  makes no sense. Start the server and go to localhost:8001 instead.
  
  Usage:
      ./tools/sancov/symcov-report-server.py \
              --symcov coverage_data.symcov \
              --srcpath root_src_dir
  
  Other options:
      --port port_number - specifies the port to use (8001)
      --host host_name - host name to bind server to (127.0.0.1)
  '''
  
  import argparse
  import http.server
  import json
  import socketserver
  import time
  import html
  import os
  import string
  import math
  
  INDEX_PAGE_TMPL = """
  <html>
  <head>
    <title>Coverage Report</title>
    <style>
      .lz { color: lightgray; }
    </style>
  </head>
  <body>
      <table>
        <tr><th>File</th><th>Coverage</th></tr>
        <tr><td><em>Files with 0 coverage are not shown.</em></td></tr>
  $filenames
      </table>
  </body>
  </html>
  """
  
  CONTENT_PAGE_TMPL = """
  <html>
  <head>
    <title>$path</title>
    <style>
      .covered { background: lightgreen; }
      .not-covered { background: lightcoral; }
      .partially-covered { background: navajowhite; }
      .lz { color: lightgray; }
    </style>
  </head>
  <body>
  <pre>
  $content
  </pre>
  </body>
  </html>
  """
  
  class SymcovData:
      def __init__(self, symcov_json):
          self.covered_points = frozenset(symcov_json['covered-points'])
          self.point_symbol_info = symcov_json['point-symbol-info']
          self.file_coverage = self.compute_filecoverage()
  
      def filenames(self):
          return self.point_symbol_info.keys()
  
      def has_file(self, filename):
          return filename in self.point_symbol_info
  
      def compute_linemap(self, filename):
          """Build a line_number->css_class map."""
          points = self.point_symbol_info.get(filename, dict())
  
          line_to_points = dict()
          for fn, points in points.items():
              for point, loc in points.items():
                  line = int(loc.split(":")[0])
                  line_to_points.setdefault(line, []).append(point)
  
          result = dict()
          for line, points in line_to_points.items():
              status = "covered"
              covered_points = self.covered_points & set(points)
              if not len(covered_points):
                  status = "not-covered"
              elif len(covered_points) != len(points):
                  status = "partially-covered"
              result[line] = status
          return result
  
      def compute_filecoverage(self):
          """Build a filename->pct coverage."""
          result = dict()
          for filename, fns in self.point_symbol_info.items():
              file_points = []
              for fn, points in fns.items():
                  file_points.extend(points.keys())
              covered_points = self.covered_points & set(file_points)
              result[filename] = int(math.ceil(
                  len(covered_points) * 100 / len(file_points)))
          return result
  
  
  def format_pct(pct):
      pct_str = str(max(0, min(100, pct)))
      zeroes = '0' * (3 - len(pct_str))
      if zeroes:
          zeroes = '<span class="lz">{0}</span>'.format(zeroes)
      return zeroes + pct_str
  
  class ServerHandler(http.server.BaseHTTPRequestHandler):
      symcov_data = None
      src_path = None
  
      def do_GET(self):
          if self.path == '/':
              self.send_response(200)
              self.send_header("Content-type", "text/html; charset=utf-8")
              self.end_headers()
  
              filelist = []
              for filename in sorted(self.symcov_data.filenames()):
                  file_coverage = self.symcov_data.file_coverage[filename]
                  if not file_coverage:
                      continue
                  filelist.append(
                          "<tr><td><a href=\"/{name}\">{name}</a></td>"
                          "<td>{coverage}%</td></tr>".format(
                              name=html.escape(filename, quote=True), 
                              coverage=format_pct(file_coverage)))
  
              response = string.Template(INDEX_PAGE_TMPL).safe_substitute(
                  filenames='\n'.join(filelist))
              self.wfile.write(response.encode('UTF-8', 'replace'))
          elif self.symcov_data.has_file(self.path[1:]):
              filename = self.path[1:]
              filepath = os.path.join(self.src_path, filename) 
              if not os.path.exists(filepath):
                  self.send_response(404)
                  self.end_headers()
                  return
  
              self.send_response(200)
              self.send_header("Content-type", "text/html; charset=utf-8")
              self.end_headers()
  
              linemap = self.symcov_data.compute_linemap(filename)
  
              with open(filepath, 'r') as f:
                  content = "\n".join(
                          ["<span class='{cls}'>{line}&nbsp;</span>".format(
                              line=html.escape(line.rstrip()), 
                              cls=linemap.get(line_no, ""))
                              for line_no, line in enumerate(f)])
  
              response = string.Template(CONTENT_PAGE_TMPL).safe_substitute(
                  path=self.path[1:],
                  content=content)
  
              self.wfile.write(response.encode('UTF-8', 'replace'))
          else:
              self.send_response(404)
              self.end_headers()
  
  
  def main():
      parser = argparse.ArgumentParser(description="symcov report http server.")
      parser.add_argument('--host', default='127.0.0.1')
      parser.add_argument('--port', default=8001)
      parser.add_argument('--symcov', required=True, type=argparse.FileType('r'))
      parser.add_argument('--srcpath', required=True)
      args = parser.parse_args()
  
      print("Loading coverage...")
      symcov_json = json.load(args.symcov)
      ServerHandler.symcov_data = SymcovData(symcov_json)
      ServerHandler.src_path = args.srcpath
  
      socketserver.TCPServer.allow_reuse_address = True
      httpd = socketserver.TCPServer((args.host, int(args.port)), ServerHandler)
      print("Serving at {host}:{port}".format(host=args.host, port=args.port))
      try:
          httpd.serve_forever()
      except KeyboardInterrupt:
          pass
      httpd.server_close()
  
  if __name__ == '__main__':
      main()
  