def test_passthrough(self): # Strings without variables are passed through unchanged. self.assertEqual(Templite("Hello").render(), "Hello") self.assertEqual( Templite("Hello, 20% fun time!").render(), "Hello, 20% fun time!" )
def index_file(self): """Write the index.html file for this report.""" index_tmpl = Templite(read_data("index.html"), self.template_globals) skipped_covered_msg = skipped_empty_msg = "" if self.skipped_covered_count: msg = "{} {} skipped due to complete coverage." skipped_covered_msg = msg.format( self.skipped_covered_count, "file" if self.skipped_covered_count == 1 else "files", ) if self.skipped_empty_count: msg = "{} empty {} skipped." skipped_empty_msg = msg.format( self.skipped_empty_count, "file" if self.skipped_empty_count == 1 else "files", ) html = index_tmpl.render({ 'files': self.file_summaries, 'totals': self.totals, 'skipped_covered_msg': skipped_covered_msg, 'skipped_empty_msg': skipped_empty_msg, }) index_file = os.path.join(self.directory, "index.html") write_html(index_file, html) self.coverage._message(f"Wrote HTML report to {index_file}") # Write the latest hashes for next time. self.incr.write()
def __init__(self, cov, config): super(HtmlReporter, self).__init__(cov, config) self.directory = None title = self.config.html_title if env.PY2: title = title.decode("utf8") self.template_globals = { 'escape': escape, 'pair': pair, 'title': title, '__url__': coverage.__url__, '__version__': coverage.__version__, } self.source_tmpl = Templite(read_data("pyfile.html"), self.template_globals) self.coverage = cov self.files = [] self.all_files_nums = [] self.has_arcs = self.coverage.data.has_arcs() self.status = HtmlStatus() self.extra_css = None self.totals = Numbers() self.time_stamp = datetime.datetime.now().strftime('%Y-%m-%d %H:%M')
def __init__(self, coverage, ignore_errors=False): super(HtmlReporter, self).__init__(coverage, ignore_errors) self.directory = None self.source_tmpl = Templite(data("htmlfiles/pyfile.html"), globals()) self.files = [] self.arcs = coverage.data.has_arcs()
def __init__(self, cov): self.coverage = cov self.config = self.coverage.config self.directory = self.config.html_dir self.skip_covered = self.config.html_skip_covered if self.skip_covered is None: self.skip_covered = self.config.skip_covered self.skip_empty = self.config.html_skip_empty if self.skip_empty is None: self.skip_empty = self.config.skip_empty self.skipped_covered_count = 0 self.skipped_empty_count = 0 title = self.config.html_title if self.config.extra_css: self.extra_css = os.path.basename(self.config.extra_css) else: self.extra_css = None self.data = self.coverage.get_data() self.has_arcs = self.data.has_arcs() self.file_summaries = [] self.all_files_nums = [] self.incr = IncrementalChecker(self.directory) self.datagen = HtmlDataGeneration(self.coverage) self.totals = Numbers(precision=self.config.precision) self.directory_was_empty = False self.template_globals = { # Functions available in the templates. 'escape': escape, 'pair': pair, 'len': len, # Constants for this report. '__url__': coverage.__url__, '__version__': coverage.__version__, 'title': title, 'time_stamp': format_local_datetime(datetime.datetime.now()), 'extra_css': self.extra_css, 'has_arcs': self.has_arcs, 'show_contexts': self.config.show_contexts, # Constants for all reports. # These css classes determine which lines are highlighted by default. 'category': { 'exc': 'exc show_exc', 'mis': 'mis show_mis', 'par': 'par run show_par', 'run': 'run', } } self.pyfile_html_source = read_data("pyfile.html") self.source_tmpl = Templite(self.pyfile_html_source, self.template_globals)
def test_reusability(self): # A single Templite can be used more than once with different data. globs = { 'upper': lambda x: x.upper(), 'punct': '!', } template = Templite("This is {{name|upper}}{{punct}}", globs) self.assertEqual(template.render({'name': 'Ned'}), "This is NED!") self.assertEqual(template.render({'name': 'Ben'}), "This is BEN!")
def test_reusability(self): # A single Templite can be used more than once with different data. globs = { 'upper': lambda x: x.upper(), 'punct': '!', } template = Templite("This is {{name|upper}}{{punct}}", globs) self.assertEqual(template.render({'name':'Ned'}), "This is NED!") self.assertEqual(template.render({'name':'Ben'}), "This is BEN!")
def index_file(self): index_tmpl = Templite(data('index.html'), self.template_globals) self.totals = sum([ f['nums'] for f in self.files ]) html = index_tmpl.render({'arcs': self.arcs, 'extra_css': self.extra_css, 'files': self.files, 'totals': self.totals}) if sys.version_info < (3, 0): html = html.decode('utf-8') self.write_html(os.path.join(self.directory, 'index.html'), html) self.status.write(self.directory)
def index_file(self): """Write the index.html file for this report.""" index_tmpl = Templite(data("htmlfiles/index.html"), globals()) files = self.files arcs = self.arcs totals = sum([f['nums'] for f in files]) fhtml = open(os.path.join(self.directory, "index.html"), "w") fhtml.write(index_tmpl.render(locals())) fhtml.close()
def index_file(self): """Write the index.html file for this report.""" index_tmpl = Templite(read_data("index.html"), self.template_globals) html = index_tmpl.render({ 'files': self.file_summaries, 'totals': self.totals, }) write_html(os.path.join(self.directory, "index.html"), html) # Write the latest hashes for next time. self.incr.write()
def index_file(self): index_tmpl = Templite(data('index.html'), self.template_globals) self.totals = sum([f['nums'] for f in self.files]) html = index_tmpl.render({ 'arcs': self.arcs, 'extra_css': self.extra_css, 'files': self.files, 'totals': self.totals }) if sys.version_info < (3, 0): html = html.decode('utf-8') self.write_html(os.path.join(self.directory, 'index.html'), html) self.status.write(self.directory)
def index_file(self): """Write the index.html file for this report.""" index_tmpl = Templite(data("index.html"), self.template_globals) self.totals = sum(f["nums"] for f in self.files) html = index_tmpl.render( {"arcs": self.arcs, "extra_css": self.extra_css, "files": self.files, "totals": self.totals} ) self.write_html(os.path.join(self.directory, "index.html"), html) # Write the latest hashes for next time. self.status.write(self.directory)
def __init__(self, cov): self.coverage = cov self.config = self.coverage.config self.directory = self.config.html_dir title = self.config.html_title if env.PY2: title = title.decode("utf8") if self.config.extra_css: self.extra_css = os.path.basename(self.config.extra_css) else: self.extra_css = None self.data = self.coverage.get_data() self.has_arcs = self.data.has_arcs() self.file_summaries = [] self.all_files_nums = [] self.incr = IncrementalChecker(self.directory) self.datagen = HtmlDataGeneration(self.coverage) self.totals = Numbers() self.template_globals = { # Functions available in the templates. 'escape': escape, 'pair': pair, 'len': len, # Constants for this report. '__url__': coverage.__url__, '__version__': coverage.__version__, 'title': title, 'time_stamp': datetime.datetime.now().strftime('%Y-%m-%d %H:%M'), 'extra_css': self.extra_css, 'has_arcs': self.has_arcs, 'show_contexts': self.config.show_contexts, # Constants for all reports. # These css classes determine which lines are highlighted by default. 'category': { 'exc': 'exc', 'mis': 'mis', 'par': 'par run hide_run', 'run': 'run hide_run', } } self.pyfile_html_source = read_data("pyfile.html") self.source_tmpl = Templite(self.pyfile_html_source, self.template_globals)
def __init__(self, cov, config): super(HtmlReporter, self).__init__(cov, config) self.directory = None title = self.config.html_title if env.PY2: title = title.decode("utf8") self.template_globals = { 'escape': escape, 'pair': pair, 'title': title, '__url__': coverage.__url__, '__version__': coverage.__version__, } self.source_tmpl = Templite( data("pyfile.html"), self.template_globals ) self.coverage = cov self.files = [] self.arcs = self.coverage.data.has_arcs() self.status = HtmlStatus() self.extra_css = None self.totals = Numbers() self.time_stamp = datetime.datetime.now().strftime('%Y-%m-%d %H:%M')
def index_file(self): """Write the index.html file for this report.""" index_tmpl = Templite(data("htmlfiles/index.html"), self.template_globals) files = self.files arcs = self.arcs totals = sum([f['nums'] for f in files]) extra_css = self.extra_css self.write_html(os.path.join(self.directory, "index.html"), index_tmpl.render(locals())) # Write the latest hashes for next time. self.status.write(self.directory)
def __init__(self, cov, ignore_errors=False): super(HtmlReporter, self).__init__(cov, ignore_errors) self.directory = None self.template_globals = { 'escape': escape, '__url__': coverage.__url__, '__version__': coverage.__version__, } self.source_tmpl = Templite(data("htmlfiles/pyfile.html"), self.template_globals) self.coverage = cov self.files = [] self.arcs = self.coverage.data.has_arcs() self.status = HtmlStatus()
def __init__(self, cov, config): super(HtmlReporter, self).__init__(cov, config) self.directory = None self.template_globals = { 'escape': escape, 'title': self.config.html_title, '__url__': coverage.__url__, '__version__': coverage.__version__ } self.source_tmpl = Templite(data('pyfile.html'), self.template_globals) self.coverage = cov self.files = [] self.arcs = self.coverage.data.has_arcs() self.status = HtmlStatus() self.extra_css = None self.totals = Numbers()
def try_render(self, text, ctx=None, result=None): """Render `text` through `ctx`, and it had better be `result`. Result defaults to None so we can shorten the calls where we expect an exception and never get to the result comparison. """ actual = Templite(text).render(ctx or {}) if result: self.assertEqual(actual, result)
def index_file(self): """Write the index.html file for this report.""" index_tmpl = Templite(data("htmlfiles/index.html"), self.template_globals) files = self.files arcs = self.arcs totals = sum([f['nums'] for f in files]) fhtml = open(os.path.join(self.directory, "index.html"), "w") try: fhtml.write(index_tmpl.render(locals())) finally: fhtml.close() # Write the latest hashes for next time. self.status.write(self.directory)
def index_file(self): """Write the index.html file for this report.""" index_tmpl = Templite( data("htmlfiles/index.html"), self.template_globals ) files = self.files arcs = self.arcs totals = sum([f['nums'] for f in files]) self.write_html( os.path.join(self.directory, "index.html"), index_tmpl.render(locals()) ) # Write the latest hashes for next time. self.status.write(self.directory)
def index_file(self): """Write the index.html file for this report.""" index_tmpl = Templite(data("index.html"), self.template_globals) self.totals = sum(f['nums'] for f in self.files) html = index_tmpl.render({ 'has_arcs': self.has_arcs, 'extra_css': self.extra_css, 'files': self.files, 'totals': self.totals, 'time_stamp': self.time_stamp, }) self.write_html(os.path.join(self.directory, "index.html"), html) # Write the latest hashes for next time. self.status.write(self.directory)
def index_file(self): """Write the index.html file for this report.""" index_tmpl = Templite(read_data("index.html"), self.template_globals) self.totals = sum(f['nums'] for f in self.files) html = index_tmpl.render({ 'has_arcs': self.has_arcs, 'extra_css': self.extra_css, 'files': self.files, 'totals': self.totals, 'time_stamp': self.time_stamp, }) write_html(os.path.join(self.directory, "index.html"), html) # Write the latest hashes for next time. self.status.write(self.directory)
def index_file(self): """Write the index.html file for this report.""" index_tmpl = Templite(data("index.html"), self.template_globals) self.totals = sum([f['nums'] for f in self.files]) html = index_tmpl.render({ 'arcs': self.arcs, 'extra_css': self.extra_css, 'files': self.files, 'totals': self.totals, }) if sys.version_info < (3, 0): html = html.decode("utf-8") self.write_html(os.path.join(self.directory, "index.html"), html) # Write the latest hashes for next time. self.status.write(self.directory)
def __init__(self, cov, config): super(HtmlReporter, self).__init__(cov, config) self.directory = None title = self.config.html_title try: self.inline_styles = self.config.inline_styles self.not_inline_styles = False # reading the css stylesheet f = open( os.path.join(os.path.dirname(__file__), *["htmlfiles", "style.css"]), "rb") self.css_styles = f.read().decode('utf-8').strip() f.flush() f.close() except Exception as e: print(e) self.inline_styles = False self.not_inline_styles = True self.css_styles = None if env.PY2: title = title.decode("utf8") self.template_globals = { 'escape': escape, 'pair': pair, 'title': title, '__url__': coverage.__url__, '__version__': coverage.__version__, } self.source_tmpl = Templite(read_data("pyfile.html"), self.template_globals) self.data = cov.get_data() self.files = [] self.all_files_nums = [] self.has_arcs = self.data.has_arcs() self.status = HtmlStatus() self.extra_css = None self.totals = Numbers() self.time_stamp = datetime.datetime.now().strftime('%Y-%m-%d %H:%M')
def try_render(self, text, ctx=None, result=None): """Render `text` through `ctx`, and it had better be `result`. Result defaults to None so we can shorten the calls where we expect an exception and never get to the result comparison. """ actual = Templite(text).render(ctx or {}) # If result is None, then an exception should have prevented us getting # to here. assert result is not None self.assertEqual(actual, result)
def index_file(self): """Write the index.html file for this report.""" index_tmpl = Templite( data("htmlfiles/index.html"), self.template_globals ) files = self.files arcs = self.arcs self.totals = totals = sum([f['nums'] for f in files]) extra_css = self.extra_css html = index_tmpl.render(locals()) if sys.version_info < (3, 0): html = html.decode("utf-8") self.write_html( os.path.join(self.directory, "index.html"), html ) # Write the latest hashes for next time. self.status.write(self.directory)
def __init__(self, cov, config): super(HtmlReporter, self).__init__(cov, config) self.directory = None self.template_globals = {'escape': escape, 'title': self.config.html_title, '__url__': coverage.__url__, '__version__': coverage.__version__} self.source_tmpl = Templite(data('pyfile.html'), self.template_globals) self.coverage = cov self.files = [] self.arcs = self.coverage.data.has_arcs() self.status = HtmlStatus() self.extra_css = None self.totals = Numbers()
def index_file(self): """Write the index.html file for this report.""" index_tmpl = Templite( data("index.html"), self.template_globals ) self.totals = sum(f['nums'] for f in self.files) html = index_tmpl.render({ 'arcs': self.arcs, 'extra_css': self.extra_css, 'files': self.files, 'totals': self.totals, }) if sys.version_info < (3, 0): html = html.decode("utf-8") self.write_html( os.path.join(self.directory, "index.html"), html ) # Write the latest hashes for next time. self.status.write(self.directory)
def __init__(self, cov, ignore_errors=False): super(HtmlReporter, self).__init__(cov, ignore_errors) self.directory = None self.template_globals = { 'escape': escape, '__url__': coverage.__url__, '__version__': coverage.__version__, } self.source_tmpl = Templite( data("htmlfiles/pyfile.html"), self.template_globals ) self.coverage = cov self.files = [] self.arcs = self.coverage.data.has_arcs() self.status = HtmlStatus()
def __init__(self, cov, config): super(HtmlReporter, self).__init__(cov, config) self.directory = None title = self.config.html_title if env.PY2: title = title.decode("utf8") self.template_globals = { "escape": escape, "pair": pair, "title": title, "__url__": coverage.__url__, "__version__": coverage.__version__, } self.source_tmpl = Templite(data("pyfile.html"), self.template_globals) self.coverage = cov self.files = [] self.arcs = self.coverage.data.has_arcs() self.status = HtmlStatus() self.extra_css = None self.totals = Numbers()
class HtmlReporter(Reporter): STATIC_FILES = ['style.css', 'jquery.min.js', 'jquery.hotkeys.js', 'jquery.isonscreen.js', 'jquery.tablesorter.min.js', 'coverage_html.js', 'keybd_closed.png', 'keybd_open.png'] def __init__(self, cov, config): super(HtmlReporter, self).__init__(cov, config) self.directory = None self.template_globals = {'escape': escape, 'title': self.config.html_title, '__url__': coverage.__url__, '__version__': coverage.__version__} self.source_tmpl = Templite(data('pyfile.html'), self.template_globals) self.coverage = cov self.files = [] self.arcs = self.coverage.data.has_arcs() self.status = HtmlStatus() self.extra_css = None self.totals = Numbers() def report(self, morfs): self.status.read(self.config.html_dir) m = Hasher() m.update(self.config) these_settings = m.digest() if self.status.settings_hash() != these_settings: self.status.reset() self.status.set_settings_hash(these_settings) if self.config.extra_css: self.extra_css = os.path.basename(self.config.extra_css) self.report_files(self.html_file, morfs, self.config.html_dir) if not self.files: raise CoverageException('No data to report.') self.index_file() self.make_local_static_report_files() return self.totals.pc_covered def make_local_static_report_files(self): for static in self.STATIC_FILES: shutil.copyfile(data_filename(static), os.path.join(self.directory, static)) if self.extra_css: shutil.copyfile(self.config.extra_css, os.path.join(self.directory, self.extra_css)) def write_html(self, fname, html): fout = open(fname, 'wb') try: fout.write(html.encode('ascii', 'xmlcharrefreplace')) finally: fout.close() def file_hash(self, source, cu): m = Hasher() m.update(source) self.coverage.data.add_to_hash(cu.filename, m) return m.digest() def html_file(self, cu, analysis): source_file = cu.source_file() try: source = source_file.read() finally: source_file.close() flat_rootname = cu.flat_rootname() this_hash = self.file_hash(source, cu) that_hash = self.status.file_hash(flat_rootname) if this_hash == that_hash: self.files.append(self.status.index_info(flat_rootname)) return self.status.set_file_hash(flat_rootname, this_hash) if sys.version_info < (3, 0): encoding = source_encoding(source) if encoding.startswith('utf-8') and source[:3] == '\xef\xbb\xbf': source = source[3:] encoding = 'utf-8' nums = analysis.numbers missing_branch_arcs = analysis.missing_branch_arcs() c_run = 'run hide_run' c_exc = 'exc' c_mis = 'mis' c_par = 'par ' + c_run lines = [] for lineno, line in enumerate(source_token_lines(source)): lineno += 1 line_class = [] annotate_html = '' annotate_title = '' if lineno in analysis.statements: line_class.append('stm') if lineno in analysis.excluded: line_class.append(c_exc) elif lineno in analysis.missing: line_class.append(c_mis) elif self.arcs and lineno in missing_branch_arcs: line_class.append(c_par) annlines = [] for b in missing_branch_arcs[lineno]: if b < 0: annlines.append('exit') else: annlines.append(str(b)) annotate_html = ' '.join(annlines) if len(annlines) > 1: annotate_title = 'no jumps to these line numbers' elif len(annlines) == 1: annotate_title = 'no jump to this line number' elif lineno in analysis.statements: line_class.append(c_run) html = [] for tok_type, tok_text in line: if tok_type == 'ws': html.append(escape(tok_text)) else: tok_html = escape(tok_text) or ' ' html.append("<span class='%s'>%s</span>" % (tok_type, tok_html)) lines.append({'html': ''.join(html), 'number': lineno, 'class': ' '.join(line_class) or 'pln', 'annotate': annotate_html, 'annotate_title': annotate_title}) html = spaceless(self.source_tmpl.render({'c_exc': c_exc, 'c_mis': c_mis, 'c_par': c_par, 'c_run': c_run, 'arcs': self.arcs, 'extra_css': self.extra_css, 'cu': cu, 'nums': nums, 'lines': lines})) if sys.version_info < (3, 0): html = html.decode(encoding) html_filename = flat_rootname + '.html' html_path = os.path.join(self.directory, html_filename) self.write_html(html_path, html) index_info = {'nums': nums, 'html_filename': html_filename, 'name': cu.name} self.files.append(index_info) self.status.set_index_info(flat_rootname, index_info) def index_file(self): index_tmpl = Templite(data('index.html'), self.template_globals) self.totals = sum([ f['nums'] for f in self.files ]) html = index_tmpl.render({'arcs': self.arcs, 'extra_css': self.extra_css, 'files': self.files, 'totals': self.totals}) if sys.version_info < (3, 0): html = html.decode('utf-8') self.write_html(os.path.join(self.directory, 'index.html'), html) self.status.write(self.directory)
class HtmlReporter(Reporter): """HTML reporting.""" # These files will be copied from the htmlfiles dir to the output dir. STATIC_FILES = [ ("style.css", ""), ("jquery.min.js", "jquery"), ("jquery.hotkeys.js", "jquery-hotkeys"), ("jquery.isonscreen.js", "jquery-isonscreen"), ("jquery.tablesorter.min.js", "jquery-tablesorter"), ("coverage_html.js", ""), ("keybd_closed.png", ""), ("keybd_open.png", ""), ] def __init__(self, cov, config): super(HtmlReporter, self).__init__(cov, config) self.directory = None self.template_globals = { 'escape': escape, 'title': self.config.html_title, '__url__': coverage.__url__, '__version__': coverage.__version__, } self.source_tmpl = Templite(data("pyfile.html"), self.template_globals) self.coverage = cov self.files = [] self.arcs = self.coverage.data.has_arcs() self.status = HtmlStatus() self.extra_css = None self.totals = Numbers() def report(self, morfs): """Generate an HTML report for `morfs`. `morfs` is a list of modules or filenames. """ assert self.config.html_dir, "must give a directory for html reporting" # Read the status data. self.status.read(self.config.html_dir) # Check that this run used the same settings as the last run. m = Hasher() m.update(self.config) these_settings = m.digest() if self.status.settings_hash() != these_settings: self.status.reset() self.status.set_settings_hash(these_settings) # The user may have extra CSS they want copied. if self.config.extra_css: self.extra_css = os.path.basename(self.config.extra_css) # Process all the files. self.report_files(self.html_file, morfs, self.config.html_dir) if not self.files: raise CoverageException("No data to report.") # Write the index file. self.index_file() self.make_local_static_report_files() return self.totals.pc_covered def make_local_static_report_files(self): """Make local instances of static files for HTML report.""" # The files we provide must always be copied. for static, pkgdir in self.STATIC_FILES: shutil.copyfile(data_filename(static, pkgdir), os.path.join(self.directory, static)) # The user may have extra CSS they want copied. if self.extra_css: shutil.copyfile(self.config.extra_css, os.path.join(self.directory, self.extra_css)) def write_html(self, fname, html): """Write `html` to `fname`, properly encoded.""" fout = open(fname, "wb") try: fout.write(html.encode('ascii', 'xmlcharrefreplace')) finally: fout.close() def file_hash(self, source, cu): """Compute a hash that changes if the file needs to be re-reported.""" m = Hasher() m.update(source) self.coverage.data.add_to_hash(cu.filename, m) return m.digest() def html_file(self, cu, analysis): """Generate an HTML file for one source file.""" source_file = cu.source_file() try: source = source_file.read() finally: source_file.close() # Find out if the file on disk is already correct. flat_rootname = cu.flat_rootname() this_hash = self.file_hash(source, cu) that_hash = self.status.file_hash(flat_rootname) if this_hash == that_hash: # Nothing has changed to require the file to be reported again. self.files.append(self.status.index_info(flat_rootname)) return self.status.set_file_hash(flat_rootname, this_hash) # If need be, determine the encoding of the source file. We use it # later to properly write the HTML. if sys.version_info < (3, 0): encoding = source_encoding(source) # Some UTF8 files have the dreaded UTF8 BOM. If so, junk it. if encoding.startswith("utf-8") and source[:3] == "\xef\xbb\xbf": source = source[3:] encoding = "utf-8" # Get the numbers for this file. nums = analysis.numbers if self.arcs: missing_branch_arcs = analysis.missing_branch_arcs() # These classes determine which lines are highlighted by default. c_run = "run hide_run" c_exc = "exc" c_mis = "mis" c_par = "par " + c_run lines = [] for lineno, line in enumerate(source_token_lines(source)): lineno += 1 # 1-based line numbers. # Figure out how to mark this line. line_class = [] annotate_html = "" annotate_title = "" if lineno in analysis.statements: line_class.append("stm") if lineno in analysis.excluded: line_class.append(c_exc) elif lineno in analysis.missing: line_class.append(c_mis) elif self.arcs and lineno in missing_branch_arcs: line_class.append(c_par) annlines = [] for b in missing_branch_arcs[lineno]: if b < 0: annlines.append("exit") else: annlines.append(str(b)) annotate_html = " ".join(annlines) if len(annlines) > 1: annotate_title = "no jumps to these line numbers" elif len(annlines) == 1: annotate_title = "no jump to this line number" elif lineno in analysis.statements: line_class.append(c_run) # Build the HTML for the line html = [] for tok_type, tok_text in line: if tok_type == "ws": html.append(escape(tok_text)) else: tok_html = escape(tok_text) or ' ' html.append("<span class='%s'>%s</span>" % (tok_type, tok_html)) lines.append({ 'html': ''.join(html), 'number': lineno, 'class': ' '.join(line_class) or "pln", 'annotate': annotate_html, 'annotate_title': annotate_title, }) # Write the HTML page for this file. html = spaceless( self.source_tmpl.render({ 'c_exc': c_exc, 'c_mis': c_mis, 'c_par': c_par, 'c_run': c_run, 'arcs': self.arcs, 'extra_css': self.extra_css, 'cu': cu, 'nums': nums, 'lines': lines, })) if sys.version_info < (3, 0): html = html.decode(encoding) html_filename = flat_rootname + ".html" html_path = os.path.join(self.directory, html_filename) self.write_html(html_path, html) # Save this file's information for the index file. index_info = { 'nums': nums, 'html_filename': html_filename, 'name': cu.name, } self.files.append(index_info) self.status.set_index_info(flat_rootname, index_info) def index_file(self): """Write the index.html file for this report.""" index_tmpl = Templite(data("index.html"), self.template_globals) self.totals = sum([f['nums'] for f in self.files]) html = index_tmpl.render({ 'arcs': self.arcs, 'extra_css': self.extra_css, 'files': self.files, 'totals': self.totals, }) if sys.version_info < (3, 0): html = html.decode("utf-8") self.write_html(os.path.join(self.directory, "index.html"), html) # Write the latest hashes for next time. self.status.write(self.directory)
class HtmlReporter(Reporter): """HTML reporting.""" # These files will be copied from the htmlfiles dir to the output dir. STATIC_FILES = [ ("style.css", ""), ("jquery.min.js", "jquery"), ("jquery.debounce.min.js", "jquery-debounce"), ("jquery.hotkeys.js", "jquery-hotkeys"), ("jquery.isonscreen.js", "jquery-isonscreen"), ("jquery.tablesorter.min.js", "jquery-tablesorter"), ("coverage_html.js", ""), ("keybd_closed.png", ""), ("keybd_open.png", ""), ] def __init__(self, cov, config): super(HtmlReporter, self).__init__(cov, config) self.directory = None title = self.config.html_title if env.PY2: title = title.decode("utf8") self.template_globals = { 'escape': escape, 'pair': pair, 'title': title, '__url__': coverage.__url__, '__version__': coverage.__version__, } self.source_tmpl = Templite( data("pyfile.html"), self.template_globals ) self.coverage = cov self.files = [] self.arcs = self.coverage.data.has_arcs() self.status = HtmlStatus() self.extra_css = None self.totals = Numbers() def report(self, morfs): """Generate an HTML report for `morfs`. `morfs` is a list of modules or filenames. """ assert self.config.html_dir, "must give a directory for html reporting" # Read the status data. self.status.read(self.config.html_dir) # Check that this run used the same settings as the last run. m = Hasher() m.update(self.config) these_settings = m.hexdigest() if self.status.settings_hash() != these_settings: self.status.reset() self.status.set_settings_hash(these_settings) # The user may have extra CSS they want copied. if self.config.extra_css: self.extra_css = os.path.basename(self.config.extra_css) # Process all the files. self.report_files(self.html_file, morfs, self.config.html_dir) if not self.files: raise CoverageException("No data to report.") # Write the index file. self.index_file() self.make_local_static_report_files() return self.totals.pc_covered def make_local_static_report_files(self): """Make local instances of static files for HTML report.""" # The files we provide must always be copied. for static, pkgdir in self.STATIC_FILES: shutil.copyfile( data_filename(static, pkgdir), os.path.join(self.directory, static) ) # The user may have extra CSS they want copied. if self.extra_css: shutil.copyfile( self.config.extra_css, os.path.join(self.directory, self.extra_css) ) def write_html(self, fname, html): """Write `html` to `fname`, properly encoded.""" with open(fname, "wb") as fout: fout.write(html.encode('ascii', 'xmlcharrefreplace')) def file_hash(self, source, fr): """Compute a hash that changes if the file needs to be re-reported.""" m = Hasher() m.update(source) self.coverage.data.add_to_hash(fr.filename, m) return m.hexdigest() def html_file(self, fr, analysis): """Generate an HTML file for one source file.""" source = fr.source() # Find out if the file on disk is already correct. flat_rootname = fr.flat_rootname() this_hash = self.file_hash(source.encode('utf-8'), fr) that_hash = self.status.file_hash(flat_rootname) if this_hash == that_hash: # Nothing has changed to require the file to be reported again. self.files.append(self.status.index_info(flat_rootname)) return self.status.set_file_hash(flat_rootname, this_hash) # Get the numbers for this file. nums = analysis.numbers if self.arcs: missing_branch_arcs = analysis.missing_branch_arcs() # These classes determine which lines are highlighted by default. c_run = "run hide_run" c_exc = "exc" c_mis = "mis" c_par = "par " + c_run lines = [] for lineno, line in enumerate(fr.source_token_lines(), start=1): # Figure out how to mark this line. line_class = [] annotate_html = "" annotate_title = "" if lineno in analysis.statements: line_class.append("stm") if lineno in analysis.excluded: line_class.append(c_exc) elif lineno in analysis.missing: line_class.append(c_mis) elif self.arcs and lineno in missing_branch_arcs: line_class.append(c_par) annlines = [] for b in missing_branch_arcs[lineno]: if b < 0: annlines.append("exit") else: annlines.append(str(b)) annotate_html = " ".join(annlines) if len(annlines) > 1: annotate_title = "no jumps to these line numbers" elif len(annlines) == 1: annotate_title = "no jump to this line number" elif lineno in analysis.statements: line_class.append(c_run) # Build the HTML for the line html = [] for tok_type, tok_text in line: if tok_type == "ws": html.append(escape(tok_text)) else: tok_html = escape(tok_text) or ' ' html.append( "<span class='%s'>%s</span>" % (tok_type, tok_html) ) lines.append({ 'html': ''.join(html), 'number': lineno, 'class': ' '.join(line_class) or "pln", 'annotate': annotate_html, 'annotate_title': annotate_title, }) # Write the HTML page for this file. template_values = { 'c_exc': c_exc, 'c_mis': c_mis, 'c_par': c_par, 'c_run': c_run, 'arcs': self.arcs, 'extra_css': self.extra_css, 'fr': fr, 'nums': nums, 'lines': lines, } html = spaceless(self.source_tmpl.render(template_values)) html_filename = flat_rootname + ".html" html_path = os.path.join(self.directory, html_filename) self.write_html(html_path, html) # Save this file's information for the index file. index_info = { 'nums': nums, 'html_filename': html_filename, 'name': fr.name, } self.files.append(index_info) self.status.set_index_info(flat_rootname, index_info) def index_file(self): """Write the index.html file for this report.""" index_tmpl = Templite( data("index.html"), self.template_globals ) self.totals = sum(f['nums'] for f in self.files) html = index_tmpl.render({ 'arcs': self.arcs, 'extra_css': self.extra_css, 'files': self.files, 'totals': self.totals, }) self.write_html( os.path.join(self.directory, "index.html"), html ) # Write the latest hashes for next time. self.status.write(self.directory)
class HtmlReporter(object): """HTML reporting.""" # These files will be copied from the htmlfiles directory to the output # directory. STATIC_FILES = [ ("style.css", ""), ("jquery.min.js", "jquery"), ("jquery.ba-throttle-debounce.min.js", "jquery-throttle-debounce"), ("jquery.hotkeys.js", "jquery-hotkeys"), ("jquery.isonscreen.js", "jquery-isonscreen"), ("jquery.tablesorter.min.js", "jquery-tablesorter"), ("coverage_html.js", ""), ("keybd_closed.png", ""), ("keybd_open.png", ""), ] def __init__(self, cov): self.coverage = cov self.config = self.coverage.config self.directory = self.config.html_dir title = self.config.html_title if env.PY2: title = title.decode("utf8") if self.config.extra_css: self.extra_css = os.path.basename(self.config.extra_css) else: self.extra_css = None self.data = self.coverage.get_data() self.has_arcs = self.data.has_arcs() self.file_summaries = [] self.all_files_nums = [] self.incr = IncrementalChecker(self.directory) self.datagen = HtmlDataGeneration(self.coverage) self.totals = Numbers() self.template_globals = { # Functions available in the templates. 'escape': escape, 'pair': pair, 'len': len, # Constants for this report. '__url__': coverage.__url__, '__version__': coverage.__version__, 'title': title, 'time_stamp': datetime.datetime.now().strftime('%Y-%m-%d %H:%M'), 'extra_css': self.extra_css, 'has_arcs': self.has_arcs, 'show_contexts': self.config.show_contexts, # Constants for all reports. # These css classes determine which lines are highlighted by default. 'category': { 'exc': 'exc show_exc', 'mis': 'mis show_mis', 'par': 'par run show_par', 'run': 'run', } } self.pyfile_html_source = read_data("pyfile.html") self.source_tmpl = Templite(self.pyfile_html_source, self.template_globals) def report(self, morfs): """Generate an HTML report for `morfs`. `morfs` is a list of modules or file names. """ # Read the status data and check that this run used the same # global data as the last run. self.incr.read() self.incr.check_global_data(self.config, self.pyfile_html_source) # Process all the files. for fr, analysis in get_analysis_to_report(self.coverage, morfs): self.html_file(fr, analysis) if not self.all_files_nums: raise CoverageException("No data to report.") self.totals = sum(self.all_files_nums) # Write the index file. self.index_file() self.make_local_static_report_files() return self.totals.n_statements and self.totals.pc_covered def make_local_static_report_files(self): """Make local instances of static files for HTML report.""" # The files we provide must always be copied. for static, pkgdir in self.STATIC_FILES: shutil.copyfile( data_filename(static, pkgdir), os.path.join(self.directory, static) ) # The user may have extra CSS they want copied. if self.extra_css: shutil.copyfile( self.config.extra_css, os.path.join(self.directory, self.extra_css) ) def html_file(self, fr, analysis): """Generate an HTML file for one source file.""" rootname = flat_rootname(fr.relative_filename()) html_filename = rootname + ".html" ensure_dir(self.directory) html_path = os.path.join(self.directory, html_filename) # Get the numbers for this file. nums = analysis.numbers self.all_files_nums.append(nums) if self.config.skip_covered: # Don't report on 100% files. no_missing_lines = (nums.n_missing == 0) no_missing_branches = (nums.n_partial_branches == 0) if no_missing_lines and no_missing_branches: # If there's an existing file, remove it. file_be_gone(html_path) return if self.config.skip_empty: # Don't report on empty files. if nums.n_statements == 0: file_be_gone(html_path) return # Find out if the file on disk is already correct. if self.incr.can_skip_file(self.data, fr, rootname): self.file_summaries.append(self.incr.index_info(rootname)) return # Write the HTML page for this file. file_data = self.datagen.data_for_file(fr, analysis) for ldata in file_data.lines: # Build the HTML for the line. html = [] for tok_type, tok_text in ldata.tokens: if tok_type == "ws": html.append(escape(tok_text)) else: tok_html = escape(tok_text) or ' ' html.append( u'<span class="{}">{}</span>'.format(tok_type, tok_html) ) ldata.html = ''.join(html) if ldata.short_annotations: # 202F is NARROW NO-BREAK SPACE. # 219B is RIGHTWARDS ARROW WITH STROKE. ldata.annotate = u", ".join( u"{} ↛ {}".format(ldata.number, d) for d in ldata.short_annotations ) else: ldata.annotate = None if ldata.long_annotations: longs = ldata.long_annotations if len(longs) == 1: ldata.annotate_long = longs[0] else: ldata.annotate_long = u"{:d} missed branches: {}".format( len(longs), u", ".join( u"{:d}) {}".format(num, ann_long) for num, ann_long in enumerate(longs, start=1) ), ) else: ldata.annotate_long = None css_classes = [] if ldata.category: css_classes.append(self.template_globals['category'][ldata.category]) ldata.css_class = ' '.join(css_classes) or "pln" html = self.source_tmpl.render(file_data.__dict__) write_html(html_path, html) # Save this file's information for the index file. index_info = { 'nums': nums, 'html_filename': html_filename, 'relative_filename': fr.relative_filename(), } self.file_summaries.append(index_info) self.incr.set_index_info(rootname, index_info) def index_file(self): """Write the index.html file for this report.""" index_tmpl = Templite(read_data("index.html"), self.template_globals) html = index_tmpl.render({ 'files': self.file_summaries, 'totals': self.totals, }) write_html(os.path.join(self.directory, "index.html"), html) # Write the latest hashes for next time. self.incr.write()
class HtmlReporter(Reporter): """HTML reporting.""" # These files will be copied from the htmlfiles dir to the output dir. STATIC_FILES = [ "style.css", "jquery-1.4.3.min.js", "jquery.hotkeys.js", "jquery.isonscreen.js", "jquery.tablesorter.min.js", "coverage_html.js", "keybd_closed.png", "keybd_open.png", ] def __init__(self, cov, ignore_errors=False): super(HtmlReporter, self).__init__(cov, ignore_errors) self.directory = None self.template_globals = { 'escape': escape, '__url__': coverage.__url__, '__version__': coverage.__version__, } self.source_tmpl = Templite(data("htmlfiles/pyfile.html"), self.template_globals) self.coverage = cov self.files = [] self.arcs = self.coverage.data.has_arcs() self.status = HtmlStatus() def report(self, morfs, config=None): """Generate an HTML report for `morfs`. `morfs` is a list of modules or filenames. `config` is a CoverageConfig instance. """ assert config.html_dir, "must provide a directory for html reporting" # Read the status data. self.status.read(config.html_dir) # Check that this run used the same settings as the last run. m = Hasher() m.update(config) these_settings = m.digest() if self.status.settings_hash() != these_settings: self.status.reset() self.status.set_settings_hash(these_settings) # Process all the files. self.report_files(self.html_file, morfs, config, config.html_dir) if not self.files: raise CoverageException("No data to report.") # Write the index file. self.index_file() # Create the once-per-directory files. for static in self.STATIC_FILES: shutil.copyfile(data_filename("htmlfiles/" + static), os.path.join(self.directory, static)) def file_hash(self, source, cu): """Compute a hash that changes if the file needs to be re-reported.""" m = Hasher() m.update(source) self.coverage.data.add_to_hash(cu.filename, m) return m.digest() def html_file(self, cu, analysis): """Generate an HTML file for one source file.""" source_file = cu.source_file() try: source = source_file.read() finally: source_file.close() # Find out if the file on disk is already correct. flat_rootname = cu.flat_rootname() this_hash = self.file_hash(source, cu) that_hash = self.status.file_hash(flat_rootname) if this_hash == that_hash: # Nothing has changed to require the file to be reported again. self.files.append(self.status.index_info(flat_rootname)) return self.status.set_file_hash(flat_rootname, this_hash) nums = analysis.numbers missing_branch_arcs = analysis.missing_branch_arcs() n_par = 0 # accumulated below. arcs = self.arcs # These classes determine which lines are highlighted by default. c_run = "run hide_run" c_exc = "exc" c_mis = "mis" c_par = "par " + c_run lines = [] for lineno, line in enumerate(source_token_lines(source)): lineno += 1 # 1-based line numbers. # Figure out how to mark this line. line_class = [] annotate_html = "" annotate_title = "" if lineno in analysis.statements: line_class.append("stm") if lineno in analysis.excluded: line_class.append(c_exc) elif lineno in analysis.missing: line_class.append(c_mis) elif self.arcs and lineno in missing_branch_arcs: line_class.append(c_par) n_par += 1 annlines = [] for b in missing_branch_arcs[lineno]: if b < 0: annlines.append("exit") else: annlines.append(str(b)) annotate_html = " ".join(annlines) if len(annlines) > 1: annotate_title = "no jumps to these line numbers" elif len(annlines) == 1: annotate_title = "no jump to this line number" elif lineno in analysis.statements: line_class.append(c_run) # Build the HTML for the line html = [] for tok_type, tok_text in line: if tok_type == "ws": html.append(escape(tok_text)) else: tok_html = escape(tok_text) or ' ' html.append("<span class='%s'>%s</span>" % (tok_type, tok_html)) lines.append({ 'html': ''.join(html), 'number': lineno, 'class': ' '.join(line_class) or "pln", 'annotate': annotate_html, 'annotate_title': annotate_title, }) # Write the HTML page for this file. html_filename = flat_rootname + ".html" html_path = os.path.join(self.directory, html_filename) html = spaceless(self.source_tmpl.render(locals())) fhtml = open(html_path, 'w') try: fhtml.write(html) finally: fhtml.close() # Save this file's information for the index file. index_info = { 'nums': nums, 'par': n_par, 'html_filename': html_filename, 'name': cu.name, } self.files.append(index_info) self.status.set_index_info(flat_rootname, index_info) def index_file(self): """Write the index.html file for this report.""" index_tmpl = Templite(data("htmlfiles/index.html"), self.template_globals) files = self.files arcs = self.arcs totals = sum([f['nums'] for f in files]) fhtml = open(os.path.join(self.directory, "index.html"), "w") try: fhtml.write(index_tmpl.render(locals())) finally: fhtml.close() # Write the latest hashes for next time. self.status.write(self.directory)
def try_render(self, text, ctx, result): """Render `text` through `ctx`, and it had better be `result`.""" self.assertEqual(Templite(text).render(ctx), result)
class HtmlReporter(Reporter): """HTML reporting.""" # These files will be copied from the htmlfiles directory to the output # directory. STATIC_FILES = [ ("style.css", ""), ("jquery.min.js", "jquery"), ("jquery.ba-throttle-debounce.min.js", "jquery-throttle-debounce"), ("jquery.hotkeys.js", "jquery-hotkeys"), ("jquery.isonscreen.js", "jquery-isonscreen"), ("jquery.tablesorter.min.js", "jquery-tablesorter"), ("coverage_html.js", ""), ("keybd_closed.png", ""), ("keybd_open.png", ""), ] def __init__(self, cov, config): super(HtmlReporter, self).__init__(cov, config) self.directory = None title = self.config.html_title if env.PY2: title = title.decode("utf8") self.template_globals = { 'escape': escape, 'pair': pair, 'title': title, '__url__': coverage.__url__, '__version__': coverage.__version__, } self.source_tmpl = Templite(read_data("pyfile.html"), self.template_globals) self.coverage = cov self.files = [] self.has_arcs = self.coverage.data.has_arcs() self.status = HtmlStatus() self.extra_css = None self.totals = Numbers() self.time_stamp = datetime.datetime.now().strftime('%Y-%m-%d %H:%M') def report(self, morfs): """Generate an HTML report for `morfs`. `morfs` is a list of modules or file names. """ assert self.config.html_dir, "must give a directory for html reporting" # Read the status data. self.status.read(self.config.html_dir) # Check that this run used the same settings as the last run. m = Hasher() m.update(self.config) these_settings = m.hexdigest() if self.status.settings_hash() != these_settings: self.status.reset() self.status.set_settings_hash(these_settings) # The user may have extra CSS they want copied. if self.config.extra_css: self.extra_css = os.path.basename(self.config.extra_css) # Process all the files. self.report_files(self.html_file, morfs, self.config.html_dir) if not self.files: raise CoverageException("No data to report.") # Write the index file. self.index_file() self.make_local_static_report_files() return self.totals.n_statements and self.totals.pc_covered def make_local_static_report_files(self): """Make local instances of static files for HTML report.""" # The files we provide must always be copied. for static, pkgdir in self.STATIC_FILES: shutil.copyfile( data_filename(static, pkgdir), os.path.join(self.directory, static) ) # The user may have extra CSS they want copied. if self.extra_css: shutil.copyfile( self.config.extra_css, os.path.join(self.directory, self.extra_css) ) def file_hash(self, source, fr): """Compute a hash that changes if the file needs to be re-reported.""" m = Hasher() m.update(source) self.coverage.data.add_to_hash(fr.filename, m) return m.hexdigest() def html_file(self, fr, analysis): """Generate an HTML file for one source file.""" source = fr.source() # Find out if the file on disk is already correct. rootname = flat_rootname(fr.relative_filename()) this_hash = self.file_hash(source.encode('utf-8'), fr) that_hash = self.status.file_hash(rootname) if this_hash == that_hash: # Nothing has changed to require the file to be reported again. self.files.append(self.status.index_info(rootname)) return self.status.set_file_hash(rootname, this_hash) # Get the numbers for this file. nums = analysis.numbers if self.has_arcs: missing_branch_arcs = analysis.missing_branch_arcs() arcs_executed = analysis.arcs_executed() # These classes determine which lines are highlighted by default. c_run = "run hide_run" c_exc = "exc" c_mis = "mis" c_par = "par " + c_run lines = [] for lineno, line in enumerate(fr.source_token_lines(), start=1): # Figure out how to mark this line. line_class = [] annotate_html = "" annotate_long = "" if lineno in analysis.statements: line_class.append("stm") if lineno in analysis.excluded: line_class.append(c_exc) elif lineno in analysis.missing: line_class.append(c_mis) elif self.has_arcs and lineno in missing_branch_arcs: line_class.append(c_par) shorts = [] longs = [] for b in missing_branch_arcs[lineno]: if b < 0: shorts.append("exit") else: shorts.append(b) longs.append(fr.missing_arc_description(lineno, b, arcs_executed)) # 202F is NARROW NO-BREAK SPACE. # 219B is RIGHTWARDS ARROW WITH STROKE. short_fmt = "%s ↛ %s" annotate_html = ", ".join(short_fmt % (lineno, d) for d in shorts) if len(longs) == 1: annotate_long = longs[0] else: annotate_long = "%d missed branches: %s" % ( len(longs), ", ".join("%d) %s" % (num, ann_long) for num, ann_long in enumerate(longs, start=1)), ) elif lineno in analysis.statements: line_class.append(c_run) # Build the HTML for the line. html = [] for tok_type, tok_text in line: if tok_type == "ws": html.append(escape(tok_text)) else: tok_html = escape(tok_text) or ' ' html.append( '<span class="%s">%s</span>' % (tok_type, tok_html) ) lines.append({ 'html': ''.join(html), 'number': lineno, 'class': ' '.join(line_class) or "pln", 'annotate': annotate_html, 'annotate_long': annotate_long, }) # Write the HTML page for this file. html = self.source_tmpl.render({ 'c_exc': c_exc, 'c_mis': c_mis, 'c_par': c_par, 'c_run': c_run, 'has_arcs': self.has_arcs, 'extra_css': self.extra_css, 'fr': fr, 'nums': nums, 'lines': lines, 'time_stamp': self.time_stamp, }) html_filename = rootname + ".html" html_path = os.path.join(self.directory, html_filename) write_html(html_path, html) # Save this file's information for the index file. index_info = { 'nums': nums, 'html_filename': html_filename, 'relative_filename': fr.relative_filename(), } self.files.append(index_info) self.status.set_index_info(rootname, index_info) def index_file(self): """Write the index.html file for this report.""" index_tmpl = Templite(read_data("index.html"), self.template_globals) self.totals = sum(f['nums'] for f in self.files) html = index_tmpl.render({ 'has_arcs': self.has_arcs, 'extra_css': self.extra_css, 'files': self.files, 'totals': self.totals, 'time_stamp': self.time_stamp, }) write_html(os.path.join(self.directory, "index.html"), html) # Write the latest hashes for next time. self.status.write(self.directory)
class HtmlReporter(Reporter): """HTML reporting.""" STATIC_FILES = [ 'style.css', 'jquery.min.js', 'jquery.hotkeys.js', 'jquery.isonscreen.js', 'jquery.tablesorter.min.js', 'coverage_html.js', 'keybd_closed.png', 'keybd_open.png' ] def __init__(self, cov, config): super(HtmlReporter, self).__init__(cov, config) self.directory = None self.template_globals = { 'escape': escape, 'title': self.config.html_title, '__url__': coverage.__url__, '__version__': coverage.__version__ } self.source_tmpl = Templite(data('pyfile.html'), self.template_globals) self.coverage = cov self.files = [] self.arcs = self.coverage.data.has_arcs() self.status = HtmlStatus() self.extra_css = None self.totals = Numbers() def report(self, morfs): """Generate an HTML report for `morfs`. `morfs` is a list of modules or filenames. """ self.status.read(self.config.html_dir) m = Hasher() m.update(self.config) these_settings = m.digest() if self.status.settings_hash() != these_settings: self.status.reset() self.status.set_settings_hash(these_settings) if self.config.extra_css: self.extra_css = os.path.basename(self.config.extra_css) self.report_files(self.html_file, morfs, self.config.html_dir) if not self.files: raise CoverageException('No data to report.') self.index_file() self.make_local_static_report_files() return self.totals.pc_covered def make_local_static_report_files(self): """Make local instances of static files for HTML report.""" for static in self.STATIC_FILES: shutil.copyfile(data_filename(static), os.path.join(self.directory, static)) if self.extra_css: shutil.copyfile(self.config.extra_css, os.path.join(self.directory, self.extra_css)) def write_html(self, fname, html): """Write `html` to `fname`, properly encoded.""" fout = open(fname, 'wb') try: fout.write(html.encode('ascii', 'xmlcharrefreplace')) finally: fout.close() def file_hash(self, source, cu): """Compute a hash that changes if the file needs to be re-reported.""" m = Hasher() m.update(source) self.coverage.data.add_to_hash(cu.filename, m) return m.digest() def html_file(self, cu, analysis): """Generate an HTML file for one source file.""" source_file = cu.source_file() try: source = source_file.read() finally: source_file.close() flat_rootname = cu.flat_rootname() this_hash = self.file_hash(source, cu) that_hash = self.status.file_hash(flat_rootname) if this_hash == that_hash: self.files.append(self.status.index_info(flat_rootname)) return self.status.set_file_hash(flat_rootname, this_hash) if sys.version_info < (3, 0): encoding = source_encoding(source) if encoding.startswith('utf-8') and source[:3] == '\xef\xbb\xbf': source = source[3:] encoding = 'utf-8' nums = analysis.numbers missing_branch_arcs = analysis.missing_branch_arcs() c_run = 'run hide_run' c_exc = 'exc' c_mis = 'mis' c_par = 'par ' + c_run lines = [] for lineno, line in enumerate(source_token_lines(source)): lineno += 1 line_class = [] annotate_html = '' annotate_title = '' if lineno in analysis.statements: line_class.append('stm') if lineno in analysis.excluded: line_class.append(c_exc) elif lineno in analysis.missing: line_class.append(c_mis) elif self.arcs and lineno in missing_branch_arcs: line_class.append(c_par) annlines = [] for b in missing_branch_arcs[lineno]: if b < 0: annlines.append('exit') else: annlines.append(str(b)) annotate_html = ' '.join(annlines) if len(annlines) > 1: annotate_title = 'no jumps to these line numbers' elif len(annlines) == 1: annotate_title = 'no jump to this line number' elif lineno in analysis.statements: line_class.append(c_run) html = [] for tok_type, tok_text in line: if tok_type == 'ws': html.append(escape(tok_text)) else: tok_html = escape(tok_text) or ' ' html.append("<span class='%s'>%s</span>" % (tok_type, tok_html)) lines.append({ 'html': ''.join(html), 'number': lineno, 'class': ' '.join(line_class) or 'pln', 'annotate': annotate_html, 'annotate_title': annotate_title }) html = spaceless( self.source_tmpl.render({ 'c_exc': c_exc, 'c_mis': c_mis, 'c_par': c_par, 'c_run': c_run, 'arcs': self.arcs, 'extra_css': self.extra_css, 'cu': cu, 'nums': nums, 'lines': lines })) if sys.version_info < (3, 0): html = html.decode(encoding) html_filename = flat_rootname + '.html' html_path = os.path.join(self.directory, html_filename) self.write_html(html_path, html) index_info = { 'nums': nums, 'html_filename': html_filename, 'name': cu.name } self.files.append(index_info) self.status.set_index_info(flat_rootname, index_info) def index_file(self): """Write the index.html file for this report.""" index_tmpl = Templite(data('index.html'), self.template_globals) self.totals = sum([f['nums'] for f in self.files]) html = index_tmpl.render({ 'arcs': self.arcs, 'extra_css': self.extra_css, 'files': self.files, 'totals': self.totals }) if sys.version_info < (3, 0): html = html.decode('utf-8') self.write_html(os.path.join(self.directory, 'index.html'), html) self.status.write(self.directory)
class HtmlReporter(Reporter): """HTML reporting.""" def __init__(self, coverage, ignore_errors=False): super(HtmlReporter, self).__init__(coverage, ignore_errors) self.directory = None self.source_tmpl = Templite(data("htmlfiles/pyfile.html"), globals()) self.files = [] self.arcs = coverage.data.has_arcs() def report(self, morfs, config=None): """Generate an HTML report for `morfs`. `morfs` is a list of modules or filenames. `config` is a CoverageConfig instance. """ assert config.html_dir, "must provide a directory for html reporting" # Process all the files. self.report_files(self.html_file, morfs, config, config.html_dir) if not self.files: raise CoverageException("No data to report.") # Write the index file. self.index_file() # Create the once-per-directory files. for static in ["style.css", "coverage_html.js", "jquery-1.3.2.min.js", "jquery.tablesorter.min.js"]: shutil.copyfile(data_filename("htmlfiles/" + static), os.path.join(self.directory, static)) def html_file(self, cu, analysis): """Generate an HTML file for one source file.""" source = cu.source_file().read() nums = analysis.numbers missing_branch_arcs = analysis.missing_branch_arcs() n_par = 0 # accumulated below. arcs = self.arcs # These classes determine which lines are highlighted by default. c_run = "run hide_run" c_exc = "exc" c_mis = "mis" c_par = "par " + c_run lines = [] for lineno, line in enumerate(source_token_lines(source)): lineno += 1 # 1-based line numbers. # Figure out how to mark this line. line_class = [] annotate_html = "" annotate_title = "" if lineno in analysis.statements: line_class.append("stm") if lineno in analysis.excluded: line_class.append(c_exc) elif lineno in analysis.missing: line_class.append(c_mis) elif self.arcs and lineno in missing_branch_arcs: line_class.append(c_par) n_par += 1 annlines = [] for b in missing_branch_arcs[lineno]: if b < 0: annlines.append("exit") else: annlines.append(str(b)) annotate_html = " ".join(annlines) if len(annlines) > 1: annotate_title = "no jumps to these line numbers" elif len(annlines) == 1: annotate_title = "no jump to this line number" elif lineno in analysis.statements: line_class.append(c_run) # Build the HTML for the line html = [] for tok_type, tok_text in line: if tok_type == "ws": html.append(escape(tok_text)) else: tok_html = escape(tok_text) or " " html.append("<span class='%s'>%s</span>" % (tok_type, tok_html)) lines.append( { "html": "".join(html), "number": lineno, "class": " ".join(line_class) or "pln", "annotate": annotate_html, "annotate_title": annotate_title, } ) # Write the HTML page for this file. html_filename = cu.flat_rootname() + ".html" html_path = os.path.join(self.directory, html_filename) html = spaceless(self.source_tmpl.render(locals())) fhtml = open(html_path, "w") fhtml.write(html) fhtml.close() # Save this file's information for the index file. self.files.append({"nums": nums, "par": n_par, "html_filename": html_filename, "cu": cu}) def index_file(self): """Write the index.html file for this report.""" index_tmpl = Templite(data("htmlfiles/index.html"), globals()) files = self.files arcs = self.arcs totals = sum([f["nums"] for f in files]) fhtml = open(os.path.join(self.directory, "index.html"), "w") fhtml.write(index_tmpl.render(locals())) fhtml.close()
class HtmlReporter(Reporter): """HTML reporting.""" def __init__(self, coverage, ignore_errors=False): super(HtmlReporter, self).__init__(coverage, ignore_errors) self.directory = None self.source_tmpl = Templite(data("htmlfiles/pyfile.html"), globals()) self.files = [] self.arcs = coverage.data.has_arcs() def report(self, morfs, directory, omit_prefixes=None): """Generate an HTML report for `morfs`. `morfs` is a list of modules or filenames. `directory` is where to put the HTML files. `omit_prefixes` is a list of strings, prefixes of modules to omit from the report. """ assert directory, "must provide a directory for html reporting" # Process all the files. self.report_files(self.html_file, morfs, directory, omit_prefixes) # Write the index file. self.index_file() # Create the once-per-directory files. for static in [ "style.css", "coverage_html.js", "jquery-1.3.2.min.js", "jquery.tablesorter.min.js" ]: shutil.copyfile( data_filename("htmlfiles/" + static), os.path.join(directory, static) ) def html_file(self, cu, analysis): """Generate an HTML file for one source file.""" source = cu.source_file().read() nums = analysis.numbers missing_branch_arcs = analysis.missing_branch_arcs() n_par = 0 # accumulated below. arcs = self.arcs # These classes determine which lines are highlighted by default. c_run = " run hide_run" c_exc = " exc" c_mis = " mis" c_par = " par" + c_run lines = [] for lineno, line in enumerate(source_token_lines(source)): lineno += 1 # 1-based line numbers. # Figure out how to mark this line. line_class = "" annotate_html = "" annotate_title = "" if lineno in analysis.statements: line_class += " stm" if lineno in analysis.excluded: line_class += c_exc elif lineno in analysis.missing: line_class += c_mis elif self.arcs and lineno in missing_branch_arcs: line_class += c_par n_par += 1 annlines = [] for b in missing_branch_arcs[lineno]: if b == -1: annlines.append("exit") else: annlines.append(str(b)) annotate_html = " ".join(annlines) if len(annlines) > 1: annotate_title = "no jumps to these line numbers" elif len(annlines) == 1: annotate_title = "no jump to this line number" elif lineno in analysis.statements: line_class += c_run # Build the HTML for the line html = "" for tok_type, tok_text in line: if tok_type == "ws": html += escape(tok_text) else: tok_html = escape(tok_text) or ' ' html += "<span class='%s'>%s</span>" % (tok_type, tok_html) lines.append({ 'html': html, 'number': lineno, 'class': line_class.strip() or "pln", 'annotate': annotate_html, 'annotate_title': annotate_title, }) # Write the HTML page for this file. html_filename = cu.flat_rootname() + ".html" html_path = os.path.join(self.directory, html_filename) html = spaceless(self.source_tmpl.render(locals())) fhtml = open(html_path, 'w') fhtml.write(html) fhtml.close() # Save this file's information for the index file. self.files.append({ 'nums': nums, 'par': n_par, 'html_filename': html_filename, 'cu': cu, }) def index_file(self): """Write the index.html file for this report.""" index_tmpl = Templite(data("htmlfiles/index.html"), globals()) files = self.files arcs = self.arcs totals = sum([f['nums'] for f in files]) fhtml = open(os.path.join(self.directory, "index.html"), "w") fhtml.write(index_tmpl.render(locals())) fhtml.close()
class HtmlReporter(Reporter): """HTML reporting.""" # These files will be copied from the htmlfiles directory to the output # directory. STATIC_FILES = [ ("style.css", ""), ("jquery.min.js", "jquery"), ("jquery.debounce.min.js", "jquery-debounce"), ("jquery.hotkeys.js", "jquery-hotkeys"), ("jquery.isonscreen.js", "jquery-isonscreen"), ("jquery.tablesorter.min.js", "jquery-tablesorter"), ("coverage_html.js", ""), ("keybd_closed.png", ""), ("keybd_open.png", ""), ] def __init__(self, cov, config): super(HtmlReporter, self).__init__(cov, config) self.directory = None title = self.config.html_title if env.PY2: title = title.decode("utf8") self.template_globals = { "escape": escape, "pair": pair, "title": title, "__url__": coverage.__url__, "__version__": coverage.__version__, } self.source_tmpl = Templite(data("pyfile.html"), self.template_globals) self.coverage = cov self.files = [] self.has_arcs = self.coverage.data.has_arcs() self.status = HtmlStatus() self.extra_css = None self.totals = Numbers() self.time_stamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M") def report(self, morfs): """Generate an HTML report for `morfs`. `morfs` is a list of modules or file names. """ assert self.config.html_dir, "must give a directory for html reporting" # Read the status data. self.status.read(self.config.html_dir) # Check that this run used the same settings as the last run. m = Hasher() m.update(self.config) these_settings = m.hexdigest() if self.status.settings_hash() != these_settings: self.status.reset() self.status.set_settings_hash(these_settings) # The user may have extra CSS they want copied. if self.config.extra_css: self.extra_css = os.path.basename(self.config.extra_css) # Process all the files. self.report_files(self.html_file, morfs, self.config.html_dir) if not self.files: raise CoverageException("No data to report.") # Write the index file. self.index_file() self.make_local_static_report_files() return self.totals.n_statements and self.totals.pc_covered def make_local_static_report_files(self): """Make local instances of static files for HTML report.""" # The files we provide must always be copied. for static, pkgdir in self.STATIC_FILES: shutil.copyfile(data_filename(static, pkgdir), os.path.join(self.directory, static)) # The user may have extra CSS they want copied. if self.extra_css: shutil.copyfile(self.config.extra_css, os.path.join(self.directory, self.extra_css)) def write_html(self, fname, html): """Write `html` to `fname`, properly encoded.""" with open(fname, "wb") as fout: fout.write(html.encode("ascii", "xmlcharrefreplace")) def file_hash(self, source, fr): """Compute a hash that changes if the file needs to be re-reported.""" m = Hasher() m.update(source) self.coverage.data.add_to_hash(fr.filename, m) return m.hexdigest() def html_file(self, fr, analysis): """Generate an HTML file for one source file.""" source = fr.source() # Find out if the file on disk is already correct. rootname = flat_rootname(fr.relative_filename()) this_hash = self.file_hash(source.encode("utf-8"), fr) that_hash = self.status.file_hash(rootname) if this_hash == that_hash: # Nothing has changed to require the file to be reported again. self.files.append(self.status.index_info(rootname)) return self.status.set_file_hash(rootname, this_hash) # Get the numbers for this file. nums = analysis.numbers if self.has_arcs: missing_branch_arcs = analysis.missing_branch_arcs() # These classes determine which lines are highlighted by default. c_run = "run hide_run" c_exc = "exc" c_mis = "mis" c_par = "par " + c_run lines = [] for lineno, line in enumerate(fr.source_token_lines(), start=1): # Figure out how to mark this line. line_class = [] annotate_html = "" annotate_title = "" if lineno in analysis.statements: line_class.append("stm") if lineno in analysis.excluded: line_class.append(c_exc) elif lineno in analysis.missing: line_class.append(c_mis) elif self.has_arcs and lineno in missing_branch_arcs: line_class.append(c_par) shorts = [] longs = [] for b in missing_branch_arcs[lineno]: if b < 0: shorts.append("exit") longs.append("the function exit") else: shorts.append(b) longs.append("line %d" % b) # 202F is NARROW NO-BREAK SPACE. # 219B is RIGHTWARDS ARROW WITH STROKE. short_fmt = "%s ↛ %s" annotate_html = ", ".join(short_fmt % (lineno, d) for d in shorts) annotate_html += " [?]" annotate_title = "Line %d was executed, but never jumped to " % lineno if len(longs) == 1: annotate_title += longs[0] elif len(longs) == 2: annotate_title += longs[0] + " or " + longs[1] else: annotate_title += ", ".join(longs[:-1]) + ", or " + longs[-1] elif lineno in analysis.statements: line_class.append(c_run) # Build the HTML for the line. html = [] for tok_type, tok_text in line: if tok_type == "ws": html.append(escape(tok_text)) else: tok_html = escape(tok_text) or " " html.append('<span class="%s">%s</span>' % (tok_type, tok_html)) lines.append( { "html": "".join(html), "number": lineno, "class": " ".join(line_class) or "pln", "annotate": annotate_html, "annotate_title": annotate_title, } ) # Write the HTML page for this file. template_values = { "c_exc": c_exc, "c_mis": c_mis, "c_par": c_par, "c_run": c_run, "has_arcs": self.has_arcs, "extra_css": self.extra_css, "fr": fr, "nums": nums, "lines": lines, "time_stamp": self.time_stamp, } html = spaceless(self.source_tmpl.render(template_values)) html_filename = rootname + ".html" html_path = os.path.join(self.directory, html_filename) self.write_html(html_path, html) # Save this file's information for the index file. index_info = {"nums": nums, "html_filename": html_filename, "relative_filename": fr.relative_filename()} self.files.append(index_info) self.status.set_index_info(rootname, index_info) def index_file(self): """Write the index.html file for this report.""" index_tmpl = Templite(data("index.html"), self.template_globals) self.totals = sum(f["nums"] for f in self.files) html = index_tmpl.render( { "has_arcs": self.has_arcs, "extra_css": self.extra_css, "files": self.files, "totals": self.totals, "time_stamp": self.time_stamp, } ) self.write_html(os.path.join(self.directory, "index.html"), html) # Write the latest hashes for next time. self.status.write(self.directory)
class HtmlReporter(Reporter): """HTML reporting.""" # These files will be copied from the htmlfiles dir to the output dir. STATIC_FILES = [ "style.css", "jquery-1.4.3.min.js", "jquery.hotkeys.js", "jquery.isonscreen.js", "jquery.tablesorter.min.js", "coverage_html.js", "keybd_closed.png", "keybd_open.png", ] def __init__(self, cov, config): super(HtmlReporter, self).__init__(cov, config) self.directory = None self.template_globals = { 'escape': escape, '__url__': coverage.__url__, '__version__': coverage.__version__, } self.source_tmpl = Templite( data("htmlfiles/pyfile.html"), self.template_globals ) self.coverage = cov self.files = [] self.arcs = self.coverage.data.has_arcs() self.status = HtmlStatus() self.extra_css = None def report(self, morfs): """Generate an HTML report for `morfs`. `morfs` is a list of modules or filenames. """ assert self.config.html_dir, "must give a directory for html reporting" # Read the status data. self.status.read(self.config.html_dir) # Check that this run used the same settings as the last run. m = Hasher() m.update(self.config) these_settings = m.digest() if self.status.settings_hash() != these_settings: self.status.reset() self.status.set_settings_hash(these_settings) # The user may have extra CSS they want copied. if self.config.extra_css: self.extra_css = os.path.basename(self.config.extra_css) # Process all the files. self.report_files(self.html_file, morfs, self.config.html_dir) if not self.files: raise CoverageException("No data to report.") # Write the index file. self.index_file() self.make_local_static_report_files() def make_local_static_report_files(self): """Make local instances of static files for HTML report.""" # The files we provide must always be copied. for static in self.STATIC_FILES: shutil.copyfile( data_filename("htmlfiles/" + static), os.path.join(self.directory, static) ) # The user may have extra CSS they want copied. if self.extra_css: shutil.copyfile( self.config.extra_css, os.path.join(self.directory, self.extra_css) ) def write_html(self, fname, html): """Write `html` to `fname`, properly encoded.""" fout = open(fname, "wb") try: fout.write(html.encode('ascii', 'xmlcharrefreplace')) finally: fout.close() def file_hash(self, source, cu): """Compute a hash that changes if the file needs to be re-reported.""" m = Hasher() m.update(source) self.coverage.data.add_to_hash(cu.filename, m) return m.digest() def html_file(self, cu, analysis): """Generate an HTML file for one source file.""" source_file = cu.source_file() try: source = source_file.read() finally: source_file.close() # Find out if the file on disk is already correct. flat_rootname = cu.flat_rootname() this_hash = self.file_hash(source, cu) that_hash = self.status.file_hash(flat_rootname) if this_hash == that_hash: # Nothing has changed to require the file to be reported again. self.files.append(self.status.index_info(flat_rootname)) return self.status.set_file_hash(flat_rootname, this_hash) # If need be, determine the encoding of the source file. We use it # later to properly write the HTML. if sys.version_info < (3, 0): encoding = source_encoding(source) # Some UTF8 files have the dreaded UTF8 BOM. If so, junk it. if encoding.startswith("utf-8") and source[:3] == "\xef\xbb\xbf": source = source[3:] encoding = "utf-8" # Get the numbers for this file. nums = analysis.numbers missing_branch_arcs = analysis.missing_branch_arcs() n_par = 0 # accumulated below. arcs = self.arcs # These classes determine which lines are highlighted by default. c_run = "run hide_run" c_exc = "exc" c_mis = "mis" c_par = "par " + c_run lines = [] for lineno, line in enumerate(source_token_lines(source)): lineno += 1 # 1-based line numbers. # Figure out how to mark this line. line_class = [] annotate_html = "" annotate_title = "" if lineno in analysis.statements: line_class.append("stm") if lineno in analysis.excluded: line_class.append(c_exc) elif lineno in analysis.missing: line_class.append(c_mis) elif self.arcs and lineno in missing_branch_arcs: line_class.append(c_par) n_par += 1 annlines = [] for b in missing_branch_arcs[lineno]: if b < 0: annlines.append("exit") else: annlines.append(str(b)) annotate_html = " ".join(annlines) if len(annlines) > 1: annotate_title = "no jumps to these line numbers" elif len(annlines) == 1: annotate_title = "no jump to this line number" elif lineno in analysis.statements: line_class.append(c_run) # Build the HTML for the line html = [] for tok_type, tok_text in line: if tok_type == "ws": html.append(escape(tok_text)) else: tok_html = escape(tok_text) or ' ' html.append( "<span class='%s'>%s</span>" % (tok_type, tok_html) ) lines.append({ 'html': ''.join(html), 'number': lineno, 'class': ' '.join(line_class) or "pln", 'annotate': annotate_html, 'annotate_title': annotate_title, }) # Write the HTML page for this file. html_filename = flat_rootname + ".html" html_path = os.path.join(self.directory, html_filename) extra_css = self.extra_css html = spaceless(self.source_tmpl.render(locals())) if sys.version_info < (3, 0): html = html.decode(encoding) self.write_html(html_path, html) # Save this file's information for the index file. index_info = { 'nums': nums, 'par': n_par, 'html_filename': html_filename, 'name': cu.name, } self.files.append(index_info) self.status.set_index_info(flat_rootname, index_info) def index_file(self): """Write the index.html file for this report.""" index_tmpl = Templite( data("htmlfiles/index.html"), self.template_globals ) files = self.files arcs = self.arcs totals = sum([f['nums'] for f in files]) extra_css = self.extra_css self.write_html( os.path.join(self.directory, "index.html"), index_tmpl.render(locals()) ) # Write the latest hashes for next time. self.status.write(self.directory)
class HtmlReporter(Reporter): """HTML reporting.""" # These files will be copied from the htmlfiles directory to the output # directory. STATIC_FILES = [ ("style.css", ""), ("jquery.min.js", "jquery"), ("jquery.debounce.min.js", "jquery-debounce"), ("jquery.hotkeys.js", "jquery-hotkeys"), ("jquery.isonscreen.js", "jquery-isonscreen"), ("jquery.tablesorter.min.js", "jquery-tablesorter"), ("coverage_html.js", ""), ("keybd_closed.png", ""), ("keybd_open.png", ""), ] def __init__(self, cov, config): super(HtmlReporter, self).__init__(cov, config) self.directory = None title = self.config.html_title if env.PY2: title = title.decode("utf8") self.template_globals = { 'escape': escape, 'pair': pair, 'title': title, '__url__': coverage.__url__, '__version__': coverage.__version__, } self.source_tmpl = Templite(read_data("pyfile.html"), self.template_globals) self.coverage = cov self.files = [] self.has_arcs = self.coverage.data.has_arcs() self.status = HtmlStatus() self.extra_css = None self.totals = Numbers() self.time_stamp = datetime.datetime.now().strftime('%Y-%m-%d %H:%M') def report(self, morfs): """Generate an HTML report for `morfs`. `morfs` is a list of modules or file names. """ assert self.config.html_dir, "must give a directory for html reporting" # Read the status data. self.status.read(self.config.html_dir) # Check that this run used the same settings as the last run. m = Hasher() m.update(self.config) these_settings = m.hexdigest() if self.status.settings_hash() != these_settings: self.status.reset() self.status.set_settings_hash(these_settings) # The user may have extra CSS they want copied. if self.config.extra_css: self.extra_css = os.path.basename(self.config.extra_css) # Process all the files. self.report_files(self.html_file, morfs, self.config.html_dir) if not self.files: raise CoverageException("No data to report.") # Write the index file. self.index_file() self.make_local_static_report_files() return self.totals.n_statements and self.totals.pc_covered def make_local_static_report_files(self): """Make local instances of static files for HTML report.""" # The files we provide must always be copied. for static, pkgdir in self.STATIC_FILES: shutil.copyfile(data_filename(static, pkgdir), os.path.join(self.directory, static)) # The user may have extra CSS they want copied. if self.extra_css: shutil.copyfile(self.config.extra_css, os.path.join(self.directory, self.extra_css)) def file_hash(self, source, fr): """Compute a hash that changes if the file needs to be re-reported.""" m = Hasher() m.update(source) self.coverage.data.add_to_hash(fr.filename, m) return m.hexdigest() def html_file(self, fr, analysis): """Generate an HTML file for one source file.""" source = fr.source() # Find out if the file on disk is already correct. rootname = flat_rootname(fr.relative_filename()) this_hash = self.file_hash(source.encode('utf-8'), fr) that_hash = self.status.file_hash(rootname) if this_hash == that_hash: # Nothing has changed to require the file to be reported again. self.files.append(self.status.index_info(rootname)) return self.status.set_file_hash(rootname, this_hash) # Get the numbers for this file. nums = analysis.numbers if self.has_arcs: missing_branch_arcs = analysis.missing_branch_arcs() arcs_executed = analysis.arcs_executed() # These classes determine which lines are highlighted by default. c_run = "run hide_run" c_exc = "exc" c_mis = "mis" c_par = "par " + c_run lines = [] for lineno, line in enumerate(fr.source_token_lines(), start=1): # Figure out how to mark this line. line_class = [] annotate_html = "" annotate_long = "" if lineno in analysis.statements: line_class.append("stm") if lineno in analysis.excluded: line_class.append(c_exc) elif lineno in analysis.missing: line_class.append(c_mis) elif self.has_arcs and lineno in missing_branch_arcs: line_class.append(c_par) shorts = [] longs = [] for b in missing_branch_arcs[lineno]: if b < 0: shorts.append("exit") else: shorts.append(b) longs.append( fr.missing_arc_description(lineno, b, arcs_executed)) # 202F is NARROW NO-BREAK SPACE. # 219B is RIGHTWARDS ARROW WITH STROKE. short_fmt = "%s ↛ %s" annotate_html = ", ".join(short_fmt % (lineno, d) for d in shorts) if len(longs) == 1: annotate_long = longs[0] else: annotate_long = "%d missed branches: %s" % ( len(longs), ", ".join( "%d) %s" % (num, ann_long) for num, ann_long in enumerate(longs, start=1)), ) elif lineno in analysis.statements: line_class.append(c_run) # Build the HTML for the line. html = [] for tok_type, tok_text in line: if tok_type == "ws": html.append(escape(tok_text)) else: tok_html = escape(tok_text) or ' ' html.append('<span class="%s">%s</span>' % (tok_type, tok_html)) lines.append({ 'html': ''.join(html), 'number': lineno, 'class': ' '.join(line_class) or "pln", 'annotate': annotate_html, 'annotate_long': annotate_long, }) # Write the HTML page for this file. html = self.source_tmpl.render({ 'c_exc': c_exc, 'c_mis': c_mis, 'c_par': c_par, 'c_run': c_run, 'has_arcs': self.has_arcs, 'extra_css': self.extra_css, 'fr': fr, 'nums': nums, 'lines': lines, 'time_stamp': self.time_stamp, }) html_filename = rootname + ".html" html_path = os.path.join(self.directory, html_filename) write_html(html_path, html) # Save this file's information for the index file. index_info = { 'nums': nums, 'html_filename': html_filename, 'relative_filename': fr.relative_filename(), } self.files.append(index_info) self.status.set_index_info(rootname, index_info) def index_file(self): """Write the index.html file for this report.""" index_tmpl = Templite(read_data("index.html"), self.template_globals) self.totals = sum(f['nums'] for f in self.files) html = index_tmpl.render({ 'has_arcs': self.has_arcs, 'extra_css': self.extra_css, 'files': self.files, 'totals': self.totals, 'time_stamp': self.time_stamp, }) write_html(os.path.join(self.directory, "index.html"), html) # Write the latest hashes for next time. self.status.write(self.directory)
class HtmlReporter: """HTML reporting.""" # These files will be copied from the htmlfiles directory to the output # directory. STATIC_FILES = [ "style.css", "coverage_html.js", "keybd_closed.png", "keybd_open.png", "favicon_32.png", ] def __init__(self, cov): self.coverage = cov self.config = self.coverage.config self.directory = self.config.html_dir self.skip_covered = self.config.html_skip_covered if self.skip_covered is None: self.skip_covered = self.config.skip_covered self.skip_empty = self.config.html_skip_empty if self.skip_empty is None: self.skip_empty = self.config.skip_empty self.skipped_covered_count = 0 self.skipped_empty_count = 0 title = self.config.html_title if self.config.extra_css: self.extra_css = os.path.basename(self.config.extra_css) else: self.extra_css = None self.data = self.coverage.get_data() self.has_arcs = self.data.has_arcs() self.file_summaries = [] self.all_files_nums = [] self.incr = IncrementalChecker(self.directory) self.datagen = HtmlDataGeneration(self.coverage) self.totals = Numbers(precision=self.config.precision) self.template_globals = { # Functions available in the templates. 'escape': escape, 'pair': pair, 'len': len, # Constants for this report. '__url__': coverage.__url__, '__version__': coverage.__version__, 'title': title, 'time_stamp': format_local_datetime(datetime.datetime.now()), 'extra_css': self.extra_css, 'has_arcs': self.has_arcs, 'show_contexts': self.config.show_contexts, # Constants for all reports. # These css classes determine which lines are highlighted by default. 'category': { 'exc': 'exc show_exc', 'mis': 'mis show_mis', 'par': 'par run show_par', 'run': 'run', } } self.pyfile_html_source = read_data("pyfile.html") self.source_tmpl = Templite(self.pyfile_html_source, self.template_globals) def report(self, morfs): """Generate an HTML report for `morfs`. `morfs` is a list of modules or file names. """ # Read the status data and check that this run used the same # global data as the last run. self.incr.read() self.incr.check_global_data(self.config, self.pyfile_html_source) # Process all the files. for fr, analysis in get_analysis_to_report(self.coverage, morfs): self.html_file(fr, analysis) if not self.all_files_nums: raise CoverageException("No data to report.") self.totals = sum(self.all_files_nums) # Write the index file. self.index_file() self.make_local_static_report_files() return self.totals.n_statements and self.totals.pc_covered def make_local_static_report_files(self): """Make local instances of static files for HTML report.""" # The files we provide must always be copied. for static in self.STATIC_FILES: shutil.copyfile(data_filename(static), os.path.join(self.directory, static)) # .gitignore can't be copied from the source tree because it would # prevent the static files from being checked in. with open(os.path.join(self.directory, ".gitignore"), "w") as fgi: fgi.write("# Created by coverage.py\n*\n") # The user may have extra CSS they want copied. if self.extra_css: shutil.copyfile(self.config.extra_css, os.path.join(self.directory, self.extra_css)) def html_file(self, fr, analysis): """Generate an HTML file for one source file.""" rootname = flat_rootname(fr.relative_filename()) html_filename = rootname + ".html" ensure_dir(self.directory) html_path = os.path.join(self.directory, html_filename) # Get the numbers for this file. nums = analysis.numbers self.all_files_nums.append(nums) if self.skip_covered: # Don't report on 100% files. no_missing_lines = (nums.n_missing == 0) no_missing_branches = (nums.n_partial_branches == 0) if no_missing_lines and no_missing_branches: # If there's an existing file, remove it. file_be_gone(html_path) self.skipped_covered_count += 1 return if self.skip_empty: # Don't report on empty files. if nums.n_statements == 0: file_be_gone(html_path) self.skipped_empty_count += 1 return # Find out if the file on disk is already correct. if self.incr.can_skip_file(self.data, fr, rootname): self.file_summaries.append(self.incr.index_info(rootname)) return # Write the HTML page for this file. file_data = self.datagen.data_for_file(fr, analysis) for ldata in file_data.lines: # Build the HTML for the line. html = [] for tok_type, tok_text in ldata.tokens: if tok_type == "ws": html.append(escape(tok_text)) else: tok_html = escape(tok_text) or ' ' html.append(f'<span class="{tok_type}">{tok_html}</span>') ldata.html = ''.join(html) if ldata.short_annotations: # 202F is NARROW NO-BREAK SPACE. # 219B is RIGHTWARDS ARROW WITH STROKE. ldata.annotate = ", ".join( f"{ldata.number} ↛ {d}" for d in ldata.short_annotations) else: ldata.annotate = None if ldata.long_annotations: longs = ldata.long_annotations if len(longs) == 1: ldata.annotate_long = longs[0] else: ldata.annotate_long = "{:d} missed branches: {}".format( len(longs), ", ".join( f"{num:d}) {ann_long}" for num, ann_long in enumerate(longs, start=1)), ) else: ldata.annotate_long = None css_classes = [] if ldata.category: css_classes.append( self.template_globals['category'][ldata.category]) ldata.css_class = ' '.join(css_classes) or "pln" html = self.source_tmpl.render(file_data.__dict__) write_html(html_path, html) # Save this file's information for the index file. index_info = { 'nums': nums, 'html_filename': html_filename, 'relative_filename': fr.relative_filename(), } self.file_summaries.append(index_info) self.incr.set_index_info(rootname, index_info) def index_file(self): """Write the index.html file for this report.""" index_tmpl = Templite(read_data("index.html"), self.template_globals) skipped_covered_msg = skipped_empty_msg = "" if self.skipped_covered_count: msg = "{} {} skipped due to complete coverage." skipped_covered_msg = msg.format( self.skipped_covered_count, "file" if self.skipped_covered_count == 1 else "files", ) if self.skipped_empty_count: msg = "{} empty {} skipped." skipped_empty_msg = msg.format( self.skipped_empty_count, "file" if self.skipped_empty_count == 1 else "files", ) html = index_tmpl.render({ 'files': self.file_summaries, 'totals': self.totals, 'skipped_covered_msg': skipped_covered_msg, 'skipped_empty_msg': skipped_empty_msg, }) index_file = os.path.join(self.directory, "index.html") write_html(index_file, html) self.coverage._message(f"Wrote HTML report to {index_file}") # Write the latest hashes for next time. self.incr.write()
class HtmlReporter(Reporter): """HTML reporting.""" # These files will be copied from the htmlfiles dir to the output dir. STATIC_FILES = [ "style.css", "jquery-1.4.3.min.js", "jquery.hotkeys.js", "jquery.isonscreen.js", "jquery.tablesorter.min.js", "coverage_html.js", "keybd_closed.png", "keybd_open.png", ] def __init__(self, cov, ignore_errors=False): super(HtmlReporter, self).__init__(cov, ignore_errors) self.directory = None self.template_globals = { 'escape': escape, '__url__': coverage.__url__, '__version__': coverage.__version__, } self.source_tmpl = Templite( data("htmlfiles/pyfile.html"), self.template_globals ) self.coverage = cov self.files = [] self.arcs = self.coverage.data.has_arcs() self.status = HtmlStatus() def report(self, morfs, config=None): """Generate an HTML report for `morfs`. `morfs` is a list of modules or filenames. `config` is a CoverageConfig instance. """ assert config.html_dir, "must provide a directory for html reporting" # Read the status data. self.status.read(config.html_dir) # Check that this run used the same settings as the last run. m = Hasher() m.update(config) these_settings = m.digest() if self.status.settings_hash() != these_settings: self.status.reset() self.status.set_settings_hash(these_settings) # Process all the files. self.report_files(self.html_file, morfs, config, config.html_dir) if not self.files: raise CoverageException("No data to report.") # Write the index file. self.index_file() # Create the once-per-directory files. for static in self.STATIC_FILES: shutil.copyfile( data_filename("htmlfiles/" + static), os.path.join(self.directory, static) ) def file_hash(self, source, cu): """Compute a hash that changes if the file needs to be re-reported.""" m = Hasher() m.update(source) self.coverage.data.add_to_hash(cu.filename, m) return m.digest() def html_file(self, cu, analysis): """Generate an HTML file for one source file.""" source_file = cu.source_file() try: source = source_file.read() finally: source_file.close() # Find out if the file on disk is already correct. flat_rootname = cu.flat_rootname() this_hash = self.file_hash(source, cu) that_hash = self.status.file_hash(flat_rootname) if this_hash == that_hash: # Nothing has changed to require the file to be reported again. self.files.append(self.status.index_info(flat_rootname)) return self.status.set_file_hash(flat_rootname, this_hash) nums = analysis.numbers missing_branch_arcs = analysis.missing_branch_arcs() n_par = 0 # accumulated below. arcs = self.arcs # These classes determine which lines are highlighted by default. c_run = "run hide_run" c_exc = "exc" c_mis = "mis" c_par = "par " + c_run lines = [] for lineno, line in enumerate(source_token_lines(source)): lineno += 1 # 1-based line numbers. # Figure out how to mark this line. line_class = [] annotate_html = "" annotate_title = "" if lineno in analysis.statements: line_class.append("stm") if lineno in analysis.excluded: line_class.append(c_exc) elif lineno in analysis.missing: line_class.append(c_mis) elif self.arcs and lineno in missing_branch_arcs: line_class.append(c_par) n_par += 1 annlines = [] for b in missing_branch_arcs[lineno]: if b < 0: annlines.append("exit") else: annlines.append(str(b)) annotate_html = " ".join(annlines) if len(annlines) > 1: annotate_title = "no jumps to these line numbers" elif len(annlines) == 1: annotate_title = "no jump to this line number" elif lineno in analysis.statements: line_class.append(c_run) # Build the HTML for the line html = [] for tok_type, tok_text in line: if tok_type == "ws": html.append(escape(tok_text)) else: tok_html = escape(tok_text) or ' ' html.append( "<span class='%s'>%s</span>" % (tok_type, tok_html) ) lines.append({ 'html': ''.join(html), 'number': lineno, 'class': ' '.join(line_class) or "pln", 'annotate': annotate_html, 'annotate_title': annotate_title, }) # Write the HTML page for this file. html_filename = flat_rootname + ".html" html_path = os.path.join(self.directory, html_filename) html = spaceless(self.source_tmpl.render(locals())) fhtml = open(html_path, 'w') try: fhtml.write(html) finally: fhtml.close() # Save this file's information for the index file. index_info = { 'nums': nums, 'par': n_par, 'html_filename': html_filename, 'name': cu.name, } self.files.append(index_info) self.status.set_index_info(flat_rootname, index_info) def index_file(self): """Write the index.html file for this report.""" index_tmpl = Templite( data("htmlfiles/index.html"), self.template_globals ) files = self.files arcs = self.arcs totals = sum([f['nums'] for f in files]) fhtml = open(os.path.join(self.directory, "index.html"), "w") try: fhtml.write(index_tmpl.render(locals())) finally: fhtml.close() # Write the latest hashes for next time. self.status.write(self.directory)
class HtmlReporter(Reporter): """HTML reporting.""" # These files will be copied from the htmlfiles dir to the output dir. STATIC_FILES = [ ("style.css", ""), ("jquery.min.js", "jquery"), ("jquery.hotkeys.js", "jquery-hotkeys"), ("jquery.isonscreen.js", "jquery-isonscreen"), ("jquery.tablesorter.min.js", "jquery-tablesorter"), ("jquery.qtip-1.0.0-rc3.min.js", "jquery-qtip"), ("coverage_html.js", ""), ("keybd_closed.png", ""), ("keybd_open.png", ""), ("page.png", ""), ] def __init__(self, cov, config): super(HtmlReporter, self).__init__(cov, config) self.directory = None self.template_globals = { 'escape': escape, 'title': self.config.html_title, '__url__': coverage.__url__, '__version__': coverage.__version__, } self.source_tmpl = Templite( data("pyfile.html"), self.template_globals ) self.coverage = cov self.files = [] self.arcs = self.coverage.data.has_arcs() self.status = HtmlStatus() self.extra_css = None self.totals = Numbers() def report(self, morfs): """Generate an HTML report for `morfs`. `morfs` is a list of modules or filenames. """ assert self.config.html_dir, "must give a directory for html reporting" # Read the status data. self.status.read(self.config.html_dir) # Check that this run used the same settings as the last run. m = Hasher() m.update(self.config) these_settings = m.digest() if self.status.settings_hash() != these_settings: self.status.reset() self.status.set_settings_hash(these_settings) # The user may have extra CSS they want copied. if self.config.extra_css: self.extra_css = os.path.basename(self.config.extra_css) # Process all the files. self.report_files(self.html_file, morfs, self.config.html_dir) if not self.files: raise CoverageException("No data to report.") # Write the index file. self.index_file() self.make_local_static_report_files() return self.totals.pc_covered def make_local_static_report_files(self): """Make local instances of static files for HTML report.""" # The files we provide must always be copied. for static, pkgdir in self.STATIC_FILES: shutil.copyfile( data_filename(static, pkgdir), os.path.join(self.directory, static) ) # The user may have extra CSS they want copied. if self.extra_css: shutil.copyfile( self.config.extra_css, os.path.join(self.directory, self.extra_css) ) def write_html(self, fname, html): """Write `html` to `fname`, properly encoded.""" with open(fname, "wb") as fout: fout.write(html.encode('ascii', 'xmlcharrefreplace')) def file_hash(self, source, cu): """Compute a hash that changes if the file needs to be re-reported.""" m = Hasher() m.update(source) self.coverage.data.add_to_hash(cu.filename, m) return m.digest() def html_file(self, cu, analysis): """Generate an HTML file for one source file.""" source = cu.source() # Find out if the file on disk is already correct. flat_rootname = cu.flat_rootname() this_hash = self.file_hash(source, cu) that_hash = self.status.file_hash(flat_rootname) if this_hash == that_hash: # Nothing has changed to require the file to be reported again. self.files.append(self.status.index_info(flat_rootname)) return self.status.set_file_hash(flat_rootname, this_hash) # If need be, determine the encoding of the source file. We use it # later to properly write the HTML. if sys.version_info < (3, 0): encoding = cu.source_encoding() # Some UTF8 files have the dreaded UTF8 BOM. If so, junk it. if encoding.startswith("utf-8") and source[:3] == "\xef\xbb\xbf": source = source[3:] encoding = "utf-8" # Get the numbers for this file. nums = analysis.numbers if self.arcs: missing_branch_arcs = analysis.missing_branch_arcs() # These classes determine which lines are highlighted by default. c_run = "run hide_run" c_exc = "exc" c_mis = "mis" c_par = "par " + c_run lines = [] for lineno, line in enumerate(cu.source_token_lines(), start=1): # Figure out how to mark this line. line_class = [] annotate_html = "" annotate_title = "" if lineno in analysis.statements: line_class.append("stm") if lineno in analysis.excluded: line_class.append(c_exc) elif lineno in analysis.missing: line_class.append(c_mis) elif self.arcs and lineno in missing_branch_arcs: line_class.append(c_par) annlines = [] for b in missing_branch_arcs[lineno]: if b < 0: annlines.append("exit") else: annlines.append(str(b)) annotate_html = " ".join(annlines) if len(annlines) > 1: annotate_title = "no jumps to these line numbers" elif len(annlines) == 1: annotate_title = "no jump to this line number" elif lineno in analysis.statements: line_class.append(c_run) # Build the HTML for the line html = [] for tok_type, tok_text in line: if tok_type == "ws": html.append(escape(tok_text)) else: tok_html = escape(tok_text) or ' ' html.append( "<span class='%s'>%s</span>" % (tok_type, tok_html) ) callers_html_list = [] callers = analysis.callers_data.get(lineno) if callers: line_class.append("has_callers") for caller in sorted(callers.test_methods.keys()): caller_count = callers.test_methods[caller] html_filename = self.get_href_for_source_file(caller.filename) content = caller.function_name + " in " + caller.filename + " line " + str(caller.line_no) + \ " (" + str(caller_count) + ")" caller_html = "<a href=" + html_filename + "#n" + str(caller.line_no) + ">" + content + "</a>" callers_html_list.append(caller_html) lines.append({ 'html': ''.join(html), 'number': lineno, 'class': ' '.join(line_class) or "pln", 'annotate': annotate_html, 'annotate_title': annotate_title, 'callers_html_list': callers_html_list, }) # Write the HTML page for this file. html = spaceless(self.source_tmpl.render({ 'c_exc': c_exc, 'c_mis': c_mis, 'c_par': c_par, 'c_run': c_run, 'arcs': self.arcs, 'extra_css': self.extra_css, 'cu': cu, 'nums': nums, 'lines': lines, })) if sys.version_info < (3, 0): # In theory, all the characters in the source can be decoded, but # strange things happen, so use 'replace' to keep errors at bay. html = html.decode(encoding, 'replace') html_filename = flat_rootname + ".html" html_path = os.path.join(self.directory, html_filename) self.write_html(html_path, html) # Save this file's information for the index file. index_info = { 'nums': nums, 'html_filename': html_filename, 'name': cu.name, } self.files.append(index_info) self.status.set_index_info(flat_rootname, index_info) def get_href_for_source_file(self, source_filename): # Bad hacks here... found_cu = None for cu in self.code_units: if cu.filename == source_filename: found_cu = cu break if found_cu is not None: # Best case: we found the code unit in our data already. return found_cu.flat_rootname() + ".html" else: # Test seems to live outside our code unit files. # This is almost certainly wrong below. if source_filename[0] == "<": source_filename = source_filename.replace("<", "_").replace(">", "_") source_filename = source_filename.replace(".py", ".html").replace("/", "_") return source_filename def index_file(self): """Write the index.html file for this report.""" index_tmpl = Templite( data("index.html"), self.template_globals ) self.totals = sum(f['nums'] for f in self.files) html = index_tmpl.render({ 'arcs': self.arcs, 'extra_css': self.extra_css, 'files': self.files, 'totals': self.totals, }) if sys.version_info < (3, 0): html = html.decode("utf-8") self.write_html( os.path.join(self.directory, "index.html"), html ) # Write the latest hashes for next time. self.status.write(self.directory)