"""String-related types."""
import json
from .. import util
from .type import Type
from .numeric import VarInt
from .util import prepare_types
[docs]class String(Type):
"""A string.
Parameters
----------
max_length : :class:`int`
The maximum length of the string. By default ``32767``.
prefix : subclass of :class:`~.Type`, optional
The type at the start of the data that
tells how many bytes to read to get the
string data. By default :class:`~.VarInt`.
encoding : :class:`str`, optional
What encoding to use for parsing the string data.
By default ``"utf-8"``.
"""
max_length = 32767
prefix = VarInt
encoding = "utf-8"
_default = ""
@classmethod
def _unpack(cls, buf, *, ctx=None):
length = cls.prefix.unpack(buf, ctx=ctx)
if length > cls.max_length * 4:
raise ValueError(f"Invalid data length ({length}) for String({cls.max_length})")
ret = buf.read(length).decode(cls.encoding)
if len(ret) > cls.max_length:
raise ValueError(f"Invalid character length ({len(ret)}) for String({cls.max_length})")
return ret
@classmethod
def _pack(cls, value, *, ctx=None):
if len(value) > cls.max_length:
raise ValueError(f"Invalid character length ({len(value)}) for String({cls.max_length})")
data = value.encode(cls.encoding)
if len(data) > cls.max_length * 4:
raise ValueError(f"Invalid data length ({len(data)}) for String({cls.max_length})")
return cls.prefix.pack(len(data), ctx=ctx) + data
@classmethod
@prepare_types
def _call(cls, max_length, *, prefix: Type = None, encoding=None):
prefix = util.default(prefix, cls.prefix)
encoding = util.default(encoding, cls.encoding)
return cls.make_type(f"String({max_length})",
max_length = max_length,
prefix = prefix,
encoding = encoding,
)
[docs]class Json(Type):
"""JSON data.
Wraps :func:`json.loads` and `json.dumps` and
:class:`String`.
"""
_default = {}
@classmethod
def _unpack(cls, buf, *, ctx=None):
return json.loads(String.unpack(buf, ctx=ctx))
@classmethod
def _pack(cls, value, *, ctx=None):
return String.pack(json.dumps(value, separators=(",", ":")), ctx=ctx)
[docs]class Identifier(Type):
"""An identifier for a resource location."""
[docs] class Identifier:
"""The value type of :class:`~.string.Identifier`.
Parameters
----------
id : :class:`str`, optional
The namespaced location.
See https://wiki.vg/Protocol#Identifier for
details.
"""
def __init__(self, id=None):
if id is None:
self.namespace = None
self.name = None
else:
parts = id.split(":")
if len(parts) == 1:
self.namespace = "minecraft"
self.name = parts[0]
elif len(parts) == 2:
self.namespace = parts[0]
self.name = parts[1]
else:
raise ValueError("Invalid identifier")
def __str__(self):
return f"{self.namespace}:{self.name}"
def __repr__(self):
return f'{type(self).__name__}("{self}")'
_default = Identifier()
def __set__(self, instance, value):
if not isinstance(value, self.Identifier):
value = self.Identifier(value)
super().__set__(instance, value)
@classmethod
def _unpack(cls, buf, *, ctx=None):
return cls.Identifier(String.unpack(buf, ctx=ctx))
@classmethod
def _pack(cls, value, *, ctx=None):
return String.pack(str(value), ctx=ctx)