Replace inlinefunc parser with FuncParser mostly
This commit is contained in:
parent
8c3910a033
commit
773bb31f55
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.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:
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 (
|
||||||
|
|
|
||||||
|
|
@ -149,7 +149,11 @@ class FuncParser:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
for funcname, clble in callables.items():
|
for funcname, clble in callables.items():
|
||||||
|
try:
|
||||||
mapping = inspect.getfullargspec(clble)
|
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.varargs, f"Parse-func callable '{funcname}' does not support *args."
|
||||||
assert mapping.varkw, f"Parse-func callable '{funcname}' does not support **kwargs."
|
assert mapping.varkw, f"Parse-func callable '{funcname}' does not support **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
|
||||||
|
|
@ -219,6 +223,10 @@ class FuncParser:
|
||||||
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
|
||||||
|
|
||||||
|
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(
|
infuncstr = self.execute(
|
||||||
curr_func, raise_errors=raise_errors, **reserved_kwargs)
|
curr_func, raise_errors=raise_errors, **reserved_kwargs)
|
||||||
|
|
||||||
curr_func = None
|
|
||||||
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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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. ",
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue