summaryrefslogtreecommitdiff
path: root/coverage
diff options
context:
space:
mode:
Diffstat (limited to 'coverage')
-rw-r--r--coverage/cmdline.py3
-rw-r--r--coverage/collector.py4
-rw-r--r--coverage/control.py6
-rw-r--r--coverage/files.py4
-rw-r--r--coverage/html.py3
-rw-r--r--coverage/misc.py31
-rw-r--r--coverage/summary.py21
-rw-r--r--coverage/xmlreport.py8
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))