6501920
#! /usr/bin/python3 -s
6501920
# Stripped down replacement for cargo2rpm parse-vendor-manifest
6501920
6501920
import re
6501920
import subprocess
6501920
import sys
6501920
from typing import Optional
6501920
6501920
6501920
VERSION_REGEX = re.compile(
6501920
    r"""
6501920
    ^
6501920
    (?P<major>0|[1-9]\d*)
6501920
    \.(?P<minor>0|[1-9]\d*)
6501920
    \.(?P<patch>0|[1-9]\d*)
6501920
    (?:-(?P
(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?
6501920
    (?:\+(?P<build>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$
6501920
    """,
6501920
    re.VERBOSE,
6501920
)
6501920
6501920
6501920
class Version:
6501920
    """
6501920
    Version that adheres to the "semantic versioning" format.
6501920
    """
6501920
6501920
    def __init__(self, major: int, minor: int, patch: int, pre: Optional[str] = None, build: Optional[str] = None):
6501920
        self.major: int = major
6501920
        self.minor: int = minor
6501920
        self.patch: int = patch
6501920
        self.pre: Optional[str] = pre
6501920
        self.build: Optional[str] = build
6501920
6501920
    @staticmethod
6501920
    def parse(version: str) -> "Version":
6501920
        """
6501920
        Parses a version string and return a `Version` object.
6501920
        Raises a `ValueError` if the string does not match the expected format.
6501920
        """
6501920
6501920
        match = VERSION_REGEX.match(version)
6501920
        if not match:
6501920
            raise ValueError(f"Invalid version: {version!r}")
6501920
6501920
        matches = match.groupdict()
6501920
6501920
        major_str = matches["major"]
6501920
        minor_str = matches["minor"]
6501920
        patch_str = matches["patch"]
6501920
        pre = matches["pre"]
6501920
        build = matches["build"]
6501920
6501920
        major = int(major_str)
6501920
        minor = int(minor_str)
6501920
        patch = int(patch_str)
6501920
6501920
        return Version(major, minor, patch, pre, build)
6501920
6501920
    def to_rpm(self) -> str:
6501920
        """
6501920
        Formats the `Version` object as an equivalent RPM version string.
6501920
        Characters that are invalid in RPM versions are replaced ("-" -> "_")
6501920
6501920
        Build metadata (the optional `Version.build` attribute) is dropped, so
6501920
        the conversion is not lossless for versions where this attribute is not
6501920
        `None`. However, build metadata is not intended to be part of the
6501920
        version (and is not even considered when doing version comparison), so
6501920
        dropping it when converting to the RPM version format is correct.
6501920
        """
6501920
6501920
        s = f"{self.major}.{self.minor}.{self.patch}"
6501920
        if self.pre:
6501920
            s += f"~{self.pre.replace('-', '_')}"
6501920
        return s
6501920
6501920
6501920
def break_the_build(error: str):
6501920
    """
6501920
    This function writes a string that is an invalid RPM dependency specifier,
6501920
    which causes dependency generators to fail and break the build. The
6501920
    additional error message is printed to stderr.
6501920
    """
6501920
6501920
    print("*** FATAL ERROR ***")
6501920
    print(error, file=sys.stderr)
6501920
6501920
 
6501920
def get_cargo_vendor_txt_paths_from_stdin() -> set[str]:  # pragma nocover
6501920
    """
6501920
    Read lines from standard input and filter out lines that look like paths
6501920
    to `cargo-vendor.txt` files. This is how RPM generators pass lists of files.
6501920
    """
6501920
6501920
    lines = {line.rstrip("\n") for line in sys.stdin.readlines()}
6501920
    return {line for line in lines if line.endswith("/cargo-vendor.txt")}
6501920
6501920
6501920
def action_parse_vendor_manifest():
6501920
    paths = get_cargo_vendor_txt_paths_from_stdin()
6501920
6501920
    for path in paths:
6501920
        with open(path) as file:
6501920
            manifest = file.read()
6501920
6501920
        for line in manifest.strip().splitlines():
6501920
            crate, version = line.split(" v")
6501920
            print(f"bundled(crate({crate})) = {Version.parse(version).to_rpm()}")
6501920
6501920
6501920
def main():
6501920
    try:
6501920
        action_parse_vendor_manifest()
6501920
        exit(0)
6501920
6501920
    # print an error message that is not a valid RPM dependency
6501920
    # to cause the generator to break the build
6501920
    except (IOError, ValueError) as exc:
6501920
        break_the_build(str(exc))
6501920
        exit(1)
6501920
6501920
    break_the_build("Uncaught exception: This should not happen, please report a bug.")
6501920
    exit(1)
6501920
6501920
6501920
if __name__ == "__main__":
6501920
    main()