Merge branch 'master' of https://github.com/trhr/evennia into aws-s3-cdn

This commit is contained in:
trhr 2020-02-28 20:31:15 -06:00
commit b4cdbbc710
5 changed files with 94 additions and 9 deletions

View file

@ -6,6 +6,7 @@ All commands in Evennia inherit from the 'Command' class in this module.
""" """
import re import re
import math import math
import inspect
from django.conf import settings from django.conf import settings
@ -74,6 +75,13 @@ def _init_command(cls, **kwargs):
cls.is_exit = False cls.is_exit = False
if not hasattr(cls, "help_category"): if not hasattr(cls, "help_category"):
cls.help_category = "general" cls.help_category = "general"
# make sure to pick up the parent's docstring if the child class is
# missing one (important for auto-help)
if cls.__doc__ is None:
for parent_class in inspect.getmro(cls):
if parent_class.__doc__ is not None:
cls.__doc__ = parent_class.__doc__
break
cls.help_category = cls.help_category.lower() cls.help_category = cls.help_category.lower()

View file

@ -19,6 +19,7 @@ from evennia.utils.eveditor import EvEditor
from evennia.utils.evmore import EvMore from evennia.utils.evmore import EvMore
from evennia.prototypes import spawner, prototypes as protlib, menus as olc_menus from evennia.prototypes import spawner, prototypes as protlib, menus as olc_menus
from evennia.utils.ansi import raw from evennia.utils.ansi import raw
from evennia.prototypes.menus import _format_diff_text_and_options
COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS) COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)
@ -1912,8 +1913,8 @@ class CmdTypeclass(COMMAND_DEFAULT_CLASS):
Usage: Usage:
typeclass[/switch] <object> [= typeclass.path] typeclass[/switch] <object> [= typeclass.path]
type '' typeclass/prototype <object> = prototype_key
parent ''
typeclass/list/show [typeclass.path] typeclass/list/show [typeclass.path]
swap - this is a shorthand for using /force/reset flags. swap - this is a shorthand for using /force/reset flags.
update - this is a shorthand for using the /force/reload flag. update - this is a shorthand for using the /force/reload flag.
@ -1930,9 +1931,12 @@ class CmdTypeclass(COMMAND_DEFAULT_CLASS):
list - show available typeclasses. Only typeclasses in modules actually list - show available typeclasses. Only typeclasses in modules actually
imported or used from somewhere in the code will show up here imported or used from somewhere in the code will show up here
(those typeclasses are still available if you know the path) (those typeclasses are still available if you know the path)
prototype - clean and overwrite the object with the specified
prototype key - effectively making a whole new object.
Example: Example:
type button = examples.red_button.RedButton type button = examples.red_button.RedButton
type/prototype button=a red button
If the typeclass_path is not given, the current object's typeclass is If the typeclass_path is not given, the current object's typeclass is
assumed. assumed.
@ -1954,7 +1958,7 @@ class CmdTypeclass(COMMAND_DEFAULT_CLASS):
key = "typeclass" key = "typeclass"
aliases = ["type", "parent", "swap", "update"] aliases = ["type", "parent", "swap", "update"]
switch_options = ("show", "examine", "update", "reset", "force", "list") switch_options = ("show", "examine", "update", "reset", "force", "list", "prototype")
locks = "cmd:perm(typeclass) or perm(Builder)" locks = "cmd:perm(typeclass) or perm(Builder)"
help_category = "Building" help_category = "Building"
@ -2038,6 +2042,27 @@ class CmdTypeclass(COMMAND_DEFAULT_CLASS):
new_typeclass = self.rhs or obj.path new_typeclass = self.rhs or obj.path
prototype = None
if "prototype" in self.switches:
key = self.rhs
prototype = protlib.search_prototype(key=key)
if len(prototype) > 1:
caller.msg(
"More than one match for {}:\n{}".format(
key, "\n".join(proto.get("prototype_key", "") for proto in prototype)
)
)
return
elif prototype:
# one match
prototype = prototype[0]
else:
# no match
caller.msg("No prototype '{}' was found.".format(key))
return
new_typeclass = prototype["typeclass"]
self.switches.append("force")
if "show" in self.switches or "examine" in self.switches: if "show" in self.switches or "examine" in self.switches:
string = "%s's current typeclass is %s." % (obj.name, obj.__class__) string = "%s's current typeclass is %s." % (obj.name, obj.__class__)
caller.msg(string) caller.msg(string)
@ -2070,11 +2095,38 @@ class CmdTypeclass(COMMAND_DEFAULT_CLASS):
hooks = "at_object_creation" if update else "all" hooks = "at_object_creation" if update else "all"
old_typeclass_path = obj.typeclass_path old_typeclass_path = obj.typeclass_path
# special prompt for the user in cases where we want
# to confirm changes.
if "prototype" in self.switches:
diff, _ = spawner.prototype_diff_from_object(prototype, obj)
txt, options = _format_diff_text_and_options(diff, objects=[obj])
prompt = "Applying prototype '%s' over '%s' will cause the follow changes:\n%s\n" % \
(
prototype["key"],
obj.name,
"\n".join(txt)
)
if not reset:
prompt += "\n|yWARNING:|n Use the /reset switch to apply the prototype over a blank state."
prompt += "\nAre you sure you want to apply these changes [yes]/no?"
answer = yield (prompt)
if answer and answer in ("no", "n"):
caller.msg(
"Canceled: No changes were applied."
)
return
# we let this raise exception if needed # we let this raise exception if needed
obj.swap_typeclass( obj.swap_typeclass(
new_typeclass, clean_attributes=reset, clean_cmdsets=reset, run_start_hooks=hooks new_typeclass, clean_attributes=reset, clean_cmdsets=reset, run_start_hooks=hooks
) )
if "prototype" in self.switches:
modified = spawner.batch_update_objects_with_prototype(prototype, objects=[obj])
prototype_success = modified > 0
if not prototype_success:
caller.msg("Prototype %s failed to apply." % prototype["key"])
if is_same: if is_same:
string = "%s updated its existing typeclass (%s).\n" % (obj.name, obj.path) string = "%s updated its existing typeclass (%s).\n" % (obj.name, obj.path)
else: else:
@ -2091,6 +2143,8 @@ class CmdTypeclass(COMMAND_DEFAULT_CLASS):
string += " All old attributes where deleted before the swap." string += " All old attributes where deleted before the swap."
else: else:
string += " Attributes set before swap were not removed." string += " Attributes set before swap were not removed."
if "prototype" in self.switches and prototype_success:
string += " Prototype '%s' was successfully applied over the object type." % prototype["key"]
caller.msg(string) caller.msg(string)

View file

@ -991,6 +991,27 @@ class TestBuilding(CommandTest):
"All object creation hooks were run. All old attributes where deleted before the swap.", "All object creation hooks were run. All old attributes where deleted before the swap.",
) )
from evennia.prototypes.prototypes import homogenize_prototype
test_prototype = [homogenize_prototype(
{"prototype_key": "testkey",
"prototype_tags": [],
"typeclass": "typeclasses.objects.Object",
"key":"replaced_obj",
"attrs": [("foo", "bar", None, ""),
("desc", "protdesc", None, "")]})]
with mock.patch("evennia.commands.default.building.protlib.search_prototype",
new=mock.MagicMock(return_value=test_prototype)) as mprot:
self.call(
building.CmdTypeclass(),
"/prototype Obj=testkey",
"replaced_obj changed typeclass from "
"evennia.objects.objects.DefaultObject to "
"typeclasses.objects.Object.\nAll object creation hooks were "
"run. Attributes set before swap were not removed. Prototype "
"'replaced_obj' was successfully applied over the object type."
)
assert self.obj1.db.desc == "protdesc"
def test_lock(self): def test_lock(self):
self.call(building.CmdLock(), "", "Usage: ") self.call(building.CmdLock(), "", "Usage: ")
self.call(building.CmdLock(), "Obj = test:all()", "Added lock 'test:all()' to Obj.") self.call(building.CmdLock(), "Obj = test:all()", "Added lock 'test:all()' to Obj.")

View file

@ -388,8 +388,7 @@ if WEBSERVER_ENABLED:
webclientstr = "webclient-websocket%s: %s" % (w_ifacestr, port) webclientstr = "webclient-websocket%s: %s" % (w_ifacestr, port)
INFO_DICT["webclient"].append(webclientstr) INFO_DICT["webclient"].append(webclientstr)
web_root = Website(web_root, logPath=settings.HTTP_LOG_FILE)
web_root.is_portal = True
if WEB_PLUGINS_MODULE: if WEB_PLUGINS_MODULE:
try: try:
@ -399,8 +398,8 @@ if WEBSERVER_ENABLED:
"WARNING: WEB_PLUGINS_MODULE is enabled but at_webproxy_root_creation() not found - " "WARNING: WEB_PLUGINS_MODULE is enabled but at_webproxy_root_creation() not found - "
"copy 'evennia/game_template/server/conf/web_plugins.py to mygame/server/conf." "copy 'evennia/game_template/server/conf/web_plugins.py to mygame/server/conf."
) )
web_root = Website(web_root, logPath=settings.HTTP_LOG_FILE)
web_root.is_portal = True
proxy_service = internet.TCPServer(proxyport, web_root, interface=interface) proxy_service = internet.TCPServer(proxyport, web_root, interface=interface)
proxy_service.setName("EvenniaWebProxy%s:%s" % (ifacestr, proxyport)) proxy_service.setName("EvenniaWebProxy%s:%s" % (ifacestr, proxyport))
PORTAL.services.addService(proxy_service) PORTAL.services.addService(proxy_service)

View file

@ -7,9 +7,12 @@ pytz
django-sekizai django-sekizai
inflect inflect
autobahn >= 17.9.3 autobahn >= 17.9.3
model_mommy
# try to resolve dependency issue in py3.7
attrs >= 19.2.0
# testing and development # testing and development
model_mommy
mock >= 1.0.1 mock >= 1.0.1
anything anything
black black