--- a/eric7/DebugClients/Python/coverage/html.py Fri Nov 19 19:28:47 2021 +0100 +++ b/eric7/DebugClients/Python/coverage/html.py Sat Nov 20 16:47:38 2021 +0100 @@ -8,13 +8,14 @@ import os import re import shutil +import types import coverage -from coverage import env -from coverage.backward import iitems, SimpleNamespace, format_local_datetime from coverage.data import add_data_to_hash +from coverage.exceptions import CoverageException from coverage.files import flat_rootname -from coverage.misc import CoverageException, ensure_dir, file_be_gone, Hasher, isolate_module +from coverage.misc import ensure_dir, file_be_gone, Hasher, isolate_module, format_local_datetime +from coverage.misc import human_sorted from coverage.report import get_analysis_to_report from coverage.results import Numbers from coverage.templite import Templite @@ -22,42 +23,12 @@ os = isolate_module(os) -# Static files are looked for in a list of places. -STATIC_PATH = [ - # The place Debian puts system Javascript libraries. - "/usr/share/javascript", - - # Our htmlfiles directory. - os.path.join(os.path.dirname(__file__), "htmlfiles"), -] - - -def data_filename(fname, pkgdir=""): - """Return the path to a data file of ours. - - The file is searched for on `STATIC_PATH`, and the first place it's found, - is returned. - - Each directory in `STATIC_PATH` is searched as-is, and also, if `pkgdir` - is provided, at that sub-directory. - +def data_filename(fname): + """Return the path to an "htmlfiles" data file of ours. """ - tried = [] - for static_dir in STATIC_PATH: - static_filename = os.path.join(static_dir, fname) - if os.path.exists(static_filename): - return static_filename - else: - tried.append(static_filename) - if pkgdir: - static_filename = os.path.join(static_dir, pkgdir, fname) - if os.path.exists(static_filename): - return static_filename - else: - tried.append(static_filename) - raise CoverageException( - "Couldn't find static file %r from %r, tried: %r" % (fname, os.getcwd(), tried) - ) + static_dir = os.path.join(os.path.dirname(__file__), "htmlfiles") + static_filename = os.path.join(static_dir, fname) + return static_filename def read_data(fname): @@ -73,7 +44,7 @@ fout.write(html.encode('ascii', 'xmlcharrefreplace')) -class HtmlDataGeneration(object): +class HtmlDataGeneration: """Generate structured data to be turned into HTML reports.""" EMPTY = "(empty)" @@ -123,14 +94,14 @@ contexts = contexts_label = None context_list = None if category and self.config.show_contexts: - contexts = sorted(c or self.EMPTY for c in contexts_by_lineno[lineno]) + contexts = human_sorted(c or self.EMPTY for c in contexts_by_lineno.get(lineno, ())) if contexts == [self.EMPTY]: contexts_label = self.EMPTY else: - contexts_label = "{} ctx".format(len(contexts)) + contexts_label = f"{len(contexts)} ctx" context_list = contexts - lines.append(SimpleNamespace( + lines.append(types.SimpleNamespace( tokens=tokens, number=lineno, category=category, @@ -142,7 +113,7 @@ long_annotations=long_annotations, )) - file_data = SimpleNamespace( + file_data = types.SimpleNamespace( relative_filename=fr.relative_filename(), nums=analysis.numbers, lines=lines, @@ -151,22 +122,17 @@ return file_data -class HtmlReporter(object): +class HtmlReporter: """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", ""), - ("favicon_32.png", ""), + "style.css", + "coverage_html.js", + "keybd_closed.png", + "keybd_open.png", + "favicon_32.png", ] def __init__(self, cov): @@ -179,11 +145,11 @@ 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.skip_empty = self.config.skip_empty + self.skipped_covered_count = 0 + self.skipped_empty_count = 0 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) @@ -197,7 +163,7 @@ self.all_files_nums = [] self.incr = IncrementalChecker(self.directory) self.datagen = HtmlDataGeneration(self.coverage) - self.totals = Numbers() + self.totals = Numbers(precision=self.config.precision) self.template_globals = { # Functions available in the templates. @@ -255,18 +221,17 @@ 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) - ) + 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) - ) + 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.""" @@ -286,12 +251,14 @@ 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. @@ -310,15 +277,15 @@ else: tok_html = escape(tok_text) or ' ' html.append( - u'<span class="{}">{}</span>'.format(tok_type, tok_html) + 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 = u", ".join( - u"{} ↛ {}".format(ldata.number, d) + ldata.annotate = ", ".join( + f"{ldata.number} ↛ {d}" for d in ldata.short_annotations ) else: @@ -329,10 +296,10 @@ if len(longs) == 1: ldata.annotate_long = longs[0] else: - ldata.annotate_long = u"{:d} missed branches: {}".format( + ldata.annotate_long = "{:d} missed branches: {}".format( len(longs), - u", ".join( - u"{:d}) {}".format(num, ann_long) + ", ".join( + f"{num:d}) {ann_long}" for num, ann_long in enumerate(longs, start=1) ), ) @@ -360,18 +327,36 @@ """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, }) - write_html(os.path.join(self.directory, "index.html"), html) + 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 IncrementalChecker(object): +class IncrementalChecker: """Logic and data to support incremental reporting.""" STATUS_FILE = "status.json" @@ -421,7 +406,7 @@ status_file = os.path.join(self.directory, self.STATUS_FILE) with open(status_file) as fstatus: status = json.load(fstatus) - except (IOError, ValueError): + except (OSError, ValueError): usable = False else: usable = True @@ -432,7 +417,7 @@ if usable: self.files = {} - for filename, fileinfo in iitems(status['files']): + for filename, fileinfo in status['files'].items(): fileinfo['index']['nums'] = Numbers(*fileinfo['index']['nums']) self.files[filename] = fileinfo self.globals = status['globals'] @@ -443,7 +428,7 @@ """Write the current status.""" status_file = os.path.join(self.directory, self.STATUS_FILE) files = {} - for filename, fileinfo in iitems(self.files): + for filename, fileinfo in self.files.items(): fileinfo['index']['nums'] = fileinfo['index']['nums'].init_args() files[filename] = fileinfo