coverage 7.6.10__cp312-cp312-musllinux_1_2_aarch64.whl → 7.12.0__cp312-cp312-musllinux_1_2_aarch64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. coverage/__init__.py +3 -1
  2. coverage/__main__.py +3 -1
  3. coverage/annotate.py +2 -3
  4. coverage/bytecode.py +178 -4
  5. coverage/cmdline.py +330 -155
  6. coverage/collector.py +32 -43
  7. coverage/config.py +167 -63
  8. coverage/context.py +5 -6
  9. coverage/control.py +165 -86
  10. coverage/core.py +71 -34
  11. coverage/data.py +4 -5
  12. coverage/debug.py +113 -57
  13. coverage/disposition.py +2 -1
  14. coverage/env.py +29 -78
  15. coverage/exceptions.py +29 -7
  16. coverage/execfile.py +19 -14
  17. coverage/files.py +24 -19
  18. coverage/html.py +118 -75
  19. coverage/htmlfiles/coverage_html.js +12 -10
  20. coverage/htmlfiles/index.html +45 -10
  21. coverage/htmlfiles/pyfile.html +2 -2
  22. coverage/htmlfiles/style.css +54 -6
  23. coverage/htmlfiles/style.scss +85 -3
  24. coverage/inorout.py +62 -45
  25. coverage/jsonreport.py +22 -9
  26. coverage/lcovreport.py +16 -18
  27. coverage/misc.py +51 -47
  28. coverage/multiproc.py +12 -7
  29. coverage/numbits.py +4 -5
  30. coverage/parser.py +150 -251
  31. coverage/patch.py +166 -0
  32. coverage/phystokens.py +25 -26
  33. coverage/plugin.py +14 -14
  34. coverage/plugin_support.py +37 -36
  35. coverage/python.py +13 -14
  36. coverage/pytracer.py +31 -33
  37. coverage/regions.py +3 -2
  38. coverage/report.py +60 -44
  39. coverage/report_core.py +7 -10
  40. coverage/results.py +152 -68
  41. coverage/sqldata.py +261 -211
  42. coverage/sqlitedb.py +37 -29
  43. coverage/sysmon.py +237 -162
  44. coverage/templite.py +19 -7
  45. coverage/tomlconfig.py +13 -13
  46. coverage/tracer.cpython-312-aarch64-linux-musl.so +0 -0
  47. coverage/tracer.pyi +3 -1
  48. coverage/types.py +26 -23
  49. coverage/version.py +4 -19
  50. coverage/xmlreport.py +17 -14
  51. {coverage-7.6.10.dist-info → coverage-7.12.0.dist-info}/METADATA +50 -28
  52. coverage-7.12.0.dist-info/RECORD +59 -0
  53. {coverage-7.6.10.dist-info → coverage-7.12.0.dist-info}/WHEEL +1 -1
  54. coverage-7.6.10.dist-info/RECORD +0 -58
  55. {coverage-7.6.10.dist-info → coverage-7.12.0.dist-info}/entry_points.txt +0 -0
  56. {coverage-7.6.10.dist-info → coverage-7.12.0.dist-info/licenses}/LICENSE.txt +0 -0
  57. {coverage-7.6.10.dist-info → coverage-7.12.0.dist-info}/top_level.txt +0 -0
coverage/html.py CHANGED
@@ -1,5 +1,5 @@
1
1
  # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
2
- # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
2
+ # For details: https://github.com/coveragepy/coveragepy/blob/main/NOTICE.txt
3
3
 
4
4
  """HTML reporting for coverage.py."""
5
5
 
@@ -13,26 +13,30 @@ import json
13
13
  import os
14
14
  import re
15
15
  import string
16
-
17
- from dataclasses import dataclass, field
18
- from typing import Any, TYPE_CHECKING
19
16
  from collections.abc import Iterable
17
+ from dataclasses import dataclass, field
18
+ from typing import TYPE_CHECKING, Any
20
19
 
21
20
  import coverage
22
21
  from coverage.data import CoverageData, add_data_to_hash
23
22
  from coverage.exceptions import NoDataError
24
23
  from coverage.files import flat_rootname
25
24
  from coverage.misc import (
26
- ensure_dir, file_be_gone, Hasher, isolate_module, format_local_datetime,
27
- human_sorted, plural, stdout_link,
25
+ Hasher,
26
+ ensure_dir,
27
+ file_be_gone,
28
+ format_local_datetime,
29
+ human_sorted,
30
+ isolate_module,
31
+ plural,
32
+ stdout_link,
28
33
  )
29
34
  from coverage.report_core import get_analysis_to_report
30
- from coverage.results import Analysis, Numbers
35
+ from coverage.results import Analysis, AnalysisNarrower, Numbers
31
36
  from coverage.templite import Templite
32
37
  from coverage.types import TLineNo, TMorf
33
38
  from coverage.version import __url__
34
39
 
35
-
36
40
  if TYPE_CHECKING:
37
41
  from coverage import Coverage
38
42
  from coverage.plugins import FileReporter
@@ -42,8 +46,7 @@ os = isolate_module(os)
42
46
 
43
47
 
44
48
  def data_filename(fname: str) -> str:
45
- """Return the path to an "htmlfiles" data file of ours.
46
- """
49
+ """Return the path to an "htmlfiles" data file of ours."""
47
50
  static_dir = os.path.join(os.path.dirname(__file__), "htmlfiles")
48
51
  static_filename = os.path.join(static_dir, fname)
49
52
  return static_filename
@@ -51,7 +54,7 @@ def data_filename(fname: str) -> str:
51
54
 
52
55
  def read_data(fname: str) -> str:
53
56
  """Return the contents of a data file of ours."""
54
- with open(data_filename(fname)) as data_file:
57
+ with open(data_filename(fname), encoding="utf-8") as data_file:
55
58
  return data_file.read()
56
59
 
57
60
 
@@ -65,6 +68,7 @@ def write_html(fname: str, html: str) -> None:
65
68
  @dataclass
66
69
  class LineData:
67
70
  """The data for each source line of HTML output."""
71
+
68
72
  tokens: list[tuple[str, str]]
69
73
  number: TLineNo
70
74
  category: str
@@ -83,6 +87,7 @@ class LineData:
83
87
  @dataclass
84
88
  class FileData:
85
89
  """The data for each source file of HTML output."""
90
+
86
91
  relative_filename: str
87
92
  nums: Numbers
88
93
  lines: list[LineData]
@@ -91,6 +96,7 @@ class FileData:
91
96
  @dataclass
92
97
  class IndexItem:
93
98
  """Information for each index entry, to render an index page."""
99
+
94
100
  url: str = ""
95
101
  file: str = ""
96
102
  description: str = ""
@@ -100,6 +106,7 @@ class IndexItem:
100
106
  @dataclass
101
107
  class IndexPage:
102
108
  """Data for each index page."""
109
+
103
110
  noun: str
104
111
  plural: str
105
112
  filename: str
@@ -138,10 +145,13 @@ class HtmlDataGeneration:
138
145
 
139
146
  lines = []
140
147
  branch_stats = analysis.branch_stats()
148
+ multiline_map = {}
149
+ if hasattr(fr, "multiline_map"):
150
+ multiline_map = fr.multiline_map()
141
151
 
142
152
  for lineno, tokens in enumerate(fr.source_token_lines(), start=1):
143
153
  # Figure out how to mark this line.
144
- category = ""
154
+ category = category2 = ""
145
155
  short_annotations = []
146
156
  long_annotations = []
147
157
 
@@ -169,6 +179,18 @@ class HtmlDataGeneration:
169
179
  )
170
180
  elif lineno in analysis.statements:
171
181
  category = "run"
182
+ elif first_line := multiline_map.get(lineno):
183
+ if first_line in analysis.excluded:
184
+ category2 = "exc2"
185
+ elif first_line in analysis.missing:
186
+ category2 = "mis2"
187
+ elif self.has_arcs and first_line in missing_branch_arcs:
188
+ category2 = "par2"
189
+ # I don't understand why this last condition is marked as
190
+ # partial. If I add an else with an exception, the exception
191
+ # is raised.
192
+ elif first_line in analysis.statements: # pragma: part covered
193
+ category2 = "run2"
172
194
 
173
195
  contexts = []
174
196
  contexts_label = ""
@@ -181,16 +203,18 @@ class HtmlDataGeneration:
181
203
  contexts_label = f"{len(contexts)} ctx"
182
204
  context_list = contexts
183
205
 
184
- lines.append(LineData(
185
- tokens=tokens,
186
- number=lineno,
187
- category=category,
188
- contexts=contexts,
189
- contexts_label=contexts_label,
190
- context_list=context_list,
191
- short_annotations=short_annotations,
192
- long_annotations=long_annotations,
193
- ))
206
+ lines.append(
207
+ LineData(
208
+ tokens=tokens,
209
+ number=lineno,
210
+ category=category or category2,
211
+ contexts=contexts,
212
+ contexts_label=contexts_label,
213
+ context_list=context_list,
214
+ short_annotations=short_annotations,
215
+ long_annotations=long_annotations,
216
+ )
217
+ )
194
218
 
195
219
  file_data = FileData(
196
220
  relative_filename=fr.relative_filename(),
@@ -203,6 +227,7 @@ class HtmlDataGeneration:
203
227
 
204
228
  class FileToReport:
205
229
  """A file we're considering reporting."""
230
+
206
231
  def __init__(self, fr: FileReporter, analysis: Analysis) -> None:
207
232
  self.fr = fr
208
233
  self.analysis = analysis
@@ -213,6 +238,7 @@ class FileToReport:
213
238
 
214
239
  HTML_SAFE = string.ascii_letters + string.digits + "!#$%'()*+,-./:;=?@[]^_`{|}~"
215
240
 
241
+
216
242
  @functools.cache
217
243
  def encode_int(n: int) -> str:
218
244
  """Create a short HTML-safe string from an integer, using HTML_SAFE."""
@@ -287,8 +313,7 @@ class HtmlReporter:
287
313
  # Functions available in the templates.
288
314
  "escape": escape,
289
315
  "pair": pair,
290
- "len": len,
291
-
316
+ "pretty_file": pretty_file,
292
317
  # Constants for this report.
293
318
  "__url__": __url__,
294
319
  "__version__": coverage.__version__,
@@ -296,9 +321,7 @@ class HtmlReporter:
296
321
  "time_stamp": format_local_datetime(datetime.datetime.now()),
297
322
  "extra_css": self.extra_css,
298
323
  "has_arcs": self.has_arcs,
299
- "show_contexts": self.config.show_contexts,
300
324
  "statics": {},
301
-
302
325
  # Constants for all reports.
303
326
  # These css classes determine which lines are highlighted by default.
304
327
  "category": {
@@ -306,6 +329,10 @@ class HtmlReporter:
306
329
  "mis": "mis show_mis",
307
330
  "par": "par run show_par",
308
331
  "run": "run",
332
+ "exc2": "exc exc2 show_exc",
333
+ "mis2": "mis mis2 show_mis",
334
+ "par2": "par par2 ru2 show_par",
335
+ "run2": "run run2",
309
336
  },
310
337
  }
311
338
  self.index_tmpl = Templite(read_data("index.html"), self.template_globals)
@@ -394,7 +421,7 @@ class HtmlReporter:
394
421
  dest = copy_with_cache_bust(src, self.directory)
395
422
  if not slug:
396
423
  slug = os.path.basename(src).replace(".", "_")
397
- self.template_globals["statics"][slug] = dest # type: ignore
424
+ self.template_globals["statics"][slug] = dest # type: ignore
398
425
 
399
426
  def make_local_static_report_files(self) -> None:
400
427
  """Make local instances of static files for HTML report."""
