Removed deprecated non-nested {inlinefuncs, only accepting (). Changed the default name of the mygame/server/conf/inlinefunc.py to mygame/server/conf/inlinefuncs.py. Added deprecationwarning for the old name.

This commit is contained in:
Griatch 2016-04-26 00:19:57 +02:00
parent b00e357868
commit 644cf9451f
7 changed files with 27 additions and 286 deletions

View file

@ -11,17 +11,17 @@ evennia.utils.inlinefunc.
In text, usage is straightforward:
{funcname([arg1,arg2,...]) text {/funcname
$funcname([arg1,[arg2,...]])
Example 1 (using the "pad" inlinefunc):
"This is {pad(50,c,-) a center-padded text{/pad of width 50."
say This is $pad("a center-padded text", 50,c,-) of width 50.
->
"This is -------------- a center-padded text--------------- of width 50."
John says, "This is -------------- a center-padded text--------------- of width 50."
Example 2 (using "pad" and "time" inlinefuncs):
"The time is {pad(30){time(){/time{/padright now."
Example 2 (using nested "pad" and "time" inlinefuncs):
say The time is $pad($time(), 30)right now.
->
"The time is Oct 25, 11:09 right now."
John says, "The time is Oct 25, 11:09 right now."
To add more inline functions, add them to this module, using
the following call signature:

View file

@ -805,6 +805,11 @@ def error_check_python_modules():
imp(settings.BASE_ROOM_TYPECLASS)
imp(settings.BASE_EXIT_TYPECLASS)
imp(settings.BASE_SCRIPT_TYPECLASS)
# changed game dir settings file names
if os.path.isfile(os.path.join("server", "conf", "inlinefunc.py")):
raise DeprecationWarning("Name change: mygame/server/conf/inlinefunc.py should "
"be renamed to mygame/server/conf/inlinefuncs.py (note the S at the end)")
def init_game_directory(path, check_db=True):

View file

@ -23,8 +23,7 @@ from evennia.utils.utils import (variable_from_module, is_iter,
to_str, to_unicode,
make_iter,
callables_from_module)
from evennia.utils.inlinefunc import parse_inlinefunc
from evennia.utils.nested_inlinefuncs import parse_inlinefunc as parse_nested_inlinefunc
from evennia.utils.inlinefuncs import parse_inlinefunc
try:
import cPickle as pickle
@ -174,8 +173,7 @@ class SessionHandler(dict):
session.protocol_flags["ENCODING"] = "utf-8"
data = to_str(to_unicode(data), encoding=session.protocol_flags["ENCODING"])
if _INLINEFUNC_ENABLED and not raw:
data = parse_inlinefunc(data, strip=strip_inlinefunc, session=session) # deprecated!
data = parse_nested_inlinefunc(data, strip=strip_inlinefunc, session=session)
data = parse_inlinefunc(data, strip=strip_inlinefunc, session=session)
return data
elif hasattr(data, "id") and hasattr(data, "db_date_created") \
and hasattr(data, '__dbclass__'):

View file

@ -396,17 +396,16 @@ TIME_MONTH_PER_YEAR = 12
######################################################################
# Inlinefunc
######################################################################
# Evennia supports inline function preprocessing. This allows
# users to supply {func() ... {/func in text, performing dynamic
# text formatting and manipulation on the fly. If disabled, such
# inline functions will not be parsed.
# Evennia supports inline function preprocessing. This allows users
# to supply inline calls on the form $func(arg, arg, ...) to do
# session-aware text formatting and manipulation on the fly. If
# disabled, such inline functions will not be parsed.
INLINEFUNC_ENABLED = False
# Only functions defined globally (and not starting with '_') in
# these modules will be considered valid inlinefuncs. The list
# is loaded from left-to-right, same-named functions will overload
INLINEFUNC_MODULES = ["evennia.utils.inlinefunc",
"evennia.utils.nested_inlinefuncs",
"server.conf.inlinefunc"]
INLINEFUNC_MODULES = ["evennia.utils.inlinefuncs",
"server.conf.inlinefuncs"]
######################################################################
# Default Player setup and access

View file

@ -1,261 +0,0 @@
"""
Inlinefunc
**Note: This module is deprecated. Use evennia.utils.nested_inlinefuncs instead.**
This is a simple inline text language for use to custom-format text in
Evennia. It is applied BEFORE ANSI/MUX parsing is applied.
To activate Inlinefunc, settings.INLINEFUNC_ENABLED must be set.
The format is straightforward:
{funcname([arg1,arg2,...]) text {/funcname
Example:
"This is {pad(50,c,-) a center-padded text{/pad of width 50."
->
"This is -------------- a center-padded text--------------- of width 50."
This can be inserted in any text, operated on by the parse_inlinefunc
function. funcname() (no space is allowed between the name and the
argument tuple) is picked from a selection of valid functions from
settings.INLINEFUNC_MODULES.
Commands can be nested, and will applied inside-out. For correct
parsing their end-tags must match the starting tags in reverse order.
Example:
"The time is {pad(30){time(){/time{/padright now."
->
"The time is Oct 25, 11:09 right now."
An inline function should have the following call signature:
def funcname(text, *args, **kwargs)
where the text is always the part between {funcname(args) and
{/funcname and the *args are taken from the appropriate part of the
call. It is important that the inline function properly clean the
incoming args, checking their type and replacing them with sane
defaults if needed. If impossible to resolve, the unmodified text
should be returned. The inlinefunc should never cause a traceback.
"""
import re
from django.conf import settings
from evennia.utils import utils, logger
_DEFAULT_WIDTH = settings.CLIENT_DEFAULT_WIDTH
# inline functions
def pad(text, *args, **kwargs):
"""
Pad to width. pad(text, width=78, align='c', fillchar=' ')
"""
width = _DEFAULT_WIDTH
align = 'c'
fillchar = ' '
for iarg, arg in enumerate(args):
if iarg == 0:
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:
fillchar = arg[0]
else:
break
return utils.pad(text, width=width, align=align, fillchar=fillchar)
def crop(text, *args, **kwargs):
"""
Crop to width. crop(text, width=78, suffix='[...]')
"""
width = _DEFAULT_WIDTH
suffix = "[...]"
for iarg, arg in enumerate(args):
if iarg == 0:
width = int(arg) if arg.strip().isdigit() else width
elif iarg == 1:
suffix = arg
else:
break
return utils.crop(text, width=width, suffix=suffix)
def wrap(text, *args, **kwargs):
"""
Wrap/Fill text to width. fill(text, width=78, indent=0)
"""
width = _DEFAULT_WIDTH
indent = 0
for iarg, arg in enumerate(args):
if iarg == 0:
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)
def time(text, *args, **kwargs):
"""
Inserts current time.
"""
import time
strformat = "%h %d, %H:%M"
if args and args[0]:
strformat = str(args[0])
return time.strftime(strformat)
def you(text, *args, **kwargs):
"""
Inserts your name.
"""
name = "You"
sess = kwargs.get("session")
if sess and sess.puppet:
name = sess.puppet.key
return name
# load functions from module (including this one, if using default settings)
_INLINE_FUNCS = {}
for module in utils.make_iter(settings.INLINEFUNC_MODULES):
_INLINE_FUNCS.update(utils.callables_from_module(module))
_INLINE_FUNCS.pop("inline_func_parse", None)
# dynamically build regexes for found functions
_RE_FUNCFULL = r"\{%s\((.*?)\)(.*?){/%s"
_RE_FUNCFULL_SINGLE = r"\{%s\((.*?)\)"
_RE_FUNCSTART = r"\{((?:%s))"
_RE_FUNCEND = r"\{/((?:%s))"
_RE_FUNCSPLIT = r"(\{/*(?:%s)(?:\(.*?\))*)"
_RE_FUNCCLEAN = r"\{%s\(.*?\)|\{/%s"
_INLINE_FUNCS = dict((key, (func, re.compile(_RE_FUNCFULL % (key, key), re.DOTALL & re.MULTILINE),
re.compile(_RE_FUNCFULL_SINGLE % key, re.DOTALL & re.MULTILINE)))
for key, func in _INLINE_FUNCS.items() if callable(func))
_FUNCSPLIT_REGEX = re.compile(_RE_FUNCSPLIT % r"|".join([key for key in _INLINE_FUNCS]), re.DOTALL & re.MULTILINE)
_FUNCSTART_REGEX = re.compile(_RE_FUNCSTART % r"|".join([key for key in _INLINE_FUNCS]), re.DOTALL & re.MULTILINE)
_FUNCEND_REGEX = re.compile(_RE_FUNCEND % r"|".join([key for key in _INLINE_FUNCS]), re.DOTALL & re.MULTILINE)
_FUNCCLEAN_REGEX = re.compile("|".join([_RE_FUNCCLEAN % (key, key) for key in _INLINE_FUNCS]), re.DOTALL & re.MULTILINE)
# inline parser functions
def _execute_inline_function(funcname, text, session):
"""
Get the enclosed text between {funcname(...) and {/funcname
and execute the inline function to replace the whole block
with the result.
Args:
funcname (str): Inlinefunction identifier.
text (str): Text to process.
session (Session): Session object.
Notes:
This lookup is "dumb" - we just grab the first end tag we find. So
to work correctly this function must be called "inside out" on a
nested function tree, so each call only works on a "flat" tag.
"""
def subfunc(match):
"""
replace the entire block with the result of the function call
"""
args = [part.strip() for part in match.group(1).split(",")]
intext = match.group(2)
kwargs = {"session":session}
return _INLINE_FUNCS[funcname][0](intext, *args, **kwargs)
return _INLINE_FUNCS[funcname][1].sub(subfunc, text)
def _execute_inline_single_function(funcname, text, session):
"""
Get the arguments of a single function call (no matching end tag)
and execute it with an empty text input.
Args:
funcname (str): Function identifier.
text (str): String to process.
session (Session): Session id.
"""
def subfunc(match):
"replace the single call with the result of the function call"
args = [part.strip() for part in match.group(1).split(",")]
kwargs = {"session":session}
return _INLINE_FUNCS[funcname][0]("", *args, **kwargs)
return _INLINE_FUNCS[funcname][2].sub(subfunc, text)
def parse_inlinefunc(text, strip=False, session=None):
"""
Parse inline function-replacement.
Args:
text (str): Text to parse.
strip (bool, optional): Remove all supported inlinefuncs from text.
session (bool): Session calling for the parsing.
Returns:
text (str): Parsed text with processed results of
inlinefuncs.
"""
if strip:
# strip all functions
return _FUNCCLEAN_REGEX.sub("", text)
stack = []
for part in _FUNCSPLIT_REGEX.split(text):
endtag = _FUNCEND_REGEX.match(part)
if endtag:
# an end tag
endname = endtag.group(1)
while stack:
new_part = stack.pop()
part = new_part + part # add backwards -> fowards
starttag = _FUNCSTART_REGEX.match(new_part)
if starttag:
startname = starttag.group(1)
if startname == endname:
part = _execute_inline_function(startname, part, session)
break
stack.append(part)
# handle single functions without matching end tags; these are treated
# as being called with an empty string as text argument.
outstack = []
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)
return "".join(outstack)
def _test():
# this should all be handled
s = "This is a text with a{pad(78,c,-)text {pad(5)of{/pad {pad(30)nice{/pad size{/pad inside {pad(4,l)it{/pad."
s2 = "This is a text with a----------------text of nice size---------------- inside it ."
t = parse_inlinefunc(s)
assert(t == s2)
return t

View file

@ -310,37 +310,37 @@ class TestTextToHTMLparser(TestCase):
'</span><a href="http://example.com/" target="_blank">http://example.com/</a><span class="red">')
from evennia.utils import nested_inlinefuncs
from evennia.utils import inlinefuncs
class TestNestedInlineFuncs(TestCase):
class TestInlineFuncs(TestCase):
"Test the nested inlinefunc module"
def test_nofunc(self):
self.assertEqual(nested_inlinefuncs.parse_inlinefunc(
self.assertEqual(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(
self.assertEqual(inlinefuncs.parse_inlinefunc(
"testing $blah{without an ending."),
"testing $blah{without an ending.")
def test_single_func(self):
self.assertEqual(nested_inlinefuncs.parse_inlinefunc(
self.assertEqual(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(
self.assertEqual(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(
self.assertEqual(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. ")
def test_escaped2(self):
self.assertEqual(nested_inlinefuncs.parse_inlinefunc(
self.assertEqual(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. ")