diff --git a/CHANGELOG.md b/CHANGELOG.md index 0bf4f6e..e6c5958 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,8 @@ 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 - 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 numbers that have changed, i.e., everything after the part common to all diff --git a/src/nvd b/src/nvd index fd0a571..40105cb 100755 --- a/src/nvd +++ b/src/nvd @@ -253,6 +253,9 @@ class Version: """Returns the version string, empty (not None) if there is no version.""" return self._text + def chunks(self) -> list[VersionChunk]: + return self._chunks + class Package: def __init__(self, *, pname: str, version: Version, store_path: StorePath): assert isinstance(pname, str), f"Not a string: {pname!r}" @@ -405,6 +408,8 @@ class PackageListEntry: self._left_packages = left_packages self._right_packages = right_packages + self._first_changed_version_chunk: Optional[int] = None + def get_pname(self) -> str: if self._right_packages: return self._right_packages[0].pname() @@ -417,6 +422,31 @@ class PackageListEntry: def get_right_packages(self) -> Optional[list[Package]]: 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: @abstractmethod def name(self) -> str: @@ -443,6 +473,8 @@ class PackageListEntryReverseComparator(PackageListEntryComparator): return self._delegate class PackageListEntryNameComparator(PackageListEntryComparator): + """Sorts package list entries by pname.""" + def name(self) -> str: return "name" @@ -452,11 +484,31 @@ class PackageListEntryNameComparator(PackageListEntryComparator): 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] = { sk.name(): sk for sk in ( # The first one listed here is the default. PackageListEntryNameComparator(), + PackageListEntrySemVerChangeComparator(), ) } diff --git a/src/nvd.1 b/src/nvd.1 index 45f4141..01da6c0 100644 --- a/src/nvd.1 +++ b/src/nvd.1 @@ -193,7 +193,24 @@ break ties, in order. Each keyword may be preceeded by a hyphen .B \- to reverse the regular sort order of the keyword. The default order is .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 -r|--root For the