Replace inlinefunc parser with FuncParser mostly

This commit is contained in:
Griatch 2021-03-17 23:44:08 +01:00
parent 8c3910a033
commit 773bb31f55
10 changed files with 294 additions and 322 deletions

View file

@ -7,7 +7,7 @@ from django.db.models import Q, Min, Max
from evennia.objects.models import ObjectDB from evennia.objects.models import ObjectDB
from evennia.locks.lockhandler import LockException from evennia.locks.lockhandler import LockException
from evennia.commands.cmdhandler import get_and_merge_cmdsets from evennia.commands.cmdhandler import get_and_merge_cmdsets
from evennia.utils import create, utils, search, logger from evennia.utils import create, utils, search, logger, funcparser
from evennia.utils.utils import ( from evennia.utils.utils import (
inherits_from, inherits_from,
class_from_module, class_from_module,
@ -22,10 +22,11 @@ from evennia.utils.eveditor import EvEditor
from evennia.utils.evmore import EvMore from evennia.utils.evmore import EvMore
from evennia.prototypes import spawner, prototypes as protlib, menus as olc_menus from evennia.prototypes import spawner, prototypes as protlib, menus as olc_menus
from evennia.utils.ansi import raw as ansi_raw from evennia.utils.ansi import raw as ansi_raw
from evennia.utils.inlinefuncs import raw as inlinefunc_raw
COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS) COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)
_FUNCPARSER = funcparser.FuncParser(settings.INLINEFUNC_MODULES)
# limit symbol import for API # limit symbol import for API
__all__ = ( __all__ = (
"ObjManipCommand", "ObjManipCommand",
@ -2385,7 +2386,7 @@ class CmdExamine(ObjManipCommand):
value = utils.to_str(value) value = utils.to_str(value)
if crop: if crop:
value = utils.crop(value) value = utils.crop(value)
value = inlinefunc_raw(ansi_raw(value)) value = _FUNCPARSER.parse(ansi_raw(value), escape=True)
if category: if category:
return f"{attr}[{category}] = {value}" return f"{attr}[{category}] = {value}"
else: else:

View file

@ -151,6 +151,7 @@ def add(*args, **kwargs):
val1, val2 = args[0], args[1] val1, val2 = args[0], args[1]
# try to convert to python structures, otherwise, keep as strings # try to convert to python structures, otherwise, keep as strings
try: try:
print("val1", val1, type(literal_eval(val1)))
val1 = literal_eval(val1.strip()) val1 = literal_eval(val1.strip())
except Exception: except Exception:
pass pass

View file

@ -30,6 +30,7 @@ from evennia.utils.utils import (
) )
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.funcparser import FuncParser
from evennia.utils import inlinefuncs, dbserialize from evennia.utils import inlinefuncs, dbserialize
from evennia.utils.evtable import EvTable from evennia.utils.evtable import EvTable
@ -758,9 +759,11 @@ def protfunc_parser(value, available_functions=None, testing=False, stacktrace=F
available_functions = PROT_FUNCS if available_functions is None else available_functions available_functions = PROT_FUNCS if available_functions is None else available_functions
result = inlinefuncs.parse_inlinefunc( result = FuncParser(available_functions).parse(value, raise_errors=True, **kwargs)
value, available_funcs=available_functions, stacktrace=stacktrace, testing=testing, **kwargs
) # result = inlinefuncs.parse_inlinefunc(
# value, available_funcs=available_functions, stacktrace=stacktrace, testing=testing, **kwargs
# )
err = None err = None
try: try:

View file

@ -377,7 +377,7 @@ class TestProtFuncs(EvenniaTest):
self.assertEqual(protlib.protfunc_parser("$add(1, 2)"), 3) self.assertEqual(protlib.protfunc_parser("$add(1, 2)"), 3)
self.assertEqual(protlib.protfunc_parser("$add(10, 25)"), 35) self.assertEqual(protlib.protfunc_parser("$add(10, 25)"), 35)
self.assertEqual( self.assertEqual(
protlib.protfunc_parser("$add('''[1,2,3]''', '''[4,5,6]''')"), [1, 2, 3, 4, 5, 6] 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("$add(foo, bar)"), "foo bar")

View file

@ -28,7 +28,7 @@ from evennia.utils.utils import (
from evennia.server.portal import amp from evennia.server.portal import amp
from evennia.server.signals import SIGNAL_ACCOUNT_POST_LOGIN, SIGNAL_ACCOUNT_POST_LOGOUT from evennia.server.signals import SIGNAL_ACCOUNT_POST_LOGIN, SIGNAL_ACCOUNT_POST_LOGOUT
from evennia.server.signals import SIGNAL_ACCOUNT_POST_FIRST_LOGIN, SIGNAL_ACCOUNT_POST_LAST_LOGOUT from evennia.server.signals import SIGNAL_ACCOUNT_POST_FIRST_LOGIN, SIGNAL_ACCOUNT_POST_LAST_LOGOUT
from evennia.utils.inlinefuncs import parse_inlinefunc # from evennia.utils.inlinefuncs import parse_inlinefunc
from codecs import decode as codecs_decode from codecs import decode as codecs_decode
_INLINEFUNC_ENABLED = settings.INLINEFUNC_ENABLED _INLINEFUNC_ENABLED = settings.INLINEFUNC_ENABLED
@ -59,6 +59,9 @@ _DELAY_CMD_LOGINSTART = settings.DELAY_CMD_LOGINSTART
_MAX_SERVER_COMMANDS_PER_SECOND = 100.0 _MAX_SERVER_COMMANDS_PER_SECOND = 100.0
_MAX_SESSION_COMMANDS_PER_SECOND = 5.0 _MAX_SESSION_COMMANDS_PER_SECOND = 5.0
_MODEL_MAP = None _MODEL_MAP = None
_FUNCPARSER = None
# input handlers # input handlers
@ -171,6 +174,11 @@ class SessionHandler(dict):
applied. applied.
""" """
global _FUNCPARSER
if not _FUNCPARSER:
from evennia.utils.funcparser import FuncParser
_FUNCPARSER = FuncParser(settings.INLINEFUNC_MODULES, raise_errors=True)
options = kwargs.pop("options", None) or {} options = kwargs.pop("options", None) or {}
raw = options.get("raw", False) raw = options.get("raw", False)
strip_inlinefunc = options.get("strip_inlinefunc", False) strip_inlinefunc = options.get("strip_inlinefunc", False)
@ -204,7 +212,8 @@ class SessionHandler(dict):
if _INLINEFUNC_ENABLED and not raw and isinstance(self, ServerSessionHandler): if _INLINEFUNC_ENABLED and not raw and isinstance(self, ServerSessionHandler):
# only parse inlinefuncs on the outgoing path (sessionhandler->) # only parse inlinefuncs on the outgoing path (sessionhandler->)
data = parse_inlinefunc(data, strip=strip_inlinefunc, session=session) # data = parse_inlinefunc(data, strip=strip_inlinefunc, session=session)
data = _FUNCPARSER.parse(data, strip=strip_inlinefunc, session=session)
return str(data) return str(data)
elif ( elif (

View file

@ -149,9 +149,13 @@ class FuncParser:
""" """
for funcname, clble in callables.items(): for funcname, clble in callables.items():
mapping = inspect.getfullargspec(clble) try:
assert mapping.varargs, f"Parse-func callable '{funcname}' does not support *args." mapping = inspect.getfullargspec(clble)
assert mapping.varkw, f"Parse-func callable '{funcname}' does not support **kwargs." except TypeError:
logger.log_trace(f"Could not run getfullargspec on {funcname}: {clble}")
else:
assert mapping.varargs, f"Parse-func callable '{funcname}' does not support *args."
assert mapping.varkw, f"Parse-func callable '{funcname}' does not support **kwargs."
def execute(self, parsedfunc, raise_errors=False, **reserved_kwargs): def execute(self, parsedfunc, raise_errors=False, **reserved_kwargs):
""" """
@ -207,7 +211,7 @@ class FuncParser:
raise raise
return str(parsedfunc) return str(parsedfunc)
def parse(self, string, raise_errors=False, **reserved_kwargs): def parse(self, string, raise_errors=False, escape=False, strip=False, **reserved_kwargs):
""" """
Use parser to parse a string that may or may not have `$funcname(*args, **kwargs)` Use parser to parse a string that may or may not have `$funcname(*args, **kwargs)`
- style tokens in it. Only the callables used to initiate the parser - style tokens in it. Only the callables used to initiate the parser
@ -215,10 +219,14 @@ class FuncParser:
Args: Args:
string (str): The string to parse. string (str): The string to parse.
raise_errors (bool, optional): By default, a failing parse just raise_errors (bool, optional): By default, a failing parse just
means not parsing the string but leaving it as-is. If this is means not parsing the string but leaving it as-is. If this is
`True`, errors (like not closing brackets) will lead to an `True`, errors (like not closing brackets) will lead to an
ParsingError. ParsingError.
escape (bool, optional): If set, escape all found functions so they
are not executed by later parsing.
strip (bool, optional): If set, strip any inline funcs from string
as if they were not there.
**reserved_kwargs: If given, these are guaranteed to _always_ pass **reserved_kwargs: If given, these are guaranteed to _always_ pass
as part of each parsed callable's **kwargs. These override as part of each parsed callable's **kwargs. These override
same-named default options given in `__init__` as well as any same-named default options given in `__init__` as well as any
@ -371,10 +379,18 @@ class FuncParser:
# closing the function list - this means we have a # closing the function list - this means we have a
# ready function-def to run. # ready function-def to run.
open_lparens = 0 open_lparens = 0
infuncstr = self.execute(
curr_func, raise_errors=raise_errors, **reserved_kwargs)
curr_func = None if strip:
# remove function as if it returned empty
infuncstr = ''
elif escape:
# get function and set it as escaped
infuncstr = escape_char + curr_func.fullstr
else:
# execute the function
infuncstr = self.execute(
curr_func, raise_errors=raise_errors, **reserved_kwargs)
if callstack: if callstack:
# unnest the higher-level funcdef from stack # unnest the higher-level funcdef from stack
# and continue where we were # and continue where we were

View file

@ -357,269 +357,173 @@ class ParseStack(list):
self._string_last = False self._string_last = False
class InlinefuncError(RuntimeError): # class InlinefuncError(RuntimeError):
pass # pass
def parse_inlinefunc(string, strip=False, available_funcs=None, stacktrace=False, **kwargs):
"""
Parse the incoming string.
Args:
string (str): The incoming string to parse.
strip (bool, optional): Whether to strip function calls rather than
execute them.
available_funcs (dict, optional): Define an alternative source of functions to parse for.
If unset, use the functions found through `settings.INLINEFUNC_MODULES`.
stacktrace (bool, optional): If set, print the stacktrace to log.
Keyword Args:
session (Session): This is sent to this function by Evennia when triggering
it. It is passed to the inlinefunc.
kwargs (any): All other kwargs are also passed on to the inlinefunc.
"""
global _PARSING_CACHE
usecache = False
if not available_funcs:
available_funcs = _INLINE_FUNCS
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:
# stack is already cached
stack = _PARSING_CACHE[string]
elif not _RE_STARTTOKEN.search(string):
# if there are no unescaped start tokens at all, return immediately.
return string
else:
# no cached stack; build a new stack and continue
stack = ParseStack()
# process string on stack
ncallable = 0
nlparens = 0
nvalid = 0
if stacktrace:
out = "STRING: {} =>".format(string)
print(out)
logger.log_info(out)
for match in _RE_TOKEN.finditer(string):
gdict = match.groupdict()
if stacktrace:
out = " MATCH: {}".format({key: val for key, val in gdict.items() if val})
print(out)
logger.log_info(out)
if gdict["singlequote"]:
stack.append(gdict["singlequote"])
elif gdict["doublequote"]:
stack.append(gdict["doublequote"])
elif gdict["leftparens"]:
# we have a left-parens inside a callable
if ncallable:
nlparens += 1
stack.append("(")
elif gdict["end"]:
if nlparens > 0:
nlparens -= 1
stack.append(")")
continue
if ncallable <= 0:
stack.append(")")
continue
args = []
while stack:
operation = stack.pop()
if callable(operation):
if not strip:
stack.append((operation, [arg for arg in reversed(args)]))
ncallable -= 1
break
else:
args.append(operation)
elif gdict["start"]:
funcname = _RE_STARTTOKEN.match(gdict["start"]).group(1)
try:
# try to fetch the matching inlinefunc from storage
stack.append(available_funcs[funcname])
nvalid += 1
except KeyError:
stack.append(available_funcs["nomatch"])
stack.append(funcname)
stack.append(None)
ncallable += 1
elif gdict["escaped"]:
# escaped tokens
token = gdict["escaped"].lstrip("\\")
stack.append(token)
elif gdict["comma"]:
if ncallable > 0:
# commas outside strings and inside a callable are
# used to mark argument separation - we use None
# in the stack to indicate such a separation.
stack.append(None)
else:
# no callable active - just a string
stack.append(",")
else:
# the rest
stack.append(gdict["rest"])
if ncallable > 0:
# this means not all inlinefuncs were complete
return string
if _STACK_MAXSIZE > 0 and _STACK_MAXSIZE < nvalid:
# if stack is larger than limit, throw away parsing
return string + available_funcs["stackfull"](*args, **kwargs)
elif usecache:
# cache the stack - we do this also if we don't check the cache above
_PARSING_CACHE[string] = stack
# run the stack recursively
def _run_stack(item, depth=0):
retval = item
if isinstance(item, tuple):
if strip:
return ""
else:
func, arglist = item
args = [""]
for arg in arglist:
if arg is None:
# an argument-separating comma - start a new arg
args.append("")
else:
# all other args should merge into one string
args[-1] += _run_stack(arg, depth=depth + 1)
# execute the inlinefunc at this point or strip it.
kwargs["inlinefunc_stack_depth"] = depth
retval = "" if strip else func(*args, **kwargs)
return utils.to_str(retval)
retval = "".join(_run_stack(item) for item in stack)
if stacktrace:
out = "STACK: \n{} => {}\n".format(stack, retval)
print(out)
logger.log_info(out)
# execute the stack
return retval
def raw(string):
"""
Escape all inlinefuncs in a string so they won't get parsed.
Args:
string (str): String with inlinefuncs to escape.
"""
def _escape(match):
return "\\" + match.group(0)
return _RE_STARTTOKEN.sub(_escape, string)
# #
# Nick templating
# #
# def parse_inlinefunc(string, strip=False, available_funcs=None, stacktrace=False, **kwargs):
# """
""" # Parse the incoming string.
This supports the use of replacement templates in nicks: #
# Args:
This happens in two steps: # string (str): The incoming string to parse.
# strip (bool, optional): Whether to strip function calls rather than
1) The user supplies a template that is converted to a regex according # execute them.
to the unix-like templating language. # available_funcs (dict, optional): Define an alternative source of functions to parse for.
2) This regex is tested against nicks depending on which nick replacement # If unset, use the functions found through `settings.INLINEFUNC_MODULES`.
strategy is considered (most commonly inputline). # stacktrace (bool, optional): If set, print the stacktrace to log.
3) If there is a template match and there are templating markers, # Keyword Args:
these are replaced with the arguments actually given. # session (Session): This is sent to this function by Evennia when triggering
# it. It is passed to the inlinefunc.
@desc $1 $2 $3 # kwargs (any): All other kwargs are also passed on to the inlinefunc.
#
This will be converted to the following regex: #
# """
\@desc (?P<1>\w+) (?P<2>\w+) $(?P<3>\w+) # global _PARSING_CACHE
# usecache = False
Supported template markers (through fnmatch) # if not available_funcs:
* matches anything (non-greedy) -> .*? # available_funcs = _INLINE_FUNCS
? matches any single character -> # usecache = True
[seq] matches any entry in sequence # else:
[!seq] matches entries not in sequence # # make sure the default keys are available, but also allow overriding
Custom arg markers # tmp = _DEFAULT_FUNCS.copy()
$N argument position (1-99) # tmp.update(available_funcs)
# available_funcs = tmp
""" #
_RE_NICK_ARG = re.compile(r"\\(\$)([1-9][0-9]?)") # if usecache and string in _PARSING_CACHE:
_RE_NICK_TEMPLATE_ARG = re.compile(r"(\$)([1-9][0-9]?)") # # stack is already cached
_RE_NICK_SPACE = re.compile(r"\\ ") # stack = _PARSING_CACHE[string]
# elif not _RE_STARTTOKEN.search(string):
# # if there are no unescaped start tokens at all, return immediately.
class NickTemplateInvalid(ValueError): # return string
pass # else:
# # no cached stack; build a new stack and continue
# stack = ParseStack()
def initialize_nick_templates(in_template, out_template): #
""" # # process string on stack
Initialize the nick templates for matching and remapping a string. # ncallable = 0
# nlparens = 0
Args: # nvalid = 0
in_template (str): The template to be used for nick recognition. #
out_template (str): The template to be used to replace the string # if stacktrace:
matched by the in_template. # out = "STRING: {} =>".format(string)
# print(out)
Returns: # logger.log_info(out)
regex (regex): Regex to match against strings #
template (str): Template with markers {arg1}, {arg2}, etc for # for match in _RE_TOKEN.finditer(string):
replacement using the standard .format method. # gdict = match.groupdict()
#
Raises: # if stacktrace:
evennia.utils.inlinefuncs.NickTemplateInvalid: If the in/out template # out = " MATCH: {}".format({key: val for key, val in gdict.items() if val})
does not have a matching number of $args. # print(out)
# logger.log_info(out)
""" #
# create the regex for in_template # if gdict["singlequote"]:
regex_string = fnmatch.translate(in_template) # stack.append(gdict["singlequote"])
n_inargs = len(_RE_NICK_ARG.findall(regex_string)) # elif gdict["doublequote"]:
regex_string = _RE_NICK_SPACE.sub("\s+", regex_string) # stack.append(gdict["doublequote"])
regex_string = _RE_NICK_ARG.sub(lambda m: "(?P<arg%s>.+?)" % m.group(2), regex_string) # elif gdict["leftparens"]:
# # we have a left-parens inside a callable
# create the out_template # if ncallable:
template_string = _RE_NICK_TEMPLATE_ARG.sub(lambda m: "{arg%s}" % m.group(2), out_template) # nlparens += 1
# stack.append("(")
# validate the tempaltes - they should at least have the same number of args # elif gdict["end"]:
n_outargs = len(_RE_NICK_TEMPLATE_ARG.findall(out_template)) # if nlparens > 0:
if n_inargs != n_outargs: # nlparens -= 1
raise NickTemplateInvalid # stack.append(")")
# continue
return re.compile(regex_string), template_string # if ncallable <= 0:
# stack.append(")")
# continue
def parse_nick_template(string, template_regex, outtemplate): # args = []
""" # while stack:
Parse a text using a template and map it to another template # operation = stack.pop()
# if callable(operation):
Args: # if not strip:
string (str): The input string to processj # stack.append((operation, [arg for arg in reversed(args)]))
template_regex (regex): A template regex created with # ncallable -= 1
initialize_nick_template. # break
outtemplate (str): The template to which to map the matches # else:
produced by the template_regex. This should have $1, $2, # args.append(operation)
etc to match the regex. # elif gdict["start"]:
# funcname = _RE_STARTTOKEN.match(gdict["start"]).group(1)
""" # try:
match = template_regex.match(string) # # try to fetch the matching inlinefunc from storage
if match: # stack.append(available_funcs[funcname])
return outtemplate.format(**match.groupdict()) # nvalid += 1
return string # except KeyError:
# stack.append(available_funcs["nomatch"])
# stack.append(funcname)
# stack.append(None)
# ncallable += 1
# elif gdict["escaped"]:
# # escaped tokens
# token = gdict["escaped"].lstrip("\\")
# stack.append(token)
# elif gdict["comma"]:
# if ncallable > 0:
# # commas outside strings and inside a callable are
# # used to mark argument separation - we use None
# # in the stack to indicate such a separation.
# stack.append(None)
# else:
# # no callable active - just a string
# stack.append(",")
# else:
# # the rest
# stack.append(gdict["rest"])
#
# if ncallable > 0:
# # this means not all inlinefuncs were complete
# return string
#
# if _STACK_MAXSIZE > 0 and _STACK_MAXSIZE < nvalid:
# # if stack is larger than limit, throw away parsing
# return string + available_funcs["stackfull"](*args, **kwargs)
# elif usecache:
# # cache the stack - we do this also if we don't check the cache above
# _PARSING_CACHE[string] = stack
#
# # run the stack recursively
# def _run_stack(item, depth=0):
# retval = item
# if isinstance(item, tuple):
# if strip:
# return ""
# else:
# func, arglist = item
# args = [""]
# for arg in arglist:
# if arg is None:
# # an argument-separating comma - start a new arg
# args.append("")
# else:
# # all other args should merge into one string
# args[-1] += _run_stack(arg, depth=depth + 1)
# # execute the inlinefunc at this point or strip it.
# kwargs["inlinefunc_stack_depth"] = depth
# retval = "" if strip else func(*args, **kwargs)
# return utils.to_str(retval)
#
# retval = "".join(_run_stack(item) for item in stack)
# if stacktrace:
# out = "STACK: \n{} => {}\n".format(stack, retval)
# print(out)
# logger.log_info(out)
#
# # execute the stack
# return retval
#
#
# def raw(string):
# """
# Escape all inlinefuncs in a string so they won't get parsed.
#
# Args:
# string (str): String with inlinefuncs to escape.
# """
#
# def _escape(match):
# return "\\" + match.group(0)
#
# return _RE_STARTTOKEN.sub(_escape, string)

View file

@ -7,7 +7,7 @@ Test the funcparser module.
import time import time
from simpleeval import simple_eval from simpleeval import simple_eval
from parameterized import parameterized from parameterized import parameterized
from django.test import TestCase from django.test import TestCase, override_settings
from evennia.utils import funcparser from evennia.utils import funcparser
@ -135,22 +135,44 @@ class TestFuncParser(TestCase):
Test parsing of string. Test parsing of string.
""" """
t0 = time.time() #t0 = time.time()
# from evennia import set_trace;set_trace() # from evennia import set_trace;set_trace()
ret = self.parser.parse(string) ret = self.parser.parse(string)
t1 = time.time() #t1 = time.time()
print(f"time: {(t1-t0)*1000} ms") #print(f"time: {(t1-t0)*1000} ms")
self.assertEqual(expected, ret) self.assertEqual(expected, ret)
def test_parse_raise(self): def test_parse_raise(self):
"""
Make sure error is raised if told to do so.
"""
string = "Test invalid $dummy()" string = "Test invalid $dummy()"
with self.assertRaises(funcparser.ParsingError): with self.assertRaises(funcparser.ParsingError):
self.parser.parse(string, raise_errors=True) self.parser.parse(string, raise_errors=True)
def test_parse_strip(self):
"""
Test the parser's strip functionality.
"""
string = "Test $foo(a,b, $bar()) and $repl($eval(3+2)) things"
ret = self.parser.parse(string, strip=True)
self.assertEqual("Test and things", ret)
def test_parse_escape(self):
"""
Test the parser's escape functionality.
"""
string = "Test $foo(a) and $bar() and $rep(c) things"
ret = self.parser.parse(string, escape=True)
self.assertEqual("Test \$foo(a) and \$bar() and \$rep(c) things", ret)
def test_kwargs_overrides(self): def test_kwargs_overrides(self):
""" """
Test so default kwargs are added and overridden properly Test so default kwargs are added and overridden properly
""" """
# default kwargs passed on initializations # default kwargs passed on initializations
parser = funcparser.FuncParser( parser = funcparser.FuncParser(
@ -174,3 +196,40 @@ class TestFuncParser(TestCase):
ret = parser.parse("This is a $foo(foo=moo) string", foo="bar") ret = parser.parse("This is a $foo(foo=moo) string", foo="bar")
self.assertEqual("This is a _test(test=foo, foo=bar) string", ret) self.assertEqual("This is a _test(test=foo, foo=bar) string", ret)
class TestDefaultCallables(TestCase):
"""
Test default callables
"""
@override_settings(INLINEFUNC_MODULES=["evennia.prototypes.protfuncs",
"evennia.utils.inlinefuncs"])
def setUp(self):
from django.conf import settings
self.parser = funcparser.FuncParser(settings.INLINEFUNC_MODULES)
@parameterized.expand([
("Test $pad(Hello, 20, c, -) there", "Test -------Hello-------- there"),
("Test $crop(This is a long test, 12)", "Test This is[...]"),
("Some $space(10) here", "Some here"),
("Some $clr(b, blue color) now", "Some |bblue color|n now"),
("Some $add(1, 2) things", "Some 3 things"),
("Some $sub(10, 2) things", "Some 8 things"),
("Some $mult(3, 2) things", "Some 6 things"),
("Some $div(6, 2) things", "Some 3.0 things"),
("Some $toint(6) things", "Some 6 things"),
])
def test_callable(self, string, expected):
"""
Test default callables.
"""
ret = self.parser.parse(string, raise_errors=True)
self.assertEqual(expected, ret)
def test_random(self):
string = "$random(1,10)"
ret = self.parser.parse(string, raise_errors=True)
ret = int(ret)
self.assertTrue(1 <= ret <= 10)

View file

@ -3,10 +3,10 @@ Unit tests for all sorts of inline text-tag parsing, like ANSI, html conversion,
""" """
import re import re
from django.test import TestCase from django.test import TestCase, override_settings
from evennia.utils.ansi import ANSIString from evennia.utils.ansi import ANSIString
from evennia.utils.text2html import TextToHTMLparser from evennia.utils.text2html import TextToHTMLparser
from evennia.utils import inlinefuncs from evennia.utils import funcparser
class ANSIStringTestCase(TestCase): class ANSIStringTestCase(TestCase):
@ -352,27 +352,33 @@ class TestTextToHTMLparser(TestCase):
class TestInlineFuncs(TestCase): class TestInlineFuncs(TestCase):
"""Test the nested inlinefunc module""" """Test the nested inlinefunc module"""
@override_settings(INLINEFUNC_MODULES=["evennia.utils.inlinefuncs"])
def setUp(self):
from django.conf import settings
self.parser = funcparser.FuncParser(settings.INLINEFUNC_MODULES)
def test_nofunc(self): def test_nofunc(self):
self.assertEqual( self.assertEqual(
inlinefuncs.parse_inlinefunc("as$382ewrw w we w werw,|44943}"), self.parser.parse("as$382ewrw w we w werw,|44943}"),
"as$382ewrw w we w werw,|44943}", "as$382ewrw w we w werw,|44943}",
) )
def test_incomplete(self): def test_incomplete(self):
self.assertEqual( self.assertEqual(
inlinefuncs.parse_inlinefunc("testing $blah{without an ending."), self.parser.parse("testing $blah{without an ending."),
"testing $blah{without an ending.", "testing $blah{without an ending.",
) )
def test_single_func(self): def test_single_func(self):
self.assertEqual( self.assertEqual(
inlinefuncs.parse_inlinefunc("this is a test with $pad(centered, 20) text in it."), self.parser.parse("this is a test with $pad(centered, 20) text in it."),
"this is a test with centered text in it.", "this is a test with centered text in it.",
) )
def test_nested(self): def test_nested(self):
self.assertEqual( self.assertEqual(
inlinefuncs.parse_inlinefunc( self.parser.parse(
"this $crop(is a test with $pad(padded, 20) text in $pad(pad2, 10) a crop, 80)" "this $crop(is a test with $pad(padded, 20) text in $pad(pad2, 10) a crop, 80)"
), ),
"this is a test with padded text in pad2 a crop", "this is a test with padded text in pad2 a crop",
@ -380,16 +386,16 @@ class TestInlineFuncs(TestCase):
def test_escaped(self): def test_escaped(self):
self.assertEqual( self.assertEqual(
inlinefuncs.parse_inlinefunc( self.parser.parse(
"this should be $pad('''escaped,''' and '''instead,''' cropped $crop(with a long,5) text., 80)" "this should be $pad('''escaped,''' and '''instead,''' cropped $crop(with a long,5) text., 80)"
), ),
"this should be escaped, and instead, cropped with text. ", "this should be '''escaped,''' and '''instead,''' cropped with text. ",
) )
def test_escaped2(self): def test_escaped2(self):
self.assertEqual( self.assertEqual(
inlinefuncs.parse_inlinefunc( self.parser.parse(
'this should be $pad("""escaped,""" and """instead,""" cropped $crop(with a long,5) text., 80)' 'this should be $pad("""escaped,""" and """instead,""" cropped $crop(with a long,5) text., 80)'
), ),
"this should be escaped, and instead, cropped with text. ", "this should be \"\"\"escaped,\"\"\" and \"\"\"instead,\"\"\" cropped with text. ",
) )

View file

@ -385,30 +385,3 @@ class TestPercent(TestCase):
self.assertEqual(utils.percent(3, 1, 1), "0.0%") self.assertEqual(utils.percent(3, 1, 1), "0.0%")
self.assertEqual(utils.percent(3, 0, 1), "100.0%") self.assertEqual(utils.percent(3, 0, 1), "100.0%")
self.assertEqual(utils.percent(-3, 0, 1), "0.0%") self.assertEqual(utils.percent(-3, 0, 1), "0.0%")
class ParseArgumentsTest(TestCase):
def _run_test(s):
return utils.parse_arguments(s)
def test_happy_flow(self):
s = "1, \"The text \\\"Hello, world.\\\" is often used by programmers to test if their code works.\", caller, looker=\"Qwerty\""
args, kwargs = ParseArgumentsTest._run_test(s)
self.assertEqual(len(args), 3)
self.assertEqual(args[0], 1)
self.assertEqual(args[1], "The text \"Hello, world.\" is often used by programmers to test if their code works.")
#self.assertEqual(args[2], "caller")
self.assertEqual(len(kwargs), 1)
self.assertEqual(kwargs["looker"], "Qwerty")
def test_malformed_string(self):
s = ",(,),"
args, kwargs = ParseArgumentsTest._run_test(s)
self.assertEqual(len(args), 4)
self.assertEqual(args[0], "")
self.assertEqual(args[1].__class__, utils.FunctionArgument)
self.assertEqual(args[1].name, "(")
self.assertEqual(args[2].__class__, utils.FunctionArgument)
self.assertEqual(args[2].name, ")")
self.assertEqual(args[3], "")
self.assertEqual(len(kwargs), 0)