From 8b150ce4194925410881b98a0467d954991f2657 Mon Sep 17 00:00:00 2001 From: Vincent Le Goff Date: Mon, 12 Jun 2017 13:32:08 -0700 Subject: [PATCH 1/4] [Event system] Check that the script has valid non-attributes before using it --- evennia/contrib/events/utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/evennia/contrib/events/utils.py b/evennia/contrib/events/utils.py index 9ecb3544ec..4380f92b70 100644 --- a/evennia/contrib/events/utils.py +++ b/evennia/contrib/events/utils.py @@ -57,6 +57,7 @@ def register_events(path_or_typeclass): try: storage = ScriptDB.objects.get(db_key="event_handler") assert storage.is_active + assert storage.ndb.events is not None except (ScriptDB.DoesNotExist, AssertionError): storage = EVENTS From d894b9571730ea8d42efe467d7859818a3d438d6 Mon Sep 17 00:00:00 2001 From: Vincent Le Goff Date: Wed, 14 Jun 2017 12:35:23 -0700 Subject: [PATCH 2/4] Add cmdhandler's ability to handle 'yield' in command.func() --- evennia/commands/cmdhandler.py | 62 +++++++++++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/evennia/commands/cmdhandler.py b/evennia/commands/cmdhandler.py index 3281687377..0b9b8d94af 100644 --- a/evennia/commands/cmdhandler.py +++ b/evennia/commands/cmdhandler.py @@ -38,6 +38,7 @@ from weakref import WeakValueDictionary from traceback import format_exc from itertools import chain from copy import copy +import types from twisted.internet.defer import inlineCallbacks, returnValue from django.conf import settings from evennia.comms.channelhandler import CHANNELHANDLER @@ -506,7 +507,13 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess # main command code # (return value is normally None) - ret = yield cmd.func() + ret = cmd.func() + if isinstance(ret, types.GeneratorType): + # cmd.func() is a generator, execute progressively + _progressive_cmd_run(cmd, ret) + yield None + else: + ret = yield ret # post-command hook yield cmd.at_post_cmd() @@ -668,3 +675,56 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess except Exception: # This catches exceptions in cmdhandler exceptions themselves _msg_err(error_to, _ERROR_CMDHANDLER) + +def _progressive_cmd_run(cmd, generator, response=None): + """ + Progressively call the command that was given in argument. + + Args: + cmd (Command): the command itself. + generator (GeneratorType): the generator describing the processing. + reponse (str, optional): the response to send to the generator. + + Note: + This function is responsible for executing the command, if + the func() method contains 'yield' instructions. The yielded + value will be accessible at each step and will affect the + process. If the value is a number, just delay the execution + of the command. If it's a string, wait for the user input. + + """ + try: + if response is None: + value = generator.next() + else: + value = generator.send(response) + except StopIteration: + pass + else: + if isinstance(value, (int, float)): + utils.delay(value, _progressive_cmd_run, cmd, generator) + elif isinstance(value, basestring): + from evennia.utils.evmenu import get_input + get_input(cmd.caller, value, _process_input, cmd=cmd, generator=generator) + else: + raise ValueError("unknown type for a yielded value in command: {}".format(type(value))) + +def _process_input(caller, prompt, result, cmd, generator): + """ + Specifically handle the get_input value to send to _progressive_cmd_run. + + Args: + caller (Character, Player or Session): the caller. + prompt (basestring): the sent prompt. + result (basestring): the unprocessed answer. + cmd (Command): the command itself. + generator (GeneratorType): the generator. + + Returns: + Always False (stop processing). + + """ + # We call it in a 'utils.delay()' to make sure the input is properly closed. + utils.delay(0, _progressive_cmd_run, cmd, generator, response=result) + + return False From 5fa084c976442befd129843cf79bf82cef10933b Mon Sep 17 00:00:00 2001 From: Griatch Date: Wed, 14 Jun 2017 22:02:01 +0200 Subject: [PATCH 3/4] Refactor yield-mechanism to be present on top of the module, removing dependence on utils.delay and optimizing some imports. --- evennia/commands/cmdhandler.py | 125 +++++++++++++++++++-------------- 1 file changed, 73 insertions(+), 52 deletions(-) diff --git a/evennia/commands/cmdhandler.py b/evennia/commands/cmdhandler.py index 0b9b8d94af..c0ff71e766 100644 --- a/evennia/commands/cmdhandler.py +++ b/evennia/commands/cmdhandler.py @@ -39,6 +39,8 @@ from traceback import format_exc from itertools import chain from copy import copy import types +from twisted.internet import reactor +from twisted.internet.task import deferLater from twisted.internet.defer import inlineCallbacks, returnValue from django.conf import settings from evennia.comms.channelhandler import CHANNELHANDLER @@ -128,6 +130,12 @@ _ERROR_RECURSION_LIMIT = "Command recursion limit ({recursion_limit}) " \ "reached for '{raw_string}' ({cmdclass})." +# delayed imports +_GET_INPUT = None + + +# helper functions + def _msg_err(receiver, stringtuple): """ Helper function for returning an error to the caller. @@ -152,12 +160,77 @@ def _msg_err(receiver, stringtuple): errmsg=stringtuple[1].strip(), timestamp=timestamp).strip()) + +def _progressive_cmd_run(cmd, generator, response=None): + """ + Progressively call the command that was given in argument. Used + when `yield` is present in the Command's `func()` method. + + Args: + cmd (Command): the command itself. + generator (GeneratorType): the generator describing the processing. + reponse (str, optional): the response to send to the generator. + + Raises: + ValueError: If the func call yields something not identifiable as a + time-delay or a string prompt. + + Note: + This function is responsible for executing the command, if + the func() method contains 'yield' instructions. The yielded + value will be accessible at each step and will affect the + process. If the value is a number, just delay the execution + of the command. If it's a string, wait for the user input. + + """ + global _GET_INPUT + if not _GET_INPUT: + from evennia.utils.evmenu import get_input as _GET_INPUT + + try: + if response is None: + value = generator.next() + else: + value = generator.send(response) + except StopIteration: + pass + else: + if isinstance(value, (int, float)): + utils.delay(value, _progressive_cmd_run, cmd, generator) + elif isinstance(value, basestring): + _GET_INPUT(cmd.caller, value, _process_input, cmd=cmd, generator=generator) + else: + raise ValueError("unknown type for a yielded value in command: {}".format(type(value))) + + +def _process_input(caller, prompt, result, cmd, generator): + """ + Specifically handle the get_input value to send to _progressive_cmd_run as + part of yielding from a Command's `func`. + + Args: + caller (Character, Player or Session): the caller. + prompt (basestring): The sent prompt. + result (basestring): The unprocessed answer. + cmd (Command): The command itself. + generator (GeneratorType): The generator. + + Returns: + result (bool): Always `False` (stop processing). + + """ + # We call it using a Twisted deferLater to make sure the input is properly closed. + deferLater(reactor, 0, _progressive_cmd_run, cmd, generator, response=result) + return False + + # custom Exceptions class NoCmdSets(Exception): "No cmdsets found. Critical error." pass + class ExecSystemCommand(Exception): "Run a system command" def __init__(self, syscmd, sysarg): @@ -676,55 +749,3 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess # This catches exceptions in cmdhandler exceptions themselves _msg_err(error_to, _ERROR_CMDHANDLER) -def _progressive_cmd_run(cmd, generator, response=None): - """ - Progressively call the command that was given in argument. - - Args: - cmd (Command): the command itself. - generator (GeneratorType): the generator describing the processing. - reponse (str, optional): the response to send to the generator. - - Note: - This function is responsible for executing the command, if - the func() method contains 'yield' instructions. The yielded - value will be accessible at each step and will affect the - process. If the value is a number, just delay the execution - of the command. If it's a string, wait for the user input. - - """ - try: - if response is None: - value = generator.next() - else: - value = generator.send(response) - except StopIteration: - pass - else: - if isinstance(value, (int, float)): - utils.delay(value, _progressive_cmd_run, cmd, generator) - elif isinstance(value, basestring): - from evennia.utils.evmenu import get_input - get_input(cmd.caller, value, _process_input, cmd=cmd, generator=generator) - else: - raise ValueError("unknown type for a yielded value in command: {}".format(type(value))) - -def _process_input(caller, prompt, result, cmd, generator): - """ - Specifically handle the get_input value to send to _progressive_cmd_run. - - Args: - caller (Character, Player or Session): the caller. - prompt (basestring): the sent prompt. - result (basestring): the unprocessed answer. - cmd (Command): the command itself. - generator (GeneratorType): the generator. - - Returns: - Always False (stop processing). - - """ - # We call it in a 'utils.delay()' to make sure the input is properly closed. - utils.delay(0, _progressive_cmd_run, cmd, generator, response=result) - - return False From 35db4bf30184e2ce8e75fb892430ff5ced198630 Mon Sep 17 00:00:00 2001 From: Griatch Date: Wed, 14 Jun 2017 22:07:15 +0200 Subject: [PATCH 4/4] Make minor docstring indent fix. --- evennia/commands/cmdhandler.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/evennia/commands/cmdhandler.py b/evennia/commands/cmdhandler.py index c0ff71e766..a24f46d449 100644 --- a/evennia/commands/cmdhandler.py +++ b/evennia/commands/cmdhandler.py @@ -143,8 +143,8 @@ def _msg_err(receiver, stringtuple): Args: receiver (Object): object to get the error message. stringtuple (tuple): tuple with two strings - one for the - _IN_GAME_ERRORS mode (with the traceback) and one with the - production string (with a timestamp) to be shown to the user. + _IN_GAME_ERRORS mode (with the traceback) and one with the + production string (with a timestamp) to be shown to the user. """ string = "{traceback}\n{errmsg}\n(Traceback was logged {timestamp})."