Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor gem and make nuget hashable #75

Merged
merged 2 commits into from
Apr 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 48 additions & 75 deletions src/univers/gem.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,19 +199,14 @@ def __init__(self, version):
if not self.is_correct(version):
raise InvalidVersionError(version)

# If version is an empty string convert it to 0
version = str(version).strip()

self.original = version

# If version is an empty string convert it to 0
if not version:
version = "0"

self.version = version.replace("-", ".pre.")
self._segments = ()
self._canonical_segments = ()
self._bump = None
self._release = None

def __str__(self):
return self.original
Expand All @@ -225,7 +220,7 @@ def equal_strictly(self, other):
return self.version == other.version

def __hash__(self):
return hash(tuple(self.canonical_segments))
return hash(self.canonical_segments)

def __eq__(self, other):
return self.canonical_segments == other.canonical_segments
Expand All @@ -242,6 +237,32 @@ def __gt__(self, other):
def __ge__(self, other):
return self.__cmp__(other) >= 0

@property
def segments(self):
keshav-space marked this conversation as resolved.
Show resolved Hide resolved
"""
Returns segments for this version where segments are
ints or strings parsed from the original version string.
"""
segments = []
find_segments = re.compile(r"[0-9]+|[a-z]+", re.IGNORECASE).findall
for seg in find_segments(self.version):
sub_segments = [int(seg) if seg.isdigit() else seg]
segments.extend(sub_segments)
return tuple(segments)

@property
def canonical_segments(self):
"""
Returns "canonical segments" for this version using
the Rubygems way for canonicalization.
"""
canonical_segments = []
for segments in self.split_segments():
# drop the section preceding the least significant zero (if exists)
segs = list(dropwhile(lambda s: s == 0, reversed(segments)))
keshav-space marked this conversation as resolved.
Show resolved Hide resolved
canonical_segments.extend(reversed(segs))
return tuple(canonical_segments)

def bump(self):
"""
Return a new version object where the next to the last revision number
Expand All @@ -252,40 +273,33 @@ def bump(self):
>>> assert GemVersion("5.3.1").bump() == GemVersion("5.4"), repr(GemVersion("5.3.1").bump())
>>> assert GemVersion("5.3.1.4-2").bump() == GemVersion("5.3.2"), GemVersion("5.3.1.4-2").bump()
"""
if not self._bump:
segments = []
for seg in self.segments:
if isinstance(seg, str):
break
else:
segments.append(seg)

if len(segments) > 1:
segments.pop()
segments = []
for seg in self.segments:
if isinstance(seg, str):
break
else:
segments.append(seg)

segments[-1] += 1
segments = [str(r) for r in segments]
self._bump = GemVersion(".".join(segments))
if len(segments) > 1:
segments.pop()

return self._bump
segments[-1] += 1
segments = [str(r) for r in segments]
return GemVersion(".".join(segments))

def release(self):
"""
Return a new GemVersion which is the release for this version (e.g.,
1.2.0.a -> 1.2.0). Non-prerelease versions return themselves. A release
is composed only of numeric segments.
"""
if not self._release:
if self.prerelease():
segments = self.segments
while any(isinstance(s, str) for s in segments):
segments.pop()
segments = (str(s) for s in segments)
self._release = GemVersion(".".join(segments))
else:
self._release = self

return self._release
if self.prerelease():
segments = list(self.segments)
while any(isinstance(s, str) for s in segments):
segments.pop()
segments = (str(s) for s in segments)
return GemVersion(".".join(segments))
return self

def prerelease(self):
"""
Expand All @@ -294,47 +308,6 @@ def prerelease(self):
"""
return any(not str(s).isdigit() for s in self.segments)

@property
def segments(self):
"""
Return a new sequence of segments for this version where segments are
ints or strings parsed from the original version string.
"""
if not self._segments:
self._segments = self.get_segments()
return list(self._segments)

def get_segments(self):
"""
Return a sequence of segments for this version where segments are ints
or strings parsed from the original version string.
"""
find_segments = re.compile(r"[0-9]+|[a-z]+", re.IGNORECASE).findall
segments = []
for seg in find_segments(self.version):
if seg.isdigit():
seg = int(seg)
segments.append(seg)
return tuple(segments)

@property
def canonical_segments(self):
if not self._canonical_segments:
self._canonical_segments = self.get_canonical_segments()
return tuple(self._canonical_segments)

def get_canonical_segments(self):
"""
Return a new sequence of "canonical segments" for this version using
the Rubygems way.
"""
canonical_segments = []
for segments in self.split_segments():
segs = list(dropwhile(lambda s: s == 0, reversed(segments)))
segs = reversed(segs)
canonical_segments.extend(segs)
return tuple(canonical_segments)

def split_segments(self):
"""
Return a two-tuple of segments:
Expand Down Expand Up @@ -380,11 +353,11 @@ def __cmp__(self, other, trace=False):
if self.version == other.version:
return 0

lhsegments = self.canonical_segments
lhsegments = list(self.canonical_segments)
if trace:
print(f" lhsegments: canonical_segments: {lhsegments!r}")

rhsegments = other.canonical_segments
rhsegments = list(other.canonical_segments)
if trace:
print(f" rhsegments: canonical_segments: {rhsegments!r}")

Expand Down
8 changes: 8 additions & 0 deletions src/univers/nuget.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,14 @@ def __lt__(self, other):
# Revision is the same, so ignore it for comparison purposes.
return self._base_semver < other._base_semver

def __hash__(self):
return hash(
(
self._base_semver.to_tuple(),
self._revision,
)
)

@classmethod
def from_string(cls, str_version):
if not str_version:
Expand Down
11 changes: 11 additions & 0 deletions tests/test_nuget.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import unittest

from univers.nuget import Version
from univers.versions import NugetVersion


Expand Down Expand Up @@ -77,3 +78,13 @@ def test_less(self):
self.check_order(self.assertLess, "1.0.0-pre", "1.0.0.1-alpha")
self.check_order(self.assertLess, "1.0.0", "1.0.0.1-alpha")
self.check_order(self.assertLess, "0.9.9.1", "1.0.0")

def test_NugetVersion_hash(self):
vers1 = NugetVersion("1.0.1+23")
vers2 = NugetVersion("1.0.1+23")
assert hash(vers1) == hash(vers2)

def test_nuget_semver_hash(self):
vers1 = Version.from_string("51.0.0+2")
vers2 = Version.from_string("51.0.0+2")
assert hash(vers1) == hash(vers2)
17 changes: 17 additions & 0 deletions tests/test_python_semver.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#
# Copyright (c) nexB Inc. and others.
# SPDX-License-Identifier: Apache-2.0
#
# Visit https://aboutcode.org and https://github.com/nexB/univers for support and download.

from unittest import TestCase

import semver


class TestPythonSemver(TestCase):
def test_semver_hash(self):
# python-semver doesn't consider build while hashing
vers1 = semver.VersionInfo.parse("1.2.3")
vers2 = semver.VersionInfo.parse("1.2.3+1")
assert hash(vers1) == hash(vers2)
2 changes: 1 addition & 1 deletion tests/test_rubygems_gem_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ def test_segments():
secondseg += 1

refute_version_eql("9.8.8", "9.8.7")
assert GemVersion("9.8.7").segments == [9, 8, 7]
assert GemVersion("9.8.7").segments == (9, 8, 7)


def test_split_segments():
Expand Down