diff options
author | Ned Batchelder <ned@nedbatchelder.com> | 2021-10-12 08:46:25 -0400 |
---|---|---|
committer | Ned Batchelder <ned@nedbatchelder.com> | 2021-10-12 08:46:25 -0400 |
commit | 0eaeb99f2de1330a562752d30d02d1898f681cf8 (patch) | |
tree | d51b41a302dddafd3092c0fc367b1676bab56a6d /coverage | |
parent | 5b6b6ecb87f4aa1145977b1a4c8359b202da0d7a (diff) | |
download | python-coveragepy-git-0eaeb99f2de1330a562752d30d02d1898f681cf8.tar.gz |
fix: use human sorting on human-readable things
Diffstat (limited to 'coverage')
-rw-r--r-- | coverage/cmdline.py | 3 | ||||
-rw-r--r-- | coverage/collector.py | 4 | ||||
-rw-r--r-- | coverage/control.py | 6 | ||||
-rw-r--r-- | coverage/files.py | 4 | ||||
-rw-r--r-- | coverage/html.py | 3 | ||||
-rw-r--r-- | coverage/misc.py | 31 | ||||
-rw-r--r-- | coverage/summary.py | 21 | ||||
-rw-r--r-- | coverage/xmlreport.py | 8 |
8 files changed, 58 insertions, 22 deletions
diff --git a/coverage/cmdline.py b/coverage/cmdline.py index 1be155b8..dfdbd1c7 100644 --- a/coverage/cmdline.py +++ b/coverage/cmdline.py @@ -20,6 +20,7 @@ from coverage.data import line_counts from coverage.debug import info_formatter, info_header, short_stack from coverage.exceptions import BaseCoverageException, ExceptionDuringRun, NoSource from coverage.execfile import PyRunner +from coverage.misc import human_sorted from coverage.results import Numbers, should_fail_under @@ -780,7 +781,7 @@ class CoverageScript: if data: print(f"has_arcs: {data.has_arcs()!r}") summary = line_counts(data, fullpath=True) - filenames = sorted(summary.keys()) + filenames = human_sorted(summary.keys()) print(f"\n{len(filenames)} files:") for f in filenames: line = f"{f}: {summary[f]} lines" diff --git a/coverage/collector.py b/coverage/collector.py index 73babf44..733b6f32 100644 --- a/coverage/collector.py +++ b/coverage/collector.py @@ -10,7 +10,7 @@ from coverage import env from coverage.debug import short_stack from coverage.disposition import FileDisposition from coverage.exceptions import CoverageException -from coverage.misc import isolate_module +from coverage.misc import human_sorted, isolate_module from coverage.pytracer import PyTracer os = isolate_module(os) @@ -352,7 +352,7 @@ class Collector: stats = tracer.get_stats() if stats: print("\nCoverage.py tracer stats:") - for k in sorted(stats.keys()): + for k in human_sorted(stats.keys()): print(f"{k:>20}: {stats[k]}") if self.threading: self.threading.settrace(None) diff --git a/coverage/control.py b/coverage/control.py index 8a55a317..defe9209 100644 --- a/coverage/control.py +++ b/coverage/control.py @@ -26,7 +26,7 @@ from coverage.files import PathAliases, abs_file, relative_filename, set_relativ from coverage.html import HtmlReporter from coverage.inorout import InOrOut from coverage.jsonreport import JsonReporter -from coverage.misc import bool_or_none, join_regex +from coverage.misc import bool_or_none, join_regex, human_sorted, human_sorted_items from coverage.misc import DefaultValue, ensure_dir_for_file, isolate_module from coverage.plugin import FileReporter from coverage.plugin_support import Plugins @@ -309,7 +309,7 @@ class Coverage: wrote_any = False with self._debug.without_callers(): if self._debug.should('config'): - config_info = sorted(self.config.__dict__.items()) + config_info = human_sorted_items(self.config.__dict__.items()) config_info = [(k, v) for k, v in config_info if not k.startswith('_')] write_formatted_info(self._debug, "config", config_info) wrote_any = True @@ -1076,7 +1076,7 @@ class Coverage: ('pid', os.getpid()), ('cwd', os.getcwd()), ('path', sys.path), - ('environment', sorted( + ('environment', human_sorted( f"{k} = {v}" for k, v in os.environ.items() if ( diff --git a/coverage/files.py b/coverage/files.py index 68671744..6a4f5906 100644 --- a/coverage/files.py +++ b/coverage/files.py @@ -14,7 +14,7 @@ import sys from coverage import env from coverage.exceptions import CoverageException -from coverage.misc import contract, join_regex, isolate_module +from coverage.misc import contract, human_sorted, isolate_module, join_regex os = isolate_module(os) @@ -199,7 +199,7 @@ class TreeMatcher: """ def __init__(self, paths, name): - self.original_paths = sorted(paths) + self.original_paths = human_sorted(paths) self.paths = list(map(os.path.normcase, paths)) self.name = name diff --git a/coverage/html.py b/coverage/html.py index b095343e..1fbac4b3 100644 --- a/coverage/html.py +++ b/coverage/html.py @@ -15,6 +15,7 @@ from coverage.data import add_data_to_hash from coverage.exceptions import CoverageException from coverage.files import flat_rootname 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 @@ -123,7 +124,7 @@ class HtmlDataGeneration: 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.get(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: diff --git a/coverage/misc.py b/coverage/misc.py index 29397537..40f00930 100644 --- a/coverage/misc.py +++ b/coverage/misc.py @@ -389,3 +389,34 @@ def import_local_file(modname, modfile=None): spec.loader.exec_module(mod) return mod + + +def human_key(s): + """Turn a string into a list of string and number chunks. + "z23a" -> ["z", 23, "a"] + """ + def tryint(s): + """If `s` is a number, return an int, else `s` unchanged.""" + try: + return int(s) + except ValueError: + return s + + return [tryint(c) for c in re.split(r"(\d+)", s)] + +def human_sorted(strings): + """Sort the given iterable of strings the way that humans expect. + + Numeric components in the strings are sorted as numbers. + + Returns the sorted list. + + """ + return sorted(strings, key=human_key) + +def human_sorted_items(items, reverse=False): + """Sort the (string, value) items the way humans expect. + + Returns the sorted list of items. + """ + return sorted(items, key=lambda pair: (human_key(pair[0]), pair[1]), reverse=reverse) diff --git a/coverage/summary.py b/coverage/summary.py index b7b172f8..0b54a05b 100644 --- a/coverage/summary.py +++ b/coverage/summary.py @@ -6,6 +6,7 @@ import sys from coverage.exceptions import CoverageException +from coverage.misc import human_sorted_items from coverage.report import get_analysis_to_report from coverage.results import Numbers @@ -89,15 +90,17 @@ class SummaryReporter: lines.append((text, args)) # Sort the lines and write them out. - if getattr(self.config, 'sort', None): - sort_option = self.config.sort.lower() - reverse = False - if sort_option[0] == '-': - reverse = True - sort_option = sort_option[1:] - elif sort_option[0] == '+': - sort_option = sort_option[1:] - + sort_option = (self.config.sort or "name").lower() + reverse = False + if sort_option[0] == '-': + reverse = True + sort_option = sort_option[1:] + elif sort_option[0] == '+': + sort_option = sort_option[1:] + + if sort_option == "name": + lines = human_sorted_items(lines, reverse=reverse) + else: position = column_order.get(sort_option) if position is None: raise CoverageException(f"Invalid sorting option: {self.config.sort!r}") diff --git a/coverage/xmlreport.py b/coverage/xmlreport.py index 0538bfd5..6dc330f1 100644 --- a/coverage/xmlreport.py +++ b/coverage/xmlreport.py @@ -10,7 +10,7 @@ import time import xml.dom.minidom from coverage import __url__, __version__, files -from coverage.misc import isolate_module +from coverage.misc import isolate_module, human_sorted, human_sorted_items from coverage.report import get_analysis_to_report os = isolate_module(os) @@ -77,7 +77,7 @@ class XmlReporter: xcoverage.appendChild(xsources) # Populate the XML DOM with the source info. - for path in sorted(self.source_paths): + for path in human_sorted(self.source_paths): xsource = self.xml_out.createElement("source") xsources.appendChild(xsource) txt = self.xml_out.createTextNode(path) @@ -90,13 +90,13 @@ class XmlReporter: xcoverage.appendChild(xpackages) # Populate the XML DOM with the package info. - for pkg_name, pkg_data in sorted(self.packages.items()): + for pkg_name, pkg_data in human_sorted_items(self.packages.items()): class_elts, lhits, lnum, bhits, bnum = pkg_data xpackage = self.xml_out.createElement("package") xpackages.appendChild(xpackage) xclasses = self.xml_out.createElement("classes") xpackage.appendChild(xclasses) - for _, class_elt in sorted(class_elts.items()): + for _, class_elt in human_sorted_items(class_elts.items()): xclasses.appendChild(class_elt) xpackage.setAttribute("name", pkg_name.replace(os.sep, '.')) xpackage.setAttribute("line-rate", rate(lhits, lnum)) |