mirror of
https://github.com/evennia/evennia.git
synced 2026-03-25 09:16:32 +01:00
Replace inlinefunc parser with FuncParser mostly
This commit is contained in:
parent
b832280f37
commit
8924304d80
10 changed files with 294 additions and 322 deletions
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<arg%s>.+?)" % 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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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. ",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue