pytrainer: unpin python 3.10
[NixPkgs.git] / pkgs / desktops / gnome / find-latest-version.py
blob1cc2b55fadb3a85135898036594c74862b8ada2e
1 import argparse
2 import math
3 import json
4 import requests
5 import sys
6 from enum import Enum
7 from libversion import Version
8 from typing import (
9 Callable,
10 Iterable,
11 List,
12 NamedTuple,
13 Optional,
14 Tuple,
15 TypeVar,
16 Type,
17 cast,
21 EnumValue = TypeVar("EnumValue", bound=Enum)
24 def enum_to_arg(enum: Enum) -> str:
25 return enum.name.lower().replace("_", "-")
28 def arg_to_enum(enum_meta: Type[EnumValue], name: str) -> EnumValue:
29 return enum_meta[name.upper().replace("-", "_")]
32 def enum_to_arg_choices(enum_meta: Type[EnumValue]) -> Tuple[str, ...]:
33 return tuple(enum_to_arg(v) for v in cast(Iterable[EnumValue], enum_meta))
36 class Stability(Enum):
37 STABLE = "stable"
38 UNSTABLE = "unstable"
41 VersionPolicy = Callable[[Version], bool]
42 VersionPredicate = Callable[[Version, Stability], bool]
45 class VersionPredicateHolder(NamedTuple):
46 function: VersionPredicate
49 def version_to_list(version: str) -> List[int]:
50 return list(map(int, version.split(".")))
53 def odd_unstable(version: Version, selected: Stability) -> bool:
54 try:
55 version_parts = version_to_list(version.value)
56 except:
57 # Failing to parse as a list of numbers likely means the version contains a string tag like “beta”, therefore it is not a stable release.
58 return selected != Stability.STABLE
60 if len(version_parts) < 2:
61 return True
63 even = version_parts[1] % 2 == 0
64 prerelease = (version_parts[1] >= 90 and version_parts[1] < 100) or (version_parts[1] >= 900 and version_parts[1] < 1000)
65 stable = even and not prerelease
66 if selected == Stability.STABLE:
67 return stable
68 else:
69 return True
72 def tagged(version: Version, selected: Stability) -> bool:
73 if selected == Stability.STABLE:
74 return not ("alpha" in version.value or "beta" in version.value or "rc" in version.value)
75 else:
76 return True
79 def no_policy(version: Version, selected: Stability) -> bool:
80 return True
83 class VersionPolicyKind(Enum):
84 # HACK: Using function as values directly would make Enum
85 # think they are methods and skip them.
86 ODD_UNSTABLE = VersionPredicateHolder(odd_unstable)
87 TAGGED = VersionPredicateHolder(tagged)
88 NONE = VersionPredicateHolder(no_policy)
91 def make_version_policy(
92 version_policy_kind: VersionPolicyKind,
93 selected: Stability,
94 upper_bound: Optional[Version],
95 ) -> VersionPolicy:
96 version_predicate = version_policy_kind.value.function
97 if not upper_bound:
98 return lambda version: version_predicate(version, selected)
99 else:
100 return lambda version: version_predicate(version, selected) and version < upper_bound
103 def find_versions(package_name: str, version_policy: VersionPolicy) -> List[Version]:
104 # The structure of cache.json: https://gitlab.gnome.org/Infrastructure/sysadmin-bin/blob/master/ftpadmin#L762
105 cache = json.loads(requests.get(f"https://ftp.gnome.org/pub/GNOME/sources/{package_name}/cache.json").text)
106 if type(cache) != list or cache[0] != 4:
107 raise Exception("Unknown format of cache.json file.")
109 versions: Iterable[Version] = map(Version, cache[2][package_name])
110 versions = sorted(filter(version_policy, versions))
112 return versions
115 parser = argparse.ArgumentParser(
116 description="Find latest version for a GNOME package by crawling their release server.",
118 parser.add_argument(
119 "package-name",
120 help="Name of the directory in https://ftp.gnome.org/pub/GNOME/sources/ containing the package.",
122 parser.add_argument(
123 "version-policy",
124 help="Policy determining which versions are considered stable. GNOME packages usually denote stability by alpha/beta/rc tag in the version. For older packages, odd minor versions are unstable but there are exceptions.",
125 choices=enum_to_arg_choices(VersionPolicyKind),
126 nargs="?",
127 default=enum_to_arg(VersionPolicyKind.TAGGED),
129 parser.add_argument(
130 "requested-release",
131 help="Most of the time, we will want to update to stable version but sometimes it is useful to test.",
132 choices=enum_to_arg_choices(Stability),
133 nargs="?",
134 default=enum_to_arg(Stability.STABLE),
136 parser.add_argument(
137 "--upper-bound",
138 dest="upper-bound",
139 help="Only look for versions older than this one (useful for pinning dependencies).",
143 if __name__ == "__main__":
144 args = parser.parse_args()
146 package_name = getattr(args, "package-name")
147 requested_release = arg_to_enum(Stability, getattr(args, "requested-release"))
148 upper_bound = getattr(args, "upper-bound")
149 if upper_bound is not None:
150 upper_bound = Version(upper_bound)
151 version_policy_kind = arg_to_enum(VersionPolicyKind, getattr(args, "version-policy"))
152 version_policy = make_version_policy(version_policy_kind, requested_release, upper_bound)
154 try:
155 versions = find_versions(package_name, version_policy)
156 except Exception as error:
157 print(error, file=sys.stderr)
158 sys.exit(1)
160 if len(versions) == 0:
161 print("No versions matched.", file=sys.stderr)
162 sys.exit(1)
164 print(versions[-1].value)