@@ -412,7 +439,7 @@ class HtmlReporter:
412
439
  # .gitignore can't be copied from the source tree because if it was in
413
440
  # the source tree, it would stop the static files from being checked in.
414
441
  if self.directory_was_empty:
415
- with open(os.path.join(self.directory, ".gitignore"), "w") as fgi:
442
+ with open(os.path.join(self.directory, ".gitignore"), "w", encoding="utf-8") as fgi:
416
443
  fgi.write("# Created by coverage.py\n*\n")
417
444
 
418
445
  def should_report(self, analysis: Analysis, index_page: IndexPage) -> bool:
@@ -423,8 +450,8 @@ class HtmlReporter:
423
450
 
424
451
  if self.skip_covered:
425
452
  # Don't report on 100% files.
426
- no_missing_lines = (nums.n_missing == 0)
427
- no_missing_branches = (nums.n_partial_branches == 0)
453
+ no_missing_lines = (nums.n_missing == 0) # fmt: skip
454
+ no_missing_branches = (nums.n_partial_branches == 0) # fmt: skip
428
455
  if no_missing_lines and no_missing_branches:
429
456
  index_page.skipped_covered_count += 1
430
457
  return False
@@ -478,9 +505,8 @@ class HtmlReporter:
478
505
  encode_int(context_codes[c_context]) for c_context in ldata.context_list
479
506
  ]
480
507
  code_width = max(len(ec) for ec in encoded_contexts)
