"""Version handling."""
import inspect
from . import util
[docs]class Version:
"""A version of Minecraft.
:meta no-undoc-members:
Parameters
----------
name : :class:`str` or :class:`int` or :class:`Version` or ``None``
If :class:`str`, then the version's name.
If :class:`int`, then the version's protocol version.
The name of the version will be looked up using
:meth:`name_from_proto`.
If :class:`Version`, the :attr:`name` and :attr:`proto`
attributes will be copied.
If ``None``, then it will behave as if you had passed
:meth:`latest` as ``name``.
proto : :class:`int`, optional
The version's protocol version. If unspecified, it will
look up the protocol version from :attr:`supported_versions`.
check_supported : :class:`bool`, optional
Whether or not to check if the version is supported.
Will be ignored if ``name`` is ``None``.
Attributes
----------
name : :class:`str`
The version's name.
proto : :class:`int`
The version's protocol version.
supported_versions : :class:`dict`
A chronologically-ordered dictionary with version names as keys
and the corresponding protocol version as values.
"""
PRERELEASE = util.bit(30)
supported_versions = {
"1.15.2": 578,
"20w06a": 701,
"20w07a": 702,
"20w08a": 703,
"20w09a": 704,
"20w10a": 705,
"20w11a": 706,
"20w12a": 707,
"20w13a": 708,
"20w13b": 709,
# Skip 20w14∞ (April Fools snapshot)
"20w14a": 710,
"20w15a": 711,
"20w16a": 712,
"20w17a": 713,
"20w18a": 714,
"20w19a": 715,
"20w20a": 716,
"20w20b": 717,
"20w21a": 718,
"20w22a": 719,
"1.16-pre1": 721,
"1.16-pre2": 722,
"1.16-pre3": 725,
"1.16-pre4": 727,
"1.16-pre5": 729,
"1.16-pre6": 730,
"1.16-pre7": 732,
"1.16-pre8": 733,
"1.16-rc1": 734,
"1.16": 735,
"1.16.1": 736,
"20w27a": 738,
"20w28a": 740,
"20w29a": 741,
"20w30a": 743,
"1.16.2-pre1": 744,
"1.16.2-pre2": 746,
"1.16.2-pre3": 748,
"1.16.2-rc1": 749,
"1.16.2-rc2": 750,
"1.16.2": 751,
"1.16.3-rc1": 752,
"1.16.3": 753,
"1.16.4-pre1": PRERELEASE | 1,
"1.16.4-pre2": PRERELEASE | 2,
"1.16.4-rc1": PRERELEASE | 3,
"1.16.4": 754,
"1.16.5": 754,
"20w45a": PRERELEASE | 5,
"20w46a": PRERELEASE | 6,
"20w48a": PRERELEASE | 7,
"20w49a": PRERELEASE | 8,
"20w51a": PRERELEASE | 9,
}
# Cached so it doesn't need to be
# regenerated on every comparison
_supported_versions_list = list(supported_versions.values())
[docs] @classmethod
def latest(cls):
"""Gets the latest supported version.
Returns
-------
:class:`Version`
The latest supported version.
"""
return cls(cls._supported_versions_list[-1])
[docs] @classmethod
def name_from_proto(cls, proto):
"""Gets the version name corresponding to the protocol version.
Parameters
----------
proto : :class:`int`
The protocol version.
Returns
-------
:class:`str`
The corresponding version name.
Raises
------
:exc:`ValueError`
If no corresponding version name can be found.
"""
# Prefers later version names when
# protocol versions are equal.
for name, proto_version in reversed(cls.supported_versions.items()):
if proto == proto_version:
return name
raise ValueError(f"No version name corresponds to protocol version {proto}")
def __init__(self, name, proto=-1, *, check_supported=False):
if name is None:
name = self.latest()
if isinstance(name, Version):
proto = name.proto
name = name.name
if isinstance(name, int):
proto = name
name = self.name_from_proto(name)
if check_supported and name not in self.supported_versions:
raise ValueError(f"Unsupported version: {name}")
self.name = name
if proto < 0:
self.proto = self.supported_versions.get(self.name, -1)
else:
self.proto = proto
[docs] def __eq__(self, other):
"""Checks whether a version is equal to another.
Parameters
----------
other : :class:`Version` or :class:`str`
The other version.
Returns
-------
:class:`bool`
Whether the version is equal to ``other``.
Examples
--------
>>> from dolor.versions import Version
>>> Version("1.16.4") == Version("1.16.4")
True
>>> Version("1.16.4") == "1.16.4"
True
>>> Version("1.16.4") == "1.15.2"
False
"""
other = Version(other)
return self.proto == other.proto
def __ne__(self, other):
return not self == other
def __hash__(self):
return hash(self.proto)
[docs] def __gt__(self, other):
"""Checks whether a version is greater than another.
Parameters
----------
other : :class:`Version` or :class:`str`
The other version.
Returns
-------
:class:`bool`
Whether the version is greater than ``other``.
Examples
--------
>>> from dolor.versions import Version
>>> Version("1.16.4") > Version("1.15.2")
True
>>> Version("1.16.4") > "1.15.2"
True
>>> Version("1.15.2") > "1.16.4"
False
"""
other = Version(other)
versions = self._supported_versions_list
return versions.index(self.proto) > versions.index(other.proto)
def __ge__(self, other):
return self == other or self > other
[docs] def __lt__(self, other):
"""Checks whether a version is less than another.
Parameters
----------
other : :class:`Version` or :class:`str`
The other version.
Returns
-------
:class:`bool`
Whether the version is less than ``other``.
Examples
--------
>>> from dolor.versions import Version
>>> Version("1.15.2") < Version("1.16.4")
True
>>> Version("1.15.2") < "1.16.4"
True
>>> Version("1.16.4") < "1.15.2"
False
"""
other = Version(other)
versions = self._supported_versions_list
return versions.index(self.proto) < versions.index(other.proto)
def __le__(self, other):
return self == other or self < other
def __repr__(self):
return f"{type(self).__name__}({repr(self.name)}, {repr(self.proto)})"
[docs]class VersionRange:
"""A range of versions.
A version is contained in the range when it is greater
than or equal to ``start`` and less than ``stop``. In more
mathematical terms, the range is [``start``, ``stop``), just like
the builtin :class:`range`.
Parameters
----------
start : :class:`Version` or :class:`str` or ``None``
The lower bound of the range. If ``None``, then `start` will
not be checked when seeing if a version is contained
in the range.
stop : :class:`Version` or :class:`str` or ``None``
The upper bound of the range. If ``None``, then `stop` will
not be checked when seeing if a version is contained
in the range.
Examples
--------
>>> from dolor.versions import VersionRange
>>> "1.16" in VersionRange("1.15.2", "1.16.4")
True
>>> "1.16" in VersionRange(None, "1.16.4")
True
>>> "1.16" in VersionRange("1.15.2", None)
True
>>> "1.16" in VersionRange(None, None)
True
>>> "1.16" in VersionRange("1.16.2", "1.16.4")
False
>>> "1.15.2" in VersionRange("1.15.2", "1.16.4")
True
>>> "1.16.4" in VersionRange("1.15.2", "1.16.4")
False
"""
def __init__(self, start, stop):
self.start = start
self.stop = stop
def __contains__(self, value):
value = Version(value)
ret = True
if self.start is not None:
ret = ret and self.start <= value
if self.stop is not None:
ret = ret and value < self.stop
return ret
[docs]class VersionSwitcher:
"""A class to simplify getting different values based on different versions.
Parameters
----------
switch : :class:`dict`
A dictionary whose keys can be:
- A :class:`function` which takes one argument (the version) and returns a :class:`bool`.
- A :class:`str` which is the version's name.
- A container (checked with :func:`~.is_container`) which contains versions.
- ``None``, whose value will be the default if no other key fits a version.
Examples
--------
>>> from dolor.versions import VersionSwitcher, VersionRange
>>> switcher = VersionSwitcher({
... (lambda v: v == "1.16.4"): 0,
... "1.16.3": 1,
... VersionRange("1.16", "1.16.3"): 2,
... None: 4,
... })
>>> switcher["1.16.4"]
0
>>> switcher["1.16.3"]
1
>>> switcher["1.16.1"]
2
>>> switcher["1.15.2"]
4
"""
def __init__(self, switch):
for key in switch:
if not inspect.isfunction(key) and not isinstance(key, str) and not util.is_container(key) and key is not None:
raise TypeError(f"Invalid type for key: {key}")
self.switch = switch
[docs] def get(self, version):
"""Gets the appropriate value for the version."""
version = Version(version)
for key, value in self.switch.items():
if key is None:
continue
if inspect.isfunction(key):
if key(version):
return value
elif isinstance(key, str):
if version == key:
return value
elif util.is_container(key):
if version in key:
return value
return self.switch[None]
[docs] def __getitem__(self, version):
"""Does the same as :meth:`get`."""
return self.get(version)