Skip to content

Commit

Permalink
Allow semver parsing to fall back on more generic parser
Browse files Browse the repository at this point in the history
  • Loading branch information
steinwurf-sofie committed Sep 26, 2024
1 parent 69c38a6 commit eb71b20
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 15 deletions.
70 changes: 55 additions & 15 deletions src/wurf/semver_selector.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
#! /usr/bin/env python
# encoding: utf-8

import re


BASEVERSION = re.compile(
r"""[vV]?
(?P<major>0|[1-9]\d*)
(\.
(?P<minor>0|[1-9]\d*)
(\.
(?P<patch>0|[1-9]\d*)
)?
)?
""",
re.VERBOSE,
)

class SemverSelector(object):
"""
Expand All @@ -27,32 +42,57 @@ def select_tag(self, major, tags):
"""
assert isinstance(major, int), "Major version is not an int"

valid_tags = []
valid_tags_ver = dict()

# Get tags with a matching major version
for tag in tags:
for tag_str in tags:
try:
t = self.semver.parse(tag)
if t["major"] != major:
continue

valid_tags.append(tag)
except ValueError: # ignore tags we cannot parse
pass
tag_ver = self.semver.VersionInfo.parse(tag_str)
except ValueError:
# Version might be prefixed with `v` or otherwise not be semver
# compatible. Try to find a simpler `major.minor.patch` match
# somewhere in the tag as a last resort.
tag_ver = self._semver_coerce(tag_str)
if tag_ver and tag_ver.major == major:
valid_tags_ver[tag_ver] = tag_str

if len(valid_tags) == 0:
if not valid_tags_ver:
return None

# Now figure out which version is the newest.
# We only use tags that have the specified major version to ensure
# compatibility, see rules at semver.org
best_match = valid_tags[0]
best_match_ver = max(valid_tags_ver.keys())

# Return original string representation of version tag.
return valid_tags_ver[best_match_ver]

def _semver_coerce(self, version):
"""Convert an incomplete version string into a semver-compatible Version
object
for t in valid_tags:
if self.semver.match(best_match, "<" + t):
best_match = t
* Tries to detect a "basic" version string (``major.minor.patch``).
* If not enough components can be found, missing components are
set to zero to obtain a valid semver version.
Copyright (c) 2013, Konstantine Rybnikov
Original: https://github.com/python-semver/python-semver/blob/master/docs/advanced/coerce.py
:param str version: the version string to convert
:return: a :class:`Version` instance (or ``None`` if it's not a version)
:rtype: :class:`Version` | None
"""
# Changed: using `match` instead of `search` to avoid situation where
# a tag simply containing a number inside it is parsed as a major
# version.
match = BASEVERSION.match(version)
if not match:
return None

return best_match
ver = {
key: 0 if value is None else int(value) for key, value in match.groupdict().items()
}
return self.semver.VersionInfo(**ver)

def __repr__(self):
"""
Expand Down
11 changes: 11 additions & 0 deletions test/python/test_semver_selector.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ def test_semver_selector():
"3.0.0",
"3.0.0-lts.0",
"3.0.0-lts.1",
"backups/3.1.0-lts.0", # Do not match non-toplevel tags.
"4.0.0",
"v4.0.1",
"5.0.0",
"5.1",
]

# Select latest tag for major version 1
Expand All @@ -27,3 +32,9 @@ def test_semver_selector():

# Select latest tag for major version 3 (LTS tags should be ignored)
assert selector.select_tag(major=3, tags=tags) == "3.0.0"

# Select latest tag for major version 4 (v prefix should be ignored)
assert selector.select_tag(major=4, tags=tags) == "v4.0.1"

# Select latest tag for major version 5 (missing minor should be ignored)
assert selector.select_tag(major=5, tags=tags) == "5.1"

0 comments on commit eb71b20

Please sign in to comment.