481
- ldata.context_str = (
482
- str(code_width)
483
- + "".join(ec.ljust(code_width) for ec in encoded_contexts)
508
+ ldata.context_str = str(code_width) + "".join(
509
+ ec.ljust(code_width) for ec in encoded_contexts
484
510
  )
485
511
  else:
486
512
  ldata.context_str = ""
@@ -489,8 +515,7 @@ class HtmlReporter:
489
515
  # 202F is NARROW NO-BREAK SPACE.
490
516
  # 219B is RIGHTWARDS ARROW WITH STROKE.
491
517
  ldata.annotate = ",   ".join(
492
- f"{ldata.number} ↛ {d}"
493
- for d in ldata.short_annotations
518
+ f"{ldata.number} ↛ {d}" for d in ldata.short_annotations
494
519
  )
495
520
  else:
496
521
  ldata.annotate = None
@@ -509,24 +534,26 @@ class HtmlReporter:
509
534
  css_classes = []
510
535
  if ldata.category:
511
536
  css_classes.append(
512
- self.template_globals["category"][ldata.category], # type: ignore[index]
537
+ self.template_globals["category"][ldata.category], # type: ignore[index]
513
538
  )
514
539
  ldata.css_class = " ".join(css_classes) or "pln"
515
540
 
516
541
  html_path = os.path.join(self.directory, ftr.html_filename)
517
- html = self.source_tmpl.render({
518
- **file_data.__dict__,
519
- "contexts_json": contexts_json,
520
- "prev_html": ftr.prev_html,
521
- "next_html": ftr.next_html,
522
- })
542
+ html = self.source_tmpl.render(
543
+ {
544
+ **file_data.__dict__,
545
+ "contexts_json": contexts_json,
546
+ "prev_html": ftr.prev_html,
547
+ "next_html": ftr.next_html,
548
+ }
549
+ )
523
550
  write_html(html_path, html)
524
551
 
525
552
  # Save this file's information for the index page.
526
553
  index_info = IndexItem(
527
- url = ftr.html_filename,
528
- file = escape(ftr.fr.relative_filename()),
529
- nums = ftr.analysis.numbers,
554
+ url=ftr.html_filename,
555
+ file=escape(ftr.fr.relative_filename()),
556
+ nums=ftr.analysis.numbers,
530
557
  )
531
558
  self.index_pages["file"].summaries.append(index_info)
532
559
  self.incr.set_index_info(ftr.rootname, index_info)
@@ -554,39 +581,51 @@ class HtmlReporter:
554
581
 
555
582
  for noun in region_nouns:
556
583
  page_data = self.index_pages[noun]
557
- outside_lines = set(range(1, num_lines + 1))
558
584
 
585
+ outside_lines = set(range(1, num_lines + 1))
559
586
  for region in regions:
560
587
  if region.kind != noun:
561
588
  continue
562
589
  outside_lines -= region.lines
563
- analysis = ftr.analysis.narrow(region.lines)
590
+
591
+ narrower = AnalysisNarrower(ftr.analysis)
592
+ narrower.add_regions(r.lines for r in regions if r.kind == noun)
593
+ narrower.add_regions([outside_lines])
594
+
595
+ for region in regions:
596
+ if region.kind != noun:
597
+ continue
598
+ analysis = narrower.narrow(region.lines)
564
599
  if not self.should_report(analysis, page_data):
565
600
  continue
566
601
  sorting_name = region.name.rpartition(".")[-1].lstrip("_")
567
- page_data.summaries.append(IndexItem(
568
- url=f"{ftr.html_filename}#t{region.start}",
569
- file=escape(ftr.fr.relative_filename()),
570
- description=(
571
- f"<data value='{escape(sorting_name)}'>"
572
- + escape(region.name)
573
- + "</data>"
574
- ),
575
- nums=analysis.numbers,
576
- ))
577
-
578
- analysis = ftr.analysis.narrow(outside_lines)
602
+ page_data.summaries.append(
603
+ IndexItem(
604
+ url=f"{ftr.html_filename}#t{region.start}",
605
+ file=escape(ftr.fr.relative_filename()),
606
+ description=(
607
+ f"<data value='{escape(sorting_name)}'>"
608
+ + escape(region.name)
609
+ + "</data>"
610
+ ),
611
+ nums=analysis.numbers,
612
+ )
613
+ )
614
+
615
+ analysis = narrower.narrow(outside_lines)
579
616
  if self.should_report(analysis, page_data):
580
- page_data.summaries.append(IndexItem(
581
- url=ftr.html_filename,
582
- file=escape(ftr.fr.relative_filename()),
583
- description=(
584
- "<data value=''>"
585
- + f"<span class='no-noun'>(no {escape(noun)})</span>"
586
- + "</data>"
587
- ),
588
- nums=analysis.numbers,
589
- ))
617
+ page_data.summaries.append(
618
+ IndexItem(
619
+ url=ftr.html_filename,
620
+ file=escape(ftr.fr.relative_filename()),
621
+ description=(
622
+ "<data value=''>"
623
+ + f"<span class='no-noun'>(no {escape(noun)})</span>"
624
+ + "</data>"
625
+ ),
626
+ nums=analysis.numbers,
627
+ )
628
+ )
590
629
 
591
630
  for noun, index_page in self.index_pages.items():
592
631
  if noun != "file":
@@ -636,6 +675,7 @@ class HtmlReporter:
636
675
  @dataclass
637
676
  class FileInfo:
638
677
  """Summary of the information from last rendering, to avoid duplicate work."""
678
+
639
679
  hash: str = ""
640
680
  index: IndexItem = field(default_factory=IndexItem)
641
681
 
@@ -706,7 +746,7 @@ class IncrementalChecker:
706
746
  """Read the information we stored last time."""
707
747
  try:
708
748
  status_file = os.path.join(self.directory, self.STATUS_FILE)
709
- with open(status_file) as fstatus:
749
+ with open(status_file, encoding="utf-8") as fstatus:
710
750
  status = json.load(fstatus)
711
751
  except (OSError, ValueError):
712
752
  # Status file is missing or malformed.
@@ -742,12 +782,9 @@ class IncrementalChecker:
742
782
  "format": self.STATUS_FORMAT,
743
783
  "version": coverage.__version__,
744
784
  "globals": self.globals,
745
- "files": {
746
- fname: dataclasses.asdict(finfo)
747
- for fname, finfo in self.files.items()
748
- },
785
+ "files": {fname: dataclasses.asdict(finfo) for fname, finfo in self.files.items()},
749
786
  }
