mirror of
https://github.com/solero/houdini.git
synced 2024-11-13 22:28:21 +00:00
Overhaul converter system
- Keyword argument support - New Greedy, Union and Optional converters - Optionally passing extra data as raw list
This commit is contained in:
parent
d292407dc7
commit
5a01be8c68
@ -1,155 +1,221 @@
|
|||||||
import zope.interface
|
from abc import ABC
|
||||||
from zope.interface import implementer
|
from abc import abstractmethod
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
|
||||||
class IConverter(zope.interface.Interface):
|
class IConverter(ABC):
|
||||||
|
|
||||||
description = zope.interface.Attribute("""A short description of the purpose of the converter""")
|
@property
|
||||||
|
@abstractmethod
|
||||||
|
def description(self):
|
||||||
|
"""A short description of the purpose of the converter"""
|
||||||
|
|
||||||
async def convert(self):
|
@abstractmethod
|
||||||
raise NotImplementedError('Converter must derive this class!')
|
async def convert(self, ctx):
|
||||||
|
"""The actual converter implementation"""
|
||||||
|
|
||||||
|
|
||||||
class Converter:
|
class CredentialsConverter(IConverter):
|
||||||
|
|
||||||
__slots__ = ['p', 'argument']
|
|
||||||
|
|
||||||
def __init__(self, p, argument):
|
|
||||||
self.p = p
|
|
||||||
self.argument = argument
|
|
||||||
|
|
||||||
|
|
||||||
@implementer(IConverter)
|
|
||||||
class CredentialsConverter(Converter):
|
|
||||||
|
|
||||||
description = """Used for obtaining login credentials from XML login data"""
|
description = """Used for obtaining login credentials from XML login data"""
|
||||||
|
|
||||||
async def convert(self):
|
async def convert(self, ctx):
|
||||||
username = self.argument[0][0].text
|
username = ctx.argument[0][0].text
|
||||||
password = self.argument[0][1].text
|
password = ctx.argument[0][1].text
|
||||||
return username, password
|
return username, password
|
||||||
|
|
||||||
|
|
||||||
@implementer(IConverter)
|
class VersionChkConverter(IConverter):
|
||||||
class VersionChkConverter(Converter):
|
|
||||||
|
|
||||||
description = """Used for checking the verChk version number"""
|
description = """Used for checking the verChk version number"""
|
||||||
|
|
||||||
async def convert(self):
|
async def convert(self, ctx):
|
||||||
return self.argument[0].get('v')
|
return ctx.argument[0].get('v')
|
||||||
|
|
||||||
|
|
||||||
@implementer(IConverter)
|
class ConnectedPenguinConverter(IConverter):
|
||||||
class ConnectedPenguinConverter(Converter):
|
|
||||||
|
|
||||||
description = """Converts a penguin ID into a live penguin instance
|
description = """Converts a penguin ID into a live penguin instance
|
||||||
or none if the player is offline"""
|
or none if the player is offline"""
|
||||||
|
|
||||||
async def convert(self):
|
async def convert(self, ctx):
|
||||||
penguin_id = int(self.argument)
|
penguin_id = int(ctx.argument)
|
||||||
if penguin_id in self.p.server.penguins_by_id:
|
if penguin_id in ctx.p.server.penguins_by_id:
|
||||||
return self.p.server.penguins_by_id[penguin_id]
|
return ctx.p.server.penguins_by_id[penguin_id]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@implementer(IConverter)
|
class ConnectedIglooConverter(IConverter):
|
||||||
class ConnectedIglooConverter(Converter):
|
|
||||||
|
|
||||||
description = """Converts a penguin ID into a live igloo instance or
|
description = """Converts a penguin ID into a live igloo instance or
|
||||||
none if it's not available"""
|
none if it's not available"""
|
||||||
|
|
||||||
async def convert(self):
|
async def convert(self, ctx):
|
||||||
igloo_id = int(self.argument)
|
igloo_id = int(ctx.argument)
|
||||||
if igloo_id in self.p.server.igloo_map:
|
if igloo_id in ctx.p.server.igloo_map:
|
||||||
return self.p.server.igloo_map[igloo_id]
|
return ctx.p.server.igloo_map[igloo_id]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@implementer(IConverter)
|
class RoomConverter(IConverter):
|
||||||
class RoomConverter(Converter):
|
|
||||||
|
|
||||||
description = """Converts a room ID into a Houdini.Data.Room instance"""
|
description = """Converts a room ID into a Houdini.Data.Room instance"""
|
||||||
|
|
||||||
async def convert(self):
|
async def convert(self, ctx):
|
||||||
room_id = int(self.argument)
|
room_id = int(ctx.argument)
|
||||||
if room_id in self.p.server.rooms:
|
if room_id in ctx.p.server.rooms:
|
||||||
return self.p.server.rooms[room_id]
|
return ctx.p.server.rooms[room_id]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@implementer(IConverter)
|
class ItemConverter(IConverter):
|
||||||
class ItemConverter(Converter):
|
|
||||||
|
|
||||||
description = """Converts an item ID into a Houdini.Data.Item instance"""
|
description = """Converts an item ID into a Houdini.Data.Item instance"""
|
||||||
|
|
||||||
async def convert(self):
|
async def convert(self, ctx):
|
||||||
item_id = int(self.argument)
|
item_id = int(ctx.argument)
|
||||||
if item_id in self.p.server.items:
|
if item_id in ctx.p.server.items:
|
||||||
return self.p.server.items[item_id]
|
return ctx.p.server.items[item_id]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@implementer(IConverter)
|
class IglooConverter(IConverter):
|
||||||
class IglooConverter(Converter):
|
|
||||||
|
|
||||||
description = """Converts an igloo ID into a Houdini.Data.Igloo instance"""
|
description = """Converts an igloo ID into a Houdini.Data.Igloo instance"""
|
||||||
|
|
||||||
async def convert(self):
|
async def convert(self, ctx):
|
||||||
igloo_id = int(self.argument)
|
igloo_id = int(ctx.argument)
|
||||||
if igloo_id in self.p.server.igloos:
|
if igloo_id in ctx.p.server.igloos:
|
||||||
return self.p.server.igloos[igloo_id]
|
return ctx.p.server.igloos[igloo_id]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@implementer(IConverter)
|
class FurnitureConverter(IConverter):
|
||||||
class FurnitureConverter(Converter):
|
|
||||||
|
|
||||||
description = """Converts a furniture ID into a Houdini.Data.Furniture instance"""
|
description = """Converts a furniture ID into a Houdini.Data.Furniture instance"""
|
||||||
|
|
||||||
async def convert(self):
|
async def convert(self, ctx):
|
||||||
furniture_id = int(self.argument)
|
furniture_id = int(ctx.argument)
|
||||||
if furniture_id in self.p.server.furniture:
|
if furniture_id in ctx.p.server.furniture:
|
||||||
return self.p.server.furniture[furniture_id]
|
return ctx.p.server.furniture[furniture_id]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@implementer(IConverter)
|
class FlooringConverter(IConverter):
|
||||||
class FlooringConverter(Converter):
|
|
||||||
|
|
||||||
description = """Converts a flooring ID into a Houdini.Data.Flooring instance"""
|
description = """Converts a flooring ID into a Houdini.Data.Flooring instance"""
|
||||||
|
|
||||||
async def convert(self):
|
async def convert(self, ctx):
|
||||||
flooring_id = int(self.argument)
|
flooring_id = int(ctx.argument)
|
||||||
if flooring_id in self.p.server.flooring:
|
if flooring_id in ctx.p.server.flooring:
|
||||||
return self.p.server.flooring[flooring_id]
|
return ctx.p.server.flooring[flooring_id]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@implementer(IConverter)
|
class StampConverter(IConverter):
|
||||||
class StampConverter(Converter):
|
|
||||||
|
|
||||||
description = """Converts a stamp ID into a Houdini.Data.Stamp instance"""
|
description = """Converts a stamp ID into a Houdini.Data.Stamp instance"""
|
||||||
|
|
||||||
async def convert(self):
|
async def convert(self, ctx):
|
||||||
stamp_id = int(self.argument)
|
stamp_id = int(ctx.argument)
|
||||||
if stamp_id in self.p.server.stamps:
|
if stamp_id in ctx.p.server.stamps:
|
||||||
return self.p.server.stamps[stamp_id]
|
return ctx.p.server.stamps[stamp_id]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@implementer(IConverter)
|
class SeparatorConverter(IConverter):
|
||||||
class VerticalConverter(Converter):
|
|
||||||
|
|
||||||
description = """Converts vertically separated values into an int list"""
|
__slots__ = ['separator', 'mapper']
|
||||||
|
|
||||||
async def convert(self):
|
description = """Converts strings separated by char into a list of type"""
|
||||||
return map(int, self.argument.split('|'))
|
|
||||||
|
def __init__(self, separator='|', mapper=int):
|
||||||
|
self.separator = separator
|
||||||
|
self.mapper = mapper
|
||||||
|
|
||||||
|
async def convert(self, ctx):
|
||||||
|
return map(self.mapper, ctx.argument.split(self.separator))
|
||||||
|
|
||||||
|
|
||||||
@implementer(IConverter)
|
class UnionConverter(IConverter):
|
||||||
class CommaConverter(Converter):
|
|
||||||
|
|
||||||
description = """Converts comma separated values into an int list"""
|
__slots__ = ['types']
|
||||||
|
|
||||||
async def convert(self):
|
description = """Converts union type into argument"""
|
||||||
return map(int, self.argument.split(','))
|
|
||||||
|
def __init__(self, *types, skip_none=False):
|
||||||
|
self.types = types
|
||||||
|
self.skip_none = skip_none
|
||||||
|
|
||||||
|
async def convert(self, ctx):
|
||||||
|
for converter in self.types:
|
||||||
|
try:
|
||||||
|
result = await do_conversion(converter, ctx)
|
||||||
|
if not self.skip_none or result is not None:
|
||||||
|
return result
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
|
||||||
|
class GreedyConverter(IConverter):
|
||||||
|
|
||||||
|
__slots__ = ['target']
|
||||||
|
|
||||||
|
description = """Converts until it can't any longer"""
|
||||||
|
|
||||||
|
def __init__(self, target=int):
|
||||||
|
self.target = target
|
||||||
|
|
||||||
|
async def convert(self, ctx):
|
||||||
|
converted = []
|
||||||
|
try:
|
||||||
|
converted.append(await do_conversion(self.target, ctx))
|
||||||
|
for ctx.argument in ctx.arguments:
|
||||||
|
converted.append(await do_conversion(self.target, ctx))
|
||||||
|
except ValueError:
|
||||||
|
return converted
|
||||||
|
return converted
|
||||||
|
|
||||||
|
|
||||||
|
class OptionalConverter(IConverter):
|
||||||
|
|
||||||
|
__slots__ = ['target']
|
||||||
|
|
||||||
|
description = """Tries to convert but ignores if it can't"""
|
||||||
|
|
||||||
|
def __init__(self, target=int):
|
||||||
|
self.target = target
|
||||||
|
|
||||||
|
async def convert(self, ctx):
|
||||||
|
try:
|
||||||
|
return await do_conversion(self.target, ctx)
|
||||||
|
except ValueError:
|
||||||
|
return ctx.component.default
|
||||||
|
|
||||||
|
|
||||||
|
class _ConverterContext:
|
||||||
|
|
||||||
|
__slots__ = ['component', 'arguments', 'argument', 'p']
|
||||||
|
|
||||||
|
def __init__(self, component, arguments, argument, p):
|
||||||
|
self.component = component
|
||||||
|
self.arguments = arguments
|
||||||
|
self.argument = argument
|
||||||
|
self.p = p
|
||||||
|
|
||||||
|
|
||||||
|
def get_converter(component):
|
||||||
|
if component.annotation is component.empty:
|
||||||
|
return str
|
||||||
|
return component.annotation
|
||||||
|
|
||||||
|
|
||||||
|
async def do_conversion(converter, ctx):
|
||||||
|
if issubclass(type(converter), IConverter) and not isinstance(converter, IConverter):
|
||||||
|
converter = converter()
|
||||||
|
if isinstance(converter, IConverter):
|
||||||
|
if asyncio.iscoroutinefunction(converter.convert):
|
||||||
|
return await converter.convert(ctx)
|
||||||
|
return converter.convert(ctx)
|
||||||
|
return converter(ctx.argument)
|
||||||
|
@ -110,39 +110,53 @@ class _Listener:
|
|||||||
|
|
||||||
class _XTListener(_Listener):
|
class _XTListener(_Listener):
|
||||||
|
|
||||||
__slots__ = ['pre_login']
|
__slots__ = ['pre_login', 'rest_raw', 'keywords']
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
self.pre_login = kwargs.get('pre_login')
|
self.pre_login = kwargs.get('pre_login')
|
||||||
|
self.rest_raw = kwargs.get('rest_raw', False)
|
||||||
|
|
||||||
|
self.keywords = len(inspect.getfullargspec(self.handler).kwonlyargs)
|
||||||
|
|
||||||
|
if self.rest_raw:
|
||||||
|
self.components = self.components[:-1]
|
||||||
|
|
||||||
async def __call__(self, p, packet_data):
|
async def __call__(self, p, packet_data):
|
||||||
if not self.pre_login and not p.joined_world:
|
await super().__call__(p, packet_data)
|
||||||
p.logger.warn('{} tried sending XT packet before authentication!'.format(p.peer_name))
|
|
||||||
await p.close()
|
|
||||||
return
|
|
||||||
|
|
||||||
super().__call__(p, packet_data)
|
handler_call_arguments = [self.plugin] if self.plugin is not None else []
|
||||||
|
|
||||||
handler_call_arguments = [self.instance] if self.instance is not None else []
|
|
||||||
handler_call_arguments += [self.packet, p] if self.pass_packet else [p]
|
handler_call_arguments += [self.packet, p] if self.pass_packet else [p]
|
||||||
|
handler_call_keywords = {}
|
||||||
|
|
||||||
arguments = iter(packet_data)
|
arguments = iter(packet_data[:-self.keywords])
|
||||||
for index, component in enumerate(self.components):
|
ctx = _ConverterContext(None, arguments, None, p)
|
||||||
if component.default is not component.empty:
|
for ctx.component in self.components:
|
||||||
handler_call_arguments.append(component.default)
|
if ctx.component.annotation is ctx.component.empty and ctx.component.default is not ctx.component.empty:
|
||||||
next(arguments)
|
handler_call_arguments.append(ctx.component.default)
|
||||||
elif component.kind == component.POSITIONAL_OR_KEYWORD:
|
next(ctx.arguments)
|
||||||
component_data = next(arguments)
|
elif ctx.component.kind == ctx.component.POSITIONAL_OR_KEYWORD:
|
||||||
converter = get_converter(component)
|
ctx.argument = next(ctx.arguments)
|
||||||
handler_call_arguments.append(await do_conversion(converter, p, component_data))
|
converter = get_converter(ctx.component)
|
||||||
elif component.kind == component.VAR_POSITIONAL:
|
|
||||||
for component_data in arguments:
|
handler_call_arguments.append(await do_conversion(converter, ctx))
|
||||||
converter = get_converter(component)
|
elif ctx.component.kind == ctx.component.VAR_POSITIONAL:
|
||||||
handler_call_arguments.append(await do_conversion(converter, p, component_data))
|
for argument in ctx.arguments:
|
||||||
break
|
ctx.argument = argument
|
||||||
return await self.handler(*handler_call_arguments)
|
converter = get_converter(ctx.component)
|
||||||
|
|
||||||
|
handler_call_arguments.append(await do_conversion(converter, ctx))
|
||||||
|
elif ctx.component.kind == ctx.component.KEYWORD_ONLY:
|
||||||
|
ctx.argument = packet_data[-self.keywords:][len(handler_call_keywords)]
|
||||||
|
converter = get_converter(ctx.component)
|
||||||
|
handler_call_keywords[ctx.component.name] = await do_conversion(converter, ctx)
|
||||||
|
|
||||||
|
if self.rest_raw:
|
||||||
|
handler_call_arguments.append(list(ctx.arguments))
|
||||||
|
return await self.handler(*handler_call_arguments, **handler_call_keywords)
|
||||||
|
elif not len(list(ctx.arguments)):
|
||||||
|
return await self.handler(*handler_call_arguments, **handler_call_keywords)
|
||||||
|
|
||||||
|
|
||||||
class _XMLListener(_Listener):
|
class _XMLListener(_Listener):
|
||||||
|
Loading…
Reference in New Issue
Block a user