Loading sbom_scanner/scan.py +99 −1 Original line number Diff line number Diff line Loading @@ -2,6 +2,7 @@ import argparse import glob import json import os import re import ssl import sys from enum import Enum Loading @@ -13,7 +14,6 @@ from typing import Optional import requests from cyclonedx.model.bom import Bom from cyclonedx.schema import SchemaVersion from packaging.version import Version from sbom_scanner import sbom_utils from sbom_scanner.AnsiColors import AnsiColors Loading Loading @@ -90,6 +90,104 @@ class DtProjectDef: return self.definition.split("@")[1] if "@" in self.definition else None class Version: def __init__(self, version_str): self.version_str = version_str self.major, self.minor, self.patch, self.prerelease, self.build = self._parse( version_str ) def _parse(self, version_str): regex = r"^(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)(?:-(?P<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P<build>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$" match = re.match(regex, version_str) if match: major = int(match.group("major")) minor = int(match.group("minor")) patch = int(match.group("patch")) prerelease = match.group("prerelease") build = match.group("build") return major, minor, patch, prerelease, build else: raise ValueError(f"Invalid semantic version: {version_str}") def __str__(self): version_str = f"{self.major}.{self.minor}.{self.patch}" if self.prerelease: version_str += f"-{'.'.join(self.prerelease)}" if self.build: version_str += f"+{'.'.join(self.build)}" return version_str def __lt__(self, other): return self._compare(other) < 0 def __le__(self, other): return self._compare(other) <= 0 def __eq__(self, other): return self._compare(other) == 0 def __ge__(self, other): return self._compare(other) >= 0 def __gt__(self, other): return self._compare(other) > 0 def __ne__(self, other): return self._compare(other) != 0 def _compare(self, other): if not isinstance(other, Version): other = Version(str(other)) if self.major != other.major: return 1 if self.major > other.major else -1 if self.minor != other.minor: return 1 if self.minor > other.minor else -1 if self.patch != other.patch: return 1 if self.patch > other.patch else -1 # Handle pre-release versions if self.prerelease and other.prerelease: self_prerelease = [ self._parse_prerelease(x) for x in self.prerelease.split(".") ] other_prerelease = [ self._parse_prerelease(x) for x in other.prerelease.split(".") ] for i in range(min(len(self_prerelease), len(other_prerelease))): if self_prerelease[i] != other_prerelease[i]: return 1 if self_prerelease[i] > other_prerelease[i] else -1 return 1 if len(self_prerelease) > len(other_prerelease) else -1 elif self.prerelease: return -1 elif other.prerelease: return 1 # Handle build metadata if self.build and other.build: self_build = [int(x) if x.isdigit() else x for x in self.build.split(".")] other_build = [int(x) if x.isdigit() else x for x in other.build.split(".")] for i in range(min(len(self_build), len(other_build))): if self_build[i] != other_build[i]: return 1 if self_build[i] > other_build[i] else -1 return 1 if len(self_build) > len(other_build) else -1 elif self.build: return 1 elif other.build: return -1 return 0 def _parse_prerelease(self, prerelease_str): digits = "".join(c for c in prerelease_str if c.isdigit()) alpha = "".join(c for c in prerelease_str if not c.isdigit()) if digits: return int(digits), alpha else: return 0, alpha class Scanner: def __init__( self, Loading Loading
sbom_scanner/scan.py +99 −1 Original line number Diff line number Diff line Loading @@ -2,6 +2,7 @@ import argparse import glob import json import os import re import ssl import sys from enum import Enum Loading @@ -13,7 +14,6 @@ from typing import Optional import requests from cyclonedx.model.bom import Bom from cyclonedx.schema import SchemaVersion from packaging.version import Version from sbom_scanner import sbom_utils from sbom_scanner.AnsiColors import AnsiColors Loading Loading @@ -90,6 +90,104 @@ class DtProjectDef: return self.definition.split("@")[1] if "@" in self.definition else None class Version: def __init__(self, version_str): self.version_str = version_str self.major, self.minor, self.patch, self.prerelease, self.build = self._parse( version_str ) def _parse(self, version_str): regex = r"^(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)(?:-(?P<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P<build>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$" match = re.match(regex, version_str) if match: major = int(match.group("major")) minor = int(match.group("minor")) patch = int(match.group("patch")) prerelease = match.group("prerelease") build = match.group("build") return major, minor, patch, prerelease, build else: raise ValueError(f"Invalid semantic version: {version_str}") def __str__(self): version_str = f"{self.major}.{self.minor}.{self.patch}" if self.prerelease: version_str += f"-{'.'.join(self.prerelease)}" if self.build: version_str += f"+{'.'.join(self.build)}" return version_str def __lt__(self, other): return self._compare(other) < 0 def __le__(self, other): return self._compare(other) <= 0 def __eq__(self, other): return self._compare(other) == 0 def __ge__(self, other): return self._compare(other) >= 0 def __gt__(self, other): return self._compare(other) > 0 def __ne__(self, other): return self._compare(other) != 0 def _compare(self, other): if not isinstance(other, Version): other = Version(str(other)) if self.major != other.major: return 1 if self.major > other.major else -1 if self.minor != other.minor: return 1 if self.minor > other.minor else -1 if self.patch != other.patch: return 1 if self.patch > other.patch else -1 # Handle pre-release versions if self.prerelease and other.prerelease: self_prerelease = [ self._parse_prerelease(x) for x in self.prerelease.split(".") ] other_prerelease = [ self._parse_prerelease(x) for x in other.prerelease.split(".") ] for i in range(min(len(self_prerelease), len(other_prerelease))): if self_prerelease[i] != other_prerelease[i]: return 1 if self_prerelease[i] > other_prerelease[i] else -1 return 1 if len(self_prerelease) > len(other_prerelease) else -1 elif self.prerelease: return -1 elif other.prerelease: return 1 # Handle build metadata if self.build and other.build: self_build = [int(x) if x.isdigit() else x for x in self.build.split(".")] other_build = [int(x) if x.isdigit() else x for x in other.build.split(".")] for i in range(min(len(self_build), len(other_build))): if self_build[i] != other_build[i]: return 1 if self_build[i] > other_build[i] else -1 return 1 if len(self_build) > len(other_build) else -1 elif self.build: return 1 elif other.build: return -1 return 0 def _parse_prerelease(self, prerelease_str): digits = "".join(c for c in prerelease_str if c.isdigit()) alpha = "".join(c for c in prerelease_str if not c.isdigit()) if digits: return int(digits), alpha else: return 0, alpha class Scanner: def __init__( self, Loading