eric7/DebugClients/Python/coverage/html.py

branch
eric7
changeset 8775
0802ae193343
parent 8527
2bd1325d727e
child 8929
fcca2fa618bf
--- 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",&nbsp;&nbsp; ".join(
-                    u"{}&#x202F;&#x219B;&#x202F;{}".format(ldata.number, d)
+                ldata.annotate = ",&nbsp;&nbsp; ".join(
+                    f"{ldata.number}&#x202F;&#x219B;&#x202F;{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
 

eric ide

mercurial