mirror of
https://github.com/evennia/evennia.git
synced 2026-03-16 21:06:30 +01:00
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:
|
||||
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
|
||||
if not prototype_success:
|
||||
caller.msg("Prototype %s failed to apply." % prototype["key"])
|
||||
|
|
@ -3559,7 +3560,7 @@ class CmdSpawn(COMMAND_DEFAULT_CLASS):
|
|||
return
|
||||
try:
|
||||
n_updated = spawner.batch_update_objects_with_prototype(
|
||||
prototype, objects=existing_objects
|
||||
prototype, objects=existing_objects, caller=caller,
|
||||
)
|
||||
except Exception:
|
||||
logger.log_trace()
|
||||
|
|
@ -3811,7 +3812,7 @@ class CmdSpawn(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
# proceed to spawning
|
||||
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))
|
||||
if not prototype.get("location") and not noloc:
|
||||
# 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
|
||||
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:
|
||||
|
||||
|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
|
||||
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
|
||||
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
|
||||
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
|
||||
|
||||
|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.commands import cmdset, command
|
||||
from evennia.commands.cmdsethandler import CmdSetHandler
|
||||
from evennia.utils import funcparser
|
||||
from evennia.utils import create
|
||||
from evennia.utils import search
|
||||
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)
|
||||
_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):
|
||||
"""
|
||||
|
|
@ -717,64 +724,94 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
|||
text (str or tuple): Message to send. If a tuple, this should be
|
||||
on the valid OOB outmessage form `(message, {kwargs})`,
|
||||
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.
|
||||
from_obj (Object, optional): An object designated as the
|
||||
"sender" of the message. See `DefaultObject.msg()` for
|
||||
more info.
|
||||
mapping (dict, optional): A mapping of formatting keys
|
||||
`{"key":<object>, "key2":<object2>,...}. The keys
|
||||
must match `{key}` markers in the `text` if this is a string or
|
||||
in the internal `message` if `text` is a tuple. These
|
||||
formatting statements will be
|
||||
replaced by the return of `<object>.get_display_name(looker)`
|
||||
for every looker in contents that receives the
|
||||
message. This allows for every object to potentially
|
||||
get its own customized string.
|
||||
Keyword Args:
|
||||
Keyword arguments will be passed on to `obj.msg()` for all
|
||||
messaged objects.
|
||||
`{"key":<object>, "key2":<object2>,...}.
|
||||
The keys must either match `{key}` or `$You(key)/$you(key)` markers
|
||||
in the `text` string. If `<object>` doesn't have a `get_display_name`
|
||||
method, it will be returned as a string. If not set, a key `you` will
|
||||
be auto-added to point to `from_obj` if given, otherwise to `self`.
|
||||
**kwargs: Keyword arguments will be passed on to `obj.msg()` for all
|
||||
messaged objects.
|
||||
|
||||
Notes:
|
||||
The `mapping` argument is required if `message` contains
|
||||
{}-style format syntax. The keys of `mapping` should match
|
||||
named format tokens, and its values will have their
|
||||
`get_display_name()` function called for each object in
|
||||
the room before substitution. If an item in the mapping does
|
||||
not have `get_display_name()`, its string value will be used.
|
||||
For 'actor-stance' reporting (You say/Name says), use the
|
||||
`$You()/$you()/$You(key)` and `$conj(verb)` (verb-conjugation)
|
||||
inline callables. This will use the respective `get_display_name()`
|
||||
for all onlookers except for `from_obj or self`, which will become
|
||||
'You/you'. If you use `$You/you(key)`, the key must be in `mapping`.
|
||||
|
||||
Example:
|
||||
Say Char is a Character object and Npc is an NPC object:
|
||||
For 'director-stance' reporting (Name says/Name says), use {key}
|
||||
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(
|
||||
"{attacker} kicks {defender}",
|
||||
mapping=dict(attacker=char, defender=npc), exclude=(char, npc))
|
||||
Examples:
|
||||
|
||||
This will result in everyone in the room seeing 'Char kicks NPC'
|
||||
where everyone may potentially see different results for Char and Npc
|
||||
depending on the results of `char.get_display_name(looker)` and
|
||||
`npc.get_display_name(looker)` for each particular onlooker
|
||||
Let's assume
|
||||
- `player1.key -> "Player1"`,
|
||||
`player1.get_display_name(looker=player2) -> "The First girl"`
|
||||
- `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})
|
||||
is_outcmd = text and is_iter(text)
|
||||
inmessage = text[0] if is_outcmd else text
|
||||
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
|
||||
if exclude:
|
||||
exclude = make_iter(exclude)
|
||||
contents = [obj for obj in contents if obj not in exclude]
|
||||
for obj in contents:
|
||||
if mapping:
|
||||
substitutions = {
|
||||
t: sub.get_display_name(obj) if hasattr(sub, "get_display_name") else str(sub)
|
||||
for t, sub in mapping.items()
|
||||
}
|
||||
outmessage = inmessage.format(**substitutions)
|
||||
else:
|
||||
outmessage = inmessage
|
||||
obj.msg(text=(outmessage, outkwargs), from_obj=from_obj, **kwargs)
|
||||
|
||||
for receiver in contents:
|
||||
|
||||
# actor-stance replacements
|
||||
inmessage = _MSG_CONTENTS_PARSER.parse(
|
||||
inmessage, raise_errors=True, return_string=True,
|
||||
you=you, receiver=receiver, mapping=mapping)
|
||||
|
||||
# director-stance replacements
|
||||
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(
|
||||
self,
|
||||
|
|
|
|||
|
|
@ -2115,7 +2115,8 @@ def _apply_diff(caller, **kwargs):
|
|||
objects = kwargs["objects"]
|
||||
back_node = kwargs["back_node"]
|
||||
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))
|
||||
return back_node
|
||||
|
||||
|
|
@ -2483,7 +2484,7 @@ def _spawn(caller, **kwargs):
|
|||
if not prototype.get("location"):
|
||||
prototype["location"] = caller
|
||||
|
||||
obj = spawner.spawn(prototype)
|
||||
obj = spawner.spawn(prototype, caller=caller)
|
||||
if obj:
|
||||
obj = obj[0]
|
||||
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
|
||||
prototype with custom logics without having access to Python. The Protfunc is parsed using the
|
||||
inlinefunc parser but is fired at the moment the spawning happens, using the creating object's
|
||||
session as input.
|
||||
Protfuncs are FuncParser-callables that can be embedded in a prototype to
|
||||
provide custom logic without having access to Python. The protfunc is parsed at
|
||||
the time of spawning, using the creating object's session as input. If the
|
||||
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.:
|
||||
|
||||
{ ...
|
||||
|
||||
"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
|
||||
value for that prototype key for that individual spawn.
|
||||
|
||||
Available protfuncs are callables in one of the modules of `settings.PROT_FUNC_MODULES`. They
|
||||
are specified as functions
|
||||
Available protfuncs are either all callables in one of the modules of `settings.PROT_FUNC_MODULES`
|
||||
or all callables added to a dict FUNCPARSER_CALLABLES in such a module.
|
||||
|
||||
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.
|
||||
- prototype (dict): The dict this protfunc is a part of.
|
||||
- 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
|
||||
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 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]+")
|
||||
from evennia.utils import funcparser
|
||||
|
||||
|
||||
# default protfuncs
|
||||
|
||||
|
||||
def random(*args, **kwargs):
|
||||
def protfunc_callable_protkey(*args, **kwargs):
|
||||
"""
|
||||
Usage: $random()
|
||||
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>)
|
||||
Usage: $protkey(keyname)
|
||||
Returns the value of another key in this prototoype. Will raise an error if
|
||||
the key is not found in this prototype.
|
||||
|
||||
"""
|
||||
if args:
|
||||
prototype = kwargs["prototype"]
|
||||
return prototype[args[0].strip()]
|
||||
if not args:
|
||||
return ""
|
||||
|
||||
prototype = kwargs.get("prototype", {})
|
||||
return prototype[args[0].strip()]
|
||||
|
||||
|
||||
def add(*args, **kwargs):
|
||||
"""
|
||||
Usage: $add(val1, val2)
|
||||
Returns the result of val1 + val2. Values must be
|
||||
valid simple Python structures possible to add,
|
||||
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])
|
||||
# this is picked up by FuncParser
|
||||
FUNCPARSER_CALLABLES = {
|
||||
"protkey": protfunc_callable_protkey,
|
||||
**funcparser.FUNCPARSER_CALLABLES,
|
||||
**funcparser.SEARCHING_CALLABLES,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ from evennia.utils.utils import (
|
|||
from evennia.locks.lockhandler import validate_lockstring, check_lockstring
|
||||
from evennia.utils import logger
|
||||
from evennia.utils.funcparser import FuncParser
|
||||
from evennia.utils import inlinefuncs, dbserialize
|
||||
from evennia.utils import dbserialize
|
||||
from evennia.utils.evtable import EvTable
|
||||
|
||||
|
||||
|
|
@ -721,7 +721,7 @@ for mod in settings.PROT_FUNC_MODULES:
|
|||
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.
|
||||
|
||||
|
|
@ -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.
|
||||
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.
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
result = FuncParser(available_functions).parse(value, raise_errors=True, **kwargs)
|
||||
|
||||
# result = inlinefuncs.parse_inlinefunc(
|
||||
# value, available_funcs=available_functions, stacktrace=stacktrace, testing=testing, **kwargs
|
||||
# )
|
||||
result = FuncParser(available_functions).parse(
|
||||
value, raise_errors=True, caller=caller, **kwargs)
|
||||
|
||||
err = None
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -607,7 +607,8 @@ def format_diff(diff, minimal=True):
|
|||
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.
|
||||
|
||||
|
|
@ -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
|
||||
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.
|
||||
caller (Object or Account, optional): This may be used by protfuncs to do permission checks.
|
||||
Returns:
|
||||
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
|
||||
|
||||
if key == "key":
|
||||
obj.db_key = init_spawn_value(val, str)
|
||||
obj.db_key = init_spawn_value(val, str, caller=caller)
|
||||
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":
|
||||
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":
|
||||
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":
|
||||
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":
|
||||
if directive == "REPLACE":
|
||||
obj.locks.clear()
|
||||
obj.locks.add(init_spawn_value(val, str))
|
||||
obj.locks.add(init_spawn_value(val, str, caller=caller))
|
||||
elif key == "permissions":
|
||||
if directive == "REPLACE":
|
||||
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":
|
||||
if directive == "REPLACE":
|
||||
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":
|
||||
if directive == "REPLACE":
|
||||
obj.tags.clear()
|
||||
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
|
||||
)
|
||||
)
|
||||
|
|
@ -711,8 +713,8 @@ def batch_update_objects_with_prototype(prototype, diff=None, objects=None, exac
|
|||
obj.attributes.batch_add(
|
||||
*(
|
||||
(
|
||||
init_spawn_value(akey, str),
|
||||
init_spawn_value(aval, value_to_obj),
|
||||
init_spawn_value(akey, str, caller=caller),
|
||||
init_spawn_value(aval, value_to_obj, caller=caller),
|
||||
acategory,
|
||||
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!
|
||||
pass
|
||||
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":
|
||||
do_save = True
|
||||
if key == "key":
|
||||
|
|
@ -836,7 +838,7 @@ def batch_create_object(*objparams):
|
|||
# Spawner mechanism
|
||||
|
||||
|
||||
def spawn(*prototypes, **kwargs):
|
||||
def spawn(*prototypes, caller=None, **kwargs):
|
||||
"""
|
||||
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
|
||||
dictionary. These will be batched-spawned as one object each.
|
||||
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
|
||||
module, or a list of such paths. These will be used to build
|
||||
the global protparents dictionary accessible by the input
|
||||
|
|
@ -910,39 +913,39 @@ def spawn(*prototypes, **kwargs):
|
|||
"key",
|
||||
"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)
|
||||
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)
|
||||
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:
|
||||
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:
|
||||
# settings.DEFAULT_HOME not existing is common for unittests
|
||||
pass
|
||||
|
||||
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)
|
||||
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
|
||||
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", "")
|
||||
lock_string = init_spawn_value(val, str)
|
||||
lock_string = init_spawn_value(val, str, caller=caller)
|
||||
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", [])
|
||||
tags = []
|
||||
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)
|
||||
if prototype_key:
|
||||
|
|
@ -950,11 +953,11 @@ def spawn(*prototypes, **kwargs):
|
|||
tags.append((prototype_key, PROTOTYPE_TAG_CATEGORY))
|
||||
|
||||
val = prot.pop("exec", "")
|
||||
execs = init_spawn_value(val, make_iter)
|
||||
execs = init_spawn_value(val, make_iter, caller=caller)
|
||||
|
||||
# extract ndb assignments
|
||||
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()
|
||||
if key.startswith("ndb_")
|
||||
)
|
||||
|
|
@ -963,7 +966,7 @@ def spawn(*prototypes, **kwargs):
|
|||
val = make_iter(prot.pop("attrs", []))
|
||||
attributes = []
|
||||
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))
|
||||
|
||||
simple_attributes = []
|
||||
|
|
@ -975,7 +978,7 @@ def spawn(*prototypes, **kwargs):
|
|||
continue
|
||||
else:
|
||||
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
|
||||
|
|
|
|||
|
|
@ -611,7 +611,7 @@ INLINEFUNC_STACK_MAXSIZE = 20
|
|||
# Only functions defined globally (and not starting with '_') in
|
||||
# these modules will be considered valid inlinefuncs. The list
|
||||
# is loaded from left-to-right, same-named functions will overload
|
||||
INLINEFUNC_MODULES = ["evennia.utils.inlinefuncs", "server.conf.inlinefuncs"]
|
||||
INLINEFUNC_MODULES = ["evennia.utils.funcparser", "server.conf.inlinefuncs"]
|
||||
# Module holding handlers for ProtFuncs. These allow for embedding
|
||||
# functional code in prototypes and has the same syntax as inlinefuncs.
|
||||
PROTOTYPEFUNC_MODULES = ["evennia.prototypes.protfuncs", "server.conf.prototypefuncs"]
|
||||
|
|
|
|||
|
|
@ -820,15 +820,16 @@ def funcparser_callable_pad(*args, **kwargs):
|
|||
if not args:
|
||||
return ''
|
||||
text, *rest = args
|
||||
nargs = len(args)
|
||||
nrest = len(rest)
|
||||
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:
|
||||
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 ' ')
|
||||
if fillchar not in ('c', 'l', 'r'):
|
||||
fillchar = 'c'
|
||||
|
||||
align = kwargs.get("align", rest[1] if nrest > 1 else 'c')
|
||||
fillchar = kwargs.get("fillchar", rest[2] if nrest > 2 else ' ')
|
||||
if align not in ('c', 'l', 'r'):
|
||||
align = 'c'
|
||||
return pad(str(text), width=width, align=align, fillchar=fillchar)
|
||||
|
||||
|
||||
|
|
@ -867,12 +868,12 @@ def funcparser_callable_crop(*args, **kwargs):
|
|||
if not args:
|
||||
return ''
|
||||
text, *rest = args
|
||||
nargs = len(args)
|
||||
nrest = len(rest)
|
||||
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:
|
||||
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))
|
||||
|
||||
|
||||
|
|
@ -896,14 +897,15 @@ def funcparser_callable_justify(*args, **kwargs):
|
|||
"""
|
||||
if not args:
|
||||
return ''
|
||||
text = args[0]
|
||||
text, *rest = args
|
||||
lrest = len(rest)
|
||||
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:
|
||||
width = _CLIENT_DEFAULT_WIDTH
|
||||
align = str(kwargs.get("align", 'f'))
|
||||
align = str(kwargs.get("align", rest[1] if lrest > 1 else 'f'))
|
||||
try:
|
||||
indent = int(kwargs.get("indent", 0))
|
||||
indent = int(kwargs.get("indent", rest[2] if lrest > 2 else 0))
|
||||
except TypeError:
|
||||
indent = 0
|
||||
return justify(str(text), width=width, align=align, indent=indent)
|
||||
|
|
@ -912,17 +914,17 @@ def funcparser_callable_justify(*args, **kwargs):
|
|||
# legacy for backwards compatibility
|
||||
def funcparser_callable_left_justify(*args, **kwargs):
|
||||
"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):
|
||||
"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):
|
||||
"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):
|
||||
|
|
@ -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.
|
||||
|
||||
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:
|
||||
- "$search(#233)"
|
||||
|
|
@ -996,10 +999,12 @@ def funcparser_callable_search(*args, caller=None, access="control", **kwargs):
|
|||
- "$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
|
||||
if not caller:
|
||||
raise ParsingError("$search requires a `caller` passed to the parser.")
|
||||
|
||||
query = str(args[0])
|
||||
|
||||
|
|
@ -1040,63 +1045,72 @@ def funcparser_callable_search_list(*args, caller=None, access="control", **kwar
|
|||
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
|
||||
of the caller for others.
|
||||
|
||||
Kwargs:
|
||||
you_obj (Object): The object who represents 'you' in the string.
|
||||
you_target (Object): The recipient of the string.
|
||||
you (Object): The 'you' in the string. This is used unless another
|
||||
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.
|
||||
|
||||
Returns:
|
||||
str: The parsed string.
|
||||
|
||||
Raises:
|
||||
ParsingError: If `you_obj` and `you_target` were not supplied.
|
||||
ParsingError: If `you` and `receiver` were not supplied.
|
||||
|
||||
Notes:
|
||||
The kwargs must be supplied to the parse method. If not given,
|
||||
the parsing will be aborted. Note that it will not capitalize
|
||||
The kwargs should be passed the to parser directly.
|
||||
|
||||
Examples:
|
||||
This can be used by the say or emote hooks to pass actor stance
|
||||
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."
|
||||
Others will see "With a grin, CharName jumps."
|
||||
The You-object will see "With a grin, you jump at Tommy."
|
||||
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):
|
||||
raise ParsingError("No you_obj/target supplied to $you callable")
|
||||
if args and mapping:
|
||||
# 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)
|
||||
if you_obj == you_target:
|
||||
if you == receiver:
|
||||
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.
|
||||
|
||||
"""
|
||||
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
|
||||
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.
|
||||
$conj(verb)
|
||||
|
||||
Conjugate a verb according to if it should be 2nd or third person.
|
||||
Kwargs:
|
||||
you_obj (Object): The object who represents 'you' in 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.
|
||||
|
||||
Raises:
|
||||
ParsingError: If `you_obj` and `you_target` were not supplied.
|
||||
ParsingError: If `you` and `recipient` were not both supplied.
|
||||
|
||||
Notes:
|
||||
The kwargs must be supplied to the parse method. If not given,
|
||||
the parsing will be aborted. Note that it will not capitalize
|
||||
Note that it will not capitalized.
|
||||
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:
|
||||
This is often used in combination with the $you/You( callables.
|
||||
|
||||
- `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."
|
||||
|
||||
"""
|
||||
if not args:
|
||||
return ''
|
||||
if not (you_obj and you_target):
|
||||
raise ParsingError("No you_obj/target supplied to $conj callable")
|
||||
if not (you and receiver):
|
||||
raise ParsingError("No youj/receiver supplied to $conj callable")
|
||||
|
||||
you_str, them_str = verb_actor_stance_components(args[0])
|
||||
return you_str if you_obj == you_target else them_str
|
||||
second_person_str, third_person_str = verb_actor_stance_components(args[0])
|
||||
return second_person_str if you == receiver else third_person_str
|
||||
|
||||
|
||||
# these are made available as callables by adding 'evennia.utils.funcparser' as
|
||||
# a callable-path when initializing the FuncParser.
|
||||
|
||||
FUNCPARSER_CALLABLES = {
|
||||
# 'standard' callables
|
||||
|
||||
# eval and arithmetic
|
||||
"eval": funcparser_callable_eval,
|
||||
"add": funcparser_callable_add,
|
||||
|
|
@ -1160,14 +1179,18 @@ FUNCPARSER_CALLABLES = {
|
|||
"justify_center": funcparser_callable_center_justify,
|
||||
"space": funcparser_callable_space,
|
||||
"clr": funcparser_callable_clr,
|
||||
}
|
||||
|
||||
# seaching
|
||||
SEARCHING_CALLABLES = {
|
||||
# requires `caller` and optionally `access` to be passed into parser
|
||||
"search": funcparser_callable_search,
|
||||
"obj": funcparser_callable_search, # aliases for backwards compat
|
||||
"objlist": funcparser_callable_search_list,
|
||||
"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,
|
||||
"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 django.test import TestCase, override_settings
|
||||
|
||||
from evennia.utils import funcparser
|
||||
from evennia.utils import funcparser, test_resources
|
||||
|
||||
|
||||
def _test_callable(*args, **kwargs):
|
||||
|
|
@ -300,33 +300,27 @@ class TestDefaultCallables(TestCase):
|
|||
|
||||
@parameterized.expand([
|
||||
("$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):
|
||||
"""
|
||||
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)
|
||||
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)
|
||||
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([
|
||||
("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[...]"),
|
||||
("Some $space(10) here", "Some here"),
|
||||
("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 $div(6, 2) things", "Some 3.0 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.
|
||||
|
||||
|
|
@ -349,3 +348,60 @@ class TestOldDefaultCallables(TestCase):
|
|||
ret = self.parser.parse(string, raise_errors=True)
|
||||
ret = int(ret)
|
||||
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