Skip to content

Commit

Permalink
Fix mike version sorting (#171)
Browse files Browse the repository at this point in the history
For the same major and minor, pre-releases were sorted as newer than
stable releases, but this is not correct. This PR fixes that.

It also makes the sorting of the `versions.json` file more robust, as it
doesn't parse and re-serialize the version object, but instead only uses
the `version` field for sorting, and the rest of the fields are dumping
verbatim without any rewriting.

This means that if new versions of `mike` add more fields to the JSON,
we won't need to update the sorting code, as it will just work.
  • Loading branch information
llucax authored Nov 3, 2023
2 parents b8ee525 + 0ee52d5 commit 8bf6c38
Show file tree
Hide file tree
Showing 4 changed files with 201 additions and 63 deletions.
6 changes: 3 additions & 3 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@

## Summary

This release adds support for `pylint` 3, so downstream projects can upgrade their `pylint` version.
This release fixes a bug in `mike` version sorting.

## Upgrading

If upgrading `pylint` you might get a few new check errors.
- `frequenz.repo.config.mkdocs.mike.`: The `sort_versions()` function now takes plain `str`s as arguments instead of `MikeVersionInfo` objects.

### Cookiecutter template

There is no need to regenerate any templates with this release.

## Bug Fixes

- `mkdocs`: The `conftest` module is now properly hidden from the documentation again.
- CI / `mkdocs`: `mike` version sorting now properly sort pre-releases as older than stable releases for the same major and minor version.
31 changes: 9 additions & 22 deletions src/frequenz/repo/config/cli/version/mike/sort.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,48 +4,35 @@
"""Sort `mike`'s `version.json` file with a custom order."""


import dataclasses
import json
import sys
from typing import TextIO
from typing import Any, TextIO

from .... import github
from ....mkdocs.mike import MikeVersionInfo, sort_mike_versions
from ....mkdocs.mike import sort_mike_versions


def _sort(stream_in: TextIO, stream_out: TextIO) -> None:
"""Sort the versions in the given in stream to the given out stream.
Args:
stream_in: The stream to read the versions from.
stream_out: The stream to write the sorted versions to.
"""
versions = json.load(stream_in)
sorted_versions = sort_mike_versions([MikeVersionInfo(**v) for v in versions])
json.dump(sorted_versions, stream_out, separators=(",", ":"))


def _load_and_sort_versions_from(stream: TextIO) -> list[MikeVersionInfo]:
"""Load the versions from the given stream.
def _load_and_sort_versions_from(stream: TextIO) -> dict[str, dict[str, Any]]:
"""Load the versions from the given stream and sort them.
Args:
stream: The stream to read the versions from.
Returns:
The loaded versions.
The sorted loaded versions.
"""
versions = [MikeVersionInfo(**v) for v in json.load(stream)]
return sort_mike_versions(versions)
versions = {v["version"]: v for v in json.load(stream)}
return {v: versions[v] for v in sort_mike_versions(list(versions.keys()))}


def _dump_versions_to(versions: list[MikeVersionInfo], stream: TextIO) -> None:
def _dump_versions_to(versions: dict[str, dict[str, Any]], stream: TextIO) -> None:
"""Dump the versions to the given stream.
Args:
versions: The versions to dump.
stream: The stream to write the versions to.
"""
json.dump([dataclasses.asdict(v) for v in versions], stream, separators=(",", ":"))
json.dump(list(versions.values()), stream, separators=(",", ":"))


def main() -> None:
Expand Down
29 changes: 12 additions & 17 deletions src/frequenz/repo/config/mkdocs/mike.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,8 @@ def _to_fake_sortable_semver(version: str) -> str:
The following transformations are applied:
- `vX.Y-pre` -> `X.Y.0-pre`
- `vX.Y` -> `X.Y.0`
- `vX.Y-pre` -> `X.Y.99999`
- `vX.Y-dev` -> `X.Y.999999`
The idea is to convert the version string to a semver string that can be sorted
Expand All @@ -161,7 +161,7 @@ def _to_fake_sortable_semver(version: str) -> str:
The converted version string.
"""
version = _stable_to_semver_re.sub(r"\1.\2.0", version)
version = _pre_to_semver_re.sub(r"\1.\2.99999", version)
version = _pre_to_semver_re.sub(r"\1.\2.0-pre", version)
version = _dev_to_semver_re.sub(r"\1.\2.999999", version)
if version.startswith("v"):
version = version[1:]
Expand All @@ -175,17 +175,17 @@ def compare_mike_version(version1: str, version2: str) -> int:
- Versions are first compared by major version (`X`).
- If they have the same major, then they are compared by minor version (`Y`).
- If they have the same major and minor, then pre-releases (`vX.Y-pre`) are
considered bigger than stable versions (`vX.Y`) and development versions
- If they have the same major and minor, then stable versions (`vX.Y`) are
considered bigger than pre-releases (`vX.Y-pre`) and development versions
(`vX.Y-dev`) are considered bigger than pre-releases.
- Any other version not matching `vX.Y(-pre|-dev)?` is considered to be bigger than
the matching versions.
- Not matching versions are compared alphabetically.
Example:
`v1.0` < `v1.0-pre` < `v1.0-dev` < `v1.1` < `v2.0` < `v2.0-pre` < `v2.0-dev`
< `whatever` < `x`.
`v1.0-pre` < `v1.0` < `v1.0-dev` < `v1.1` < `v2.0-pre` < `v2.0` < `v2.0-dev`
< `whatever` < `x`.
Args:
version1: The first version to compare.
Expand All @@ -210,9 +210,7 @@ def compare_mike_version(version1: str, version2: str) -> int:
return -1 if version1 < version2 else 1


def sort_mike_versions(
versions: list[MikeVersionInfo], *, reverse: bool = True
) -> list[MikeVersionInfo]:
def sort_mike_versions(versions: list[str], *, reverse: bool = True) -> list[str]:
"""Sort `mike`'s `version.json` file with a custom order.
The `version` keys are expected as follows:
Expand All @@ -227,16 +225,17 @@ def sort_mike_versions(
- Versions are first sorted by major version (`X`).
- Inside a major version group, versions are sorted by minor version (`Y`).
- For the same major and minor version, development versions (`-dev`) considered
the latest for that major version group, then pre-release versions (`-pre`), and
finally stable versions.
the latest for that major version group, then stable versions, and finally
pre-release versions (`-pre`).
- Other versions appear first and are sorted alphabetically.
The versions are sorted in-place using
[`compare_mike_version()`][frequenz.repo.config.mkdocs.mike.compare_mike_version].
Example:
`z`, `whatever`, `v2.1-dev`, `v2.1-pre`, `v2.0`, `v1.1-dev`, `v1.0-dev`, `v1.0`
`z`, `whatever`, `v2.1-dev`, `v2.1`, `v2.1-pre`, `v2.0`, `v1.1-dev`, `v1.0-dev`,
`v1.0`
Args:
versions: The list of versions to sort.
Expand All @@ -245,9 +244,5 @@ def sort_mike_versions(
Returns:
The sorted list of versions.
"""

def compare(ver1: MikeVersionInfo, ver2: MikeVersionInfo) -> int:
return compare_mike_version(ver1.version, ver2.version)

versions.sort(key=functools.cmp_to_key(compare), reverse=reverse)
versions.sort(key=functools.cmp_to_key(compare_mike_version), reverse=reverse)
return versions
Loading

0 comments on commit 8bf6c38

Please sign in to comment.