Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/docstub-stubs/_report.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ from typing import Any, ClassVar, Literal, Self, TextIO
import click

from ._cli_help import should_strip_ansi
from ._utils import naive_natsort_key

logger: logging.Logger

Expand Down
5 changes: 5 additions & 0 deletions src/docstub-stubs/_utils.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import re
from collections.abc import Callable, Hashable, Mapping, Sequence
from functools import lru_cache, wraps
from pathlib import Path
from typing import Any
from zlib import crc32

def accumulate_qualname(qualname: str, *, start_right: bool = ...) -> None: ...
Expand All @@ -18,3 +19,7 @@ def update_with_add_values(

class DocstubError(Exception):
pass

_regex_digit: re.Pattern

def naive_natsort_key(item: Any) -> tuple[str | int, ...]: ...
3 changes: 2 additions & 1 deletion src/docstub/_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import click

from ._cli_help import should_strip_ansi
from ._utils import naive_natsort_key

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

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

# Append locations
for location in sorted(src_locations):
for location in sorted(src_locations, key=naive_natsort_key):
location_styled = click.style(location, fg="magenta")
msg = f"{msg}\n {location_styled}"

Expand Down
38 changes: 38 additions & 0 deletions src/docstub/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,3 +198,41 @@ def update_with_add_values(*mappings, out=None):

class DocstubError(Exception):
"""An error raised by docstub."""


_regex_digit: re.Pattern = re.compile(r"(\d+)")


def naive_natsort_key(item):
"""Transforms strings into tuples that can be sorted in natural order [1]_.

This can be passed to the "key" argument of Python's `sorted` function.
This is a simple implementation that will likely miss many edge cases.

Parameters
----------
item : Any
Item to generate the key from. `str` is called on this item before
generating the key.

Returns
-------
key : tuple[str | int, ...]
Key to sort by.

Examples
--------
>>> naive_natsort_key("exposure.py:0:10")
('exposure.py:', 0, ':', 10, '')
>>> paths = ["exposure.py:180", "exposure.py:47"]
>>> sorted(paths, key=naive_natsort_key)
['exposure.py:47', 'exposure.py:180']

References
----------
.. [1] https://en.wikipedia.org/wiki/Natural_sort_order
"""
parts = _regex_digit.split(str(item))
# IDEA: Every second element should always be a digit, use that?
key = tuple(int(part) if part.isdigit() else part for part in parts)
return key
5 changes: 3 additions & 2 deletions tests/test_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,18 +102,19 @@ def test_format(self, log_record):

def test_format_multiple_locations(self, log_record):
log_record.details = "Some details"
log_record.src_location = ["foo.py:42", "bar.py", "a/path.py:100"]
log_record.src_location = ["foo.py:42", "foo.py:9", "bar.py", "a/path.py:100"]
log_record.log_id = "E321"

handler = ReportHandler()
result = handler.format(log_record)

expected = dedent(
"""
E321 The actual log message (3x)
E321 The actual log message (4x)
Some details
a/path.py:100
bar.py
foo.py:9
foo.py:42
"""
).strip()
Expand Down
Loading