Add @interactive decorator to use yield to pause in a method
This commit is contained in:
parent
866e8611b7
commit
a2bc979503
2 changed files with 81 additions and 13 deletions
|
|
@ -116,6 +116,9 @@ Web/Django standard initiative (@strikaco)
|
|||
|
||||
- `evennia` launcher now fully handles all django-admin commands, like running tests in parallel.
|
||||
- `evennia.utils.create.account` now also takes `tags` and `attrs` keywords.
|
||||
- `evennia.utils.interactive` decorator can now allow you to use yield(secs) to pause operation
|
||||
in any function, not just in Command.func. Likewise, response = yield(question) will work
|
||||
if the decorated function has an argument or kwarg `caller`.
|
||||
- Added many more unit tests.
|
||||
- Swap argument order of `evennia.set_trace` to `set_trace(term_size=(140, 40), debugger='auto')`
|
||||
since the size is more likely to be changed on the command line.
|
||||
|
|
|
|||
|
|
@ -16,10 +16,11 @@ import math
|
|||
import re
|
||||
import textwrap
|
||||
import random
|
||||
import pickle
|
||||
import inspect
|
||||
from twisted.internet.task import deferLater
|
||||
from os.path import join as osjoin
|
||||
from importlib import import_module
|
||||
from importlib.util import find_spec, module_from_spec
|
||||
from importlib.util import find_spec
|
||||
from inspect import ismodule, trace, getmembers, getmodule, getmro
|
||||
from collections import defaultdict, OrderedDict
|
||||
from twisted.internet import threads, reactor, task
|
||||
|
|
@ -1981,3 +1982,67 @@ def get_all_typeclasses(parent=None):
|
|||
typeclasses = {name: typeclass for name, typeclass in typeclasses.items()
|
||||
if inherits_from(typeclass, parent)}
|
||||
return typeclasses
|
||||
|
||||
|
||||
def interactive(func):
|
||||
"""
|
||||
Decorator to make a method pausable with yield(seconds)
|
||||
and able to ask for user-input with response=yield(question).
|
||||
For the question-asking to work, 'caller' must the name
|
||||
of an argument or kwarg to the decorated function.
|
||||
|
||||
Note that this turns the method into a generator.
|
||||
|
||||
Example usage:
|
||||
|
||||
@interactive
|
||||
def myfunc(caller):
|
||||
caller.msg("This is a test")
|
||||
# wait five seconds
|
||||
yield(5)
|
||||
# ask user (caller) a question
|
||||
response = yield("Do you want to continue waiting?")
|
||||
if response == "yes":
|
||||
yield(5)
|
||||
else:
|
||||
# ...
|
||||
|
||||
"""
|
||||
from evennia.utils.evmenu import get_input
|
||||
def _process_input(caller, prompt, result, generator):
|
||||
deferLater(reactor, 0, _iterate, generator, caller, response=result)
|
||||
return False
|
||||
|
||||
def _iterate(generator, caller=None, response=None):
|
||||
try:
|
||||
if response is None:
|
||||
value = next(generator)
|
||||
else:
|
||||
value = generator.send(response)
|
||||
except StopIteration:
|
||||
pass
|
||||
else:
|
||||
if isinstance(value, (int, float)):
|
||||
delay(value, _iterate, generator, caller=caller)
|
||||
elif isinstance(value, str):
|
||||
if not caller:
|
||||
raise ValueError("To retrieve input from a @pausable method, that method "
|
||||
"must be called with a 'caller' argument)")
|
||||
get_input(caller, value, _process_input, generator=generator)
|
||||
else:
|
||||
raise ValueError("yield(val) in a @pausable method must have an int/float as arg.")
|
||||
|
||||
def decorator(*args, **kwargs):
|
||||
argnames = inspect.getfullargspec(func).args
|
||||
caller = None
|
||||
if 'caller' in argnames:
|
||||
# we assume this is an object
|
||||
caller = args[argnames.index('caller')]
|
||||
|
||||
ret = func(*args, **kwargs)
|
||||
if isinstance(ret, types.GeneratorType):
|
||||
_iterate(ret, caller)
|
||||
else:
|
||||
return ret
|
||||
|
||||
return decorator
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue