Many more tests, debugging of protfuncs/inlinefuncs
This commit is contained in:
parent
646b73e872
commit
721cdb5ae0
5 changed files with 267 additions and 92 deletions
|
|
@ -23,8 +23,8 @@ are specified as functions
|
||||||
where *args are the arguments given in the prototype, and **kwargs are inserted by Evennia:
|
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.
|
- 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.
|
- 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
|
- 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
|
set, the protfunc should take care not to perform any persistent actions, such as operate on
|
||||||
objects or add things to the database.
|
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 ast import literal_eval
|
||||||
from random import randint as base_randint, random as base_random
|
from random import randint as base_randint, random as base_random
|
||||||
|
|
||||||
from django.conf import settings
|
from evennia.utils import search
|
||||||
from evennia.utils import inlinefuncs
|
from evennia.utils.utils import justify as base_justify, is_iter, to_str
|
||||||
from evennia.utils.utils import callables_from_module
|
|
||||||
from evennia.utils.utils import justify as base_justify, is_iter
|
|
||||||
|
|
||||||
_PROTLIB = None
|
_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
|
# default protfuncs
|
||||||
|
|
@ -180,7 +122,7 @@ def protkey(*args, **kwargs):
|
||||||
"""
|
"""
|
||||||
if args:
|
if args:
|
||||||
prototype = kwargs['prototype']
|
prototype = kwargs['prototype']
|
||||||
return prototype[args[0]]
|
return prototype[args[0].strip()]
|
||||||
|
|
||||||
|
|
||||||
def add(*args, **kwargs):
|
def add(*args, **kwargs):
|
||||||
|
|
@ -193,7 +135,16 @@ def add(*args, **kwargs):
|
||||||
"""
|
"""
|
||||||
if len(args) > 1:
|
if len(args) > 1:
|
||||||
val1, val2 = args[0], 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.")
|
raise ValueError("$add requires two arguments.")
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -207,11 +158,20 @@ def sub(*args, **kwargs):
|
||||||
"""
|
"""
|
||||||
if len(args) > 1:
|
if len(args) > 1:
|
||||||
val1, val2 = args[0], 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.")
|
raise ValueError("$sub requires two arguments.")
|
||||||
|
|
||||||
|
|
||||||
def mul(*args, **kwargs):
|
def mult(*args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Usage: $mul(val1, val2)
|
Usage: $mul(val1, val2)
|
||||||
Returns the value of val1 * val2. The values must be
|
Returns the value of val1 * val2. The values must be
|
||||||
|
|
@ -221,7 +181,16 @@ def mul(*args, **kwargs):
|
||||||
"""
|
"""
|
||||||
if len(args) > 1:
|
if len(args) > 1:
|
||||||
val1, val2 = args[0], 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.")
|
raise ValueError("$mul requires two arguments.")
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -234,10 +203,33 @@ def div(*args, **kwargs):
|
||||||
"""
|
"""
|
||||||
if len(args) > 1:
|
if len(args) > 1:
|
||||||
val1, val2 = args[0], 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.")
|
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):
|
def eval(*args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Usage $eval(<expression>)
|
Usage $eval(<expression>)
|
||||||
|
|
@ -247,16 +239,79 @@ def eval(*args, **kwargs):
|
||||||
- those will then be evaluated *after* $eval.
|
- 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)
|
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):
|
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):
|
if is_iter(val):
|
||||||
stype = type(val)
|
stype = type(val)
|
||||||
if stype == dict:
|
if stype == dict:
|
||||||
return {_recursive_parse(key): _recursive_parse(v) for key, v in val.items()}
|
return {_recursive_parse(key): _recursive_parse(v) for key, v in val.items()}
|
||||||
return stype((_recursive_parse(v) for v in val))
|
return stype((_recursive_parse(v) for v in val))
|
||||||
return protfunc_parser(val)
|
return _PROTLIB.protfunc_parser(val)
|
||||||
|
|
||||||
return _recursive_parse(struct)
|
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)
|
||||||
|
|
|
||||||
|
|
@ -5,17 +5,17 @@ Handling storage of prototypes, both database-based ones (DBPrototypes) and thos
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from ast import literal_eval
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
from evennia.scripts.scripts import DefaultScript
|
from evennia.scripts.scripts import DefaultScript
|
||||||
from evennia.objects.models import ObjectDB
|
from evennia.objects.models import ObjectDB
|
||||||
from evennia.utils.create import create_script
|
from evennia.utils.create import create_script
|
||||||
from evennia.utils.utils import (
|
from evennia.utils.utils import (
|
||||||
all_from_module, make_iter, is_iter, dbid_to_obj)
|
all_from_module, make_iter, is_iter, dbid_to_obj, callables_from_module)
|
||||||
from evennia.locks.lockhandler import validate_lockstring, check_lockstring
|
from evennia.locks.lockhandler import validate_lockstring, check_lockstring
|
||||||
from evennia.utils import logger
|
from evennia.utils import logger
|
||||||
|
from evennia.utils import inlinefuncs
|
||||||
from evennia.utils.evtable import EvTable
|
from evennia.utils.evtable import EvTable
|
||||||
from evennia.prototypes.protfuncs import protfunc_parser
|
|
||||||
|
|
||||||
|
|
||||||
_MODULE_PROTOTYPE_MODULES = {}
|
_MODULE_PROTOTYPE_MODULES = {}
|
||||||
|
|
@ -23,6 +23,7 @@ _MODULE_PROTOTYPES = {}
|
||||||
_PROTOTYPE_META_NAMES = ("prototype_key", "prototype_desc", "prototype_tags", "prototype_locks")
|
_PROTOTYPE_META_NAMES = ("prototype_key", "prototype_desc", "prototype_tags", "prototype_locks")
|
||||||
_PROTOTYPE_TAG_CATEGORY = "from_prototype"
|
_PROTOTYPE_TAG_CATEGORY = "from_prototype"
|
||||||
_PROTOTYPE_TAG_META_CATEGORY = "db_prototype"
|
_PROTOTYPE_TAG_META_CATEGORY = "db_prototype"
|
||||||
|
_PROT_FUNCS = {}
|
||||||
|
|
||||||
|
|
||||||
class PermissionError(RuntimeError):
|
class PermissionError(RuntimeError):
|
||||||
|
|
@ -36,6 +37,68 @@ class ValidationError(RuntimeError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# Protfunc parsing
|
||||||
|
|
||||||
|
for mod in settings.PROT_FUNC_MODULES:
|
||||||
|
try:
|
||||||
|
callables = callables_from_module(mod)
|
||||||
|
_PROT_FUNCS.update(callables)
|
||||||
|
except ImportError:
|
||||||
|
logger.log_trace()
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
def protfunc_parser(value, available_functions=None, testing=False, **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.
|
||||||
|
testing (bool, optional): Passed to protfunc. If in a testing mode, some protfuncs may
|
||||||
|
behave differently.
|
||||||
|
|
||||||
|
Kwargs:
|
||||||
|
session (Session): Passed to protfunc. Session of the entity spawning the prototype.
|
||||||
|
protototype (dict): Passed to protfunc. The dict this protfunc is a part of.
|
||||||
|
current_key(str): Passed to protfunc. The key in the prototype that will hold this value.
|
||||||
|
any (any): Passed on to the protfunc.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
testresult (tuple): If `testing` is set, returns a tuple (error, result) where error is
|
||||||
|
either None or a string detailing the error from protfunc_parser or seen when trying to
|
||||||
|
run `literal_eval` on the parsed string.
|
||||||
|
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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
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, testing=testing, **kwargs)
|
||||||
|
# at this point we have a string where all procfuncs were parsed
|
||||||
|
# print("parse_inlinefuncs(\"{}\", available_funcs={}) => {}".format(value, available_functions, result))
|
||||||
|
result = value_to_obj_or_any(result)
|
||||||
|
err = None
|
||||||
|
try:
|
||||||
|
result = literal_eval(result)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
except Exception as err:
|
||||||
|
err = str(err)
|
||||||
|
if testing:
|
||||||
|
return err, result
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
# helper functions
|
# helper functions
|
||||||
|
|
||||||
def value_to_obj(value, force=True):
|
def value_to_obj(value, force=True):
|
||||||
|
|
|
||||||
|
|
@ -167,7 +167,7 @@ class TestProtLib(EvenniaTest):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@override_settings(PROT_FUNC_MODULES=['evennia.prototypes.protfuncs'])
|
@override_settings(PROT_FUNC_MODULES=['evennia.prototypes.protfuncs'], CLIENT_DEFAULT_WIDTH=20)
|
||||||
class TestProtFuncs(EvenniaTest):
|
class TestProtFuncs(EvenniaTest):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|
@ -176,11 +176,55 @@ class TestProtFuncs(EvenniaTest):
|
||||||
"prototype_desc": "testing prot",
|
"prototype_desc": "testing prot",
|
||||||
"key": "ExampleObj"}
|
"key": "ExampleObj"}
|
||||||
|
|
||||||
@mock.patch("random.random", new=mock.MagicMock(return_value=0.5))
|
@mock.patch("evennia.prototypes.protfuncs.base_random", new=mock.MagicMock(return_value=0.5))
|
||||||
@mock.patch("random.randint", new=mock.MagicMock(return_value=5))
|
@mock.patch("evennia.prototypes.protfuncs.base_randint", new=mock.MagicMock(return_value=5))
|
||||||
def test_protfuncs(self):
|
def test_protfuncs(self):
|
||||||
self.assertEqual(protfuncs.protfunc_parser("$random()", 0.5))
|
self.assertEqual(protlib.protfunc_parser("$random()"), 0.5)
|
||||||
self.assertEqual(protfuncs.protfunc_parser("$randint(1, 10)", 5))
|
self.assertEqual(protlib.protfunc_parser("$randint(1, 10)"), 5)
|
||||||
|
self.assertEqual(protlib.protfunc_parser("$left_justify( foo )"), "foo ")
|
||||||
|
self.assertEqual(protlib.protfunc_parser("$right_justify( foo )"), " foo")
|
||||||
|
self.assertEqual(protlib.protfunc_parser("$center_justify(foo )"), " foo ")
|
||||||
|
self.assertEqual(protlib.protfunc_parser(
|
||||||
|
"$full_justify(foo bar moo too)"), 'foo bar moo too')
|
||||||
|
self.assertEqual(
|
||||||
|
protlib.protfunc_parser("$right_justify( foo )", testing=True),
|
||||||
|
('unexpected indent (<unknown>, line 1)', ' foo'))
|
||||||
|
|
||||||
|
test_prot = {"key1": "value1",
|
||||||
|
"key2": 2}
|
||||||
|
|
||||||
|
self.assertEqual(protlib.protfunc_parser(
|
||||||
|
"$protkey(key1)", testing=True, prototype=test_prot), (None, "value1"))
|
||||||
|
self.assertEqual(protlib.protfunc_parser(
|
||||||
|
"$protkey(key2)", testing=True, prototype=test_prot), (None, 2))
|
||||||
|
|
||||||
|
self.assertEqual(protlib.protfunc_parser("$add(1, 2)"), 3)
|
||||||
|
self.assertEqual(protlib.protfunc_parser("$add(10, 25)"), 35)
|
||||||
|
self.assertEqual(protlib.protfunc_parser(
|
||||||
|
"$add('''[1,2,3]''', '''[4,5,6]''')"), [1, 2, 3, 4, 5, 6])
|
||||||
|
self.assertEqual(protlib.protfunc_parser("$add(foo, bar)"), "foo bar")
|
||||||
|
|
||||||
|
self.assertEqual(protlib.protfunc_parser("$sub(5, 2)"), 3)
|
||||||
|
self.assertRaises(TypeError, protlib.protfunc_parser, "$sub(5, test)")
|
||||||
|
|
||||||
|
self.assertEqual(protlib.protfunc_parser("$mult(5, 2)"), 10)
|
||||||
|
self.assertEqual(protlib.protfunc_parser("$mult( 5 , 10)"), 50)
|
||||||
|
self.assertEqual(protlib.protfunc_parser("$mult('foo',3)"), "foofoofoo")
|
||||||
|
self.assertEqual(protlib.protfunc_parser("$mult(foo,3)"), "foofoofoo")
|
||||||
|
self.assertRaises(TypeError, protlib.protfunc_parser, "$mult(foo, foo)")
|
||||||
|
|
||||||
|
self.assertEqual(protlib.protfunc_parser("$toint(5.3)"), 5)
|
||||||
|
|
||||||
|
self.assertEqual(protlib.protfunc_parser("$div(5, 2)"), 2.5)
|
||||||
|
self.assertEqual(protlib.protfunc_parser("$toint($div(5, 2))"), 2)
|
||||||
|
self.assertEqual(protlib.protfunc_parser("$sub($add(5, 3), $add(10, 2))"), -4)
|
||||||
|
|
||||||
|
self.assertEqual(protlib.protfunc_parser("$eval('2')"), '2')
|
||||||
|
|
||||||
|
self.assertEqual(protlib.protfunc_parser(
|
||||||
|
"$eval(['test', 1, '2', 3.5, \"foo\"])"), ['test', 1, '2', 3.5, 'foo'])
|
||||||
|
self.assertEqual(protlib.protfunc_parser(
|
||||||
|
"$eval({'test': '1', 2:3, 3: $toint(3.5)})"), {'test': '1', 2: 3, 3: 3})
|
||||||
|
|
||||||
|
|
||||||
class TestPrototypeStorage(EvenniaTest):
|
class TestPrototypeStorage(EvenniaTest):
|
||||||
|
|
|
||||||
|
|
@ -161,13 +161,15 @@ def clr(*args, **kwargs):
|
||||||
def null(*args, **kwargs):
|
def null(*args, **kwargs):
|
||||||
return args[0] if args else ''
|
return args[0] if args else ''
|
||||||
|
|
||||||
|
_INLINE_FUNCS = {}
|
||||||
|
|
||||||
# we specify a default nomatch function to use if no matching func was
|
# we specify a default nomatch function to use if no matching func was
|
||||||
# found. This will be overloaded by any nomatch function defined in
|
# found. This will be overloaded by any nomatch function defined in
|
||||||
# the imported modules.
|
# the imported modules.
|
||||||
_INLINE_FUNCS = {"nomatch": lambda *args, **kwargs: "<UKNOWN>",
|
_DEFAULT_FUNCS = {"nomatch": lambda *args, **kwargs: "<UNKNOWN>",
|
||||||
"stackfull": lambda *args, **kwargs: "\n (not parsed: "
|
"stackfull": lambda *args, **kwargs: "\n (not parsed: "}
|
||||||
"inlinefunc stack size exceeded.)"}
|
|
||||||
|
|
||||||
|
_INLINE_FUNCS.update(_DEFAULT_FUNCS)
|
||||||
|
|
||||||
# load custom inline func modules.
|
# load custom inline func modules.
|
||||||
for module in utils.make_iter(settings.INLINEFUNC_MODULES):
|
for module in utils.make_iter(settings.INLINEFUNC_MODULES):
|
||||||
|
|
@ -285,6 +287,11 @@ def parse_inlinefunc(string, strip=False, available_funcs=None, **kwargs):
|
||||||
if not available_funcs:
|
if not available_funcs:
|
||||||
available_funcs = _INLINE_FUNCS
|
available_funcs = _INLINE_FUNCS
|
||||||
usecache = True
|
usecache = True
|
||||||
|
else:
|
||||||
|
# make sure the default keys are available, but also allow overriding
|
||||||
|
tmp = _DEFAULT_FUNCS.copy()
|
||||||
|
tmp.update(available_funcs)
|
||||||
|
available_funcs = tmp
|
||||||
|
|
||||||
if usecache and string in _PARSING_CACHE:
|
if usecache and string in _PARSING_CACHE:
|
||||||
# stack is already cached
|
# stack is already cached
|
||||||
|
|
@ -299,9 +306,14 @@ def parse_inlinefunc(string, strip=False, available_funcs=None, **kwargs):
|
||||||
# process string on stack
|
# process string on stack
|
||||||
ncallable = 0
|
ncallable = 0
|
||||||
nlparens = 0
|
nlparens = 0
|
||||||
|
|
||||||
|
# print("STRING: {} =>".format(string))
|
||||||
|
|
||||||
for match in _RE_TOKEN.finditer(string):
|
for match in _RE_TOKEN.finditer(string):
|
||||||
gdict = match.groupdict()
|
gdict = match.groupdict()
|
||||||
# print("match: {}".format({key: val for key, val in gdict.items() if val}))
|
|
||||||
|
# print(" MATCH: {}".format({key: val for key, val in gdict.items() if val}))
|
||||||
|
|
||||||
if gdict["singlequote"]:
|
if gdict["singlequote"]:
|
||||||
stack.append(gdict["singlequote"])
|
stack.append(gdict["singlequote"])
|
||||||
elif gdict["doublequote"]:
|
elif gdict["doublequote"]:
|
||||||
|
|
@ -386,10 +398,10 @@ def parse_inlinefunc(string, strip=False, available_funcs=None, **kwargs):
|
||||||
kwargs["inlinefunc_stack_depth"] = depth
|
kwargs["inlinefunc_stack_depth"] = depth
|
||||||
retval = "" if strip else func(*args, **kwargs)
|
retval = "" if strip else func(*args, **kwargs)
|
||||||
return utils.to_str(retval, force_string=True)
|
return utils.to_str(retval, force_string=True)
|
||||||
|
retval = "".join(_run_stack(item) for item in stack)
|
||||||
# print("STACK:\n{}".format(stack))
|
# print("STACK: \n{} => {}\n".format(stack, retval))
|
||||||
# execute the stack
|
# execute the stack
|
||||||
return "".join(_run_stack(item) for item in stack)
|
return retval
|
||||||
|
|
||||||
#
|
#
|
||||||
# Nick templating
|
# Nick templating
|
||||||
|
|
|
||||||
|
|
@ -43,8 +43,6 @@ _GA = object.__getattribute__
|
||||||
_SA = object.__setattr__
|
_SA = object.__setattr__
|
||||||
_DA = object.__delattr__
|
_DA = object.__delattr__
|
||||||
|
|
||||||
_DEFAULT_WIDTH = settings.CLIENT_DEFAULT_WIDTH
|
|
||||||
|
|
||||||
|
|
||||||
def is_iter(iterable):
|
def is_iter(iterable):
|
||||||
"""
|
"""
|
||||||
|
|
@ -80,7 +78,7 @@ def make_iter(obj):
|
||||||
return not hasattr(obj, '__iter__') and [obj] or obj
|
return not hasattr(obj, '__iter__') and [obj] or obj
|
||||||
|
|
||||||
|
|
||||||
def wrap(text, width=_DEFAULT_WIDTH, indent=0):
|
def wrap(text, width=None, indent=0):
|
||||||
"""
|
"""
|
||||||
Safely wrap text to a certain number of characters.
|
Safely wrap text to a certain number of characters.
|
||||||
|
|
||||||
|
|
@ -93,6 +91,7 @@ def wrap(text, width=_DEFAULT_WIDTH, indent=0):
|
||||||
text (str): Properly wrapped text.
|
text (str): Properly wrapped text.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
width = width if width else settings.CLIENT_DEFAULT_WIDTH
|
||||||
if not text:
|
if not text:
|
||||||
return ""
|
return ""
|
||||||
text = to_unicode(text)
|
text = to_unicode(text)
|
||||||
|
|
@ -104,7 +103,7 @@ def wrap(text, width=_DEFAULT_WIDTH, indent=0):
|
||||||
fill = wrap
|
fill = wrap
|
||||||
|
|
||||||
|
|
||||||
def pad(text, width=_DEFAULT_WIDTH, align="c", fillchar=" "):
|
def pad(text, width=None, align="c", fillchar=" "):
|
||||||
"""
|
"""
|
||||||
Pads to a given width.
|
Pads to a given width.
|
||||||
|
|
||||||
|
|
@ -119,6 +118,7 @@ def pad(text, width=_DEFAULT_WIDTH, align="c", fillchar=" "):
|
||||||
text (str): The padded text.
|
text (str): The padded text.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
width = width if width else settings.CLIENT_DEFAULT_WIDTH
|
||||||
align = align if align in ('c', 'l', 'r') else 'c'
|
align = align if align in ('c', 'l', 'r') else 'c'
|
||||||
fillchar = fillchar[0] if fillchar else " "
|
fillchar = fillchar[0] if fillchar else " "
|
||||||
if align == 'l':
|
if align == 'l':
|
||||||
|
|
@ -129,7 +129,7 @@ def pad(text, width=_DEFAULT_WIDTH, align="c", fillchar=" "):
|
||||||
return text.center(width, fillchar)
|
return text.center(width, fillchar)
|
||||||
|
|
||||||
|
|
||||||
def crop(text, width=_DEFAULT_WIDTH, suffix="[...]"):
|
def crop(text, width=None, suffix="[...]"):
|
||||||
"""
|
"""
|
||||||
Crop text to a certain width, throwing away text from too-long
|
Crop text to a certain width, throwing away text from too-long
|
||||||
lines.
|
lines.
|
||||||
|
|
@ -147,7 +147,7 @@ def crop(text, width=_DEFAULT_WIDTH, suffix="[...]"):
|
||||||
text (str): The cropped text.
|
text (str): The cropped text.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
width = width if width else settings.CLIENT_DEFAULT_WIDTH
|
||||||
utext = to_unicode(text)
|
utext = to_unicode(text)
|
||||||
ltext = len(utext)
|
ltext = len(utext)
|
||||||
if ltext <= width:
|
if ltext <= width:
|
||||||
|
|
@ -179,7 +179,7 @@ def dedent(text):
|
||||||
return textwrap.dedent(text)
|
return textwrap.dedent(text)
|
||||||
|
|
||||||
|
|
||||||
def justify(text, width=_DEFAULT_WIDTH, align="f", indent=0):
|
def justify(text, width=None, align="f", indent=0):
|
||||||
"""
|
"""
|
||||||
Fully justify a text so that it fits inside `width`. When using
|
Fully justify a text so that it fits inside `width`. When using
|
||||||
full justification (default) this will be done by padding between
|
full justification (default) this will be done by padding between
|
||||||
|
|
@ -198,6 +198,7 @@ def justify(text, width=_DEFAULT_WIDTH, align="f", indent=0):
|
||||||
justified (str): The justified and indented block of text.
|
justified (str): The justified and indented block of text.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
width = width if width else settings.CLIENT_DEFAULT_WIDTH
|
||||||
|
|
||||||
def _process_line(line):
|
def _process_line(line):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue