Homogenize funcparser calls
This commit is contained in:
parent
adb370b1d3
commit
a3a57314a1
11 changed files with 293 additions and 993 deletions
|
|
@ -2123,7 +2123,8 @@ class CmdTypeclass(COMMAND_DEFAULT_CLASS):
|
||||||
)
|
)
|
||||||
|
|
||||||
if "prototype" in self.switches:
|
if "prototype" in self.switches:
|
||||||
modified = spawner.batch_update_objects_with_prototype(prototype, objects=[obj])
|
modified = spawner.batch_update_objects_with_prototype(
|
||||||
|
prototype, objects=[obj], caller=self.caller)
|
||||||
prototype_success = modified > 0
|
prototype_success = modified > 0
|
||||||
if not prototype_success:
|
if not prototype_success:
|
||||||
caller.msg("Prototype %s failed to apply." % prototype["key"])
|
caller.msg("Prototype %s failed to apply." % prototype["key"])
|
||||||
|
|
@ -3559,7 +3560,7 @@ class CmdSpawn(COMMAND_DEFAULT_CLASS):
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
n_updated = spawner.batch_update_objects_with_prototype(
|
n_updated = spawner.batch_update_objects_with_prototype(
|
||||||
prototype, objects=existing_objects
|
prototype, objects=existing_objects, caller=caller,
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.log_trace()
|
logger.log_trace()
|
||||||
|
|
@ -3811,7 +3812,7 @@ class CmdSpawn(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
||||||
# proceed to spawning
|
# proceed to spawning
|
||||||
try:
|
try:
|
||||||
for obj in spawner.spawn(prototype):
|
for obj in spawner.spawn(prototype, caller=self.caller):
|
||||||
self.caller.msg("Spawned %s." % obj.get_display_name(self.caller))
|
self.caller.msg("Spawned %s." % obj.get_display_name(self.caller))
|
||||||
if not prototype.get("location") and not noloc:
|
if not prototype.get("location") and not noloc:
|
||||||
# we don't hardcode the location in the prototype (unless the user
|
# we don't hardcode the location in the prototype (unless the user
|
||||||
|
|
|
||||||
|
|
@ -695,27 +695,27 @@ If you want there is also some |wextra|n info for where to go beyond that.
|
||||||
After playing through the tutorial-world quest, if you aim to make a game with
|
After playing through the tutorial-world quest, if you aim to make a game with
|
||||||
Evennia you are wise to take a look at the |wEvennia documentation|n at
|
Evennia you are wise to take a look at the |wEvennia documentation|n at
|
||||||
|
|
||||||
|yhttps://github.com/evennia/evennia/wiki|n
|
|yhttps://www.evennia.com/docs/latest|n
|
||||||
|
|
||||||
- You can start by trying to build some stuff by following the |wBuilder quick-start|n:
|
- You can start by trying to build some stuff by following the |wBuilder quick-start|n:
|
||||||
|
|
||||||
|yhttps://github.com/evennia/evennia/wiki/Building-Quickstart|n
|
|yhttps://www.evennia.com/docs/latest/Building-Quickstart|n
|
||||||
|
|
||||||
- The tutorial-world may or may not be your cup of tea, but it does show off
|
- The tutorial-world may or may not be your cup of tea, but it does show off
|
||||||
several |wuseful tools|n of Evennia. You may want to check out how it works:
|
several |wuseful tools|n of Evennia. You may want to check out how it works:
|
||||||
|
|
||||||
|yhttps://github.com/evennia/evennia/wiki/Tutorial-World-Introduction|n
|
|yhttps://www.evennia.com/docs/latest/Tutorial-World-Introduction|n
|
||||||
|
|
||||||
- You can then continue looking through the |wTutorials|n and pick one that
|
- You can then continue looking through the |wTutorials|n and pick one that
|
||||||
fits your level of understanding.
|
fits your level of understanding.
|
||||||
|
|
||||||
|yhttps://github.com/evennia/evennia/wiki/Tutorials|n
|
|yhttps://www.evennia.com/docs/latest/Tutorials|n
|
||||||
|
|
||||||
- Make sure to |wjoin our forum|n and connect to our |wsupport chat|n! The
|
- Make sure to |wjoin our forum|n and connect to our |wsupport chat|n! The
|
||||||
Evennia community is very active and friendly and no question is too simple.
|
Evennia community is very active and friendly and no question is too simple.
|
||||||
You will often quickly get help. You can everything you need linked from
|
You will often quickly get help. You can everything you need linked from
|
||||||
|
|
||||||
|yhttp://www.evennia.com|n
|
|yhttps://www.evennia.com|n
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ from evennia.objects.models import ObjectDB
|
||||||
from evennia.scripts.scripthandler import ScriptHandler
|
from evennia.scripts.scripthandler import ScriptHandler
|
||||||
from evennia.commands import cmdset, command
|
from evennia.commands import cmdset, command
|
||||||
from evennia.commands.cmdsethandler import CmdSetHandler
|
from evennia.commands.cmdsethandler import CmdSetHandler
|
||||||
|
from evennia.utils import funcparser
|
||||||
from evennia.utils import create
|
from evennia.utils import create
|
||||||
from evennia.utils import search
|
from evennia.utils import search
|
||||||
from evennia.utils import logger
|
from evennia.utils import logger
|
||||||
|
|
@ -47,6 +48,12 @@ _COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)
|
||||||
# the sessid_max is based on the length of the db_sessid csv field (excluding commas)
|
# the sessid_max is based on the length of the db_sessid csv field (excluding commas)
|
||||||
_SESSID_MAX = 16 if _MULTISESSION_MODE in (1, 3) else 1
|
_SESSID_MAX = 16 if _MULTISESSION_MODE in (1, 3) else 1
|
||||||
|
|
||||||
|
_MSG_CONTENTS_PARSER = funcparser.FuncParser(
|
||||||
|
{"you": funcparser.funcparser_callable_you,
|
||||||
|
"You": funcparser.funcparser_callable_You,
|
||||||
|
"conj": funcparser.funcparser_callable_conjugate
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
class ObjectSessionHandler(object):
|
class ObjectSessionHandler(object):
|
||||||
"""
|
"""
|
||||||
|
|
@ -717,64 +724,94 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
||||||
text (str or tuple): Message to send. If a tuple, this should be
|
text (str or tuple): Message to send. If a tuple, this should be
|
||||||
on the valid OOB outmessage form `(message, {kwargs})`,
|
on the valid OOB outmessage form `(message, {kwargs})`,
|
||||||
where kwargs are optional data passed to the `text`
|
where kwargs are optional data passed to the `text`
|
||||||
outputfunc.
|
outputfunc. The message will be parsed for `{key}` formatting and
|
||||||
|
`$You/$you()/$You(key)` and `$conj(verb)` inline function callables.
|
||||||
|
The `key` is taken from the `mapping` kwarg {"key": object, ...}`.
|
||||||
|
The `mapping[key].get_display_name(looker=recipient)` will be called
|
||||||
|
for that key for every recipient of the string.
|
||||||
exclude (list, optional): A list of objects not to send to.
|
exclude (list, optional): A list of objects not to send to.
|
||||||
from_obj (Object, optional): An object designated as the
|
from_obj (Object, optional): An object designated as the
|
||||||
"sender" of the message. See `DefaultObject.msg()` for
|
"sender" of the message. See `DefaultObject.msg()` for
|
||||||
more info.
|
more info.
|
||||||
mapping (dict, optional): A mapping of formatting keys
|
mapping (dict, optional): A mapping of formatting keys
|
||||||
`{"key":<object>, "key2":<object2>,...}. The keys
|
`{"key":<object>, "key2":<object2>,...}.
|
||||||
must match `{key}` markers in the `text` if this is a string or
|
The keys must either match `{key}` or `$You(key)/$you(key)` markers
|
||||||
in the internal `message` if `text` is a tuple. These
|
in the `text` string. If `<object>` doesn't have a `get_display_name`
|
||||||
formatting statements will be
|
method, it will be returned as a string. If not set, a key `you` will
|
||||||
replaced by the return of `<object>.get_display_name(looker)`
|
be auto-added to point to `from_obj` if given, otherwise to `self`.
|
||||||
for every looker in contents that receives the
|
**kwargs: Keyword arguments will be passed on to `obj.msg()` for all
|
||||||
message. This allows for every object to potentially
|
messaged objects.
|
||||||
get its own customized string.
|
|
||||||
Keyword Args:
|
|
||||||
Keyword arguments will be passed on to `obj.msg()` for all
|
|
||||||
messaged objects.
|
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
The `mapping` argument is required if `message` contains
|
For 'actor-stance' reporting (You say/Name says), use the
|
||||||
{}-style format syntax. The keys of `mapping` should match
|
`$You()/$you()/$You(key)` and `$conj(verb)` (verb-conjugation)
|
||||||
named format tokens, and its values will have their
|
inline callables. This will use the respective `get_display_name()`
|
||||||
`get_display_name()` function called for each object in
|
for all onlookers except for `from_obj or self`, which will become
|
||||||
the room before substitution. If an item in the mapping does
|
'You/you'. If you use `$You/you(key)`, the key must be in `mapping`.
|
||||||
not have `get_display_name()`, its string value will be used.
|
|
||||||
|
|
||||||
Example:
|
For 'director-stance' reporting (Name says/Name says), use {key}
|
||||||
Say Char is a Character object and Npc is an NPC object:
|
syntax directly. For both `{key}` and `You/you(key)`,
|
||||||
|
`mapping[key].get_display_name(looker=recipient)` may be called
|
||||||
|
depending on who the recipient is.
|
||||||
|
|
||||||
char.location.msg_contents(
|
Examples:
|
||||||
"{attacker} kicks {defender}",
|
|
||||||
mapping=dict(attacker=char, defender=npc), exclude=(char, npc))
|
|
||||||
|
|
||||||
This will result in everyone in the room seeing 'Char kicks NPC'
|
Let's assume
|
||||||
where everyone may potentially see different results for Char and Npc
|
- `player1.key -> "Player1"`,
|
||||||
depending on the results of `char.get_display_name(looker)` and
|
`player1.get_display_name(looker=player2) -> "The First girl"`
|
||||||
`npc.get_display_name(looker)` for each particular onlooker
|
- `player2.key -> "Player2"`,
|
||||||
|
`player2.get_display_name(looker=player1) -> "The Second girl"`
|
||||||
|
|
||||||
|
Actor-stance:
|
||||||
|
::
|
||||||
|
|
||||||
|
char.location.msg_contents(
|
||||||
|
"$You() $conj(attack) $you(defender).",
|
||||||
|
mapping={"defender": player2})
|
||||||
|
|
||||||
|
- player1 will see `You attack The Second girl.`
|
||||||
|
- player2 will see 'The First girl attacks you.'
|
||||||
|
|
||||||
|
Director-stance:
|
||||||
|
::
|
||||||
|
|
||||||
|
char.location.msg_contents(
|
||||||
|
"{attacker} attacks {defender}.",
|
||||||
|
mapping={"attacker:player1, "defender":player2})
|
||||||
|
|
||||||
|
- player1 will see: 'Player1 attacks The Second girl.'
|
||||||
|
- player2 will see: 'The First girl attacks Player2'
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# we also accept an outcommand on the form (message, {kwargs})
|
# we also accept an outcommand on the form (message, {kwargs})
|
||||||
is_outcmd = text and is_iter(text)
|
is_outcmd = text and is_iter(text)
|
||||||
inmessage = text[0] if is_outcmd else text
|
inmessage = text[0] if is_outcmd else text
|
||||||
outkwargs = text[1] if is_outcmd and len(text) > 1 else {}
|
outkwargs = text[1] if is_outcmd and len(text) > 1 else {}
|
||||||
|
mapping = mapping or {}
|
||||||
|
you = from_obj or self
|
||||||
|
|
||||||
|
if 'you' not in mapping:
|
||||||
|
mapping[you] = you
|
||||||
|
|
||||||
contents = self.contents
|
contents = self.contents
|
||||||
if exclude:
|
if exclude:
|
||||||
exclude = make_iter(exclude)
|
exclude = make_iter(exclude)
|
||||||
contents = [obj for obj in contents if obj not in exclude]
|
contents = [obj for obj in contents if obj not in exclude]
|
||||||
for obj in contents:
|
|
||||||
if mapping:
|
for receiver in contents:
|
||||||
substitutions = {
|
|
||||||
t: sub.get_display_name(obj) if hasattr(sub, "get_display_name") else str(sub)
|
# actor-stance replacements
|
||||||
for t, sub in mapping.items()
|
inmessage = _MSG_CONTENTS_PARSER.parse(
|
||||||
}
|
inmessage, raise_errors=True, return_string=True,
|
||||||
outmessage = inmessage.format(**substitutions)
|
you=you, receiver=receiver, mapping=mapping)
|
||||||
else:
|
|
||||||
outmessage = inmessage
|
# director-stance replacements
|
||||||
obj.msg(text=(outmessage, outkwargs), from_obj=from_obj, **kwargs)
|
outmessage = inmessage.format(
|
||||||
|
**{key: obj.get_display_name(looker=receiver)
|
||||||
|
if hasattr(obj, "get_display_name") else str(obj)
|
||||||
|
for key, obj in mapping.items()})
|
||||||
|
|
||||||
|
receiver.msg(text=(outmessage, outkwargs), from_obj=from_obj, **kwargs)
|
||||||
|
|
||||||
def move_to(
|
def move_to(
|
||||||
self,
|
self,
|
||||||
|
|
|
||||||
|
|
@ -2115,7 +2115,8 @@ def _apply_diff(caller, **kwargs):
|
||||||
objects = kwargs["objects"]
|
objects = kwargs["objects"]
|
||||||
back_node = kwargs["back_node"]
|
back_node = kwargs["back_node"]
|
||||||
diff = kwargs.get("diff", None)
|
diff = kwargs.get("diff", None)
|
||||||
num_changed = spawner.batch_update_objects_with_prototype(prototype, diff=diff, objects=objects)
|
num_changed = spawner.batch_update_objects_with_prototype(prototype, diff=diff, objects=objects,
|
||||||
|
caller=caller)
|
||||||
caller.msg("|g{num} objects were updated successfully.|n".format(num=num_changed))
|
caller.msg("|g{num} objects were updated successfully.|n".format(num=num_changed))
|
||||||
return back_node
|
return back_node
|
||||||
|
|
||||||
|
|
@ -2483,7 +2484,7 @@ def _spawn(caller, **kwargs):
|
||||||
if not prototype.get("location"):
|
if not prototype.get("location"):
|
||||||
prototype["location"] = caller
|
prototype["location"] = caller
|
||||||
|
|
||||||
obj = spawner.spawn(prototype)
|
obj = spawner.spawn(prototype, caller=caller)
|
||||||
if obj:
|
if obj:
|
||||||
obj = obj[0]
|
obj = obj[0]
|
||||||
text = "|gNew instance|n {key} ({dbref}) |gspawned at location |n{loc}|n|g.|n".format(
|
text = "|gNew instance|n {key} ({dbref}) |gspawned at location |n{loc}|n|g.|n".format(
|
||||||
|
|
|
||||||
|
|
@ -1,33 +1,28 @@
|
||||||
"""
|
"""
|
||||||
Protfuncs are function-strings embedded in a prototype and allows for a builder to create a
|
Protfuncs are FuncParser-callables that can be embedded in a prototype to
|
||||||
prototype with custom logics without having access to Python. The Protfunc is parsed using the
|
provide custom logic without having access to Python. The protfunc is parsed at
|
||||||
inlinefunc parser but is fired at the moment the spawning happens, using the creating object's
|
the time of spawning, using the creating object's session as input. If the
|
||||||
session as input.
|
protfunc returns a non-string, this is what will be added to the prototype.
|
||||||
|
|
||||||
In the prototype dict, the protfunc is specified as a string inside the prototype, e.g.:
|
In the prototype dict, the protfunc is specified as a string inside the prototype, e.g.:
|
||||||
|
|
||||||
{ ...
|
{ ...
|
||||||
|
|
||||||
"key": "$funcname(arg1, arg2, ...)"
|
"key": "$funcname(args, kwargs)"
|
||||||
|
|
||||||
... }
|
... }
|
||||||
|
|
||||||
and multiple functions can be nested (no keyword args are supported). The result will be used as the
|
Available protfuncs are either all callables in one of the modules of `settings.PROT_FUNC_MODULES`
|
||||||
value for that prototype key for that individual spawn.
|
or all callables added to a dict FUNCPARSER_CALLABLES in such a module.
|
||||||
|
|
||||||
Available protfuncs are callables in one of the modules of `settings.PROT_FUNC_MODULES`. They
|
|
||||||
are specified as functions
|
|
||||||
|
|
||||||
def funcname (*args, **kwargs)
|
def funcname (*args, **kwargs)
|
||||||
|
|
||||||
where *args are the arguments given in the prototype, and **kwargs are inserted by Evennia:
|
At spawn-time the spawner passes the following extra kwargs into each callable (in addition to
|
||||||
|
what is added in the call itself):
|
||||||
|
|
||||||
- session (Session): The Session of the entity spawning using this prototype.
|
- session (Session): The Session of the entity spawning using this prototype.
|
||||||
- prototype (dict): The dict this protfunc is a part of.
|
- prototype (dict): The dict this protfunc is a part of.
|
||||||
- current_key (str): The active key this value belongs to in the prototype.
|
- current_key (str): The active key this value belongs to in the prototype.
|
||||||
- testing (bool): This is set if this function is called as part of the prototype validation; if
|
|
||||||
set, the protfunc should take care not to perform any persistent actions, such as operate on
|
|
||||||
objects or add things to the database.
|
|
||||||
|
|
||||||
Any traceback raised by this function will be handled at the time of spawning and abort the spawn
|
Any traceback raised by this function will be handled at the time of spawning and abort the spawn
|
||||||
before any object is created/updated. It must otherwise return the value to store for the specified
|
before any object is created/updated. It must otherwise return the value to store for the specified
|
||||||
|
|
@ -35,312 +30,26 @@ prototype key (this value must be possible to serialize in an Attribute).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from ast import literal_eval
|
from evennia.utils import funcparser
|
||||||
from random import randint as base_randint, random as base_random, choice as base_choice
|
|
||||||
import re
|
|
||||||
|
|
||||||
from evennia.utils import search
|
|
||||||
from evennia.utils.utils import justify as base_justify, is_iter, to_str
|
|
||||||
|
|
||||||
_PROTLIB = None
|
|
||||||
|
|
||||||
_RE_DBREF = re.compile(r"\#[0-9]+")
|
|
||||||
|
|
||||||
|
|
||||||
# default protfuncs
|
def protfunc_callable_protkey(*args, **kwargs):
|
||||||
|
|
||||||
|
|
||||||
def random(*args, **kwargs):
|
|
||||||
"""
|
"""
|
||||||
Usage: $random()
|
Usage: $protkey(keyname)
|
||||||
Returns a random value in the interval [0, 1)
|
|
||||||
|
|
||||||
"""
|
|
||||||
return base_random()
|
|
||||||
|
|
||||||
|
|
||||||
def randint(*args, **kwargs):
|
|
||||||
"""
|
|
||||||
Usage: $randint(start, end)
|
|
||||||
Returns random integer in interval [start, end]
|
|
||||||
|
|
||||||
"""
|
|
||||||
if len(args) != 2:
|
|
||||||
raise TypeError("$randint needs two arguments - start and end.")
|
|
||||||
start, end = int(args[0]), int(args[1])
|
|
||||||
return base_randint(start, end)
|
|
||||||
|
|
||||||
|
|
||||||
def left_justify(*args, **kwargs):
|
|
||||||
"""
|
|
||||||
Usage: $left_justify(<text>)
|
|
||||||
Returns <text> left-justified.
|
|
||||||
|
|
||||||
"""
|
|
||||||
if args:
|
|
||||||
return base_justify(args[0], align="l")
|
|
||||||
return ""
|
|
||||||
|
|
||||||
|
|
||||||
def right_justify(*args, **kwargs):
|
|
||||||
"""
|
|
||||||
Usage: $right_justify(<text>)
|
|
||||||
Returns <text> right-justified across screen width.
|
|
||||||
|
|
||||||
"""
|
|
||||||
if args:
|
|
||||||
return base_justify(args[0], align="r")
|
|
||||||
return ""
|
|
||||||
|
|
||||||
|
|
||||||
def center_justify(*args, **kwargs):
|
|
||||||
|
|
||||||
"""
|
|
||||||
Usage: $center_justify(<text>)
|
|
||||||
Returns <text> centered in screen width.
|
|
||||||
|
|
||||||
"""
|
|
||||||
if args:
|
|
||||||
return base_justify(args[0], align="c")
|
|
||||||
return ""
|
|
||||||
|
|
||||||
|
|
||||||
def choice(*args, **kwargs):
|
|
||||||
"""
|
|
||||||
Usage: $choice(val, val, val, ...)
|
|
||||||
Returns one of the values randomly
|
|
||||||
"""
|
|
||||||
if args:
|
|
||||||
return base_choice(args)
|
|
||||||
return ""
|
|
||||||
|
|
||||||
|
|
||||||
def full_justify(*args, **kwargs):
|
|
||||||
|
|
||||||
"""
|
|
||||||
Usage: $full_justify(<text>)
|
|
||||||
Returns <text> filling up screen width by adding extra space.
|
|
||||||
|
|
||||||
"""
|
|
||||||
if args:
|
|
||||||
return base_justify(args[0], align="f")
|
|
||||||
return ""
|
|
||||||
|
|
||||||
|
|
||||||
def protkey(*args, **kwargs):
|
|
||||||
"""
|
|
||||||
Usage: $protkey(<key>)
|
|
||||||
Returns the value of another key in this prototoype. Will raise an error if
|
Returns the value of another key in this prototoype. Will raise an error if
|
||||||
the key is not found in this prototype.
|
the key is not found in this prototype.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if args:
|
if not args:
|
||||||
prototype = kwargs["prototype"]
|
return ""
|
||||||
return prototype[args[0].strip()]
|
|
||||||
|
prototype = kwargs.get("prototype", {})
|
||||||
|
return prototype[args[0].strip()]
|
||||||
|
|
||||||
|
|
||||||
def add(*args, **kwargs):
|
# this is picked up by FuncParser
|
||||||
"""
|
FUNCPARSER_CALLABLES = {
|
||||||
Usage: $add(val1, val2)
|
"protkey": protfunc_callable_protkey,
|
||||||
Returns the result of val1 + val2. Values must be
|
**funcparser.FUNCPARSER_CALLABLES,
|
||||||
valid simple Python structures possible to add,
|
**funcparser.SEARCHING_CALLABLES,
|
||||||
such as numbers, lists etc.
|
}
|
||||||
|
|
||||||
"""
|
|
||||||
if len(args) > 1:
|
|
||||||
val1, val2 = args[0], args[1]
|
|
||||||
# try to convert to python structures, otherwise, keep as strings
|
|
||||||
try:
|
|
||||||
val1 = literal_eval(val1.strip())
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
val2 = literal_eval(val2.strip())
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
return val1 + val2
|
|
||||||
raise ValueError("$add requires two arguments.")
|
|
||||||
|
|
||||||
|
|
||||||
def sub(*args, **kwargs):
|
|
||||||
"""
|
|
||||||
Usage: $del(val1, val2)
|
|
||||||
Returns the value of val1 - val2. Values must be
|
|
||||||
valid simple Python structures possible to
|
|
||||||
subtract.
|
|
||||||
|
|
||||||
"""
|
|
||||||
if len(args) > 1:
|
|
||||||
val1, val2 = args[0], args[1]
|
|
||||||
# try to convert to python structures, otherwise, keep as strings
|
|
||||||
try:
|
|
||||||
val1 = literal_eval(val1.strip())
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
val2 = literal_eval(val2.strip())
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
return val1 - val2
|
|
||||||
raise ValueError("$sub requires two arguments.")
|
|
||||||
|
|
||||||
|
|
||||||
def mult(*args, **kwargs):
|
|
||||||
"""
|
|
||||||
Usage: $mul(val1, val2)
|
|
||||||
Returns the value of val1 * val2. The values must be
|
|
||||||
valid simple Python structures possible to
|
|
||||||
multiply, like strings and/or numbers.
|
|
||||||
|
|
||||||
"""
|
|
||||||
if len(args) > 1:
|
|
||||||
val1, val2 = args[0], args[1]
|
|
||||||
# try to convert to python structures, otherwise, keep as strings
|
|
||||||
try:
|
|
||||||
val1 = literal_eval(val1.strip())
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
val2 = literal_eval(val2.strip())
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
return val1 * val2
|
|
||||||
raise ValueError("$mul requires two arguments.")
|
|
||||||
|
|
||||||
|
|
||||||
def div(*args, **kwargs):
|
|
||||||
"""
|
|
||||||
Usage: $div(val1, val2)
|
|
||||||
Returns the value of val1 / val2. Values must be numbers and
|
|
||||||
the result is always a float.
|
|
||||||
|
|
||||||
"""
|
|
||||||
if len(args) > 1:
|
|
||||||
val1, val2 = args[0], args[1]
|
|
||||||
# try to convert to python structures, otherwise, keep as strings
|
|
||||||
try:
|
|
||||||
val1 = literal_eval(val1.strip())
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
val2 = literal_eval(val2.strip())
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
return val1 / float(val2)
|
|
||||||
raise ValueError("$mult requires two arguments.")
|
|
||||||
|
|
||||||
|
|
||||||
def toint(*args, **kwargs):
|
|
||||||
"""
|
|
||||||
Usage: $toint(<number>)
|
|
||||||
Returns <number> as an integer.
|
|
||||||
"""
|
|
||||||
if args:
|
|
||||||
val = args[0]
|
|
||||||
try:
|
|
||||||
return int(literal_eval(val.strip()))
|
|
||||||
except ValueError:
|
|
||||||
return val
|
|
||||||
raise ValueError("$toint requires one argument.")
|
|
||||||
|
|
||||||
|
|
||||||
def eval(*args, **kwargs):
|
|
||||||
"""
|
|
||||||
Usage $eval(<expression>)
|
|
||||||
Returns evaluation of a simple Python expression. The string may *only* consist of the following
|
|
||||||
Python literal structures: strings, numbers, tuples, lists, dicts, booleans,
|
|
||||||
and None. The strings can also contain #dbrefs. Escape embedded protfuncs as $$protfunc(..)
|
|
||||||
- those will then be evaluated *after* $eval.
|
|
||||||
|
|
||||||
"""
|
|
||||||
global _PROTLIB
|
|
||||||
if not _PROTLIB:
|
|
||||||
from evennia.prototypes import prototypes as _PROTLIB
|
|
||||||
|
|
||||||
string = ",".join(args)
|
|
||||||
struct = literal_eval(string)
|
|
||||||
|
|
||||||
if isinstance(struct, str):
|
|
||||||
# we must shield the string, otherwise it will be merged as a string and future
|
|
||||||
# literal_evas will pick up e.g. '2' as something that should be converted to a number
|
|
||||||
struct = '"{}"'.format(struct)
|
|
||||||
|
|
||||||
# convert any #dbrefs to objects (also in nested structures)
|
|
||||||
struct = _PROTLIB.value_to_obj_or_any(struct)
|
|
||||||
|
|
||||||
return struct
|
|
||||||
|
|
||||||
|
|
||||||
def _obj_search(*args, **kwargs):
|
|
||||||
"Helper function to search for an object"
|
|
||||||
|
|
||||||
query = "".join(args)
|
|
||||||
session = kwargs.get("session", None)
|
|
||||||
return_list = kwargs.pop("return_list", False)
|
|
||||||
account = None
|
|
||||||
|
|
||||||
if session:
|
|
||||||
account = session.account
|
|
||||||
|
|
||||||
targets = search.search_object(query)
|
|
||||||
|
|
||||||
if return_list:
|
|
||||||
retlist = []
|
|
||||||
if account:
|
|
||||||
for target in targets:
|
|
||||||
if target.access(account, target, "control"):
|
|
||||||
retlist.append(target)
|
|
||||||
else:
|
|
||||||
retlist = targets
|
|
||||||
return retlist
|
|
||||||
else:
|
|
||||||
# single-match
|
|
||||||
if not targets:
|
|
||||||
raise ValueError("$obj: Query '{}' gave no matches.".format(query))
|
|
||||||
if len(targets) > 1:
|
|
||||||
raise ValueError(
|
|
||||||
"$obj: Query '{query}' gave {nmatches} matches. Limit your "
|
|
||||||
"query or use $objlist instead.".format(query=query, nmatches=len(targets))
|
|
||||||
)
|
|
||||||
target = targets[0]
|
|
||||||
if account:
|
|
||||||
if not target.access(account, target, "control"):
|
|
||||||
raise ValueError(
|
|
||||||
"$obj: Obj {target}(#{dbref} cannot be added - "
|
|
||||||
"Account {account} does not have 'control' access.".format(
|
|
||||||
target=target.key, dbref=target.id, account=account
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return target
|
|
||||||
|
|
||||||
|
|
||||||
def obj(*args, **kwargs):
|
|
||||||
"""
|
|
||||||
Usage $obj(<query>)
|
|
||||||
Returns one Object searched globally by key, alias or #dbref. Error if more than one.
|
|
||||||
|
|
||||||
"""
|
|
||||||
obj = _obj_search(return_list=False, *args, **kwargs)
|
|
||||||
if obj:
|
|
||||||
return "#{}".format(obj.id)
|
|
||||||
return "".join(args)
|
|
||||||
|
|
||||||
|
|
||||||
def objlist(*args, **kwargs):
|
|
||||||
"""
|
|
||||||
Usage $objlist(<query>)
|
|
||||||
Returns list with one or more Objects searched globally by key, alias or #dbref.
|
|
||||||
|
|
||||||
"""
|
|
||||||
return ["#{}".format(obj.id) for obj in _obj_search(return_list=True, *args, **kwargs)]
|
|
||||||
|
|
||||||
|
|
||||||
def dbref(*args, **kwargs):
|
|
||||||
"""
|
|
||||||
Usage $dbref(<#dbref>)
|
|
||||||
Validate that a #dbref input is valid.
|
|
||||||
"""
|
|
||||||
if not args or len(args) < 1 or _RE_DBREF.match(args[0]) is None:
|
|
||||||
raise ValueError("$dbref requires a valid #dbref argument.")
|
|
||||||
|
|
||||||
return obj(args[0])
|
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,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.funcparser import FuncParser
|
||||||
from evennia.utils import inlinefuncs, dbserialize
|
from evennia.utils import dbserialize
|
||||||
from evennia.utils.evtable import EvTable
|
from evennia.utils.evtable import EvTable
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -721,7 +721,7 @@ for mod in settings.PROT_FUNC_MODULES:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
def protfunc_parser(value, available_functions=None, testing=False, stacktrace=False, **kwargs):
|
def protfunc_parser(value, available_functions=None, testing=False, stacktrace=False, caller=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
Parse a prototype value string for a protfunc and process it.
|
Parse a prototype value string for a protfunc and process it.
|
||||||
|
|
||||||
|
|
@ -741,6 +741,8 @@ def protfunc_parser(value, available_functions=None, testing=False, stacktrace=F
|
||||||
session (Session): Passed to protfunc. Session of the entity spawning the prototype.
|
session (Session): Passed to protfunc. Session of the entity spawning the prototype.
|
||||||
protototype (dict): Passed to protfunc. The dict this protfunc is a part of.
|
protototype (dict): Passed to protfunc. The dict this protfunc is a part of.
|
||||||
current_key(str): Passed to protfunc. The key in the prototype that will hold this value.
|
current_key(str): Passed to protfunc. The key in the prototype that will hold this value.
|
||||||
|
caller (Object or Account): This is necessary for certain protfuncs that perform object
|
||||||
|
searches and have to check permissions.
|
||||||
any (any): Passed on to the protfunc.
|
any (any): Passed on to the protfunc.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
@ -759,11 +761,8 @@ 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 = FuncParser(available_functions).parse(value, raise_errors=True, **kwargs)
|
result = FuncParser(available_functions).parse(
|
||||||
|
value, raise_errors=True, caller=caller, **kwargs)
|
||||||
# result = inlinefuncs.parse_inlinefunc(
|
|
||||||
# value, available_funcs=available_functions, stacktrace=stacktrace, testing=testing, **kwargs
|
|
||||||
# )
|
|
||||||
|
|
||||||
err = None
|
err = None
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
|
|
@ -607,7 +607,8 @@ def format_diff(diff, minimal=True):
|
||||||
return "\n ".join(line for line in texts if line)
|
return "\n ".join(line for line in texts if line)
|
||||||
|
|
||||||
|
|
||||||
def batch_update_objects_with_prototype(prototype, diff=None, objects=None, exact=False):
|
def batch_update_objects_with_prototype(prototype, diff=None, objects=None,
|
||||||
|
exact=False, caller=None):
|
||||||
"""
|
"""
|
||||||
Update existing objects with the latest version of the prototype.
|
Update existing objects with the latest version of the prototype.
|
||||||
|
|
||||||
|
|
@ -624,6 +625,7 @@ def batch_update_objects_with_prototype(prototype, diff=None, objects=None, exac
|
||||||
if it's not set in the prototype. With `exact=True`, all un-specified properties of the
|
if it's not set in the prototype. With `exact=True`, all un-specified properties of the
|
||||||
objects will be removed if they exist. This will lead to a more accurate 1:1 correlation
|
objects will be removed if they exist. This will lead to a more accurate 1:1 correlation
|
||||||
between the object and the prototype but is usually impractical.
|
between the object and the prototype but is usually impractical.
|
||||||
|
caller (Object or Account, optional): This may be used by protfuncs to do permission checks.
|
||||||
Returns:
|
Returns:
|
||||||
changed (int): The number of objects that had changes applied to them.
|
changed (int): The number of objects that had changes applied to them.
|
||||||
|
|
||||||
|
|
@ -675,33 +677,33 @@ def batch_update_objects_with_prototype(prototype, diff=None, objects=None, exac
|
||||||
do_save = True
|
do_save = True
|
||||||
|
|
||||||
if key == "key":
|
if key == "key":
|
||||||
obj.db_key = init_spawn_value(val, str)
|
obj.db_key = init_spawn_value(val, str, caller=caller)
|
||||||
elif key == "typeclass":
|
elif key == "typeclass":
|
||||||
obj.db_typeclass_path = init_spawn_value(val, str)
|
obj.db_typeclass_path = init_spawn_value(val, str, caller=caller)
|
||||||
elif key == "location":
|
elif key == "location":
|
||||||
obj.db_location = init_spawn_value(val, value_to_obj)
|
obj.db_location = init_spawn_value(val, value_to_obj, caller=caller)
|
||||||
elif key == "home":
|
elif key == "home":
|
||||||
obj.db_home = init_spawn_value(val, value_to_obj)
|
obj.db_home = init_spawn_value(val, value_to_obj, caller=caller)
|
||||||
elif key == "destination":
|
elif key == "destination":
|
||||||
obj.db_destination = init_spawn_value(val, value_to_obj)
|
obj.db_destination = init_spawn_value(val, value_to_obj, caller=caller)
|
||||||
elif key == "locks":
|
elif key == "locks":
|
||||||
if directive == "REPLACE":
|
if directive == "REPLACE":
|
||||||
obj.locks.clear()
|
obj.locks.clear()
|
||||||
obj.locks.add(init_spawn_value(val, str))
|
obj.locks.add(init_spawn_value(val, str, caller=caller))
|
||||||
elif key == "permissions":
|
elif key == "permissions":
|
||||||
if directive == "REPLACE":
|
if directive == "REPLACE":
|
||||||
obj.permissions.clear()
|
obj.permissions.clear()
|
||||||
obj.permissions.batch_add(*(init_spawn_value(perm, str) for perm in val))
|
obj.permissions.batch_add(*(init_spawn_value(perm, str, caller=caller) for perm in val))
|
||||||
elif key == "aliases":
|
elif key == "aliases":
|
||||||
if directive == "REPLACE":
|
if directive == "REPLACE":
|
||||||
obj.aliases.clear()
|
obj.aliases.clear()
|
||||||
obj.aliases.batch_add(*(init_spawn_value(alias, str) for alias in val))
|
obj.aliases.batch_add(*(init_spawn_value(alias, str, caller=caller) for alias in val))
|
||||||
elif key == "tags":
|
elif key == "tags":
|
||||||
if directive == "REPLACE":
|
if directive == "REPLACE":
|
||||||
obj.tags.clear()
|
obj.tags.clear()
|
||||||
obj.tags.batch_add(
|
obj.tags.batch_add(
|
||||||
*(
|
*(
|
||||||
(init_spawn_value(ttag, str), tcategory, tdata)
|
(init_spawn_value(ttag, str, caller=caller), tcategory, tdata)
|
||||||
for ttag, tcategory, tdata in val
|
for ttag, tcategory, tdata in val
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -711,8 +713,8 @@ def batch_update_objects_with_prototype(prototype, diff=None, objects=None, exac
|
||||||
obj.attributes.batch_add(
|
obj.attributes.batch_add(
|
||||||
*(
|
*(
|
||||||
(
|
(
|
||||||
init_spawn_value(akey, str),
|
init_spawn_value(akey, str, caller=caller),
|
||||||
init_spawn_value(aval, value_to_obj),
|
init_spawn_value(aval, value_to_obj, caller=caller),
|
||||||
acategory,
|
acategory,
|
||||||
alocks,
|
alocks,
|
||||||
)
|
)
|
||||||
|
|
@ -723,7 +725,7 @@ def batch_update_objects_with_prototype(prototype, diff=None, objects=None, exac
|
||||||
# we don't auto-rerun exec statements, it would be huge security risk!
|
# we don't auto-rerun exec statements, it would be huge security risk!
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
obj.attributes.add(key, init_spawn_value(val, value_to_obj))
|
obj.attributes.add(key, init_spawn_value(val, value_to_obj, caller=caller))
|
||||||
elif directive == "REMOVE":
|
elif directive == "REMOVE":
|
||||||
do_save = True
|
do_save = True
|
||||||
if key == "key":
|
if key == "key":
|
||||||
|
|
@ -836,7 +838,7 @@ def batch_create_object(*objparams):
|
||||||
# Spawner mechanism
|
# Spawner mechanism
|
||||||
|
|
||||||
|
|
||||||
def spawn(*prototypes, **kwargs):
|
def spawn(*prototypes, caller=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
Spawn a number of prototyped objects.
|
Spawn a number of prototyped objects.
|
||||||
|
|
||||||
|
|
@ -845,6 +847,7 @@ def spawn(*prototypes, **kwargs):
|
||||||
prototype_key (will be used to find the prototype) or a full prototype
|
prototype_key (will be used to find the prototype) or a full prototype
|
||||||
dictionary. These will be batched-spawned as one object each.
|
dictionary. These will be batched-spawned as one object each.
|
||||||
Keyword Args:
|
Keyword Args:
|
||||||
|
caller (Object or Account, optional): This may be used by protfuncs to do access checks.
|
||||||
prototype_modules (str or list): A python-path to a prototype
|
prototype_modules (str or list): A python-path to a prototype
|
||||||
module, or a list of such paths. These will be used to build
|
module, or a list of such paths. These will be used to build
|
||||||
the global protparents dictionary accessible by the input
|
the global protparents dictionary accessible by the input
|
||||||
|
|
@ -910,39 +913,39 @@ def spawn(*prototypes, **kwargs):
|
||||||
"key",
|
"key",
|
||||||
"Spawned-{}".format(hashlib.md5(bytes(str(time.time()), "utf-8")).hexdigest()[:6]),
|
"Spawned-{}".format(hashlib.md5(bytes(str(time.time()), "utf-8")).hexdigest()[:6]),
|
||||||
)
|
)
|
||||||
create_kwargs["db_key"] = init_spawn_value(val, str)
|
create_kwargs["db_key"] = init_spawn_value(val, str, caller=caller)
|
||||||
|
|
||||||
val = prot.pop("location", None)
|
val = prot.pop("location", None)
|
||||||
create_kwargs["db_location"] = init_spawn_value(val, value_to_obj)
|
create_kwargs["db_location"] = init_spawn_value(val, value_to_obj, caller=caller)
|
||||||
|
|
||||||
val = prot.pop("home", None)
|
val = prot.pop("home", None)
|
||||||
if val:
|
if val:
|
||||||
create_kwargs["db_home"] = init_spawn_value(val, value_to_obj)
|
create_kwargs["db_home"] = init_spawn_value(val, value_to_obj, caller=caller)
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
create_kwargs["db_home"] = init_spawn_value(settings.DEFAULT_HOME, value_to_obj)
|
create_kwargs["db_home"] = init_spawn_value(settings.DEFAULT_HOME, value_to_obj, caller=caller)
|
||||||
except ObjectDB.DoesNotExist:
|
except ObjectDB.DoesNotExist:
|
||||||
# settings.DEFAULT_HOME not existing is common for unittests
|
# settings.DEFAULT_HOME not existing is common for unittests
|
||||||
pass
|
pass
|
||||||
|
|
||||||
val = prot.pop("destination", None)
|
val = prot.pop("destination", None)
|
||||||
create_kwargs["db_destination"] = init_spawn_value(val, value_to_obj)
|
create_kwargs["db_destination"] = init_spawn_value(val, value_to_obj, caller=caller)
|
||||||
|
|
||||||
val = prot.pop("typeclass", settings.BASE_OBJECT_TYPECLASS)
|
val = prot.pop("typeclass", settings.BASE_OBJECT_TYPECLASS)
|
||||||
create_kwargs["db_typeclass_path"] = init_spawn_value(val, str)
|
create_kwargs["db_typeclass_path"] = init_spawn_value(val, str, caller=caller)
|
||||||
|
|
||||||
# extract calls to handlers
|
# extract calls to handlers
|
||||||
val = prot.pop("permissions", [])
|
val = prot.pop("permissions", [])
|
||||||
permission_string = init_spawn_value(val, make_iter)
|
permission_string = init_spawn_value(val, make_iter, caller=caller)
|
||||||
val = prot.pop("locks", "")
|
val = prot.pop("locks", "")
|
||||||
lock_string = init_spawn_value(val, str)
|
lock_string = init_spawn_value(val, str, caller=caller)
|
||||||
val = prot.pop("aliases", [])
|
val = prot.pop("aliases", [])
|
||||||
alias_string = init_spawn_value(val, make_iter)
|
alias_string = init_spawn_value(val, make_iter, caller=caller)
|
||||||
|
|
||||||
val = prot.pop("tags", [])
|
val = prot.pop("tags", [])
|
||||||
tags = []
|
tags = []
|
||||||
for (tag, category, *data) in val:
|
for (tag, category, *data) in val:
|
||||||
tags.append((init_spawn_value(tag, str), category, data[0] if data else None))
|
tags.append((init_spawn_value(tag, str, caller=caller), category, data[0] if data else None))
|
||||||
|
|
||||||
prototype_key = prototype.get("prototype_key", None)
|
prototype_key = prototype.get("prototype_key", None)
|
||||||
if prototype_key:
|
if prototype_key:
|
||||||
|
|
@ -950,11 +953,11 @@ def spawn(*prototypes, **kwargs):
|
||||||
tags.append((prototype_key, PROTOTYPE_TAG_CATEGORY))
|
tags.append((prototype_key, PROTOTYPE_TAG_CATEGORY))
|
||||||
|
|
||||||
val = prot.pop("exec", "")
|
val = prot.pop("exec", "")
|
||||||
execs = init_spawn_value(val, make_iter)
|
execs = init_spawn_value(val, make_iter, caller=caller)
|
||||||
|
|
||||||
# extract ndb assignments
|
# extract ndb assignments
|
||||||
nattributes = dict(
|
nattributes = dict(
|
||||||
(key.split("_", 1)[1], init_spawn_value(val, value_to_obj))
|
(key.split("_", 1)[1], init_spawn_value(val, value_to_obj, caller=caller))
|
||||||
for key, val in prot.items()
|
for key, val in prot.items()
|
||||||
if key.startswith("ndb_")
|
if key.startswith("ndb_")
|
||||||
)
|
)
|
||||||
|
|
@ -963,7 +966,7 @@ def spawn(*prototypes, **kwargs):
|
||||||
val = make_iter(prot.pop("attrs", []))
|
val = make_iter(prot.pop("attrs", []))
|
||||||
attributes = []
|
attributes = []
|
||||||
for (attrname, value, *rest) in val:
|
for (attrname, value, *rest) in val:
|
||||||
attributes.append((attrname, init_spawn_value(value),
|
attributes.append((attrname, init_spawn_value(value, caller=caller),
|
||||||
rest[0] if rest else None, rest[1] if len(rest) > 1 else None))
|
rest[0] if rest else None, rest[1] if len(rest) > 1 else None))
|
||||||
|
|
||||||
simple_attributes = []
|
simple_attributes = []
|
||||||
|
|
@ -975,7 +978,7 @@ def spawn(*prototypes, **kwargs):
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
simple_attributes.append(
|
simple_attributes.append(
|
||||||
(key, init_spawn_value(value, value_to_obj_or_any), None, None)
|
(key, init_spawn_value(value, value_to_obj_or_any, caller=caller), None, None)
|
||||||
)
|
)
|
||||||
|
|
||||||
attributes = attributes + simple_attributes
|
attributes = attributes + simple_attributes
|
||||||
|
|
|
||||||
|
|
@ -611,7 +611,7 @@ INLINEFUNC_STACK_MAXSIZE = 20
|
||||||
# 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.inlinefuncs", "server.conf.inlinefuncs"]
|
INLINEFUNC_MODULES = ["evennia.utils.funcparser", "server.conf.inlinefuncs"]
|
||||||
# Module holding handlers for ProtFuncs. These allow for embedding
|
# Module holding handlers for ProtFuncs. These allow for embedding
|
||||||
# functional code in prototypes and has the same syntax as inlinefuncs.
|
# functional code in prototypes and has the same syntax as inlinefuncs.
|
||||||
PROTOTYPEFUNC_MODULES = ["evennia.prototypes.protfuncs", "server.conf.prototypefuncs"]
|
PROTOTYPEFUNC_MODULES = ["evennia.prototypes.protfuncs", "server.conf.prototypefuncs"]
|
||||||
|
|
|
||||||
|
|
@ -820,15 +820,16 @@ def funcparser_callable_pad(*args, **kwargs):
|
||||||
if not args:
|
if not args:
|
||||||
return ''
|
return ''
|
||||||
text, *rest = args
|
text, *rest = args
|
||||||
nargs = len(args)
|
nrest = len(rest)
|
||||||
try:
|
try:
|
||||||
width = int(kwargs.get("width", rest[0] if nargs > 0 else _CLIENT_DEFAULT_WIDTH))
|
width = int(kwargs.get("width", rest[0] if nrest > 0 else _CLIENT_DEFAULT_WIDTH))
|
||||||
except TypeError:
|
except TypeError:
|
||||||
width = _CLIENT_DEFAULT_WIDTH
|
width = _CLIENT_DEFAULT_WIDTH
|
||||||
align = kwargs.get("align", rest[1] if nargs > 1 else 'c')
|
|
||||||
fillchar = kwargs.get("fillchar", rest[2] if nargs > 2 else ' ')
|
align = kwargs.get("align", rest[1] if nrest > 1 else 'c')
|
||||||
if fillchar not in ('c', 'l', 'r'):
|
fillchar = kwargs.get("fillchar", rest[2] if nrest > 2 else ' ')
|
||||||
fillchar = 'c'
|
if align not in ('c', 'l', 'r'):
|
||||||
|
align = 'c'
|
||||||
return pad(str(text), width=width, align=align, fillchar=fillchar)
|
return pad(str(text), width=width, align=align, fillchar=fillchar)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -867,12 +868,12 @@ def funcparser_callable_crop(*args, **kwargs):
|
||||||
if not args:
|
if not args:
|
||||||
return ''
|
return ''
|
||||||
text, *rest = args
|
text, *rest = args
|
||||||
nargs = len(args)
|
nrest = len(rest)
|
||||||
try:
|
try:
|
||||||
width = int(kwargs.get("width", rest[0] if nargs > 0 else _CLIENT_DEFAULT_WIDTH))
|
width = int(kwargs.get("width", rest[0] if nrest > 0 else _CLIENT_DEFAULT_WIDTH))
|
||||||
except TypeError:
|
except TypeError:
|
||||||
width = _CLIENT_DEFAULT_WIDTH
|
width = _CLIENT_DEFAULT_WIDTH
|
||||||
suffix = kwargs.get('suffix', rest[1] if nargs > 1 else "[...]")
|
suffix = kwargs.get('suffix', rest[1] if nrest > 1 else "[...]")
|
||||||
return crop(str(text), width=width, suffix=str(suffix))
|
return crop(str(text), width=width, suffix=str(suffix))
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -896,14 +897,15 @@ def funcparser_callable_justify(*args, **kwargs):
|
||||||
"""
|
"""
|
||||||
if not args:
|
if not args:
|
||||||
return ''
|
return ''
|
||||||
text = args[0]
|
text, *rest = args
|
||||||
|
lrest = len(rest)
|
||||||
try:
|
try:
|
||||||
width = int(kwargs.get("width", _CLIENT_DEFAULT_WIDTH))
|
width = int(kwargs.get("width", rest[0] if lrest > 0 else _CLIENT_DEFAULT_WIDTH))
|
||||||
except TypeError:
|
except TypeError:
|
||||||
width = _CLIENT_DEFAULT_WIDTH
|
width = _CLIENT_DEFAULT_WIDTH
|
||||||
align = str(kwargs.get("align", 'f'))
|
align = str(kwargs.get("align", rest[1] if lrest > 1 else 'f'))
|
||||||
try:
|
try:
|
||||||
indent = int(kwargs.get("indent", 0))
|
indent = int(kwargs.get("indent", rest[2] if lrest > 2 else 0))
|
||||||
except TypeError:
|
except TypeError:
|
||||||
indent = 0
|
indent = 0
|
||||||
return justify(str(text), width=width, align=align, indent=indent)
|
return justify(str(text), width=width, align=align, indent=indent)
|
||||||
|
|
@ -912,17 +914,17 @@ def funcparser_callable_justify(*args, **kwargs):
|
||||||
# legacy for backwards compatibility
|
# legacy for backwards compatibility
|
||||||
def funcparser_callable_left_justify(*args, **kwargs):
|
def funcparser_callable_left_justify(*args, **kwargs):
|
||||||
"Usage: $ljust(text)"
|
"Usage: $ljust(text)"
|
||||||
return funcparser_callable_justify(*args, justify='l', **kwargs)
|
return funcparser_callable_justify(*args, align='l', **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def funcparser_callable_right_justify(*args, **kwargs):
|
def funcparser_callable_right_justify(*args, **kwargs):
|
||||||
"Usage: $rjust(text)"
|
"Usage: $rjust(text)"
|
||||||
return funcparser_callable_justify(*args, justify='r', **kwargs)
|
return funcparser_callable_justify(*args, align='r', **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def funcparser_callable_center_justify(*args, **kwargs):
|
def funcparser_callable_center_justify(*args, **kwargs):
|
||||||
"Usage: $cjust(text)"
|
"Usage: $cjust(text)"
|
||||||
return funcparser_callable_justify(*args, justify='c', **kwargs)
|
return funcparser_callable_justify(*args, align='c', **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def funcparser_callable_clr(*args, **kwargs):
|
def funcparser_callable_clr(*args, **kwargs):
|
||||||
|
|
@ -988,7 +990,8 @@ def funcparser_callable_search(*args, caller=None, access="control", **kwargs):
|
||||||
any: An entity match or None if no match or a list if `return_list` is set.
|
any: An entity match or None if no match or a list if `return_list` is set.
|
||||||
|
|
||||||
Raise:
|
Raise:
|
||||||
ParsingError: If zero/multimatch and `return_list` is False.
|
ParsingError: If zero/multimatch and `return_list` is False, or caller was not
|
||||||
|
passed into parser.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
- "$search(#233)"
|
- "$search(#233)"
|
||||||
|
|
@ -996,10 +999,12 @@ def funcparser_callable_search(*args, caller=None, access="control", **kwargs):
|
||||||
- "$search(meadow, return_list=True)"
|
- "$search(meadow, return_list=True)"
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return_list = bool(kwargs.get("return_list", "False"))
|
return_list = kwargs.get("return_list", "false").lower() == "true"
|
||||||
|
|
||||||
if not (args and caller):
|
if not args:
|
||||||
return [] if return_list else None
|
return [] if return_list else None
|
||||||
|
if not caller:
|
||||||
|
raise ParsingError("$search requires a `caller` passed to the parser.")
|
||||||
|
|
||||||
query = str(args[0])
|
query = str(args[0])
|
||||||
|
|
||||||
|
|
@ -1040,63 +1045,72 @@ def funcparser_callable_search_list(*args, caller=None, access="control", **kwar
|
||||||
return_list=True, **kwargs)
|
return_list=True, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def funcparser_callable_you(*args, you_obj=None, you_target=None, capitalize=False, **kwargs):
|
def funcparser_callable_you(*args, you=None, receiver=None, mapping=None, capitalize=False, **kwargs):
|
||||||
"""
|
"""
|
||||||
Usage: %you()
|
Usage: $you() or $you(key)
|
||||||
|
|
||||||
Replaces with you for the caller of the string, with the display_name
|
Replaces with you for the caller of the string, with the display_name
|
||||||
of the caller for others.
|
of the caller for others.
|
||||||
|
|
||||||
Kwargs:
|
Kwargs:
|
||||||
you_obj (Object): The object who represents 'you' in the string.
|
you (Object): The 'you' in the string. This is used unless another
|
||||||
you_target (Object): The recipient of the string.
|
you-key is passed to the callable in combination with `mapping`.
|
||||||
|
receiver (Object): The recipient of the string.
|
||||||
|
mapping (dict, optional): This is a mapping `{key:Object, ...}` and is
|
||||||
|
used to find which object `$you(key)` refers to. If not given, the
|
||||||
|
`you` kwarg is used.
|
||||||
capitalize (bool): Passed by the You helper, to capitalize you.
|
capitalize (bool): Passed by the You helper, to capitalize you.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
str: The parsed string.
|
str: The parsed string.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
ParsingError: If `you_obj` and `you_target` were not supplied.
|
ParsingError: If `you` and `receiver` were not supplied.
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
The kwargs must be supplied to the parse method. If not given,
|
The kwargs should be passed the to parser directly.
|
||||||
the parsing will be aborted. Note that it will not capitalize
|
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
This can be used by the say or emote hooks to pass actor stance
|
This can be used by the say or emote hooks to pass actor stance
|
||||||
strings. This should usually be combined with the $inflect() callable.
|
strings. This should usually be combined with the $inflect() callable.
|
||||||
|
|
||||||
- `With a grin, $you() $conj(jump).`
|
- `With a grin, $you() $conj(jump) at $you(tommy).`
|
||||||
|
|
||||||
The You-object will see "With a grin, you jump."
|
The You-object will see "With a grin, you jump at Tommy."
|
||||||
Others will see "With a grin, CharName jumps."
|
Tommy will see "With a grin, CharName jumps at you."
|
||||||
|
Others will see "With a grin, CharName jumps at Tommy."
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if not (you_obj and you_target):
|
if args and mapping:
|
||||||
raise ParsingError("No you_obj/target supplied to $you callable")
|
# this would mean a $you(key) form
|
||||||
|
try:
|
||||||
|
you = mapping.get(args[0])
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if not (you and receiver):
|
||||||
|
raise ParsingError("No you-object or receiver supplied to $you callable.")
|
||||||
|
|
||||||
capitalize = bool(capitalize)
|
capitalize = bool(capitalize)
|
||||||
if you_obj == you_target:
|
if you == receiver:
|
||||||
return "You" if capitalize else "you"
|
return "You" if capitalize else "you"
|
||||||
return you_obj.get_display_name(looker=you_target)
|
return you.get_display_name(looker=receiver) if hasattr(you, "get_display_name") else str(you)
|
||||||
|
|
||||||
|
|
||||||
def funcparser_callable_You(*args, you_obj=None, you_target=None, capitalize=True, **kwargs):
|
def funcparser_callable_You(*args, you=None, receiver=None, mapping=None, capitalize=True, **kwargs):
|
||||||
"""
|
"""
|
||||||
Usage: $You() - capitalizes the 'you' output.
|
Usage: $You() - capitalizes the 'you' output.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return funcparser_callable_you(
|
return funcparser_callable_you(
|
||||||
*args, you_obj=you_obj, you_target=you_target, capitalize=capitalize, **kwargs)
|
*args, you=you, receiver=receiver, mapping=mapping, capitalize=capitalize, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def funcparser_callable_conjugate(*args, you_obj=None, you_target=None, **kwargs):
|
def funcparser_callable_conjugate(*args, you=None, receiver=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
Conjugate a verb according to if it should be 2nd or third person. The
|
$conj(verb)
|
||||||
mlconjug3 package supports French, English, Italian, Portugese and Romanian
|
|
||||||
(see https://pypi.org/project/mlconjug3/). The function will pick the
|
|
||||||
language from settings.LANGUAGE_CODE, or English if an unsupported language
|
|
||||||
was found.
|
|
||||||
|
|
||||||
|
Conjugate a verb according to if it should be 2nd or third person.
|
||||||
Kwargs:
|
Kwargs:
|
||||||
you_obj (Object): The object who represents 'you' in the string.
|
you_obj (Object): The object who represents 'you' in the string.
|
||||||
you_target (Object): The recipient of the string.
|
you_target (Object): The recipient of the string.
|
||||||
|
|
@ -1105,34 +1119,39 @@ def funcparser_callable_conjugate(*args, you_obj=None, you_target=None, **kwargs
|
||||||
str: The parsed string.
|
str: The parsed string.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
ParsingError: If `you_obj` and `you_target` were not supplied.
|
ParsingError: If `you` and `recipient` were not both supplied.
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
The kwargs must be supplied to the parse method. If not given,
|
Note that it will not capitalized.
|
||||||
the parsing will be aborted. Note that it will not capitalize
|
This assumes that the active party (You) is the one performing the verb.
|
||||||
|
This automatic conjugation will fail if the active part is another person
|
||||||
|
than 'you'.
|
||||||
|
The you/receiver should be passed to the parser directly.
|
||||||
|
|
||||||
Exampels:
|
Exampels:
|
||||||
This is often used in combination with the $you/You( callables.
|
This is often used in combination with the $you/You( callables.
|
||||||
|
|
||||||
- `With a grin, $you() $conj(jump)`
|
- `With a grin, $you() $conj(jump)`
|
||||||
|
|
||||||
The You-object will see "With a grin, you jump."
|
You will see "With a grin, you jump."
|
||||||
Others will see "With a grin, CharName jumps."
|
Others will see "With a grin, CharName jumps."
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if not args:
|
if not args:
|
||||||
return ''
|
return ''
|
||||||
if not (you_obj and you_target):
|
if not (you and receiver):
|
||||||
raise ParsingError("No you_obj/target supplied to $conj callable")
|
raise ParsingError("No youj/receiver supplied to $conj callable")
|
||||||
|
|
||||||
you_str, them_str = verb_actor_stance_components(args[0])
|
second_person_str, third_person_str = verb_actor_stance_components(args[0])
|
||||||
return you_str if you_obj == you_target else them_str
|
return second_person_str if you == receiver else third_person_str
|
||||||
|
|
||||||
|
|
||||||
# these are made available as callables by adding 'evennia.utils.funcparser' as
|
# these are made available as callables by adding 'evennia.utils.funcparser' as
|
||||||
# a callable-path when initializing the FuncParser.
|
# a callable-path when initializing the FuncParser.
|
||||||
|
|
||||||
FUNCPARSER_CALLABLES = {
|
FUNCPARSER_CALLABLES = {
|
||||||
|
# 'standard' callables
|
||||||
|
|
||||||
# eval and arithmetic
|
# eval and arithmetic
|
||||||
"eval": funcparser_callable_eval,
|
"eval": funcparser_callable_eval,
|
||||||
"add": funcparser_callable_add,
|
"add": funcparser_callable_add,
|
||||||
|
|
@ -1160,14 +1179,18 @@ FUNCPARSER_CALLABLES = {
|
||||||
"justify_center": funcparser_callable_center_justify,
|
"justify_center": funcparser_callable_center_justify,
|
||||||
"space": funcparser_callable_space,
|
"space": funcparser_callable_space,
|
||||||
"clr": funcparser_callable_clr,
|
"clr": funcparser_callable_clr,
|
||||||
|
}
|
||||||
|
|
||||||
# seaching
|
SEARCHING_CALLABLES = {
|
||||||
|
# requires `caller` and optionally `access` to be passed into parser
|
||||||
"search": funcparser_callable_search,
|
"search": funcparser_callable_search,
|
||||||
"obj": funcparser_callable_search, # aliases for backwards compat
|
"obj": funcparser_callable_search, # aliases for backwards compat
|
||||||
"objlist": funcparser_callable_search_list,
|
"objlist": funcparser_callable_search_list,
|
||||||
"dbref": funcparser_callable_search,
|
"dbref": funcparser_callable_search,
|
||||||
|
}
|
||||||
|
|
||||||
# referencing
|
ACTOR_STANCE_CALLABLES = {
|
||||||
|
# requires `you`, `receiver` and `mapping` to be passed into parser
|
||||||
"you": funcparser_callable_you,
|
"you": funcparser_callable_you,
|
||||||
"You": funcparser_callable_You,
|
"You": funcparser_callable_You,
|
||||||
"conj": funcparser_callable_conjugate,
|
"conj": funcparser_callable_conjugate,
|
||||||
|
|
|
||||||
|
|
@ -1,529 +0,0 @@
|
||||||
"""
|
|
||||||
Inline functions (nested form).
|
|
||||||
|
|
||||||
This parser accepts nested inlinefunctions on the form
|
|
||||||
|
|
||||||
```python
|
|
||||||
$funcname(arg, arg, ...)
|
|
||||||
```
|
|
||||||
|
|
||||||
embedded in any text where any arg can be another ``$funcname()`` call.
|
|
||||||
This functionality is turned off by default - to activate,
|
|
||||||
`settings.INLINEFUNC_ENABLED` must be set to `True`.
|
|
||||||
|
|
||||||
Each token starts with `$funcname(` where there must be no space
|
|
||||||
between the `$funcname` and `"("`. The inlinefunc ends with a matched ending parentesis.
|
|
||||||
`")"`.
|
|
||||||
|
|
||||||
Inside the inlinefunc definition, one can use `\` to escape. This is
|
|
||||||
mainly needed for escaping commas in flowing text (which would
|
|
||||||
otherwise be interpreted as an argument separator), or to escape `)`
|
|
||||||
when not intended to close the function block. Enclosing text in
|
|
||||||
matched `\"\"\"` (triple quotes) or `'''` (triple single-quotes) will
|
|
||||||
also escape *everything* within without needing to escape individual
|
|
||||||
characters.
|
|
||||||
|
|
||||||
The available inlinefuncs are defined as global-level functions in
|
|
||||||
modules defined by `settings.INLINEFUNC_MODULES`. They are identified
|
|
||||||
by their function name (and ignored if this name starts with `_`). They
|
|
||||||
should be on the following form:
|
|
||||||
|
|
||||||
```python
|
|
||||||
def funcname (*args, **kwargs):
|
|
||||||
# ...
|
|
||||||
```
|
|
||||||
|
|
||||||
Here, the arguments given to `$funcname(arg1,arg2)` will appear as the
|
|
||||||
`*args` tuple. This will be populated by the arguments given to the
|
|
||||||
inlinefunc in-game - the only part that will be available from
|
|
||||||
in-game. `**kwargs` are not supported from in-game but are only used
|
|
||||||
internally by Evennia to make details about the caller available to
|
|
||||||
the function. The kwarg passed to all functions is `session`, the
|
|
||||||
Sessionobject for the object seeing the string. This may be `None` if
|
|
||||||
the string is sent to a non-puppetable object. The inlinefunc should
|
|
||||||
never raise an exception.
|
|
||||||
|
|
||||||
There are two reserved function names:
|
|
||||||
|
|
||||||
- "nomatch": This is called if the user uses a functionname that is
|
|
||||||
not registered. The nomatch function will get the name of the
|
|
||||||
not-found function as its first argument followed by the normal
|
|
||||||
arguments to the given function. If not defined the default effect is
|
|
||||||
to print `<UNKNOWN>` to replace the unknown function.
|
|
||||||
- "stackfull": This is called when the maximum nested function stack is reached.
|
|
||||||
When this happens, the original parsed string is returned and the result of
|
|
||||||
the `stackfull` inlinefunc is appended to the end. By default this is an
|
|
||||||
error message.
|
|
||||||
|
|
||||||
Syntax errors, notably failing to completely closing all inlinefunc
|
|
||||||
blocks, will lead to the entire string remaining unparsed. Inlineparsing should
|
|
||||||
never traceback.
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
import re
|
|
||||||
import fnmatch
|
|
||||||
import random as base_random
|
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
from evennia.utils import utils, logger
|
|
||||||
|
|
||||||
# The stack size is a security measure. Set to <=0 to disable.
|
|
||||||
_STACK_MAXSIZE = settings.INLINEFUNC_STACK_MAXSIZE
|
|
||||||
|
|
||||||
|
|
||||||
# example/testing inline functions
|
|
||||||
|
|
||||||
|
|
||||||
def random(*args, **kwargs):
|
|
||||||
"""
|
|
||||||
Inlinefunc. Returns a random number between
|
|
||||||
0 and 1, from 0 to a maximum value, or within a given range (inclusive).
|
|
||||||
|
|
||||||
Args:
|
|
||||||
minval (str, optional): Minimum value. If not given, assumed 0.
|
|
||||||
maxval (str, optional): Maximum value.
|
|
||||||
|
|
||||||
Keyword argumuents:
|
|
||||||
session (Session): Session getting the string.
|
|
||||||
|
|
||||||
Notes:
|
|
||||||
If either of the min/maxvalue has a '.' in it, a floating-point random
|
|
||||||
value will be returned. Otherwise it will be an integer value in the
|
|
||||||
given range.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
`$random()`
|
|
||||||
`$random(5)`
|
|
||||||
`$random(5, 10)`
|
|
||||||
|
|
||||||
"""
|
|
||||||
nargs = len(args)
|
|
||||||
if nargs == 1:
|
|
||||||
# only maxval given
|
|
||||||
minval, maxval = "0", args[0]
|
|
||||||
elif nargs > 1:
|
|
||||||
minval, maxval = args[:2]
|
|
||||||
else:
|
|
||||||
minval, maxval = ("0", "1")
|
|
||||||
|
|
||||||
if "." in minval or "." in maxval:
|
|
||||||
# float mode
|
|
||||||
try:
|
|
||||||
minval, maxval = float(minval), float(maxval)
|
|
||||||
except ValueError:
|
|
||||||
minval, maxval = 0, 1
|
|
||||||
return "{:.2f}".format(minval + maxval * base_random.random())
|
|
||||||
else:
|
|
||||||
# int mode
|
|
||||||
try:
|
|
||||||
minval, maxval = int(minval), int(maxval)
|
|
||||||
except ValueError:
|
|
||||||
minval, maxval = 0, 1
|
|
||||||
return str(base_random.randint(minval, maxval))
|
|
||||||
|
|
||||||
|
|
||||||
def pad(*args, **kwargs):
|
|
||||||
"""
|
|
||||||
Inlinefunc. Pads text to given width.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
text (str, optional): Text to pad.
|
|
||||||
width (str, optional): Will be converted to integer. Width
|
|
||||||
of padding.
|
|
||||||
align (str, optional): Alignment of padding; one of 'c', 'l' or 'r'.
|
|
||||||
fillchar (str, optional): Character used for padding. Defaults to a
|
|
||||||
space.
|
|
||||||
|
|
||||||
Keyword Args:
|
|
||||||
session (Session): Session performing the pad.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
`$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(*args, **kwargs):
|
|
||||||
"""
|
|
||||||
Inlinefunc. Crops ingoing text to given widths.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
text (str, optional): Text to crop.
|
|
||||||
width (str, optional): Will be converted to an integer. Width of
|
|
||||||
crop in characters.
|
|
||||||
suffix (str, optional): End string to mark the fact that a part
|
|
||||||
of the string was cropped. Defaults to `[...]`.
|
|
||||||
Keyword Args:
|
|
||||||
session (Session): Session performing the crop.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
`$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)
|
|
||||||
|
|
||||||
|
|
||||||
def space(*args, **kwargs):
|
|
||||||
"""
|
|
||||||
Inlinefunc. Inserts an arbitrary number of spaces. Defaults to 4 spaces.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
spaces (int, optional): The number of spaces to insert.
|
|
||||||
|
|
||||||
Keyword Args:
|
|
||||||
session (Session): Session performing the crop.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
`$space(20)`
|
|
||||||
|
|
||||||
"""
|
|
||||||
width = 4
|
|
||||||
if args:
|
|
||||||
width = abs(int(args[0])) if args[0].strip().isdigit() else 4
|
|
||||||
return " " * width
|
|
||||||
|
|
||||||
|
|
||||||
def clr(*args, **kwargs):
|
|
||||||
"""
|
|
||||||
Inlinefunc. Colorizes nested text.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
startclr (str, optional): An ANSI color abbreviation without the
|
|
||||||
prefix `|`, such as `r` (red foreground) or `[r` (red background).
|
|
||||||
text (str, optional): Text
|
|
||||||
endclr (str, optional): The color to use at the end of the string. Defaults
|
|
||||||
to `|n` (reset-color).
|
|
||||||
Keyword Args:
|
|
||||||
session (Session): Session object triggering inlinefunc.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
`$clr(startclr, text, endclr)`
|
|
||||||
|
|
||||||
"""
|
|
||||||
text = ""
|
|
||||||
nargs = len(args)
|
|
||||||
if nargs > 0:
|
|
||||||
color = args[0].strip()
|
|
||||||
if nargs > 1:
|
|
||||||
text = args[1]
|
|
||||||
text = "|" + color + text
|
|
||||||
if nargs > 2:
|
|
||||||
text += "|" + args[2].strip()
|
|
||||||
else:
|
|
||||||
text += "|n"
|
|
||||||
return text
|
|
||||||
|
|
||||||
|
|
||||||
def null(*args, **kwargs):
|
|
||||||
return args[0] if args else ""
|
|
||||||
|
|
||||||
|
|
||||||
def nomatch(name, *args, **kwargs):
|
|
||||||
"""
|
|
||||||
Default implementation of nomatch returns the function as-is as a string.
|
|
||||||
|
|
||||||
"""
|
|
||||||
kwargs.pop("inlinefunc_stack_depth", None)
|
|
||||||
kwargs.pop("session")
|
|
||||||
|
|
||||||
return "${name}({args}{kwargs})".format(
|
|
||||||
name=name,
|
|
||||||
args=",".join(args),
|
|
||||||
kwargs=",".join("{}={}".format(key, val) for key, val in kwargs.items()),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
_INLINE_FUNCS = {}
|
|
||||||
|
|
||||||
# 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.
|
|
||||||
_DEFAULT_FUNCS = {
|
|
||||||
"nomatch": lambda *args, **kwargs: "<UNKNOWN>",
|
|
||||||
"stackfull": lambda *args, **kwargs: "\n (not parsed: ",
|
|
||||||
}
|
|
||||||
|
|
||||||
_INLINE_FUNCS.update(_DEFAULT_FUNCS)
|
|
||||||
|
|
||||||
# load custom inline func modules.
|
|
||||||
for module in utils.make_iter(settings.INLINEFUNC_MODULES):
|
|
||||||
try:
|
|
||||||
_INLINE_FUNCS.update(utils.callables_from_module(module))
|
|
||||||
except ImportError as err:
|
|
||||||
if module == "server.conf.inlinefuncs":
|
|
||||||
# a temporary warning since the default module changed name
|
|
||||||
raise ImportError(
|
|
||||||
"Error: %s\nPossible reason: mygame/server/conf/inlinefunc.py should "
|
|
||||||
"be renamed to mygame/server/conf/inlinefuncs.py (note "
|
|
||||||
"the S at the end)." % err
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
raise
|
|
||||||
|
|
||||||
|
|
||||||
# regex definitions
|
|
||||||
|
|
||||||
_RE_STARTTOKEN = re.compile(r"(?<!\\)\$(\w+)\(") # unescaped $funcname( (start of function call)
|
|
||||||
|
|
||||||
# note: this regex can be experimented with at https://regex101.com/r/kGR3vE/2
|
|
||||||
_RE_TOKEN = re.compile(
|
|
||||||
r"""
|
|
||||||
(?<!\\)\'\'\'(?P<singlequote>.*?)(?<!\\)\'\'\'| # single-triplets escape all inside
|
|
||||||
(?<!\\)\"\"\"(?P<doublequote>.*?)(?<!\\)\"\"\"| # double-triplets escape all inside
|
|
||||||
(?P<comma>(?<!\\)\,)| # , (argument sep)
|
|
||||||
(?P<end>(?<!\\)\))| # ) (possible end of func call)
|
|
||||||
(?P<leftparens>(?<!\\)\()| # ( (lone left-parens)
|
|
||||||
(?P<start>(?<!\\)\$\w+\()| # $funcname (start of func call)
|
|
||||||
(?P<escaped> # escaped tokens to re-insert sans backslash
|
|
||||||
\\\'|\\\"|\\\)|\\\$\w+\(|\\\()|
|
|
||||||
(?P<rest> # everything else to re-insert verbatim
|
|
||||||
\$(?!\w+\()|\'|\"|\\|[^),$\'\"\\\(]+)""",
|
|
||||||
re.UNICODE | re.IGNORECASE | re.VERBOSE | re.DOTALL,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Cache for function lookups.
|
|
||||||
_PARSING_CACHE = utils.LimitedSizeOrderedDict(size_limit=1000)
|
|
||||||
|
|
||||||
|
|
||||||
class ParseStack(list):
|
|
||||||
"""
|
|
||||||
Custom stack that always concatenates strings together when the
|
|
||||||
strings are added next to one another. Tuples are stored
|
|
||||||
separately and None is used to mark that a string should be broken
|
|
||||||
up into a new chunk. Below is the resulting stack after separately
|
|
||||||
appending 3 strings, None, 2 strings, a tuple and finally 2
|
|
||||||
strings:
|
|
||||||
|
|
||||||
[string + string + string,
|
|
||||||
None
|
|
||||||
string + string,
|
|
||||||
tuple,
|
|
||||||
string + string]
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
# always start stack with the empty string
|
|
||||||
list.append(self, "")
|
|
||||||
# indicates if the top of the stack is a string or not
|
|
||||||
self._string_last = True
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
return (
|
|
||||||
super().__eq__(other)
|
|
||||||
and hasattr(other, "_string_last")
|
|
||||||
and self._string_last == other._string_last
|
|
||||||
)
|
|
||||||
|
|
||||||
def __ne__(self, other):
|
|
||||||
return not self.__eq__(other)
|
|
||||||
|
|
||||||
def append(self, item):
|
|
||||||
"""
|
|
||||||
The stack will merge strings, add other things as normal
|
|
||||||
"""
|
|
||||||
if isinstance(item, str):
|
|
||||||
if self._string_last:
|
|
||||||
self[-1] += item
|
|
||||||
else:
|
|
||||||
list.append(self, item)
|
|
||||||
self._string_last = True
|
|
||||||
else:
|
|
||||||
# everything else is added as normal
|
|
||||||
list.append(self, item)
|
|
||||||
self._string_last = False
|
|
||||||
|
|
||||||
|
|
||||||
# class InlinefuncError(RuntimeError):
|
|
||||||
# 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)
|
|
||||||
|
|
@ -10,7 +10,7 @@ from simpleeval import simple_eval
|
||||||
from parameterized import parameterized
|
from parameterized import parameterized
|
||||||
from django.test import TestCase, override_settings
|
from django.test import TestCase, override_settings
|
||||||
|
|
||||||
from evennia.utils import funcparser
|
from evennia.utils import funcparser, test_resources
|
||||||
|
|
||||||
|
|
||||||
def _test_callable(*args, **kwargs):
|
def _test_callable(*args, **kwargs):
|
||||||
|
|
@ -300,33 +300,27 @@ class TestDefaultCallables(TestCase):
|
||||||
|
|
||||||
@parameterized.expand([
|
@parameterized.expand([
|
||||||
("$You() $conj(smile) at him.", "You smile at him.", "Char1 smiles at him."),
|
("$You() $conj(smile) at him.", "You smile at him.", "Char1 smiles at him."),
|
||||||
|
("$You() $conj(smile) at $You(char1).", "You smile at You.", "Char1 smiles at Char1."),
|
||||||
|
("$You() $conj(smile) at $You(char2).", "You smile at Char2.", "Char1 smiles at You."),
|
||||||
|
("$You(char2) $conj(smile) at $you(char1).", "Char2 smile at you.", "You smiles at Char1."),
|
||||||
])
|
])
|
||||||
def test_conjugate(self, string, expected_you, expected_them):
|
def test_conjugate(self, string, expected_you, expected_them):
|
||||||
"""
|
"""
|
||||||
Test callables with various input strings
|
Test callables with various input strings
|
||||||
|
|
||||||
"""
|
"""
|
||||||
ret = self.parser.parse(string, you_obj=self.obj1, you_target=self.obj1,
|
mapping = {"char1": self.obj1, "char2": self.obj2}
|
||||||
|
ret = self.parser.parse(string, you=self.obj1, receiver=self.obj1, mapping=mapping,
|
||||||
raise_errors=True)
|
raise_errors=True)
|
||||||
self.assertEqual(expected_you, ret)
|
self.assertEqual(expected_you, ret)
|
||||||
ret = self.parser.parse(string, you_obj=self.obj1, you_target=self.obj2,
|
ret = self.parser.parse(string, you=self.obj1, receiver=self.obj2, mapping=mapping,
|
||||||
raise_errors=True)
|
raise_errors=True)
|
||||||
self.assertEqual(expected_them, ret)
|
self.assertEqual(expected_them, ret)
|
||||||
|
|
||||||
|
|
||||||
class TestOldDefaultCallables(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([
|
@parameterized.expand([
|
||||||
("Test $pad(Hello, 20, c, -) there", "Test -------Hello-------- there"),
|
("Test $pad(Hello, 20, c, -) there", "Test -------Hello-------- there"),
|
||||||
|
("Test $pad(Hello, width=20, align=c, fillchar=-) there",
|
||||||
|
"Test -------Hello-------- there"),
|
||||||
("Test $crop(This is a long test, 12)", "Test This is[...]"),
|
("Test $crop(This is a long test, 12)", "Test This is[...]"),
|
||||||
("Some $space(10) here", "Some here"),
|
("Some $space(10) here", "Some here"),
|
||||||
("Some $clr(b, blue color) now", "Some |bblue color|n now"),
|
("Some $clr(b, blue color) now", "Some |bblue color|n now"),
|
||||||
|
|
@ -335,8 +329,13 @@ class TestOldDefaultCallables(TestCase):
|
||||||
("Some $mult(3, 2) things", "Some 6 things"),
|
("Some $mult(3, 2) things", "Some 6 things"),
|
||||||
("Some $div(6, 2) things", "Some 3.0 things"),
|
("Some $div(6, 2) things", "Some 3.0 things"),
|
||||||
("Some $toint(6) things", "Some 6 things"),
|
("Some $toint(6) things", "Some 6 things"),
|
||||||
|
("Some $ljust(Hello, 30)", "Some Hello "),
|
||||||
|
("Some $rjust(Hello, 30)", "Some Hello"),
|
||||||
|
("Some $rjust(Hello, width=30)", "Some Hello"),
|
||||||
|
("Some $cjust(Hello, 30)", "Some Hello "),
|
||||||
|
("Some $eval('-'*20)Hello", "Some --------------------Hello"),
|
||||||
])
|
])
|
||||||
def test_callable(self, string, expected):
|
def test_other_callables(self, string, expected):
|
||||||
"""
|
"""
|
||||||
Test default callables.
|
Test default callables.
|
||||||
|
|
||||||
|
|
@ -349,3 +348,60 @@ class TestOldDefaultCallables(TestCase):
|
||||||
ret = self.parser.parse(string, raise_errors=True)
|
ret = self.parser.parse(string, raise_errors=True)
|
||||||
ret = int(ret)
|
ret = int(ret)
|
||||||
self.assertTrue(1 <= ret <= 10)
|
self.assertTrue(1 <= ret <= 10)
|
||||||
|
|
||||||
|
|
||||||
|
class TestCallableSearch(test_resources.EvenniaTest):
|
||||||
|
"""
|
||||||
|
Test the $search(query) callable
|
||||||
|
|
||||||
|
"""
|
||||||
|
@override_settings(INLINEFUNC_MODULES=["evennia.utils.funcparser"])
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
self.parser = funcparser.FuncParser(settings.INLINEFUNC_MODULES)
|
||||||
|
|
||||||
|
def test_search_obj(self):
|
||||||
|
"""
|
||||||
|
Test searching for an object
|
||||||
|
|
||||||
|
"""
|
||||||
|
string = "$search(Char)"
|
||||||
|
expected = self.char1
|
||||||
|
|
||||||
|
ret = self.parser.parse(string, caller=self.char1, return_str=False, raise_errors=True)
|
||||||
|
self.assertEqual(expected, ret)
|
||||||
|
|
||||||
|
def test_search_account(self):
|
||||||
|
"""
|
||||||
|
Test searching for an account
|
||||||
|
|
||||||
|
"""
|
||||||
|
string = "$search(TestAccount, type=account)"
|
||||||
|
expected = self.account
|
||||||
|
|
||||||
|
ret = self.parser.parse(string, caller=self.char1, return_str=False, raise_errors=True)
|
||||||
|
self.assertEqual(expected, ret)
|
||||||
|
|
||||||
|
def test_search_script(self):
|
||||||
|
"""
|
||||||
|
Test searching for a script
|
||||||
|
|
||||||
|
"""
|
||||||
|
string = "$search(Script, type=script)"
|
||||||
|
expected = self.script
|
||||||
|
|
||||||
|
ret = self.parser.parse(string, caller=self.char1, return_str=False, raise_errors=True)
|
||||||
|
self.assertEqual(expected, ret)
|
||||||
|
|
||||||
|
def test_search_obj_embedded(self):
|
||||||
|
"""
|
||||||
|
Test searching for an object - embedded in str
|
||||||
|
|
||||||
|
"""
|
||||||
|
string = "This is $search(Char) the guy."
|
||||||
|
expected = "This is " + str(self.char1) + " the guy."
|
||||||
|
|
||||||
|
ret = self.parser.parse(string, caller=self.char1, return_str=False, raise_errors=True)
|
||||||
|
self.assertEqual(expected, ret)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue