Added unittests for nested inlinefuncs, and deprecation warning for the old version of {inlinefunc syntax.

This commit is contained in:
Griatch 2015-11-17 22:39:26 +01:00
parent 641846e855
commit 0a4c217b3e
3 changed files with 90 additions and 19 deletions

View file

@ -45,7 +45,7 @@ should be returned. The inlinefunc should never cause a traceback.
import re
from django.conf import settings
from evennia.utils import utils
from evennia.utils import utils, logger
_DEFAULT_WIDTH = settings.CLIENT_DEFAULT_WIDTH
@ -61,7 +61,7 @@ def pad(text, *args, **kwargs):
fillchar = ' '
for iarg, arg in enumerate(args):
if iarg == 0:
width = int(arg) if arg.isdigit() else width
width = int(arg) if arg.strip().isdigit() else width
elif iarg == 1:
align = arg if arg in ('c', 'l', 'r') else align
elif iarg == 2:
@ -80,7 +80,7 @@ def crop(text, *args, **kwargs):
suffix = "[...]"
for iarg, arg in enumerate(args):
if iarg == 0:
width = int(arg) if arg.isdigit() else width
width = int(arg) if arg.strip().isdigit() else width
elif iarg == 1:
suffix = arg
else:
@ -97,7 +97,7 @@ def wrap(text, *args, **kwargs):
indent = 0
for iarg, arg in enumerate(args):
if iarg == 0:
width = int(arg) if arg.isdigit() else width
width = int(arg) if arg.strip().isdigit() else width
elif iarg == 1:
indent = int(arg) if arg.isdigit() else indent
return utils.wrap(text, width=width, indent=indent)
@ -242,6 +242,7 @@ def parse_inlinefunc(text, strip=False, session=None):
for part in _FUNCSPLIT_REGEX.split("".join(stack)):
starttag = _FUNCSTART_REGEX.match(part)
if starttag:
logger.log_dep("The {func()-style inlinefunc is deprecated. Use the $func{} form instead.")
startname = starttag.group(1)
part = _execute_inline_single_function(startname, part, session)
outstack.append(part)

View file

@ -4,9 +4,9 @@ Inline functions (nested form).
This parser accepts nested inlinefunctions on the form
```
$funcname(arg, arg, ...)
$funcname{arg, arg, ...}
```
where any arg can be another $funcname() call.
where any arg can be another $funcname{} call.
Each token starts with "$funcname(" where there must be no space
between the $funcname and (. It ends with a matched ending parentesis.
@ -50,18 +50,56 @@ import re
from django.conf import settings
from evennia.utils import utils
# example/testing inline functions
def pad(*args, **kwargs):
"""
Pad to width. pad(text, width, align, fillchar)
"""
text, width, align, fillchar = "", 78, 'c', ' '
nargs = len(args)
if nargs > 0:
text = args[0]
if nargs > 1:
width = int(args[1]) if args[1].strip().isdigit() else 78
if nargs > 2:
align = args[2] if args[2] in ('c', 'l', 'r') else 'c'
if nargs > 3:
fillchar = args[3]
return utils.pad(text, width=width, align=align, fillchar=fillchar)
def crop(text, *args, **kwargs):
"""
Crop to width. crop(text, width=78, suffix='[...]')
"""
text = width, suffix = "", 78, "[...]"
nargs = len(args)
if nargs > 0:
text = args[0]
if nargs > 1:
width = int(args[1]) if args[1].strip().isdigit() else 78
if nargs > 2:
suffix = args[2]
return utils.crop(text, width=width, suffix=suffix)
# we specify a default nomatch function to use if no matching func was
# found. This will be overloaded by any nomatch function defined in
# the imported modules.
_INLINE_FUNCS = {"nomatch": lambda *args, **kwargs: "<UKNOWN: %s>" % (args[0]),
_INLINE_FUNCS = {"nomatch": lambda *args, **kwargs: "<UKNOWN>",
"stackfull": lambda *args, **kwargs: "\n (not parsed: inlinefunc stack size exceeded.)"}
# load custom inline func modules.
for module in utils.make_iter(settings.INLINEFUNC_MODULES):
_INLINE_FUNCS.update(utils.all_from_module(module))
# remove
_INLINE_FUNCS.pop("inline_func_parse", None)
# remove the core function if we include examples in this module itself
#_INLINE_FUNCS.pop("inline_func_parse", None)
# The stack size is a security measure. Set to <=0 to disable.
@ -74,7 +112,7 @@ except AttributeError:
_RE_STARTTOKEN = re.compile(r"(?<!\\)\$(\w+)\{") # unescaped $funcname( (start of function call)
_RE_TOKEN = re.compile(r"""(?<!\\)''(?P<singlequote>.*?)(?<!\\)\''| # unescaped '' (escapes all inside them)
_RE_TOKEN = re.compile(r"""(?<!\\)'''(?P<singlequote>.*?)(?<!\\)'''| # unescaped '' (escapes all inside them)
(?<!\\)\"\"\"(?P<triplequote>.*?)(?<!\\)\"\"\"| # unescaped triple quote (escapes all inside them)
(?P<comma>(?<!\\)\,)| # unescaped , (argument lists) - this is thrown away
(?P<end>(?<!\\)\})| # unescaped ) (end of function call)
@ -138,10 +176,11 @@ def parse_inlinefunc(string, strip=False, **kwargs):
string (str): The incoming string to parse.
strip (bool, optional): Whether to strip function calls rather than
execute them.
kwargs (any, optional): This will be passed on to all found inlinefuncs as
part of their call signature. This is to be called by Evennia or the
coder and is used to make various game states available, such as
the session of the user triggering the parse, character etc.
Kwargs:
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
@ -159,7 +198,6 @@ def parse_inlinefunc(string, strip=False, **kwargs):
ncallable = 0
for match in _RE_TOKEN.finditer(string):
gdict = match.groupdict()
print "gdict:", gdict
if gdict["singlequote"]:
stack.append(gdict["singlequote"])
elif gdict["triplequote"]:
@ -204,6 +242,10 @@ def parse_inlinefunc(string, strip=False, **kwargs):
# 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 < len(stack):
# if stack is larger than limit, throw away parsing
return string + gdict["stackfull"](*args, **kwargs)
@ -213,6 +255,7 @@ def parse_inlinefunc(string, strip=False, **kwargs):
# run the stack recursively
def _run_stack(item):
retval = item
if isinstance(item, tuple):
if strip:
return ""
@ -227,11 +270,9 @@ def parse_inlinefunc(string, strip=False, **kwargs):
# all other args should merge into one string
args[-1] += _run_stack(arg)
# execute the inlinefunc at this point or strip it.
return "" if strip else utils.to_str(func(*args, **kwargs))
else:
return item
retval = "" if strip else func(*args, **kwargs)
return utils.to_str(retval, force_string=True)
# execute the stack from the cache
print "_PARSING_CACHE[string]:", _PARSING_CACHE[string]
return "".join(_run_stack(item) for item in _PARSING_CACHE[string])

View file

@ -309,3 +309,32 @@ class TestTextToHTMLparser(TestCase):
self.assertEqual(self.parser.convert_urls('</span>http://example.com/<span class="red">'),
'</span><a href="http://example.com/" target="_blank">http://example.com/</a><span class="red">')
from evennia.utils import nested_inlinefuncs
class TestNestedInlineFuncs(TestCase):
"Test the nested inlinefunc module"
def test_nofunc(self):
self.assertEqual(nested_inlinefuncs.parse_inlinefunc(
"as$382ewrw w we w werw,|44943}"),
"as$382ewrw w we w werw,|44943}")
def test_incomplete(self):
self.assertEqual(nested_inlinefuncs.parse_inlinefunc(
"testing $blah{without an ending."),
"testing $blah{without an ending.")
def test_single_func(self):
self.assertEqual(nested_inlinefuncs.parse_inlinefunc(
"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(nested_inlinefuncs.parse_inlinefunc(
"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")
def test_escaped(self):
self.assertEqual(nested_inlinefuncs.parse_inlinefunc(
"this should be $pad{'''escaped,''' and \"\"\"instead,\"\"\" cropped $crop{with a long,5} text., 80}"),
"this should be escaped, and instead, cropped with text. ")