Skip to content

Commit 5514ac3

Browse files
authored
Sort src lines in log messages "naturally" (#119)
* Sort src lines in log messages "naturally" * Sync stubs with source code changes * Fix return type
1 parent 2cfe2a4 commit 5514ac3

File tree

5 files changed

+49
-3
lines changed

5 files changed

+49
-3
lines changed

src/docstub-stubs/_report.pyi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ from typing import Any, ClassVar, Literal, Self, TextIO
99
import click
1010

1111
from ._cli_help import should_strip_ansi
12+
from ._utils import naive_natsort_key
1213

1314
logger: logging.Logger
1415

src/docstub-stubs/_utils.pyi

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import re
55
from collections.abc import Callable, Hashable, Mapping, Sequence
66
from functools import lru_cache, wraps
77
from pathlib import Path
8+
from typing import Any
89
from zlib import crc32
910

1011
def accumulate_qualname(qualname: str, *, start_right: bool = ...) -> None: ...
@@ -20,3 +21,7 @@ def update_with_add_values(
2021

2122
class DocstubError(Exception):
2223
pass
24+
25+
_regex_digit: re.Pattern
26+
27+
def naive_natsort_key(item: Any) -> tuple[str | int, ...]: ...

src/docstub/_report.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import click
99

1010
from ._cli_help import should_strip_ansi
11+
from ._utils import naive_natsort_key
1112

1213
logger: logging.Logger = logging.getLogger(__name__)
1314

@@ -297,7 +298,7 @@ def format(self, record):
297298
msg = f"{msg}\n{indented}"
298299

299300
# Append locations
300-
for location in sorted(src_locations):
301+
for location in sorted(src_locations, key=naive_natsort_key):
301302
location_styled = click.style(location, fg="magenta")
302303
msg = f"{msg}\n {location_styled}"
303304

src/docstub/_utils.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,3 +198,41 @@ def update_with_add_values(*mappings, out=None):
198198

199199
class DocstubError(Exception):
200200
"""An error raised by docstub."""
201+
202+
203+
_regex_digit: re.Pattern = re.compile(r"(\d+)")
204+
205+
206+
def naive_natsort_key(item):
207+
"""Transforms strings into tuples that can be sorted in natural order [1]_.
208+
209+
This can be passed to the "key" argument of Python's `sorted` function.
210+
This is a simple implementation that will likely miss many edge cases.
211+
212+
Parameters
213+
----------
214+
item : Any
215+
Item to generate the key from. `str` is called on this item before
216+
generating the key.
217+
218+
Returns
219+
-------
220+
key : tuple[str | int, ...]
221+
Key to sort by.
222+
223+
Examples
224+
--------
225+
>>> naive_natsort_key("exposure.py:0:10")
226+
('exposure.py:', 0, ':', 10, '')
227+
>>> paths = ["exposure.py:180", "exposure.py:47"]
228+
>>> sorted(paths, key=naive_natsort_key)
229+
['exposure.py:47', 'exposure.py:180']
230+
231+
References
232+
----------
233+
.. [1] https://en.wikipedia.org/wiki/Natural_sort_order
234+
"""
235+
parts = _regex_digit.split(str(item))
236+
# IDEA: Every second element should always be a digit, use that?
237+
key = tuple(int(part) if part.isdigit() else part for part in parts)
238+
return key

tests/test_report.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,18 +102,19 @@ def test_format(self, log_record):
102102

103103
def test_format_multiple_locations(self, log_record):
104104
log_record.details = "Some details"
105-
log_record.src_location = ["foo.py:42", "bar.py", "a/path.py:100"]
105+
log_record.src_location = ["foo.py:42", "foo.py:9", "bar.py", "a/path.py:100"]
106106
log_record.log_id = "E321"
107107

108108
handler = ReportHandler()
109109
result = handler.format(log_record)
110110

111111
expected = dedent(
112112
"""
113-
E321 The actual log message (3x)
113+
E321 The actual log message (4x)
114114
Some details
115115
a/path.py:100
116116
bar.py
117+
foo.py:9
117118
foo.py:42
118119
"""
119120
).strip()

0 commit comments

Comments
 (0)