From b8498bf9c144344fd70cbb6e72c13e65c13b5e48 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sat, 1 Apr 2023 08:22:48 -0400 Subject: perf: make the context shortcodes even shorter On the coverage.py metacov, this makes the HTML 5% smaller. --- coverage/html.py | 36 +++++++++++++++++--- coverage/htmlfiles/coverage_html.js | 34 +++++++++---------- tests/gold/html/contexts/two_tests_py.html | 28 ++++++++-------- tests/gold/html/support/coverage_html.js | 54 +++++++++++++++--------------- tests/test_html.py | 9 +++++ 5 files changed, 99 insertions(+), 62 deletions(-) diff --git a/coverage/html.py b/coverage/html.py index d1a90ecd..57076060 100644 --- a/coverage/html.py +++ b/coverage/html.py @@ -7,10 +7,12 @@ from __future__ import annotations import collections import datetime +import functools import json import os import re import shutil +import string # pylint: disable=deprecated-module from dataclasses import dataclass from typing import Any, Dict, Iterable, List, Optional, Tuple, TYPE_CHECKING, cast @@ -187,6 +189,21 @@ class FileToReport: self.html_filename = self.rootname + ".html" +HTML_SAFE = string.ascii_letters + string.digits + "!#$%'()*+,-./:;=?@[]^_`{|}~" + +@functools.lru_cache(maxsize=None) +def encode_int(n: int) -> str: + """Create a short HTML-safe string from an integer, using HTML_SAFE.""" + if n == 0: + return HTML_SAFE[0] + + r = [] + while n: + n, t = divmod(n, len(HTML_SAFE)) + r.append(HTML_SAFE[t]) + return "".join(r) + + class HtmlReporter: """HTML reporting.""" @@ -373,7 +390,10 @@ class HtmlReporter: contexts = collections.Counter(c for cline in file_data.lines for c in cline.contexts) context_codes = {y: i for (i, y) in enumerate(x[0] for x in contexts.most_common())} if context_codes: - contexts_json = json.dumps({v: k for (k, v) in context_codes.items()}, indent=2) + contexts_json = json.dumps( + {encode_int(v): k for (k, v) in context_codes.items()}, + indent=2, + ) else: contexts_json = None @@ -387,9 +407,17 @@ class HtmlReporter: tok_html = escape(tok_text) or " " html_parts.append(f'{tok_html}') ldata.html = "".join(html_parts) - ldata.context_str = ",".join( - str(context_codes[c_context]) for c_context in ldata.context_list - ) + if ldata.context_list: + encoded_contexts = [ + encode_int(context_codes[c_context]) for c_context in ldata.context_list + ] + code_width = max(len(ec) for ec in encoded_contexts) + ldata.context_str = ( + str(code_width) + + "".join(ec.ljust(code_width) for ec in encoded_contexts) + ) + else: + ldata.context_str = "" if ldata.short_annotations: # 202F is NARROW NO-BREAK SPACE. diff --git a/coverage/htmlfiles/coverage_html.js b/coverage/htmlfiles/coverage_html.js index 691a56c3..4c321182 100644 --- a/coverage/htmlfiles/coverage_html.js +++ b/coverage/htmlfiles/coverage_html.js @@ -212,11 +212,6 @@ coverage.index_ready = function () { coverage.LINE_FILTERS_STORAGE = "COVERAGE_LINE_FILTERS"; coverage.pyfile_ready = function () { - cboxes = document.querySelectorAll('[id^=ctxs]') - cboxes.forEach(function(cbox) { - cbox.addEventListener("click", coverage.showContexts) - }); - // If we're directed to a particular line number, highlight the line. var frag = location.hash; if (frag.length > 2 && frag[1] === "t") { @@ -262,6 +257,10 @@ coverage.pyfile_ready = function () { coverage.init_scroll_markers(); coverage.wire_up_sticky_header(); + document.querySelectorAll("[id^=ctxs]").forEach( + cbox => cbox.addEventListener("click", coverage.expand_contexts) + ); + // Rebuild scroll markers when the window height changes. window.addEventListener("resize", coverage.build_scroll_markers); }; @@ -600,17 +599,19 @@ coverage.wire_up_sticky_header = function () { updateHeader(); }; -coverage.showContexts = function (e) { - span = e.target.nextElementSibling.nextElementSibling; - span_text = span.textContent; - - if (/^[0-9,]+$/.test(span_text)) { - span.textContent = ""; - span_text.split(",").forEach(function(s) { - ctx = contexts[s]; - span.appendChild(document.createTextNode(ctx)); - span.appendChild(document.createElement("br")); - }) +coverage.expand_contexts = function (e) { + var ctxs = e.target.parentNode.querySelector(".ctxs"); + + if (!ctxs.classList.contains("expanded")) { + var ctxs_text = ctxs.textContent; + var width = Number(ctxs_text[0]); + ctxs.textContent = ""; + for (var i = 1; i < ctxs_text.length; i += width) { + key = ctxs_text.substring(i, i + width).trim(); + ctxs.appendChild(document.createTextNode(contexts[key])); + ctxs.appendChild(document.createElement("br")); + } + ctxs.classList.add("expanded"); } }; @@ -620,5 +621,4 @@ document.addEventListener("DOMContentLoaded", () => { } else { coverage.pyfile_ready(); } - }); diff --git a/tests/gold/html/contexts/two_tests_py.html b/tests/gold/html/contexts/two_tests_py.html index cbb6e573..aadc7976 100644 --- a/tests/gold/html/contexts/two_tests_py.html +++ b/tests/gold/html/contexts/two_tests_py.html @@ -7,9 +7,9 @@ @@ -72,7 +72,7 @@ » next       coverage.py v7.2.3a0.dev1, - created at 2023-03-22 13:16 -0400 + created at 2023-04-01 08:30 -0400