750
- with open(status_file, "w") as fout:
787
+ with open(status_file, "w", encoding="utf-8") as fout:
751
788
  json.dump(status_data, fout, separators=(",", ":"))
752
789
 
753
790
  def check_global_data(self, *data: Any) -> None:
@@ -802,6 +839,7 @@ class IncrementalChecker:
802
839
 
803
840
  # Helpers for templates and generating HTML
804
841
 
842
+
805
843
  def escape(t: str) -> str:
806
844
  """HTML-escape the text in `t`.
807
845
 
@@ -815,3 +853,8 @@ def escape(t: str) -> str:
815
853
  def pair(ratio: tuple[int, int]) -> str:
816
854
  """Format a pair of numbers so JavaScript can read them in an attribute."""
817
855
  return "{} {}".format(*ratio)
856
+
857
+
858
+ def pretty_file(filename: str) -> str:
859
+ """Return a prettier version of `filename` for display."""
860
+ return re.sub(r"[/\\]", "\N{THIN SPACE}\\g<0>\N{THIN SPACE}", filename)
@@ -1,5 +1,5 @@
1
1
  // Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
2
- // For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
2
+ // For details: https://github.com/coveragepy/coveragepy/blob/main/NOTICE.txt
3
3
 
4
4
  // Coverage.py HTML report browser code.
5
5
  /*jslint browser: true, sloppy: true, vars: true, plusplus: true, maxerr: 50, indent: 4 */
@@ -140,12 +140,15 @@ coverage.wire_up_filter = function () {
140
140
  const table_body_rows = table.querySelectorAll("tbody tr");
141
141
  const no_rows = document.getElementById("no_rows");
142
142
 
143
+ const footer = table.tFoot.rows[0];
144
+ const ratio_columns = Array.from(footer.cells).map(cell => Boolean(cell.dataset.ratio));
145
+
143
146
  // Observe filter keyevents.
144
147
  const filter_handler = (event => {
145
148
  // Keep running total of each metric, first index contains number of shown rows
146
- const totals = new Array(table.rows[0].cells.length).fill(0);
147
- // Accumulate the percentage as fraction
148
- totals[totals.length - 1] = { "numer": 0, "denom": 0 }; // nosemgrep: eslint.detect-object-injection
149
+ const totals = ratio_columns.map(
150
+ is_ratio => is_ratio ? {"numer": 0, "denom": 0} : 0
151
+ );
149
152
 
150
153
  var text = document.getElementById("filter").value;
151
154
  // Store filter value
@@ -191,11 +194,11 @@ coverage.wire_up_filter = function () {
191
194
  for (let column = 0; column < totals.length; column++) {
192
195
  // Accumulate dynamic totals
193
196
  cell = row.cells[column] // nosemgrep: eslint.detect-object-injection
194
- if (cell.classList.contains("name")) {
197
+ if (cell.matches(".name, .spacer")) {
195
198
  continue;
196
199
  }
197
- if (column === totals.length - 1) {
198
- // Last column contains percentage
200
+ if (ratio_columns[column] && cell.dataset.ratio) {
201
+ // Column stores a ratio
199
202
  const [numer, denom] = cell.dataset.ratio.split(" ");
200
203
  totals[column]["numer"] += parseInt(numer, 10); // nosemgrep: eslint.detect-object-injection
201
204
  totals[column]["denom"] += parseInt(denom, 10); // nosemgrep: eslint.detect-object-injection
@@ -218,17 +221,16 @@ coverage.wire_up_filter = function () {
218
221
  no_rows.style.display = null;
219
222
  table.style.display = null;
220
223
 
221
- const footer = table.tFoot.rows[0];
222
224
  // Calculate new dynamic sum values based on visible rows.
223
225
  for (let column = 0; column < totals.length; column++) {
224
226
  // Get footer cell element.
225
227
  const cell = footer.cells[column]; // nosemgrep: eslint.detect-object-injection
226
- if (cell.classList.contains("name")) {
228
+ if (cell.matches(".name, .spacer")) {
227
229
  continue;
228
230
  }
229
231
 
230
232
  // Set value into dynamic footer cell element.
231
- if (column === totals.length - 1) {
233
+ if (ratio_columns[column]) {
232
234
  // Percentage column uses the numerator and denominator,
233
235
  // and adapts to the number of decimal places.
234
236
  const match = /\.([0-9]+)/.exec(cell.textContent);
@@ -1,5 +1,5 @@
1
1
  {# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 #}
2
- {# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt #}
2
+ {# For details: https://github.com/coveragepy/coveragepy/blob/main/NOTICE.txt #}
3
3
 
4
4
  <!DOCTYPE html>
5
5
  <html lang="en">
@@ -81,53 +81,88 @@
81
81
  <table class="index" data-sortable>
82
82
  <thead>
83
83
  {# The title="" attr doesn't work in Safari. #}
84
+ {% if has_arcs %}
85
+ <tr class="tablehead grouphead">
86
+ <th class="spacer">&nbsp;</th>
87
+ {% if region_noun %}
88
+ <th class="spacer">&nbsp;</th>
89
+ {% endif %}
90
+ <th class="spacer">&nbsp;</th>
91
+ <th class="left" colspan="{% if has_arcs %}4{% else %}3{% endif %}">Statements</th>
92
+ <th class="spacer">&nbsp;</th>
93
+ <th class="left" colspan="3">Branches</th>
94
+ <th class="spacer">&nbsp;</th>
95
+ <th>Total</th>
96
+ </tr>
97
+ {% endif %}
84
98
  <tr class="tablehead" title="Click to sort">
85
- <th id="file" class="name left" aria-sort="none" data-shortcut="f">File<span class="arrows"></span></th>
99
+ <th id="file" class="name" aria-sort="none" data-shortcut="f">File<span class="arrows"></span></th>
86
100
  {% if region_noun %}
87
- <th id="region" class="name left" aria-sort="none" data-default-sort-order="ascending" data-shortcut="n">{{ region_noun }}<span class="arrows"></span></th>
101
+ <th id="region" class="name" aria-sort="none" data-default-sort-order="ascending" data-shortcut="n">{{ region_noun }}<span class="arrows"></span></th>
102
+ {% endif %}
103
+ <th class="spacer">&nbsp;</th>
104
+ {% if has_arcs %}
105
+ <th id="statements_coverage" aria-sort="none" data-default-sort-order="descending">coverage<span class="arrows"></span></th>
88
106
  {% endif %}
89
107
  <th id="statements" aria-sort="none" data-default-sort-order="descending" data-shortcut="s">statements<span class="arrows"></span></th>
90
108
  <th id="missing" aria-sort="none" data-default-sort-order="descending" data-shortcut="m">missing<span class="arrows"></span></th>
91
109
  <th id="excluded" aria-sort="none" data-default-sort-order="descending" data-shortcut="x">excluded<span class="arrows"></span></th>
92
110
  {% if has_arcs %}
111
+ <th class="spacer">&nbsp;</th>
112
+ <th id="branches_coverage" aria-sort="none" data-default-sort-order="descending">coverage<span class="arrows"></span></th>
93
113
  <th id="branches" aria-sort="none" data-default-sort-order="descending" data-shortcut="b">branches<span class="arrows"></span></th>
94
114
  <th id="partial" aria-sort="none" data-default-sort-order="descending" data-shortcut="p">partial<span class="arrows"></span></th>
95
115
  {% endif %}
96
- <th id="coverage" class="right" aria-sort="none" data-shortcut="c">coverage<span class="arrows"></span></th>
116
+ <th class="spacer">&nbsp;</th>
117
+ <th id="coverage" aria-sort="none" data-shortcut="c">coverage<span class="arrows"></span></th>
97
118
  </tr>
98
119
  </thead>
99
120
  <tbody>
100
121
  {% for region in regions %}
101
122
  <tr class="region">
102
- <td class="name left"><a href="{{region.url}}">{{region.file}}</a></td>
123
+ <td class="name"><a href="{{region.url}}">{{region.file|escape|pretty_file}}</a></td>
103
124
  {% if region_noun %}
104
- <td class="name left"><a href="{{region.url}}">{{region.description}}</a></td>
125
+ <td class="name"><a href="{{region.url}}">{{region.description}}</a></td>
126
+ {% endif %}
127
+ <td class="spacer">&nbsp;</td>
128
+ {% if has_arcs %}
129
+ <td data-ratio="{{region.nums.ratio_statements|pair}}">{{region.nums.pc_statements_str}}%</td>
105
130
  {% endif %}
106
131
  <td>{{region.nums.n_statements}}</td>
107
132
  <td>{{region.nums.n_missing}}</td>
108
133
  <td>{{region.nums.n_excluded}}</td>
109
134
  {% if has_arcs %}
135
+ <td class="spacer">&nbsp;</td>
136
+ <td data-ratio="{{region.nums.ratio_branches|pair}}">{{region.nums.pc_branches_str}}%</td>
110
137
  <td>{{region.nums.n_branches}}</td>
111
138
  <td>{{region.nums.n_partial_branches}}</td>
112
139
  {% endif %}
113
- <td class="right" data-ratio="{{region.nums.ratio_covered|pair}}">{{region.nums.pc_covered_str}}%</td>
140
+ <td class="spacer">&nbsp;</td>
141
+ <td data-ratio="{{region.nums.ratio_covered|pair}}">{{region.nums.pc_covered_str}}%</td>
114
142
  </tr>
115
143
  {% endfor %}
116
144
  </tbody>
117
145
  <tfoot>
118
146
  <tr class="total">
119
- <td class="name left">Total</td>
147
+ <td class="name">Total</td>
120
148
  {% if region_noun %}
121
- <td class="name left">&nbsp;</td>
149
+ <td class="name">&nbsp;</td>
150
+ {% endif %}
151
+ <td class="spacer">&nbsp;</td>
152
+ {% if has_arcs %}
153
+ <td data-ratio="{{totals.ratio_statements|pair}}">{{totals.pc_statements_str}}%</td>
122
154
  {% endif %}
123
155
  <td>{{totals.n_statements}}</td>
124
156
  <td>{{totals.n_missing}}</td>
125
157
  <td>{{totals.n_excluded}}</td>
126
158
  {% if has_arcs %}
159
+ <td class="spacer">&nbsp;</td>
160
+ <td data-ratio="{{totals.ratio_branches|pair}}">{{totals.pc_branches_str}}%</td>
127
161
  <td>{{totals.n_branches}}</td>
128
162
  <td>{{totals.n_partial_branches}}</td>
129
163
  {% endif %}
130
- <td class="right" data-ratio="{{totals.ratio_covered|pair}}">{{totals.pc_covered_str}}%</td>
164
+ <td class="spacer">&nbsp;</td>
165
+ <td data-ratio="{{totals.ratio_covered|pair}}">{{totals.pc_covered_str}}%</td>
131
166
  </tr>
132
167
  </tfoot>
133
168
  </table>
@@ -1,5 +1,5 @@
1
1
  {# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 #}
2
- {# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt #}
2
+ {# For details: https://github.com/coveragepy/coveragepy/blob/main/NOTICE.txt #}
3
3
 
4
4
  <!DOCTYPE html>
5
5
  <html lang="en">
@@ -25,7 +25,7 @@
25
25
  <header>
26
26
  <div class="content">
27
27
  <h1>
28
- <span class="text">Coverage for </span><b>{{relative_filename|escape}}</b>:
28
+ <span class="text">Coverage for </span><b>{{relative_filename|escape|pretty_file}}</b>:
29
29
  <span class="pc_cov">{{nums.pc_covered_str}}%</span>
30
30
  </h1>
31
31