17757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch"""HTML reporting for Coverage.""" 27757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 37757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochimport os, re, shutil 47757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 57757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochimport coverage 67757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochfrom coverage.backward import pickle, write_encoded 77757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochfrom coverage.misc import CoverageException, Hasher 87757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochfrom coverage.phystokens import source_token_lines 97757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochfrom coverage.report import Reporter 107757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochfrom coverage.templite import Templite 117757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 127757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch# Disable pylint msg W0612, because a bunch of variables look unused, but 137757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch# they're accessed in a Templite context via locals(). 147757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch# pylint: disable=W0612 157757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 167757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochdef data_filename(fname): 177757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch """Return the path to a data file of ours.""" 187757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch return os.path.join(os.path.split(__file__)[0], fname) 197757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 207757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochdef data(fname): 217757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch """Return the contents of a data file of ours.""" 227757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch data_file = open(data_filename(fname)) 237757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch try: 247757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch return data_file.read() 257757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch finally: 267757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch data_file.close() 277757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 287757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 297757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochclass HtmlReporter(Reporter): 307757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch """HTML reporting.""" 317757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 327757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch # These files will be copied from the htmlfiles dir to the output dir. 337757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch STATIC_FILES = [ 347757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch "style.css", 357757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch "jquery-1.4.3.min.js", 367757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch "jquery.hotkeys.js", 377757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch "jquery.isonscreen.js", 387757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch "jquery.tablesorter.min.js", 397757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch "coverage_html.js", 407757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch "keybd_closed.png", 417757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch "keybd_open.png", 427757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch ] 437757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 447757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch def __init__(self, cov, ignore_errors=False): 457757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch super(HtmlReporter, self).__init__(cov, ignore_errors) 467757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch self.directory = None 477757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch self.template_globals = { 487757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 'escape': escape, 497757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch '__url__': coverage.__url__, 507757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch '__version__': coverage.__version__, 517757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch } 527757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch self.source_tmpl = Templite( 537757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch data("htmlfiles/pyfile.html"), self.template_globals 547757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch ) 557757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 567757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch self.coverage = cov 577757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 587757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch self.files = [] 597757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch self.arcs = self.coverage.data.has_arcs() 607757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch self.status = HtmlStatus() 617757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 627757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch def report(self, morfs, config=None): 637757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch """Generate an HTML report for `morfs`. 647757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 657757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch `morfs` is a list of modules or filenames. `config` is a 667757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch CoverageConfig instance. 677757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 687757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch """ 697757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch assert config.html_dir, "must provide a directory for html reporting" 707757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 717757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch # Read the status data. 727757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch self.status.read(config.html_dir) 737757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 747757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch # Check that this run used the same settings as the last run. 757757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch m = Hasher() 767757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch m.update(config) 777757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch these_settings = m.digest() 787757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch if self.status.settings_hash() != these_settings: 797757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch self.status.reset() 807757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch self.status.set_settings_hash(these_settings) 817757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 827757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch # Process all the files. 837757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch self.report_files(self.html_file, morfs, config, config.html_dir) 847757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 857757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch if not self.files: 867757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch raise CoverageException("No data to report.") 877757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 887757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch # Write the index file. 897757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch self.index_file() 907757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 917757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch self.make_local_static_report_files() 927757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 937757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch def make_local_static_report_files(self): 947757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch """Make local instances of static files for HTML report.""" 957757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch for static in self.STATIC_FILES: 967757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch shutil.copyfile( 977757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch data_filename("htmlfiles/" + static), 987757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch os.path.join(self.directory, static) 997757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch ) 1007757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 1017757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch def write_html(self, fname, html): 1027757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch """Write `html` to `fname`, properly encoded.""" 1037757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch write_encoded(fname, html, 'ascii', 'xmlcharrefreplace') 1047757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 1057757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch def file_hash(self, source, cu): 1067757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch """Compute a hash that changes if the file needs to be re-reported.""" 1077757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch m = Hasher() 1087757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch m.update(source) 1097757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch self.coverage.data.add_to_hash(cu.filename, m) 1107757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch return m.digest() 1117757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 1127757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch def html_file(self, cu, analysis): 1137757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch """Generate an HTML file for one source file.""" 1147757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch source_file = cu.source_file() 1157757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch try: 1167757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch source = source_file.read() 1177757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch finally: 1187757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch source_file.close() 1197757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 1207757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch # Find out if the file on disk is already correct. 1217757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch flat_rootname = cu.flat_rootname() 1227757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch this_hash = self.file_hash(source, cu) 1237757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch that_hash = self.status.file_hash(flat_rootname) 1247757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch if this_hash == that_hash: 1257757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch # Nothing has changed to require the file to be reported again. 1267757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch self.files.append(self.status.index_info(flat_rootname)) 1277757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch return 1287757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 1297757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch self.status.set_file_hash(flat_rootname, this_hash) 1307757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 1317757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch nums = analysis.numbers 1327757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 1337757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch missing_branch_arcs = analysis.missing_branch_arcs() 1347757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch n_par = 0 # accumulated below. 1357757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch arcs = self.arcs 1367757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 1377757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch # These classes determine which lines are highlighted by default. 1387757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch c_run = "run hide_run" 1397757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch c_exc = "exc" 1407757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch c_mis = "mis" 1417757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch c_par = "par " + c_run 1427757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 1437757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch lines = [] 1447757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 1457757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch for lineno, line in enumerate(source_token_lines(source)): 1467757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch lineno += 1 # 1-based line numbers. 1477757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch # Figure out how to mark this line. 1487757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch line_class = [] 1497757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch annotate_html = "" 1507757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch annotate_title = "" 1517757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch if lineno in analysis.statements: 1527757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch line_class.append("stm") 1537757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch if lineno in analysis.excluded: 1547757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch line_class.append(c_exc) 1557757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch elif lineno in analysis.missing: 1567757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch line_class.append(c_mis) 1577757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch elif self.arcs and lineno in missing_branch_arcs: 1587757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch line_class.append(c_par) 1597757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch n_par += 1 1607757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch annlines = [] 1617757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch for b in missing_branch_arcs[lineno]: 1627757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch if b < 0: 1637757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch annlines.append("exit") 1647757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch else: 1657757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch annlines.append(str(b)) 1667757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch annotate_html = " ".join(annlines) 1677757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch if len(annlines) > 1: 1687757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch annotate_title = "no jumps to these line numbers" 1697757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch elif len(annlines) == 1: 1707757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch annotate_title = "no jump to this line number" 1717757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch elif lineno in analysis.statements: 1727757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch line_class.append(c_run) 1737757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 1747757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch # Build the HTML for the line 1757757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch html = [] 1767757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch for tok_type, tok_text in line: 1777757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch if tok_type == "ws": 1787757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch html.append(escape(tok_text)) 1797757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch else: 1807757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch tok_html = escape(tok_text) or ' ' 1817757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch html.append( 1827757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch "<span class='%s'>%s</span>" % (tok_type, tok_html) 1837757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch ) 1847757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 1857757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch lines.append({ 1867757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 'html': ''.join(html), 1877757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 'number': lineno, 1887757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 'class': ' '.join(line_class) or "pln", 1897757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 'annotate': annotate_html, 1907757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 'annotate_title': annotate_title, 1917757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch }) 1927757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 1937757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch # Write the HTML page for this file. 1947757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch html_filename = flat_rootname + ".html" 1957757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch html_path = os.path.join(self.directory, html_filename) 1967757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 1977757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch html = spaceless(self.source_tmpl.render(locals())) 1987757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch self.write_html(html_path, html) 1997757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 2007757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch # Save this file's information for the index file. 2017757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch index_info = { 2027757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 'nums': nums, 2037757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 'par': n_par, 2047757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 'html_filename': html_filename, 2057757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 'name': cu.name, 2067757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch } 2077757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch self.files.append(index_info) 2087757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch self.status.set_index_info(flat_rootname, index_info) 2097757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 2107757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch def index_file(self): 2117757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch """Write the index.html file for this report.""" 2127757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch index_tmpl = Templite( 2137757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch data("htmlfiles/index.html"), self.template_globals 2147757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch ) 2157757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 2167757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch files = self.files 2177757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch arcs = self.arcs 2187757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 2197757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch totals = sum([f['nums'] for f in files]) 2207757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 2217757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch self.write_html( 2227757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch os.path.join(self.directory, "index.html"), 2237757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch index_tmpl.render(locals()) 2247757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch ) 2257757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 2267757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch # Write the latest hashes for next time. 2277757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch self.status.write(self.directory) 2287757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 2297757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 2307757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochclass HtmlStatus(object): 2317757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch """The status information we keep to support incremental reporting.""" 2327757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 2337757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch STATUS_FILE = "status.dat" 2347757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch STATUS_FORMAT = 1 2357757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 2367757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch def __init__(self): 2377757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch self.reset() 2387757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 2397757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch def reset(self): 2407757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch """Initialize to empty.""" 2417757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch self.settings = '' 2427757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch self.files = {} 2437757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 2447757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch def read(self, directory): 2457757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch """Read the last status in `directory`.""" 2467757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch usable = False 2477757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch try: 2487757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch status_file = os.path.join(directory, self.STATUS_FILE) 2497757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch status = pickle.load(open(status_file, "rb")) 2507757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch except IOError: 2517757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch usable = False 2527757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch else: 2537757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch usable = True 2547757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch if status['format'] != self.STATUS_FORMAT: 2557757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch usable = False 2567757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch elif status['version'] != coverage.__version__: 2577757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch usable = False 2587757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 2597757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch if usable: 2607757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch self.files = status['files'] 2617757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch self.settings = status['settings'] 2627757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch else: 2637757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch self.reset() 2647757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 2657757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch def write(self, directory): 2667757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch """Write the current status to `directory`.""" 2677757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch status_file = os.path.join(directory, self.STATUS_FILE) 2687757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch status = { 2697757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 'format': self.STATUS_FORMAT, 2707757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 'version': coverage.__version__, 2717757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 'settings': self.settings, 2727757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 'files': self.files, 2737757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch } 2747757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch fout = open(status_file, "wb") 2757757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch try: 2767757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch pickle.dump(status, fout) 2777757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch finally: 2787757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch fout.close() 2797757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 2807757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch def settings_hash(self): 2817757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch """Get the hash of the coverage.py settings.""" 2827757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch return self.settings 2837757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 2847757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch def set_settings_hash(self, settings): 2857757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch """Set the hash of the coverage.py settings.""" 2867757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch self.settings = settings 2877757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 2887757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch def file_hash(self, fname): 2897757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch """Get the hash of `fname`'s contents.""" 2907757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch return self.files.get(fname, {}).get('hash', '') 2917757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 2927757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch def set_file_hash(self, fname, val): 2937757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch """Set the hash of `fname`'s contents.""" 2947757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch self.files.setdefault(fname, {})['hash'] = val 2957757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 2967757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch def index_info(self, fname): 2977757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch """Get the information for index.html for `fname`.""" 2987757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch return self.files.get(fname, {}).get('index', {}) 2997757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 3007757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch def set_index_info(self, fname, info): 3017757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch """Set the information for index.html for `fname`.""" 3027757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch self.files.setdefault(fname, {})['index'] = info 3037757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 3047757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 3057757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch# Helpers for templates and generating HTML 3067757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 3077757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochdef escape(t): 3087757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch """HTML-escape the text in `t`.""" 3097757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch return (t 3107757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch # Convert HTML special chars into HTML entities. 3117757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch .replace("&", "&").replace("<", "<").replace(">", ">") 3127757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch .replace("'", "'").replace('"', """) 3137757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch # Convert runs of spaces: "......" -> " . . ." 3147757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch .replace(" ", " ") 3157757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch # To deal with odd-length runs, convert the final pair of spaces 3167757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch # so that "....." -> " . ." 3177757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch .replace(" ", " ") 3187757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch ) 3197757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 3207757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochdef spaceless(html): 3217757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch """Squeeze out some annoying extra space from an HTML string. 3227757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 3237757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch Nicely-formatted templates mean lots of extra space in the result. 3247757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch Get rid of some. 3257757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch 3267757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch """ 3277757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch html = re.sub(">\s+<p ", ">\n<p ", html) 3287757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch return html 329