Source code for kojismokydingo.rpm

# This library is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This library is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this library; if not, see <http://www.gnu.org/licenses/>.


"""
Koji Smoky Dingo - RPM NEVRA Utils

:author: Christopher O'Brien <obriencj@gmail.com>
:license: GPL v3
"""


import re

from itertools import zip_longest
from typing import Tuple, cast


__all__ = (
    "evr_compare",
    "evr_split",
    "nevr_split",
    "nevra_split",
)


_rpm_str_split_re = re.compile(r"([~^]?(?:\d+|[a-zA-Z]+))").split


def _rpm_str_split(s: str) -> Tuple[str]:
    """
    Split an E, V, or R string for comparison by its segments
    """

    return tuple(i for i in _rpm_str_split_re(s)  # type: ignore
                 if i and (i.isalnum() or (i[0] in "~^")))


def _rpm_str_compare(leftstr: str, rightstr: str) -> int:
    """
    Comparison of left and right by RPM version comparison rules.

    Either string should be *one* element of the EVR tuple (ie. either the
    epoch, version, or release). Comparison will split the element on RPM's
    special delimeters.

    :since: 2.0
    """

    left: Tuple[str] = _rpm_str_split(leftstr)
    right: Tuple[str] = _rpm_str_split(rightstr)

    lp: str
    rp: str

    for lp, rp in zip_longest(left, right, fillvalue=""):

        # Special comparison for tilde segments
        if lp.startswith("~"):
            # left is tilde

            if rp.startswith("~"):
                # right also is tilde, let's just chop off the tilde
                # and fall through to non-tilde comparisons below

                lp = lp[1:]
                rp = rp[1:]

            else:
                # right is not tilde, therefore right is greater
                return -1

        elif rp.startswith("~"):
            # left is not tilde, but right is, therefore left is greater
            return 1

        elif lp.startswith("^"):
            # left is caret

            if rp.startswith("^"):
                lp = lp[1:]
                rp = rp[1:]

            else:
                return -1 if rp else 1

        elif rp.startswith("^"):
            # left is not caret, nor tilde, but right is caret
            return 1 if lp else -1

        # Special comparison for digits vs. alphabetical
        if lp.isdigit():
            # left is numeric

            if rp.isdigit():
                # left and right are both numeric, convert and fall
                # through
                ilp = int(lp)
                irp = int(rp)
                if ilp == irp:
                    continue
                else:
                    return 1 if ilp > irp else -1

            else:
                # right is alphabetical or absent, left is greater
                return 1

        elif rp.isdigit():
            # left is alphabetical but right is not, right is greater
            return -1

        # Final comparison for segment
        if lp == rp:
            # left and right are equivalent, check next segment
            continue
        else:
            # left and right are not equivalent
            return 1 if lp > rp else -1

    else:
        # ran out of segments to check, must be equivalent
        return 0


[docs] def evr_compare( left_evr: Tuple[str, str, str], right_evr: Tuple[str, str, str]) -> int: """ Compare two (Epoch, Version, Release) tuples. This is an alternative implementation of the rpm lib's labelCompare function. Return values: * 1 if left_evr is greater-than right_evr * 0 if left_evr is equal-to right_evr * -1 if left_evr is less-than right_evr :param left_evr: The left Epoch, Version, Release for comparison :param right_evr: The right Epoch, Version, Release for comparison :since: 2.0 """ for lp, rp in zip_longest(left_evr, right_evr, fillvalue="0"): if lp == rp: # fast check to potentially skip all the matching continue compared = _rpm_str_compare(lp, rp) if compared: # non zero comparison for segment, done checking return compared else: # ran out of segments to check, must be equivalent return 0
[docs] def nevra_split(nevra: str) -> Tuple[str, str, str, str, str]: """ Splits an NEVRA into a five-tuple representing the name, epoch, version, release, and arch. If name, epoch, arch, or release are absent, they are represented as a ``None`` This differs from NEVRA splitting in that the last dotted segment of the release is considered to be the architecture. Because there may be 1 or more dotted segments to a release, it's impossible to determine whether the final segment is an arch or not simply from the structure. However, a valid NEVRA will always have at least two segments -- the last will be the architecture. Valid RPM NEVRA is in the following layout: eg. ``"bind-32:9.10.2-2.P1.fc22.x86_64"`` * name: ``"bind"`` * epoch: ``"32"`` * version: ``"9.10.2"`` * release: ``"2.P1.fc22"`` * arch: ``"x86_64"`` :since: 2.0 """ name, epoch, version, release = nevr_split(nevra) if release and "." in release: release, arch = release.rsplit(".", 1) else: arch = None return name, epoch, version, release, arch
[docs] def nevr_split(nevr: str) -> Tuple[str, str, str, str]: """ Splits an NEVR into a four-tuple represending the name, epoch, version, and release. If name, epoch, or release are absent they are represented as ``None`` This differs from NEVRA splitting in that the last dotted segment of the release is not considered an architecture. Because there may be 1 or more dotted segments to a release, it's impossible to determine whether the final segment is an arch or not simply from the structure. Valid RPM NEVRA is in the following layout: eg. ``"bind-32:9.10.2-2.P1.fc22"`` * name: ``"bind"`` * epoch: ``"32"`` * version: ``"9.10.2"`` * release: ``"2.P1.fc22"`` :since: 2.0 """ epoch, version, release = evr_split(nevr) if epoch: if "-" in epoch: name, epoch = epoch.rsplit("-", 1) else: name = None else: name = version if release and "-" in release: version, release = release.split("-", 1) else: version = release release = None return name, epoch, version, release
[docs] def evr_split(evr: str) -> Tuple[str, str, str]: """ Splits an EVR into a dict with the keys epoch, version, and release. If epoch is omitted, it is presumed to be ``"0"`` If release is omitted, it is presumed to be ``None`` Valid RPM EVR is in the following layout: eg. ``"32:9.10.2-2.P1.fc22"`` * epoch: ``"32"`` * version: ``"9.10.2"`` * release: ``"2.P1.fc22"`` :since: 2.0 """ version = evr if ":" in version: epoch, version = version.split(":", 1) else: epoch = None if "-" in version: version, release = version.split("-", 1) else: release = None return epoch, version, release
# # The end.