Merge branch 'develop' into 2736-fix-cmdset-remove
This commit is contained in:
commit
90a555b259
99 changed files with 3381 additions and 1246 deletions
|
|
@ -678,7 +678,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
|||
typeclass (str, optional): Typeclass to use for this character. If
|
||||
not given, use settings.BASE_CHARACTER_TYPECLASS.
|
||||
permissions (list, optional): If not given, use the account's permissions.
|
||||
ip (str, optiona): The client IP creating this character. Will fall back to the
|
||||
ip (str, optional): The client IP creating this character. Will fall back to the
|
||||
one stored for the account if not given.
|
||||
kwargs (any): Other kwargs will be used in the create_call.
|
||||
Returns:
|
||||
|
|
@ -955,7 +955,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
|||
kwargs (any): Other keyword arguments will be added to the
|
||||
found command object instance as variables before it
|
||||
executes. This is unused by default Evennia but may be
|
||||
used to set flags and change operating paramaters for
|
||||
used to set flags and change operating parameters for
|
||||
commands at run-time.
|
||||
|
||||
"""
|
||||
|
|
@ -1433,7 +1433,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
|||
self._send_to_connect_channel(_("|G{key} connected|n").format(key=self.key))
|
||||
if _MULTISESSION_MODE == 0:
|
||||
# in this mode we should have only one character available. We
|
||||
# try to auto-connect to our last conneted object, if any
|
||||
# try to auto-connect to our last connected object, if any
|
||||
try:
|
||||
self.puppet_object(session, self.db._last_puppet)
|
||||
except RuntimeError:
|
||||
|
|
@ -1460,7 +1460,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
|||
"""
|
||||
Called by the login process if a user account is targeted correctly
|
||||
but provided with an invalid password. By default it does nothing,
|
||||
but exists to be overriden.
|
||||
but exists to be overridden.
|
||||
|
||||
Args:
|
||||
session (session): Session logging in.
|
||||
|
|
@ -1703,7 +1703,7 @@ class DefaultGuest(DefaultAccount):
|
|||
Gets or creates a Guest account object.
|
||||
|
||||
Keyword Args:
|
||||
ip (str, optional): IP address of requestor; used for ban checking,
|
||||
ip (str, optional): IP address of requester; used for ban checking,
|
||||
throttling and logging
|
||||
|
||||
Returns:
|
||||
|
|
|
|||
|
|
@ -450,9 +450,7 @@ class CmdSetHandler(object):
|
|||
|
||||
"""
|
||||
if "permanent" in kwargs:
|
||||
logger.log_dep(
|
||||
"obj.cmdset.add() kwarg 'permanent' has changed name to 'persistent'."
|
||||
)
|
||||
logger.log_dep("obj.cmdset.add() kwarg 'permanent' has changed name to 'persistent'.")
|
||||
persistent = kwargs["permanent"] if persistent is False else persistent
|
||||
|
||||
if not (isinstance(cmdset, str) or utils.inherits_from(cmdset, CmdSet)):
|
||||
|
|
|
|||
|
|
@ -603,7 +603,7 @@ class CmdCreate(ObjManipCommand):
|
|||
if "drop" in self.switches:
|
||||
if caller.location:
|
||||
obj.home = caller.location
|
||||
obj.move_to(caller.location, quiet=True)
|
||||
obj.move_to(caller.location, quiet=True, move_type="drop")
|
||||
if string:
|
||||
caller.msg(string)
|
||||
|
||||
|
|
@ -993,7 +993,7 @@ class CmdDig(ObjManipCommand):
|
|||
)
|
||||
caller.msg("%s%s%s" % (room_string, exit_to_string, exit_back_string))
|
||||
if new_room and "teleport" in self.switches:
|
||||
caller.move_to(new_room)
|
||||
caller.move_to(new_room, move_type="teleport")
|
||||
|
||||
|
||||
class CmdTunnel(COMMAND_DEFAULT_CLASS):
|
||||
|
|
@ -1071,7 +1071,7 @@ class CmdTunnel(COMMAND_DEFAULT_CLASS):
|
|||
exitname, backshort = self.directions[exitshort]
|
||||
backname = self.directions[backshort][0]
|
||||
|
||||
# if we recieved a typeclass for the exit, add it to the alias(short name)
|
||||
# if we received a typeclass for the exit, add it to the alias(short name)
|
||||
if ":" in self.lhs:
|
||||
# limit to only the first : character
|
||||
exit_typeclass = ":" + self.lhs.split(":", 1)[-1]
|
||||
|
|
@ -1665,7 +1665,7 @@ class CmdSetAttribute(ObjManipCommand):
|
|||
def split_nested_attr(self, attr):
|
||||
"""
|
||||
Yields tuples of (possible attr name, nested keys on that attr).
|
||||
For performance, this is biased to the deepest match, but allows compatability
|
||||
For performance, this is biased to the deepest match, but allows compatibility
|
||||
with older attrs that might have been named with `[]`'s.
|
||||
|
||||
> list(split_nested_attr("nested['asdf'][0]"))
|
||||
|
|
@ -1927,14 +1927,11 @@ class CmdSetAttribute(ObjManipCommand):
|
|||
if self.rhs is None:
|
||||
# no = means we inspect the attribute(s)
|
||||
if not attrs:
|
||||
attrs = [attr.key for attr in obj.attributes.get(category=None)]
|
||||
attrs = [attr.key for attr in obj.attributes.get(category=None, return_obj=True)]
|
||||
for attr in attrs:
|
||||
if not self.check_attr(obj, attr, category):
|
||||
continue
|
||||
result.append(self.view_attr(obj, attr, category))
|
||||
# we view it without parsing markup.
|
||||
self.caller.msg("".join(result).strip(), options={"raw": True})
|
||||
return
|
||||
else:
|
||||
# deleting the attribute(s)
|
||||
if not (obj.access(self.caller, "control") or obj.access(self.caller, "edit")):
|
||||
|
|
@ -1979,8 +1976,12 @@ class CmdSetAttribute(ObjManipCommand):
|
|||
else:
|
||||
value = _convert_from_string(self, value)
|
||||
result.append(self.set_attr(obj, attr, value, category))
|
||||
# send feedback
|
||||
caller.msg("".join(result).strip("\n"))
|
||||
# check if anything was done
|
||||
if not result:
|
||||
caller.msg("No valid attributes were found. Usage: set obj/attr[:category] = value. Use empty value to clear.")
|
||||
else:
|
||||
# send feedback
|
||||
caller.msg("".join(result).strip("\n"))
|
||||
|
||||
|
||||
class CmdTypeclass(COMMAND_DEFAULT_CLASS):
|
||||
|
|
@ -2219,11 +2220,13 @@ class CmdTypeclass(COMMAND_DEFAULT_CLASS):
|
|||
old_typeclass_path = obj.typeclass_path
|
||||
|
||||
if reset:
|
||||
answer = yield("|yNote that this will reset the object back to its typeclass' default state, "
|
||||
"removing any custom locks/perms/attributes etc that may have been added "
|
||||
"by an explicit create_object call. Use `update` or type/force instead in order "
|
||||
"to keep such data. "
|
||||
"Continue [Y]/N?|n")
|
||||
answer = yield (
|
||||
"|yNote that this will reset the object back to its typeclass' default state, "
|
||||
"removing any custom locks/perms/attributes etc that may have been added "
|
||||
"by an explicit create_object call. Use `update` or type/force instead in order "
|
||||
"to keep such data. "
|
||||
"Continue [Y]/N?|n"
|
||||
)
|
||||
if answer.upper() in ("N", "NO"):
|
||||
caller.msg("Aborted.")
|
||||
return
|
||||
|
|
@ -2732,7 +2735,7 @@ class CmdExamine(ObjManipCommand):
|
|||
return
|
||||
|
||||
if ndb_attr and ndb_attr[0]:
|
||||
return "\n " + " \n".join(
|
||||
return "\n " + "\n ".join(
|
||||
sorted(self.format_single_attribute(attr) for attr in ndb_attr)
|
||||
)
|
||||
|
||||
|
|
@ -2830,7 +2833,7 @@ class CmdExamine(ObjManipCommand):
|
|||
objdata["Stored Cmdset(s)"] = self.format_stored_cmdsets(obj)
|
||||
objdata["Merged Cmdset(s)"] = self.format_merged_cmdsets(obj, current_cmdset)
|
||||
objdata[
|
||||
f"Commands vailable to {obj.key} (result of Merged Cmdset(s))"
|
||||
f"Commands available to {obj.key} (result of Merged Cmdset(s))"
|
||||
] = self.format_current_cmds(obj, current_cmdset)
|
||||
if self.object_type == "script":
|
||||
objdata["Description"] = self.format_script_desc(obj)
|
||||
|
|
@ -3473,7 +3476,7 @@ class CmdScripts(COMMAND_DEFAULT_CLASS):
|
|||
caller.msg("\n".join(msgs))
|
||||
if "delete" not in self.switches:
|
||||
if script and script.pk:
|
||||
ScriptEvMore(caller, [script], session=self.session)
|
||||
ScriptEvMore(caller, [script], session=self.session)
|
||||
else:
|
||||
caller.msg("Script was deleted automatically.")
|
||||
else:
|
||||
|
|
@ -3706,6 +3709,7 @@ class CmdTeleport(COMMAND_DEFAULT_CLASS):
|
|||
quiet="quiet" in self.switches,
|
||||
emit_to_obj=caller,
|
||||
use_destination="intoexit" not in self.switches,
|
||||
move_type="teleport"
|
||||
):
|
||||
|
||||
if obj_to_teleport == caller:
|
||||
|
|
@ -4029,7 +4033,7 @@ class CmdSpawn(COMMAND_DEFAULT_CLASS):
|
|||
)
|
||||
return
|
||||
try:
|
||||
# we homogenize the protoype first, to be more lenient with free-form
|
||||
# we homogenize the prototype first, to be more lenient with free-form
|
||||
protlib.validate_prototype(protlib.homogenize_prototype(prototype))
|
||||
except RuntimeError as err:
|
||||
self.caller.msg(str(err))
|
||||
|
|
|
|||
|
|
@ -1818,7 +1818,7 @@ class CmdRSS2Chan(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
class CmdGrapevine2Chan(COMMAND_DEFAULT_CLASS):
|
||||
"""
|
||||
Link an Evennia channel to an exteral Grapevine channel
|
||||
Link an Evennia channel to an external Grapevine channel
|
||||
|
||||
Usage:
|
||||
grapevine2chan[/switches] <evennia_channel> = <grapevine_channel>
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ class CmdHome(COMMAND_DEFAULT_CLASS):
|
|||
caller.msg("You are already home!")
|
||||
else:
|
||||
caller.msg("There's no place like home ...")
|
||||
caller.move_to(home)
|
||||
caller.move_to(home, move_type="teleport")
|
||||
|
||||
|
||||
class CmdLook(COMMAND_DEFAULT_CLASS):
|
||||
|
|
@ -434,7 +434,7 @@ class CmdGet(COMMAND_DEFAULT_CLASS):
|
|||
if not obj.at_pre_get(caller):
|
||||
return
|
||||
|
||||
success = obj.move_to(caller, quiet=True)
|
||||
success = obj.move_to(caller, quiet=True, move_type="get")
|
||||
if not success:
|
||||
caller.msg("This can't be picked up.")
|
||||
else:
|
||||
|
|
@ -484,7 +484,7 @@ class CmdDrop(COMMAND_DEFAULT_CLASS):
|
|||
if not obj.at_pre_drop(caller):
|
||||
return
|
||||
|
||||
success = obj.move_to(caller.location, quiet=True)
|
||||
success = obj.move_to(caller.location, quiet=True, move_type="drop")
|
||||
if not success:
|
||||
caller.msg("This couldn't be dropped.")
|
||||
else:
|
||||
|
|
@ -538,7 +538,7 @@ class CmdGive(COMMAND_DEFAULT_CLASS):
|
|||
return
|
||||
|
||||
# give object
|
||||
success = to_give.move_to(target, quiet=True)
|
||||
success = to_give.move_to(target, quiet=True, move_type="get")
|
||||
if not success:
|
||||
caller.msg("This could not be given.")
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
|
|||
help <topic>/<subtopic>/<subsubtopic> ...
|
||||
|
||||
Use the 'help' command alone to see an index of all help topics, organized
|
||||
by category.eSome big topics may offer additional sub-topics.
|
||||
by category. Some big topics may offer additional sub-topics.
|
||||
|
||||
"""
|
||||
|
||||
|
|
@ -138,7 +138,7 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
|
|||
click_topics=True,
|
||||
):
|
||||
"""This visually formats the help entry.
|
||||
This method can be overriden to customize the way a help
|
||||
This method can be overridden to customize the way a help
|
||||
entry is displayed.
|
||||
|
||||
Args:
|
||||
|
|
|
|||
|
|
@ -107,8 +107,7 @@ class TestGeneral(BaseEvenniaCommandTest):
|
|||
|
||||
def test_nick_list(self):
|
||||
self.call(general.CmdNick(), "/list", "No nicks defined.")
|
||||
self.call(general.CmdNick(), "test1 = Hello",
|
||||
"Inputline-nick 'test1' mapped to 'Hello'.")
|
||||
self.call(general.CmdNick(), "test1 = Hello", "Inputline-nick 'test1' mapped to 'Hello'.")
|
||||
self.call(general.CmdNick(), "/list", "Defined Nicks:")
|
||||
|
||||
def test_get_and_drop(self):
|
||||
|
|
@ -1295,7 +1294,8 @@ class TestBuilding(BaseEvenniaCommandTest):
|
|||
"Obj2 = evennia.objects.objects.DefaultExit",
|
||||
"Obj2 changed typeclass from evennia.objects.objects.DefaultObject "
|
||||
"to evennia.objects.objects.DefaultExit.",
|
||||
cmdstring="swap", inputs=["yes"],
|
||||
cmdstring="swap",
|
||||
inputs=["yes"],
|
||||
)
|
||||
self.call(building.CmdTypeclass(), "/list Obj", "Core typeclasses")
|
||||
self.call(
|
||||
|
|
@ -1332,7 +1332,7 @@ class TestBuilding(BaseEvenniaCommandTest):
|
|||
"/reset/force Obj=evennia.objects.objects.DefaultObject",
|
||||
"Obj updated its existing typeclass (evennia.objects.objects.DefaultObject).\n"
|
||||
"All object creation hooks were run. All old attributes where deleted before the swap.",
|
||||
inputs=["yes"]
|
||||
inputs=["yes"],
|
||||
)
|
||||
|
||||
from evennia.prototypes.prototypes import homogenize_prototype
|
||||
|
|
@ -1359,7 +1359,7 @@ class TestBuilding(BaseEvenniaCommandTest):
|
|||
"typeclasses.objects.Object.\nOnly the at_object_creation hook was run "
|
||||
"(update mode). Attributes set before swap were not removed\n"
|
||||
"(use `swap` or `type/reset` to clear all). Prototype 'replaced_obj' was "
|
||||
"successfully applied over the object type."
|
||||
"successfully applied over the object type.",
|
||||
)
|
||||
assert self.obj1.db.desc == "protdesc"
|
||||
|
||||
|
|
|
|||
|
|
@ -17,8 +17,10 @@ def get_component_class(component_name):
|
|||
subclasses = Component.__subclasses__()
|
||||
component_class = next((sc for sc in subclasses if sc.name == component_name), None)
|
||||
if component_class is None:
|
||||
message = f"Component named {component_name} has not been found. " \
|
||||
f"Make sure it has been imported before being used."
|
||||
message = (
|
||||
f"Component named {component_name} has not been found. "
|
||||
f"Make sure it has been imported before being used."
|
||||
)
|
||||
raise Exception(message)
|
||||
|
||||
return component_class
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ class Component:
|
|||
|
||||
Each Component must supply the name, it is used as a slot name but also part of the attribute key.
|
||||
"""
|
||||
|
||||
name = ""
|
||||
|
||||
def __init__(self, host=None):
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ class DBField(AttributeProperty):
|
|||
db_fields = getattr(owner, "_db_fields", None)
|
||||
if db_fields is None:
|
||||
db_fields = {}
|
||||
setattr(owner, '_db_fields', db_fields)
|
||||
setattr(owner, "_db_fields", db_fields)
|
||||
db_fields[name] = self
|
||||
|
||||
|
||||
|
|
@ -50,7 +50,7 @@ class NDBField(NAttributeProperty):
|
|||
ndb_fields = getattr(owner, "_ndb_fields", None)
|
||||
if ndb_fields is None:
|
||||
ndb_fields = {}
|
||||
setattr(owner, '_ndb_fields', ndb_fields)
|
||||
setattr(owner, "_ndb_fields", ndb_fields)
|
||||
ndb_fields[name] = self
|
||||
|
||||
|
||||
|
|
@ -64,6 +64,7 @@ class TagField:
|
|||
Default value of a tag is added when the component is registered.
|
||||
Tags are removed if the component itself is removed.
|
||||
"""
|
||||
|
||||
def __init__(self, default=None, enforce_single=False):
|
||||
self._category_key = None
|
||||
self._default = default
|
||||
|
|
@ -78,7 +79,7 @@ class TagField:
|
|||
tag_fields = getattr(owner, "_tag_fields", None)
|
||||
if tag_fields is None:
|
||||
tag_fields = {}
|
||||
setattr(owner, '_tag_fields', tag_fields)
|
||||
setattr(owner, "_tag_fields", tag_fields)
|
||||
tag_fields[name] = self
|
||||
|
||||
def __get__(self, instance, owner):
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ class ComponentProperty:
|
|||
|
||||
Defaults can be overridden for this typeclass by passing kwargs
|
||||
"""
|
||||
|
||||
def __init__(self, component_name, **kwargs):
|
||||
"""
|
||||
Initializes the descriptor
|
||||
|
|
@ -49,6 +50,7 @@ class ComponentHandler:
|
|||
It lets you add or remove components and will load components as needed.
|
||||
It stores the list of registered components on the host .db with component_names as key.
|
||||
"""
|
||||
|
||||
def __init__(self, host):
|
||||
self.host = host
|
||||
self._loaded_components = {}
|
||||
|
|
@ -124,7 +126,9 @@ class ComponentHandler:
|
|||
self.host.signals.remove_object_listeners_and_responders(component)
|
||||
del self._loaded_components[component_name]
|
||||
else:
|
||||
message = f"Cannot remove {component_name} from {self.host.name} as it is not registered."
|
||||
message = (
|
||||
f"Cannot remove {component_name} from {self.host.name} as it is not registered."
|
||||
)
|
||||
raise ComponentIsNotRegistered(message)
|
||||
|
||||
def remove_by_name(self, name):
|
||||
|
|
@ -199,7 +203,9 @@ class ComponentHandler:
|
|||
self._set_component(component_instance)
|
||||
self.host.signals.add_object_listeners_and_responders(component_instance)
|
||||
else:
|
||||
message = f"Could not initialize runtime component {component_name} of {self.host.name}"
|
||||
message = (
|
||||
f"Could not initialize runtime component {component_name} of {self.host.name}"
|
||||
)
|
||||
raise ComponentDoesNotExist(message)
|
||||
|
||||
def _set_component(self, component):
|
||||
|
|
|
|||
|
|
@ -15,9 +15,11 @@ def as_listener(func=None, signal_name=None):
|
|||
signal_name (str): The name of the signal to listen to, defaults to function name.
|
||||
"""
|
||||
if not func and signal_name:
|
||||
|
||||
def wrapper(func):
|
||||
func._listener_signal_name = signal_name
|
||||
return func
|
||||
|
||||
return wrapper
|
||||
|
||||
signal_name = func.__name__
|
||||
|
|
@ -35,9 +37,11 @@ def as_responder(func=None, signal_name=None):
|
|||
signal_name (str): The name of the signal to respond to, defaults to function name.
|
||||
"""
|
||||
if not func and signal_name:
|
||||
|
||||
def wrapper(func):
|
||||
func._responder_signal_name = signal_name
|
||||
return func
|
||||
|
||||
return wrapper
|
||||
|
||||
signal_name = func.__name__
|
||||
|
|
@ -177,12 +181,12 @@ class SignalsHandler(object):
|
|||
"""
|
||||
type_host = type(obj)
|
||||
for att_name, att_obj in type_host.__dict__.items():
|
||||
listener_signal_name = getattr(att_obj, '_listener_signal_name', None)
|
||||
listener_signal_name = getattr(att_obj, "_listener_signal_name", None)
|
||||
if listener_signal_name:
|
||||
callback = getattr(obj, att_name)
|
||||
self.add_listener(signal_name=listener_signal_name, callback=callback)
|
||||
|
||||
responder_signal_name = getattr(att_obj, '_responder_signal_name', None)
|
||||
responder_signal_name = getattr(att_obj, "_responder_signal_name", None)
|
||||
if responder_signal_name:
|
||||
callback = getattr(obj, att_name)
|
||||
self.add_responder(signal_name=responder_signal_name, callback=callback)
|
||||
|
|
@ -196,12 +200,12 @@ class SignalsHandler(object):
|
|||
"""
|
||||
type_host = type(obj)
|
||||
for att_name, att_obj in type_host.__dict__.items():
|
||||
listener_signal_name = getattr(att_obj, '_listener_signal_name', None)
|
||||
listener_signal_name = getattr(att_obj, "_listener_signal_name", None)
|
||||
if listener_signal_name:
|
||||
callback = getattr(obj, att_name)
|
||||
self.remove_listener(signal_name=listener_signal_name, callback=callback)
|
||||
|
||||
responder_signal_name = getattr(att_obj, '_responder_signal_name', None)
|
||||
responder_signal_name = getattr(att_obj, "_responder_signal_name", None)
|
||||
if responder_signal_name:
|
||||
callback = getattr(obj, att_name)
|
||||
self.remove_responder(signal_name=responder_signal_name, callback=callback)
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ class TestComponents(EvenniaTest):
|
|||
def test_character_can_register_runtime_component(self):
|
||||
rct = RuntimeComponentTestC.create(self.char1)
|
||||
self.char1.components.add(rct)
|
||||
test_c = self.char1.components.get('test_c')
|
||||
test_c = self.char1.components.get("test_c")
|
||||
|
||||
assert test_c
|
||||
assert test_c.my_int == 6
|
||||
|
|
@ -110,7 +110,7 @@ class TestComponents(EvenniaTest):
|
|||
assert handler.get("test_c") is rct
|
||||
|
||||
def test_can_access_component_regular_get(self):
|
||||
assert self.char1.cmp.test_a is self.char1.components.get('test_a')
|
||||
assert self.char1.cmp.test_a is self.char1.components.get("test_a")
|
||||
|
||||
def test_returns_none_with_regular_get_when_no_attribute(self):
|
||||
assert self.char1.cmp.does_not_exist is None
|
||||
|
|
@ -127,7 +127,7 @@ class TestComponents(EvenniaTest):
|
|||
def test_host_has_added_component_tags(self):
|
||||
rct = RuntimeComponentTestC.create(self.char1)
|
||||
self.char1.components.add(rct)
|
||||
test_c = self.char1.components.get('test_c')
|
||||
test_c = self.char1.components.get("test_c")
|
||||
|
||||
assert self.char1.tags.has(key="test_c", category="components")
|
||||
assert self.char1.tags.has(key="added_value", category="test_c::added_tag")
|
||||
|
|
@ -162,7 +162,7 @@ class TestComponents(EvenniaTest):
|
|||
assert not self.char1.tags.has(key="added_value", category="test_c::added_tag")
|
||||
|
||||
def test_component_tags_only_hold_one_value_when_enforce_single(self):
|
||||
test_b = self.char1.components.get('test_b')
|
||||
test_b = self.char1.components.get("test_b")
|
||||
test_b.single_tag = "first_value"
|
||||
test_b.single_tag = "second value"
|
||||
|
||||
|
|
@ -171,7 +171,7 @@ class TestComponents(EvenniaTest):
|
|||
assert not self.char1.tags.has(key="first_value", category="test_b::single_tag")
|
||||
|
||||
def test_component_tags_default_value_is_overridden_when_enforce_single(self):
|
||||
test_b = self.char1.components.get('test_b')
|
||||
test_b = self.char1.components.get("test_b")
|
||||
test_b.default_single_tag = "second value"
|
||||
|
||||
assert self.char1.tags.has(key="second value", category="test_b::default_single_tag")
|
||||
|
|
@ -179,12 +179,14 @@ class TestComponents(EvenniaTest):
|
|||
assert not self.char1.tags.has(key="first_value", category="test_b::default_single_tag")
|
||||
|
||||
def test_component_tags_support_multiple_values_by_default(self):
|
||||
test_b = self.char1.components.get('test_b')
|
||||
test_b = self.char1.components.get("test_b")
|
||||
test_b.multiple_tags = "first value"
|
||||
test_b.multiple_tags = "second value"
|
||||
test_b.multiple_tags = "third value"
|
||||
|
||||
assert all(val in test_b.multiple_tags for val in ("first value", "second value", "third value"))
|
||||
assert all(
|
||||
val in test_b.multiple_tags for val in ("first value", "second value", "third value")
|
||||
)
|
||||
assert self.char1.tags.has(key="first value", category="test_b::multiple_tags")
|
||||
assert self.char1.tags.has(key="second value", category="test_b::multiple_tags")
|
||||
assert self.char1.tags.has(key="third value", category="test_b::multiple_tags")
|
||||
|
|
@ -193,11 +195,11 @@ class TestComponents(EvenniaTest):
|
|||
class CharWithSignal(ComponentHolderMixin, DefaultCharacter):
|
||||
@signals.as_listener
|
||||
def my_signal(self):
|
||||
setattr(self, 'my_signal_is_called', True)
|
||||
setattr(self, "my_signal_is_called", True)
|
||||
|
||||
@signals.as_listener
|
||||
def my_other_signal(self):
|
||||
setattr(self, 'my_other_signal_is_called', True)
|
||||
setattr(self, "my_other_signal_is_called", True)
|
||||
|
||||
@signals.as_responder
|
||||
def my_response(self):
|
||||
|
|
@ -213,11 +215,11 @@ class ComponentWithSignal(Component):
|
|||
|
||||
@signals.as_listener
|
||||
def my_signal(self):
|
||||
setattr(self, 'my_signal_is_called', True)
|
||||
setattr(self, "my_signal_is_called", True)
|
||||
|
||||
@signals.as_listener
|
||||
def my_other_signal(self):
|
||||
setattr(self, 'my_other_signal_is_called', True)
|
||||
setattr(self, "my_other_signal_is_called", True)
|
||||
|
||||
@signals.as_responder
|
||||
def my_response(self):
|
||||
|
|
@ -236,14 +238,15 @@ class TestComponentSignals(BaseEvenniaTest):
|
|||
def setUp(self):
|
||||
super().setUp()
|
||||
self.char1 = create.create_object(
|
||||
CharWithSignal, key="Char",
|
||||
CharWithSignal,
|
||||
key="Char",
|
||||
)
|
||||
|
||||
def test_host_can_register_as_listener(self):
|
||||
self.char1.signals.trigger("my_signal")
|
||||
|
||||
assert self.char1.my_signal_is_called
|
||||
assert not getattr(self.char1, 'my_other_signal_is_called', None)
|
||||
assert not getattr(self.char1, "my_other_signal_is_called", None)
|
||||
|
||||
def test_host_can_register_as_responder(self):
|
||||
responses = self.char1.signals.query("my_response")
|
||||
|
|
@ -258,7 +261,7 @@ class TestComponentSignals(BaseEvenniaTest):
|
|||
|
||||
component = char.cmp.test_signal_a
|
||||
assert component.my_signal_is_called
|
||||
assert not getattr(component, 'my_other_signal_is_called', None)
|
||||
assert not getattr(component, "my_other_signal_is_called", None)
|
||||
|
||||
def test_component_can_register_as_responder(self):
|
||||
char = self.char1
|
||||
|
|
|
|||
|
|
@ -328,4 +328,4 @@ class GametimeScript(DefaultScript):
|
|||
callback()
|
||||
|
||||
seconds = real_seconds_until(**self.db.gametime)
|
||||
self.restart(interval=seconds)
|
||||
self.start(interval=seconds, force_restart=True)
|
||||
|
|
|
|||
|
|
@ -189,7 +189,7 @@ class EventCharacter(DefaultCharacter):
|
|||
"""Return the CallbackHandler."""
|
||||
return CallbackHandler(self)
|
||||
|
||||
def announce_move_from(self, destination, msg=None, mapping=None):
|
||||
def announce_move_from(self, destination, msg=None, move_type="move", mapping=None, **kwargs):
|
||||
"""
|
||||
Called if the move is to be announced. This is
|
||||
called while we are still standing in the old
|
||||
|
|
@ -234,9 +234,9 @@ class EventCharacter(DefaultCharacter):
|
|||
if not string:
|
||||
return
|
||||
|
||||
super().announce_move_from(destination, msg=string, mapping=mapping)
|
||||
super().announce_move_from(destination, msg=string, move_type=move_type, mapping=mapping, **kwargs)
|
||||
|
||||
def announce_move_to(self, source_location, msg=None, mapping=None):
|
||||
def announce_move_to(self, source_location, msg=None, move_type="move", mapping=None, **kwargs):
|
||||
"""
|
||||
Called after the move if the move was not quiet. At this point
|
||||
we are standing in the new location.
|
||||
|
|
@ -292,9 +292,9 @@ class EventCharacter(DefaultCharacter):
|
|||
if not string:
|
||||
return
|
||||
|
||||
super().announce_move_to(source_location, msg=string, mapping=mapping)
|
||||
super().announce_move_to(source_location, msg=string, move_type=move_type, mapping=mapping, **kwargs)
|
||||
|
||||
def at_pre_move(self, destination):
|
||||
def at_pre_move(self, destination, move_type="move", **kwargs):
|
||||
"""
|
||||
Called just before starting to move this object to
|
||||
destination.
|
||||
|
|
@ -334,7 +334,7 @@ class EventCharacter(DefaultCharacter):
|
|||
|
||||
return True
|
||||
|
||||
def at_post_move(self, source_location):
|
||||
def at_post_move(self, source_location, move_type="move", **kwargs):
|
||||
"""
|
||||
Called after move has completed, regardless of quiet mode or
|
||||
not. Allows changes to the object due to the location it is
|
||||
|
|
@ -644,7 +644,7 @@ class EventExit(DefaultExit):
|
|||
"""Return the CallbackHandler."""
|
||||
return CallbackHandler(self)
|
||||
|
||||
def at_traverse(self, traversing_object, target_location):
|
||||
def at_traverse(self, traversing_object, target_location, **kwargs):
|
||||
"""
|
||||
This hook is responsible for handling the actual traversal,
|
||||
normally by calling
|
||||
|
|
@ -665,7 +665,7 @@ class EventExit(DefaultExit):
|
|||
if not allow:
|
||||
return
|
||||
|
||||
super().at_traverse(traversing_object, target_location)
|
||||
super().at_traverse(traversing_object, target_location, **kwargs)
|
||||
|
||||
# After traversing
|
||||
if is_character:
|
||||
|
|
@ -732,7 +732,7 @@ class EventObject(DefaultObject):
|
|||
"""Return the CallbackHandler."""
|
||||
return CallbackHandler(self)
|
||||
|
||||
def at_get(self, getter):
|
||||
def at_get(self, getter, **kwargs):
|
||||
"""
|
||||
Called by the default `get` command when this object has been
|
||||
picked up.
|
||||
|
|
@ -745,10 +745,10 @@ class EventObject(DefaultObject):
|
|||
permissions for that.
|
||||
|
||||
"""
|
||||
super().at_get(getter)
|
||||
super().at_get(getter, **kwargs)
|
||||
self.callbacks.call("get", getter, self)
|
||||
|
||||
def at_drop(self, dropper):
|
||||
def at_drop(self, dropper, **kwargs):
|
||||
"""
|
||||
Called by the default `drop` command when this object has been
|
||||
dropped.
|
||||
|
|
@ -761,7 +761,7 @@ class EventObject(DefaultObject):
|
|||
permissions from that.
|
||||
|
||||
"""
|
||||
super().at_drop(dropper)
|
||||
super().at_drop(dropper, **kwargs)
|
||||
self.callbacks.call("drop", dropper, self)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -236,7 +236,7 @@ class CmdGiveUp(CmdEvscapeRoom):
|
|||
# manually call move hooks
|
||||
self.room.msg_room(self.caller, f"|r{self.caller.key} gave up and was whisked away!|n")
|
||||
self.room.at_object_leave(self.caller, self.caller.home)
|
||||
self.caller.move_to(self.caller.home, quiet=True, move_hooks=False)
|
||||
self.caller.move_to(self.caller.home, quiet=True, move_hooks=False, move_type="teleport")
|
||||
|
||||
# back to menu
|
||||
run_evscaperoom_menu(self.caller)
|
||||
|
|
|
|||
|
|
@ -185,7 +185,7 @@ class EvscapeRoom(EvscaperoomObject, DefaultRoom):
|
|||
|
||||
# Evennia hooks
|
||||
|
||||
def at_object_receive(self, moved_obj, source_location):
|
||||
def at_object_receive(self, moved_obj, source_location, move_type="move", **kwargs):
|
||||
"""
|
||||
Called when an object arrives in the room. This can be used to
|
||||
sum up the situation, set tags etc.
|
||||
|
|
@ -195,7 +195,7 @@ class EvscapeRoom(EvscaperoomObject, DefaultRoom):
|
|||
self.log(f"JOIN: {moved_obj} joined room")
|
||||
self.state.character_enters(moved_obj)
|
||||
|
||||
def at_object_leave(self, moved_obj, target_location, **kwargs):
|
||||
def at_object_leave(self, moved_obj, target_location, move_type="move", **kwargs):
|
||||
"""
|
||||
Called when an object leaves the room; if this is a Character we need
|
||||
to clean them up and move them to the menu state.
|
||||
|
|
|
|||
|
|
@ -611,7 +611,7 @@ class CmdDrop(MuxCommand):
|
|||
if obj.db.worn:
|
||||
obj.remove(caller, quiet=True)
|
||||
|
||||
obj.move_to(caller.location, quiet=True)
|
||||
obj.move_to(caller.location, quiet=True, move_type="drop")
|
||||
caller.msg("You drop %s." % (obj.name,))
|
||||
caller.location.msg_contents("%s drops %s." % (caller.name, obj.name), exclude=caller)
|
||||
# Call the object script's at_drop() method.
|
||||
|
|
@ -664,10 +664,10 @@ class CmdGive(MuxCommand):
|
|||
# Remove clothes if they're given.
|
||||
if to_give.db.worn:
|
||||
to_give.remove(caller)
|
||||
to_give.move_to(caller.location, quiet=True)
|
||||
to_give.move_to(caller.location, quiet=True, move_type="remove")
|
||||
# give object
|
||||
caller.msg("You give %s to %s." % (to_give.key, target.key))
|
||||
to_give.move_to(target, quiet=True)
|
||||
to_give.move_to(target, quiet=True, move_type="give")
|
||||
target.msg("%s gives you %s." % (caller.key, to_give.key))
|
||||
# Call the object script's at_give() method.
|
||||
to_give.at_give(caller, target)
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ class SlowExit(DefaultExit):
|
|||
def move_callback():
|
||||
"This callback will be called by utils.delay after move_delay seconds."
|
||||
source_location = traversing_object.location
|
||||
if traversing_object.move_to(target_location):
|
||||
if traversing_object.move_to(target_location, move_type="traverse"):
|
||||
self.at_post_traverse(traversing_object, source_location)
|
||||
else:
|
||||
if self.db.err_traverse:
|
||||
|
|
|
|||
|
|
@ -538,7 +538,7 @@ class WildernessRoom(DefaultRoom):
|
|||
# This object wasn't in the wilderness yet. Let's add it.
|
||||
itemcoords[moved_obj] = self.coordinates
|
||||
|
||||
def at_object_leave(self, moved_obj, target_location):
|
||||
def at_object_leave(self, moved_obj, target_location, move_type="move", **kwargs):
|
||||
"""
|
||||
Called just before an object leaves from inside this object. This is a
|
||||
default Evennia hook.
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ Roleplaying emotes and language - Griatch, 2015
|
|||
"""
|
||||
|
||||
from .rpsystem import EmoteError, SdescError, RecogError, LanguageError # noqa
|
||||
from .rpsystem import ordered_permutation_regex, regex_tuple_from_key_alias # noqa
|
||||
from .rpsystem import parse_language, parse_sdescs_and_recogs, send_emote # noqa
|
||||
from .rpsystem import SdescHandler, RecogHandler # noqa
|
||||
from .rpsystem import RPCommand, CmdEmote, CmdSay, CmdSdesc, CmdPose, CmdRecog, CmdMask # noqa
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -96,7 +96,8 @@ recog01 = "Mr Receiver"
|
|||
recog02 = "Mr Receiver2"
|
||||
recog10 = "Mr Sender"
|
||||
emote = 'With a flair, /me looks at /first and /colliding sdesc-guy. She says "This is a test."'
|
||||
case_emote = "/me looks at /first, then /FIRST, /First and /Colliding twice."
|
||||
case_emote = "/Me looks at /first. Then, /me looks at /FIRST, /First and /Colliding twice."
|
||||
poss_emote = "/Me frowns at /first for trying to steal /me's test."
|
||||
|
||||
|
||||
class TestRPSystem(BaseEvenniaTest):
|
||||
|
|
@ -113,41 +114,11 @@ class TestRPSystem(BaseEvenniaTest):
|
|||
rpsystem.ContribRPCharacter, key="Receiver2", location=self.room
|
||||
)
|
||||
|
||||
def test_ordered_permutation_regex(self):
|
||||
self.assertEqual(
|
||||
rpsystem.ordered_permutation_regex(sdesc0),
|
||||
"/[0-9]*-*A\\ nice\\ sender\\ of\\ emotes(?=\\W|$)+|"
|
||||
"/[0-9]*-*nice\\ sender\\ of\\ emotes(?=\\W|$)+|"
|
||||
"/[0-9]*-*A\\ nice\\ sender\\ of(?=\\W|$)+|"
|
||||
"/[0-9]*-*sender\\ of\\ emotes(?=\\W|$)+|"
|
||||
"/[0-9]*-*nice\\ sender\\ of(?=\\W|$)+|"
|
||||
"/[0-9]*-*A\\ nice\\ sender(?=\\W|$)+|"
|
||||
"/[0-9]*-*nice\\ sender(?=\\W|$)+|"
|
||||
"/[0-9]*-*of\\ emotes(?=\\W|$)+|"
|
||||
"/[0-9]*-*sender\\ of(?=\\W|$)+|"
|
||||
"/[0-9]*-*A\\ nice(?=\\W|$)+|"
|
||||
"/[0-9]*-*emotes(?=\\W|$)+|"
|
||||
"/[0-9]*-*sender(?=\\W|$)+|"
|
||||
"/[0-9]*-*nice(?=\\W|$)+|"
|
||||
"/[0-9]*-*of(?=\\W|$)+|"
|
||||
"/[0-9]*-*A(?=\\W|$)+",
|
||||
)
|
||||
|
||||
def test_sdesc_handler(self):
|
||||
self.speaker.sdesc.add(sdesc0)
|
||||
self.assertEqual(self.speaker.sdesc.get(), sdesc0)
|
||||
self.speaker.sdesc.add("This is {#324} ignored")
|
||||
self.assertEqual(self.speaker.sdesc.get(), "This is 324 ignored")
|
||||
self.speaker.sdesc.add("Testing three words")
|
||||
self.assertEqual(
|
||||
self.speaker.sdesc.get_regex_tuple()[0].pattern,
|
||||
"/[0-9]*-*Testing\ three\ words(?=\W|$)+|"
|
||||
"/[0-9]*-*Testing\ three(?=\W|$)+|"
|
||||
"/[0-9]*-*three\ words(?=\W|$)+|"
|
||||
"/[0-9]*-*Testing(?=\W|$)+|"
|
||||
"/[0-9]*-*three(?=\W|$)+|"
|
||||
"/[0-9]*-*words(?=\W|$)+",
|
||||
)
|
||||
|
||||
def test_recog_handler(self):
|
||||
self.speaker.sdesc.add(sdesc0)
|
||||
|
|
@ -156,12 +127,8 @@ class TestRPSystem(BaseEvenniaTest):
|
|||
self.speaker.recog.add(self.receiver2, recog02)
|
||||
self.assertEqual(self.speaker.recog.get(self.receiver1), recog01)
|
||||
self.assertEqual(self.speaker.recog.get(self.receiver2), recog02)
|
||||
self.assertEqual(
|
||||
self.speaker.recog.get_regex_tuple(self.receiver1)[0].pattern,
|
||||
"/[0-9]*-*Mr\\ Receiver(?=\\W|$)+|/[0-9]*-*Receiver(?=\\W|$)+|/[0-9]*-*Mr(?=\\W|$)+",
|
||||
)
|
||||
self.speaker.recog.remove(self.receiver1)
|
||||
self.assertEqual(self.speaker.recog.get(self.receiver1), sdesc1)
|
||||
self.assertEqual(self.speaker.recog.get(self.receiver1), None)
|
||||
|
||||
self.assertEqual(self.speaker.recog.all(), {"Mr Receiver2": self.receiver2})
|
||||
|
||||
|
|
@ -174,18 +141,21 @@ class TestRPSystem(BaseEvenniaTest):
|
|||
),
|
||||
)
|
||||
|
||||
def parse_sdescs_and_recogs(self):
|
||||
def test_parse_sdescs_and_recogs(self):
|
||||
speaker = self.speaker
|
||||
speaker.sdesc.add(sdesc0)
|
||||
self.receiver1.sdesc.add(sdesc1)
|
||||
self.receiver2.sdesc.add(sdesc2)
|
||||
id0 = f"#{speaker.id}"
|
||||
id1 = f"#{self.receiver1.id}"
|
||||
id2 = f"#{self.receiver2.id}"
|
||||
candidates = (self.receiver1, self.receiver2)
|
||||
result = (
|
||||
'With a flair, {#9} looks at {#10} and {#11}. She says "This is a test."',
|
||||
'With a flair, {'+id0+'} looks at {'+id1+'} and {'+id2+'}. She says "This is a test."',
|
||||
{
|
||||
"#11": "Another nice colliding sdesc-guy for tests",
|
||||
"#10": "The first receiver of emotes.",
|
||||
"#9": "A nice sender of emotes",
|
||||
id2: self.receiver2,
|
||||
id1: self.receiver1,
|
||||
id0: speaker,
|
||||
},
|
||||
)
|
||||
self.assertEqual(
|
||||
|
|
@ -198,6 +168,47 @@ class TestRPSystem(BaseEvenniaTest):
|
|||
result,
|
||||
)
|
||||
|
||||
def test_possessive_selfref(self):
|
||||
speaker = self.speaker
|
||||
speaker.sdesc.add(sdesc0)
|
||||
self.receiver1.sdesc.add(sdesc1)
|
||||
self.receiver2.sdesc.add(sdesc2)
|
||||
id0 = f"#{speaker.id}"
|
||||
id1 = f"#{self.receiver1.id}"
|
||||
id2 = f"#{self.receiver2.id}"
|
||||
candidates = (self.receiver1, self.receiver2)
|
||||
result = (
|
||||
"{"+id0+"} frowns at {"+id1+"} for trying to steal {"+id0+"}'s test.",
|
||||
{
|
||||
id1: self.receiver1,
|
||||
id0: speaker,
|
||||
},
|
||||
)
|
||||
self.assertEqual(
|
||||
rpsystem.parse_sdescs_and_recogs(speaker, candidates, poss_emote, case_sensitive=False),
|
||||
result,
|
||||
)
|
||||
|
||||
def test_get_sdesc(self):
|
||||
looker = self.speaker # Sender
|
||||
target = self.receiver1 # Receiver1
|
||||
looker.sdesc.add(sdesc0) # A nice sender of emotes
|
||||
target.sdesc.add(sdesc1) # The first receiver of emotes.
|
||||
|
||||
# sdesc with no processing
|
||||
self.assertEqual(looker.get_sdesc(target), "The first receiver of emotes.")
|
||||
# sdesc with processing
|
||||
self.assertEqual(
|
||||
looker.get_sdesc(target, process=True), "|bThe first receiver of emotes.|n"
|
||||
)
|
||||
|
||||
looker.recog.add(target, recog01) # Mr Receiver
|
||||
|
||||
# recog with no processing
|
||||
self.assertEqual(looker.get_sdesc(target), "Mr Receiver")
|
||||
# recog with processing
|
||||
self.assertEqual(looker.get_sdesc(target, process=True), "|mMr Receiver|n")
|
||||
|
||||
def test_send_emote(self):
|
||||
speaker = self.speaker
|
||||
receiver1 = self.receiver1
|
||||
|
|
@ -211,19 +222,19 @@ class TestRPSystem(BaseEvenniaTest):
|
|||
receiver2.msg = lambda text, **kwargs: setattr(self, "out2", text)
|
||||
rpsystem.send_emote(speaker, receivers, emote, case_sensitive=False)
|
||||
self.assertEqual(
|
||||
self.out0,
|
||||
"With a flair, |bSender|n looks at |bThe first receiver of emotes.|n "
|
||||
self.out0[0],
|
||||
"With a flair, |mSender|n looks at |bThe first receiver of emotes.|n "
|
||||
'and |bAnother nice colliding sdesc-guy for tests|n. She says |w"This is a test."|n',
|
||||
)
|
||||
self.assertEqual(
|
||||
self.out1,
|
||||
"With a flair, |bA nice sender of emotes|n looks at |bReceiver1|n and "
|
||||
self.out1[0],
|
||||
"With a flair, |bA nice sender of emotes|n looks at |mReceiver1|n and "
|
||||
'|bAnother nice colliding sdesc-guy for tests|n. She says |w"This is a test."|n',
|
||||
)
|
||||
self.assertEqual(
|
||||
self.out2,
|
||||
self.out2[0],
|
||||
"With a flair, |bA nice sender of emotes|n looks at |bThe first "
|
||||
'receiver of emotes.|n and |bReceiver2|n. She says |w"This is a test."|n',
|
||||
'receiver of emotes.|n and |mReceiver2|n. She says |w"This is a test."|n',
|
||||
)
|
||||
|
||||
def test_send_case_sensitive_emote(self):
|
||||
|
|
@ -240,21 +251,22 @@ class TestRPSystem(BaseEvenniaTest):
|
|||
receiver2.msg = lambda text, **kwargs: setattr(self, "out2", text)
|
||||
rpsystem.send_emote(speaker, receivers, case_emote)
|
||||
self.assertEqual(
|
||||
self.out0,
|
||||
"|bSender|n looks at |bthe first receiver of emotes.|n, then "
|
||||
"|bTHE FIRST RECEIVER OF EMOTES.|n, |bThe first receiver of emotes.|n and "
|
||||
"|bAnother nice colliding sdesc-guy for tests|n twice.",
|
||||
self.out0[0],
|
||||
"|mSender|n looks at |bthe first receiver of emotes.|n. Then, |mSender|n "
|
||||
"looks at |bTHE FIRST RECEIVER OF EMOTES.|n, |bThe first receiver of emotes.|n "
|
||||
"and |bAnother nice colliding sdesc-guy for tests|n twice.",
|
||||
)
|
||||
self.assertEqual(
|
||||
self.out1,
|
||||
"|bA nice sender of emotes|n looks at |bReceiver1|n, then |bReceiver1|n, "
|
||||
"|bReceiver1|n and |bAnother nice colliding sdesc-guy for tests|n twice.",
|
||||
self.out1[0],
|
||||
"|bA nice sender of emotes|n looks at |mReceiver1|n. Then, "
|
||||
"|ba nice sender of emotes|n looks at |mReceiver1|n, |mReceiver1|n "
|
||||
"and |bAnother nice colliding sdesc-guy for tests|n twice.",
|
||||
)
|
||||
self.assertEqual(
|
||||
self.out2,
|
||||
"|bA nice sender of emotes|n looks at |bthe first receiver of emotes.|n, "
|
||||
"then |bTHE FIRST RECEIVER OF EMOTES.|n, |bThe first receiver of "
|
||||
"emotes.|n and |bReceiver2|n twice.",
|
||||
self.out2[0],
|
||||
"|bA nice sender of emotes|n looks at |bthe first receiver of emotes.|n. "
|
||||
"Then, |ba nice sender of emotes|n looks at |bTHE FIRST RECEIVER OF EMOTES.|n, "
|
||||
"|bThe first receiver of emotes.|n and |mReceiver2|n twice.",
|
||||
)
|
||||
|
||||
def test_rpsearch(self):
|
||||
|
|
@ -265,18 +277,6 @@ class TestRPSystem(BaseEvenniaTest):
|
|||
self.assertEqual(self.speaker.search("receiver of emotes"), self.receiver1)
|
||||
self.assertEqual(self.speaker.search("colliding"), self.receiver2)
|
||||
|
||||
def test_regex_tuple_from_key_alias(self):
|
||||
self.speaker.aliases.add("foo bar")
|
||||
self.speaker.aliases.add("this thing is a long thing")
|
||||
t0 = time.time()
|
||||
result = rpsystem.regex_tuple_from_key_alias(self.speaker)
|
||||
t1 = time.time()
|
||||
result = rpsystem.regex_tuple_from_key_alias(self.speaker)
|
||||
t2 = time.time()
|
||||
# print(f"t1: {t1 - t0}, t2: {t2 - t1}")
|
||||
self.assertLess(t2 - t1, 10**-4)
|
||||
self.assertEqual(result, (Anything, self.speaker, self.speaker.key))
|
||||
|
||||
|
||||
class TestRPSystemCommands(BaseEvenniaCommandTest):
|
||||
def setUp(self):
|
||||
|
|
@ -305,7 +305,7 @@ class TestRPSystemCommands(BaseEvenniaCommandTest):
|
|||
self.call(
|
||||
rpsystem.CmdRecog(),
|
||||
"barfoo as friend",
|
||||
"Char will now remember BarFoo Character as friend.",
|
||||
"You will now remember BarFoo Character as friend.",
|
||||
)
|
||||
self.call(
|
||||
rpsystem.CmdRecog(),
|
||||
|
|
@ -316,6 +316,6 @@ class TestRPSystemCommands(BaseEvenniaCommandTest):
|
|||
self.call(
|
||||
rpsystem.CmdRecog(),
|
||||
"friend",
|
||||
"Char will now know them only as 'BarFoo Character'",
|
||||
"You will now know them only as 'BarFoo Character'",
|
||||
cmdstring="forget",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -321,7 +321,6 @@ class TestTraitStatic(_TraitHandlerBase):
|
|||
self.trait.mult = 0.75
|
||||
self.assertEqual(self._get_values(), (5, 1, 0.75, 4.5))
|
||||
|
||||
|
||||
def test_delete(self):
|
||||
"""Deleting resets to default."""
|
||||
self.trait.mult = 2.0
|
||||
|
|
@ -362,7 +361,14 @@ class TestTraitCounter(_TraitHandlerBase):
|
|||
|
||||
def _get_values(self):
|
||||
"""Get (base, mod, mult, value, min, max)."""
|
||||
return (self.trait.base, self.trait.mod, self.trait.mult, self.trait.value, self.trait.min, self.trait.max)
|
||||
return (
|
||||
self.trait.base,
|
||||
self.trait.mod,
|
||||
self.trait.mult,
|
||||
self.trait.value,
|
||||
self.trait.min,
|
||||
self.trait.max,
|
||||
)
|
||||
|
||||
def test_init(self):
|
||||
self.assertEqual(
|
||||
|
|
@ -634,7 +640,14 @@ class TestTraitGauge(_TraitHandlerBase):
|
|||
|
||||
def _get_values(self):
|
||||
"""Get (base, mod, mult, value, min, max)."""
|
||||
return (self.trait.base, self.trait.mod, self.trait.mult, self.trait.value, self.trait.min, self.trait.max)
|
||||
return (
|
||||
self.trait.base,
|
||||
self.trait.mod,
|
||||
self.trait.mult,
|
||||
self.trait.value,
|
||||
self.trait.min,
|
||||
self.trait.max,
|
||||
)
|
||||
|
||||
def test_init(self):
|
||||
self.assertEqual(
|
||||
|
|
|
|||
|
|
@ -1148,7 +1148,7 @@ class Trait:
|
|||
|
||||
class StaticTrait(Trait):
|
||||
"""
|
||||
Static Trait. This is a single value with a modifier,
|
||||
Static Trait. This is a single value with a modifier,
|
||||
multiplier, and no concept of a 'current' value or min/max etc.
|
||||
|
||||
value = (base + mod) * mult
|
||||
|
|
@ -1161,7 +1161,9 @@ class StaticTrait(Trait):
|
|||
|
||||
def __str__(self):
|
||||
status = "{value:11}".format(value=self.value)
|
||||
return "{name:12} {status} ({mod:+3}) (* {mult:.2f})".format(name=self.name, status=status, mod=self.mod, mult=self.mult)
|
||||
return "{name:12} {status} ({mod:+3}) (* {mult:.2f})".format(
|
||||
name=self.name, status=status, mod=self.mod, mult=self.mult
|
||||
)
|
||||
|
||||
# Helpers
|
||||
@property
|
||||
|
|
@ -1189,7 +1191,7 @@ class StaticTrait(Trait):
|
|||
def mult(self):
|
||||
"""The trait's multiplier."""
|
||||
return self._data["mult"]
|
||||
|
||||
|
||||
@mult.setter
|
||||
def mult(self, amount):
|
||||
if type(amount) in (int, float):
|
||||
|
|
@ -1322,16 +1324,16 @@ class CounterTrait(Trait):
|
|||
now = time()
|
||||
tdiff = now - self._data["last_update"]
|
||||
current += rate * tdiff
|
||||
value = (current + self.mod)
|
||||
value = current + self.mod
|
||||
|
||||
# we must make sure so we don't overstep our bounds
|
||||
# even if .mod is included
|
||||
|
||||
if self._passed_ratetarget(value):
|
||||
current = (self._data["ratetarget"] - self.mod)
|
||||
current = self._data["ratetarget"] - self.mod
|
||||
self._stop_timer()
|
||||
elif not self._within_boundaries(value):
|
||||
current = (self._enforce_boundaries(value) - self.mod)
|
||||
current = self._enforce_boundaries(value) - self.mod
|
||||
self._stop_timer()
|
||||
else:
|
||||
self._data["last_update"] = now
|
||||
|
|
@ -1378,7 +1380,7 @@ class CounterTrait(Trait):
|
|||
@property
|
||||
def mult(self):
|
||||
return self._data["mult"]
|
||||
|
||||
|
||||
@mult.setter
|
||||
def mult(self, amount):
|
||||
if type(amount) in (int, float):
|
||||
|
|
@ -1571,7 +1573,9 @@ class GaugeTrait(CounterTrait):
|
|||
|
||||
def __str__(self):
|
||||
status = "{value:4} / {base:4}".format(value=self.value, base=self.base)
|
||||
return "{name:12} {status} ({mod:+3}) (* {mult:.2f})".format(name=self.name, status=status, mod=self.mod, mult=self.mult)
|
||||
return "{name:12} {status} ({mod:+3}) (* {mult:.2f})".format(
|
||||
name=self.name, status=status, mod=self.mod, mult=self.mult
|
||||
)
|
||||
|
||||
@property
|
||||
def base(self):
|
||||
|
|
@ -1596,11 +1600,11 @@ class GaugeTrait(CounterTrait):
|
|||
if value + self.base < self.min:
|
||||
value = self.min - self.base
|
||||
self._data["mod"] = value
|
||||
|
||||
|
||||
@property
|
||||
def mult(self):
|
||||
return self._data["mult"]
|
||||
|
||||
|
||||
@mult.setter
|
||||
def mult(self, amount):
|
||||
if type(amount) in (int, float):
|
||||
|
|
@ -1621,7 +1625,7 @@ class GaugeTrait(CounterTrait):
|
|||
if value is None:
|
||||
self._data["min"] = self.default_keys["min"]
|
||||
elif type(value) in (int, float):
|
||||
self._data["min"] = min(value, (self.base + self.mod) * self.mult)
|
||||
self._data["min"] = min(value, (self.base + self.mod) * self.mult)
|
||||
|
||||
@property
|
||||
def max(self):
|
||||
|
|
@ -1644,7 +1648,7 @@ class GaugeTrait(CounterTrait):
|
|||
def current(self):
|
||||
"""The `current` value of the gauge."""
|
||||
return self._update_current(
|
||||
self._enforce_boundaries(self._data.get("current", (self.base + self.mod) * self.mult))
|
||||
self._enforce_boundaries(self._data.get("current", (self.base + self.mod) * self.mult))
|
||||
)
|
||||
|
||||
@current.setter
|
||||
|
|
@ -1655,7 +1659,7 @@ class GaugeTrait(CounterTrait):
|
|||
@current.deleter
|
||||
def current(self):
|
||||
"Resets current back to 'full'"
|
||||
self._data["current"] = (self.base + self.mod) * self.mult
|
||||
self._data["current"] = (self.base + self.mod) * self.mult
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
|
|
|
|||
|
|
@ -225,7 +225,7 @@ class CmdTutorialGiveUp(default_cmds.MuxCommand):
|
|||
)
|
||||
return
|
||||
|
||||
self.caller.move_to(outro_room)
|
||||
self.caller.move_to(outro_room, move_type="teleport")
|
||||
|
||||
|
||||
class TutorialRoomCmdSet(CmdSet):
|
||||
|
|
@ -259,7 +259,7 @@ class TutorialRoom(DefaultRoom):
|
|||
)
|
||||
self.cmdset.add_default(TutorialRoomCmdSet)
|
||||
|
||||
def at_object_receive(self, new_arrival, source_location):
|
||||
def at_object_receive(self, new_arrival, source_location, move_type="move", **kwargs):
|
||||
"""
|
||||
When an object enter a tutorial room we tell other objects in
|
||||
the room about it by trying to call a hook on them. The Mob object
|
||||
|
|
@ -451,7 +451,7 @@ class IntroRoom(TutorialRoom):
|
|||
"the account."
|
||||
)
|
||||
|
||||
def at_object_receive(self, character, source_location):
|
||||
def at_object_receive(self, character, source_location, move_type="move", **kwargs):
|
||||
"""
|
||||
Assign properties on characters
|
||||
"""
|
||||
|
|
@ -523,7 +523,7 @@ class CmdEast(Command):
|
|||
# Move to the east room.
|
||||
eexit = search_object(self.obj.db.east_exit)
|
||||
if eexit:
|
||||
caller.move_to(eexit[0])
|
||||
caller.move_to(eexit[0], move_type="traverse")
|
||||
else:
|
||||
caller.msg("No east exit was found for this room. Contact an admin.")
|
||||
return
|
||||
|
|
@ -570,7 +570,7 @@ class CmdWest(Command):
|
|||
# Move to the west room.
|
||||
wexit = search_object(self.obj.db.west_exit)
|
||||
if wexit:
|
||||
caller.move_to(wexit[0])
|
||||
caller.move_to(wexit[0], move_type="traverse")
|
||||
else:
|
||||
caller.msg("No west exit was found for this room. Contact an admin.")
|
||||
return
|
||||
|
|
@ -658,7 +658,7 @@ class CmdLookBridge(Command):
|
|||
fall_exit = search_object(self.obj.db.fall_exit)
|
||||
if fall_exit:
|
||||
self.caller.msg("|r%s|n" % FALL_MESSAGE)
|
||||
self.caller.move_to(fall_exit[0], quiet=True)
|
||||
self.caller.move_to(fall_exit[0], quiet=True, move_type="fall")
|
||||
# inform others on the bridge
|
||||
self.obj.msg_contents(
|
||||
"A plank gives way under %s's feet and "
|
||||
|
|
@ -770,7 +770,7 @@ class BridgeRoom(WeatherRoom):
|
|||
# send a message most of the time
|
||||
self.msg_contents("|w%s|n" % random.choice(BRIDGE_WEATHER))
|
||||
|
||||
def at_object_receive(self, character, source_location):
|
||||
def at_object_receive(self, character, source_location, move_type="move", **kwargs):
|
||||
"""
|
||||
This hook is called by the engine whenever the player is moved
|
||||
into this room.
|
||||
|
|
@ -796,7 +796,7 @@ class BridgeRoom(WeatherRoom):
|
|||
character.db.tutorial_bridge_position = 0
|
||||
character.execute_cmd("look")
|
||||
|
||||
def at_object_leave(self, character, target_location):
|
||||
def at_object_leave(self, character, target_location, move_type="move", **kwargs):
|
||||
"""
|
||||
This is triggered when the player leaves the bridge room.
|
||||
"""
|
||||
|
|
@ -1038,7 +1038,7 @@ class DarkRoom(TutorialRoom):
|
|||
# put players in darkness
|
||||
char.msg("The room is completely dark.")
|
||||
|
||||
def at_object_receive(self, obj, source_location):
|
||||
def at_object_receive(self, obj, source_location, move_type="move", **kwargs):
|
||||
"""
|
||||
Called when an object enters the room.
|
||||
"""
|
||||
|
|
@ -1048,7 +1048,7 @@ class DarkRoom(TutorialRoom):
|
|||
# in case the new guy carries light with them
|
||||
self.check_light_state()
|
||||
|
||||
def at_object_leave(self, obj, target_location):
|
||||
def at_object_leave(self, obj, target_location, move_type="move", **kwargs):
|
||||
"""
|
||||
In case people leave with the light, we make sure to clear the
|
||||
DarkCmdSet if necessary. This also works if they are
|
||||
|
|
@ -1103,7 +1103,7 @@ class TeleportRoom(TutorialRoom):
|
|||
self.db.failure_teleport_msg = "You fail!"
|
||||
self.db.failure_teleport_to = "dark cell"
|
||||
|
||||
def at_object_receive(self, character, source_location):
|
||||
def at_object_receive(self, character, source_location, move_type="move", **kwargs):
|
||||
"""
|
||||
This hook is called by the engine whenever the player is moved into
|
||||
this room.
|
||||
|
|
@ -1130,7 +1130,7 @@ class TeleportRoom(TutorialRoom):
|
|||
else:
|
||||
character.msg(self.db.failure_teleport_msg)
|
||||
# teleport quietly to the new place
|
||||
character.move_to(results[0], quiet=True, move_hooks=False)
|
||||
character.move_to(results[0], quiet=True, move_hooks=False, move_type="teleport")
|
||||
# we have to call this manually since we turn off move_hooks
|
||||
# - this is necessary to make the target dark room aware of an
|
||||
# already carried light.
|
||||
|
|
@ -1167,7 +1167,7 @@ class OutroRoom(TutorialRoom):
|
|||
"character."
|
||||
)
|
||||
|
||||
def at_object_receive(self, character, source_location):
|
||||
def at_object_receive(self, character, source_location, move_type="move", **kwargs):
|
||||
"""
|
||||
Do cleanup.
|
||||
"""
|
||||
|
|
@ -1183,6 +1183,6 @@ class OutroRoom(TutorialRoom):
|
|||
obj.delete()
|
||||
character.tags.clear(category="tutorial_world")
|
||||
|
||||
def at_object_leave(self, character, destination):
|
||||
def at_object_leave(self, character, destination, move_type="move", **kwargs):
|
||||
if character.account:
|
||||
character.account.execute_cmd("unquell")
|
||||
|
|
|
|||
|
|
@ -160,7 +160,7 @@ class TestTutorialWorldRooms(BaseEvenniaCommandTest):
|
|||
def test_bridgeroom(self):
|
||||
room = create_object(tutrooms.BridgeRoom, key="bridgeroom")
|
||||
room.update_weather()
|
||||
self.char1.move_to(room)
|
||||
self.char1.move_to(room, move_type="teleport")
|
||||
self.call(
|
||||
tutrooms.CmdBridgeHelp(),
|
||||
"",
|
||||
|
|
@ -181,7 +181,7 @@ class TestTutorialWorldRooms(BaseEvenniaCommandTest):
|
|||
|
||||
def test_darkroom(self):
|
||||
room = create_object(tutrooms.DarkRoom, key="darkroom")
|
||||
self.char1.move_to(room)
|
||||
self.char1.move_to(room, move_type="teleport")
|
||||
self.call(tutrooms.CmdDarkHelp(), "", "Can't help you until")
|
||||
|
||||
def test_teleportroom(self):
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# world/
|
||||
|
||||
This folder is meant as a miscellanous folder for all that other stuff
|
||||
This folder is meant as a miscellaneous folder for all that other stuff
|
||||
related to the game. Code which are not commands or typeclasses go
|
||||
here, like custom economy systems, combat code, batch-files etc.
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ Possible keywords are:
|
|||
- `prototype_key` - the name of the prototype. This is required for db-prototypes,
|
||||
for module-prototypes, the global variable name of the dict is used instead
|
||||
- `prototype_parent` - string pointing to parent prototype if any. Prototype inherits
|
||||
in a similar way as classes, with children overriding values in their partents.
|
||||
in a similar way as classes, with children overriding values in their parents.
|
||||
- `key` - string, the main object identifier.
|
||||
- `typeclass` - string, if not set, will use `settings.BASE_OBJECT_TYPECLASS`.
|
||||
- `location` - this should be a valid object or #dbref.
|
||||
|
|
@ -42,7 +42,7 @@ Possible keywords are:
|
|||
of the shorter forms, defaults are used for the rest.
|
||||
- `tags` - Tags, as a list of tuples `(tag,)`, `(tag, category)` or `(tag, category, data)`.
|
||||
- Any other keywords are interpreted as Attributes with no category or lock.
|
||||
These will internally be added to `attrs` (eqivalent to `(attrname, value)`.
|
||||
These will internally be added to `attrs` (equivalent to `(attrname, value)`.
|
||||
|
||||
See the `spawn` command and `evennia.prototypes.spawner.spawn` for more info.
|
||||
|
||||
|
|
|
|||
|
|
@ -12,9 +12,13 @@ import re
|
|||
# since we use them (e.g. as command names).
|
||||
# Lunr's default ignore-word list is found here:
|
||||
# https://github.com/yeraydiazdiaz/lunr.py/blob/master/lunr/stop_word_filter.py
|
||||
_LUNR_STOP_WORD_FILTER_EXCEPTIONS = (
|
||||
["about", "might", "get", "who", "say"] + settings.LUNR_STOP_WORD_FILTER_EXCEPTIONS
|
||||
)
|
||||
_LUNR_STOP_WORD_FILTER_EXCEPTIONS = [
|
||||
"about",
|
||||
"might",
|
||||
"get",
|
||||
"who",
|
||||
"say",
|
||||
] + settings.LUNR_STOP_WORD_FILTER_EXCEPTIONS
|
||||
|
||||
|
||||
_LUNR = None
|
||||
|
|
|
|||
|
|
@ -473,6 +473,7 @@ def tag(accessing_obj, accessed_obj, *args, **kwargs):
|
|||
category = args[1] if len(args) > 1 else None
|
||||
return bool(accessing_obj.tags.get(tagkey, category=category))
|
||||
|
||||
|
||||
def is_ooc(accessing_obj, accessed_obj, *args, **kwargs):
|
||||
"""
|
||||
Usage:
|
||||
|
|
@ -489,13 +490,14 @@ def is_ooc(accessing_obj, accessed_obj, *args, **kwargs):
|
|||
session = accessed_obj.session
|
||||
except AttributeError:
|
||||
session = account.sessions.get()[0] # note-this doesn't work well
|
||||
# for high multisession mode. We may need
|
||||
# to change to sessiondb to resolve this
|
||||
# for high multisession mode. We may need
|
||||
# to change to sessiondb to resolve this
|
||||
try:
|
||||
return not account.get_puppet(session)
|
||||
except TypeError:
|
||||
return not session.get_puppet()
|
||||
|
||||
|
||||
def objtag(accessing_obj, accessed_obj, *args, **kwargs):
|
||||
"""
|
||||
Usage:
|
||||
|
|
|
|||
|
|
@ -836,6 +836,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
|||
use_destination=True,
|
||||
to_none=False,
|
||||
move_hooks=True,
|
||||
move_type="move",
|
||||
**kwargs,
|
||||
):
|
||||
"""
|
||||
|
|
@ -857,6 +858,11 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
|||
move_hooks (bool): If False, turn off the calling of move-related hooks
|
||||
(at_pre/post_move etc) with quiet=True, this is as quiet a move
|
||||
as can be done.
|
||||
move_type (str): The "kind of move" being performed, such as "teleport", "traverse",
|
||||
"get", "give", or "drop". The value can be arbitrary. By default, it only affects
|
||||
the text message generated by announce_move_to and announce_move_from by defining
|
||||
their {"type": move_type} for outgoing text. This can be used for altering
|
||||
messages and/or overloaded hook behaviors.
|
||||
|
||||
Keyword Args:
|
||||
Passed on to announce_move_to and announce_move_from hooks.
|
||||
|
|
@ -906,7 +912,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
|||
# Before the move, call eventual pre-commands.
|
||||
if move_hooks:
|
||||
try:
|
||||
if not self.at_pre_move(destination, **kwargs):
|
||||
if not self.at_pre_move(destination, move_type=move_type, **kwargs):
|
||||
return False
|
||||
except Exception as err:
|
||||
logerr(errtxt.format(err="at_pre_move()"), err)
|
||||
|
|
@ -918,7 +924,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
|||
# Call hook on source location
|
||||
if move_hooks and source_location:
|
||||
try:
|
||||
source_location.at_object_leave(self, destination, **kwargs)
|
||||
source_location.at_object_leave(self, destination, move_type=move_type, **kwargs)
|
||||
except Exception as err:
|
||||
logerr(errtxt.format(err="at_object_leave()"), err)
|
||||
return False
|
||||
|
|
@ -926,7 +932,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
|||
if not quiet:
|
||||
# tell the old room we are leaving
|
||||
try:
|
||||
self.announce_move_from(destination, **kwargs)
|
||||
self.announce_move_from(destination, move_type=move_type, **kwargs)
|
||||
except Exception as err:
|
||||
logerr(errtxt.format(err="announce_move_from()"), err)
|
||||
return False
|
||||
|
|
@ -941,7 +947,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
|||
if not quiet:
|
||||
# Tell the new room we are there.
|
||||
try:
|
||||
self.announce_move_to(source_location, **kwargs)
|
||||
self.announce_move_to(source_location, move_type=move_type, **kwargs)
|
||||
except Exception as err:
|
||||
logerr(errtxt.format(err="announce_move_to()"), err)
|
||||
return False
|
||||
|
|
@ -950,7 +956,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
|||
# Perform eventual extra commands on the receiving location
|
||||
# (the object has already arrived at this point)
|
||||
try:
|
||||
destination.at_object_receive(self, source_location, **kwargs)
|
||||
destination.at_object_receive(self, source_location, move_type=move_type, **kwargs)
|
||||
except Exception as err:
|
||||
logerr(errtxt.format(err="at_object_receive()"), err)
|
||||
return False
|
||||
|
|
@ -959,7 +965,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
|||
# (usually calling 'look')
|
||||
if move_hooks:
|
||||
try:
|
||||
self.at_post_move(source_location, **kwargs)
|
||||
self.at_post_move(source_location, move_type=move_type, **kwargs)
|
||||
except Exception as err:
|
||||
logerr(errtxt.format(err="at_post_move"), err)
|
||||
return False
|
||||
|
|
@ -1020,7 +1026,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
|||
# Famous last words: The account should never see this.
|
||||
string = "This place should not exist ... contact an admin."
|
||||
obj.msg(_(string))
|
||||
obj.move_to(home)
|
||||
obj.move_to(home, move_type="teleport")
|
||||
|
||||
@classmethod
|
||||
def create(cls, key, account=None, **kwargs):
|
||||
|
|
@ -1472,13 +1478,17 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
|||
|
||||
# hooks called when moving the object
|
||||
|
||||
def at_pre_move(self, destination, **kwargs):
|
||||
def at_pre_move(self, destination, move_type="move", **kwargs):
|
||||
"""
|
||||
Called just before starting to move this object to
|
||||
destination.
|
||||
|
||||
Args:
|
||||
destination (Object): The object we are moving to
|
||||
move_type (str): The type of move. "give", "traverse", etc.
|
||||
This is an arbitrary string provided to obj.move_to().
|
||||
Useful for altering messages or altering logic depending
|
||||
on the kind of movement.
|
||||
**kwargs (dict): Arbitrary, optional arguments for users
|
||||
overriding the call (unused by default).
|
||||
|
||||
|
|
@ -1496,7 +1506,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
|||
# deprecated alias
|
||||
at_before_move = at_pre_move
|
||||
|
||||
def announce_move_from(self, destination, msg=None, mapping=None, **kwargs):
|
||||
def announce_move_from(self, destination, msg=None, mapping=None, move_type="move", **kwargs):
|
||||
"""
|
||||
Called if the move is to be announced. This is
|
||||
called while we are still standing in the old
|
||||
|
|
@ -1506,6 +1516,10 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
|||
destination (Object): The place we are going to.
|
||||
msg (str, optional): a replacement message.
|
||||
mapping (dict, optional): additional mapping objects.
|
||||
move_type (str): The type of move. "give", "traverse", etc.
|
||||
This is an arbitrary string provided to obj.move_to().
|
||||
Useful for altering messages or altering logic depending
|
||||
on the kind of movement.
|
||||
**kwargs (dict): Arbitrary, optional arguments for users
|
||||
overriding the call (unused by default).
|
||||
|
||||
|
|
@ -1541,9 +1555,9 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
|||
}
|
||||
)
|
||||
|
||||
location.msg_contents(string, exclude=(self,), from_obj=self, mapping=mapping)
|
||||
location.msg_contents((string, {"type": move_type}), exclude=(self,), from_obj=self, mapping=mapping)
|
||||
|
||||
def announce_move_to(self, source_location, msg=None, mapping=None, **kwargs):
|
||||
def announce_move_to(self, source_location, msg=None, mapping=None, move_type="move", **kwargs):
|
||||
"""
|
||||
Called after the move if the move was not quiet. At this point
|
||||
we are standing in the new location.
|
||||
|
|
@ -1552,6 +1566,10 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
|||
source_location (Object): The place we came from
|
||||
msg (str, optional): the replacement message if location.
|
||||
mapping (dict, optional): additional mapping objects.
|
||||
move_type (str): The type of move. "give", "traverse", etc.
|
||||
This is an arbitrary string provided to obj.move_to().
|
||||
Useful for altering messages or altering logic depending
|
||||
on the kind of movement.
|
||||
**kwargs (dict): Arbitrary, optional arguments for users
|
||||
overriding the call (unused by default).
|
||||
|
||||
|
|
@ -1605,9 +1623,9 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
|||
}
|
||||
)
|
||||
|
||||
destination.msg_contents(string, exclude=(self,), from_obj=self, mapping=mapping)
|
||||
destination.msg_contents((string, {"type": move_type}), exclude=(self,), from_obj=self, mapping=mapping)
|
||||
|
||||
def at_post_move(self, source_location, **kwargs):
|
||||
def at_post_move(self, source_location, move_type="move", **kwargs):
|
||||
"""
|
||||
Called after move has completed, regardless of quiet mode or
|
||||
not. Allows changes to the object due to the location it is
|
||||
|
|
@ -1615,6 +1633,10 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
|||
|
||||
Args:
|
||||
source_location (Object): Wwhere we came from. This may be `None`.
|
||||
move_type (str): The type of move. "give", "traverse", etc.
|
||||
This is an arbitrary string provided to obj.move_to().
|
||||
Useful for altering messages or altering logic depending
|
||||
on the kind of movement.
|
||||
**kwargs (dict): Arbitrary, optional arguments for users
|
||||
overriding the call (unused by default).
|
||||
|
||||
|
|
@ -1624,20 +1646,24 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
|||
# deprecated
|
||||
at_after_move = at_post_move
|
||||
|
||||
def at_object_leave(self, moved_obj, target_location, **kwargs):
|
||||
def at_object_leave(self, moved_obj, target_location, move_type="move", **kwargs):
|
||||
"""
|
||||
Called just before an object leaves from inside this object
|
||||
|
||||
Args:
|
||||
moved_obj (Object): The object leaving
|
||||
target_location (Object): Where `moved_obj` is going.
|
||||
move_type (str): The type of move. "give", "traverse", etc.
|
||||
This is an arbitrary string provided to obj.move_to().
|
||||
Useful for altering messages or altering logic depending
|
||||
on the kind of movement.
|
||||
**kwargs (dict): Arbitrary, optional arguments for users
|
||||
overriding the call (unused by default).
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
def at_object_receive(self, moved_obj, source_location, **kwargs):
|
||||
def at_object_receive(self, moved_obj, source_location, move_type="move", **kwargs):
|
||||
"""
|
||||
Called after an object has been moved into this object.
|
||||
|
||||
|
|
@ -1645,6 +1671,10 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
|||
moved_obj (Object): The object moved into this one
|
||||
source_location (Object): Where `moved_object` came from.
|
||||
Note that this could be `None`.
|
||||
move_type (str): The type of move. "give", "traverse", etc.
|
||||
This is an arbitrary string provided to obj.move_to().
|
||||
Useful for altering messages or altering logic depending
|
||||
on the kind of movement.
|
||||
**kwargs (dict): Arbitrary, optional arguments for users
|
||||
overriding the call (unused by default).
|
||||
|
||||
|
|
@ -2381,7 +2411,7 @@ class DefaultCharacter(DefaultObject):
|
|||
# add the default cmdset
|
||||
self.cmdset.add_default(settings.CMDSET_CHARACTER, persistent=True)
|
||||
|
||||
def at_post_move(self, source_location, **kwargs):
|
||||
def at_post_move(self, source_location, move_type="move", **kwargs):
|
||||
"""
|
||||
We make sure to look around after a move.
|
||||
|
||||
|
|
@ -2648,9 +2678,9 @@ class ExitCommand(_COMMAND_DEFAULT_CLASS):
|
|||
|
||||
"""
|
||||
if self.obj.destination:
|
||||
return " (exit to %s)" % self.obj.destination.get_display_name(caller)
|
||||
return " (exit to %s)" % self.obj.destination.get_display_name(caller, **kwargs)
|
||||
else:
|
||||
return " (%s)" % self.obj.get_display_name(caller)
|
||||
return " (%s)" % self.obj.get_display_name(caller, **kwargs)
|
||||
|
||||
|
||||
#
|
||||
|
|
@ -2859,7 +2889,7 @@ class DefaultExit(DefaultObject):
|
|||
|
||||
"""
|
||||
source_location = traversing_object.location
|
||||
if traversing_object.move_to(target_location):
|
||||
if traversing_object.move_to(target_location, move_type="traverse"):
|
||||
self.at_post_traverse(traversing_object, source_location)
|
||||
else:
|
||||
if self.db.err_traverse:
|
||||
|
|
|
|||
|
|
@ -239,23 +239,32 @@ class SubAttributeProperty(AttributeProperty):
|
|||
class SubTagProperty(TagProperty):
|
||||
pass
|
||||
|
||||
class CustomizedProperty(AttributeProperty):
|
||||
def at_set(self, value, obj):
|
||||
obj.settest = value
|
||||
return value
|
||||
|
||||
def at_get(self, value, obj):
|
||||
return value + obj.awaretest
|
||||
|
||||
class TestObjectPropertiesClass(DefaultObject):
|
||||
attr1 = AttributeProperty(default="attr1")
|
||||
attr2 = AttributeProperty(default="attr2", category="attrcategory")
|
||||
attr3 = AttributeProperty(default="attr3", autocreate=False)
|
||||
attr4 = SubAttributeProperty(default="attr4")
|
||||
cusattr = CustomizedProperty(default=5)
|
||||
tag1 = TagProperty()
|
||||
tag2 = TagProperty(category="tagcategory")
|
||||
tag3 = SubTagProperty()
|
||||
testalias = AliasProperty()
|
||||
testperm = PermissionProperty()
|
||||
awaretest = 5
|
||||
settest = 0
|
||||
|
||||
@property
|
||||
def base_property(self):
|
||||
self.property_initialized = True
|
||||
|
||||
|
||||
class TestProperties(EvenniaTestCase):
|
||||
"""
|
||||
Test Properties.
|
||||
|
|
@ -263,7 +272,7 @@ class TestProperties(EvenniaTestCase):
|
|||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.obj = create.create_object(TestObjectPropertiesClass, key="testobj")
|
||||
self.obj: TestObjectPropertiesClass = create.create_object(TestObjectPropertiesClass, key="testobj")
|
||||
|
||||
def tearDown(self):
|
||||
self.obj.delete()
|
||||
|
|
@ -305,3 +314,30 @@ class TestProperties(EvenniaTestCase):
|
|||
# Verify that regular properties do not get fetched in init_evennia_properties,
|
||||
# only Attribute or TagProperties.
|
||||
self.assertFalse(hasattr(obj, "property_initialized"))
|
||||
|
||||
def test_object_awareness(self):
|
||||
'''Test the "object-awareness" of customized AttributeProperty getter/setters'''
|
||||
obj = self.obj
|
||||
|
||||
# attribute properties receive on obj ref in the getter/setter that can customize return
|
||||
self.assertEqual(obj.cusattr, 10)
|
||||
self.assertEqual(obj.settest, 5)
|
||||
obj.awaretest = 10
|
||||
self.assertEqual(obj.cusattr, 15)
|
||||
obj.cusattr = 10
|
||||
self.assertEqual(obj.cusattr, 20)
|
||||
self.assertEqual(obj.settest, 10)
|
||||
|
||||
# attribute value mutates if you do += or similar (combined get-set)
|
||||
obj.cusattr += 10
|
||||
self.assertEqual(obj.attributes.get("cusattr"), 30)
|
||||
self.assertEqual(obj.settest, 30)
|
||||
self.assertEqual(obj.cusattr, 40)
|
||||
obj.awaretest = 0
|
||||
obj.cusattr += 20
|
||||
self.assertEqual(obj.attributes.get("cusattr"), 50)
|
||||
self.assertEqual(obj.settest, 50)
|
||||
self.assertEqual(obj.cusattr, 50)
|
||||
del obj.cusattr
|
||||
self.assertEqual(obj.cusattr, 5)
|
||||
self.assertEqual(obj.settest, 5)
|
||||
|
|
|
|||
|
|
@ -528,10 +528,10 @@ def search_prototype(
|
|||
|
||||
"""
|
||||
# This will load the prototypes the first time they are searched
|
||||
loaded = getattr(load_module_prototypes, '_LOADED', False)
|
||||
loaded = getattr(load_module_prototypes, "_LOADED", False)
|
||||
if not loaded:
|
||||
load_module_prototypes()
|
||||
setattr(load_module_prototypes, '_LOADED', True)
|
||||
setattr(load_module_prototypes, "_LOADED", True)
|
||||
|
||||
# prototype keys are always in lowecase
|
||||
if key:
|
||||
|
|
|
|||
|
|
@ -2316,9 +2316,11 @@ def main():
|
|||
if option in ("makemessages", "compilemessages"):
|
||||
# some commands don't require the presence of a game directory to work
|
||||
need_gamedir = False
|
||||
if CURRENT_DIR != EVENNIA_LIB:
|
||||
print("You must stand in the evennia/evennia/ folder (where the 'locale/' "
|
||||
"folder is located) to run this command.")
|
||||
if CURRENT_DIR != EVENNIA_LIB:
|
||||
print(
|
||||
"You must stand in the evennia/evennia/ folder (where the 'locale/' "
|
||||
"folder is located) to run this command."
|
||||
)
|
||||
sys.exit()
|
||||
|
||||
if option in ("shell", "check", "makemigrations", "createsuperuser", "shell_plus"):
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ The client for sending data to the Evennia Game Index
|
|||
"""
|
||||
import urllib.request, urllib.parse, urllib.error
|
||||
import platform
|
||||
import warnings
|
||||
|
||||
import django
|
||||
from django.conf import settings
|
||||
|
|
|
|||
|
|
@ -226,6 +226,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, _BASE_SESSION_CLASS):
|
|||
or option == naws.NAWS
|
||||
or option == MCCP
|
||||
or option == mssp.MSSP
|
||||
or option == ECHO
|
||||
or option == suppress_ga.SUPPRESS_GA
|
||||
)
|
||||
|
||||
|
|
@ -236,6 +237,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, _BASE_SESSION_CLASS):
|
|||
or option == naws.NAWS
|
||||
or option == MCCP
|
||||
or option == mssp.MSSP
|
||||
or option == ECHO
|
||||
or option == suppress_ga.SUPPRESS_GA
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -423,6 +423,7 @@ class Evennia:
|
|||
logger.log_msg("Evennia Server successfully restarted in 'reset' mode.")
|
||||
elif mode == "shutdown":
|
||||
from evennia.objects.models import ObjectDB
|
||||
|
||||
self.at_server_cold_start()
|
||||
# clear eventual lingering session storages
|
||||
ObjectDB.objects.clear_all_sessids()
|
||||
|
|
|
|||
|
|
@ -269,7 +269,12 @@ class SessionHandler(dict):
|
|||
rkwargs[key] = [_validate(data), {}]
|
||||
else:
|
||||
rkwargs[key] = [[_validate(data)], {}]
|
||||
rkwargs[key][1]["options"] = options
|
||||
rkwargs[key][1]["options"] = dict(options)
|
||||
# make sure that any "prompt" message will be processed last
|
||||
# by moving it to the end
|
||||
if "prompt" in rkwargs:
|
||||
prompt = rkwargs.pop("prompt")
|
||||
rkwargs["prompt"] = prompt
|
||||
return rkwargs
|
||||
|
||||
|
||||
|
|
@ -683,7 +688,7 @@ class ServerSessionHandler(SessionHandler):
|
|||
Get a unique list of connected and logged-in Accounts.
|
||||
|
||||
Returns:
|
||||
accounts (list): All conected Accounts (which may be fewer than the
|
||||
accounts (list): All connected Accounts (which may be fewer than the
|
||||
amount of Sessions due to multi-playing).
|
||||
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -197,7 +197,6 @@ class TestServer(TestCase):
|
|||
|
||||
|
||||
class TestInitHooks(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
from evennia.utils import create
|
||||
|
|
|
|||
|
|
@ -16,15 +16,18 @@ class EvenniaTestSuiteRunner(DiscoverRunner):
|
|||
avoid running the large number of tests defined by Django
|
||||
|
||||
"""
|
||||
|
||||
def setup_test_environment(self, **kwargs):
|
||||
# the portal looping call starts before the unit-test suite so we
|
||||
# can't mock it - instead we stop it before starting the test - otherwise
|
||||
# we'd get unclean reactor errors across test boundaries.
|
||||
from evennia.server.portal.portal import PORTAL
|
||||
|
||||
PORTAL.maintenance_task.stop()
|
||||
|
||||
# initialize evennia itself
|
||||
import evennia
|
||||
|
||||
evennia._init()
|
||||
|
||||
from django.conf import settings
|
||||
|
|
@ -37,6 +40,7 @@ class EvenniaTestSuiteRunner(DiscoverRunner):
|
|||
|
||||
# remove testing flag after suite has run
|
||||
from django.conf import settings
|
||||
|
||||
settings._TEST_ENVIRONMENT = False
|
||||
|
||||
super().teardown_test_environment(**kwargs)
|
||||
|
|
|
|||
|
|
@ -224,7 +224,7 @@ class AttributeProperty:
|
|||
category=self._category,
|
||||
strattr=self._strattr,
|
||||
raise_exception=self._autocreate,
|
||||
))
|
||||
), instance)
|
||||
except AttributeError:
|
||||
if self._autocreate:
|
||||
# attribute didn't exist and autocreate is set
|
||||
|
|
@ -241,7 +241,7 @@ class AttributeProperty:
|
|||
(
|
||||
getattr(instance, self.attrhandler_name).add(
|
||||
self._key,
|
||||
self.at_set(value),
|
||||
self.at_set(value, instance),
|
||||
category=self._category,
|
||||
lockstring=self._lockstring,
|
||||
strattr=self._strattr,
|
||||
|
|
@ -257,13 +257,14 @@ class AttributeProperty:
|
|||
"""
|
||||
getattr(instance, self.attrhandler_name).remove(key=self._key, category=self._category)
|
||||
|
||||
def at_set(self, value):
|
||||
def at_set(self, value, obj):
|
||||
"""
|
||||
The value to set is passed through the method. It can be used to customize/validate
|
||||
the input in a custom child class.
|
||||
|
||||
Args:
|
||||
value (any): The value about to the stored in this Attribute.
|
||||
obj (object): Object the attribute is attached to
|
||||
|
||||
Returns:
|
||||
any: The value to store.
|
||||
|
|
@ -274,13 +275,14 @@ class AttributeProperty:
|
|||
"""
|
||||
return value
|
||||
|
||||
def at_get(self, value):
|
||||
def at_get(self, value, obj):
|
||||
"""
|
||||
The value returned from the Attribute is passed through this method. It can be used
|
||||
to react to the retrieval or modify the result in some way.
|
||||
|
||||
Args:
|
||||
value (any): Value returned from the Attribute.
|
||||
obj (object): Object the attribute is attached to
|
||||
|
||||
Returns:
|
||||
any: The value to return to the caller.
|
||||
|
|
|
|||
|
|
@ -286,7 +286,7 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager):
|
|||
categories = make_iter(category) if category else []
|
||||
n_keys = len(keys)
|
||||
n_categories = len(categories)
|
||||
unique_categories = sorted(set(categories))
|
||||
unique_categories = set(categories)
|
||||
n_unique_categories = len(unique_categories)
|
||||
|
||||
dbmodel = self.model.__dbclass__.__name__.lower()
|
||||
|
|
|
|||
|
|
@ -96,6 +96,7 @@ class Tag(models.Model):
|
|||
# Handlers making use of the Tags model
|
||||
#
|
||||
|
||||
|
||||
class TagProperty:
|
||||
"""
|
||||
Tag property descriptor. Allows for setting tags on an object as Django-like 'fields'
|
||||
|
|
@ -112,6 +113,7 @@ class TagProperty:
|
|||
mytag2 = TagProperty(category="tagcategory")
|
||||
|
||||
"""
|
||||
|
||||
taghandler_name = "tags"
|
||||
|
||||
def __init__(self, category=None, data=None):
|
||||
|
|
@ -134,10 +136,7 @@ class TagProperty:
|
|||
"""
|
||||
try:
|
||||
return getattr(instance, self.taghandler_name).get(
|
||||
key=self._key,
|
||||
category=self._category,
|
||||
return_list=False,
|
||||
raise_exception=True
|
||||
key=self._key, category=self._category, return_list=False, raise_exception=True
|
||||
)
|
||||
except AttributeError:
|
||||
self.__set__(instance, self._category)
|
||||
|
|
@ -150,9 +149,7 @@ class TagProperty:
|
|||
self._category = category
|
||||
(
|
||||
getattr(instance, self.taghandler_name).add(
|
||||
key=self._key,
|
||||
category=self._category,
|
||||
data=self._data
|
||||
key=self._key, category=self._category, data=self._data
|
||||
)
|
||||
)
|
||||
|
||||
|
|
@ -430,8 +427,15 @@ class TagHandler(object):
|
|||
|
||||
return ret[0] if len(ret) == 1 else ret
|
||||
|
||||
def get(self, key=None, default=None, category=None, return_tagobj=False, return_list=False,
|
||||
raise_exception=False):
|
||||
def get(
|
||||
self,
|
||||
key=None,
|
||||
default=None,
|
||||
category=None,
|
||||
return_tagobj=False,
|
||||
return_list=False,
|
||||
raise_exception=False,
|
||||
):
|
||||
"""
|
||||
Get the tag for the given key, category or combination of the two.
|
||||
|
||||
|
|
@ -613,6 +617,7 @@ class AliasProperty(TagProperty):
|
|||
bob = AliasProperty()
|
||||
|
||||
"""
|
||||
|
||||
taghandler_name = "aliases"
|
||||
|
||||
|
||||
|
|
@ -636,6 +641,7 @@ class PermissionProperty(TagProperty):
|
|||
myperm = PermissionProperty()
|
||||
|
||||
"""
|
||||
|
||||
taghandler_name = "permissions"
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -142,6 +142,15 @@ class TestTypedObjectManager(BaseEvenniaTest):
|
|||
[self.obj1],
|
||||
)
|
||||
|
||||
def test_get_tag_with_any_including_nones(self):
|
||||
self.obj1.tags.add("tagA", "categoryA")
|
||||
self.assertEqual(
|
||||
self._manager(
|
||||
"get_by_tag", ["tagA", "tagB"], ["categoryA", "categoryB", None], match="any"
|
||||
),
|
||||
[self.obj1],
|
||||
)
|
||||
|
||||
def test_get_tag_withnomatch(self):
|
||||
self.obj1.tags.add("tagC", "categoryC")
|
||||
self.assertEqual(
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ evennia.OPTION_CLASSES
|
|||
|
||||
|
||||
from pickle import dumps
|
||||
from django.db.utils import OperationalError, ProgrammingError
|
||||
from django.conf import settings
|
||||
from evennia.utils.utils import class_from_module, callables_from_module
|
||||
from evennia.utils import logger
|
||||
|
|
@ -167,7 +168,6 @@ class GlobalScriptContainer(Container):
|
|||
|
||||
# store a hash representation of the setup
|
||||
script.attributes.add("_global_script_settings", compare_hash, category="settings_hash")
|
||||
script.start()
|
||||
|
||||
return script
|
||||
|
||||
|
|
@ -183,9 +183,16 @@ class GlobalScriptContainer(Container):
|
|||
# populate self.typeclass_storage
|
||||
self.load_data()
|
||||
|
||||
# start registered scripts
|
||||
# make sure settings-defined scripts are loaded
|
||||
for key in self.loaded_data:
|
||||
self._load_script(key)
|
||||
# start all global scripts
|
||||
try:
|
||||
for script in self._get_scripts():
|
||||
script.start()
|
||||
except (OperationalError, ProgrammingError):
|
||||
# this can happen if db is not loaded yet (such as when building docs)
|
||||
pass
|
||||
|
||||
def load_data(self):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -18,19 +18,20 @@ in-situ, e.g `obj.db.mynestedlist[3][5] = 3` would never be saved and
|
|||
be out of sync with the database.
|
||||
|
||||
"""
|
||||
from collections import OrderedDict, defaultdict, deque
|
||||
from collections.abc import MutableMapping, MutableSequence, MutableSet
|
||||
from functools import update_wrapper
|
||||
from collections import deque, OrderedDict, defaultdict
|
||||
from collections.abc import MutableSequence, MutableSet, MutableMapping
|
||||
|
||||
try:
|
||||
from pickle import dumps, loads
|
||||
from pickle import UnpicklingError, dumps, loads
|
||||
except ImportError:
|
||||
from pickle import dumps, loads
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.utils.safestring import SafeString
|
||||
from evennia.utils.utils import uses_database, is_iter, to_bytes
|
||||
from evennia.utils import logger
|
||||
from evennia.utils.utils import is_iter, to_bytes, uses_database
|
||||
|
||||
__all__ = ("to_pickle", "from_pickle", "do_pickle", "do_unpickle", "dbserialize", "dbunserialize")
|
||||
|
||||
|
|
@ -239,6 +240,9 @@ class _SaverMutable(object):
|
|||
def __gt__(self, other):
|
||||
return self._data > other
|
||||
|
||||
def __or__(self, other):
|
||||
return self._data | other
|
||||
|
||||
@_save
|
||||
def __setitem__(self, key, value):
|
||||
self._data.__setitem__(key, self._convert_mutables(value))
|
||||
|
|
@ -450,7 +454,9 @@ def deserialize(obj):
|
|||
elif tname in ("_SaverOrderedDict", "OrderedDict"):
|
||||
return OrderedDict([(_iter(key), _iter(val)) for key, val in obj.items()])
|
||||
elif tname in ("_SaverDefaultDict", "defaultdict"):
|
||||
return defaultdict(obj.default_factory, {_iter(key): _iter(val) for key, val in obj.items()})
|
||||
return defaultdict(
|
||||
obj.default_factory, {_iter(key): _iter(val) for key, val in obj.items()}
|
||||
)
|
||||
elif tname in _DESERIALIZE_MAPPING:
|
||||
return _DESERIALIZE_MAPPING[tname](_iter(val) for val in obj)
|
||||
elif is_iter(obj):
|
||||
|
|
@ -602,7 +608,9 @@ def to_pickle(data):
|
|||
|
||||
def process_item(item):
|
||||
"""Recursive processor and identification of data"""
|
||||
|
||||
dtype = type(item)
|
||||
|
||||
if dtype in (str, int, float, bool, bytes, SafeString):
|
||||
return item
|
||||
elif dtype == tuple:
|
||||
|
|
@ -612,7 +620,10 @@ def to_pickle(data):
|
|||
elif dtype in (dict, _SaverDict):
|
||||
return dict((process_item(key), process_item(val)) for key, val in item.items())
|
||||
elif dtype in (defaultdict, _SaverDefaultDict):
|
||||
return defaultdict(item.default_factory, ((process_item(key), process_item(val)) for key, val in item.items()))
|
||||
return defaultdict(
|
||||
item.default_factory,
|
||||
((process_item(key), process_item(val)) for key, val in item.items()),
|
||||
)
|
||||
elif dtype in (set, _SaverSet):
|
||||
return set(process_item(val) for val in item)
|
||||
elif dtype in (OrderedDict, _SaverOrderedDict):
|
||||
|
|
@ -620,7 +631,20 @@ def to_pickle(data):
|
|||
elif dtype in (deque, _SaverDeque):
|
||||
return deque(process_item(val) for val in item)
|
||||
|
||||
elif hasattr(item, "__iter__"):
|
||||
# not one of the base types
|
||||
if hasattr(item, "__serialize_dbobjs__"):
|
||||
# Allows custom serialization of any dbobjects embedded in
|
||||
# the item that Evennia will otherwise not find (these would
|
||||
# otherwise lead to an error). Use the dbserialize helper from
|
||||
# this method.
|
||||
try:
|
||||
item.__serialize_dbobjs__()
|
||||
except TypeError as err:
|
||||
# we catch typerrors so we can handle both classes (requiring
|
||||
# classmethods) and instances
|
||||
pass
|
||||
|
||||
if hasattr(item, "__iter__"):
|
||||
# we try to conserve the iterable class, if not convert to list
|
||||
try:
|
||||
return item.__class__([process_item(val) for val in item])
|
||||
|
|
@ -678,7 +702,10 @@ def from_pickle(data, db_obj=None):
|
|||
elif dtype == dict:
|
||||
return dict((process_item(key), process_item(val)) for key, val in item.items())
|
||||
elif dtype == defaultdict:
|
||||
return defaultdict(item.default_factory, ((process_item(key), process_item(val)) for key, val in item.items()))
|
||||
return defaultdict(
|
||||
item.default_factory,
|
||||
((process_item(key), process_item(val)) for key, val in item.items()),
|
||||
)
|
||||
elif dtype == set:
|
||||
return set(process_item(val) for val in item)
|
||||
elif dtype == OrderedDict:
|
||||
|
|
@ -692,6 +719,22 @@ def from_pickle(data, db_obj=None):
|
|||
return item.__class__(process_item(val) for val in item)
|
||||
except (AttributeError, TypeError):
|
||||
return [process_item(val) for val in item]
|
||||
|
||||
if hasattr(item, "__deserialize_dbobjs__"):
|
||||
# this allows the object to custom-deserialize any embedded dbobjs
|
||||
# that we previously serialized with __serialize_dbobjs__.
|
||||
# use the dbunserialize helper in this module.
|
||||
try:
|
||||
item.__deserialize_dbobjs__()
|
||||
except (TypeError, UnpicklingError):
|
||||
# handle recoveries both of classes (requiring classmethods
|
||||
# or instances. Unpickling errors can happen when re-loading the
|
||||
# data from cache (because the hidden entity was already
|
||||
# deserialized and stored back on the object, unpickling it
|
||||
# again fails). TODO: Maybe one could avoid this retry in a
|
||||
# more graceful way?
|
||||
pass
|
||||
|
||||
return item
|
||||
|
||||
def process_tree(item, parent):
|
||||
|
|
@ -744,6 +787,13 @@ def from_pickle(data, db_obj=None):
|
|||
dat = _SaverList(_parent=parent)
|
||||
dat._data.extend(process_tree(val, dat) for val in item)
|
||||
return dat
|
||||
|
||||
if hasattr(item, "__deserialize_dbobjs__"):
|
||||
try:
|
||||
item.__deserialize_dbobjs__()
|
||||
except (TypeError, UnpicklingError):
|
||||
pass
|
||||
|
||||
return item
|
||||
|
||||
if db_obj:
|
||||
|
|
|
|||
|
|
@ -274,12 +274,13 @@ import inspect
|
|||
|
||||
from ast import literal_eval
|
||||
from fnmatch import fnmatch
|
||||
from math import ceil
|
||||
|
||||
from inspect import isfunction, getargspec
|
||||
from django.conf import settings
|
||||
from evennia import Command, CmdSet
|
||||
from evennia.utils import logger
|
||||
from evennia.utils.evtable import EvTable
|
||||
from evennia.utils.evtable import EvTable, EvColumn
|
||||
from evennia.utils.ansi import strip_ansi
|
||||
from evennia.utils.utils import mod_import, make_iter, pad, to_str, m_len, is_iter, dedent, crop
|
||||
from evennia.commands import cmdhandler
|
||||
|
|
@ -1210,7 +1211,6 @@ class EvMenu:
|
|||
Args:
|
||||
optionlist (list): List of (key, description) tuples for every
|
||||
option related to this node.
|
||||
caller (Object, Account or None, optional): The caller of the node.
|
||||
|
||||
Returns:
|
||||
options (str): The formatted option display.
|
||||
|
|
@ -1229,7 +1229,7 @@ class EvMenu:
|
|||
table = []
|
||||
for key, desc in optionlist:
|
||||
if key or desc:
|
||||
desc_string = ": %s" % desc if desc else ""
|
||||
desc_string = f": {desc}" if desc else ""
|
||||
table_width_max = max(
|
||||
table_width_max,
|
||||
max(m_len(p) for p in key.split("\n"))
|
||||
|
|
@ -1239,42 +1239,31 @@ class EvMenu:
|
|||
raw_key = strip_ansi(key)
|
||||
if raw_key != key:
|
||||
# already decorations in key definition
|
||||
table.append(" |lc%s|lt%s|le%s" % (raw_key, key, desc_string))
|
||||
table.append(f" |lc{raw_key}|lt{key}|le{desc_string}")
|
||||
else:
|
||||
# add a default white color to key
|
||||
table.append(" |lc%s|lt|w%s|n|le%s" % (raw_key, raw_key, desc_string))
|
||||
ncols = _MAX_TEXT_WIDTH // table_width_max # number of ncols
|
||||
table.append(f" |lc{raw_key}|lt|w{key}|n|le{desc_string}")
|
||||
ncols = _MAX_TEXT_WIDTH // table_width_max # number of columns
|
||||
|
||||
if ncols < 0:
|
||||
# no visible option at all
|
||||
# no visible options at all
|
||||
return ""
|
||||
|
||||
ncols = ncols + 1 if ncols == 0 else ncols
|
||||
# get the amount of rows needed (start with 4 rows)
|
||||
nrows = 4
|
||||
while nrows * ncols < nlist:
|
||||
nrows += 1
|
||||
ncols = nlist // nrows # number of full columns
|
||||
nlastcol = nlist % nrows # number of elements in last column
|
||||
ncols = 1 if ncols == 0 else ncols
|
||||
|
||||
# get the final column count
|
||||
ncols = ncols + 1 if nlastcol > 0 else ncols
|
||||
if ncols > 1:
|
||||
# only extend if longer than one column
|
||||
table.extend([" " for i in range(nrows - nlastcol)])
|
||||
# minimum number of rows in a column
|
||||
min_rows = 4
|
||||
|
||||
# build the actual table grid
|
||||
table = [table[icol * nrows : (icol * nrows) + nrows] for icol in range(0, ncols)]
|
||||
# split the items into columns
|
||||
split = max(min_rows, ceil(len(table) / ncols))
|
||||
max_end = len(table)
|
||||
cols_list = []
|
||||
for icol in range(ncols):
|
||||
start = icol * split
|
||||
end = min(start + split, max_end)
|
||||
cols_list.append(EvColumn(*table[start:end]))
|
||||
|
||||
# adjust the width of each column
|
||||
for icol in range(len(table)):
|
||||
col_width = (
|
||||
max(max(m_len(p) for p in part.split("\n")) for part in table[icol]) + colsep
|
||||
)
|
||||
table[icol] = [pad(part, width=col_width + colsep, align="l") for part in table[icol]]
|
||||
|
||||
# format the table into columns
|
||||
return str(EvTable(table=table, border="none"))
|
||||
return str(EvTable(table=cols_list, border="none"))
|
||||
|
||||
def node_formatter(self, nodetext, optionstext):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -181,7 +181,7 @@ class EvMore(object):
|
|||
justify (bool, optional): If set, auto-justify long lines. This must be turned
|
||||
off for fixed-width or formatted output, like tables. It's force-disabled
|
||||
if `inp` is an EvTable.
|
||||
justify_kwargs (dict, optional): Keywords for the justifiy function. Used only
|
||||
justify_kwargs (dict, optional): Keywords for the justify function. Used only
|
||||
if `justify` is True. If this is not set, default arguments will be used.
|
||||
exit_on_lastpage (bool, optional): If reaching the last page without the
|
||||
page being completely filled, exit pager immediately. If unset,
|
||||
|
|
@ -507,7 +507,7 @@ class EvMore(object):
|
|||
def page_formatter(self, page):
|
||||
"""
|
||||
Page formatter. Every page passes through this method. Override
|
||||
it to customize behvaior per-page. A common use is to generate a new
|
||||
it to customize behavior per-page. A common use is to generate a new
|
||||
EvTable for every page (this is more efficient than to generate one huge
|
||||
EvTable across many pages and feed it into EvMore all at once).
|
||||
|
||||
|
|
|
|||
|
|
@ -84,8 +84,8 @@ class _ParsedFunc:
|
|||
# state storage
|
||||
fullstr: str = ""
|
||||
infuncstr: str = ""
|
||||
single_quoted: bool = False
|
||||
double_quoted: bool = False
|
||||
single_quoted: int = -1
|
||||
double_quoted: int = -1
|
||||
current_kwarg: str = ""
|
||||
open_lparens: int = 0
|
||||
open_lsquate: int = 0
|
||||
|
|
@ -318,8 +318,8 @@ class FuncParser:
|
|||
# parsing state
|
||||
callstack = []
|
||||
|
||||
single_quoted = False
|
||||
double_quoted = False
|
||||
single_quoted = -1
|
||||
double_quoted = -1
|
||||
open_lparens = 0 # open (
|
||||
open_lsquare = 0 # open [
|
||||
open_lcurly = 0 # open {
|
||||
|
|
@ -330,6 +330,7 @@ class FuncParser:
|
|||
curr_func = None
|
||||
fullstr = "" # final string
|
||||
infuncstr = "" # string parts inside the current level of $funcdef (including $)
|
||||
literal_infuncstr = False
|
||||
|
||||
for char in string:
|
||||
|
||||
|
|
@ -373,12 +374,13 @@ class FuncParser:
|
|||
curr_func.open_lcurly = open_lcurly
|
||||
current_kwarg = ""
|
||||
infuncstr = ""
|
||||
single_quoted = False
|
||||
double_quoted = False
|
||||
single_quoted = -1
|
||||
double_quoted = -1
|
||||
open_lparens = 0
|
||||
open_lsquare = 0
|
||||
open_lcurly = 0
|
||||
exec_return = ""
|
||||
literal_infuncstr = False
|
||||
callstack.append(curr_func)
|
||||
|
||||
# start a new func
|
||||
|
|
@ -401,19 +403,41 @@ class FuncParser:
|
|||
infuncstr += str(exec_return)
|
||||
exec_return = ""
|
||||
|
||||
if char == "'": # note that this is the same as "\'"
|
||||
if char == "'" and double_quoted < 0: # note that this is the same as "\'"
|
||||
# a single quote - flip status
|
||||
single_quoted = not single_quoted
|
||||
infuncstr += char
|
||||
if single_quoted == 0:
|
||||
infuncstr = infuncstr[1:]
|
||||
single_quoted = -1
|
||||
elif single_quoted > 0:
|
||||
prefix = infuncstr[0:single_quoted]
|
||||
infuncstr = prefix + infuncstr[single_quoted + 1 :]
|
||||
single_quoted = -1
|
||||
else:
|
||||
infuncstr += char
|
||||
infuncstr = infuncstr.strip()
|
||||
single_quoted = len(infuncstr) - 1
|
||||
literal_infuncstr = True
|
||||
|
||||
continue
|
||||
|
||||
if char == '"': # note that this is the same as '\"'
|
||||
if char == '"' and single_quoted < 0: # note that this is the same as '\"'
|
||||
# a double quote = flip status
|
||||
double_quoted = not double_quoted
|
||||
infuncstr += char
|
||||
if double_quoted == 0:
|
||||
infuncstr = infuncstr[1:]
|
||||
double_quoted = -1
|
||||
elif double_quoted > 0:
|
||||
prefix = infuncstr[0:double_quoted]
|
||||
infuncstr = prefix + infuncstr[double_quoted + 1 :]
|
||||
double_quoted = -1
|
||||
else:
|
||||
infuncstr += char
|
||||
infuncstr = infuncstr.strip()
|
||||
double_quoted = len(infuncstr) - 1
|
||||
literal_infuncstr = True
|
||||
|
||||
continue
|
||||
|
||||
if double_quoted or single_quoted:
|
||||
if double_quoted >= 0 or single_quoted >= 0:
|
||||
# inside a string definition - this escapes everything else
|
||||
infuncstr += char
|
||||
continue
|
||||
|
|
@ -477,12 +501,15 @@ class FuncParser:
|
|||
else:
|
||||
curr_func.args.append(exec_return)
|
||||
else:
|
||||
if not literal_infuncstr:
|
||||
infuncstr = infuncstr.strip()
|
||||
|
||||
# store a string instead
|
||||
if current_kwarg:
|
||||
curr_func.kwargs[current_kwarg] = infuncstr.strip()
|
||||
elif infuncstr.strip():
|
||||
curr_func.kwargs[current_kwarg] = infuncstr
|
||||
elif literal_infuncstr or infuncstr.strip():
|
||||
# don't store the empty string
|
||||
curr_func.args.append(infuncstr.strip())
|
||||
curr_func.args.append(infuncstr)
|
||||
|
||||
# note that at this point either exec_return or infuncstr will
|
||||
# be empty. We need to store the full string so we can print
|
||||
|
|
@ -493,6 +520,7 @@ class FuncParser:
|
|||
current_kwarg = ""
|
||||
exec_return = ""
|
||||
infuncstr = ""
|
||||
literal_infuncstr = False
|
||||
|
||||
if char == ")":
|
||||
# closing the function list - this means we have a
|
||||
|
|
@ -536,6 +564,7 @@ class FuncParser:
|
|||
if return_str:
|
||||
exec_return = ""
|
||||
infuncstr = ""
|
||||
literal_infuncstr = False
|
||||
continue
|
||||
|
||||
infuncstr += char
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ class TimeScript(DefaultScript):
|
|||
callback(*args, **kwargs)
|
||||
|
||||
seconds = real_seconds_until(**self.db.gametime)
|
||||
self.restart(interval=seconds)
|
||||
self.start(interval=seconds, force_restart=True)
|
||||
|
||||
|
||||
# Access functions
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ def _log(msg, logfunc, prefix="", **kwargs):
|
|||
|
||||
# log call functions (each has legacy aliases)
|
||||
|
||||
|
||||
def log_info(msg, **kwargs):
|
||||
"""
|
||||
Logs any generic debugging/informative info that should appear in the log.
|
||||
|
|
@ -62,6 +63,7 @@ def log_info(msg, **kwargs):
|
|||
"""
|
||||
_log(msg, log.info, **kwargs)
|
||||
|
||||
|
||||
info = log_info
|
||||
log_infomsg = log_info
|
||||
log_msg = log_info
|
||||
|
|
@ -79,6 +81,7 @@ def log_warn(msg, **kwargs):
|
|||
"""
|
||||
_log(msg, log.warn, **kwargs)
|
||||
|
||||
|
||||
warn = log_warn
|
||||
warning = log_warn
|
||||
log_warnmsg = log_warn
|
||||
|
|
@ -120,6 +123,7 @@ def log_trace(msg=None, **kwargs):
|
|||
if msg:
|
||||
_log(msg, log.error, prefix="!!", **kwargs)
|
||||
|
||||
|
||||
log_tracemsg = log_trace
|
||||
exception = log_trace
|
||||
critical = log_trace
|
||||
|
|
@ -156,6 +160,7 @@ def log_sec(msg, **kwargs):
|
|||
"""
|
||||
_log(msg, log.info, prefix="SS", **kwargs)
|
||||
|
||||
|
||||
sec = log_sec
|
||||
security = log_sec
|
||||
log_secmsg = log_sec
|
||||
|
|
@ -174,12 +179,12 @@ def log_server(msg, **kwargs):
|
|||
_log(msg, log.info, prefix="Server", **kwargs)
|
||||
|
||||
|
||||
|
||||
class GetLogObserver:
|
||||
"""
|
||||
Sets up how the system logs are formatted.
|
||||
|
||||
"""
|
||||
|
||||
component_prefix = ""
|
||||
event_levels = {
|
||||
twisted_logger.LogLevel.debug: "??",
|
||||
|
|
@ -207,8 +212,7 @@ class GetLogObserver:
|
|||
event["log_format"] = str(event.get("log_format", ""))
|
||||
component_prefix = self.component_prefix or ""
|
||||
log_msg = twisted_logger.formatEventAsClassicLogText(
|
||||
event,
|
||||
formatTime=lambda e: twisted_logger.formatTime(e, _TIME_FORMAT)
|
||||
event, formatTime=lambda e: twisted_logger.formatTime(e, _TIME_FORMAT)
|
||||
)
|
||||
return f"{component_prefix}{log_msg}"
|
||||
|
||||
|
|
@ -218,14 +222,15 @@ class GetLogObserver:
|
|||
|
||||
# Called by server/portal on startup
|
||||
|
||||
|
||||
class GetPortalLogObserver(GetLogObserver):
|
||||
component_prefix = "|Portal| "
|
||||
|
||||
|
||||
class GetServerLogObserver(GetLogObserver):
|
||||
component_prefix = ""
|
||||
|
||||
|
||||
|
||||
# logging overrides
|
||||
|
||||
|
||||
|
|
@ -352,6 +357,7 @@ class WeeklyLogFile(logfile.DailyLogFile):
|
|||
self.lastDate = max(self.lastDate, self.toDate())
|
||||
self.size += len(data)
|
||||
|
||||
|
||||
# Arbitrary file logger
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -96,7 +96,6 @@ DEFAULT_SETTING_RESETS = dict(
|
|||
"evennia.game_template.server.conf.prototypefuncs",
|
||||
],
|
||||
BASE_GUEST_TYPECLASS="evennia.accounts.accounts.DefaultGuest",
|
||||
|
||||
# a special setting boolean _TEST_ENVIRONMENT is set by the test runner
|
||||
# while the test suite is running.
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,10 +2,11 @@
|
|||
Tests for dbserialize module
|
||||
"""
|
||||
|
||||
from collections import deque
|
||||
from collections import defaultdict, deque
|
||||
|
||||
from django.test import TestCase
|
||||
from evennia.utils import dbserialize
|
||||
from evennia.objects.objects import DefaultObject
|
||||
from evennia.utils import dbserialize
|
||||
from parameterized import parameterized
|
||||
|
||||
|
||||
|
|
@ -15,9 +16,7 @@ class TestDbSerialize(TestCase):
|
|||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.obj = DefaultObject(
|
||||
db_key="Tester",
|
||||
)
|
||||
self.obj = DefaultObject(db_key="Tester")
|
||||
self.obj.save()
|
||||
|
||||
def test_constants(self):
|
||||
|
|
@ -62,10 +61,12 @@ class TestDbSerialize(TestCase):
|
|||
self.obj.db.test.sort(key=lambda d: str(d))
|
||||
self.assertEqual(self.obj.db.test, [{0: 1}, {1: 0}])
|
||||
|
||||
def test_dict(self):
|
||||
def test_saverdict(self):
|
||||
self.obj.db.test = {"a": True}
|
||||
self.obj.db.test.update({"b": False})
|
||||
self.assertEqual(self.obj.db.test, {"a": True, "b": False})
|
||||
self.obj.db.test |= {"c": 5}
|
||||
self.assertEqual(self.obj.db.test, {"a": True, "b": False, "c": 5})
|
||||
|
||||
@parameterized.expand(
|
||||
[
|
||||
|
|
@ -88,27 +89,119 @@ class TestDbSerialize(TestCase):
|
|||
self.assertIsInstance(value, base_type)
|
||||
self.assertNotIsInstance(value, saver_type)
|
||||
self.assertEqual(value, default_value)
|
||||
self.obj.db.test = {'a': True}
|
||||
self.obj.db.test.update({'b': False})
|
||||
self.assertEqual(self.obj.db.test, {'a': True, 'b': False})
|
||||
self.obj.db.test = {"a": True}
|
||||
self.obj.db.test.update({"b": False})
|
||||
self.assertEqual(self.obj.db.test, {"a": True, "b": False})
|
||||
|
||||
def test_defaultdict(self):
|
||||
from collections import defaultdict
|
||||
# baseline behavior for a defaultdict
|
||||
_dd = defaultdict(list)
|
||||
_dd['a']
|
||||
self.assertEqual(_dd, {'a': []})
|
||||
_dd["a"]
|
||||
self.assertEqual(_dd, {"a": []})
|
||||
|
||||
# behavior after defaultdict is set as attribute
|
||||
|
||||
dd = defaultdict(list)
|
||||
self.obj.db.test = dd
|
||||
self.obj.db.test['a']
|
||||
self.assertEqual(self.obj.db.test, {'a': []})
|
||||
self.obj.db.test["a"]
|
||||
self.assertEqual(self.obj.db.test, {"a": []})
|
||||
|
||||
self.obj.db.test['a'].append(1)
|
||||
self.assertEqual(self.obj.db.test, {'a': [1]})
|
||||
self.obj.db.test['a'].append(2)
|
||||
self.assertEqual(self.obj.db.test, {'a': [1, 2]})
|
||||
self.obj.db.test['a'].append(3)
|
||||
self.assertEqual(self.obj.db.test, {'a': [1, 2, 3]})
|
||||
self.obj.db.test["a"].append(1)
|
||||
self.assertEqual(self.obj.db.test, {"a": [1]})
|
||||
self.obj.db.test["a"].append(2)
|
||||
self.assertEqual(self.obj.db.test, {"a": [1, 2]})
|
||||
self.obj.db.test["a"].append(3)
|
||||
self.assertEqual(self.obj.db.test, {"a": [1, 2, 3]})
|
||||
self.obj.db.test |= {"b": [5, 6]}
|
||||
self.assertEqual(self.obj.db.test, {"a": [1, 2, 3], "b": [5, 6]})
|
||||
|
||||
|
||||
class _InvalidContainer:
|
||||
"""Container not saveable in Attribute (if obj is dbobj, it 'hides' it)"""
|
||||
|
||||
def __init__(self, obj):
|
||||
self.hidden_obj = obj
|
||||
|
||||
|
||||
class _ValidContainer(_InvalidContainer):
|
||||
"""Container possible to save in Attribute (handles hidden dbobj explicitly)"""
|
||||
|
||||
def __serialize_dbobjs__(self):
|
||||
self.hidden_obj = dbserialize.dbserialize(self.hidden_obj)
|
||||
|
||||
def __deserialize_dbobjs__(self):
|
||||
self.hidden_obj = dbserialize.dbunserialize(self.hidden_obj)
|
||||
|
||||
|
||||
class DbObjWrappers(TestCase):
|
||||
"""
|
||||
Test the `__serialize_dbobjs__` and `__deserialize_dbobjs__` methods.
|
||||
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.dbobj1 = DefaultObject(db_key="Tester1")
|
||||
self.dbobj1.save()
|
||||
self.dbobj2 = DefaultObject(db_key="Tester2")
|
||||
self.dbobj2.save()
|
||||
|
||||
def test_dbobj_hidden_obj__fail(self):
|
||||
with self.assertRaises(TypeError):
|
||||
self.dbobj1.db.testarg = _InvalidContainer(self.dbobj1)
|
||||
|
||||
def test_consecutive_fetch(self):
|
||||
con = _ValidContainer(self.dbobj2)
|
||||
self.dbobj1.db.testarg = con
|
||||
attrobj = self.dbobj1.attributes.get("testarg", return_obj=True)
|
||||
|
||||
self.assertEqual(attrobj.value, con)
|
||||
self.assertEqual(attrobj.value, con)
|
||||
self.assertEqual(attrobj.value.hidden_obj, self.dbobj2)
|
||||
|
||||
def test_dbobj_hidden_obj__success(self):
|
||||
con = _ValidContainer(self.dbobj2)
|
||||
self.dbobj1.db.testarg = con
|
||||
|
||||
# accessing the same data multiple times
|
||||
res1 = self.dbobj1.db.testarg
|
||||
res2 = self.dbobj1.db.testarg
|
||||
res3 = self.dbobj1.db.testarg
|
||||
|
||||
self.assertEqual(res1, res2)
|
||||
self.assertEqual(res1, res3)
|
||||
self.assertEqual(res1, con)
|
||||
self.assertEqual(res2, con)
|
||||
self.assertEqual(res1.hidden_obj, self.dbobj2)
|
||||
self.assertEqual(res2.hidden_obj, self.dbobj2)
|
||||
self.assertEqual(res3.hidden_obj, self.dbobj2)
|
||||
|
||||
def test_dbobj_hidden_dict(self):
|
||||
con1 = _ValidContainer(self.dbobj2)
|
||||
con2 = _ValidContainer(self.dbobj2)
|
||||
|
||||
self.dbobj1.db.dict = {}
|
||||
|
||||
self.dbobj1.db.dict["key1"] = con1
|
||||
self.dbobj1.db.dict["key2"] = con2
|
||||
|
||||
self.assertEqual(self.dbobj1.db.dict["key1"].hidden_obj, self.dbobj2)
|
||||
self.assertEqual(self.dbobj1.db.dict["key1"].hidden_obj, self.dbobj2)
|
||||
self.assertEqual(self.dbobj1.db.dict["key2"].hidden_obj, self.dbobj2)
|
||||
self.assertEqual(self.dbobj1.db.dict["key2"].hidden_obj, self.dbobj2)
|
||||
|
||||
def test_dbobj_hidden_defaultdict(self):
|
||||
|
||||
con1 = _ValidContainer(self.dbobj2)
|
||||
con2 = _ValidContainer(self.dbobj2)
|
||||
|
||||
self.dbobj1.db.dfdict = defaultdict(dict)
|
||||
|
||||
self.dbobj1.db.dfdict["key"]["con1"] = con1
|
||||
self.dbobj1.db.dfdict["key"]["con2"] = con2
|
||||
|
||||
self.assertEqual(self.dbobj1.db.dfdict["key"]["con1"].hidden_obj, self.dbobj2)
|
||||
|
||||
self.assertEqual(self.dbobj1.db.dfdict["key"]["con1"].hidden_obj, self.dbobj2)
|
||||
self.assertEqual(self.dbobj1.db.dfdict["key"]["con2"].hidden_obj, self.dbobj2)
|
||||
self.assertEqual(self.dbobj1.db.dfdict["key"]["con2"].hidden_obj, self.dbobj2)
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ def _double_callable(*args, **kwargs):
|
|||
def _eval_callable(*args, **kwargs):
|
||||
if args:
|
||||
return simple_eval(args[0])
|
||||
|
||||
return ""
|
||||
|
||||
|
||||
|
|
@ -113,25 +114,25 @@ class TestFuncParser(TestCase):
|
|||
("$foo() Test noargs5", "_test() Test noargs5"),
|
||||
("Test args1 $foo(a,b,c)", "Test args1 _test(a, b, c)"),
|
||||
("Test args2 $bar(foo, bar, too)", "Test args2 _test(foo, bar, too)"),
|
||||
("Test args3 $bar(foo, bar, ' too')", "Test args3 _test(foo, bar, ' too')"),
|
||||
("Test args4 $foo('')", "Test args4 _test('')"),
|
||||
('Test args4 $foo("")', 'Test args4 _test("")'),
|
||||
(r"Test args3 $bar(foo, bar, ' too')", "Test args3 _test(foo, bar, too)"),
|
||||
("Test args4 $foo('')", "Test args4 _test()"),
|
||||
('Test args4 $foo("")', "Test args4 _test()"),
|
||||
("Test args5 $foo(\(\))", "Test args5 _test(())"),
|
||||
("Test args6 $foo(\()", "Test args6 _test(()"),
|
||||
("Test args7 $foo(())", "Test args7 _test(())"),
|
||||
("Test args8 $foo())", "Test args8 _test())"),
|
||||
("Test args9 $foo(=)", "Test args9 _test(=)"),
|
||||
("Test args10 $foo(\,)", "Test args10 _test(,)"),
|
||||
("Test args10 $foo(',')", "Test args10 _test(',')"),
|
||||
("Test args10 $foo(',')", "Test args10 _test(,)"),
|
||||
("Test args11 $foo(()", "Test args11 $foo(()"), # invalid syntax
|
||||
(
|
||||
"Test kwarg1 $bar(foo=1, bar='foo', too=ere)",
|
||||
"Test kwarg1 _test(foo=1, bar='foo', too=ere)",
|
||||
"Test kwarg1 _test(foo=1, bar=foo, too=ere)",
|
||||
),
|
||||
("Test kwarg2 $bar(foo,bar,too=ere)", "Test kwarg2 _test(foo, bar, too=ere)"),
|
||||
("test kwarg3 $foo(foo = bar, bar = ere )", "test kwarg3 _test(foo=bar, bar=ere)"),
|
||||
(
|
||||
"test kwarg4 $foo(foo =' bar ',\" bar \"= ere )",
|
||||
r"test kwarg4 $foo(foo =\' bar \',\" bar \"= ere )",
|
||||
"test kwarg4 _test(foo=' bar ', \" bar \"=ere)",
|
||||
),
|
||||
(
|
||||
|
|
@ -180,22 +181,29 @@ class TestFuncParser(TestCase):
|
|||
("Test clr $clr(r, This is a red string!)", "Test clr |rThis is a red string!|n"),
|
||||
("Test eval1 $eval(21 + 21 - 10)", "Test eval1 32"),
|
||||
("Test eval2 $eval((21 + 21) / 2)", "Test eval2 21.0"),
|
||||
("Test eval3 $eval('21' + 'foo' + 'bar')", "Test eval3 21foobar"),
|
||||
("Test eval4 $eval('21' + '$repl()' + '' + str(10 // 2))", "Test eval4 21rr5"),
|
||||
("Test eval5 $eval('21' + '\$repl()' + '' + str(10 // 2))", "Test eval5 21$repl()5"),
|
||||
("Test eval6 $eval('$repl(a)' + '$repl(b)')", "Test eval6 rarrbr"),
|
||||
("Test eval3 $eval(\"'21' + 'foo' + 'bar'\")", "Test eval3 21foobar"),
|
||||
(r"Test eval4 $eval(\'21\' + \'$repl()\' + \"''\" + str(10 // 2))", "Test eval4 21rr5"),
|
||||
(
|
||||
r"Test eval5 $eval(\'21\' + \'\$repl()\' + \'\' + str(10 // 2))",
|
||||
"Test eval5 21$repl()5",
|
||||
),
|
||||
("Test eval6 $eval(\"'$repl(a)' + '$repl(b)'\")", "Test eval6 rarrbr"),
|
||||
("Test type1 $typ([1,2,3,4])", "Test type1 <class 'list'>"),
|
||||
("Test type2 $typ((1,2,3,4))", "Test type2 <class 'tuple'>"),
|
||||
("Test type3 $typ({1,2,3,4})", "Test type3 <class 'set'>"),
|
||||
("Test type4 $typ({1:2,3:4})", "Test type4 <class 'dict'>"),
|
||||
("Test type5 $typ(1), $typ(1.0)", "Test type5 <class 'int'>, <class 'float'>"),
|
||||
("Test type6 $typ('1'), $typ(\"1.0\")", "Test type6 <class 'str'>, <class 'str'>"),
|
||||
(
|
||||
"Test type6 $typ(\"'1'\"), $typ('\"1.0\"')",
|
||||
"Test type6 <class 'str'>, <class 'str'>",
|
||||
),
|
||||
("Test add1 $add(1, 2)", "Test add1 3"),
|
||||
("Test add2 $add([1,2,3,4], [5,6])", "Test add2 [1, 2, 3, 4, 5, 6]"),
|
||||
("Test literal1 $sum($lit([1,2,3,4,5,6]))", "Test literal1 21"),
|
||||
("Test literal2 $typ($lit(1))", "Test literal2 <class 'int'>"),
|
||||
("Test literal3 $typ($lit(1)aaa)", "Test literal3 <class 'str'>"),
|
||||
("Test literal4 $typ(aaa$lit(1))", "Test literal4 <class 'str'>"),
|
||||
("Test spider's thread", "Test spider's thread"),
|
||||
]
|
||||
)
|
||||
def test_parse(self, string, expected):
|
||||
|
|
@ -258,7 +266,11 @@ class TestFuncParser(TestCase):
|
|||
self.assertEqual([1, 2, 3, 4], ret)
|
||||
self.assertTrue(isinstance(ret, list))
|
||||
|
||||
ret = self.parser.parse_to_any("$lit('')")
|
||||
ret = self.parser.parse_to_any("$lit(\"''\")")
|
||||
self.assertEqual("", ret)
|
||||
self.assertTrue(isinstance(ret, str))
|
||||
|
||||
ret = self.parser.parse_to_any(r"$lit(\'\')")
|
||||
self.assertEqual("", ret)
|
||||
self.assertTrue(isinstance(ret, str))
|
||||
|
||||
|
|
@ -390,7 +402,8 @@ class TestDefaultCallables(TestCase):
|
|||
("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"),
|
||||
("Some $eval(\"'-'*20\")Hello", "Some --------------------Hello"),
|
||||
('$crop("spider\'s silk", 5)', "spide"),
|
||||
]
|
||||
)
|
||||
def test_other_callables(self, string, expected):
|
||||
|
|
@ -455,15 +468,16 @@ class TestDefaultCallables(TestCase):
|
|||
self.parser.parse(
|
||||
"this should be $pad('''escaped,''' and '''instead,''' cropped $crop(with a long,5) text., 80)"
|
||||
),
|
||||
"this should be '''escaped,''' and '''instead,''' cropped with text. ",
|
||||
"this should be escaped, and instead, cropped with text. ",
|
||||
)
|
||||
|
||||
def test_escaped2(self):
|
||||
raw_str = 'this should be $pad("""escaped,""" and """instead,""" cropped $crop(with a long,5) text., 80)'
|
||||
expected = "this should be escaped, and instead, cropped with text. "
|
||||
result = self.parser.parse(raw_str)
|
||||
self.assertEqual(
|
||||
self.parser.parse(
|
||||
'this should be $pad("""escaped,""" and """instead,""" cropped $crop(with a long,5) text., 80)'
|
||||
),
|
||||
'this should be """escaped,""" and """instead,""" cropped with text. ',
|
||||
result,
|
||||
expected,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
64
evennia/utils/tests/test_search.py
Normal file
64
evennia/utils/tests/test_search.py
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
from evennia.scripts.scripts import DefaultScript
|
||||
from evennia.utils.test_resources import EvenniaTest
|
||||
from evennia.utils.search import search_script_attribute, search_script_tag, search_script
|
||||
|
||||
class TestSearch(EvenniaTest):
|
||||
|
||||
def test_search_script_tag(self):
|
||||
"""Check that a script can be found by its tag."""
|
||||
script, errors = DefaultScript.create("a-script")
|
||||
script.tags.add("a-tag")
|
||||
found = search_script_tag("a-tag")
|
||||
self.assertEqual(len(found), 1, errors)
|
||||
self.assertEqual(script.key, found[0].key, errors)
|
||||
|
||||
def test_search_script_tag_category(self):
|
||||
"""Check that a script can be found by its tag and category."""
|
||||
script, errors = DefaultScript.create("a-script")
|
||||
script.tags.add("a-tag", category="a-category")
|
||||
found = search_script_tag("a-tag", category="a-category")
|
||||
self.assertEqual(len(found), 1, errors)
|
||||
self.assertEqual(script.key, found[0].key, errors)
|
||||
|
||||
def test_search_script_tag_wrong_category(self):
|
||||
"""Check that a script cannot be found by the wrong category."""
|
||||
script, errors = DefaultScript.create("a-script")
|
||||
script.tags.add("a-tag", category="a-category")
|
||||
found = search_script_tag("a-tag", category="wrong-category")
|
||||
self.assertEqual(len(found), 0, errors)
|
||||
|
||||
def test_search_script_tag_wrong(self):
|
||||
"""Check that a script cannot be found by the wrong tag."""
|
||||
script, errors = DefaultScript.create("a-script")
|
||||
script.tags.add("a-tag", category="a-category")
|
||||
found = search_script_tag("wrong-tag", category="a-category")
|
||||
self.assertEqual(len(found), 0, errors)
|
||||
|
||||
def test_search_script_attribute(self):
|
||||
"""Check that a script can be found by its attributes."""
|
||||
script, errors = DefaultScript.create("a-script")
|
||||
script.db.an_attribute = "some value"
|
||||
found = search_script_attribute(key="an_attribute", value="some value")
|
||||
self.assertEqual(len(found), 1, errors)
|
||||
self.assertEqual(script.key, found[0].key, errors)
|
||||
|
||||
def test_search_script_attribute_wrong(self):
|
||||
"""Check that a script cannot be found by wrong value of its attributes."""
|
||||
script, errors = DefaultScript.create("a-script")
|
||||
script.db.an_attribute = "some value"
|
||||
found = search_script_attribute(key="an_attribute", value="wrong value")
|
||||
self.assertEqual(len(found), 0, errors)
|
||||
|
||||
def test_search_script_key(self):
|
||||
"""Check that a script can be found by its key value."""
|
||||
script, errors = DefaultScript.create("a-script")
|
||||
found = search_script("a-script")
|
||||
self.assertEqual(len(found), 1, errors)
|
||||
self.assertEqual(script.key, found[0].key, errors)
|
||||
|
||||
def test_search_script_wrong_key(self):
|
||||
"""Check that a script cannot be found by a wrong key value."""
|
||||
script, errors = DefaultScript.create("a-script")
|
||||
found = search_script("wrong_key")
|
||||
self.assertEqual(len(found), 0, errors)
|
||||
|
||||
|
|
@ -7,20 +7,22 @@ import mock
|
|||
|
||||
|
||||
class TestText2Html(TestCase):
|
||||
def test_re_color(self):
|
||||
def test_format_styles(self):
|
||||
parser = text2html.HTML_PARSER
|
||||
self.assertEqual("foo", parser.re_color("foo"))
|
||||
self.assertEqual("foo", parser.format_styles("foo"))
|
||||
self.assertEqual(
|
||||
'<span class="color-001">red</span>foo',
|
||||
parser.re_color(ansi.ANSI_UNHILITE + ansi.ANSI_RED + "red" + ansi.ANSI_NORMAL + "foo"),
|
||||
parser.format_styles(
|
||||
ansi.ANSI_UNHILITE + ansi.ANSI_RED + "red" + ansi.ANSI_NORMAL + "foo"
|
||||
),
|
||||
)
|
||||
self.assertEqual(
|
||||
'<span class="bgcolor-001">red</span>foo',
|
||||
parser.re_color(ansi.ANSI_BACK_RED + "red" + ansi.ANSI_NORMAL + "foo"),
|
||||
parser.format_styles(ansi.ANSI_BACK_RED + "red" + ansi.ANSI_NORMAL + "foo"),
|
||||
)
|
||||
self.assertEqual(
|
||||
'<span class="bgcolor-001"><span class="color-002">red</span></span>foo',
|
||||
parser.re_color(
|
||||
'<span class="bgcolor-001 color-002">red</span>foo',
|
||||
parser.format_styles(
|
||||
ansi.ANSI_BACK_RED
|
||||
+ ansi.ANSI_UNHILITE
|
||||
+ ansi.ANSI_GREEN
|
||||
|
|
@ -29,75 +31,25 @@ class TestText2Html(TestCase):
|
|||
+ "foo"
|
||||
),
|
||||
)
|
||||
|
||||
@unittest.skip("parser issues")
|
||||
def test_re_bold(self):
|
||||
parser = text2html.HTML_PARSER
|
||||
self.assertEqual("foo", parser.re_bold("foo"))
|
||||
self.assertEqual(
|
||||
# "a <strong>red</strong>foo", # TODO: why not?
|
||||
"a <strong>redfoo</strong>",
|
||||
parser.re_bold("a " + ansi.ANSI_HILITE + "red" + ansi.ANSI_UNHILITE + "foo"),
|
||||
'a <span class="underline">red</span>foo',
|
||||
parser.format_styles("a " + ansi.ANSI_UNDERLINE + "red" + ansi.ANSI_NORMAL + "foo"),
|
||||
)
|
||||
self.assertEqual(
|
||||
'a <span class="blink">red</span>foo',
|
||||
parser.format_styles("a " + ansi.ANSI_BLINK + "red" + ansi.ANSI_NORMAL + "foo"),
|
||||
)
|
||||
self.assertEqual(
|
||||
'a <span class="bgcolor-007 color-000">red</span>foo',
|
||||
parser.format_styles("a " + ansi.ANSI_INVERSE + "red" + ansi.ANSI_NORMAL + "foo"),
|
||||
)
|
||||
|
||||
@unittest.skip("parser issues")
|
||||
def test_re_underline(self):
|
||||
parser = text2html.HTML_PARSER
|
||||
self.assertEqual("foo", parser.re_underline("foo"))
|
||||
self.assertEqual(
|
||||
'a <span class="underline">red</span>' + ansi.ANSI_NORMAL + "foo",
|
||||
parser.re_underline(
|
||||
"a "
|
||||
+ ansi.ANSI_UNDERLINE
|
||||
+ "red"
|
||||
+ ansi.ANSI_NORMAL # TODO: why does it keep it?
|
||||
+ "foo"
|
||||
),
|
||||
)
|
||||
|
||||
@unittest.skip("parser issues")
|
||||
def test_re_blinking(self):
|
||||
parser = text2html.HTML_PARSER
|
||||
self.assertEqual("foo", parser.re_blinking("foo"))
|
||||
self.assertEqual(
|
||||
'a <span class="blink">red</span>' + ansi.ANSI_NORMAL + "foo",
|
||||
parser.re_blinking(
|
||||
"a "
|
||||
+ ansi.ANSI_BLINK
|
||||
+ "red"
|
||||
+ ansi.ANSI_NORMAL # TODO: why does it keep it?
|
||||
+ "foo"
|
||||
),
|
||||
)
|
||||
|
||||
@unittest.skip("parser issues")
|
||||
def test_re_inversing(self):
|
||||
parser = text2html.HTML_PARSER
|
||||
self.assertEqual("foo", parser.re_inversing("foo"))
|
||||
self.assertEqual(
|
||||
'a <span class="inverse">red</span>' + ansi.ANSI_NORMAL + "foo",
|
||||
parser.re_inversing(
|
||||
"a "
|
||||
+ ansi.ANSI_INVERSE
|
||||
+ "red"
|
||||
+ ansi.ANSI_NORMAL # TODO: why does it keep it?
|
||||
+ "foo"
|
||||
),
|
||||
)
|
||||
|
||||
@unittest.skip("parser issues")
|
||||
def test_remove_bells(self):
|
||||
parser = text2html.HTML_PARSER
|
||||
self.assertEqual("foo", parser.remove_bells("foo"))
|
||||
self.assertEqual(
|
||||
"a red" + ansi.ANSI_NORMAL + "foo",
|
||||
parser.remove_bells(
|
||||
"a "
|
||||
+ ansi.ANSI_BEEP
|
||||
+ "red"
|
||||
+ ansi.ANSI_NORMAL # TODO: why does it keep it?
|
||||
+ "foo"
|
||||
),
|
||||
parser.remove_bells("a " + ansi.ANSI_BEEP + "red" + ansi.ANSI_NORMAL + "foo"),
|
||||
)
|
||||
|
||||
def test_remove_backspaces(self):
|
||||
|
|
@ -110,7 +62,6 @@ class TestText2Html(TestCase):
|
|||
self.assertEqual("foo", parser.convert_linebreaks("foo"))
|
||||
self.assertEqual("a<br> redfoo<br>", parser.convert_linebreaks("a\n redfoo\n"))
|
||||
|
||||
@unittest.skip("parser issues")
|
||||
def test_convert_urls(self):
|
||||
parser = text2html.HTML_PARSER
|
||||
self.assertEqual("foo", parser.convert_urls("foo"))
|
||||
|
|
@ -118,7 +69,6 @@ class TestText2Html(TestCase):
|
|||
'a <a href="http://redfoo" target="_blank">http://redfoo</a> runs',
|
||||
parser.convert_urls("a http://redfoo runs"),
|
||||
)
|
||||
# TODO: doesn't URL encode correctly
|
||||
|
||||
def test_sub_mxp_links(self):
|
||||
parser = text2html.HTML_PARSER
|
||||
|
|
@ -186,22 +136,22 @@ class TestText2Html(TestCase):
|
|||
self.assertEqual("foo", text2html.parse_html("foo"))
|
||||
self.maxDiff = None
|
||||
self.assertEqual(
|
||||
# TODO: note that the blink is currently *not* correctly aborted
|
||||
# with |n here! This is probably not possible to correctly handle
|
||||
# with regex - a stateful parser may be needed.
|
||||
# blink back-cyan normal underline red green yellow blue magenta cyan back-green
|
||||
text2html.parse_html("|^|[CHello|n|u|rW|go|yr|bl|md|c!|[G!"),
|
||||
'<span class="blink">'
|
||||
'<span class="bgcolor-006">Hello</span>' # noqa
|
||||
'<span class="underline">'
|
||||
'<span class="color-009">W</span>' # noqa
|
||||
'<span class="color-010">o</span>'
|
||||
'<span class="color-011">r</span>'
|
||||
'<span class="color-012">l</span>'
|
||||
'<span class="color-013">d</span>'
|
||||
'<span class="color-014">!'
|
||||
'<span class="bgcolor-002">!</span>' # noqa
|
||||
"</span>"
|
||||
"</span>"
|
||||
'<span class="blink bgcolor-006">'
|
||||
"Hello"
|
||||
'</span><span class="underline color-009">'
|
||||
"W"
|
||||
'</span><span class="underline color-010">'
|
||||
"o"
|
||||
'</span><span class="underline color-011">'
|
||||
"r"
|
||||
'</span><span class="underline color-012">'
|
||||
"l"
|
||||
'</span><span class="underline color-013">'
|
||||
"d"
|
||||
'</span><span class="underline color-014">'
|
||||
"!"
|
||||
'</span><span class="underline bgcolor-002 color-014">'
|
||||
"!"
|
||||
"</span>",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -57,21 +57,30 @@ class TestDedent(TestCase):
|
|||
class TestListToString(TestCase):
|
||||
"""
|
||||
Default function header from utils.py:
|
||||
list_to_string(inlist, endsep="and", addquote=False)
|
||||
list_to_string(inlist, sep=",", endsep=", and", addquote=False)
|
||||
|
||||
Examples:
|
||||
no endsep:
|
||||
with defaults:
|
||||
[1,2,3] -> '1, 2, and 3'
|
||||
with endsep==',':
|
||||
[1,2,3] -> '1, 2, 3'
|
||||
with sep==';' and endsep==';':
|
||||
[1,2,3] -> '1; 2; 3'
|
||||
with endsep=='and':
|
||||
[1,2,3] -> '1, 2 and 3'
|
||||
with addquote and endsep
|
||||
with endsep=='':
|
||||
[1,2,3] -> '1, 2 3'
|
||||
with addquote and endsep="and"
|
||||
[1,2,3] -> '"1", "2" and "3"'
|
||||
"""
|
||||
|
||||
def test_list_to_string(self):
|
||||
self.assertEqual("1, 2, 3", utils.list_to_string([1, 2, 3], endsep=""))
|
||||
self.assertEqual('"1", "2", "3"', utils.list_to_string([1, 2, 3], endsep="", addquote=True))
|
||||
self.assertEqual("1, 2, and 3", utils.list_to_string([1, 2, 3]))
|
||||
self.assertEqual("1, 2, 3", utils.list_to_string([1, 2, 3], endsep=","))
|
||||
self.assertEqual("1, 2 and 3", utils.list_to_string([1, 2, 3], endsep="and"))
|
||||
self.assertEqual("1, 2 3", utils.list_to_string([1, 2, 3], endsep=""))
|
||||
self.assertEqual("1; 2; 3", utils.list_to_string([1, 2, 3], sep=";", endsep=";"))
|
||||
self.assertEqual('"1", "2", "3"', utils.list_to_string([1, 2, 3], endsep=",", addquote=True))
|
||||
self.assertEqual(
|
||||
'"1", "2" and "3"', utils.list_to_string([1, 2, 3], endsep="and", addquote=True)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -12,11 +12,10 @@ import re
|
|||
from html import escape as html_escape
|
||||
from .ansi import *
|
||||
|
||||
|
||||
# All xterm256 RGB equivalents
|
||||
|
||||
XTERM256_FG = "\033[38;5;%sm"
|
||||
XTERM256_BG = "\033[48;5;%sm"
|
||||
XTERM256_FG = "\033[38;5;{}m"
|
||||
XTERM256_BG = "\033[48;5;{}m"
|
||||
|
||||
|
||||
class TextToHTMLparser(object):
|
||||
|
|
@ -25,77 +24,65 @@ class TextToHTMLparser(object):
|
|||
"""
|
||||
|
||||
tabstop = 4
|
||||
# mapping html color name <-> ansi code.
|
||||
hilite = ANSI_HILITE
|
||||
unhilite = ANSI_UNHILITE # this will be stripped - there is no css equivalent.
|
||||
normal = ANSI_NORMAL # "
|
||||
underline = ANSI_UNDERLINE
|
||||
blink = ANSI_BLINK
|
||||
inverse = ANSI_INVERSE # this will produce an outline; no obvious css equivalent?
|
||||
colorcodes = [
|
||||
("color-000", unhilite + ANSI_BLACK), # pure black
|
||||
("color-001", unhilite + ANSI_RED),
|
||||
("color-002", unhilite + ANSI_GREEN),
|
||||
("color-003", unhilite + ANSI_YELLOW),
|
||||
("color-004", unhilite + ANSI_BLUE),
|
||||
("color-005", unhilite + ANSI_MAGENTA),
|
||||
("color-006", unhilite + ANSI_CYAN),
|
||||
("color-007", unhilite + ANSI_WHITE), # light grey
|
||||
("color-008", hilite + ANSI_BLACK), # dark grey
|
||||
("color-009", hilite + ANSI_RED),
|
||||
("color-010", hilite + ANSI_GREEN),
|
||||
("color-011", hilite + ANSI_YELLOW),
|
||||
("color-012", hilite + ANSI_BLUE),
|
||||
("color-013", hilite + ANSI_MAGENTA),
|
||||
("color-014", hilite + ANSI_CYAN),
|
||||
("color-015", hilite + ANSI_WHITE), # pure white
|
||||
] + [("color-%03i" % (i + 16), XTERM256_FG % ("%i" % (i + 16))) for i in range(240)]
|
||||
|
||||
colorback = [
|
||||
("bgcolor-000", ANSI_BACK_BLACK), # pure black
|
||||
("bgcolor-001", ANSI_BACK_RED),
|
||||
("bgcolor-002", ANSI_BACK_GREEN),
|
||||
("bgcolor-003", ANSI_BACK_YELLOW),
|
||||
("bgcolor-004", ANSI_BACK_BLUE),
|
||||
("bgcolor-005", ANSI_BACK_MAGENTA),
|
||||
("bgcolor-006", ANSI_BACK_CYAN),
|
||||
("bgcolor-007", ANSI_BACK_WHITE), # light grey
|
||||
("bgcolor-008", hilite + ANSI_BACK_BLACK), # dark grey
|
||||
("bgcolor-009", hilite + ANSI_BACK_RED),
|
||||
("bgcolor-010", hilite + ANSI_BACK_GREEN),
|
||||
("bgcolor-011", hilite + ANSI_BACK_YELLOW),
|
||||
("bgcolor-012", hilite + ANSI_BACK_BLUE),
|
||||
("bgcolor-013", hilite + ANSI_BACK_MAGENTA),
|
||||
("bgcolor-014", hilite + ANSI_BACK_CYAN),
|
||||
("bgcolor-015", hilite + ANSI_BACK_WHITE), # pure white
|
||||
] + [("bgcolor-%03i" % (i + 16), XTERM256_BG % ("%i" % (i + 16))) for i in range(240)]
|
||||
style_codes = [
|
||||
# non-color style markers
|
||||
ANSI_NORMAL,
|
||||
ANSI_UNDERLINE,
|
||||
ANSI_HILITE,
|
||||
ANSI_UNHILITE,
|
||||
ANSI_INVERSE,
|
||||
ANSI_BLINK,
|
||||
ANSI_INV_HILITE,
|
||||
ANSI_BLINK_HILITE,
|
||||
ANSI_INV_BLINK,
|
||||
ANSI_INV_BLINK_HILITE,
|
||||
]
|
||||
|
||||
# make sure to escape [
|
||||
# colorcodes = [(c, code.replace("[", r"\[")) for c, code in colorcodes]
|
||||
# colorback = [(c, code.replace("[", r"\[")) for c, code in colorback]
|
||||
fg_colormap = dict((code, clr) for clr, code in colorcodes)
|
||||
bg_colormap = dict((code, clr) for clr, code in colorback)
|
||||
ansi_color_codes = [
|
||||
# Foreground colors
|
||||
ANSI_BLACK,
|
||||
ANSI_RED,
|
||||
ANSI_GREEN,
|
||||
ANSI_YELLOW,
|
||||
ANSI_BLUE,
|
||||
ANSI_MAGENTA,
|
||||
ANSI_CYAN,
|
||||
ANSI_WHITE,
|
||||
]
|
||||
|
||||
# create stop markers
|
||||
fgstop = "(?:\033\[1m|\033\[22m){0,1}\033\[3[0-8].*?m|\033\[0m|$"
|
||||
bgstop = "(?:\033\[1m|\033\[22m){0,1}\033\[4[0-8].*?m|\033\[0m|$"
|
||||
bgfgstop = bgstop[:-2] + fgstop
|
||||
xterm_fg_codes = [XTERM256_FG.format(i + 16) for i in range(240)]
|
||||
|
||||
fgstart = "((?:\033\[1m|\033\[22m){0,1}\033\[3[0-8].*?m)"
|
||||
bgstart = "((?:\033\[1m|\033\[22m){0,1}\033\[4[0-8].*?m)"
|
||||
bgfgstart = bgstart + r"((?:\033\[1m|\033\[22m){0,1}\033\[[3-4][0-8].*?m){0,1}"
|
||||
ansi_bg_codes = [
|
||||
# Background colors
|
||||
ANSI_BACK_BLACK,
|
||||
ANSI_BACK_RED,
|
||||
ANSI_BACK_GREEN,
|
||||
ANSI_BACK_YELLOW,
|
||||
ANSI_BACK_BLUE,
|
||||
ANSI_BACK_MAGENTA,
|
||||
ANSI_BACK_CYAN,
|
||||
ANSI_BACK_WHITE,
|
||||
]
|
||||
|
||||
# extract color markers, tagging the start marker and the text marked
|
||||
re_fgs = re.compile(fgstart + "(.*?)(?=" + fgstop + ")")
|
||||
re_bgs = re.compile(bgstart + "(.*?)(?=" + bgstop + ")")
|
||||
re_bgfg = re.compile(bgfgstart + "(.*?)(?=" + bgfgstop + ")")
|
||||
xterm_bg_codes = [XTERM256_BG.format(i + 16) for i in range(240)]
|
||||
|
||||
re_style = re.compile(
|
||||
r"({})".format(
|
||||
"|".join(
|
||||
style_codes + ansi_color_codes + xterm_fg_codes + ansi_bg_codes + xterm_bg_codes
|
||||
).replace("[", r"\[")
|
||||
)
|
||||
)
|
||||
|
||||
colorlist = (
|
||||
[ANSI_UNHILITE + code for code in ansi_color_codes]
|
||||
+ [ANSI_HILITE + code for code in ansi_color_codes]
|
||||
+ xterm_fg_codes
|
||||
)
|
||||
|
||||
bglist = ansi_bg_codes + [ANSI_HILITE + code for code in ansi_bg_codes] + xterm_bg_codes
|
||||
|
||||
re_normal = re.compile(normal.replace("[", r"\["))
|
||||
re_hilite = re.compile("(?:%s)(.*)(?=%s|%s)" % (hilite.replace("[", r"\["), fgstop, bgstop))
|
||||
re_unhilite = re.compile("(?:%s)(.*)(?=%s|%s)" % (unhilite.replace("[", r"\["), fgstop, bgstop))
|
||||
re_uline = re.compile("(?:%s)(.*?)(?=%s|%s)" % (underline.replace("[", r"\["), fgstop, bgstop))
|
||||
re_blink = re.compile("(?:%s)(.*?)(?=%s|%s)" % (blink.replace("[", r"\["), fgstop, bgstop))
|
||||
re_inverse = re.compile("(?:%s)(.*?)(?=%s|%s)" % (inverse.replace("[", r"\["), fgstop, bgstop))
|
||||
re_string = re.compile(
|
||||
r"(?P<htmlchars>[<&>])|(?P<tab>[\t]+)|(?P<lineend>\r\n|\r|\n)",
|
||||
re.S | re.M | re.I,
|
||||
|
|
@ -106,100 +93,6 @@ class TextToHTMLparser(object):
|
|||
re_mxplink = re.compile(r"\|lc(.*?)\|lt(.*?)\|le", re.DOTALL)
|
||||
re_mxpurl = re.compile(r"\|lu(.*?)\|lt(.*?)\|le", re.DOTALL)
|
||||
|
||||
def _sub_bgfg(self, colormatch):
|
||||
# print("colormatch.groups()", colormatch.groups())
|
||||
bgcode, fgcode, text = colormatch.groups()
|
||||
if not fgcode:
|
||||
ret = r"""<span class="%s">%s</span>""" % (
|
||||
self.bg_colormap.get(bgcode, self.fg_colormap.get(bgcode, "err")),
|
||||
text,
|
||||
)
|
||||
else:
|
||||
ret = r"""<span class="%s"><span class="%s">%s</span></span>""" % (
|
||||
self.bg_colormap.get(bgcode, self.fg_colormap.get(bgcode, "err")),
|
||||
self.fg_colormap.get(fgcode, self.bg_colormap.get(fgcode, "err")),
|
||||
text,
|
||||
)
|
||||
return ret
|
||||
|
||||
def _sub_fg(self, colormatch):
|
||||
code, text = colormatch.groups()
|
||||
return r"""<span class="%s">%s</span>""" % (self.fg_colormap.get(code, "err"), text)
|
||||
|
||||
def _sub_bg(self, colormatch):
|
||||
code, text = colormatch.groups()
|
||||
return r"""<span class="%s">%s</span>""" % (self.bg_colormap.get(code, "err"), text)
|
||||
|
||||
def re_color(self, text):
|
||||
"""
|
||||
Replace ansi colors with html color class names. Let the
|
||||
client choose how it will display colors, if it wishes to.
|
||||
|
||||
Args:
|
||||
text (str): the string with color to replace.
|
||||
|
||||
Returns:
|
||||
text (str): Re-colored text.
|
||||
|
||||
"""
|
||||
text = self.re_bgfg.sub(self._sub_bgfg, text)
|
||||
text = self.re_fgs.sub(self._sub_fg, text)
|
||||
text = self.re_bgs.sub(self._sub_bg, text)
|
||||
text = self.re_normal.sub("", text)
|
||||
return text
|
||||
|
||||
def re_bold(self, text):
|
||||
"""
|
||||
Clean out superfluous hilights rather than set <strong>to make
|
||||
it match the look of telnet.
|
||||
|
||||
Args:
|
||||
text (str): Text to process.
|
||||
|
||||
Returns:
|
||||
text (str): Processed text.
|
||||
|
||||
"""
|
||||
text = self.re_hilite.sub(r"<strong>\1</strong>", text)
|
||||
return self.re_unhilite.sub(r"\1", text) # strip unhilite - there is no equivalent in css.
|
||||
|
||||
def re_underline(self, text):
|
||||
"""
|
||||
Replace ansi underline with html underline class name.
|
||||
|
||||
Args:
|
||||
text (str): Text to process.
|
||||
|
||||
Returns:
|
||||
text (str): Processed text.
|
||||
|
||||
"""
|
||||
return self.re_uline.sub(r'<span class="underline">\1</span>', text)
|
||||
|
||||
def re_blinking(self, text):
|
||||
"""
|
||||
Replace ansi blink with custom blink css class
|
||||
|
||||
Args:
|
||||
text (str): Text to process.
|
||||
|
||||
Returns:
|
||||
text (str): Processed text.
|
||||
"""
|
||||
return self.re_blink.sub(r'<span class="blink">\1</span>', text)
|
||||
|
||||
def re_inversing(self, text):
|
||||
"""
|
||||
Replace ansi inverse with custom inverse css class
|
||||
|
||||
Args:
|
||||
text (str): Text to process.
|
||||
|
||||
Returns:
|
||||
text (str): Processed text.
|
||||
"""
|
||||
return self.re_inverse.sub(r'<span class="inverse">\1</span>', text)
|
||||
|
||||
def remove_bells(self, text):
|
||||
"""
|
||||
Remove ansi specials
|
||||
|
|
@ -211,7 +104,7 @@ class TextToHTMLparser(object):
|
|||
text (str): Processed text.
|
||||
|
||||
"""
|
||||
return text.replace("\07", "")
|
||||
return text.replace(ANSI_BEEP, "")
|
||||
|
||||
def remove_backspaces(self, text):
|
||||
"""
|
||||
|
|
@ -315,6 +208,128 @@ class TextToHTMLparser(object):
|
|||
return text
|
||||
return None
|
||||
|
||||
def format_styles(self, text):
|
||||
"""
|
||||
Takes a string with parsed ANSI codes and replaces them with
|
||||
HTML spans and CSS classes.
|
||||
|
||||
Args:
|
||||
text (str): The string to process.
|
||||
|
||||
Returns:
|
||||
text (str): Processed text.
|
||||
"""
|
||||
|
||||
# split out the ANSI codes and clean out any empty items
|
||||
str_list = [substr for substr in self.re_style.split(text) if substr]
|
||||
# initialize all the flags and classes
|
||||
classes = []
|
||||
clean = True
|
||||
inverse = False
|
||||
# default color is light grey - unhilite + white
|
||||
hilight = ANSI_UNHILITE
|
||||
fg = ANSI_WHITE
|
||||
# default bg is black
|
||||
bg = ANSI_BACK_BLACK
|
||||
|
||||
for i, substr in enumerate(str_list):
|
||||
# reset all current styling
|
||||
if substr == ANSI_NORMAL:
|
||||
# close any existing span if necessary
|
||||
str_list[i] = "</span>" if not clean else ""
|
||||
# reset to defaults
|
||||
classes = []
|
||||
clean = True
|
||||
inverse = False
|
||||
hilight = ANSI_UNHILITE
|
||||
fg = ANSI_WHITE
|
||||
bg = ANSI_BACK_BLACK
|
||||
|
||||
# change color
|
||||
elif substr in self.ansi_color_codes + self.xterm_fg_codes:
|
||||
# erase ANSI code from output
|
||||
str_list[i] = ""
|
||||
# set new color
|
||||
fg = substr
|
||||
|
||||
# change bg color
|
||||
elif substr in self.ansi_bg_codes + self.xterm_bg_codes:
|
||||
# erase ANSI code from output
|
||||
str_list[i] = ""
|
||||
# set new bg
|
||||
bg = substr
|
||||
|
||||
# non-color codes
|
||||
elif substr in self.style_codes:
|
||||
# erase ANSI code from output
|
||||
str_list[i] = ""
|
||||
|
||||
# hilight codes
|
||||
if substr in (ANSI_HILITE, ANSI_UNHILITE, ANSI_INV_HILITE, ANSI_INV_BLINK_HILITE):
|
||||
# set new hilight status
|
||||
hilight = ANSI_UNHILITE if substr == ANSI_UNHILITE else ANSI_HILITE
|
||||
|
||||
# inversion codes
|
||||
if substr in (ANSI_INVERSE, ANSI_INV_HILITE, ANSI_INV_BLINK_HILITE):
|
||||
inverse = True
|
||||
|
||||
# blink codes
|
||||
if (
|
||||
substr in (ANSI_BLINK, ANSI_BLINK_HILITE, ANSI_INV_BLINK_HILITE)
|
||||
and "blink" not in classes
|
||||
):
|
||||
classes.append("blink")
|
||||
|
||||
# underline
|
||||
if substr == ANSI_UNDERLINE and "underline" not in classes:
|
||||
classes.append("underline")
|
||||
|
||||
else:
|
||||
# normal text, add text back to list
|
||||
if not str_list[i - 1]:
|
||||
# prior entry was cleared, which means style change
|
||||
# get indices for the fg and bg codes
|
||||
bg_index = self.bglist.index(bg)
|
||||
try:
|
||||
color_index = self.colorlist.index(hilight + fg)
|
||||
except ValueError:
|
||||
# xterm256 colors don't have the hilight codes
|
||||
color_index = self.colorlist.index(fg)
|
||||
|
||||
if inverse:
|
||||
# inverse means swap fg and bg indices
|
||||
bg_class = "bgcolor-{}".format(str(color_index).rjust(3, "0"))
|
||||
color_class = "color-{}".format(str(bg_index).rjust(3, "0"))
|
||||
else:
|
||||
# use fg and bg indices for classes
|
||||
bg_class = "bgcolor-{}".format(str(bg_index).rjust(3, "0"))
|
||||
color_class = "color-{}".format(str(color_index).rjust(3, "0"))
|
||||
|
||||
# black bg is the default, don't explicitly style
|
||||
if bg_class != "bgcolor-000":
|
||||
classes.append(bg_class)
|
||||
# light grey text is the default, don't explicitly style
|
||||
if color_class != "color-007":
|
||||
classes.append(color_class)
|
||||
# define the new style span
|
||||
prefix = '<span class="{}">'.format(" ".join(classes))
|
||||
# close any prior span
|
||||
if not clean:
|
||||
prefix = "</span>" + prefix
|
||||
# add span to output
|
||||
str_list[i - 1] = prefix
|
||||
|
||||
# clean out color classes to easily update next time
|
||||
classes = [cls for cls in classes if "color" not in cls]
|
||||
# flag as currently being styled
|
||||
clean = False
|
||||
|
||||
# close span if necessary
|
||||
if not clean:
|
||||
str_list.append("</span>")
|
||||
# recombine back into string
|
||||
return "".join(str_list)
|
||||
|
||||
def parse(self, text, strip_ansi=False):
|
||||
"""
|
||||
Main access function, converts a text containing ANSI codes
|
||||
|
|
@ -328,19 +343,14 @@ class TextToHTMLparser(object):
|
|||
text (str): Parsed text.
|
||||
|
||||
"""
|
||||
# print(f"incoming text:\n{text}")
|
||||
# parse everything to ansi first
|
||||
text = parse_ansi(text, strip_ansi=strip_ansi, xterm256=True, mxp=True)
|
||||
# convert all ansi to html
|
||||
result = re.sub(self.re_string, self.sub_text, text)
|
||||
result = re.sub(self.re_mxplink, self.sub_mxp_links, result)
|
||||
result = re.sub(self.re_mxpurl, self.sub_mxp_urls, result)
|
||||
result = self.re_color(result)
|
||||
result = self.re_bold(result)
|
||||
result = self.re_underline(result)
|
||||
result = self.re_blinking(result)
|
||||
result = self.re_inversing(result)
|
||||
result = self.remove_bells(result)
|
||||
result = self.format_styles(result)
|
||||
result = self.convert_linebreaks(result)
|
||||
result = self.remove_backspaces(result)
|
||||
result = self.convert_urls(result)
|
||||
|
|
|
|||
|
|
@ -362,7 +362,7 @@ def columnize(string, columns=2, spacing=4, align="l", width=None):
|
|||
return "\n".join(rows)
|
||||
|
||||
|
||||
def iter_to_str(iterable, endsep=", and", addquote=False):
|
||||
def iter_to_str(iterable, sep=",", endsep=", and", addquote=False):
|
||||
"""
|
||||
This pretty-formats an iterable list as string output, adding an optional
|
||||
alternative separator to the second to last entry. If `addquote`
|
||||
|
|
@ -372,8 +372,8 @@ def iter_to_str(iterable, endsep=", and", addquote=False):
|
|||
iterable (any): Usually an iterable to print. Each element must be possible to
|
||||
present with a string. Note that if this is a generator, it will be
|
||||
consumed by this operation.
|
||||
endsep (str, optional): If set, the last item separator will
|
||||
be replaced with this value.
|
||||
sep (str, optional): The string to use as a separator for each item in the iterable.
|
||||
endsep (str, optional): The last item separator will be replaced with this value.
|
||||
addquote (bool, optional): This will surround all outgoing
|
||||
values with double quotes.
|
||||
|
||||
|
|
@ -381,17 +381,20 @@ def iter_to_str(iterable, endsep=", and", addquote=False):
|
|||
str: The list represented as a string.
|
||||
|
||||
Notes:
|
||||
Default is to use 'Oxford comma', like 1, 2, 3, and 4. To remove, give
|
||||
`endsep` as just `and`.
|
||||
Default is to use 'Oxford comma', like 1, 2, 3, and 4.
|
||||
|
||||
Examples:
|
||||
|
||||
```python
|
||||
>>> list_to_string([1,2,3], endsep='')
|
||||
>>> list_to_string([1,2,3], endsep=',')
|
||||
'1, 2, 3'
|
||||
>>> list_to_string([1,2,3], endsep='')
|
||||
'1, 2 3'
|
||||
>>> list_to_string([1,2,3], ensdep='and')
|
||||
'1, 2 and 3'
|
||||
>>> list_to_string([1,2,3], endsep=', and', addquote=True)
|
||||
>>> list_to_string([1,2,3], sep=';', endsep=';')
|
||||
'1; 2; 3'
|
||||
>>> list_to_string([1,2,3], addquote=True)
|
||||
'"1", "2", and "3"'
|
||||
```
|
||||
|
||||
|
|
@ -406,22 +409,19 @@ def iter_to_str(iterable, endsep=", and", addquote=False):
|
|||
else:
|
||||
iterable = tuple(str(val) for val in iterable)
|
||||
|
||||
if endsep.startswith(","):
|
||||
if endsep.startswith(sep):
|
||||
# oxford comma alternative
|
||||
endsep = endsep[1:] if len_iter < 3 else endsep
|
||||
elif endsep:
|
||||
# normal space-separated end separator
|
||||
endsep = " " + str(endsep).strip()
|
||||
else:
|
||||
# no separator given - use comma
|
||||
endsep = ","
|
||||
|
||||
if len_iter == 1:
|
||||
return str(iterable[0])
|
||||
elif len_iter == 2:
|
||||
return f"{endsep} ".join(str(v) for v in iterable)
|
||||
else:
|
||||
return ", ".join(str(v) for v in iterable[:-1]) + f"{endsep} {iterable[-1]}"
|
||||
return f"{sep} ".join(str(v) for v in iterable[:-1]) + f"{endsep} {iterable[-1]}"
|
||||
|
||||
|
||||
# legacy aliases
|
||||
|
|
@ -819,7 +819,7 @@ def latinify(string, default="?", pure_ascii=False):
|
|||
This is used as a last resort when normal encoding does not work.
|
||||
|
||||
Arguments:
|
||||
string (str): A string to convert to 'safe characters' convertable
|
||||
string (str): A string to convert to 'safe characters' convertible
|
||||
to an latin-1 bytestring later.
|
||||
default (str, optional): Characters resisting mapping will be replaced
|
||||
with this character or string. The intent is to apply an encode operation
|
||||
|
|
@ -1078,7 +1078,7 @@ def delay(timedelay, callback, *args, **kwargs):
|
|||
Keep in mind that persistent tasks arguments and callback should not
|
||||
use memory references.
|
||||
If persistent is set to True the delay function will return an int
|
||||
which is the task's id itended for use with TASK_HANDLER's do_task
|
||||
which is the task's id intended for use with TASK_HANDLER's do_task
|
||||
and remove methods.
|
||||
All persistent tasks whose time delays have passed will be called on server startup.
|
||||
|
||||
|
|
@ -1531,12 +1531,12 @@ def class_from_module(path, defaultpaths=None, fallback=None):
|
|||
defaultpaths (iterable, optional): If a direct import from `path` fails,
|
||||
try subsequent imports by prepending those paths to `path`.
|
||||
fallback (str): If all other attempts fail, use this path as a fallback.
|
||||
This is intended as a last-resport. In the example of Evennia
|
||||
This is intended as a last-resort. In the example of Evennia
|
||||
loading, this would be a path to a default parent class in the
|
||||
evennia repo itself.
|
||||
|
||||
Returns:
|
||||
class (Class): An uninstatiated class recovered from path.
|
||||
class (Class): An uninstantiated class recovered from path.
|
||||
|
||||
Raises:
|
||||
ImportError: If all loading failed.
|
||||
|
|
@ -1675,7 +1675,7 @@ def string_partial_matching(alternatives, inp, ret_index=True):
|
|||
Matching is made from the start of each subword in each
|
||||
alternative. Case is not important. So e.g. "bi sh sw" or just
|
||||
"big" or "shiny" or "sw" will match "Big shiny sword". Scoring is
|
||||
done to allow to separate by most common demoninator. You will get
|
||||
done to allow to separate by most common denominator. You will get
|
||||
multiple matches returned if appropriate.
|
||||
|
||||
Args:
|
||||
|
|
@ -1749,7 +1749,7 @@ def format_table(table, extra_space=1):
|
|||
|
||||
ftable = format_table([[1,2,3], [4,5,6]])
|
||||
string = ""
|
||||
for ir, row in enumarate(ftable):
|
||||
for ir, row in enumerate(ftable):
|
||||
if ir == 0:
|
||||
# make first row white
|
||||
string += "\\n|w" + "".join(row) + "|n"
|
||||
|
|
@ -2695,6 +2695,7 @@ def copy_word_case(base_word, new_word):
|
|||
+ excess
|
||||
)
|
||||
|
||||
|
||||
def run_in_main_thread(function_or_method, *args, **kwargs):
|
||||
"""
|
||||
Force a callable to execute in the main Evennia thread. This is only relevant when
|
||||
|
|
|
|||
|
|
@ -149,7 +149,7 @@ An "emitter" object must have a function
|
|||
// kwargs (obj): keyword-args to listener
|
||||
//
|
||||
emit: function (cmdname, args, kwargs) {
|
||||
if (kwargs.cmdid) {
|
||||
if (kwargs.cmdid && (kwargs.cmdid in cmdmap)) {
|
||||
cmdmap[kwargs.cmdid].apply(this, [args, kwargs]);
|
||||
delete cmdmap[kwargs.cmdid];
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue