From 0684925001b54348b382ef636cc004467cf4015c Mon Sep 17 00:00:00 2001 From: Ole Herman Schumacher Elgesem Date: Fri, 2 Jan 2026 20:51:33 +0100 Subject: [PATCH] Moved reusable version functions into utils Signed-off-by: Ole Herman Schumacher Elgesem --- cfbs/analyze.py | 8 +- cfbs/commands.py | 2 +- cfbs/masterfiles/analyze.py | 99 +------------------ .../masterfiles/check_download_matches_git.py | 9 +- cfbs/masterfiles/download.py | 10 +- .../generate_release_information.py | 3 +- cfbs/utils.py | 99 +++++++++++++++++++ 7 files changed, 120 insertions(+), 110 deletions(-) diff --git a/cfbs/analyze.py b/cfbs/analyze.py index 0341bb4e..d9fe6c4e 100644 --- a/cfbs/analyze.py +++ b/cfbs/analyze.py @@ -4,11 +4,6 @@ import copy from cfbs.internal_file_management import fetch_archive -from cfbs.masterfiles.analyze import ( - highest_version, - sort_versions, - version_as_comparable_list, -) from cfbs.utils import ( CFBSNetworkError, cfbs_dir, @@ -16,9 +11,12 @@ fetch_url, file_sha256, get_json, + highest_version, immediate_subdirectories, mkdir, read_json, + sort_versions, + version_as_comparable_list, CFBSExitError, ) diff --git a/cfbs/commands.py b/cfbs/commands.py index de5ec285..279a2801 100644 --- a/cfbs/commands.py +++ b/cfbs/commands.py @@ -59,7 +59,6 @@ def search_command(terms: List[str]): from cfbs.cfbs_json import CFBSJson from cfbs.cfbs_types import CFBSCommandExitCode, CFBSCommandGitResult -from cfbs.masterfiles.analyze import most_relevant_version from cfbs.masterfiles.download import download_single_version from cfbs.updates import ModuleUpdates, update_module from cfbs.utils import ( @@ -72,6 +71,7 @@ def search_command(terms: List[str]): file_diff_text, is_cfbs_repo, mkdir, + most_relevant_version, read_json, CFBSExitError, save_file, diff --git a/cfbs/masterfiles/analyze.py b/cfbs/masterfiles/analyze.py index 5f9a7afb..b56da27e 100644 --- a/cfbs/masterfiles/analyze.py +++ b/cfbs/masterfiles/analyze.py @@ -1,8 +1,7 @@ from collections import OrderedDict import os -from typing import Iterable, List -from cfbs.utils import dict_sorted_by_key, file_sha256 +from cfbs.utils import dict_sorted_by_key, file_sha256, version_as_comparable_list Version = str @@ -92,99 +91,3 @@ def finalize_vcf(versions_dict, checksums_dict, files_dict): ) return versions_dict, checksums_dict, files_dict - - -def version_as_comparable_list(version: str): - """Also supports versions containing exactly one of `b` or `-`. - - Example of the version ordering: `3.24.0b1 < 3.24.0 < 3.24.0-1`. - - Examples: - * `version_as_comparable_list("3.24.0b1")` is `[[3, 24, 0], [-1, 1]]` - * `version_as_comparable_list("3.24.0-2")` is `[[3, 24, 0], [1, 2]]` - * `version_as_comparable_list("3.24.x")` is `[[3, 24, 99999], [0, 0]]`""" - if version == "master": - version = "x" - - if "b" not in version: - if "-" not in version: - version += "|0.0" - version = version.replace("x", "99999").replace("-", "|1.").replace("b", "|-1.") - versionpair = version.split("|") - versions_str = [versionpair[0].split("."), versionpair[1].split(".")] - - versions_int = [ - [int(s) for s in versions_str[0]], - [int(s) for s in versions_str[1]], - ] - - return versions_int - - -def version_as_comparable_list_negated(version): - vcl = version_as_comparable_list(version) - - vcl[0] = [-x for x in vcl[0]] - vcl[1] = [-x for x in vcl[1]] - - return vcl - - -def version_is_at_least(version, min_version): - return min_version is None or ( - version_as_comparable_list(version) >= version_as_comparable_list(min_version) - ) - - -def sort_versions(versions: list, reverse: bool = True): - """Sorts a list of versions, in descending order by default.""" - versions.sort( - key=version_as_comparable_list, - reverse=reverse, - ) - - -def highest_version(versions: Iterable[Version]): - return max(versions, key=version_as_comparable_list, default=None) - - -def lowest_version(versions: Iterable[Version]): - return min(versions, key=version_as_comparable_list, default=None) - - -def is_lower_version(version_a: Version, version_b: Version): - """Returns `True` if and only if `version_a` is lower than `version_b`.""" - - return version_as_comparable_list(version_a) < version_as_comparable_list(version_b) - - -def most_relevant_version( - other_versions: List[Version], reference_version: Version -) -> Version: - """ - The most relevant version is the highest version among older other versions, - or if there are no older other versions, the lowest version among other versions. - - `other_versions` is assumed to be non-empty and not contain `reference_version`.""" - assert len(other_versions) > 0 - assert reference_version not in other_versions - - highest_other_version = highest_version(other_versions) - lowest_other_version = lowest_version(other_versions) - # Unfortunately, Pyright can not infer that these can't be `None` when `other_versions` is non-empty. - assert highest_other_version is not None - assert lowest_other_version is not None - - if is_lower_version(highest_other_version, reference_version): - # all other versions are older - return highest_other_version - if is_lower_version(reference_version, lowest_other_version): - # all other versions are newer - return lowest_other_version - # there are both older and newer versions - lower_other_versions = [ - o_v for o_v in other_versions if is_lower_version(o_v, reference_version) - ] - highest_lower = highest_version(lower_other_versions) - assert highest_lower is not None - return highest_lower diff --git a/cfbs/masterfiles/check_download_matches_git.py b/cfbs/masterfiles/check_download_matches_git.py index 1898b5f6..0e54e7d8 100644 --- a/cfbs/masterfiles/check_download_matches_git.py +++ b/cfbs/masterfiles/check_download_matches_git.py @@ -1,8 +1,13 @@ import os from collections import OrderedDict -from cfbs.masterfiles.analyze import version_as_comparable_list -from cfbs.utils import dict_diff, read_json, CFBSExitError, write_json +from cfbs.utils import ( + dict_diff, + read_json, + CFBSExitError, + write_json, + version_as_comparable_list, +) def check_download_matches_git(versions): diff --git a/cfbs/masterfiles/download.py b/cfbs/masterfiles/download.py index 43685887..388f2e0a 100644 --- a/cfbs/masterfiles/download.py +++ b/cfbs/masterfiles/download.py @@ -1,8 +1,14 @@ import os import shutil -from cfbs.masterfiles.analyze import version_is_at_least -from cfbs.utils import CFBSNetworkError, fetch_url, get_json, mkdir, CFBSExitError +from cfbs.utils import ( + CFBSNetworkError, + fetch_url, + get_json, + mkdir, + CFBSExitError, + version_is_at_least, +) ENTERPRISE_RELEASES_URL = "https://cfengine.com/release-data/enterprise/releases.json" diff --git a/cfbs/masterfiles/generate_release_information.py b/cfbs/masterfiles/generate_release_information.py index 2e4ea7e2..e08457b5 100644 --- a/cfbs/masterfiles/generate_release_information.py +++ b/cfbs/masterfiles/generate_release_information.py @@ -1,9 +1,8 @@ -from cfbs.masterfiles.analyze import version_is_at_least from cfbs.masterfiles.download import download_all_versions from cfbs.masterfiles.generate_vcf_download import generate_vcf_download from cfbs.masterfiles.generate_vcf_git_checkout import generate_vcf_git_checkout from cfbs.masterfiles.check_download_matches_git import check_download_matches_git -from cfbs.utils import immediate_subdirectories +from cfbs.utils import immediate_subdirectories, version_is_at_least DOWNLOAD_PATH = "downloaded_masterfiles" diff --git a/cfbs/utils.py b/cfbs/utils.py index 615b2b03..dc2dc8bb 100644 --- a/cfbs/utils.py +++ b/cfbs/utils.py @@ -601,3 +601,102 @@ def loads_bundlenames(policy: str): regex = r"(?<=^bundle agent )[a-zA-Z0-9_\200-\377]+" return re.findall(regex, policy, re.MULTILINE) + + +Version = str + + +def version_as_comparable_list(version: str): + """Also supports versions containing exactly one of `b` or `-`. + + Example of the version ordering: `3.24.0b1 < 3.24.0 < 3.24.0-1`. + + Examples: + * `version_as_comparable_list("3.24.0b1")` is `[[3, 24, 0], [-1, 1]]` + * `version_as_comparable_list("3.24.0-2")` is `[[3, 24, 0], [1, 2]]` + * `version_as_comparable_list("3.24.x")` is `[[3, 24, 99999], [0, 0]]`""" + if version == "master": + version = "x" + + if "b" not in version: + if "-" not in version: + version += "|0.0" + version = version.replace("x", "99999").replace("-", "|1.").replace("b", "|-1.") + versionpair = version.split("|") + versions_str = [versionpair[0].split("."), versionpair[1].split(".")] + + versions_int = [ + [int(s) for s in versions_str[0]], + [int(s) for s in versions_str[1]], + ] + + return versions_int + + +def version_as_comparable_list_negated(version): + vcl = version_as_comparable_list(version) + + vcl[0] = [-x for x in vcl[0]] + vcl[1] = [-x for x in vcl[1]] + + return vcl + + +def version_is_at_least(version, min_version): + return min_version is None or ( + version_as_comparable_list(version) >= version_as_comparable_list(min_version) + ) + + +def sort_versions(versions: list, reverse: bool = True): + """Sorts a list of versions, in descending order by default.""" + versions.sort( + key=version_as_comparable_list, + reverse=reverse, + ) + + +def highest_version(versions: Iterable[Version]): + return max(versions, key=version_as_comparable_list, default=None) + + +def lowest_version(versions: Iterable[Version]): + return min(versions, key=version_as_comparable_list, default=None) + + +def is_lower_version(version_a: Version, version_b: Version): + """Returns `True` if and only if `version_a` is lower than `version_b`.""" + + return version_as_comparable_list(version_a) < version_as_comparable_list(version_b) + + +def most_relevant_version( + other_versions: List[Version], reference_version: Version +) -> Version: + """ + The most relevant version is the highest version among older other versions, + or if there are no older other versions, the lowest version among other versions. + + `other_versions` is assumed to be non-empty and not contain `reference_version`.""" + assert len(other_versions) > 0 + assert reference_version not in other_versions + + highest_other_version = highest_version(other_versions) + lowest_other_version = lowest_version(other_versions) + # Unfortunately, Pyright can not infer that these can't be `None` when `other_versions` is non-empty. + assert highest_other_version is not None + assert lowest_other_version is not None + + if is_lower_version(highest_other_version, reference_version): + # all other versions are older + return highest_other_version + if is_lower_version(reference_version, lowest_other_version): + # all other versions are newer + return lowest_other_version + # there are both older and newer versions + lower_other_versions = [ + o_v for o_v in other_versions if is_lower_version(o_v, reference_version) + ] + highest_lower = highest_version(lower_other_versions) + assert highest_lower is not None + return highest_lower