Many more tests, debugging of protfuncs/inlinefuncs

This commit is contained in:
Griatch 2018-06-17 23:42:53 +02:00
parent 646b73e872
commit 721cdb5ae0
5 changed files with 267 additions and 92 deletions

View file

@ -23,8 +23,8 @@ are specified as functions
where *args are the arguments given in the prototype, and **kwargs are inserted by Evennia:
- session (Session): The Session of the entity spawning using this prototype.
- prototype_key (str): The currently spawning prototype-key.
- prototype (dict): The dict this protfunc is a part of.
- current_key (str): The active key this value belongs to in the prototype.
- testing (bool): This is set if this function is called as part of the prototype validation; if
set, the protfunc should take care not to perform any persistent actions, such as operate on
objects or add things to the database.
@ -38,68 +38,10 @@ prototype key (this value must be possible to serialize in an Attribute).
from ast import literal_eval
from random import randint as base_randint, random as base_random
from django.conf import settings
from evennia.utils import inlinefuncs
from evennia.utils.utils import callables_from_module
from evennia.utils.utils import justify as base_justify, is_iter
from evennia.utils import search
from evennia.utils.utils import justify as base_justify, is_iter, to_str
_PROTLIB = None
_PROT_FUNCS = {}
for mod in settings.PROT_FUNC_MODULES:
try:
callables = callables_from_module(mod)
if mod == __name__:
callables.pop("protfunc_parser", None)
_PROT_FUNCS.update(callables)
except ImportError:
pass
def protfunc_parser(value, available_functions=None, **kwargs):
"""
Parse a prototype value string for a protfunc and process it.
Available protfuncs are specified as callables in one of the modules of
`settings.PROTFUNC_MODULES`, or specified on the command line.
Args:
value (any): The value to test for a parseable protfunc. Only strings will be parsed for
protfuncs, all other types are returned as-is.
available_functions (dict, optional): Mapping of name:protfunction to use for this parsing.
Kwargs:
any (any): Passed on to the inlinefunc.
Returns:
any (any): A structure to replace the string on the prototype level. If this is a
callable or a (callable, (args,)) structure, it will be executed as if one had supplied
it to the prototype directly. This structure is also passed through literal_eval so one
can get actual Python primitives out of it (not just strings). It will also identify
eventual object #dbrefs in the output from the protfunc.
"""
global _PROTLIB
if not _PROTLIB:
from evennia.prototypes import prototypes as _PROTLIB
if not isinstance(value, basestring):
return value
available_functions = _PROT_FUNCS if available_functions is None else available_functions
result = inlinefuncs.parse_inlinefunc(value, available_funcs=available_functions, **kwargs)
# at this point we have a string where all procfuncs were parsed
try:
result = literal_eval(result)
except ValueError:
# this is due to the string not being valid for literal_eval - keep it a string
pass
result = _PROTLIB.value_to_obj_or_any(result)
try:
return literal_eval(result)
except ValueError:
return result
# default protfuncs
@ -180,7 +122,7 @@ def protkey(*args, **kwargs):
"""
if args:
prototype = kwargs['prototype']
return prototype[args[0]]
return prototype[args[0].strip()]
def add(*args, **kwargs):
@ -193,7 +135,16 @@ def add(*args, **kwargs):
"""
if len(args) > 1:
val1, val2 = args[0], args[1]
return literal_eval(val1) + literal_eval(val2)
# try to convert to python structures, otherwise, keep as strings
try:
val1 = literal_eval(val1.strip())
except Exception:
pass
try:
val2 = literal_eval(val2.strip())
except Exception:
pass
return val1 + val2
raise ValueError("$add requires two arguments.")
@ -207,11 +158,20 @@ def sub(*args, **kwargs):
"""
if len(args) > 1:
val1, val2 = args[0], args[1]
return literal_eval(val1) - literal_eval(val2)
# try to convert to python structures, otherwise, keep as strings
try:
val1 = literal_eval(val1.strip())
except Exception:
pass
try:
val2 = literal_eval(val2.strip())
except Exception:
pass
return val1 - val2
raise ValueError("$sub requires two arguments.")
def mul(*args, **kwargs):
def mult(*args, **kwargs):
"""
Usage: $mul(val1, val2)
Returns the value of val1 * val2. The values must be
@ -221,7 +181,16 @@ def mul(*args, **kwargs):
"""
if len(args) > 1:
val1, val2 = args[0], args[1]
return literal_eval(val1) * literal_eval(val2)
# try to convert to python structures, otherwise, keep as strings
try:
val1 = literal_eval(val1.strip())
except Exception:
pass
try:
val2 = literal_eval(val2.strip())
except Exception:
pass
return val1 * val2
raise ValueError("$mul requires two arguments.")
@ -234,10 +203,33 @@ def div(*args, **kwargs):
"""
if len(args) > 1:
val1, val2 = args[0], args[1]
return literal_eval(val1) / float(literal_eval(val2))
# try to convert to python structures, otherwise, keep as strings
try:
val1 = literal_eval(val1.strip())
except Exception:
pass
try:
val2 = literal_eval(val2.strip())
except Exception:
pass
return val1 / float(val2)
raise ValueError("$mult requires two arguments.")
def toint(*args, **kwargs):
"""
Usage: $toint(<number>)
Returns <number> as an integer.
"""
if args:
val = args[0]
try:
return int(literal_eval(val.strip()))
except ValueError:
return val
raise ValueError("$toint requires one argument.")
def eval(*args, **kwargs):
"""
Usage $eval(<expression>)
@ -247,16 +239,79 @@ def eval(*args, **kwargs):
- those will then be evaluated *after* $eval.
"""
string = args[0] if args else ''
global _PROTLIB
if not _PROTLIB:
from evennia.prototypes import prototypes as _PROTLIB
string = ",".join(args)
struct = literal_eval(string)
if isinstance(struct, basestring):
# we must shield the string, otherwise it will be merged as a string and future
# literal_evals will pick up e.g. '2' as something that should be converted to a number
struct = '"{}"'.format(struct)
def _recursive_parse(val):
# an extra round of recursive parsing, to catch any escaped $$profuncs
# an extra round of recursive parsing after literal_eval, to catch any
# escaped $$profuncs. This is commonly useful for object references.
if is_iter(val):
stype = type(val)
if stype == dict:
return {_recursive_parse(key): _recursive_parse(v) for key, v in val.items()}
return stype((_recursive_parse(v) for v in val))
return protfunc_parser(val)
return _PROTLIB.protfunc_parser(val)
return _recursive_parse(struct)
def _obj_search(return_list=False, *args, **kwargs):
"Helper function to search for an object"
query = "".join(args)
session = kwargs.get("session", None)
if not session:
raise ValueError("$obj called by Evennia without Session. This is not supported.")
account = session.account
if not account:
raise ValueError("$obj requires a logged-in account session.")
targets = search.search_object(query)
if return_list:
retlist = []
for target in targets:
if target.access(account, target, 'control'):
retlist.append(target)
return retlist
else:
# single-match
if not targets:
raise ValueError("$obj: Query '{}' gave no matches.".format(query))
if targets.count() > 1:
raise ValueError("$obj: Query '{query}' gave {nmatches} matches. Limit your "
"query or use $objlist instead.".format(
query=query, nmatches=targets.count()))
target = target[0]
if not target.access(account, target, 'control'):
raise ValueError("$obj: Obj {target}(#{dbref} cannot be added - "
"Account {account} does not have 'control' access.".format(
target=target.key, dbref=target.id, account=account))
return target
def obj(*args, **kwargs):
"""
Usage $obj(<query>)
Returns one Object searched globally by key, alias or #dbref. Error if more than one.
"""
return _obj_search(*args, **kwargs)
def objlist(*args, **kwargs):
"""
Usage $objlist(<query>)
Returns list with one or more Objects searched globally by key, alias or #dbref.
"""
return _obj_search(return_list=True, *args, **kwargs)