Add semver sorting (issue #17).

This commit is contained in:
Bryan Gardiner 2024-09-29 17:56:08 -07:00
parent 4ae4ae9fae
commit c0ed21c632
No known key found for this signature in database
GPG key ID: 53EFBCA063E6183C
3 changed files with 72 additions and 2 deletions

View file

@ -6,7 +6,8 @@
profile, issue #18. Big thanks to Felix Uhl for the idea and implementation! profile, issue #18. Big thanks to Felix Uhl for the idea and implementation!
- Added `--sort` option for controlling the order packages are listed in, for - Added `--sort` option for controlling the order packages are listed in, for
issue #17. issue #17. Supported sort options are package name, and the level of semver
version change.
- In the "Version changes" section of a diff, highlight the portions of version - In the "Version changes" section of a diff, highlight the portions of version
numbers that have changed, i.e., everything after the part common to all numbers that have changed, i.e., everything after the part common to all

52
src/nvd
View file

@ -253,6 +253,9 @@ class Version:
"""Returns the version string, empty (not None) if there is no version.""" """Returns the version string, empty (not None) if there is no version."""
return self._text return self._text
def chunks(self) -> list[VersionChunk]:
return self._chunks
class Package: class Package:
def __init__(self, *, pname: str, version: Version, store_path: StorePath): def __init__(self, *, pname: str, version: Version, store_path: StorePath):
assert isinstance(pname, str), f"Not a string: {pname!r}" assert isinstance(pname, str), f"Not a string: {pname!r}"
@ -405,6 +408,8 @@ class PackageListEntry:
self._left_packages = left_packages self._left_packages = left_packages
self._right_packages = right_packages self._right_packages = right_packages
self._first_changed_version_chunk: Optional[int] = None
def get_pname(self) -> str: def get_pname(self) -> str:
if self._right_packages: if self._right_packages:
return self._right_packages[0].pname() return self._right_packages[0].pname()
@ -417,6 +422,31 @@ class PackageListEntry:
def get_right_packages(self) -> Optional[list[Package]]: def get_right_packages(self) -> Optional[list[Package]]:
return self._right_packages return self._right_packages
def find_first_changed_version_chunk(self) -> int:
"""
Returns the position of the earliest version chunk that differs when
comparing the maximum version on the left, with the maximum version on
the right.
"""
if self._right_packages is None:
return 0
if self._first_changed_version_chunk is not None:
return self._first_changed_version_chunk
max_left_ver = self.get_left_packages()[-1].version()
max_right_ver = self.get_right_packages()[-1].version()
chunks_in_common = 0
for left_chunk, right_chunk in zip(max_left_ver.chunks(), max_right_ver.chunks()):
if left_chunk == right_chunk:
chunks_in_common += 1
else:
break
self._first_changed_version_chunk = chunks_in_common
return chunks_in_common
class PackageListEntryComparator: class PackageListEntryComparator:
@abstractmethod @abstractmethod
def name(self) -> str: def name(self) -> str:
@ -443,6 +473,8 @@ class PackageListEntryReverseComparator(PackageListEntryComparator):
return self._delegate return self._delegate
class PackageListEntryNameComparator(PackageListEntryComparator): class PackageListEntryNameComparator(PackageListEntryComparator):
"""Sorts package list entries by pname."""
def name(self) -> str: def name(self) -> str:
return "name" return "name"
@ -452,11 +484,31 @@ class PackageListEntryNameComparator(PackageListEntryComparator):
b.get_left_packages()[0].pname(), b.get_left_packages()[0].pname(),
) )
class PackageListEntrySemVerChangeComparator(PackageListEntryComparator):
"""
Sorts package list entries by the level of version change according to
Semantic Versioning.
The maximum version on the left is compared with the maximum version on the
right. Packages whose first version chunks differ are sorted first,
followed by packages whose second chunks differ, and so on.
"""
def name(self) -> str:
return "semver"
def cmp_packages(self, a: PackageListEntry, b: PackageListEntry) -> int:
return cmp(
a.find_first_changed_version_chunk(),
b.find_first_changed_version_chunk(),
)
ALL_SORTS: dict[str, PackageListEntryComparator] = { ALL_SORTS: dict[str, PackageListEntryComparator] = {
sk.name(): sk sk.name(): sk
for sk in ( for sk in (
# The first one listed here is the default. # The first one listed here is the default.
PackageListEntryNameComparator(), PackageListEntryNameComparator(),
PackageListEntrySemVerChangeComparator(),
) )
} }

View file

@ -193,7 +193,24 @@ break ties, in order. Each keyword may be preceeded by a hyphen
.B \- .B \-
to reverse the regular sort order of the keyword. The default order is to reverse the regular sort order of the keyword. The default order is
.B name, .B name,
which sorts by package name. Currently only this keyword is supported. which sorts by package name.
.IP
The keyword
.B semver
sorts packages according to how significant their version changes are, by the
rules of Semantic Versioning, when versions are being compared (with the
.B diff
and
.B history
commands). Each package's highest old version is compared with its highest new
version. Packages where the first component of these two versions have changed
are sorted first. Packages where the second version components differ are
sorted second, and so on. Components here are consecutive runs of digits, or of
letters (but not both), as per the Nix version handling rules. Only the
position of the first difference is used; the numeric difference between version
numbers does not matter. Because only the highest old and new versions are
compared, this sort type may not produce ideal results if multiple versions of a
package are present.
.TP .TP
-r|--root <store-path> -r|--root <store-path>
For the For the