Added unittests for nested inlinefuncs, and deprecation warning for the old version of {inlinefunc syntax.
This commit is contained in:
parent
641846e855
commit
0a4c217b3e
3 changed files with 90 additions and 19 deletions
|
|
@ -45,7 +45,7 @@ should be returned. The inlinefunc should never cause a traceback.
|
||||||
|
|
||||||
import re
|
import re
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from evennia.utils import utils
|
from evennia.utils import utils, logger
|
||||||
|
|
||||||
_DEFAULT_WIDTH = settings.CLIENT_DEFAULT_WIDTH
|
_DEFAULT_WIDTH = settings.CLIENT_DEFAULT_WIDTH
|
||||||
|
|
||||||
|
|
@ -61,7 +61,7 @@ def pad(text, *args, **kwargs):
|
||||||
fillchar = ' '
|
fillchar = ' '
|
||||||
for iarg, arg in enumerate(args):
|
for iarg, arg in enumerate(args):
|
||||||
if iarg == 0:
|
if iarg == 0:
|
||||||
width = int(arg) if arg.isdigit() else width
|
width = int(arg) if arg.strip().isdigit() else width
|
||||||
elif iarg == 1:
|
elif iarg == 1:
|
||||||
align = arg if arg in ('c', 'l', 'r') else align
|
align = arg if arg in ('c', 'l', 'r') else align
|
||||||
elif iarg == 2:
|
elif iarg == 2:
|
||||||
|
|
@ -80,7 +80,7 @@ def crop(text, *args, **kwargs):
|
||||||
suffix = "[...]"
|
suffix = "[...]"
|
||||||
for iarg, arg in enumerate(args):
|
for iarg, arg in enumerate(args):
|
||||||
if iarg == 0:
|
if iarg == 0:
|
||||||
width = int(arg) if arg.isdigit() else width
|
width = int(arg) if arg.strip().isdigit() else width
|
||||||
elif iarg == 1:
|
elif iarg == 1:
|
||||||
suffix = arg
|
suffix = arg
|
||||||
else:
|
else:
|
||||||
|
|
@ -97,7 +97,7 @@ def wrap(text, *args, **kwargs):
|
||||||
indent = 0
|
indent = 0
|
||||||
for iarg, arg in enumerate(args):
|
for iarg, arg in enumerate(args):
|
||||||
if iarg == 0:
|
if iarg == 0:
|
||||||
width = int(arg) if arg.isdigit() else width
|
width = int(arg) if arg.strip().isdigit() else width
|
||||||
elif iarg == 1:
|
elif iarg == 1:
|
||||||
indent = int(arg) if arg.isdigit() else indent
|
indent = int(arg) if arg.isdigit() else indent
|
||||||
return utils.wrap(text, width=width, indent=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)):
|
for part in _FUNCSPLIT_REGEX.split("".join(stack)):
|
||||||
starttag = _FUNCSTART_REGEX.match(part)
|
starttag = _FUNCSTART_REGEX.match(part)
|
||||||
if starttag:
|
if starttag:
|
||||||
|
logger.log_dep("The {func()-style inlinefunc is deprecated. Use the $func{} form instead.")
|
||||||
startname = starttag.group(1)
|
startname = starttag.group(1)
|
||||||
part = _execute_inline_single_function(startname, part, session)
|
part = _execute_inline_single_function(startname, part, session)
|
||||||
outstack.append(part)
|
outstack.append(part)
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,9 @@ Inline functions (nested form).
|
||||||
This parser accepts nested inlinefunctions on the 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
|
Each token starts with "$funcname(" where there must be no space
|
||||||
between the $funcname and (. It ends with a matched ending parentesis.
|
between the $funcname and (. It ends with a matched ending parentesis.
|
||||||
|
|
@ -50,18 +50,56 @@ import re
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from evennia.utils import utils
|
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
|
# we specify a default nomatch function to use if no matching func was
|
||||||
# found. This will be overloaded by any nomatch function defined in
|
# found. This will be overloaded by any nomatch function defined in
|
||||||
# the imported modules.
|
# 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.)"}
|
"stackfull": lambda *args, **kwargs: "\n (not parsed: inlinefunc stack size exceeded.)"}
|
||||||
|
|
||||||
|
|
||||||
# load custom inline func modules.
|
# load custom inline func modules.
|
||||||
for module in utils.make_iter(settings.INLINEFUNC_MODULES):
|
for module in utils.make_iter(settings.INLINEFUNC_MODULES):
|
||||||
_INLINE_FUNCS.update(utils.all_from_module(module))
|
_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.
|
# 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_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<triplequote>.*?)(?<!\\)\"\"\"| # unescaped triple quote (escapes all inside them)
|
||||||
(?P<comma>(?<!\\)\,)| # unescaped , (argument lists) - this is thrown away
|
(?P<comma>(?<!\\)\,)| # unescaped , (argument lists) - this is thrown away
|
||||||
(?P<end>(?<!\\)\})| # unescaped ) (end of function call)
|
(?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.
|
string (str): The incoming string to parse.
|
||||||
strip (bool, optional): Whether to strip function calls rather than
|
strip (bool, optional): Whether to strip function calls rather than
|
||||||
execute them.
|
execute them.
|
||||||
kwargs (any, optional): This will be passed on to all found inlinefuncs as
|
Kwargs:
|
||||||
part of their call signature. This is to be called by Evennia or the
|
session (Session): This is sent to this function by Evennia when triggering
|
||||||
coder and is used to make various game states available, such as
|
it. It is passed to the inlinefunc.
|
||||||
the session of the user triggering the parse, character etc.
|
kwargs (any): All other kwargs are also passed on to the inlinefunc.
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
global _PARSING_CACHE
|
global _PARSING_CACHE
|
||||||
|
|
@ -159,7 +198,6 @@ def parse_inlinefunc(string, strip=False, **kwargs):
|
||||||
ncallable = 0
|
ncallable = 0
|
||||||
for match in _RE_TOKEN.finditer(string):
|
for match in _RE_TOKEN.finditer(string):
|
||||||
gdict = match.groupdict()
|
gdict = match.groupdict()
|
||||||
print "gdict:", gdict
|
|
||||||
if gdict["singlequote"]:
|
if gdict["singlequote"]:
|
||||||
stack.append(gdict["singlequote"])
|
stack.append(gdict["singlequote"])
|
||||||
elif gdict["triplequote"]:
|
elif gdict["triplequote"]:
|
||||||
|
|
@ -204,6 +242,10 @@ def parse_inlinefunc(string, strip=False, **kwargs):
|
||||||
# the rest
|
# the rest
|
||||||
stack.append(gdict["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_MAXSIZE > 0 and _STACK_MAXSIZE < len(stack):
|
||||||
# if stack is larger than limit, throw away parsing
|
# if stack is larger than limit, throw away parsing
|
||||||
return string + gdict["stackfull"](*args, **kwargs)
|
return string + gdict["stackfull"](*args, **kwargs)
|
||||||
|
|
@ -213,6 +255,7 @@ def parse_inlinefunc(string, strip=False, **kwargs):
|
||||||
|
|
||||||
# run the stack recursively
|
# run the stack recursively
|
||||||
def _run_stack(item):
|
def _run_stack(item):
|
||||||
|
retval = item
|
||||||
if isinstance(item, tuple):
|
if isinstance(item, tuple):
|
||||||
if strip:
|
if strip:
|
||||||
return ""
|
return ""
|
||||||
|
|
@ -227,11 +270,9 @@ def parse_inlinefunc(string, strip=False, **kwargs):
|
||||||
# all other args should merge into one string
|
# all other args should merge into one string
|
||||||
args[-1] += _run_stack(arg)
|
args[-1] += _run_stack(arg)
|
||||||
# execute the inlinefunc at this point or strip it.
|
# execute the inlinefunc at this point or strip it.
|
||||||
return "" if strip else utils.to_str(func(*args, **kwargs))
|
retval = "" if strip else func(*args, **kwargs)
|
||||||
else:
|
return utils.to_str(retval, force_string=True)
|
||||||
return item
|
|
||||||
|
|
||||||
# execute the stack from the cache
|
# execute the stack from the cache
|
||||||
print "_PARSING_CACHE[string]:", _PARSING_CACHE[string]
|
|
||||||
return "".join(_run_stack(item) for item in _PARSING_CACHE[string])
|
return "".join(_run_stack(item) for item in _PARSING_CACHE[string])
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -309,3 +309,32 @@ class TestTextToHTMLparser(TestCase):
|
||||||
self.assertEqual(self.parser.convert_urls('</span>http://example.com/<span class="red">'),
|
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">')
|
'</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. ")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue