From 8924304d80d21e7d8ef1fa55df1af4e78b5a3383 Mon Sep 17 00:00:00 2001 From: Griatch Date: Wed, 17 Mar 2021 23:44:08 +0100 Subject: [PATCH] Replace inlinefunc parser with FuncParser mostly --- evennia/commands/default/building.py | 7 +- evennia/prototypes/protfuncs.py | 1 + evennia/prototypes/prototypes.py | 9 +- evennia/prototypes/tests.py | 2 +- evennia/server/sessionhandler.py | 13 +- evennia/utils/funcparser.py | 32 +- evennia/utils/inlinefuncs.py | 432 ++++++++++--------------- evennia/utils/tests/test_funcparser.py | 67 +++- evennia/utils/tests/test_tagparsing.py | 26 +- evennia/utils/tests/test_utils.py | 27 -- 10 files changed, 294 insertions(+), 322 deletions(-) diff --git a/evennia/commands/default/building.py b/evennia/commands/default/building.py index 50b57e41ff..f9f5d0cd8d 100644 --- a/evennia/commands/default/building.py +++ b/evennia/commands/default/building.py @@ -7,7 +7,7 @@ from django.db.models import Q, Min, Max from evennia.objects.models import ObjectDB from evennia.locks.lockhandler import LockException 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 ( inherits_from, class_from_module, @@ -22,10 +22,11 @@ from evennia.utils.eveditor import EvEditor from evennia.utils.evmore import EvMore from evennia.prototypes import spawner, prototypes as protlib, menus as olc_menus 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) +_FUNCPARSER = funcparser.FuncParser(settings.INLINEFUNC_MODULES) + # limit symbol import for API __all__ = ( "ObjManipCommand", @@ -2385,7 +2386,7 @@ class CmdExamine(ObjManipCommand): value = utils.to_str(value) if crop: value = utils.crop(value) - value = inlinefunc_raw(ansi_raw(value)) + value = _FUNCPARSER.parse(ansi_raw(value), escape=True) if category: return f"{attr}[{category}] = {value}" else: diff --git a/evennia/prototypes/protfuncs.py b/evennia/prototypes/protfuncs.py index e22fd5a46a..56b7d070ad 100644 --- a/evennia/prototypes/protfuncs.py +++ b/evennia/prototypes/protfuncs.py @@ -151,6 +151,7 @@ def add(*args, **kwargs): val1, val2 = args[0], args[1] # try to convert to python structures, otherwise, keep as strings try: + print("val1", val1, type(literal_eval(val1))) val1 = literal_eval(val1.strip()) except Exception: pass diff --git a/evennia/prototypes/prototypes.py b/evennia/prototypes/prototypes.py index 185b9dfc5d..eba1fe7f06 100644 --- a/evennia/prototypes/prototypes.py +++ b/evennia/prototypes/prototypes.py @@ -30,6 +30,7 @@ from evennia.utils.utils import ( ) from evennia.locks.lockhandler import validate_lockstring, check_lockstring from evennia.utils import logger +from evennia.utils.funcparser import FuncParser from evennia.utils import inlinefuncs, dbserialize 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 - result = inlinefuncs.parse_inlinefunc( - value, available_funcs=available_functions, stacktrace=stacktrace, testing=testing, **kwargs - ) + result = FuncParser(available_functions).parse(value, raise_errors=True, **kwargs) + + # result = inlinefuncs.parse_inlinefunc( + # value, available_funcs=available_functions, stacktrace=stacktrace, testing=testing, **kwargs + # ) err = None try: diff --git a/evennia/prototypes/tests.py b/evennia/prototypes/tests.py index f7eac5d912..bbda63a36d 100644 --- a/evennia/prototypes/tests.py +++ b/evennia/prototypes/tests.py @@ -377,7 +377,7 @@ class TestProtFuncs(EvenniaTest): 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] + 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") diff --git a/evennia/server/sessionhandler.py b/evennia/server/sessionhandler.py index 32ce6879e0..dafa4c2245 100644 --- a/evennia/server/sessionhandler.py +++ b/evennia/server/sessionhandler.py @@ -28,7 +28,7 @@ from evennia.utils.utils import ( 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_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 _INLINEFUNC_ENABLED = settings.INLINEFUNC_ENABLED @@ -59,6 +59,9 @@ _DELAY_CMD_LOGINSTART = settings.DELAY_CMD_LOGINSTART _MAX_SERVER_COMMANDS_PER_SECOND = 100.0 _MAX_SESSION_COMMANDS_PER_SECOND = 5.0 _MODEL_MAP = None +_FUNCPARSER = None + + # input handlers @@ -171,6 +174,11 @@ class SessionHandler(dict): 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 {} raw = options.get("raw", 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): # 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) elif ( diff --git a/evennia/utils/funcparser.py b/evennia/utils/funcparser.py index 1a54725985..939f7159f1 100644 --- a/evennia/utils/funcparser.py +++ b/evennia/utils/funcparser.py @@ -149,9 +149,13 @@ class FuncParser: """ for funcname, clble in callables.items(): - mapping = inspect.getfullargspec(clble) - assert mapping.varargs, f"Parse-func callable '{funcname}' does not support *args." - assert mapping.varkw, f"Parse-func callable '{funcname}' does not support **kwargs." + try: + mapping = inspect.getfullargspec(clble) + 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): """ @@ -207,7 +211,7 @@ class FuncParser: raise 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)` - style tokens in it. Only the callables used to initiate the parser @@ -215,10 +219,14 @@ class FuncParser: Args: 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 `True`, errors (like not closing brackets) will lead to an 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 as part of each parsed callable's **kwargs. These override 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 # ready function-def to run. 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: # unnest the higher-level funcdef from stack # and continue where we were diff --git a/evennia/utils/inlinefuncs.py b/evennia/utils/inlinefuncs.py index 83c09e1ba3..6d82407743 100644 --- a/evennia/utils/inlinefuncs.py +++ b/evennia/utils/inlinefuncs.py @@ -357,269 +357,173 @@ class ParseStack(list): self._string_last = False -class InlinefuncError(RuntimeError): - 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) - - +# class InlinefuncError(RuntimeError): +# pass # -# Nick templating # - - -""" -This supports the use of replacement templates in nicks: - -This happens in two steps: - -1) The user supplies a template that is converted to a regex according - to the unix-like templating language. -2) This regex is tested against nicks depending on which nick replacement - strategy is considered (most commonly inputline). -3) If there is a template match and there are templating markers, - these are replaced with the arguments actually given. - -@desc $1 $2 $3 - -This will be converted to the following regex: - -\@desc (?P<1>\w+) (?P<2>\w+) $(?P<3>\w+) - -Supported template markers (through fnmatch) - * matches anything (non-greedy) -> .*? - ? matches any single character -> - [seq] matches any entry in sequence - [!seq] matches entries not in sequence -Custom arg markers - $N argument position (1-99) - -""" -_RE_NICK_ARG = re.compile(r"\\(\$)([1-9][0-9]?)") -_RE_NICK_TEMPLATE_ARG = re.compile(r"(\$)([1-9][0-9]?)") -_RE_NICK_SPACE = re.compile(r"\\ ") - - -class NickTemplateInvalid(ValueError): - pass - - -def initialize_nick_templates(in_template, out_template): - """ - Initialize the nick templates for matching and remapping a string. - - Args: - in_template (str): The template to be used for nick recognition. - out_template (str): The template to be used to replace the string - matched by the in_template. - - Returns: - regex (regex): Regex to match against strings - template (str): Template with markers {arg1}, {arg2}, etc for - replacement using the standard .format method. - - Raises: - evennia.utils.inlinefuncs.NickTemplateInvalid: If the in/out template - does not have a matching number of $args. - - """ - # create the regex for in_template - regex_string = fnmatch.translate(in_template) - n_inargs = len(_RE_NICK_ARG.findall(regex_string)) - regex_string = _RE_NICK_SPACE.sub("\s+", regex_string) - regex_string = _RE_NICK_ARG.sub(lambda m: "(?P.+?)" % m.group(2), regex_string) - - # create the out_template - template_string = _RE_NICK_TEMPLATE_ARG.sub(lambda m: "{arg%s}" % m.group(2), out_template) - - # validate the tempaltes - they should at least have the same number of args - n_outargs = len(_RE_NICK_TEMPLATE_ARG.findall(out_template)) - if n_inargs != n_outargs: - raise NickTemplateInvalid - - return re.compile(regex_string), template_string - - -def parse_nick_template(string, template_regex, outtemplate): - """ - Parse a text using a template and map it to another template - - Args: - string (str): The input string to processj - template_regex (regex): A template regex created with - initialize_nick_template. - outtemplate (str): The template to which to map the matches - produced by the template_regex. This should have $1, $2, - etc to match the regex. - - """ - match = template_regex.match(string) - if match: - return outtemplate.format(**match.groupdict()) - return string +# 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) diff --git a/evennia/utils/tests/test_funcparser.py b/evennia/utils/tests/test_funcparser.py index b8faa9414b..ed3b8d51ed 100644 --- a/evennia/utils/tests/test_funcparser.py +++ b/evennia/utils/tests/test_funcparser.py @@ -7,7 +7,7 @@ Test the funcparser module. import time from simpleeval import simple_eval from parameterized import parameterized -from django.test import TestCase +from django.test import TestCase, override_settings from evennia.utils import funcparser @@ -135,22 +135,44 @@ class TestFuncParser(TestCase): Test parsing of string. """ - t0 = time.time() + #t0 = time.time() # from evennia import set_trace;set_trace() ret = self.parser.parse(string) - t1 = time.time() - print(f"time: {(t1-t0)*1000} ms") + #t1 = time.time() + #print(f"time: {(t1-t0)*1000} ms") self.assertEqual(expected, ret) def test_parse_raise(self): + """ + Make sure error is raised if told to do so. + + """ string = "Test invalid $dummy()" with self.assertRaises(funcparser.ParsingError): 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): """ Test so default kwargs are added and overridden properly + """ # default kwargs passed on initializations parser = funcparser.FuncParser( @@ -174,3 +196,40 @@ class TestFuncParser(TestCase): ret = parser.parse("This is a $foo(foo=moo) string", foo="bar") 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) diff --git a/evennia/utils/tests/test_tagparsing.py b/evennia/utils/tests/test_tagparsing.py index 6435c5c2ce..8272a8eaae 100644 --- a/evennia/utils/tests/test_tagparsing.py +++ b/evennia/utils/tests/test_tagparsing.py @@ -3,10 +3,10 @@ Unit tests for all sorts of inline text-tag parsing, like ANSI, html conversion, """ import re -from django.test import TestCase +from django.test import TestCase, override_settings from evennia.utils.ansi import ANSIString from evennia.utils.text2html import TextToHTMLparser -from evennia.utils import inlinefuncs +from evennia.utils import funcparser class ANSIStringTestCase(TestCase): @@ -352,27 +352,33 @@ class TestTextToHTMLparser(TestCase): class TestInlineFuncs(TestCase): """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): 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}", ) def test_incomplete(self): self.assertEqual( - inlinefuncs.parse_inlinefunc("testing $blah{without an ending."), + self.parser.parse("testing $blah{without an ending."), "testing $blah{without an ending.", ) def test_single_func(self): 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.", ) def test_nested(self): 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 is a test with padded text in pad2 a crop", @@ -380,16 +386,16 @@ class TestInlineFuncs(TestCase): def test_escaped(self): 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 escaped, and instead, cropped with text. ", + "this should be '''escaped,''' and '''instead,''' cropped with text. ", ) def test_escaped2(self): 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 escaped, and instead, cropped with text. ", + "this should be \"\"\"escaped,\"\"\" and \"\"\"instead,\"\"\" cropped with text. ", ) diff --git a/evennia/utils/tests/test_utils.py b/evennia/utils/tests/test_utils.py index 6402c09b7f..b74668845d 100644 --- a/evennia/utils/tests/test_utils.py +++ b/evennia/utils/tests/test_utils.py @@ -385,30 +385,3 @@ class TestPercent(TestCase): 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), "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)