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:
parent
b00e357868
commit
644cf9451f
7 changed files with 27 additions and 286 deletions
|
|
@ -11,17 +11,17 @@ evennia.utils.inlinefunc.
|
||||||
|
|
||||||
In text, usage is straightforward:
|
In text, usage is straightforward:
|
||||||
|
|
||||||
{funcname([arg1,arg2,...]) text {/funcname
|
$funcname([arg1,[arg2,...]])
|
||||||
|
|
||||||
Example 1 (using the "pad" inlinefunc):
|
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):
|
Example 2 (using nested "pad" and "time" inlinefuncs):
|
||||||
"The time is {pad(30){time(){/time{/padright now."
|
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
|
To add more inline functions, add them to this module, using
|
||||||
the following call signature:
|
the following call signature:
|
||||||
|
|
@ -805,6 +805,11 @@ def error_check_python_modules():
|
||||||
imp(settings.BASE_ROOM_TYPECLASS)
|
imp(settings.BASE_ROOM_TYPECLASS)
|
||||||
imp(settings.BASE_EXIT_TYPECLASS)
|
imp(settings.BASE_EXIT_TYPECLASS)
|
||||||
imp(settings.BASE_SCRIPT_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):
|
def init_game_directory(path, check_db=True):
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,7 @@ from evennia.utils.utils import (variable_from_module, is_iter,
|
||||||
to_str, to_unicode,
|
to_str, to_unicode,
|
||||||
make_iter,
|
make_iter,
|
||||||
callables_from_module)
|
callables_from_module)
|
||||||
from evennia.utils.inlinefunc import parse_inlinefunc
|
from evennia.utils.inlinefuncs import parse_inlinefunc
|
||||||
from evennia.utils.nested_inlinefuncs import parse_inlinefunc as parse_nested_inlinefunc
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import cPickle as pickle
|
import cPickle as pickle
|
||||||
|
|
@ -174,8 +173,7 @@ class SessionHandler(dict):
|
||||||
session.protocol_flags["ENCODING"] = "utf-8"
|
session.protocol_flags["ENCODING"] = "utf-8"
|
||||||
data = to_str(to_unicode(data), encoding=session.protocol_flags["ENCODING"])
|
data = to_str(to_unicode(data), encoding=session.protocol_flags["ENCODING"])
|
||||||
if _INLINEFUNC_ENABLED and not raw:
|
if _INLINEFUNC_ENABLED and not raw:
|
||||||
data = parse_inlinefunc(data, strip=strip_inlinefunc, session=session) # deprecated!
|
data = parse_inlinefunc(data, strip=strip_inlinefunc, session=session)
|
||||||
data = parse_nested_inlinefunc(data, strip=strip_inlinefunc, session=session)
|
|
||||||
return data
|
return data
|
||||||
elif hasattr(data, "id") and hasattr(data, "db_date_created") \
|
elif hasattr(data, "id") and hasattr(data, "db_date_created") \
|
||||||
and hasattr(data, '__dbclass__'):
|
and hasattr(data, '__dbclass__'):
|
||||||
|
|
|
||||||
|
|
@ -396,17 +396,16 @@ TIME_MONTH_PER_YEAR = 12
|
||||||
######################################################################
|
######################################################################
|
||||||
# Inlinefunc
|
# Inlinefunc
|
||||||
######################################################################
|
######################################################################
|
||||||
# Evennia supports inline function preprocessing. This allows
|
# Evennia supports inline function preprocessing. This allows users
|
||||||
# users to supply {func() ... {/func in text, performing dynamic
|
# to supply inline calls on the form $func(arg, arg, ...) to do
|
||||||
# text formatting and manipulation on the fly. If disabled, such
|
# session-aware text formatting and manipulation on the fly. If
|
||||||
# inline functions will not be parsed.
|
# disabled, such inline functions will not be parsed.
|
||||||
INLINEFUNC_ENABLED = False
|
INLINEFUNC_ENABLED = False
|
||||||
# Only functions defined globally (and not starting with '_') in
|
# Only functions defined globally (and not starting with '_') in
|
||||||
# these modules will be considered valid inlinefuncs. The list
|
# these modules will be considered valid inlinefuncs. The list
|
||||||
# is loaded from left-to-right, same-named functions will overload
|
# is loaded from left-to-right, same-named functions will overload
|
||||||
INLINEFUNC_MODULES = ["evennia.utils.inlinefunc",
|
INLINEFUNC_MODULES = ["evennia.utils.inlinefuncs",
|
||||||
"evennia.utils.nested_inlinefuncs",
|
"server.conf.inlinefuncs"]
|
||||||
"server.conf.inlinefunc"]
|
|
||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
# Default Player setup and access
|
# Default Player setup and access
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -310,37 +310,37 @@ class TestTextToHTMLparser(TestCase):
|
||||||
'</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
|
from evennia.utils import inlinefuncs
|
||||||
|
|
||||||
class TestNestedInlineFuncs(TestCase):
|
class TestInlineFuncs(TestCase):
|
||||||
"Test the nested inlinefunc module"
|
"Test the nested inlinefunc module"
|
||||||
def test_nofunc(self):
|
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}"),
|
||||||
"as$382ewrw w we w werw,|44943}")
|
"as$382ewrw w we w werw,|44943}")
|
||||||
|
|
||||||
def test_incomplete(self):
|
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."),
|
||||||
"testing $blah{without an ending.")
|
"testing $blah{without an ending.")
|
||||||
|
|
||||||
def test_single_func(self):
|
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 $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(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 $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")
|
||||||
|
|
||||||
def test_escaped(self):
|
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 $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(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 $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. ")
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue