diff --git a/setup.py b/setup.py index e992031..b1b6f15 100644 --- a/setup.py +++ b/setup.py @@ -28,7 +28,7 @@ def get_version(filename): requirements = [ 'click >= 6.7', 'jinja2', - 'packaging < 22.0', + 'packaging', 'pyparsing >= 2.0.2', 'setuptools', 'sphinx', diff --git a/src/docs_versions_menu/folder_spec.py b/src/docs_versions_menu/folder_spec.py index 49d7730..aefcdc3 100644 --- a/src/docs_versions_menu/folder_spec.py +++ b/src/docs_versions_menu/folder_spec.py @@ -2,7 +2,6 @@ from collections import OrderedDict from functools import partial -from packaging.version import parse as parse_version from pyparsing import ( Forward, Group, @@ -17,6 +16,8 @@ oneOf, ) +from .parse_version import parse_version + def _parse_folder_spec(spec, groups, sort_key): """Parse the folder specification into a nested list. diff --git a/src/docs_versions_menu/groups.py b/src/docs_versions_menu/groups.py index 6bde6a7..64867ac 100644 --- a/src/docs_versions_menu/groups.py +++ b/src/docs_versions_menu/groups.py @@ -1,6 +1,6 @@ """Classification of folders into groups according to :pep:`440`.""" -from packaging.version import LegacyVersion -from packaging.version import parse as parse_version + +from .parse_version import NonVersionFolderName, parse_version def get_groups(folders, default_branches=None): @@ -51,7 +51,7 @@ def get_groups(folders, default_branches=None): version = parse_version(folder) if folder in default_branches: groups['default-branch'].add(folder) - if isinstance(version, LegacyVersion): + if isinstance(version, NonVersionFolderName): groups['branches'].add(folder) else: groups['releases'].add(folder) diff --git a/src/docs_versions_menu/parse_version.py b/src/docs_versions_menu/parse_version.py new file mode 100644 index 0000000..2983670 --- /dev/null +++ b/src/docs_versions_menu/parse_version.py @@ -0,0 +1,43 @@ +"""Parse a version-based foldeer name. + +The ``parse_version`` function provided here is functionally equivalent to +``packaging.version.parse`` in ``packaging < 22.0``. +""" +import packaging.version + + +class NonVersionFolderName(packaging.version._BaseVersion): + """A "version" that is just an arbitrary folder name""" + + def __init__(self, name): + name = str(name) + self.name = name + self._key = (-1, (f'*{name}', '*final')) + # The _key mimics the _key of a LegacyVersion in packaging < 22.0. + # The "-1" is a hard-coded "epoch" here. A PEP 440 version can only + # have a epoch greater than or equal to 0. This will sort + # NonVersionFolderName before any PEP 440 version. + # + # Sorting behavior is inherited from _BaseVersion + + def __str__(self): + return self.name + + def __repr__(self): + return f"" + + +class VersionFolderName(packaging.version.Version): + """A PEP440-compatible version name.""" + + def __repr__(self): + return f"" + + +def parse_version(name): + """Parse `name` string into either a `VersionFolderName` or a + `NonVersionFolderName` object.""" + try: + return VersionFolderName(name) + except packaging.version.InvalidVersion: + return NonVersionFolderName(name) diff --git a/src/docs_versions_menu/version_data.py b/src/docs_versions_menu/version_data.py index 65b2aac..00140e4 100644 --- a/src/docs_versions_menu/version_data.py +++ b/src/docs_versions_menu/version_data.py @@ -4,7 +4,6 @@ from pathlib import Path import jinja2 -from packaging.version import parse as parse_version from .folder_spec import resolve_folder_spec from .groups import get_groups @@ -12,7 +11,6 @@ def get_version_data( *, - sort_key=None, suffix_latest, default_branch_spec, versions_spec, @@ -23,8 +21,6 @@ def get_version_data( ): """Get the versions data, to be serialized to json.""" logger = logging.getLogger(__name__) - if sort_key is None: - sort_key = parse_version folders = sorted( [ diff --git a/tests/test_parse_version.py b/tests/test_parse_version.py new file mode 100644 index 0000000..48897e5 --- /dev/null +++ b/tests/test_parse_version.py @@ -0,0 +1,75 @@ +"""Test the parse_version function""" +from docs_versions_menu.parse_version import parse_version + + +def test_parse_version(): + """Test that :func:`parse_version` behaves like ``packaging.version.parse` + in ``packaging < 22.0``. + """ + versions = [ + "1.0", + "0.8.3", + "v0.1.0", + "v0.2.1", + "v0.2.1+dev", + "v0.2.1-dev", + "v0.2.1-rc1", + "v0.2.1-rc2", + "main", + "master", + "fix-bug", + ] + for version in versions: + assert parse_version(version) is not None # should not error + sorted_versions = sorted(versions, key=parse_version) + assert sorted_versions == [ + 'fix-bug', + 'main', + 'master', + 'v0.1.0', + 'v0.2.1-dev', + 'v0.2.1-rc1', + 'v0.2.1-rc2', + 'v0.2.1', + 'v0.2.1+dev', + '0.8.3', + '1.0', + ] + + +def test_non_version_folder_name(): + """Test the behavior of NonVersionFolderName inherited from _BaseVersion""" + v1 = parse_version("branch-a") + v1_copy = parse_version("branch-a") + v2 = parse_version("branch_b") + v3 = parse_version("0.0.1-dev") + + assert repr(v1) == "" + + assert repr(v3) == "" + # Note the normalization! + + # __lt__ + assert v1 < v2 + assert v1 < v3 + assert not (v3 < v1) + # __le__ + assert v1 <= v2 + assert v1 <= v1_copy + assert not (v3 <= v1) + # __eq__ + assert v1 == v1_copy + assert not (v1 == v3) + assert not (v3 == v1) + # __ge__ + assert v2 >= v1 + assert v3 >= v1 + assert v1_copy >= v1 + # __gt__ + assert v2 > v1 + assert v3 > v1 + assert not (v1 > v3) + # __ne__ + assert v1 != v2 + assert v1 != v3 + assert v3 != v1 diff --git a/tox.ini b/tox.ini index 9b29582..617065e 100644 --- a/tox.ini +++ b/tox.ini @@ -22,6 +22,10 @@ envdir = py38: {toxworkdir}/py38 py37: {toxworkdir}/py37 deps = + py37: packaging<22.0 + py38: packaging>=22.0 + py39: packaging>=22.0 + py310: packaging>=22.0 usedevelop = true extras= dev