7 from libversion
import Version
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
):
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:
55 version_parts
= version_to_list(version
.value
)
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:
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
:
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
)
79 def no_policy(version
: Version
, selected
: Stability
) -> bool:
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
,
94 upper_bound
: Optional
[Version
],
96 version_predicate
= version_policy_kind
.value
.function
98 return lambda version
: version_predicate(version
, selected
)
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
))
115 parser
= argparse
.ArgumentParser(
116 description
="Find latest version for a GNOME package by crawling their release server.",
120 help="Name of the directory in https://ftp.gnome.org/pub/GNOME/sources/ containing the package.",
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
),
127 default
=enum_to_arg(VersionPolicyKind
.TAGGED
),
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
),
134 default
=enum_to_arg(Stability
.STABLE
),
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
)
155 versions
= find_versions(package_name
, version_policy
)
156 except Exception as error
:
157 print(error
, file=sys
.stderr
)
160 if len(versions
) == 0:
161 print("No versions matched.", file=sys
.stderr
)
164 print(versions
[-1].value
)