From 865ab131bb3d2a38a91ad556964015efb65aa87a Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 18 Feb 2017 19:21:43 +0100 Subject: [PATCH 001/134] Fix reference error in get_input. Resolves #1219. --- evennia/utils/evmenu.py | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/evennia/utils/evmenu.py b/evennia/utils/evmenu.py index 987a8ba184..f8af42e5f7 100644 --- a/evennia/utils/evmenu.py +++ b/evennia/utils/evmenu.py @@ -888,24 +888,28 @@ class CmdGetInput(Command): def func(self): "This is called when user enters anything." caller = self.caller - callback = caller.ndb._getinput._callback - if not callback: - # this can be happen if called from a player-command when IC - caller = self.player - callback = caller.ndb._getinput._callback - if not callback: - raise RuntimeError("No input callback found.") + try: + getinput = caller.ndb._getinput + if not getinput and hasattr(caller, "player"): + getinput = caller.player.ndb._getinput + caller = caller.player + callback = getinput._callback - caller.ndb._getinput._session = self.session - prompt = caller.ndb._getinput._prompt - result = self.raw_string.strip() # we strip the ending line break caused by sending + caller.ndb._getinput._session = self.session + prompt = caller.ndb._getinput._prompt + result = self.raw_string.strip() # we strip the ending line break caused by sending - ok = not callback(caller, prompt, result) - if ok: - # only clear the state if the callback does not return - # anything - del caller.ndb._getinput - caller.cmdset.remove(InputCmdSet) + ok = not callback(caller, prompt, result) + if ok: + # only clear the state if the callback does not return + # anything + del caller.ndb._getinput + caller.cmdset.remove(InputCmdSet) + except Exception: + # make sure to clean up cmdset if something goes wrong + caller.msg("|rError in get_input. Choice not confirmed (report to admin)|n") + logger.log_trace("Error in get_input") + caller.cdmset.remove(InputCmdSet) class InputCmdSet(CmdSet): From d9e305702fcca18a5140a3ffe688ed72ff556876 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 18 Feb 2017 19:41:03 +0100 Subject: [PATCH 002/134] Made sure to allow setting the option even though the client does not report it. Might help with #1218 ... maybe. --- evennia/commands/default/player.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evennia/commands/default/player.py b/evennia/commands/default/player.py index 03680efc04..b2d18b625a 100644 --- a/evennia/commands/default/player.py +++ b/evennia/commands/default/player.py @@ -549,7 +549,7 @@ class CmdOption(COMMAND_DEFAULT_CLASS): def update(name, val, validator): # helper: update property and report errors try: - old_val = flags[name] + old_val = flags.get(name, False) new_val = validator(val) flags[name] = new_val self.msg("Option |w%s|n was changed from '|w%s|n' to '|w%s|n'." % (name, old_val, new_val)) From ee8c9a93c39c58f6e5096b7815a597e3989073a2 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 18 Feb 2017 20:32:13 +0100 Subject: [PATCH 003/134] Add tentative errback for not firing an error when response is not fast enough to fire before a user closes the connection. Might handle #1207. --- evennia/server/webserver.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/evennia/server/webserver.py b/evennia/server/webserver.py index 7160b097a7..beda2d5ecf 100644 --- a/evennia/server/webserver.py +++ b/evennia/server/webserver.py @@ -70,6 +70,7 @@ class EvenniaReverseProxyResource(ReverseProxyResource): resource (EvenniaReverseProxyResource): A proxy resource. """ + request.notifyFinish().addErrback(lambda f: f.cancel()) return EvenniaReverseProxyResource( self.host, self.port, self.path + '/' + urlquote(path, safe=""), self.reactor) @@ -98,6 +99,8 @@ class EvenniaReverseProxyResource(ReverseProxyResource): request.getAllHeaders(), request.content.read(), request) clientFactory.noisy = False self.reactor.connectTCP(self.host, self.port, clientFactory) + # don't trigger traceback if connection is lost before request finish. + request.notifyFinish().addErrback(lambda f: f.cancel()) return NOT_DONE_YET From 959e5ec558d0ec79863e901736af769d01ed5c1b Mon Sep 17 00:00:00 2001 From: Tehom Date: Sat, 18 Feb 2017 09:46:19 -0500 Subject: [PATCH 004/134] Put in fix for nicks.has check being True when returning list of False matches due to None caching. --- evennia/commands/default/general.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/evennia/commands/default/general.py b/evennia/commands/default/general.py index e386fbe927..797aa8719e 100644 --- a/evennia/commands/default/general.py +++ b/evennia/commands/default/general.py @@ -175,10 +175,9 @@ class CmdNick(COMMAND_DEFAULT_CLASS): errstring += "Not a valid nick index." else: errstring += "Nick not found." - if "delete" in switches or "del" in switches: # clear the nick - if caller.nicks.has(old_nickstring, category=nicktype): + if old_nickstring and caller.nicks.has(old_nickstring, category=nicktype): caller.nicks.remove(old_nickstring, category=nicktype) string += "\nNick removed: '|w%s|n' -> |w%s|n." % (old_nickstring, old_replstring) else: From 33000a55f643bf0e63432a66a9806f421cb3955b Mon Sep 17 00:00:00 2001 From: Griatch Date: Sun, 19 Feb 2017 09:25:21 +0100 Subject: [PATCH 005/134] Have CmdPerm check the 'edit' locktype when caller is a player instead of 'control' (which is not defined on players). Resolves #1223. --- evennia/commands/default/admin.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/evennia/commands/default/admin.py b/evennia/commands/default/admin.py index a8b218962d..9befacd327 100644 --- a/evennia/commands/default/admin.py +++ b/evennia/commands/default/admin.py @@ -495,9 +495,10 @@ class CmdPerm(COMMAND_DEFAULT_CLASS): return # we supplied an argument on the form obj = perm - - if not obj.access(caller, 'control'): - caller.msg("You are not allowed to edit this object's permissions.") + locktype = "edit" if playermode else "control" + if not obj.access(caller, locktype): + caller.msg("You are not allowed to edit this %s's permissions." % + ("player" if playermode else "object")) return cstring = "" From 7f06bb4b7399414266cdebd4bf08f6c7a56147bc Mon Sep 17 00:00:00 2001 From: BlauFeuer Date: Sat, 18 Feb 2017 19:30:57 -0500 Subject: [PATCH 006/134] Correct errant find/replace from last commit --- evennia/contrib/barter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evennia/contrib/barter.py b/evennia/contrib/barter.py index 3f67643a32..a7ec15afcf 100644 --- a/evennia/contrib/barter.py +++ b/evennia/contrib/barter.py @@ -580,7 +580,7 @@ class CmdDecline(CmdTradeBase): self.msg_other(caller, self.str_other % "%s changes their mind, |Rdeclining|n the current offer." % caller.key) else: - # no accept_ance to change + # no acceptance to change caller.msg(self.str_caller % "You |Rdecline|n the current offer.") self.msg_other(caller, self.str_other % "%s declines the current offer." % caller.key) From f857a43f4be02d6146e2bf1a1e246c9800f139a5 Mon Sep 17 00:00:00 2001 From: BlauFeuer Date: Sat, 18 Feb 2017 19:34:18 -0500 Subject: [PATCH 007/134] Markup update, docstring and whitespace fixes --- evennia/comms/comms.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/evennia/comms/comms.py b/evennia/comms/comms.py index 71d61f4e6f..f50d3bf4a8 100644 --- a/evennia/comms/comms.py +++ b/evennia/comms/comms.py @@ -95,7 +95,7 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)): listening = [ob for ob in subs if ob.is_connected and ob not in self.mutelist] if subs: # display listening subscribers in bold - string = ", ".join([player.key if player not in listening else "{w%s{n" % player.key for player in subs]) + string = ", ".join([player.key if player not in listening else "|w%s|n" % player.key for player in subs]) else: string = "" return string @@ -126,7 +126,6 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)): return True return False - def connect(self, subscriber): """ Connect the user to this channel. This checks access. @@ -254,7 +253,7 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)): try: # note our addition of the from_channel keyword here. This could be checked # by a custom player.msg() to treat channel-receives differently. - entity.msg(msgobj.message, from_obj=msgobj.senders, options={"from_channel":self.id}) + entity.msg(msgobj.message, from_obj=msgobj.senders, options={"from_channel": self.id}) except AttributeError as e: logger.log_trace("%s\nCannot send msg to '%s'." % (e, entity)) @@ -331,7 +330,6 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)): """ self.msg(message, senders=senders, header=header, keep_log=False) - # hooks def channel_prefix(self, msg=None, emit=False): @@ -376,7 +374,7 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)): message accordingly. Args: - msgob (Msg or TempMsg): The message to analyze for a pose. + msgobj (Msg or TempMsg): The message to analyze for a pose. sender_string (str): The name of the sender/poser. Returns: @@ -426,7 +424,7 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)): Hook method. Formats a message body for display. Args: - msgob (Msg or TempMsg): The message object to send. + msgobj (Msg or TempMsg): The message object to send. emit (bool, optional): The message is agnostic of senders. Returns: @@ -475,7 +473,7 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)): value, leaving the channel will be aborted. Args: - joiner (object): The joining object. + leaver (object): The leaving object. Returns: should_leave (bool): If `False`, channel parting is aborted. @@ -488,7 +486,7 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)): Hook method. Runs right after an object or player leaves a channel. Args: - joiner (object): The joining object. + leaver (object): The leaving object. """ pass From 3bae1190a9568eb792b13a97e64a6bc89c7705c3 Mon Sep 17 00:00:00 2001 From: BlauFeuer Date: Sat, 18 Feb 2017 19:52:29 -0500 Subject: [PATCH 008/134] markup and whitespace update --- evennia/commands/default/muxcommand.py | 40 +++++++++++++------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/evennia/commands/default/muxcommand.py b/evennia/commands/default/muxcommand.py index 854f8032b2..24e15ecdee 100644 --- a/evennia/commands/default/muxcommand.py +++ b/evennia/commands/default/muxcommand.py @@ -9,6 +9,7 @@ from evennia.commands.command import Command # limit symbol import for API __all__ = ("MuxCommand", "MuxPlayerCommand") + class MuxCommand(Command): """ This sets up the basis for a MUX command. The idea @@ -150,33 +151,32 @@ class MuxCommand(Command): """ # a simple test command to show the available properties string = "-" * 50 - string += "\n{w%s{n - Command variables from evennia:\n" % self.key + string += "\n|w%s|n - Command variables from evennia:\n" % self.key string += "-" * 50 - string += "\nname of cmd (self.key): {w%s{n\n" % self.key - string += "cmd aliases (self.aliases): {w%s{n\n" % self.aliases - string += "cmd locks (self.locks): {w%s{n\n" % self.locks - string += "help category (self.help_category): {w%s{n\n" % self.help_category - string += "object calling (self.caller): {w%s{n\n" % self.caller - string += "object storing cmdset (self.obj): {w%s{n\n" % self.obj - string += "command string given (self.cmdstring): {w%s{n\n" % self.cmdstring + string += "\nname of cmd (self.key): |w%s|n\n" % self.key + string += "cmd aliases (self.aliases): |w%s|n\n" % self.aliases + string += "cmd locks (self.locks): |w%s|n\n" % self.locks + string += "help category (self.help_category): |w%s|n\n" % self.help_category + string += "object calling (self.caller): |w%s|n\n" % self.caller + string += "object storing cmdset (self.obj): |w%s|n\n" % self.obj + string += "command string given (self.cmdstring): |w%s|n\n" % self.cmdstring # show cmdset.key instead of cmdset to shorten output - string += utils.fill("current cmdset (self.cmdset): {w%s{n\n" % self.cmdset) - - + string += utils.fill("current cmdset (self.cmdset): |w%s|n\n" % self.cmdset) string += "\n" + "-" * 50 - string += "\nVariables from MuxCommand baseclass\n" + string += "\nVariables from MuxCommand baseclass\n" string += "-" * 50 - string += "\nraw argument (self.raw): {w%s{n \n" % self.raw - string += "cmd args (self.args): {w%s{n\n" % self.args - string += "cmd switches (self.switches): {w%s{n\n" % self.switches - string += "space-separated arg list (self.arglist): {w%s{n\n" % self.arglist - string += "lhs, left-hand side of '=' (self.lhs): {w%s{n\n" % self.lhs - string += "lhs, comma separated (self.lhslist): {w%s{n\n" % self.lhslist - string += "rhs, right-hand side of '=' (self.rhs): {w%s{n\n" % self.rhs - string += "rhs, comma separated (self.rhslist): {w%s{n\n" % self.rhslist + string += "\nraw argument (self.raw): |w%s|n \n" % self.raw + string += "cmd args (self.args): |w%s|n\n" % self.args + string += "cmd switches (self.switches): |w%s|n\n" % self.switches + string += "space-separated arg list (self.arglist): |w%s|n\n" % self.arglist + string += "lhs, left-hand side of '=' (self.lhs): |w%s|n\n" % self.lhs + string += "lhs, comma separated (self.lhslist): |w%s|n\n" % self.lhslist + string += "rhs, right-hand side of '=' (self.rhs): |w%s|n\n" % self.rhs + string += "rhs, comma separated (self.rhslist): |w%s|n\n" % self.rhslist string += "-" * 50 self.caller.msg(string) + class MuxPlayerCommand(MuxCommand): """ This is an on-Player version of the MuxCommand. Since these commands sit From ec113885312849d02b44fdfa027574728357a965 Mon Sep 17 00:00:00 2001 From: BlauFeuer Date: Sat, 18 Feb 2017 19:56:44 -0500 Subject: [PATCH 009/134] Markup, whitespace, PEP 8 and docstring updates --- evennia/commands/command.py | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/evennia/commands/command.py b/evennia/commands/command.py index 79c6b48ce7..2684f9e8da 100644 --- a/evennia/commands/command.py +++ b/evennia/commands/command.py @@ -35,7 +35,7 @@ def _init_command(cls, **kwargs): if cls.aliases and not is_iter(cls.aliases): try: cls.aliases = [str(alias).strip().lower() - for alias in cls.aliases.split(',')] + for alias in cls.aliases.split(',')] except Exception: cls.aliases = [] cls.aliases = list(set(alias for alias in cls.aliases @@ -57,7 +57,7 @@ def _init_command(cls, **kwargs): if not hasattr(cls, 'locks'): # default if one forgets to define completely cls.locks = "cmd:all()" - if not "cmd:" in cls.locks: + if "cmd:" not in cls.locks: cls.locks = "cmd:all();" + cls.locks for lockstring in cls.locks.split(';'): if lockstring and not ':' in lockstring: @@ -197,7 +197,6 @@ class Command(with_metaclass(CommandMeta, object)): try: # first assume input is a command (the most common case) return self._matchset.intersection(cmd._matchset) - #return cmd.key in self._matchset except AttributeError: # probably got a string return cmd in self._matchset @@ -211,9 +210,8 @@ class Command(with_metaclass(CommandMeta, object)): """ try: return self._matchset.isdisjoint(cmd._matchset) - #return not cmd.key in self._matcheset except AttributeError: - return not cmd in self._matchset + return cmd not in self._matchset def __contains__(self, query): """ @@ -308,7 +306,7 @@ class Command(with_metaclass(CommandMeta, object)): def msg(self, text=None, to_obj=None, from_obj=None, session=None, **kwargs): """ - This is a shortcut instad of calling msg() directly on an + This is a shortcut instead of calling msg() directly on an object - it will detect if caller is an Object or a Player and also appends self.session automatically if self.msg_all_sessions is False. @@ -398,17 +396,18 @@ class Command(with_metaclass(CommandMeta, object)): """ # a simple test command to show the available properties string = "-" * 50 - string += "\n{w%s{n - Command variables from evennia:\n" % self.key + string += "\n|w%s|n - Command variables from evennia:\n" % self.key string += "-" * 50 - string += "\nname of cmd (self.key): {w%s{n\n" % self.key - string += "cmd aliases (self.aliases): {w%s{n\n" % self.aliases - string += "cmd locks (self.locks): {w%s{n\n" % self.locks - string += "help category (self.help_category): {w%s{n\n" % self.help_category.capitalize() - string += "object calling (self.caller): {w%s{n\n" % self.caller - string += "object storing cmdset (self.obj): {w%s{n\n" % self.obj - string += "command string given (self.cmdstring): {w%s{n\n" % self.cmdstring + string += "\nname of cmd (self.key): |w%s|n\n" % self.key + string += "cmd aliases (self.aliases): |w%s|n\n" % self.aliases + string += "cmd locks (self.locks): |w%s|n\n" % self.locks + string += "help category (self.help_category): |w%s|n\n" % self.help_category.capitalize() + string += "object calling (self.caller): |w%s|n\n" % self.caller + string += "object storing cmdset (self.obj): |w%s|n\n" % self.obj + string += "command string given (self.cmdstring): |w%s|n\n" % self.cmdstring # show cmdset.key instead of cmdset to shorten output - string += fill("current cmdset (self.cmdset): {w%s{n\n" % (self.cmdset.key if self.cmdset.key else self.cmdset.__class__)) + string += fill("current cmdset (self.cmdset): |w%s|n\n" % + (self.cmdset.key if self.cmdset.key else self.cmdset.__class__)) self.caller.msg(string) From 24bd3a6412d0207202a06e57379939805cf347e6 Mon Sep 17 00:00:00 2001 From: BlauFeuer Date: Sat, 18 Feb 2017 20:56:08 -0500 Subject: [PATCH 010/134] markup, whitespace and PEP 8 Indent and variable name updates, also. --- evennia/commands/default/player.py | 208 +++++++++++++++-------------- 1 file changed, 106 insertions(+), 102 deletions(-) diff --git a/evennia/commands/default/player.py b/evennia/commands/default/player.py index b2d18b625a..2a0d192086 100644 --- a/evennia/commands/default/player.py +++ b/evennia/commands/default/player.py @@ -13,7 +13,7 @@ The property self.character can be used to access the character when these commands are triggered with a connected character (such as the case of the @ooc command), it is None if we are OOC. -Note that under MULTISESSION_MODE > 2, Player- commands should use +Note that under MULTISESSION_MODE > 2, Player commands should use self.msg() and similar methods to reroute returns to the correct method. Otherwise all text will be returned to all connected sessions. @@ -44,7 +44,7 @@ class MuxPlayerLookCommand(COMMAND_DEFAULT_CLASS): """ def parse(self): - "Custom parsing" + """Custom parsing""" super(MuxPlayerLookCommand, self).parse() @@ -62,7 +62,7 @@ class MuxPlayerLookCommand(COMMAND_DEFAULT_CLASS): # store playable property if self.args: self.playable = dict((utils.to_str(char.key.lower()), char) - for char in playable).get(self.args.lower(), None) + for char in playable).get(self.args.lower(), None) else: self.playable = playable @@ -83,10 +83,10 @@ class CmdOOCLook(MuxPlayerLookCommand): Look in the ooc state. """ - #This is an OOC version of the look command. Since a - #Player doesn't have an in-game existence, there is no - #concept of location or "self". If we are controlling - #a character, pass control over to normal look. + # This is an OOC version of the look command. Since a + # Player doesn't have an in-game existence, there is no + # concept of location or "self". If we are controlling + # a character, pass control over to normal look. key = "look" aliases = ["l", "ls"] @@ -97,11 +97,11 @@ class CmdOOCLook(MuxPlayerLookCommand): player_caller = True def func(self): - "implement the ooc look command" + """implement the ooc look command""" if _MULTISESSION_MODE < 2: # only one character allowed - self.msg("You are out-of-character (OOC).\nUse {w@ic{n to get back into the game.") + self.msg("You are out-of-character (OOC).\nUse |w@ic|n to get back into the game.") return # call on-player look helper method @@ -128,7 +128,7 @@ class CmdCharCreate(COMMAND_DEFAULT_CLASS): player_caller = True def func(self): - "create the new character" + """create the new character""" player = self.player if not self.args: self.msg("Usage: @charcreate [= description]") @@ -139,8 +139,8 @@ class CmdCharCreate(COMMAND_DEFAULT_CLASS): charmax = _MAX_NR_CHARACTERS if _MULTISESSION_MODE > 1 else 1 if not player.is_superuser and \ - (player.db._playable_characters and - len(player.db._playable_characters) >= charmax): + (player.db._playable_characters and + len(player.db._playable_characters) >= charmax): self.msg("You may only create a maximum of %i characters." % charmax) return from evennia.objects.models import ObjectDB @@ -150,15 +150,13 @@ class CmdCharCreate(COMMAND_DEFAULT_CLASS): # check if this Character already exists. Note that we are only # searching the base character typeclass here, not any child # classes. - self.msg("{rA character named '{w%s{r' already exists.{n" % key) + self.msg("|rA character named '|w%s|r' already exists.|n" % key) return # create the character start_location = ObjectDB.objects.get_id(settings.START_LOCATION) default_home = ObjectDB.objects.get_id(settings.DEFAULT_HOME) permissions = settings.PERMISSION_PLAYER_DEFAULT - - new_character = create.create_object(typeclass, key=key, location=start_location, home=default_home, @@ -171,7 +169,8 @@ class CmdCharCreate(COMMAND_DEFAULT_CLASS): new_character.db.desc = desc elif not new_character.db.desc: new_character.db.desc = "This is a Player." - self.msg("Created new character %s. Use {w@ic %s{n to enter the game as this character." % (new_character.key, new_character.key)) + self.msg("Created new character %s. Use |w@ic %s|n to enter the game as this character." + % (new_character.key, new_character.key)) class CmdCharDelete(COMMAND_DEFAULT_CLASS): @@ -188,7 +187,7 @@ class CmdCharDelete(COMMAND_DEFAULT_CLASS): help_category = "General" def func(self): - "delete the character" + """delete the character""" player = self.player if not self.args: @@ -196,23 +195,23 @@ class CmdCharDelete(COMMAND_DEFAULT_CLASS): return # use the playable_characters list to search - match = [char for char in utils.make_iter(player.db._playable_characters) if char.key.lower() == self.args.lower()] + match = [char for char in utils.make_iter(player.db._playable_characters) + if char.key.lower() == self.args.lower()] if not match: self.msg("You have no such character to delete.") return elif len(match) > 1: self.msg("Aborting - there are two characters with the same name. Ask an admin to delete the right one.") return - else: # one match + else: # one match from evennia.utils.evmenu import get_input - def _callback(caller, prompt, result): + def _callback(caller, callback_prompt, result): if result.lower() == "yes": # only take action delobj = caller.ndb._char_to_delete key = delobj.key - caller.db._playable_characters = [char for char - in caller.db._playable_characters if char != delobj] + caller.db._playable_characters = [pc for pc in caller.db._playable_characters if pc != delobj] delobj.delete() self.msg("Character '%s' was permanently deleted." % key) else: @@ -272,7 +271,8 @@ class CmdIC(COMMAND_DEFAULT_CLASS): self.msg("That is not a valid character choice.") return if len(new_character) > 1: - self.msg("Multiple targets with the same name:\n %s" % ", ".join("%s(#%s)" % (obj.key, obj.id) for obj in new_character)) + self.msg("Multiple targets with the same name:\n %s" + % ", ".join("%s(#%s)" % (obj.key, obj.id) for obj in new_character)) return else: new_character = new_character[0] @@ -280,7 +280,7 @@ class CmdIC(COMMAND_DEFAULT_CLASS): player.puppet_object(session, new_character) player.db._last_puppet = new_character except RuntimeError as exc: - self.msg("{rYou cannot become {C%s{n: %s" % (new_character.name, exc)) + self.msg("|rYou cannot become |C%s|n: %s" % (new_character.name, exc)) # note that this is inheriting from MuxPlayerLookCommand, @@ -306,7 +306,7 @@ class CmdOOC(MuxPlayerLookCommand): player_caller = True def func(self): - "Implement function" + """Implement function""" player = self.player session = self.session @@ -322,17 +322,18 @@ class CmdOOC(MuxPlayerLookCommand): # disconnect try: player.unpuppet_object(session) - self.msg("\n{GYou go OOC.{n\n") + self.msg("\n|GYou go OOC.|n\n") if _MULTISESSION_MODE < 2: # only one character allowed - self.msg("You are out-of-character (OOC).\nUse {w@ic{n to get back into the game.") + self.msg("You are out-of-character (OOC).\nUse |w@ic|n to get back into the game.") return self.msg(player.at_look(target=self.playable, session=session)) except RuntimeError as exc: - self.msg("{rCould not unpuppet from {c%s{n: %s" % (old_char, exc)) + self.msg("|rCould not unpuppet from |c%s|n: %s" % (old_char, exc)) + class CmdSessions(COMMAND_DEFAULT_CLASS): """ @@ -352,22 +353,22 @@ class CmdSessions(COMMAND_DEFAULT_CLASS): player_caller = True def func(self): - "Implement function" + """Implement function""" player = self.player sessions = player.sessions.all() - table = prettytable.PrettyTable(["{wsessid", - "{wprotocol", - "{whost", - "{wpuppet/character", - "{wlocation"]) + table = prettytable.PrettyTable(["|wsessid", + "|wprotocol", + "|whost", + "|wpuppet/character", + "|wlocation"]) for sess in sorted(sessions, key=lambda x: x.sessid): char = player.get_puppet(sess) table.add_row([str(sess.sessid), str(sess.protocol_key), type(sess.address) == tuple and sess.address[0] or sess.address, char and str(char) or "None", char and str(char.location) or "N/A"]) - string = "{wYour current session(s):{n\n%s" % table + string = "|wYour current session(s):|n\n%s" % table self.msg(string) @@ -408,16 +409,17 @@ class CmdWho(COMMAND_DEFAULT_CLASS): nplayers = (SESSIONS.player_count()) if show_session_data: # privileged info - table = prettytable.PrettyTable(["{wPlayer Name", - "{wOn for", - "{wIdle", - "{wPuppeting", - "{wRoom", - "{wCmds", - "{wProtocol", - "{wHost"]) + table = prettytable.PrettyTable(["|wPlayer Name", + "|wOn for", + "|wIdle", + "|wPuppeting", + "|wRoom", + "|wCmds", + "|wProtocol", + "|wHost"]) for session in session_list: - if not session.logged_in: continue + if not session.logged_in: + continue delta_cmd = time.time() - session.cmd_last_visible delta_conn = time.time() - session.conn_time player = session.get_player() @@ -433,7 +435,7 @@ class CmdWho(COMMAND_DEFAULT_CLASS): isinstance(session.address, tuple) and session.address[0] or session.address]) else: # unprivileged - table = prettytable.PrettyTable(["{wPlayer name", "{wOn for", "{wIdle"]) + table = prettytable.PrettyTable(["|wPlayer name", "|wOn for", "|wIdle"]) for session in session_list: if not session.logged_in: continue @@ -444,8 +446,9 @@ class CmdWho(COMMAND_DEFAULT_CLASS): utils.time_format(delta_conn, 0), utils.time_format(delta_cmd, 1)]) - isone = nplayers == 1 - string = "{wPlayers:{n\n%s\n%s unique account%s logged in." % (table, "One" if isone else nplayers, "" if isone else "s") + is_one = nplayers == 1 + string = "|wPlayers:|n\n%s\n%s unique account%s logged in." \ + % (table, "One" if is_one else nplayers, "" if is_one else "s") self.msg(string) @@ -489,13 +492,13 @@ class CmdOption(COMMAND_DEFAULT_CLASS): if "save" in self.switches: # save all options self.caller.db._saved_protocol_flags = flags - self.msg("{gSaved all options. Use @option/clear to remove.{n") + self.msg("|gSaved all options. Use @option/clear to remove.|n") if "clear" in self.switches: # clear all saves self.caller.db._saved_protocol_flags = {} - self.msg("{gCleared all saved options.") + self.msg("|gCleared all saved options.") - options = dict(flags) # make a copy of the flag dict + options = dict(flags) # make a copy of the flag dict saved_options = dict(self.caller.attributes.get("_saved_protocol_flags", default={})) if "SCREENWIDTH" in options: @@ -503,7 +506,7 @@ class CmdOption(COMMAND_DEFAULT_CLASS): options["SCREENWIDTH"] = options["SCREENWIDTH"][0] else: options["SCREENWIDTH"] = " \n".join("%s : %s" % (screenid, size) - for screenid, size in options["SCREENWIDTH"].iteritems()) + for screenid, size in options["SCREENWIDTH"].iteritems()) if "SCREENHEIGHT" in options: if len(options["SCREENHEIGHT"]) == 1: options["SCREENHEIGHT"] = options["SCREENHEIGHT"][0] @@ -522,7 +525,7 @@ class CmdOption(COMMAND_DEFAULT_CLASS): row.append("%s%s" % (saved, changed)) table.add_row(*row) - self.msg("{wClient settings (%s):|n\n%s|n" % (self.session.protocol_key, table)) + self.msg("|wClient settings (%s):|n\n%s|n" % (self.session.protocol_key, table)) return @@ -532,30 +535,30 @@ class CmdOption(COMMAND_DEFAULT_CLASS): # Try to assign new values - def validate_encoding(val): + def validate_encoding(new_encoding): # helper: change encoding try: - utils.to_str(utils.to_unicode("test-string"), encoding=val) + utils.to_str(utils.to_unicode("test-string"), encoding=new_encoding) except LookupError: - raise RuntimeError("The encoding '|w%s|n' is invalid. " % val) + raise RuntimeError("The encoding '|w%s|n' is invalid. " % new_encoding) return val - def validate_size(val): - return {0: int(val)} + def validate_size(new_size): + return {0: int(new_size)} - def validate_bool(val): - return True if val.lower() in ("true", "on", "1") else False + def validate_bool(new_bool): + return True if new_bool.lower() in ("true", "on", "1") else False - def update(name, val, validator): + def update(new_name, new_val, validator): # helper: update property and report errors try: - old_val = flags.get(name, False) - new_val = validator(val) - flags[name] = new_val - self.msg("Option |w%s|n was changed from '|w%s|n' to '|w%s|n'." % (name, old_val, new_val)) - return {name: new_val} + old_val = flags.get(new_name, False) + new_val = validator(new_val) + flags[new_name] = new_val + self.msg("Option |w%s|n was changed from '|w%s|n' to '|w%s|n'." % (new_name, old_val, new_val)) + return {new_name: new_val} except Exception, err: - self.msg("|rCould not set option |w%s|r:|n %s" % (name, err)) + self.msg("|rCould not set option |w%s|r:|n %s" % (new_name, err)) return False validators = {"ANSI": validate_bool, @@ -590,12 +593,12 @@ class CmdOption(COMMAND_DEFAULT_CLASS): saved_options.update(optiondict) self.player.attributes.add("_saved_protocol_flags", saved_options) for key in optiondict: - self.msg("{gSaved option %s.{n" % key) + self.msg("|gSaved option %s.|n" % key) if "clear" in self.switches: # clear this save for key in optiondict: self.player.attributes.get("_saved_protocol_flags", {}).pop(key, None) - self.msg("{gCleared saved %s." % key) + self.msg("|gCleared saved %s." % key) self.session.update_flags(**optiondict) @@ -616,14 +619,14 @@ class CmdPassword(COMMAND_DEFAULT_CLASS): player_caller = True def func(self): - "hook function." + """hook function.""" player = self.player if not self.rhs: self.msg("Usage: @password = ") return - oldpass = self.lhslist[0] # this is already stripped by parse() - newpass = self.rhslist[0] # '' + oldpass = self.lhslist[0] # Both of these are + newpass = self.rhslist[0] # already stripped by parse() if not player.check_password(oldpass): self.msg("The specified old password isn't correct.") elif len(newpass) < 3: @@ -655,11 +658,11 @@ class CmdQuit(COMMAND_DEFAULT_CLASS): player_caller = True def func(self): - "hook function" + """hook function""" player = self.player if 'all' in self.switches: - player.msg("{RQuitting{n all sessions. Hope to see you soon again.", session=self.session) + player.msg("|RQuitting|n all sessions. Hope to see you soon again.", session=self.session) for session in player.sessions.all(): player.disconnect_session_from_player(session) else: @@ -674,7 +677,6 @@ class CmdQuit(COMMAND_DEFAULT_CLASS): player.disconnect_session_from_player(self.session) - class CmdColorTest(COMMAND_DEFAULT_CLASS): """ testing which colors your client support @@ -697,23 +699,23 @@ class CmdColorTest(COMMAND_DEFAULT_CLASS): player_caller = True def table_format(self, table): - """ - Helper method to format the ansi/xterm256 tables. - Takes a table of columns [[val,val,...],[val,val,...],...] - """ - if not table: - return [[]] + """ + Helper method to format the ansi/xterm256 tables. + Takes a table of columns [[val,val,...],[val,val,...],...] + """ + if not table: + return [[]] - extra_space = 1 - max_widths = [max([len(str(val)) for val in col]) for col in table] - ftable = [] - for irow in range(len(table[0])): - ftable.append([str(col[irow]).ljust(max_widths[icol]) + " " * extra_space - for icol, col in enumerate(table)]) - return ftable + extra_space = 1 + max_widths = [max([len(str(val)) for val in col]) for col in table] + ftable = [] + for irow in range(len(table[0])): + ftable.append([str(col[irow]).ljust(max_widths[icol]) + " " * + extra_space for icol, col in enumerate(table)]) + return ftable def func(self): - "Show color tables" + """Show color tables""" if self.args.startswith("a"): # show ansi 16-color table @@ -723,9 +725,11 @@ class CmdColorTest(COMMAND_DEFAULT_CLASS): # show all ansi color-related codes col1 = ["%s%s|n" % (code, code.replace("|", "||")) for code, _ in ap.ext_ansi_map[48:56]] col2 = ["%s%s|n" % (code, code.replace("|", "||")) for code, _ in ap.ext_ansi_map[56:64]] - col3 = ["%s%s|n" % (code.replace("\\",""), code.replace("|", "||").replace("\\", "")) for code, _ in ap.ext_ansi_map[-8:]] - col4 = ["%s%s|n" % (code.replace("\\",""), code.replace("|", "||").replace("\\", "")) for code, _ in ap.ansi_bright_bgs[-8:]] - col2.extend(["" for i in range(len(col1)-len(col2))]) + col3 = ["%s%s|n" % (code.replace("\\", ""), code.replace("|", "||").replace("\\", "")) + for code, _ in ap.ext_ansi_map[-8:]] + col4 = ["%s%s|n" % (code.replace("\\", ""), code.replace("|", "||").replace("\\", "")) + for code, _ in ap.ansi_bright_bgs[-8:]] + col2.extend(["" for _ in range(len(col1)-len(col2))]) table = utils.format_table([col1, col2, col4, col3]) string = "ANSI colors:" for row in table: @@ -744,9 +748,8 @@ class CmdColorTest(COMMAND_DEFAULT_CLASS): # foreground table table[ir].append("|%i%i%i%s|n" % (ir, ig, ib, "||%i%i%i" % (ir, ig, ib))) # background table - table[6+ir].append("|%i%i%i|[%i%i%i%s|n" % (5 - ir, 5 - ig, 5 - ib, - ir, ig, ib, - "||[%i%i%i" % (ir, ig, ib))) + table[6+ir].append("|%i%i%i|[%i%i%i%s|n" + % (5 - ir, 5 - ig, 5 - ib, ir, ig, ib, "||[%i%i%i" % (ir, ig, ib))) table = self.table_format(table) string = "Xterm256 colors (if not all hues show, your client might not report that it can handle xterm256):" string += "\n" + "\n".join("".join(row) for row in table) @@ -755,15 +758,15 @@ class CmdColorTest(COMMAND_DEFAULT_CLASS): for igray in range(6): letter = chr(97 + (ibatch*6 + igray)) inverse = chr(122 - (ibatch*6 + igray)) - table[0 + igray].append("|=%s%s |n" % (letter, "||=%s" % (letter))) - table[6 + igray].append("|=%s|[=%s%s |n" % (inverse, letter, "||[=%s" % (letter))) + table[0 + igray].append("|=%s%s |n" % (letter, "||=%s" % letter)) + table[6 + igray].append("|=%s|[=%s%s |n" % (inverse, letter, "||[=%s" % letter)) for igray in range(6): # the last row (y, z) has empty columns if igray < 2: letter = chr(121 + igray) inverse = chr(98 - igray) - fg = "|=%s%s |n" % (letter, "||=%s" % (letter)) - bg = "|=%s|[=%s%s |n" % (inverse, letter, "||[=%s" % (letter)) + fg = "|=%s%s |n" % (letter, "||=%s" % letter) + bg = "|=%s|[=%s%s |n" % (inverse, letter, "||[=%s" % letter) else: fg, bg = " ", " " table[0 + igray].append(fg) @@ -802,7 +805,7 @@ class CmdQuell(COMMAND_DEFAULT_CLASS): player_caller = True def _recache_locks(self, player): - "Helper method to reset the lockhandler on an already puppeted object" + """Helper method to reset the lockhandler on an already puppeted object""" if self.session: char = self.session.puppet if char: @@ -813,7 +816,7 @@ class CmdQuell(COMMAND_DEFAULT_CLASS): player.locks.reset() def func(self): - "Perform the command" + """Perform the command""" player = self.player permstr = player.is_superuser and " (superuser)" or " (%s)" % (", ".join(player.permissions.all())) if self.cmdstring == '@unquell': @@ -829,9 +832,10 @@ class CmdQuell(COMMAND_DEFAULT_CLASS): player.attributes.add('_quell', True) puppet = self.session.puppet if puppet: - cpermstr = " (%s)" % ", ".join(puppet.permissions.all()) + cpermstr = "(%s)" % ", ".join(puppet.permissions.all()) cpermstr = "Quelling to current puppet's permissions%s." % cpermstr - cpermstr += "\n(Note: If this is higher than Player permissions%s, the lowest of the two will be used.)" % permstr + cpermstr += "\n(Note: If this is higher than Player permissions %s," \ + " the lowest of the two will be used.)" % permstr cpermstr += "\nUse @unquell to return to normal permission usage." self.msg(cpermstr) else: From be05b3a1c8b17689806566b492c8389262561a2c Mon Sep 17 00:00:00 2001 From: BlauFeuer Date: Sat, 18 Feb 2017 21:29:32 -0500 Subject: [PATCH 011/134] Markup, whitespace and PEP 8 updates --- evennia/commands/default/system.py | 164 ++++++++++++++--------------- 1 file changed, 81 insertions(+), 83 deletions(-) diff --git a/evennia/commands/default/system.py b/evennia/commands/default/system.py index 429308ce26..4a645ff68a 100644 --- a/evennia/commands/default/system.py +++ b/evennia/commands/default/system.py @@ -109,7 +109,7 @@ class CmdShutdown(COMMAND_DEFAULT_CLASS): help_category = "System" def func(self): - "Define function" + """Define function""" # Only allow shutdown if caller has session if not self.caller.sessions.get(): return @@ -126,6 +126,7 @@ class CmdShutdown(COMMAND_DEFAULT_CLASS): def _py_load(caller): return "" + def _py_code(caller, buf): """ Execute the buffer. @@ -139,10 +140,12 @@ def _py_code(caller, buf): show_input=False) return True + def _py_quit(caller): del caller.db._py_measure_time caller.msg("Exited the code editor.") + def _run_code_snippet(caller, pycode, mode="eval", measure_time=False, show_input=True): """ @@ -174,9 +177,9 @@ def _run_code_snippet(caller, pycode, mode="eval", measure_time=False, if show_input: try: caller.msg(">>> %s" % pycode, session=session, - options={"raw":True}) + options={"raw": True}) except TypeError: - caller.msg(">>> %s" % pycode, options={"raw":True}) + caller.msg(">>> %s" % pycode, options={"raw": True}) try: try: @@ -204,9 +207,10 @@ def _run_code_snippet(caller, pycode, mode="eval", measure_time=False, ret = "\n".join("%s" % line for line in errlist if line) try: - caller.msg(ret, session=session, options={"raw":True}) + caller.msg(ret, session=session, options={"raw": True}) except TypeError: - caller.msg(ret, options={"raw":True}) + caller.msg(ret, options={"raw": True}) + class CmdPy(COMMAND_DEFAULT_CLASS): """ @@ -234,8 +238,8 @@ class CmdPy(COMMAND_DEFAULT_CLASS): You can explore The evennia API from inside the game by calling evennia.help(), evennia.managers.help() etc. - {rNote: In the wrong hands this command is a severe security risk. - It should only be accessible by trusted server admins/superusers.{n + |rNote: In the wrong hands this command is a severe security risk. + It should only be accessible by trusted server admins/superusers.|n """ key = "@py" @@ -244,7 +248,7 @@ class CmdPy(COMMAND_DEFAULT_CLASS): help_category = "System" def func(self): - "hook function" + """hook function""" caller = self.caller pycode = self.args @@ -266,13 +270,14 @@ class CmdPy(COMMAND_DEFAULT_CLASS): # helper function. Kept outside so it can be imported and run # by other commands. + def format_script_list(scripts): - "Takes a list of scripts and formats the output." + """Takes a list of scripts and formats the output.""" if not scripts: return "" - table = EvTable("{wdbref{n", "{wobj{n", "{wkey{n", "{wintval{n", "{wnext{n", - "{wrept{n", "{wdb", "{wtypeclass{n", "{wdesc{n", + table = EvTable("|wdbref|n", "|wobj|n", "|wkey|n", "|wintval|n", "|wnext|n", + "|wrept|n", "|wdb", "|wtypeclass|n", "|wdesc|n", align='r', border="tablecols") for script in scripts: nextrep = script.time_until_next_repeat() @@ -326,12 +331,11 @@ class CmdScripts(COMMAND_DEFAULT_CLASS): help_category = "System" def func(self): - "implement method" + """implement method""" caller = self.caller args = self.args - string = "" if args: if "start" in self.switches: # global script-start mode @@ -374,9 +378,9 @@ class CmdScripts(COMMAND_DEFAULT_CLASS): else: string = "Stopping script '%s'." % scripts[0].key scripts[0].stop() - #import pdb - #pdb.set_trace() - ScriptDB.objects.validate() #just to be sure all is synced + # import pdb # DEBUG + # pdb.set_trace() # DEBUG + ScriptDB.objects.validate() # just to be sure all is synced else: # multiple matches. string = "Multiple script matches. Please refine your search:\n" @@ -409,26 +413,21 @@ class CmdObjects(COMMAND_DEFAULT_CLASS): help_category = "System" def func(self): - "Implement the command" + """Implement the command""" caller = self.caller - - if self.args and self.args.isdigit(): - nlim = int(self.args) - else: - nlim = 10 - + nlim = int(self.args) if self.args and self.args.isdigit() else 10 nobjs = ObjectDB.objects.count() base_char_typeclass = settings.BASE_CHARACTER_TYPECLASS nchars = ObjectDB.objects.filter(db_typeclass_path=base_char_typeclass).count() - nrooms = ObjectDB.objects.filter(db_location__isnull=True).exclude(db_typeclass_path=base_char_typeclass).count() + nrooms = ObjectDB.objects.filter(db_location__isnull=True).exclude( + db_typeclass_path=base_char_typeclass).count() nexits = ObjectDB.objects.filter(db_location__isnull=False, db_destination__isnull=False).count() nother = nobjs - nchars - nrooms - nexits - - nobjs = nobjs or 1 # fix zero-div error with empty database + nobjs = nobjs or 1 # fix zero-div error with empty database # total object sum table - totaltable = EvTable("{wtype{n", "{wcomment{n", "{wcount{n", "{w%%{n", border="table", align="l") + totaltable = EvTable("|wtype|n", "|wcomment|n", "|wcount|n", "|w%%|n", border="table", align="l") totaltable.align = 'l' totaltable.add_row("Characters", "(BASE_CHARACTER_TYPECLASS)", nchars, "%.2f" % ((float(nchars) / nobjs) * 100)) totaltable.add_row("Rooms", "(location=None)", nrooms, "%.2f" % ((float(nrooms) / nobjs) * 100)) @@ -436,7 +435,7 @@ class CmdObjects(COMMAND_DEFAULT_CLASS): totaltable.add_row("Other", "", nother, "%.2f" % ((float(nother) / nobjs) * 100)) # typeclass table - typetable = EvTable("{wtypeclass{n", "{wcount{n", "{w%%{n", border="table", align="l") + typetable = EvTable("|wtypeclass|n", "|wcount|n", "|w%%|n", border="table", align="l") typetable.align = 'l' dbtotals = ObjectDB.objects.object_totals() for path, count in dbtotals.items(): @@ -444,15 +443,15 @@ class CmdObjects(COMMAND_DEFAULT_CLASS): # last N table objs = ObjectDB.objects.all().order_by("db_date_created")[max(0, nobjs - nlim):] - latesttable = EvTable("{wcreated{n", "{wdbref{n", "{wname{n", "{wtypeclass{n", align="l", border="table") + latesttable = EvTable("|wcreated|n", "|wdbref|n", "|wname|n", "|wtypeclass|n", align="l", border="table") latesttable.align = 'l' for obj in objs: latesttable.add_row(utils.datetime_format(obj.date_created), obj.dbref, obj.key, obj.path) - string = "\n{wObject subtype totals (out of %i Objects):{n\n%s" % (nobjs, totaltable) - string += "\n{wObject typeclass distribution:{n\n%s" % typetable - string += "\n{wLast %s Objects created:{n\n%s" % (min(nobjs, nlim), latesttable) + string = "\n|wObject subtype totals (out of %i Objects):|n\n%s" % (nobjs, totaltable) + string += "\n|wObject typeclass distribution:|n\n%s" % typetable + string += "\n|wLast %s Objects created:|n\n%s" % (min(nobjs, nlim), latesttable) caller.msg(string) @@ -473,7 +472,7 @@ class CmdPlayers(COMMAND_DEFAULT_CLASS): help_category = "System" def func(self): - "List the players" + """List the players""" caller = self.caller if self.args and self.args.isdigit(): @@ -485,17 +484,17 @@ class CmdPlayers(COMMAND_DEFAULT_CLASS): # typeclass table dbtotals = PlayerDB.objects.object_totals() - typetable = EvTable("{wtypeclass{n", "{wcount{n", "{w%%{n", border="cells", align="l") + typetable = EvTable("|wtypeclass|n", "|wcount|n", "|w%%|n", border="cells", align="l") for path, count in dbtotals.items(): typetable.add_row(path, count, "%.2f" % ((float(count) / nplayers) * 100)) # last N table plyrs = PlayerDB.objects.all().order_by("db_date_created")[max(0, nplayers - nlim):] - latesttable = EvTable("{wcreated{n", "{wdbref{n", "{wname{n", "{wtypeclass{n", border="cells", align="l") + latesttable = EvTable("|wcreated|n", "|wdbref|n", "|wname|n", "|wtypeclass|n", border="cells", align="l") for ply in plyrs: latesttable.add_row(utils.datetime_format(ply.date_created), ply.dbref, ply.key, ply.path) - string = "\n{wPlayer typeclass distribution:{n\n%s" % typetable - string += "\n{wLast %s Players created:{n\n%s" % (min(nplayers, nlim), latesttable) + string = "\n|wPlayer typeclass distribution:|n\n%s" % typetable + string += "\n|wLast %s Players created:|n\n%s" % (min(nplayers, nlim), latesttable) caller.msg(string) @@ -525,7 +524,7 @@ class CmdService(COMMAND_DEFAULT_CLASS): help_category = "System" def func(self): - "Implement command" + """Implement command""" caller = self.caller switches = self.switches @@ -540,9 +539,9 @@ class CmdService(COMMAND_DEFAULT_CLASS): if not switches or switches[0] == "list": # Just display the list of installed services and their # status, then exit. - table = EvTable("{wService{n (use @services/start|stop|delete)", "{wstatus", align="l") + table = EvTable("|wService|n (use @services/start|stop|delete)", "|wstatus", align="l") for service in service_collection.services: - table.add_row(service.name, service.running and "{gRunning" or "{rNot Running") + table.add_row(service.name, service.running and "|gRunning" or "|rNot Running") caller.msg(unicode(table)) return @@ -584,7 +583,7 @@ class CmdService(COMMAND_DEFAULT_CLASS): return if switches[0] == "start": - #Starts a service. + # Attempt to start a service. if service.running: caller.msg('That service is already running.') return @@ -608,23 +607,23 @@ class CmdAbout(COMMAND_DEFAULT_CLASS): help_category = "System" def func(self): - "Show the version" + """Show the version""" string = """ - {cEvennia{n %s{n + |cEvennia|n %s|n MUD/MUX/MU* development system - {wLicence{n BSD 3-Clause Licence - {wWeb{n http://www.evennia.com - {wIrc{n #evennia on FreeNode - {wForum{n http://www.evennia.com/discussions - {wMaintainer{n (2010-) Griatch (griatch AT gmail DOT com) - {wMaintainer{n (2006-10) Greg Taylor + |wLicence|n BSD 3-Clause Licence + |wWeb|n http://www.evennia.com + |wIrc|n #evennia on FreeNode + |wForum|n http://www.evennia.com/discussions + |wMaintainer|n (2010-) Griatch (griatch AT gmail DOT com) + |wMaintainer|n (2006-10) Greg Taylor - {wOS{n %s - {wPython{n %s - {wTwisted{n %s - {wDjango{n %s + |wOS|n %s + |wPython|n %s + |wTwisted|n %s + |wDjango|n %s """ % (utils.get_evennia_version(), os.name, sys.version.split()[0], @@ -649,8 +648,8 @@ class CmdTime(COMMAND_DEFAULT_CLASS): help_category = "System" def func(self): - "Show server time data in a table." - table1 = EvTable("|wServer time","", align="l", width=78) + """Show server time data in a table.""" + table1 = EvTable("|wServer time", "", align="l", width=78) table1.add_row("Current uptime", utils.time_format(gametime.uptime(), 3)) table1.add_row("Total runtime", utils.time_format(gametime.runtime(), 2)) table1.add_row("First start", datetime.datetime.fromtimestamp(gametime.server_epoch())) @@ -682,20 +681,20 @@ class CmdServerLoad(COMMAND_DEFAULT_CLASS): Some Important statistics in the table: - {wServer load{n is an average of processor usage. It's usually + |wServer load|n is an average of processor usage. It's usually between 0 (no usage) and 1 (100% usage), but may also be temporarily higher if your computer has multiple CPU cores. - The {wResident/Virtual memory{n displays the total memory used by + The |wResident/Virtual memory|n displays the total memory used by the server process. - Evennia {wcaches{n all retrieved database entities when they are + Evennia |wcaches|n all retrieved database entities when they are loaded by use of the idmapper functionality. This allows Evennia to maintain the same instances of an entity and allowing non-persistent storage schemes. The total amount of cached objects are displayed plus a breakdown of database object types. - The {wflushmem{n switch allows to flush the object cache. Please + The |wflushmem|n switch allows to flush the object cache. Please note that due to how Python's memory management works, releasing caches may not show you a lower Residual/Virtual memory footprint, the released memory will instead be re-used by the program. @@ -707,7 +706,7 @@ class CmdServerLoad(COMMAND_DEFAULT_CLASS): help_category = "System" def func(self): - "Show list." + """Show list.""" global _IDMAPPER if not _IDMAPPER: @@ -741,21 +740,21 @@ class CmdServerLoad(COMMAND_DEFAULT_CLASS): if has_psutil: loadavg = psutil.cpu_percent() _mem = psutil.virtual_memory() - rmem = _mem.used / (1000.0 * 1000) + rmem = _mem.used / (1000.0 * 1000) pmem = _mem.percent if "mem" in self.switches: - string = "Total computer memory usage: {w%g{n MB (%g%%)" + string = "Total computer memory usage: |w%g|n MB (%g%%)" self.caller.msg(string % (rmem, pmem)) return # Display table loadtable = EvTable("property", "statistic", align="l") loadtable.add_row("Total CPU load", "%g %%" % loadavg) - loadtable.add_row("Total computer memory usage","%g MB (%g%%)" % (rmem, pmem)) + loadtable.add_row("Total computer memory usage", "%g MB (%g%%)" % (rmem, pmem)) loadtable.add_row("Process ID", "%g" % pid), else: loadtable = "Not available on Windows without 'psutil' library " \ - "(install with {wpip install psutil{n)." + "(install with |wpip install psutil|n)." else: # Linux / BSD (OSX) - proper pid-based statistics @@ -767,46 +766,49 @@ class CmdServerLoad(COMMAND_DEFAULT_CLASS): loadavg = os.getloadavg()[0] rmem = float(os.popen('ps -p %d -o %s | tail -1' % (pid, "rss")).read()) / 1000.0 # resident memory vmem = float(os.popen('ps -p %d -o %s | tail -1' % (pid, "vsz")).read()) / 1000.0 # virtual memory - pmem = float(os.popen('ps -p %d -o %s | tail -1' % (pid, "%mem")).read()) # percent of resident memory to total + pmem = float(os.popen('ps -p %d -o %s | tail -1' % (pid, "%mem")).read()) # % of resident memory to total rusage = _RESOURCE.getrusage(_RESOURCE.RUSAGE_SELF) if "mem" in self.switches: - string = "Memory usage: RMEM: {w%g{n MB (%g%%), " \ - " VMEM (res+swap+cache): {w%g{n MB." + string = "Memory usage: RMEM: |w%g|n MB (%g%%), VMEM (res+swap+cache): |w%g|n MB." self.caller.msg(string % (rmem, pmem, vmem)) return loadtable = EvTable("property", "statistic", align="l") loadtable.add_row("Server load (1 min)", "%g" % loadavg) loadtable.add_row("Process ID", "%g" % pid), - loadtable.add_row("Memory usage","%g MB (%g%%)" % (rmem, pmem)) + loadtable.add_row("Memory usage", "%g MB (%g%%)" % (rmem, pmem)) loadtable.add_row("Virtual address space", "") - loadtable.add_row("{x(resident+swap+caching){n", "%g MB" % vmem) - loadtable.add_row("CPU time used (total)", "%s (%gs)" % (utils.time_format(rusage.ru_utime), rusage.ru_utime)) - loadtable.add_row("CPU time used (user)", "%s (%gs)" % (utils.time_format(rusage.ru_stime), rusage.ru_stime)) - loadtable.add_row("Page faults", "%g hard, %g soft, %g swapouts" % (rusage.ru_majflt, rusage.ru_minflt, rusage.ru_nswap)) + loadtable.add_row("|x(resident+swap+caching)|n", "%g MB" % vmem) + loadtable.add_row("CPU time used (total)", "%s (%gs)" + % (utils.time_format(rusage.ru_utime), rusage.ru_utime)) + loadtable.add_row("CPU time used (user)", "%s (%gs)" + % (utils.time_format(rusage.ru_stime), rusage.ru_stime)) + loadtable.add_row("Page faults", "%g hard, %g soft, %g swapouts" + % (rusage.ru_majflt, rusage.ru_minflt, rusage.ru_nswap)) loadtable.add_row("Disk I/O", "%g reads, %g writes" % (rusage.ru_inblock, rusage.ru_oublock)) loadtable.add_row("Network I/O", "%g in, %g out" % (rusage.ru_msgrcv, rusage.ru_msgsnd)) - loadtable.add_row("Context switching", "%g vol, %g forced, %g signals" % (rusage.ru_nvcsw, rusage.ru_nivcsw, rusage.ru_nsignals)) - + loadtable.add_row("Context switching", "%g vol, %g forced, %g signals" + % (rusage.ru_nvcsw, rusage.ru_nivcsw, rusage.ru_nsignals)) # os-generic - string = "{wServer CPU and Memory load:{n\n%s" % loadtable + string = "|wServer CPU and Memory load:|n\n%s" % loadtable # object cache count (note that sys.getsiseof is not called so this works for pypy too. total_num, cachedict = _IDMAPPER.cache_size() sorted_cache = sorted([(key, num) for key, num in cachedict.items() if num > 0], - key=lambda tup: tup[1], reverse=True) + key=lambda tup: tup[1], reverse=True) memtable = EvTable("entity name", "number", "idmapper %", align="l") for tup in sorted_cache: memtable.add_row(tup[0], "%i" % tup[1], "%.2f" % (float(tup[1]) / total_num * 100)) - string += "\n{w Entity idmapper cache:{n %i items\n%s" % (total_num, memtable) + string += "\n|w Entity idmapper cache:|n %i items\n%s" % (total_num, memtable) # return to caller self.caller.msg(string) + class CmdTickers(COMMAND_DEFAULT_CLASS): """ View running tickers @@ -832,13 +834,9 @@ class CmdTickers(COMMAND_DEFAULT_CLASS): table = EvTable("interval (s)", "object", "path/methodname", "idstring", "db") for sub in all_subs: table.add_row(sub[3], - "%s%s" % (sub[0] or "[None]", sub[0] and " (#%s)" % (sub[0].id if hasattr(sub[0], "id") else "") or ""), + "%s%s" % (sub[0] or "[None]", + sub[0] and " (#%s)" % (sub[0].id if hasattr(sub[0], "id") else "") or ""), sub[1] if sub[1] else sub[2], sub[4] or "[Unset]", "*" if sub[5] else "-") self.caller.msg("|wActive tickers|n:\n" + unicode(table)) - - - - - From 46da1b9166c4b2c582f004e6b605f7898573d8fd Mon Sep 17 00:00:00 2001 From: BlauFeuer Date: Sat, 18 Feb 2017 21:47:18 -0500 Subject: [PATCH 012/134] Markup, whitespace, LGTP and PEP 8 updates --- evennia/commands/default/unloggedin.py | 89 ++++++++++++-------------- 1 file changed, 40 insertions(+), 49 deletions(-) diff --git a/evennia/commands/default/unloggedin.py b/evennia/commands/default/unloggedin.py index ae8f3b0c0f..9affc8fa46 100644 --- a/evennia/commands/default/unloggedin.py +++ b/evennia/commands/default/unloggedin.py @@ -30,6 +30,8 @@ CONNECTION_SCREEN_MODULE = settings.CONNECTION_SCREEN_MODULE # would also block dummyrunner, so it's not added as default. _LATEST_FAILED_LOGINS = defaultdict(list) + + def _throttle(session, maxlim=None, timeout=None, storage=_LATEST_FAILED_LOGINS): """ This will check the session's address against the @@ -95,8 +97,8 @@ def create_guest_player(session): bans = ServerConfig.objects.conf("server_bans") if bans and any(tup[2].match(session.address) for tup in bans if tup[2]): # this is a banned IP! - string = "{rYou have been banned and cannot continue from here." \ - "\nIf you feel this ban is in error, please email an admin.{x" + string = "|rYou have been banned and cannot continue from here." \ + "\nIf you feel this ban is in error, please email an admin.|x" session.msg(string) session.sessionhandler.disconnect(session, "Good bye! Disconnecting.") return True, None @@ -118,14 +120,11 @@ def create_guest_player(session): permissions = settings.PERMISSION_GUEST_DEFAULT typeclass = settings.BASE_CHARACTER_TYPECLASS ptypeclass = settings.BASE_GUEST_TYPECLASS - new_player = _create_player(session, playername, password, - permissions, ptypeclass) + new_player = _create_player(session, playername, password, permissions, ptypeclass) if new_player: - _create_character(session, new_player, typeclass, - home, permissions) + _create_character(session, new_player, typeclass, home, permissions) return True, new_player - except Exception: # We are in the middle between logged in and -not, so we have # to handle tracebacks ourselves at this point. If we don't, @@ -150,7 +149,7 @@ def create_normal_player(session, name, password): # check for too many login errors too quick. if _throttle(session, maxlim=5, timeout=5*60): # timeout is 5 minutes. - session.msg("{RYou made too many connection attempts. Try again in a few minutes.{n") + session.msg("|RYou made too many connection attempts. Try again in a few minutes.|n") return None # Match account name and check password @@ -167,15 +166,14 @@ def create_normal_player(session, name, password): player.at_failed_login(session) return None - # Check IP and/or name bans bans = ServerConfig.objects.conf("server_bans") - if bans and (any(tup[0]==player.name.lower() for tup in bans) + if bans and (any(tup[0] == player.name.lower() for tup in bans) or any(tup[2].match(session.address) for tup in bans if tup[2])): # this is a banned IP or name! - string = "{rYou have been banned and cannot continue from here." \ - "\nIf you feel this ban is in error, please email an admin.{x" + string = "|rYou have been banned and cannot continue from here." \ + "\nIf you feel this ban is in error, please email an admin.|x" session.msg(string) session.sessionhandler.disconnect(session, "Good bye! Disconnecting.") return None @@ -213,7 +211,7 @@ class CmdUnconnectedConnect(COMMAND_DEFAULT_CLASS): # check for too many login errors too quick. if _throttle(session, maxlim=5, timeout=5*60, storage=_LATEST_FAILED_LOGINS): # timeout is 5 minutes. - session.msg("{RYou made too many connection attempts. Try again in a few minutes.{n") + session.msg("|RYou made too many connection attempts. Try again in a few minutes.|n") return args = self.args @@ -237,12 +235,6 @@ class CmdUnconnectedConnect(COMMAND_DEFAULT_CLASS): name, password = parts player = create_normal_player(session, name, password) if player: - # actually do the login. This will call all other hooks: - # session.at_login() - # player.at_init() # always called when object is loaded from disk - # player.at_first_login() # only once, for player-centric setup - # player.at_pre_login() - # player.at_post_login(session=session) session.sessionhandler.login(session, player) @@ -264,7 +256,7 @@ class CmdUnconnectedCreate(COMMAND_DEFAULT_CLASS): arg_regex = r"\s.*?|$" def func(self): - "Do checks and create account" + """Do checks and create account""" session = self.caller args = self.args.strip() @@ -309,12 +301,12 @@ class CmdUnconnectedCreate(COMMAND_DEFAULT_CLASS): # Check IP and/or name bans bans = ServerConfig.objects.conf("server_bans") - if bans and (any(tup[0]==playername.lower() for tup in bans) + if bans and (any(tup[0] == playername.lower() for tup in bans) or any(tup[2].match(session.address) for tup in bans if tup[2])): # this is a banned IP or name! - string = "{rYou have been banned and cannot continue from here." \ - "\nIf you feel this ban is in error, please email an admin.{x" + string = "|rYou have been banned and cannot continue from here." \ + "\nIf you feel this ban is in error, please email an admin.|x" session.msg(string) session.sessionhandler.disconnect(session, "Good bye! Disconnecting.") return @@ -327,8 +319,7 @@ class CmdUnconnectedCreate(COMMAND_DEFAULT_CLASS): if new_player: if MULTISESSION_MODE < 2: default_home = ObjectDB.objects.get_id(settings.DEFAULT_HOME) - _create_character(session, new_player, typeclass, - default_home, permissions) + _create_character(session, new_player, typeclass, default_home, permissions) # tell the caller everything went well. string = "A new account '%s' was created. Welcome!" if " " in playername: @@ -361,7 +352,7 @@ class CmdUnconnectedQuit(COMMAND_DEFAULT_CLASS): locks = "cmd:all()" def func(self): - "Simply close the connection." + """Simply close the connection.""" session = self.caller session.sessionhandler.disconnect(session, "Good bye! Disconnecting.") @@ -383,7 +374,7 @@ class CmdUnconnectedLook(COMMAND_DEFAULT_CLASS): locks = "cmd:all()" def func(self): - "Show the connect screen." + """Show the connect screen.""" connection_screen = utils.random_string_from_module(CONNECTION_SCREEN_MODULE) if not connection_screen: connection_screen = "No connection screen found. Please contact an admin." @@ -405,25 +396,25 @@ class CmdUnconnectedHelp(COMMAND_DEFAULT_CLASS): locks = "cmd:all()" def func(self): - "Shows help" + """Shows help""" string = \ """ You are not yet logged into the game. Commands available at this point: - {wcreate{n - create a new account - {wconnect{n - connect with an existing account - {wlook{n - re-show the connection screen - {whelp{n - show this help - {wencoding{n - change the text encoding to match your client - {wscreenreader{n - make the server more suitable for use with screen readers - {wquit{n - abort the connection + |wcreate|n - create a new account + |wconnect|n - connect with an existing account + |wlook|n - re-show the connection screen + |whelp|n - show this help + |wencoding|n - change the text encoding to match your client + |wscreenreader|n - make the server more suitable for use with screen readers + |wquit|n - abort the connection -First create an account e.g. with {wcreate Anna c67jHL8p{n -(If you have spaces in your name, use double quotes: {wcreate "Anna the Barbarian" c67jHL8p{n -Next you can connect to the game: {wconnect Anna c67jHL8p{n +First create an account e.g. with |wcreate Anna c67jHL8p|n +(If you have spaces in your name, use double quotes: |wcreate "Anna the Barbarian" c67jHL8p|n +Next you can connect to the game: |wconnect Anna c67jHL8p|n -You can use the {wlook{n command if you want to see the connect screen again. +You can use the |wlook|n command if you want to see the connect screen again. """ self.caller.msg(string) @@ -479,10 +470,10 @@ class CmdUnconnectedEncoding(COMMAND_DEFAULT_CLASS): pencoding = self.session.protocol_flags.get("ENCODING", None) string = "" if pencoding: - string += "Default encoding: {g%s{n (change with {w@encoding {n)" % pencoding + string += "Default encoding: |g%s|n (change with |w@encoding |n)" % pencoding encodings = settings.ENCODINGS if encodings: - string += "\nServer's alternative encodings (tested in this order):\n {g%s{n" % ", ".join(encodings) + string += "\nServer's alternative encodings (tested in this order):\n |g%s|n" % ", ".join(encodings) if not string: string = "No encodings found." else: @@ -492,7 +483,8 @@ class CmdUnconnectedEncoding(COMMAND_DEFAULT_CLASS): try: utils.to_str(utils.to_unicode("test-string"), encoding=encoding) except LookupError: - string = "|rThe encoding '|w%s|r' is invalid. Keeping the previous encoding '|w%s|r'.|n" % (encoding, old_encoding) + string = "|rThe encoding '|w%s|r' is invalid. Keeping the previous encoding '|w%s|r'.|n"\ + % (encoding, old_encoding) else: self.session.protocol_flags["ENCODING"] = encoding string = "Your custom text encoding was changed from '|w%s|n' to '|w%s|n'." % (old_encoding, encoding) @@ -501,6 +493,7 @@ class CmdUnconnectedEncoding(COMMAND_DEFAULT_CLASS): self.session.sessionhandler.session_portal_sync(self.session) self.caller.msg(string.strip()) + class CmdUnconnectedScreenreader(COMMAND_DEFAULT_CLASS): """ Activate screenreader mode. @@ -515,10 +508,10 @@ class CmdUnconnectedScreenreader(COMMAND_DEFAULT_CLASS): aliases = "@screenreader" def func(self): - "Flips screenreader setting." + """Flips screenreader setting.""" new_setting = not self.session.protocol_flags.get("SCREENREADER", False) - self.session.protocol_flags["SCREENREADER"] = new_setting - string = "Screenreader mode turned {w%s{n." % ("on" if new_setting else "off") + self.session.protocol_flags["SCREENREADER"] = new_setting + string = "Screenreader mode turned |w%s|n." % ("on" if new_setting else "off") self.caller.msg(string) self.session.sessionhandler.session_portal_sync(self.session) @@ -528,8 +521,7 @@ def _create_player(session, playername, password, permissions, typeclass=None): Helper function, creates a player of the specified typeclass. """ try: - new_player = create.create_player(playername, None, password, - permissions=permissions, typeclass=typeclass) + new_player = create.create_player(playername, None, password, permissions=permissions, typeclass=typeclass) except Exception as e: session.msg("There was an error creating the Player:\n%s\n If this problem persists, contact an admin." % e) @@ -555,8 +547,7 @@ def _create_character(session, new_player, typeclass, home, permissions): This is meant for Guest and MULTISESSION_MODE < 2 situations. """ try: - new_character = create.create_object(typeclass, key=new_player.key, - home=home, permissions=permissions) + new_character = create.create_object(typeclass, key=new_player.key, home=home, permissions=permissions) # set playable character list new_player.db._playable_characters.append(new_character) From e40b9d14fabc191eec81fa5b1e7867332ff4046d Mon Sep 17 00:00:00 2001 From: BlauFeuer Date: Sat, 18 Feb 2017 22:14:49 -0500 Subject: [PATCH 013/134] Markup update --- evennia/locale/fr/LC_MESSAGES/django.po | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/evennia/locale/fr/LC_MESSAGES/django.po b/evennia/locale/fr/LC_MESSAGES/django.po index c42a0c133d..c1d4c534a9 100644 --- a/evennia/locale/fr/LC_MESSAGES/django.po +++ b/evennia/locale/fr/LC_MESSAGES/django.po @@ -216,20 +216,20 @@ msgstr "" #: server/initial_setup.py:29 msgid "" "\n" -"Welcome to your new {wEvennia{n-based game! Visit http://www.evennia.com if " +"Welcome to your new |wEvennia|n-based game! Visit http://www.evennia.com if " "you need\n" "help, want to contribute, report issues or just join the community.\n" -"As Player #1 you can create a demo/tutorial area with {w@batchcommand " -"tutorial_world.build{n.\n" +"As Player #1 you can create a demo/tutorial area with |w@batchcommand " +"tutorial_world.build|n.\n" " " msgstr "" "\n" -"Bienvenue dans ton nouveau jeu basé sur {wEvennia{n ! Visitez http://www." +"Bienvenue dans ton nouveau jeu basé sur |wEvennia|n ! Visitez http://www." "evennia.com si vous avez besoin\n" "d'aide, si vous voulez contribuer, rapporter des problèmes ou faire partie " "de la communauté.\n" "En tant que Joueur #1 vous pouvez créer une zone de démo/tutoriel avec " -"{w@batchcommand tutorial_world.build{n.\n" +"|w@batchcommand tutorial_world.build|n.\n" " " #: server/initial_setup.py:102 From 619208081132485467c956ff383d2ac546fa7608 Mon Sep 17 00:00:00 2001 From: BlauFeuer Date: Sat, 18 Feb 2017 22:16:39 -0500 Subject: [PATCH 014/134] Markup update --- evennia/locale/it/LC_MESSAGES/django.po | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/evennia/locale/it/LC_MESSAGES/django.po b/evennia/locale/it/LC_MESSAGES/django.po index 9ecf460e2c..6d7c89b2a7 100644 --- a/evennia/locale/it/LC_MESSAGES/django.po +++ b/evennia/locale/it/LC_MESSAGES/django.po @@ -221,16 +221,16 @@ msgstr "Aggiorna l'handler del canale." #: .\server\initial_setup.py:29 msgid "" "\n" -"Welcome to your new {wEvennia{n-based game! Visit http://www.evennia.com if you need\n" +"Welcome to your new |wEvennia|n-based game! Visit http://www.evennia.com if you need\n" "help, want to contribute, report issues or just join the community.\n" -"As Player #1 you can create a demo/tutorial area with {w@batchcommand tutorial_world.build{n.\n" +"As Player #1 you can create a demo/tutorial area with |w@batchcommand tutorial_world.build|n.\n" " " msgstr "" "\n" -"Benvenuto al tuo nuovo gioco creato con {wEvennia{n! Visita http://www.evennia.com se ti\n" +"Benvenuto al tuo nuovo gioco creato con |wEvennia|n! Visita http://www.evennia.com se ti\n" "serve aiuto, se vuoi collaborare, segnalare errori o se desideri unirti alla comunità online.\n" "In qualità di Giocatore #1 puoi creare un'area dimostrativa/tutorial digitando il comando:\n" -"{w@batchcommand tutorial_world.build{n.\n" +"|w@batchcommand tutorial_world.build|n.\n" " " #: .\server\initial_setup.py:99 From 3d9695c86b4a32158b3dcfc2c89a66a07873a573 Mon Sep 17 00:00:00 2001 From: BlauFeuer Date: Sat, 18 Feb 2017 22:18:11 -0500 Subject: [PATCH 015/134] Markup update --- evennia/locale/pt/LC_MESSAGES/django.po | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/evennia/locale/pt/LC_MESSAGES/django.po b/evennia/locale/pt/LC_MESSAGES/django.po index 46ae6f1d9c..b4a794d74f 100644 --- a/evennia/locale/pt/LC_MESSAGES/django.po +++ b/evennia/locale/pt/LC_MESSAGES/django.po @@ -208,19 +208,19 @@ msgstr "" #: server/initial_setup.py:30 msgid "" "\n" -"Welcome to your new {wEvennia{n-based game! Visit http://www.evennia.com if " +"Welcome to your new |wEvennia|n-based game! Visit http://www.evennia.com if " "you need\n" "help, want to contribute, report issues or just join the community.\n" -"As Player #1 you can create a demo/tutorial area with {w@batchcommand " -"tutorial_world.build{n.\n" +"As Player #1 you can create a demo/tutorial area with |w@batchcommand " +"tutorial_world.build|n.\n" " " msgstr "" "\n" -"Bem-vindo a seu novo jogo criado com {wEvennia{n! Visite http://www.evennia.com\n" +"Bem-vindo a seu novo jogo criado com |wEvennia|n! Visite http://www.evennia.com\n" "se você precisar de ajuda, desejar contribuir, reportar bugs ou apenas\n" "juntar-se à comunidade.\n" "Como Player #1 você pode criar uma área demonstrativa/tutorial digitando\n" -"o comando {w@batchcommand tutorial_world.build{n.\n" +"o comando |w@batchcommand tutorial_world.build|n.\n" " " #: server/initial_setup.py:103 From a96ad804c08938a85fd5170f3f0347a99db0c6fe Mon Sep 17 00:00:00 2001 From: BlauFeuer Date: Sat, 18 Feb 2017 22:20:10 -0500 Subject: [PATCH 016/134] Markup update --- evennia/locale/sv/LC_MESSAGES/django.po | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/evennia/locale/sv/LC_MESSAGES/django.po b/evennia/locale/sv/LC_MESSAGES/django.po index 8d122b0234..214cd248c0 100644 --- a/evennia/locale/sv/LC_MESSAGES/django.po +++ b/evennia/locale/sv/LC_MESSAGES/django.po @@ -214,11 +214,11 @@ msgstr "Detta är en generisk lagringskontainer." #: server/initial_setup.py:29 msgid "" "\n" -"Welcome to your new {wEvennia{n-based game! Visit http://www.evennia.com if " +"Welcome to your new |wEvennia|n-based game! Visit http://www.evennia.com if " "you need\n" "help, want to contribute, report issues or just join the community.\n" -"As Player #1 you can create a demo/tutorial area with {w@batchcommand " -"tutorial_world.build{n.\n" +"As Player #1 you can create a demo/tutorial area with |w@batchcommand " +"tutorial_world.build|n.\n" " " msgstr "" From 7e395ea7ec149902a621e129c0984e5658adacb1 Mon Sep 17 00:00:00 2001 From: BlauFeuer Date: Sat, 18 Feb 2017 22:46:34 -0500 Subject: [PATCH 017/134] CmdQuell whitespace adjust to pass tests --- evennia/commands/default/player.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/evennia/commands/default/player.py b/evennia/commands/default/player.py index 2a0d192086..8892e8d5a6 100644 --- a/evennia/commands/default/player.py +++ b/evennia/commands/default/player.py @@ -818,22 +818,22 @@ class CmdQuell(COMMAND_DEFAULT_CLASS): def func(self): """Perform the command""" player = self.player - permstr = player.is_superuser and " (superuser)" or " (%s)" % (", ".join(player.permissions.all())) + permstr = player.is_superuser and " (superuser)" or "(%s)" % (", ".join(player.permissions.all())) if self.cmdstring == '@unquell': if not player.attributes.get('_quell'): - self.msg("Already using normal Player permissions%s." % permstr) + self.msg("Already using normal Player permissions %s." % permstr) else: player.attributes.remove('_quell') - self.msg("Player permissions%s restored." % permstr) + self.msg("Player permissions %s restored." % permstr) else: if player.attributes.get('_quell'): - self.msg("Already quelling Player%s permissions." % permstr) + self.msg("Already quelling Player %s permissions." % permstr) return player.attributes.add('_quell', True) puppet = self.session.puppet if puppet: cpermstr = "(%s)" % ", ".join(puppet.permissions.all()) - cpermstr = "Quelling to current puppet's permissions%s." % cpermstr + cpermstr = "Quelling to current puppet's permissions %s." % cpermstr cpermstr += "\n(Note: If this is higher than Player permissions %s," \ " the lowest of the two will be used.)" % permstr cpermstr += "\nUse @unquell to return to normal permission usage." From 46a5a62d2b8a1499ea4ad29124337f9a08290ce4 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sun, 19 Feb 2017 13:10:17 +0100 Subject: [PATCH 018/134] Add unittests for contrib/chargen and custom_gametime. Removed unused time units settings from custom_gametime. --- evennia/contrib/chargen.py | 35 ++++------ evennia/contrib/custom_gametime.py | 101 +++++++++++++++-------------- evennia/contrib/extended_room.py | 13 ++-- evennia/contrib/tests.py | 84 ++++++++++++++++++------ 4 files changed, 137 insertions(+), 96 deletions(-) diff --git a/evennia/contrib/chargen.py b/evennia/contrib/chargen.py index a80a338b0c..915c96cd3a 100644 --- a/evennia/contrib/chargen.py +++ b/evennia/contrib/chargen.py @@ -2,32 +2,25 @@ Contribution - Griatch 2011 -[Note - with the advent of MULTISESSION_MODE=2, this is not really as +> Note - with the advent of MULTISESSION_MODE=2, this is not really as necessary anymore - the ooclook and @charcreate commands in that mode -replaces this module with better functionality.] +replaces this module with better functionality. This remains here for +inspiration. -This is a simple character creation commandset. A suggestion is to -test this together with menu_login, which doesn't create a Character -on its own. This shows some more info and gives the Player the option -to create a character without any more customizations than their name -(further options are unique for each game anyway). +This is a simple character creation commandset for the Player level. +It shows some more info and gives the Player the option to create a +character without any more customizations than their name (further +options are unique for each game anyway). -Since this extends the OOC cmdset, logging in from the menu will -automatically drop the Player into this cmdset unless they logged off -while puppeting a Character already before. +In MULTISESSION_MODEs 0 and 1, you will automatically log into an +existing Character. When using `@ooc` you will then end up in this +cmdset. Installation: -Read the instructions in contrib/examples/cmdset.py in order to create -a new default cmdset module for Evennia to use (copy the template up -one level, and change the settings file's relevant variables to point -to the cmdsets inside). If you already have such a module you should -of course use that. - -Next import this module in your custom cmdset module and add the -following line to the end of OOCCmdSet's at_cmdset_creation(): - - self.add(chargen.OOCCmdSetCharGen) +Import this module to `mygame/commands/default_cmdsets.py` and +add `chargen.OOCCMdSetCharGen` to the `PlayerCmdSet` class +(it says where to add it). Reload. """ @@ -179,7 +172,7 @@ class CmdOOCCharacterCreate(Command): else: avail_chars = [new_character.id] self.caller.db._character_dbrefs = avail_chars - self.caller.msg("|gThe Character |c%s|g was successfully created!" % charname) + self.caller.msg("|gThe character |c%s|g was successfully created!" % charname) class OOCCmdSetCharGen(default_cmds.PlayerCmdSet): diff --git a/evennia/contrib/custom_gametime.py b/evennia/contrib/custom_gametime.py index 15ab9696ad..199e6b6f87 100644 --- a/evennia/contrib/custom_gametime.py +++ b/evennia/contrib/custom_gametime.py @@ -12,14 +12,23 @@ Usage: Use as the normal gametime module, that is by importing and using the helper functions in this module in your own code. The calendar can be -specified in your settings file by adding and setting custom values -for one or more of the variables `TIME_SECS_PER_MIN`, -`TIME_MINS_PER_HOUR`, `TIME_DAYS_PER_WEEK`, `TIME_WEEKS_PER_MONTH` and -`TIME_MONTHS_PER_YEAR`. These are all given in seconds and whereas -they are called "week", "month" etc these names could represent -whatever fits your game. You can also set `TIME_UNITS` to a dict -mapping the name of a unit to its length in seconds (like `{"min": -60, ...}. If not given, sane defaults will be used. +customized by adding the `TIME_UNITS` dictionary to your settings +file. This maps unit names to their length, expressed in the smallest +unit. Here's the default as an example: + + TIME_UNITS = { + "sec": 1, + "min": 60, + "hr": 60 * 60, + "hour": 60 * 60, + "day": 60 * 60 * 24, + "week": 60 * 60 * 24 * 7, + "month": 60 * 60 * 24 * 7 * 4, + "yr": 60 * 60 * 24 * 7 * 4 * 12, + "year": 60 * 60 * 24 * 7 * 4 * 12, } + +When using a custom calendar, these time unit names are used as kwargs to +the converter functions in this module. """ @@ -28,33 +37,23 @@ mapping the name of a unit to its length in seconds (like `{"min": from django.conf import settings from evennia import DefaultScript from evennia.utils.create import create_script -from evennia.utils.gametime import gametime +from evennia.utils import gametime # The game time speedup / slowdown relative real time TIMEFACTOR = settings.TIME_FACTOR -# Game-time units, in game time seconds. These are supplied as a -# convenient measure for determining the current in-game time, e.g. -# when defining in-game events. The words month, week and year can be -# used to mean whatever units of time are used in your game. -SEC = 1 -MIN = getattr(settings, "TIME_SECS_PER_MIN", 60) -HOUR = getattr(settings, "TIME_MINS_PER_HOUR", 60) * MIN -DAY = getattr(settings, "TIME_HOURS_PER_DAY", 24) * HOUR -WEEK = getattr(settings, "TIME_DAYS_PER_WEEK", 7) * DAY -MONTH = getattr(settings, "TIME_WEEKS_PER_MONTH", 4) * WEEK -YEAR = getattr(settings, "TIME_MONTHS_PER_YEAR", 12) * MONTH -# these are the unit names understood by the scheduler. +# These are the unit names understood by the scheduler. +# Each unit must be consistent and expressed in seconds. UNITS = getattr(settings, "TIME_UNITS", { - "sec": SEC, - "min": MIN, - "hr": HOUR, - "hour": HOUR, - "day": DAY, - "week": WEEK, - "month": MONTH, - "year": YEAR, - "yr": YEAR, -}) + # default custom calendar + "sec": 1, + "min": 60, + "hr": 60 * 60, + "hour": 60 * 60, + "day": 60 * 60 * 24, + "week": 60 * 60 * 24 * 7, + "month": 60 * 60 * 24 * 7 * 4, + "yr": 60 * 60 * 24 * 7 * 4 * 12, + "year": 60 * 60 * 24 * 7 * 4 * 12, }) def time_to_tuple(seconds, *divisors): @@ -92,7 +91,8 @@ def gametime_to_realtime(format=False, **kwargs): Kwargs: format (bool): Formatting the output. - times (int): The various components of the time (must match UNITS). + days, month etc (int): These are the names of time units that must + match the `settings.TIME_UNITS` dict keys. Returns: time (float or tuple): The realtime difference or the same @@ -163,7 +163,7 @@ def custom_gametime(absolute=False): week, day, hour, minute, second). """ - current = gametime(absolute=absolute) + current = gametime.gametime(absolute=absolute) units = sorted(set(UNITS.values()), reverse=True) del units[-1] return time_to_tuple(current, *units) @@ -186,7 +186,7 @@ def real_seconds_until(**kwargs): The number of real seconds before the given game time is up. """ - current = gametime(absolute=True) + current = gametime.gametime(absolute=True) units = sorted(set(UNITS.values()), reverse=True) # Remove seconds from the tuple del units[-1] @@ -232,25 +232,26 @@ def schedule(callback, repeat=False, **kwargs): """ Call the callback when the game time is up. - This function will setup a script that will be called when the - time corresponds to the game time. If the game is stopped for - more than a few seconds, the callback may be called with a slight - delay. If `repeat` is set to True, the callback will be called - again next time the game time matches the given time. The time - is given in units as keyword arguments. For instance: - >>> schedule(func, min=5, sec=0) # Will call next hour at :05. - >>> schedule(func, hour=2, min=30, sec=0) # Will call the next day at 02:30. - Args: - callback (function): the callback function that will be called [1]. - repeat (bool, optional): should the callback be called regularly? - times (str: int): the time to call the callback. - - [1] The callback must be a top-level function, since the script will - be persistent. + callback (function): The callback function that will be called. This + must be a top-level function since the script will be persistent. + repeat (bool, optional): Should the callback be called regularly? + day, month, etc (str: int): The time units to call the callback; should + match the keys of TIME_UNITS. Returns: - The created script (Script). + script (Script): The created script. + + Examples: + schedule(func, min=5, sec=0) # Will call next hour at :05. + schedule(func, hour=2, min=30, sec=0) # Will call the next day at 02:30. + Notes: + This function will setup a script that will be called when the + time corresponds to the game time. If the game is stopped for + more than a few seconds, the callback may be called with a + slight delay. If `repeat` is set to True, the callback will be + called again next time the game time matches the given time. + The time is given in units as keyword arguments. """ seconds = real_seconds_until(**kwargs) diff --git a/evennia/contrib/extended_room.py b/evennia/contrib/extended_room.py index 6cacfeb5b2..e5a8fdd934 100644 --- a/evennia/contrib/extended_room.py +++ b/evennia/contrib/extended_room.py @@ -68,6 +68,7 @@ Installation/testing: """ from __future__ import division +import datetime import re from django.conf import settings from evennia import DefaultRoom @@ -129,12 +130,12 @@ class ExtendedRoom(DefaultRoom): """ Calculate the current time and season ids. """ - # get the current time as parts of year and parts of day - # returns a tuple (years,months,weeks,days,hours,minutes,sec) - time = gametime.gametime(format=True) - month, hour = time[1], time[4] - season = float(month) / MONTHS_PER_YEAR - timeslot = float(hour) / HOURS_PER_DAY + # get the current time as parts of year and parts of day. + # we assume a standard calendar here and use 24h format. + timestamp = gametime.gametime(absolute=True) + datestamp = datetime.datetime.fromtimestamp(timestamp) + season = float(datestamp.month) / MONTHS_PER_YEAR + timeslot = float(datestamp.hour) / HOURS_PER_DAY # figure out which slots these represent if SEASONAL_BOUNDARIES[0] <= season < SEASONAL_BOUNDARIES[1]: diff --git a/evennia/contrib/tests.py b/evennia/contrib/tests.py index 96e8a4e385..455bffc54d 100644 --- a/evennia/contrib/tests.py +++ b/evennia/contrib/tests.py @@ -3,7 +3,6 @@ Testing suite for contrib folder """ -from django.conf import settings from evennia.commands.default.tests import CommandTest from evennia.utils.test_resources import EvenniaTest from mock import Mock @@ -172,40 +171,36 @@ from evennia.contrib import extended_room from evennia import gametime from evennia.objects.objects import DefaultRoom -# mock gametime to return 7th month, 10 in morning -gametime.gametime = Mock(return_value=(None, 7, None, None, 10)) -# mock settings so we're not affected by a given server's hours of day/months in year -settings.TIME_MONTH_PER_YEAR = 12 -settings.TIME_HOUR_PER_DAY = 24 - - class TestExtendedRoom(CommandTest): room_typeclass = extended_room.ExtendedRoom DETAIL_DESC = "A test detail." - SUMMER_DESC = "A summer description." + SPRING_DESC = "A spring description." OLD_DESC = "Old description." def setUp(self): super(TestExtendedRoom, self).setUp() - self.room1.ndb.last_timeslot = "night" + self.room1.ndb.last_timeslot = "afternoon" self.room1.ndb.last_season = "winter" self.room1.db.details = {'testdetail': self.DETAIL_DESC} - self.room1.db.summer_desc = self.SUMMER_DESC + self.room1.db.spring_desc = self.SPRING_DESC self.room1.db.desc = self.OLD_DESC + # mock gametime to return 7th month, 10 in morning + gametime.gametime = Mock(return_value=2975000766) # spring evening def test_return_appearance(self): # get the appearance of a non-extended room for contrast purposes old_desc = DefaultRoom.return_appearance(self.room1, self.char1) # the new appearance should be the old one, but with the desc switched - self.assertEqual(old_desc.replace(self.OLD_DESC, self.SUMMER_DESC), self.room1.return_appearance(self.char1)) - self.assertEqual("summer", self.room1.ndb.last_season) - self.assertEqual("morning", self.room1.ndb.last_timeslot) + self.assertEqual(old_desc.replace(self.OLD_DESC, self.SPRING_DESC), + self.room1.return_appearance(self.char1)) + self.assertEqual("spring", self.room1.ndb.last_season) + self.assertEqual("evening", self.room1.ndb.last_timeslot) def test_return_detail(self): self.assertEqual(self.DETAIL_DESC, self.room1.return_detail("testdetail")) def test_cmdextendedlook(self): - self.call(extended_room.CmdExtendedLook(), "here", "Room(#1)\n%s" % self.SUMMER_DESC) + self.call(extended_room.CmdExtendedLook(), "here", "Room(#1)\n%s" % self.SPRING_DESC) self.call(extended_room.CmdExtendedLook(), "testdetail", self.DETAIL_DESC) self.call(extended_room.CmdExtendedLook(), "nonexistent", "Could not find 'nonexistent'.") @@ -220,15 +215,13 @@ class TestExtendedRoom(CommandTest): self.call(extended_room.CmdExtendedDesc(), "", "Descriptions on Room:") def test_cmdgametime(self): - self.call(extended_room.CmdGameTime(), "", "It's a summer day, in the morning.") + self.call(extended_room.CmdGameTime(), "", "It's a spring day, in the evening.") # Test the contrib barter system -from evennia import create_object from evennia.contrib import barter - class TestBarter(CommandTest): def setUp(self): @@ -309,11 +302,11 @@ class TestBarter(CommandTest): self.call(barter.CmdTradeHelp(), "", "Trading commands\n", caller=self.char1) self.call(barter.CmdFinish(), ": Ending.", "You say, \"Ending.\"\n [You aborted trade. No deal was made.]") +# Test wilderness from evennia.contrib import wilderness from evennia import DefaultCharacter - class TestWilderness(EvenniaTest): def setUp(self): @@ -429,3 +422,56 @@ class TestWilderness(EvenniaTest): for direction, correct_loc in directions.iteritems(): # Not compatible with Python 3 new_loc = wilderness.get_new_coordinates(loc, direction) self.assertEquals(new_loc, correct_loc, direction) + +# Testing chargen contrib +from evennia.contrib import chargen + +class TestChargen(CommandTest): + + def test_ooclook(self): + self.call(chargen.CmdOOCLook(), "foo", "You have no characters to look at", caller=self.player) + self.call(chargen.CmdOOCLook(), "", "You, TestPlayer, are an OOC ghost without form.", caller=self.player) + + def test_charcreate(self): + self.call(chargen.CmdOOCCharacterCreate(), "testchar", "The character testchar was successfully created!", caller=self.player) + self.call(chargen.CmdOOCCharacterCreate(), "testchar", "Character testchar already exists.", caller=self.player) + self.assertTrue(self.player.db._character_dbrefs) + self.call(chargen.CmdOOCLook(), "", "You, TestPlayer, are an OOC ghost without form.",caller=self.player) + self.call(chargen.CmdOOCLook(), "testchar", "testchar(", caller=self.player) + +# Testing custom_gametime +from evennia.contrib import custom_gametime + +def _testcallback(): + pass + +class TestCustomGameTime(EvenniaTest): + def setUp(self): + super(TestCustomGameTime, self).setUp() + gametime.gametime = Mock(return_value=2975000898.46) # does not seem to work + def tearDown(self): + if hasattr(self, "timescript"): + self.timescript.stop() + def test_time_to_tuple(self): + self.assertEqual(custom_gametime.time_to_tuple(10000, 34,2,4,6,1), (294, 2, 0, 0, 0, 0)) + self.assertEqual(custom_gametime.time_to_tuple(10000, 3,3,4), (3333, 0, 0, 1)) + self.assertEqual(custom_gametime.time_to_tuple(100000, 239,24,3), (418, 4, 0, 2)) + def test_gametime_to_realtime(self): + self.assertEqual(custom_gametime.gametime_to_realtime(days=2, mins=4), 86520.0) + self.assertEqual(custom_gametime.gametime_to_realtime(format=True, days=2), (0,0,0,1,0,0,0)) + def test_realtime_to_gametime(self): + self.assertEqual(custom_gametime.realtime_to_gametime(days=2, mins=34), 349680.0) + self.assertEqual(custom_gametime.realtime_to_gametime(days=2, mins=34, format=True), (0, 0, 0, 4, 1, 8, 0)) + self.assertEqual(custom_gametime.realtime_to_gametime(format=True, days=2, mins=4), (0, 0, 0, 4, 0, 8, 0)) + def test_custom_gametime(self): + self.assertEqual(custom_gametime.custom_gametime(), (102, 5, 2, 6, 21, 8, 18)) + self.assertEqual(custom_gametime.custom_gametime(absolute=True), (102, 5, 2, 6, 21, 8, 18)) + def test_real_seconds_until(self): + self.assertEqual(custom_gametime.real_seconds_until(year=2300, month=11, day=6), 31911667199.77) + def test_schedule(self): + self.timescript = custom_gametime.schedule(_testcallback, repeat=True, min=5, sec=0) + self.assertEqual(self.timescript.interval, 1700.7699999809265) + + + + From e9a6465c6501abe478c384af48bbf209d3f5ba90 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sun, 19 Feb 2017 13:41:56 +0100 Subject: [PATCH 019/134] Add unittest to contrib.dice as per #1105. --- evennia/contrib/tests.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/evennia/contrib/tests.py b/evennia/contrib/tests.py index 455bffc54d..7d11d97fa0 100644 --- a/evennia/contrib/tests.py +++ b/evennia/contrib/tests.py @@ -5,7 +5,7 @@ Testing suite for contrib folder from evennia.commands.default.tests import CommandTest from evennia.utils.test_resources import EvenniaTest -from mock import Mock +from mock import Mock, patch # Testing of rplanguage module @@ -472,6 +472,23 @@ class TestCustomGameTime(EvenniaTest): self.timescript = custom_gametime.schedule(_testcallback, repeat=True, min=5, sec=0) self.assertEqual(self.timescript.interval, 1700.7699999809265) +# Test dice module + + +@patch('random.randint', return_value=5) +class TestDice(CommandTest): + def test_roll_dice(self, mocked_randint): + # we must import dice here for the mocked randint to apply correctly. + from evennia.contrib import dice + self.assertEqual(dice.roll_dice(6, 6, modifier=('+', 4)), mocked_randint()*6 + 4) + self.assertEqual(dice.roll_dice(6, 6, conditional=('<', 35)), True) + self.assertEqual(dice.roll_dice(6, 6, conditional=('>', 33)), False) + def test_cmddice(self, mocked_randint): + from evennia.contrib import dice + self.call(dice.CmdDice(), "3d6 + 4", "You roll 3d6 + 4.| Roll(s): 5, 5 and 5. Total result is 19.") + self.call(dice.CmdDice(), "100000d1000", "The maximum roll allowed is 10000d10000.") + self.call(dice.CmdDice(), "/secret 3d6 + 4", "You roll 3d6 + 4 (secret, not echoed).") + From 1b9016d26a98fed0a155b462ad9730788a013cd7 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sun, 19 Feb 2017 14:35:42 +0100 Subject: [PATCH 020/134] Add unittests to email_login contrib and also update it to work correctly with latest code. Pertains to #1105. --- evennia/commands/default/unloggedin.py | 8 +- evennia/contrib/email_login.py | 123 ++++++++++--------------- evennia/contrib/tests.py | 14 ++- 3 files changed, 67 insertions(+), 78 deletions(-) diff --git a/evennia/commands/default/unloggedin.py b/evennia/commands/default/unloggedin.py index 9affc8fa46..65734611df 100644 --- a/evennia/commands/default/unloggedin.py +++ b/evennia/commands/default/unloggedin.py @@ -12,7 +12,7 @@ from evennia.objects.models import ObjectDB from evennia.server.models import ServerConfig from evennia.comms.models import ChannelDB -from evennia.utils import create, logger, utils, ansi +from evennia.utils import create, logger, utils from evennia.commands.cmdhandler import CMD_LOGINSTART COMMAND_DEFAULT_CLASS = utils.class_from_module(settings.COMMAND_DEFAULT_CLASS) @@ -516,12 +516,12 @@ class CmdUnconnectedScreenreader(COMMAND_DEFAULT_CLASS): self.session.sessionhandler.session_portal_sync(self.session) -def _create_player(session, playername, password, permissions, typeclass=None): +def _create_player(session, playername, password, permissions, typeclass=None, email=None): """ Helper function, creates a player of the specified typeclass. """ try: - new_player = create.create_player(playername, None, password, permissions=permissions, typeclass=typeclass) + new_player = create.create_player(playername, email, password, permissions=permissions, typeclass=typeclass) except Exception as e: session.msg("There was an error creating the Player:\n%s\n If this problem persists, contact an admin." % e) @@ -535,7 +535,7 @@ def _create_player(session, playername, password, permissions, typeclass=None): # join the new player to the public channel pchannel = ChannelDB.objects.get_channel(settings.DEFAULT_CHANNELS[0]["key"]) - if not pchannel.connect(new_player): + if not pchannel or not pchannel.connect(new_player): string = "New player '%s' could not connect to public channel!" % new_player.key logger.log_err(string) return new_player diff --git a/evennia/contrib/email_login.py b/evennia/contrib/email_login.py index 468b6837b6..0684e91514 100644 --- a/evennia/contrib/email_login.py +++ b/evennia/contrib/email_login.py @@ -118,7 +118,7 @@ class CmdUnconnectedConnect(MuxCommand): # actually do the login. This will call all hooks. session.sessionhandler.login(session, player) - +from evennia.commands.default import unloggedin as default_unloggedin class CmdUnconnectedCreate(MuxCommand): """ Create a new account. @@ -161,108 +161,86 @@ class CmdUnconnectedCreate(MuxCommand): """Do checks and create account""" session = self.caller - try: playername, email, password = self.playerinfo except ValueError: string = "\n\r Usage (without <>): create \"\" " session.msg(string) return - if not re.findall('^[\w. @+-]+$', playername) or not (0 < len(playername) <= 30): - session.msg("\n\r Playername can be max 30 characters, or less. Letters, spaces," - " digits and @/./+/-/_ only.") # this echoes the restrictions made by django's auth module. - return if not email or not password: session.msg("\n\r You have to supply an e-mail address followed by a password.") return - if not utils.validate_email_address(email): # check so the email at least looks ok. session.msg("'%s' is not a valid e-mail address." % email) return - - # Run sanity and security checks - - if PlayerDB.objects.filter(username=playername): - # player already exists + # sanity checks + if not re.findall(r"^[\w. @+\-']+$", playername) or not (0 < len(playername) <= 30): + # this echoes the restrictions made by django's auth + # module (except not allowing spaces, for convenience of + # logging in). + string = "\n\r Playername can max be 30 characters or fewer. Letters, spaces, digits and @/./+/-/_/' only." + session.msg(string) + return + # strip excessive spaces in playername + playername = re.sub(r"\s+", " ", playername).strip() + if PlayerDB.objects.filter(username__iexact=playername): + # player already exists (we also ignore capitalization here) session.msg("Sorry, there is already a player with the name '%s'." % playername) return if PlayerDB.objects.get_player_from_email(email): # email already set on a player session.msg("Sorry, there is already a player with that email address.") return - if len(password) < 3: - # too short password - string = "Your password must be at least 3 characters or longer." - string += "\n\rFor best security, make it at least 8 characters long, " - string += "avoid making it a real word and mix numbers into it." + # Reserve playernames found in GUEST_LIST + if settings.GUEST_LIST and playername.lower() in (guest.lower() for guest in settings.GUEST_LIST): + string = "\n\r That name is reserved. Please choose another Playername." session.msg(string) return + if not re.findall(r"^[\w. @+\-']+$", password) or not (3 < len(password)): + string = "\n\r Password should be longer than 3 characers. Letters, spaces, digits and @/./+/-/_/' only." \ + "\nFor best security, make it longer than 8 characters. You can also use a phrase of" \ + "\nmany words if you enclose the password in double quotes." + session.msg(string) + return + + # Check IP and/or name bans + bans = ServerConfig.objects.conf("server_bans") + if bans and (any(tup[0] == playername.lower() for tup in bans) + or + any(tup[2].match(session.address) for tup in bans if tup[2])): + # this is a banned IP or name! + string = "|rYou have been banned and cannot continue from here." \ + "\nIf you feel this ban is in error, please email an admin.|x" + session.msg(string) + session.sessionhandler.disconnect(session, "Good bye! Disconnecting.") + return # everything's ok. Create the new player account. try: - default_home = ObjectDB.objects.get_id(settings.DEFAULT_HOME) - - typeclass = settings.BASE_CHARACTER_TYPECLASS permissions = settings.PERMISSION_PLAYER_DEFAULT - - try: - new_player = create.create_player(playername, email, password, permissions=permissions) - - except Exception as e: - session.msg("There was an error creating the default Player/Character:\n%s\n" - " If this problem persists, contact an admin." % e) - logger.log_trace() - return - - # This needs to be set so the engine knows this player is - # logging in for the first time. (so it knows to call the right - # hooks during login later) - new_player.db.FIRST_LOGIN = True - - # join the new player to the public channel - pchanneldef = settings.CHANNEL_PUBLIC - if pchanneldef: - pchannel = ChannelDB.objects.get_channel(pchanneldef[0]) - if not pchannel.connect(new_player): - string = "New player '%s' could not connect to public channel!" % new_player.key - logger.log_err(string) - - if MULTISESSION_MODE < 2: - # if we only allow one character, create one with the same name as Player - # (in mode 2, the character must be created manually once logging in) - new_character = create.create_object(typeclass, key=playername, - location=default_home, home=default_home, - permissions=permissions) - # set playable character list - new_player.db._playable_characters.append(new_character) - - # allow only the character itself and the player to puppet this character (and Immortals). - new_character.locks.add("puppet:id(%i) or pid(%i) or perm(Immortals) or pperm(Immortals)" % - (new_character.id, new_player.id)) - - # If no description is set, set a default description - if not new_character.db.desc: - new_character.db.desc = "This is a Player." - # We need to set this to have @ic auto-connect to this character - new_player.db._last_puppet = new_character - - # tell the caller everything went well. - string = "A new account '%s' was created. Welcome!" - if " " in playername: - string += "\n\nYou can now log in with the command 'connect %s '." - else: - string += "\n\nYou can now log with the command 'connect %s '." - session.msg(string % (playername, email)) + typeclass = settings.BASE_CHARACTER_TYPECLASS + new_player = default_unloggedin._create_player(session, playername, password, permissions, email=email) + if new_player: + if MULTISESSION_MODE < 2: + default_home = ObjectDB.objects.get_id(settings.DEFAULT_HOME) + default_unloggedin._create_character(session, new_player, typeclass, default_home, permissions) + # tell the caller everything went well. + string = "A new account '%s' was created. Welcome!" + if " " in playername: + string += "\n\nYou can now log in with the command 'connect \"%s\" '." + else: + string += "\n\nYou can now log with the command 'connect %s '." + session.msg(string % (playername, email)) except Exception: # We are in the middle between logged in and -not, so we have # to handle tracebacks ourselves at this point. If we don't, # we won't see any errors at all. - session.msg("An error occurred. Please e-mail an admin if the problem persists.") + raise + session.msg("%sAn error occurred. Please e-mail an admin if the problem persists.") logger.log_trace() - class CmdUnconnectedQuit(MuxCommand): """ We maintain a different version of the `quit` command @@ -276,8 +254,7 @@ class CmdUnconnectedQuit(MuxCommand): def func(self): """Simply close the connection.""" session = self.caller - session.msg("Good bye! Disconnecting ...") - session.session_disconnect() + session.sessionhandler.disconnect(session, "Good bye! Disconnecting.") class CmdUnconnectedLook(MuxCommand): diff --git a/evennia/contrib/tests.py b/evennia/contrib/tests.py index 7d11d97fa0..a8850fc791 100644 --- a/evennia/contrib/tests.py +++ b/evennia/contrib/tests.py @@ -489,6 +489,18 @@ class TestDice(CommandTest): self.call(dice.CmdDice(), "100000d1000", "The maximum roll allowed is 10000d10000.") self.call(dice.CmdDice(), "/secret 3d6 + 4", "You roll 3d6 + 4 (secret, not echoed).") +# Test email-login +from evennia.contrib import email_login - +class TestEmailLogin(CommandTest): + def test_connect(self): + self.call(email_login.CmdUnconnectedConnect(), "mytest@test.com test", "The email 'mytest@test.com' does not match any accounts.") + self.call(email_login.CmdUnconnectedCreate(), '"mytest" mytest@test.com test11111', "A new account 'mytest' was created. Welcome!") + self.call(email_login.CmdUnconnectedConnect(), "mytest@test.com test11111", "", caller=self.player.sessions.get()[0]) + def test_quit(self): + self.call(email_login.CmdUnconnectedQuit(), "", "", caller=self.player.sessions.get()[0]) + def test_unconnectedlook(self): + self.call(email_login.CmdUnconnectedLook(), "", "==========") + def test_unconnectedhelp(self): + self.call(email_login.CmdUnconnectedHelp(), "", "You are not yet logged into the game.") From 7e762245c8ed71b7164a8805aa1203249b62542a Mon Sep 17 00:00:00 2001 From: Griatch Date: Sun, 19 Feb 2017 14:54:40 +0100 Subject: [PATCH 021/134] Add unittests for gendersub contrib as per #1105. --- evennia/contrib/gendersub.py | 24 ++++++++++++------------ evennia/contrib/tests.py | 14 ++++++++++++++ 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/evennia/contrib/gendersub.py b/evennia/contrib/gendersub.py index 3c985df669..2d3b13de86 100644 --- a/evennia/contrib/gendersub.py +++ b/evennia/contrib/gendersub.py @@ -12,18 +12,18 @@ When in use, all messages being sent to the character will make use of the character's gender, for example the echo ``` -char.msg("%s falls on {p face with a thud." % char.key) +char.msg("%s falls on |p face with a thud." % char.key) ``` -will result in "Tom falls on his|her|its face with a thud" depending -on the gender of the object being messaged. Default gender is -"neutral". +will result in "Tom falls on his|her|its|their face with a thud" +depending on the gender of the object being messaged. Default gender +is "ambiguous" (they). To use, have DefaultCharacter inherit from this, or change setting.DEFAULT_CHARACTER to point to this class. -The `@gender` command needs to be added to the default cmdset -before it becomes available. +The `@gender` command needs to be added to the default cmdset before +it becomes available. """ @@ -50,7 +50,7 @@ _GENDER_PRONOUN_MAP = {"male": {"s": "he", "p": "their", "a": "theirs"} } -_RE_GENDER_PRONOUN = re.compile(r'({s|{S|{o|{O|{p|{P|{a|{A)') +_RE_GENDER_PRONOUN = re.compile(r'(\|s|\|S|\|o|\|O|\|p|\|P|\|a|\|A)') # in-game command for setting the gender @@ -76,7 +76,7 @@ class SetGender(Command): caller.msg("Usage: @gender male|female|neutral|ambiguous") return caller.db.gender = arg - caller.msg("Your gender was set to %s." % arg) + caller.msg("Your gender was set to %s." % arg) # Gender-aware character class @@ -103,10 +103,10 @@ class GenderCharacter(DefaultCharacter): regex_match (MatchObject): the regular expression match. Notes: - - `{s`, `{S`: Subjective form: he, she, it, He, She, It, They - - `{o`, `{O`: Objective form: him, her, it, Him, Her, It, Them - - `{p`, `{P`: Possessive form: his, her, its, His, Her, Its, Their - - `{a`, `{A`: Absolute Possessive form: his, hers, its, His, Hers, Its, Theirs + - `|s`, `|S`: Subjective form: he, she, it, He, She, It, They + - `|o`, `|O`: Objective form: him, her, it, Him, Her, It, Them + - `|p`, `|P`: Possessive form: his, her, its, His, Her, Its, Their + - `|a`, `|A`: Absolute Possessive form: his, hers, its, His, Hers, Its, Theirs """ typ = regex_match.group()[1] # "s", "O" etc diff --git a/evennia/contrib/tests.py b/evennia/contrib/tests.py index a8850fc791..5559fa92c0 100644 --- a/evennia/contrib/tests.py +++ b/evennia/contrib/tests.py @@ -504,3 +504,17 @@ class TestEmailLogin(CommandTest): self.call(email_login.CmdUnconnectedLook(), "", "==========") def test_unconnectedhelp(self): self.call(email_login.CmdUnconnectedHelp(), "", "You are not yet logged into the game.") + +# test gendersub contrib + +from evennia.contrib import gendersub + +class TestGenderSub(CommandTest): + def test_setgender(self): + self.call(gendersub.SetGender(), "male", "Your gender was set to male.") + self.call(gendersub.SetGender(), "ambiguous", "Your gender was set to ambiguous.") + self.call(gendersub.SetGender(), "Foo", "Usage: @gender") + def test_gendercharacter(self): + char = create_object(gendersub.GenderCharacter, key="Gendered", location=self.room1) + txt = "Test |p gender" + self.assertEqual(gendersub._RE_GENDER_PRONOUN.sub(char._get_pronoun, txt), "Test their gender") From 23cfd3ba177898320157d39bb7a168f43e938726 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sun, 19 Feb 2017 17:36:35 +0100 Subject: [PATCH 022/134] Make attribute cache behavior consistent with the non-cached return when returning non-found objects as per #1226. --- evennia/contrib/tests.py | 5 +++++ evennia/typeclasses/attributes.py | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/evennia/contrib/tests.py b/evennia/contrib/tests.py index 5559fa92c0..3952b8200f 100644 --- a/evennia/contrib/tests.py +++ b/evennia/contrib/tests.py @@ -518,3 +518,8 @@ class TestGenderSub(CommandTest): char = create_object(gendersub.GenderCharacter, key="Gendered", location=self.room1) txt = "Test |p gender" self.assertEqual(gendersub._RE_GENDER_PRONOUN.sub(char._get_pronoun, txt), "Test their gender") + +# test mail contrib + +class TestMail(CommandTest): + diff --git a/evennia/typeclasses/attributes.py b/evennia/typeclasses/attributes.py index 3514ce284d..ef34a4ce3b 100644 --- a/evennia/typeclasses/attributes.py +++ b/evennia/typeclasses/attributes.py @@ -222,7 +222,7 @@ class AttributeHandler(object): attr.db_category.lower() if attr.db_category else None), attr) for attr in attrs) self._cache_complete = True - + def _getcache(self, key=None, category=None): """ Retrieve from cache or database (always caches) @@ -290,7 +290,7 @@ class AttributeHandler(object): # for this category before catkey = "-%s" % category if _TYPECLASS_AGGRESSIVE_CACHE and catkey in self._catcache: - return [attr for key, attr in self._cache.items() if key.endswith(catkey)] + return [attr for key, attr in self._cache.items() if key.endswith(catkey) and attr] else: # we have to query to make this category up-date in the cache query = {"%s__id" % self._model : self._objid, From 108dd35ee7bca304077e50145a14f6dbc44b8e92 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sun, 19 Feb 2017 19:24:00 +0100 Subject: [PATCH 023/134] Add unittest for mail contrib; fix some inconsistencies and refactor to better handle errors. --- evennia/contrib/mail.py | 151 +++++++++++++++++++++++---------------- evennia/contrib/tests.py | 17 ++++- 2 files changed, 106 insertions(+), 62 deletions(-) diff --git a/evennia/contrib/mail.py b/evennia/contrib/mail.py index 2397632ad2..4482542de2 100644 --- a/evennia/contrib/mail.py +++ b/evennia/contrib/mail.py @@ -7,10 +7,13 @@ A simple Brandymail style @mail system that uses the Msg class from Evennia Core Installation: import MailCommand from this module into the default Player or Character command set + """ +import re +from evennia import ObjectDB, PlayerDB from evennia import default_cmds -from evennia.utils import create, evtable +from evennia.utils import create, evtable, make_iter from evennia.comms.models import Msg @@ -56,9 +59,63 @@ class CmdMail(default_cmds.MuxCommand): lock = "cmd:all()" help_category = "General" + def search_targets(self, namelist): + """ + Search a list of targets of the same type as caller. + + Args: + caller (Object or Player): The type of object to search. + namelist (list): List of strings for objects to search for. + + Returns: + targetlist (list): List of matches, if any. + + """ + nameregex = r"|".join(r"^%s$" % re.escape(name) for name in make_iter(namelist)) + if hasattr(self.caller, "player") and self.caller.player: + matches = list(ObjectDB.objects.filter(db_key__iregex=nameregex)) + else: + matches = list(PlayerDB.objects.filter(username__iregex=nameregex)) + return matches + + def get_all_mail(self): + """ + Returns a list of all the messages where the caller is a recipient. + + Returns: + messages (list): list of Msg objects. + """ + # mail_messages = Msg.objects.get_by_tag(category="mail") + # messages = [] + messages = Msg.objects.get_by_tag(category="mail", raw_queryset=True).filter(db_receivers_players=self.caller) + return messages + + def send_mail(self, recipients, subject, message, caller): + """ + Function for sending new mail. Also useful for sending notifications from objects or systems. + + Args: + recipients (list): list of Player or character objects to receive the newly created mails. + subject (str): The header or subject of the message to be delivered. + message (str): The body of the message being sent. + caller (obj): The object (or Player or Character) that is sending the message. + """ + for recipient in recipients: + recipient.msg("You have received a new @mail from %s" % caller) + new_message = create.create_message(self.caller, message, receivers=recipient, header=subject) + new_message.tags.add("U", category="mail") + + if recipients: + caller.msg("You sent your message.") + return + else: + caller.msg("No valid players found. Cannot send message.") + return + def func(self): subject = "" body = "" + if self.switches or self.args: if "delete" in self.switches: try: @@ -66,12 +123,15 @@ class CmdMail(default_cmds.MuxCommand): self.caller.msg("No Message ID given. Unable to delete.") return else: - if self.get_all_mail()[int(self.lhs) - 1]: - self.get_all_mail()[int(self.lhs) - 1].delete() + all_mail = self.get_all_mail() + mind = int(self.lhs) - 1 + if all_mail[mind]: + all_mail[mind].delete() self.caller.msg("Message %s deleted" % self.lhs) else: - self.caller.msg("That message does not exist.") - return + raise IndexError + except IndexError: + self.caller.msg("That message does not exist.") except ValueError: self.caller.msg("Usage: @mail/delete ") elif "forward" in self.switches: @@ -83,29 +143,33 @@ class CmdMail(default_cmds.MuxCommand): self.caller.msg("You must define a message to forward.") return else: + all_mail = self.get_all_mail() if "/" in self.rhs: message_number, message = self.rhs.split("/") - if self.get_all_mail()[int(message_number) - 1]: - old_message = self.get_all_mail()[int(message_number) - 1] + mind = int(message_number) - 1 - self.send_mail(self.lhslist, "FWD: " + old_message.header, + if all_mail[mind]: + old_message = all_mail[mind] + + self.send_mail(self.search_targets(self.lhslist), "FWD: " + old_message.header, message + "\n---- Original Message ----\n" + old_message.message, self.caller) self.caller.msg("Message forwarded.") else: - self.caller.msg("Message does not exist.") - return + raise IndexError else: - if self.get_all_mail()[int(self.rhs) - 1]: - old_message = self.get_all_mail()[int(self.rhs) - 1] - self.send_mail(self.lhslist, "FWD: " + old_message.header, + mind = int(self.rhs) - 1 + if all_mail[mind]: + old_message = all_mail[mind] + self.send_mail(self.search_targets(self.lhslist), "FWD: " + old_message.header, "\n---- Original Message ----\n" + old_message.message, self.caller) self.caller.msg("Message forwarded.") old_message.tags.remove("u", category="mail") old_message.tags.add("f", category="mail") else: - self.caller.msg("Message does not exist.") - return + raise IndexError + except IndexError: + self.caller.msg("Message does not exixt.") except ValueError: self.caller.msg("Usage: @mail/forward =<#>[/]") elif "reply" in self.switches: @@ -117,29 +181,33 @@ class CmdMail(default_cmds.MuxCommand): self.caller.msg("You must supply a reply message") return else: - if self.get_all_mail()[int(self.lhs) - 1]: - old_message = self.get_all_mail()[int(self.lhs) - 1] + all_mail = self.get_all_mail() + mind = int(self.lhs) - 1 + if all_mail[mind]: + old_message = all_mail[mind] self.send_mail(old_message.senders, "RE: " + old_message.header, self.rhs + "\n---- Original Message ----\n" + old_message.message, self.caller) old_message.tags.remove("u", category="mail") old_message.tags.add("r", category="mail") return else: - self.caller.msg("Message does not exist.") - return + raise IndexError + except IndexError: + self.caller.msg("Message does not exist.") except ValueError: self.caller.msg("Usage: @mail/reply <#>=") else: + # normal send if self.rhs: if "/" in self.rhs: subject, body = self.rhs.split("/", 1) else: body = self.rhs - self.send_mail(self.lhslist, subject, body, self.caller) + self.send_mail(self.search_targets(self.lhslist), subject, body, self.caller) else: try: message = self.get_all_mail()[int(self.lhs) - 1] - except ValueError: + except (ValueError, IndexError): self.caller.msg("'%s' is not a valid mail id." % self.lhs) return @@ -176,47 +244,8 @@ class CmdMail(default_cmds.MuxCommand): table.reformat_column(4, width=7) self.caller.msg(_HEAD_CHAR * _WIDTH) - self.caller.msg(table) + self.caller.msg(unicode(table)) self.caller.msg(_HEAD_CHAR * _WIDTH) else: self.caller.msg("Sorry, you don't have any messages. What a pathetic loser!") - def get_all_mail(self): - """ - Returns a list of all the messages where the caller is a recipient. - - Returns: - messages (list): list of Msg objects. - """ - # mail_messages = Msg.objects.get_by_tag(category="mail") - # messages = [] - messages = Msg.objects.get_by_tag(category="mail", raw_queryset=True).filter(db_receivers_players=self.caller.player) - return messages - - def send_mail(self, recipients, subject, message, caller): - """ - Function for sending new mail. Also useful for sending notifications from objects or systems. - - Args: - recipients (list): list of Player or character objects to receive the newly created mails. - subject (str): The header or subject of the message to be delivered. - message (str): The body of the message being sent. - caller (obj): The object (or Player or Character) that is sending the message. - """ - recobjs = [] - for char in recipients: - - if self.caller.player.search(char) is not None: - recobjs.append(self.caller.player.search(char)) - if recobjs: - for recipient in recobjs: - recipient.msg("You have received a new @mail from %s" % caller) - - new_message = create.create_message(self.caller, message, receivers=recipient, header=subject) - new_message.tags.add("U", category="mail") - - caller.msg("You sent your message.") - return - else: - caller.msg("No valid players found. Cannot send message.") - return diff --git a/evennia/contrib/tests.py b/evennia/contrib/tests.py index 3952b8200f..85bdb5558d 100644 --- a/evennia/contrib/tests.py +++ b/evennia/contrib/tests.py @@ -521,5 +521,20 @@ class TestGenderSub(CommandTest): # test mail contrib -class TestMail(CommandTest): +from evennia.contrib import mail +class TestMail(CommandTest): + def test_mail(self): + self.call(mail.CmdMail(), "2", "'2' is not a valid mail id.", caller=self.player) + self.call(mail.CmdMail(), "", "Sorry, you don't have any messages.", caller=self.player) + self.call(mail.CmdMail(), "Char=Message 1", "You have received a new @mail from Char|You sent your message.", caller=self.char1) + self.call(mail.CmdMail(), "Char=Message 2", "You sent your message.", caller=self.char2) + self.call(mail.CmdMail(), "TestPlayer2=Message 2", + "You have received a new @mail from TestPlayer2(player 2)|You sent your message.", caller=self.player2) + self.call(mail.CmdMail(), "TestPlayer=Message 1", "You sent your message.", caller=self.player2) + self.call(mail.CmdMail(), "TestPlayer=Message 2", "You sent your message.", caller=self.player2) + self.call(mail.CmdMail(), "", "| ID: From: Subject:", caller=self.player) + self.call(mail.CmdMail(), "2", "From: TestPlayer2", caller=self.player) + self.call(mail.CmdMail(), "/forward TestPlayer2 = 1/Forward message", "You sent your message.|Message forwarded.", caller=self.player) + self.call(mail.CmdMail(), "/reply 2=Reply Message2", "You sent your message.", caller=self.player) + self.call(mail.CmdMail(), "/delete 2", "Message 2 deleted", caller=self.player) From 42c13ee87095b6c9a11017b2cfd761b7cc19f715 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sun, 19 Feb 2017 20:21:07 +0100 Subject: [PATCH 024/134] Add unit tests for mapbuilder, menu_login and multidescer contribs, as per #1105. --- evennia/contrib/mapbuilder.py | 150 ++++++++++++++++++---------------- evennia/contrib/tests.py | 53 ++++++++++++ 2 files changed, 134 insertions(+), 69 deletions(-) diff --git a/evennia/contrib/mapbuilder.py b/evennia/contrib/mapbuilder.py index 5f50908cc5..1b00630cc3 100644 --- a/evennia/contrib/mapbuilder.py +++ b/evennia/contrib/mapbuilder.py @@ -287,6 +287,87 @@ def _map_to_list(game_map): else character for character in list_map] +def build_map(caller, game_map, legend, iterations=1, build_exits=True): + """ + Receives the fetched map and legend vars provided by the player. + + Args: + caller (Object): The creator of the map. + game_map (str): An ASCII map string. + legend (dict): Mapping of map symbols to object types. + iterations (int): The number of iteration passes. + build_exits (bool): Create exits between new rooms. + + Notes: + The map + is iterated over character by character, comparing it to the trigger + characters in the legend var and executing the build instructions on + finding a match. The map is iterated over according to the `iterations` + value and exits are optionally generated between adjacent rooms according + to the `build_exits` value. + + """ + + # Split map string to list of rows and create reference list. + caller.msg("Creating Map...") + caller.msg(game_map) + game_map = _map_to_list(game_map) + + # Create a reference dictionary which be passed to build functions and + # will store obj returned by build functions so objs can be referenced. + room_dict = {} + + caller.msg("Creating Landmass...") + for iteration in xrange(iterations): + for y in xrange(len(game_map)): + for x in xrange(len(game_map[y])): + for key in legend: + if game_map[y][x] in key: + room = legend[key](x, y, iteration=iteration, + room_dict=room_dict, + caller=caller) + if iteration == 0: + room_dict[(x, y)] = room + + if build_exits: + # Creating exits. Assumes single room object in dict entry + caller.msg("Connecting Areas...") + for loc_key, location in room_dict.iteritems(): + x = loc_key[0] + y = loc_key[1] + + # north + if (x, y-1) in room_dict: + if room_dict[(x, y-1)]: + create_object(exits.Exit, key="north", + aliases=["n"], location=location, + destination=room_dict[(x, y-1)]) + + # east + if (x+1, y) in room_dict: + if room_dict[(x+1, y)]: + create_object(exits.Exit, key="east", + aliases=["e"], location=location, + destination=room_dict[(x+1, y)]) + + # south + if (x, y+1) in room_dict: + if room_dict[(x, y+1)]: + create_object(exits.Exit, key="south", + aliases=["s"], location=location, + destination=room_dict[(x, y+1)]) + + # west + if (x-1, y) in room_dict: + if room_dict[(x-1, y)]: + create_object(exits.Exit, key="west", + aliases=["w"], location=location, + destination=room_dict[(x-1, y)]) + + caller.msg("Map Created.") + +# access command + class CmdMapBuilder(COMMAND_DEFAULT_CLASS): """ Build a map from a 2D ASCII map. @@ -396,72 +477,3 @@ class CmdMapBuilder(COMMAND_DEFAULT_CLASS): # Pass map and legend to the build function. build_map(caller, game_map, legend, iterations, build_exits) - -def build_map(caller, game_map, legend, iterations=1, build_exits=True): - """ - Receives the fetched map and legend vars provided by the player. The map - is iterated over character by character, comparing it to the trigger - characters in the legend var and executing the build instructions on - finding a match. The map is iterated over according to the `iterations` - value and exits are optionally generated between adjacent rooms according - to the `build_exits` value. - - """ - - # Split map string to list of rows and create reference list. - caller.msg("Creating Map...") - caller.msg(game_map) - game_map = _map_to_list(game_map) - - # Create a reference dictionary which be passed to build functions and - # will store obj returned by build functions so objs can be referenced. - room_dict = {} - - caller.msg("Creating Landmass...") - for iteration in xrange(iterations): - for y in xrange(len(game_map)): - for x in xrange(len(game_map[y])): - for key in legend: - if game_map[y][x] in key: - room = legend[key](x, y, iteration=iteration, - room_dict=room_dict, - caller=caller) - if iteration == 0: - room_dict[(x, y)] = room - - if build_exits: - # Creating exits. Assumes single room object in dict entry - caller.msg("Connecting Areas...") - for loc_key, location in room_dict.iteritems(): - x = loc_key[0] - y = loc_key[1] - - # north - if (x, y-1) in room_dict: - if room_dict[(x, y-1)]: - create_object(exits.Exit, key="north", - aliases=["n"], location=location, - destination=room_dict[(x, y-1)]) - - # east - if (x+1, y) in room_dict: - if room_dict[(x+1, y)]: - create_object(exits.Exit, key="east", - aliases=["e"], location=location, - destination=room_dict[(x+1, y)]) - - # south - if (x, y+1) in room_dict: - if room_dict[(x, y+1)]: - create_object(exits.Exit, key="south", - aliases=["s"], location=location, - destination=room_dict[(x, y+1)]) - - # west - if (x-1, y) in room_dict: - if room_dict[(x-1, y)]: - create_object(exits.Exit, key="west", - aliases=["w"], location=location, - destination=room_dict[(x-1, y)]) - - caller.msg("Map Created.") diff --git a/evennia/contrib/tests.py b/evennia/contrib/tests.py index 85bdb5558d..e99a7a5e11 100644 --- a/evennia/contrib/tests.py +++ b/evennia/contrib/tests.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """ Testing suite for contrib folder @@ -538,3 +539,55 @@ class TestMail(CommandTest): self.call(mail.CmdMail(), "/forward TestPlayer2 = 1/Forward message", "You sent your message.|Message forwarded.", caller=self.player) self.call(mail.CmdMail(), "/reply 2=Reply Message2", "You sent your message.", caller=self.player) self.call(mail.CmdMail(), "/delete 2", "Message 2 deleted", caller=self.player) + +# test map builder contrib + +from evennia.contrib import mapbuilder + +class TestMapBuilder(CommandTest): + def test_cmdmapbuilder(self): + self.call(mapbuilder.CmdMapBuilder(), + "evennia.contrib.mapbuilder.EXAMPLE1_MAP evennia.contrib.mapbuilder.EXAMPLE1_LEGEND", +"""Creating Map...|≈≈≈≈≈ +≈♣n♣≈ +≈∩▲∩≈ +≈♠n♠≈ +≈≈≈≈≈ +|Creating Landmass...|""") + self.call(mapbuilder.CmdMapBuilder(), + "evennia.contrib.mapbuilder.EXAMPLE2_MAP evennia.contrib.mapbuilder.EXAMPLE2_LEGEND", +"""Creating Map...|≈ ≈ ≈ ≈ ≈ + +≈ ♣♣♣ ≈ + ≈ ♣ ♣ ♣ ≈ + ≈ ♣♣♣ ≈ + +≈ ≈ ≈ ≈ ≈ +|Creating Landmass...|""") + + +# test menu_login + +from evennia.contrib import menu_login + +class TestMenuLogin(CommandTest): + def test_cmdunloggedlook(self): + self.call(menu_login.CmdUnloggedinLook(), "", "======") + + +# test multidescer contrib + +from evennia.contrib import multidescer + +class TestMultidescer(CommandTest): + def test_cmdmultidesc(self): + self.call(multidescer.CmdMultiDesc(),"/list", "Stored descs:\ncaller:") + self.call(multidescer.CmdMultiDesc(),"test = Desc 1", "Stored description 'test': \"Desc 1\"") + self.call(multidescer.CmdMultiDesc(),"test2 = Desc 2", "Stored description 'test2': \"Desc 2\"") + self.call(multidescer.CmdMultiDesc(),"/swap test-test2", "Swapped descs 'test' and 'test2'.") + self.call(multidescer.CmdMultiDesc(),"test3 = Desc 3init", "Stored description 'test3': \"Desc 3init\"") + self.call(multidescer.CmdMultiDesc(),"/list", "Stored descs:\ntest3: Desc 3init\ntest: Desc 1\ntest2: Desc 2\ncaller:") + self.call(multidescer.CmdMultiDesc(),"test3 = Desc 3", "Stored description 'test3': \"Desc 3\"") + self.call(multidescer.CmdMultiDesc(),"/set test1 + test2 + + test3", "test1 Desc 2 Desc 3\n\n" + "The above was set as the current description.") + self.assertEqual(self.char1.db.desc, "test1 Desc 2 Desc 3") From a0b1a0cac1c5c72149203676b3768f6f79b4c905 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sun, 19 Feb 2017 21:06:03 +0100 Subject: [PATCH 025/134] Add unittests for simpledoor and slow_exit contribs, as per #1105. --- evennia/contrib/slow_exit.py | 3 ++- evennia/contrib/tests.py | 28 ++++++++++++++++++++++++++++ evennia/objects/objects.py | 1 + 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/evennia/contrib/slow_exit.py b/evennia/contrib/slow_exit.py index 06eccf2eb0..0a1097ed42 100644 --- a/evennia/contrib/slow_exit.py +++ b/evennia/contrib/slow_exit.py @@ -138,6 +138,7 @@ class CmdStop(Command): if currently_moving: currently_moving.cancel() self.caller.msg("You stop moving.") - self.caller.location.msg_contents("%s stops." % self.get_display_name()) + for observer in self.caller.location.contents_get(self.caller): + observer.msg("%s stops." % self.caller.get_display_name(observer)) else: self.caller.msg("You are not moving.") diff --git a/evennia/contrib/tests.py b/evennia/contrib/tests.py index e99a7a5e11..3748c94557 100644 --- a/evennia/contrib/tests.py +++ b/evennia/contrib/tests.py @@ -591,3 +591,31 @@ class TestMultidescer(CommandTest): self.call(multidescer.CmdMultiDesc(),"/set test1 + test2 + + test3", "test1 Desc 2 Desc 3\n\n" "The above was set as the current description.") self.assertEqual(self.char1.db.desc, "test1 Desc 2 Desc 3") + +# test simpledoor contrib + +from evennia.contrib import simpledoor + +class TestSimpleDoor(CommandTest): + def test_cmdopen(self): + self.call(simpledoor.CmdOpen(), "newdoor;door:contrib.simpledoor.SimpleDoor,backdoor;door = Room2", + "Created new Exit 'newdoor' from Room to Room2 (aliases: door).|Note: A doortype exit was " + "created ignored eventual custom returnexit type.|Created new Exit 'newdoor' from Room2 to Room (aliases: door).") + self.call(simpledoor.CmdOpenCloseDoor(), "newdoor", "You close newdoor.", cmdstring="close") + self.call(simpledoor.CmdOpenCloseDoor(), "newdoor", "newdoor is already closed.", cmdstring="close") + self.call(simpledoor.CmdOpenCloseDoor(), "newdoor", "You open newdoor.", cmdstring="open") + self.call(simpledoor.CmdOpenCloseDoor(), "newdoor", "newdoor is already open.", cmdstring="open") + +# test slow_exit contrib + +from evennia.contrib import slow_exit +slow_exit.MOVE_DELAY = {"stroll":0, "walk": 0, "run": 0, "sprint": 0} + +class TestSlowExit(CommandTest): + def test_exit(self): + exi = create_object(slow_exit.SlowExit, key="slowexit", location=self.room1, destination=self.room2) + exi.at_traverse(self.char1, self.room2) + self.call(slow_exit.CmdSetSpeed(), "walk", "You are now walking.") + self.call(slow_exit.CmdStop(), "", "You stop moving.") + + diff --git a/evennia/objects/objects.py b/evennia/objects/objects.py index 3dda680476..b22ce84d74 100644 --- a/evennia/objects/objects.py +++ b/evennia/objects/objects.py @@ -584,6 +584,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): "{attacker} {action} {defender}", mapping=dict(attacker=char, defender=npc, action=action), exclude=(char, npc)) + """ contents = self.contents if exclude: From fd4f72f4d0ee639878c849b889abea0c90d0ca85 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sun, 19 Feb 2017 21:30:24 +0100 Subject: [PATCH 026/134] Add unittest for talking_npc contrib as per #1105. --- evennia/contrib/tests.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/evennia/contrib/tests.py b/evennia/contrib/tests.py index 3748c94557..d6c9a2325f 100644 --- a/evennia/contrib/tests.py +++ b/evennia/contrib/tests.py @@ -618,4 +618,13 @@ class TestSlowExit(CommandTest): self.call(slow_exit.CmdSetSpeed(), "walk", "You are now walking.") self.call(slow_exit.CmdStop(), "", "You stop moving.") +# test talking npc contrib + +from evennia.contrib import talking_npc + +class TestTalkingNPC(CommandTest): + def test_talkingnpc(self): + create_object(talking_npc.TalkingNPC, key="npctalker", location=self.room1) + self.call(talking_npc.CmdTalk(), "","(You walk up and talk to Char.)|") + From 5a723697e7dbad3a00d1919753fc8c7da7baa9f1 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sun, 19 Feb 2017 23:48:24 +0100 Subject: [PATCH 027/134] Add unittests to tutorial world mob, objects and most rooms as per #1105. --- evennia/commands/default/tests.py | 4 +- evennia/contrib/tests.py | 98 ++++++++++++++++++++++- evennia/contrib/tutorial_world/objects.py | 12 +-- evennia/contrib/tutorial_world/rooms.py | 4 +- 4 files changed, 108 insertions(+), 10 deletions(-) diff --git a/evennia/commands/default/tests.py b/evennia/commands/default/tests.py index c61304710a..1fc6b210e7 100644 --- a/evennia/commands/default/tests.py +++ b/evennia/commands/default/tests.py @@ -38,7 +38,7 @@ class CommandTest(EvenniaTest): Tests a command """ - def call(self, cmdobj, args, msg=None, cmdset=None, noansi=True, caller=None, receiver=None, cmdstring=None): + def call(self, cmdobj, args, msg=None, cmdset=None, noansi=True, caller=None, receiver=None, cmdstring=None, obj=None): """ Test a command by assigning all the needed properties to cmdobj and running @@ -58,7 +58,7 @@ class CommandTest(EvenniaTest): cmdobj.session = SESSIONS.session_from_sessid(1) cmdobj.player = self.player cmdobj.raw_string = cmdobj.key + " " + args - cmdobj.obj = caller if caller else self.char1 + cmdobj.obj = obj or (caller if caller else self.char1) # test old_msg = receiver.msg try: diff --git a/evennia/contrib/tests.py b/evennia/contrib/tests.py index d6c9a2325f..f3730c24d9 100644 --- a/evennia/contrib/tests.py +++ b/evennia/contrib/tests.py @@ -624,7 +624,103 @@ from evennia.contrib import talking_npc class TestTalkingNPC(CommandTest): def test_talkingnpc(self): - create_object(talking_npc.TalkingNPC, key="npctalker", location=self.room1) + npc = create_object(talking_npc.TalkingNPC, key="npctalker", location=self.room1) self.call(talking_npc.CmdTalk(), "","(You walk up and talk to Char.)|") + npc.delete() +# tests for the tutorial world + +# test tutorial_world/mob + +from evennia.contrib.tutorial_world import mob + +class TestTutorialWorldMob(EvenniaTest): + def test_mob(self): + mobobj = create_object(mob.Mob, key="mob") + self.assertEqual(mobobj.db.is_dead, True) + mobobj.set_alive() + self.assertEqual(mobobj.db.is_dead, False) + mobobj.set_dead() + self.assertEqual(mobobj.db.is_dead, True) + mobobj._set_ticker(0, "foo", stop=True) + #TODO should be expanded with further tests of the modes and damage etc. + +# test tutorial_world/objects + +from evennia.contrib.tutorial_world import objects as tutobjects + +class TestTutorialWorldObjects(CommandTest): + def test_tutorialobj(self): + obj1 = create_object(tutobjects.TutorialObject, key="tutobj") + obj1.reset() + self.assertEqual(obj1.location, obj1.home) + def test_readable(self): + readable = create_object(tutobjects.Readable, key="book", location=self.room1) + readable.db.readable_text = "Text to read" + self.call(tutobjects.CmdRead(), "book","You read book:\n Text to read", obj=readable) + def test_climbable(self): + climbable = create_object(tutobjects.Climbable, key="tree", location=self.room1) + self.call(tutobjects.CmdClimb(), "tree", "You climb tree. Having looked around, you climb down again.", obj=climbable) + self.assertEqual(self.char1.tags.get("tutorial_climbed_tree", category="tutorial_world"), "tutorial_climbed_tree") + def test_obelisk(self): + obelisk = create_object(tutobjects.Obelisk, key="obelisk", location=self.room1) + self.assertEqual(obelisk.return_appearance(self.char1).startswith("|cobelisk("), True) + def test_lightsource(self): + light = create_object(tutobjects.LightSource, key="torch", location=self.room1) + self.call(tutobjects.CmdLight(), "", "You light torch.", obj=light) + light._burnout() + if hasattr(light, "deferred"): + light.deferred.cancel() + self.assertFalse(light.pk) + def test_crumblingwall(self): + wall = create_object(tutobjects.CrumblingWall, key="wall", location=self.room1) + self.assertFalse(wall.db.button_exposed) + self.assertFalse(wall.db.exit_open) + wall.db.root_pos = {"yellow":0, "green":0,"red":0,"blue":0} + self.call(tutobjects.CmdShiftRoot(), "blue root right", + "You shove the root adorned with small blue flowers to the right.", obj=wall) + self.call(tutobjects.CmdShiftRoot(), "red root left", + "You shift the reddish root to the left.", obj=wall) + self.call(tutobjects.CmdShiftRoot(), "yellow root down", + "You shove the root adorned with small yellow flowers downwards.", obj=wall) + self.call(tutobjects.CmdShiftRoot(), "green root up", + "You shift the weedy green root upwards.|Holding aside the root you think you notice something behind it ...", obj=wall) + self.call(tutobjects.CmdPressButton(), "", + "You move your fingers over the suspicious depression, then gives it a decisive push. First", obj=wall) + self.assertTrue(wall.db.button_exposed) + self.assertTrue(wall.db.exit_open) + wall.reset() + if hasattr(wall, "deferred"): + wall.deferred.cancel() + wall.delete() + def test_weapon(self): + weapon = create_object(tutobjects.Weapon, key="sword", location=self.char1) + self.call(tutobjects.CmdAttack(), "Char", "You stab with sword.", obj=weapon, cmdstring="stab") + self.call(tutobjects.CmdAttack(), "Char", "You slash with sword.", obj=weapon, cmdstring="slash") + def test_weaponrack(self): + rack = create_object(tutobjects.WeaponRack, key="rack", location=self.room1) + rack.db.available_weapons = ["sword"] + self.call(tutobjects.CmdGetWeapon(), "", "You find Rusty sword.", obj=rack) + +# test tutorial_world/ +from evennia.contrib.tutorial_world import rooms as tutrooms + +class TestTutorialWorldRooms(CommandTest): + def test_cmdtutorial(self): + room = create_object(tutrooms.TutorialRoom, key="tutroom") + self.char1.location = room + self.call(tutrooms.CmdTutorial(), "", "Sorry, there is no tutorial help available here.") + self.call(tutrooms.CmdTutorialSetDetail(), "detail;foo;foo2 = A detail", "Detail set: 'detail;foo;foo2': 'A detail'", obj=room) + self.call(tutrooms.CmdTutorialLook(), "", "tutroom(", obj=room) + self.call(tutrooms.CmdTutorialLook(), "detail", "A detail", obj=room) + self.call(tutrooms.CmdTutorialLook(), "foo", "A detail", obj=room) + room.delete() + def test_weatherroom(self): + room = create_object(tutrooms.WeatherRoom, key="weatherroom") + room.update_weather() + tutrooms.TICKER_HANDLER.remove(interval=room.db.interval, callback=room.update_weather, idstring="tutorial") + room.delete() + def test_introroom(self): + room = create_object(tutrooms.IntroRoom, key="introroom") + room.at_object_receive(self.char1, self.room1) diff --git a/evennia/contrib/tutorial_world/objects.py b/evennia/contrib/tutorial_world/objects.py index 061066ccfa..40e298595b 100644 --- a/evennia/contrib/tutorial_world/objects.py +++ b/evennia/contrib/tutorial_world/objects.py @@ -367,8 +367,9 @@ class LightSource(TutorialObject): pass finally: # start the burn timer. When it runs out, self._burnout - # will be called. - utils.delay(60 * 3, self._burnout) + # will be called. We store the deferred so it can be + # killed in unittesting. + self.deferred = utils.delay(60 * 3, self._burnout) return True @@ -636,9 +637,10 @@ class CrumblingWall(TutorialObject, DefaultExit): self.caller.msg("The exit leads nowhere, there's just more stone behind it ...") else: self.destination = eloc[0] - self.exit_open = True - # start a 45 second timer before closing again - utils.delay(45, self.reset) + self.db.exit_open = True + # start a 45 second timer before closing again. We store the deferred so it can be + # killed in unittesting. + self.deferred = utils.delay(45, self.reset) def _translate_position(self, root, ipos): """Translates the position into words""" diff --git a/evennia/contrib/tutorial_world/rooms.py b/evennia/contrib/tutorial_world/rooms.py index 86a6681833..2ec387032d 100644 --- a/evennia/contrib/tutorial_world/rooms.py +++ b/evennia/contrib/tutorial_world/rooms.py @@ -313,8 +313,8 @@ class WeatherRoom(TutorialRoom): # subscribe ourselves to a ticker to repeatedly call the hook # "update_weather" on this object. The interval is randomized # so as to not have all weather rooms update at the same time. - interval = random.randint(50, 70) - TICKER_HANDLER.add(interval=interval, callback=self.update_weather, idstring="tutorial") + self.db.interval = random.randint(50, 70) + TICKER_HANDLER.add(interval=self.db.interval, callback=self.update_weather, idstring="tutorial") # this is parsed by the 'tutorial' command on TutorialRooms. self.db.tutorial_info = \ "This room has a Script running that has it echo a weather-related message at irregular intervals." From 695cc4d2188ba4ed68211b725a2b08c5db0e823e Mon Sep 17 00:00:00 2001 From: Griatch Date: Mon, 20 Feb 2017 00:11:08 +0100 Subject: [PATCH 028/134] Add basic unittests for the rest of the tutorial_world rooms. This concludes #1105. --- evennia/contrib/tests.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/evennia/contrib/tests.py b/evennia/contrib/tests.py index f3730c24d9..8d878ef203 100644 --- a/evennia/contrib/tests.py +++ b/evennia/contrib/tests.py @@ -724,3 +724,22 @@ class TestTutorialWorldRooms(CommandTest): def test_introroom(self): room = create_object(tutrooms.IntroRoom, key="introroom") room.at_object_receive(self.char1, self.room1) + def test_bridgeroom(self): + room = create_object(tutrooms.BridgeRoom, key="bridgeroom") + room.update_weather() + self.char1.move_to(room) + self.call(tutrooms.CmdBridgeHelp(), "", "You are trying hard not to fall off the bridge ...", obj=room) + self.call(tutrooms.CmdLookBridge(), "", "bridgeroom\nYou are standing very close to the the bridge's western foundation.", obj=room) + room.at_object_leave(self.char1, self.room1) + tutrooms.TICKER_HANDLER.remove(interval=room.db.interval, callback=room.update_weather, idstring="tutorial") + room.delete() + def test_darkroom(self): + room = create_object(tutrooms.DarkRoom, key="darkroom") + self.char1.move_to(room) + self.call(tutrooms.CmdDarkHelp(), "", "Can't help you until") + def test_teleportroom(self): + create_object(tutrooms.TeleportRoom, key="teleportroom") + def test_outroroom(self): + create_object(tutrooms.OutroRoom, key="outroroom") + + From dc1545cb27e16213719da848fde682ce7dadcc3d Mon Sep 17 00:00:00 2001 From: BlauFeuer Date: Mon, 20 Feb 2017 01:36:58 -0500 Subject: [PATCH 029/134] Docstring, comments and whitespace fixes --- evennia/utils/ansi.py | 51 +++++++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/evennia/utils/ansi.py b/evennia/utils/ansi.py index f3f34de79e..f445f2d002 100644 --- a/evennia/utils/ansi.py +++ b/evennia/utils/ansi.py @@ -5,12 +5,15 @@ Use the codes defined in ANSIPARSER in your text to apply colour to text according to the ANSI standard. Examples: - This is %crRed text%cn and this is normal again. - This is {rRed text{n and this is normal again. + This is |rRed text|n and this is normal again. + This is {rRed text{n and this is normal again. # soon to be depreciated + This is %crRed text%cn and this is normal again. # depreciated + Mostly you should not need to call parse_ansi() explicitly; it is run by Evennia just before returning data to/from the -user. +user. Depreciated example forms are available by extending +the ansi mapping. """ from builtins import object, range @@ -133,7 +136,7 @@ class ANSIParser(object): rgbtag = rgbmatch.group()[1:] background = rgbtag[0] == '[' - grayscale = rgbtag[0 + int(background)] == '=' + grayscale = rgbtag[0 + int(background)] == '=' if not grayscale: # 6x6x6 color-cube (xterm indexes 16-231) if background: @@ -143,9 +146,9 @@ class ANSIParser(object): else: # grayscale values (xterm indexes 0, 232-255, 15) for full spectrum letter = rgbtag[int(background) + 1] - if (letter == 'a'): + if letter == 'a': colval = 16 # pure black @ index 16 (first color cube entry) - elif (letter == 'z'): + elif letter == 'z': colval = 231 # pure white @ index 231 (last color cube entry) else: # letter in range [b..y] (exactly 24 values!) @@ -161,8 +164,8 @@ class ANSIParser(object): colval = 16 + (red * 36) + (green * 6) + blue return "\033[%s8;5;%sm" % (3 + int(background), colval) - # replaced since some cliens (like Potato) does not accept codes with leading zeroes, see issue #1024. - #return "\033[%s8;5;%s%s%sm" % (3 + int(background), colval // 100, (colval % 100) // 10, colval%10) + # replaced since some clients (like Potato) does not accept codes with leading zeroes, see issue #1024. + # return "\033[%s8;5;%s%s%sm" % (3 + int(background), colval // 100, (colval % 100) // 10, colval%10) else: # xterm256 not supported, convert the rgb value to ansi instead @@ -289,7 +292,7 @@ class ANSIParser(object): in_string = utils.to_str(string) # do string replacement - parsed_string = "" + parsed_string = "" parts = self.ansi_escapes.split(in_string) + [" "] for part, sep in zip(parts[::2], parts[1::2]): pstring = self.xterm256_sub.sub(do_xterm256, part) @@ -307,7 +310,7 @@ class ANSIParser(object): # cache and crop old cache _PARSE_CACHE[cachekey] = parsed_string if len(_PARSE_CACHE) > _PARSE_CACHE_SIZE: - _PARSE_CACHE.popitem(last=False) + _PARSE_CACHE.popitem(last=False) return parsed_string @@ -321,8 +324,8 @@ class ANSIParser(object): (r'{/', ANSI_RETURN), # line break (r'{-', ANSI_TAB), # tab (r'{_', ANSI_SPACE), # space - (r'{*', ANSI_INVERSE), # invert - (r'{^', ANSI_BLINK), # blinking text (very annoying and not supported by all clients) + (r'{*', ANSI_INVERSE), # invert + (r'{^', ANSI_BLINK), # blinking text (very annoying and not supported by all clients) (r'{u', ANSI_UNDERLINE), # underline (r'{r', hilite + ANSI_RED), @@ -366,14 +369,14 @@ class ANSIParser(object): (r'{[W', ANSI_BACK_WHITE), # light grey background (r'{[X', ANSI_BACK_BLACK), # pure black background - ## alternative |-format + # alternative |-format (r'|n', ANSI_NORMAL), # reset (r'|/', ANSI_RETURN), # line break (r'|-', ANSI_TAB), # tab (r'|_', ANSI_SPACE), # space - (r'|*', ANSI_INVERSE), # invert - (r'|^', ANSI_BLINK), # blinking text (very annoying and not supported by all clients) + (r'|*', ANSI_INVERSE), # invert + (r'|^', ANSI_BLINK), # blinking text (very annoying and not supported by all clients) (r'|u', ANSI_UNDERLINE), # underline (r'|r', hilite + ANSI_RED), @@ -433,8 +436,7 @@ class ANSIParser(object): (r'{[w', r'{[555'), # white background (r'{[x', r'{[222'), # dark grey background - ## |-style variations - + # |-style variations (r'|[r', r'|[500'), (r'|[g', r'|[050'), (r'|[y', r'|[550'), @@ -448,13 +450,13 @@ class ANSIParser(object): # the sub_xterm256 method xterm256_map = [ - (r'\{[0-5]{3}', ""), # {123 - foreground colour + (r'\{[0-5]{3}', ""), # {123 - foreground colour (r'\{\[[0-5]{3}', ""), # {[123 - background colour - ## |-style - (r'\|[0-5]{3}', ""), # |123 - foreground colour - (r'\|\[[0-5]{3}', ""), # |[123 - background colour + # |-style + (r'\|[0-5]{3}', ""), # |123 - foreground colour + (r'\|\[[0-5]{3}', ""), # |[123 - background colour - ## grayscale entries including ansi extremes: {=a .. {=z + # grayscale entries including ansi extremes: {=a .. {=z (r'\{=[a-z]', ""), (r'\{\[=[a-z]', ""), (r'\|=[a-z]', ""), @@ -512,6 +514,7 @@ def strip_ansi(string, parser=ANSI_PARSER): markup. Args: + string (str): The string to strip. parser (ansi.AnsiParser, optional): The parser to use. Returns: @@ -520,6 +523,7 @@ def strip_ansi(string, parser=ANSI_PARSER): """ return parser.parse_ansi(string, strip_ansi=True) + def strip_raw_ansi(string, parser=ANSI_PARSER): """ Remove raw ansi codes from string. This assumes pure @@ -1056,7 +1060,7 @@ class ANSIString(with_metaclass(ANSIMeta, unicode)): res.append(self[start:len(self)]) if drop_spaces: - return [part for part in res if part != ""] + return [part for part in res if part != ""] return res def rsplit(self, by=None, maxsplit=-1): @@ -1129,7 +1133,6 @@ class ANSIString(with_metaclass(ANSIMeta, unicode)): rstripped = rstripped[::-1] return ANSIString(lstripped + raw[ir1:ir2+1] + rstripped) - def lstrip(self, chars=None): """ Strip from the left, taking ANSI markers into account. From 5111fb9a1e5f79339f6e35d812a6cb1ef01d8104 Mon Sep 17 00:00:00 2001 From: BlauFeuer Date: Mon, 20 Feb 2017 01:41:43 -0500 Subject: [PATCH 030/134] Markup, whitespace, typo and code style fixes --- evennia/utils/utils.py | 63 ++++++++++++++++++++++++------------------ 1 file changed, 36 insertions(+), 27 deletions(-) diff --git a/evennia/utils/utils.py b/evennia/utils/utils.py index 4d1a3b6633..22f5b2d326 100644 --- a/evennia/utils/utils.py +++ b/evennia/utils/utils.py @@ -44,6 +44,7 @@ _DA = object.__delattr__ _DEFAULT_WIDTH = settings.CLIENT_DEFAULT_WIDTH + def is_iter(iterable): """ Checks if an object behaves iterably. @@ -62,6 +63,7 @@ def is_iter(iterable): """ return hasattr(iterable, '__iter__') + def make_iter(obj): """ Makes sure that the object is always iterable. @@ -201,7 +203,7 @@ def justify(text, width=_DEFAULT_WIDTH, align="f", indent=0): distribute odd spaces randomly to one of the gaps. """ line_rest = width - (wlen + ngaps) - gap = " " # minimum gap between words + gap = " " # minimum gap between words if line_rest > 0: if align == 'l': line[-1] += " " * line_rest @@ -211,7 +213,7 @@ def justify(text, width=_DEFAULT_WIDTH, align="f", indent=0): pad = " " * (line_rest // 2) line[0] = pad + line[0] line[-1] = line[-1] + pad + " " * (line_rest % 2) - else: # align 'f' + else: # align 'f' gap += " " * (line_rest // max(1, ngaps)) rest_gap = line_rest % max(1, ngaps) for i in range(rest_gap): @@ -250,8 +252,7 @@ def justify(text, width=_DEFAULT_WIDTH, align="f", indent=0): wlen += word[1] ngaps += 1 - - if line: # catch any line left behind + if line: # catch any line left behind lines.append(_process_line(line)) indentstring = " " * indent return "\n".join([indentstring + line for line in lines]) @@ -360,7 +361,7 @@ def time_format(seconds, style=0): minutes = seconds // 60 seconds -= minutes * 60 - retval = "" + retval = "" if style == 0: """ Standard colon-style output. @@ -522,13 +523,13 @@ def pypath_to_realpath(python_path, file_ending='.py', pypath_prefixes=None): """ path = python_path.strip().split('.') plong = osjoin(*path) + file_ending - pshort = osjoin(*path[1:]) + file_ending if len(path) > 1 else plong # in case we had evennia. or mygame. + pshort = osjoin(*path[1:]) + file_ending if len(path) > 1 else plong # in case we had evennia. or mygame. prefixlong = [osjoin(*ppath.strip().split('.')) - for ppath in make_iter(pypath_prefixes)] \ - if pypath_prefixes else [] + for ppath in make_iter(pypath_prefixes)] \ + if pypath_prefixes else [] prefixshort = [osjoin(*ppath.strip().split('.')[1:]) - for ppath in make_iter(pypath_prefixes) if len(ppath.strip().split('.')) > 1] \ - if pypath_prefixes else [] + for ppath in make_iter(pypath_prefixes) if len(ppath.strip().split('.')) > 1]\ + if pypath_prefixes else [] paths = [plong] + \ [osjoin(_EVENNIA_DIR, prefix, plong) for prefix in prefixlong] + \ [osjoin(_GAME_DIR, prefix, plong) for prefix in prefixlong] + \ @@ -616,6 +617,7 @@ dbid_to_obj = dbref_to_obj _UNICODE_MAP = {"EM DASH": "-", "FIGURE DASH": "-", "EN DASH": "-", "HORIZONTAL BAR": "-", "HORIZONTAL ELLIPSIS": "...", "RIGHT SINGLE QUOTATION MARK": "'"} + def latinify(unicode_string, default='?', pure_ascii=False): """ Convert a unicode string to "safe" ascii/latin-1 characters. @@ -643,7 +645,7 @@ def latinify(unicode_string, default='?', pure_ascii=False): # point name; e.g., since `name(u'á') == 'LATIN SMALL # LETTER A WITH ACUTE'` translate `á` to `a`. However, in # some cases the unicode name is still "LATIN LETTER" - # although no direct equivalent in the Latin alphabeth + # although no direct equivalent in the Latin alphabet # exists (e.g., Þ, "LATIN CAPITAL LETTER THORN") -- we can # avoid these cases by checking that the letter name is # composed of one letter only. @@ -910,6 +912,8 @@ def delay(timedelay, callback, *args, **kwargs): _TYPECLASSMODELS = None _OBJECTMODELS = None + + def clean_object_caches(obj): """ Clean all object caches on the given object. @@ -1146,8 +1150,6 @@ def all_from_module(module): # module if available (try to avoid not imports) members = getmembers(mod, predicate=lambda obj: getmodule(obj) in (mod, None)) return dict((key, val) for key, val in members if not key.startswith("_")) - #return dict((key, val) for key, val in mod.__dict__.items() - # if not (key.startswith("_") or ismodule(val))) def callables_from_module(module): @@ -1209,7 +1211,7 @@ def variable_from_module(module, variable=None, default=None): else: # get all result = [val for key, val in mod.__dict__.items() - if not (key.startswith("_") or ismodule(val))] + if not (key.startswith("_") or ismodule(val))] if len(result) == 1: return result[0] @@ -1348,6 +1350,7 @@ def class_from_module(path, defaultpaths=None): # alias object_from_module = class_from_module + def init_new_player(player): """ Deprecated. @@ -1442,7 +1445,7 @@ def string_partial_matching(alternatives, inp, ret_index=True): # (this will invalidate input in the wrong word order) submatch = [last_index + alt_num for alt_num, alt_word in enumerate(alt_words[last_index:]) - if alt_word.startswith(inp_word)] + if alt_word.startswith(inp_word)] if submatch: last_index = min(submatch) + 1 score += 1 @@ -1488,7 +1491,7 @@ def format_table(table, extra_space=1): for ir, row in enumarate(ftable): if ir == 0: # make first row white - string += "\n{w" + ""join(row) + "{n" + string += "\n|w" + ""join(row) + "|n" else: string += "\n" + "".join(row) print string @@ -1539,6 +1542,8 @@ def get_evennia_pids(): from gc import get_referents from sys import getsizeof + + def deepsize(obj, max_depth=4): """ Get not only size of the given object, but also the size of @@ -1562,21 +1567,22 @@ def deepsize(obj, max_depth=4): """ def _recurse(o, dct, depth): - if max_depth >= 0 and depth > max_depth: + if 0 <= max_depth < depth: return for ref in get_referents(o): idr = id(ref) - if not idr in dct: + if idr not in dct: dct[idr] = (ref, getsizeof(ref, default=0)) _recurse(ref, dct, depth+1) sizedict = {} _recurse(obj, sizedict, 0) - #count = len(sizedict) + 1 size = getsizeof(obj) + sum([p[1] for p in sizedict.values()]) return size # lazy load handler _missing = object() + + class lazy_property(object): """ Delays loading of property until first access. Credit goes to the @@ -1597,14 +1603,14 @@ class lazy_property(object): """ def __init__(self, func, name=None, doc=None): - "Store all properties for now" + """Store all properties for now""" self.__name__ = name or func.__name__ self.__module__ = func.__module__ self.__doc__ = doc or func.__doc__ self.func = func def __get__(self, obj, type=None): - "Triggers initialization" + """Triggers initialization""" if obj is None: return self value = obj.__dict__.get(self.__name__, _missing) @@ -1614,7 +1620,9 @@ class lazy_property(object): return value _STRIP_ANSI = None -_RE_CONTROL_CHAR = re.compile('[%s]' % re.escape(''.join([unichr(c) for c in range(0,32)])))# + range(127,160)]))) +_RE_CONTROL_CHAR = re.compile('[%s]' % re.escape(''.join([unichr(c) for c in range(0, 32)]))) # + range(127,160)]))) + + def strip_control_sequences(string): """ Remove non-print text sequences. @@ -1675,12 +1683,12 @@ def m_len(target): return len(ANSI_PARSER.strip_mxp(target)) return len(target) -#------------------------------------------------------------------ +# ------------------------------------------------------------------- # Search handler function -#------------------------------------------------------------------ +# ------------------------------------------------------------------- # # Replace this hook function by changing settings.SEARCH_AT_RESULT. -# + def at_search_result(matches, caller, query="", quiet=False, **kwargs): """ @@ -1696,7 +1704,7 @@ def at_search_result(matches, caller, query="", quiet=False, **kwargs): should the result pass through. caller (Object): The object performing the search and/or which should receive error messages. - query (str, optional): The search query used to produce `matches`. + query (str, optional): The search query used to produce `matches`. quiet (bool, optional): If `True`, no messages will be echoed to caller on errors. @@ -1774,6 +1782,7 @@ class LimitedSizeOrderedDict(OrderedDict): super(LimitedSizeOrderedDict, self).update(*args, **kwargs) self._check_size() + def get_game_dir_path(): """ This is called by settings_default in order to determine the path @@ -1784,7 +1793,7 @@ def get_game_dir_path(): """ # current working directory, assumed to be somewhere inside gamedir. - for i in range(10): + for _ in range(10): gpath = os.getcwd() if "server" in os.listdir(gpath): if os.path.isfile(os.path.join("server", "conf", "settings.py")): From f33b562cf799e655f4f534b5e79e7f0af2ec94f6 Mon Sep 17 00:00:00 2001 From: BlauFeuer Date: Mon, 20 Feb 2017 01:47:12 -0500 Subject: [PATCH 031/134] Markup and whitespace updates --- evennia/utils/batchprocessors.py | 37 ++++++++++++++++---------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/evennia/utils/batchprocessors.py b/evennia/utils/batchprocessors.py index cf89634f58..70a741c713 100644 --- a/evennia/utils/batchprocessors.py +++ b/evennia/utils/batchprocessors.py @@ -183,12 +183,13 @@ _ENCODINGS = settings.ENCODINGS _RE_INSERT = re.compile(r"^\#INSERT (.*)", re.MULTILINE) _RE_CLEANBLOCK = re.compile(r"^\#.*?$|^\s*$", re.MULTILINE) _RE_CMD_SPLIT = re.compile(r"^\#.*?$", re.MULTILINE) -_RE_CODE_OR_HEADER = re.compile(r"(\A|^\#CODE|^\#HEADER).*?$(.*?)(?=^#CODE.*?$|^#HEADER.*?$|\Z)", re.MULTILINE + re.DOTALL) +_RE_CODE_OR_HEADER = re.compile(r"(\A|^\#CODE|^\#HEADER).*?$(.*?)(?=^#CODE.*?$|^#HEADER.*?$|\Z)", + re.MULTILINE + re.DOTALL) -#------------------------------------------------------------ +# ------------------------------------------------------------- # Helper function -#------------------------------------------------------------ +# ------------------------------------------------------------- def read_batchfile(pythonpath, file_ending='.py'): """ @@ -212,8 +213,7 @@ def read_batchfile(pythonpath, file_ending='.py'): """ # find all possible absolute paths - abspaths = utils.pypath_to_realpath(pythonpath, - file_ending, settings.BASE_BATCHPROCESS_PATHS) + abspaths = utils.pypath_to_realpath(pythonpath, file_ending, settings.BASE_BATCHPROCESS_PATHS) if not abspaths: raise IOError text = None @@ -237,11 +237,11 @@ def read_batchfile(pythonpath, file_ending='.py'): return text -#------------------------------------------------------------ +# ------------------------------------------------------------- # # Batch-command processor # -#------------------------------------------------------------ +# ------------------------------------------------------------- class BatchCommandProcessor(object): """ @@ -271,35 +271,35 @@ class BatchCommandProcessor(object): text = "".join(read_batchfile(pythonpath, file_ending='.ev')) def replace_insert(match): - "Map replace entries" + """Map replace entries""" return "\n#".join(self.parse_file(match.group(1))) # insert commands from inserted files text = _RE_INSERT.sub(replace_insert, text) - #text = re.sub(r"^\#INSERT (.*?)", replace_insert, text, flags=re.MULTILINE) + # re.sub(r"^\#INSERT (.*?)", replace_insert, text, flags=re.MULTILINE) # get all commands commands = _RE_CMD_SPLIT.split(text) - #commands = re.split(r"^\#.*?$", text, flags=re.MULTILINE) - #remove eventual newline at the end of commands + # re.split(r"^\#.*?$", text, flags=re.MULTILINE) + # remove eventual newline at the end of commands commands = [c.strip('\r\n') for c in commands] commands = [c for c in commands if c] return commands -#------------------------------------------------------------ +# ------------------------------------------------------------- # # Batch-code processor # -#------------------------------------------------------------ +# ------------------------------------------------------------- def tb_filename(tb): - "Helper to get filename from traceback" + """Helper to get filename from traceback""" return tb.tb_frame.f_code.co_filename def tb_iter(tb): - "Traceback iterator." + """Traceback iterator.""" while tb is not None: yield tb tb = tb.tb_next @@ -341,7 +341,7 @@ class BatchCodeProcessor(object): text = "".join(read_batchfile(pythonpath, file_ending='.py')) def replace_insert(match): - "Run parse_file on the import before sub:ing it into this file" + """Run parse_file on the import before sub:ing it into this file""" path = match.group(1) return "# batchcode insert (%s):" % path + "\n".join(self.parse_file(path)) @@ -356,7 +356,7 @@ class BatchCodeProcessor(object): code = text[istart:iend] if mtype == "#HEADER": headers.append(code) - else: # either #CODE or matching from start of file + else: # either #CODE or matching from start of file codes.append(code) # join all headers together to one @@ -365,7 +365,6 @@ class BatchCodeProcessor(object): codes = ["%s# batchcode code:\n%s" % (header, code) for code in codes] return codes - def code_exec(self, code, extra_environ=None, debug=False): """ Execute a single code block, including imports and appending @@ -406,7 +405,7 @@ class BatchCodeProcessor(object): err = "" for iline, line in enumerate(code.split("\n")): if iline == lineno: - err += "\n{w%02i{n: %s" % (iline + 1, line) + err += "\n|w%02i|n: %s" % (iline + 1, line) elif lineno - 5 < iline < lineno + 5: err += "\n%02i: %s" % (iline + 1, line) From 7f6db31bd65a71547a30ef9a0809eb4c53055071 Mon Sep 17 00:00:00 2001 From: BlauFeuer Date: Mon, 20 Feb 2017 02:07:38 -0500 Subject: [PATCH 032/134] Markup, whitespace and code refactor --- evennia/utils/eveditor.py | 131 ++++++++++++++++---------------------- 1 file changed, 56 insertions(+), 75 deletions(-) diff --git a/evennia/utils/eveditor.py b/evennia/utils/eveditor.py index d7f3203afa..83e9e5bf55 100644 --- a/evennia/utils/eveditor.py +++ b/evennia/utils/eveditor.py @@ -60,14 +60,13 @@ _RE_GROUP = re.compile(r"\".*?\"|\'.*?\'|\S*") # use NAWS in the future? _DEFAULT_WIDTH = settings.CLIENT_DEFAULT_WIDTH -#------------------------------------------------------------ +# ------------------------------------------------------------- # # texts # -#------------------------------------------------------------ +# ------------------------------------------------------------- -_HELP_TEXT = \ -""" +_HELP_TEXT = """ - any non-command is appended to the end of the buffer. : - view buffer or only line(s) :: - raw-view buffer or only line(s) @@ -105,31 +104,27 @@ _HELP_TEXT = \ :echo - turn echoing of the input on/off (helpful for some clients) """ -_HELP_LEGEND = \ -""" +_HELP_LEGEND = """ Legend: - line number, like '5' or range, like '3:7'. - a single word, or multiple words with quotes around them. - longer string, usually not needing quotes. """ -_HELP_CODE = \ -""" +_HELP_CODE = """ :! - Execute code buffer without saving :< - Decrease the level of automatic indentation for the next lines :> - Increase the level of automatic indentation for the next lines := - Switch automatic indentation on/off """.lstrip("\n") -_ERROR_LOADFUNC = \ -""" +_ERROR_LOADFUNC = """ {error} |rBuffer load function error. Could not load initial data.|n """ -_ERROR_SAVEFUNC = \ -""" +_ERROR_SAVEFUNC = """ {error} |rSave function returned an error. Buffer not saved.|n @@ -140,15 +135,13 @@ _ERROR_NO_SAVEFUNC = "|rNo save function defined. Buffer cannot be saved.|n" _MSG_SAVE_NO_CHANGE = "No changes need saving" _DEFAULT_NO_QUITFUNC = "Exited editor." -_ERROR_QUITFUNC = \ -""" +_ERROR_QUITFUNC = """ {error} |rQuit function gave an error. Skipping.|n """ -_ERROR_PERSISTENT_SAVING = \ -""" +_ERROR_PERSISTENT_SAVING = """ {error} |rThe editor state could not be saved for persistent mode. Switching @@ -157,9 +150,9 @@ an eventual server reload - so save often!)|n """ _TRACE_PERSISTENT_SAVING = \ -"EvEditor persistent-mode error. Commonly, this is because one or " \ -"more of the EvEditor callbacks could not be pickled, for example " \ -"because it's a class method or is defined inside another function." + "EvEditor persistent-mode error. Commonly, this is because one or " \ + "more of the EvEditor callbacks could not be pickled, for example " \ + "because it's a class method or is defined inside another function." _MSG_NO_UNDO = "Nothing to undo." @@ -167,11 +160,12 @@ _MSG_NO_REDO = "Nothing to redo." _MSG_UNDO = "Undid one step." _MSG_REDO = "Redid one step." -#------------------------------------------------------------ +# ------------------------------------------------------------- # # Handle yes/no quit question # -#------------------------------------------------------------ +# ------------------------------------------------------------- + class CmdSaveYesNo(Command): """ @@ -185,7 +179,7 @@ class CmdSaveYesNo(Command): help_cateogory = "LineEditor" def func(self): - "Implement the yes/no choice." + """Implement the yes/no choice.""" # this is only called from inside the lineeditor # so caller.ndb._lineditor must be set. @@ -200,21 +194,21 @@ class CmdSaveYesNo(Command): class SaveYesNoCmdSet(CmdSet): - "Stores the yesno question" + """Stores the yesno question""" key = "quitsave_yesno" priority = 1 mergetype = "Replace" def at_cmdset_creation(self): - "at cmdset creation" + """at cmdset creation""" self.add(CmdSaveYesNo()) -#------------------------------------------------------------ +# ------------------------------------------------------------- # # Editor commands # -#------------------------------------------------------------ +# ------------------------------------------------------------- class CmdEditorBase(Command): """ @@ -239,7 +233,6 @@ class CmdEditorBase(Command): txt - extra text (string), could be encased in quotes. """ - linebuffer = [] editor = self.caller.ndb._eveditor if not editor: # this will completely replace the editor @@ -297,11 +290,7 @@ class CmdEditorBase(Command): arglist = arglist[1:] # nicer output formatting of the line range. - lstr = "" - if not linerange or lstart + 1 == lend: - lstr = "line %i" % (lstart + 1) - else: - lstr = "lines %i-%i" % (lstart + 1, lend) + lstr = "line %i" % (lstart + 1) if not linerange or lstart + 1 == lend else "lines %i-%i" % (lstart + 1, lend) # arg1 and arg2 is whatever arguments. Line numbers or -ranges are # never included here. @@ -369,25 +358,14 @@ class CmdLineInput(CmdEditorBase): """ caller = self.caller editor = caller.ndb._eveditor - buf = editor.get_buffer() # add a line of text to buffer line = self.raw_string.strip("\r\n") - if not editor._codefunc: - if not buf: - buf = line - else: - buf = buf + "\n%s" % line - else: + if editor._codefunc and editor._indent >= 0: # if automatic indentation is active, add spaces - if editor._indent >= 0: - line = editor.deduce_indent(line, buf) - - if not buf: - buf = line - else: - buf = buf + "\n%s" % line + line = editor.deduce_indent(line, buf) + buf = line if not buf else buf + "\n%s" % line self.editor.update_buffer(buf) if self.editor._echo_mode: # need to do it here or we will be off one line @@ -398,11 +376,10 @@ class CmdLineInput(CmdEditorBase): if indent < 0: indent = "off" - self.caller.msg("{b%02i|{n ({g%s{n) %s" % ( + self.caller.msg("|b%02i|||n (|g%s|n) %s" % ( cline, indent, line)) else: - self.caller.msg("{b%02i|{n %s" % (cline, self.args)) - + self.caller.msg("|b%02i|||n %s" % (cline, self.args)) class CmdEditorGroup(CmdEditorBase): @@ -410,7 +387,7 @@ class CmdEditorGroup(CmdEditorBase): Commands for the editor """ key = ":editor_command_group" - aliases = [":","::", ":::", ":h", ":w", ":wq", ":q", ":q!", ":u", ":uu", ":UU", + aliases = [":", "::", ":::", ":h", ":w", ":wq", ":q", ":q!", ":u", ":uu", ":UU", ":dd", ":dw", ":DD", ":y", ":x", ":p", ":i", ":j", ":r", ":I", ":A", ":s", ":S", ":f", ":fi", ":fd", ":echo", ":!", ":<", ":>", ":="] @@ -443,9 +420,9 @@ class CmdEditorGroup(CmdEditorBase): buf = linebuffer[lstart:lend] editor.display_buffer(buf=buf, offset=lstart, - linenums=False, options={"raw":True}) + linenums=False, options={"raw": True}) else: - editor.display_buffer(linenums=False, options={"raw":True}) + editor.display_buffer(linenums=False, options={"raw": True}) elif cmd == ":::": # Insert single colon alone on a line editor.update_buffer(editor.buffer + "\n:") @@ -485,7 +462,7 @@ class CmdEditorGroup(CmdEditorBase): # :dd - delete line buf = linebuffer[:lstart] + linebuffer[lend:] editor.update_buffer(buf) - caller.msg("Deleted %s." % (self.lstr)) + caller.msg("Deleted %s." % self.lstr) elif cmd == ":dw": # :dw - delete word in entire buffer # :dw delete word only on line(s) @@ -556,7 +533,8 @@ class CmdEditorGroup(CmdEditorBase): if not self.raw_string and not editor._codefunc: caller.msg("You need to enter text to insert.") else: - buf = linebuffer[:lstart] + ["%s%s" % (self.args, line) for line in linebuffer[lstart:lend]] + linebuffer[lend:] + buf = linebuffer[:lstart] + ["%s%s" % (self.args, line) + for line in linebuffer[lstart:lend]] + linebuffer[lend:] editor.update_buffer(buf) caller.msg("Inserted text at beginning of %s." % self.lstr) elif cmd == ":A": @@ -564,7 +542,8 @@ class CmdEditorGroup(CmdEditorBase): if not self.args: caller.msg("You need to enter text to append.") else: - buf = linebuffer[:lstart] + ["%s%s" % (line, self.args) for line in linebuffer[lstart:lend]] + linebuffer[lend:] + buf = linebuffer[:lstart] + ["%s%s" % (line, self.args) + for line in linebuffer[lstart:lend]] + linebuffer[lend:] editor.update_buffer(buf) caller.msg("Appended text to end of %s." % self.lstr) elif cmd == ":s": @@ -576,7 +555,7 @@ class CmdEditorGroup(CmdEditorBase): if not self.linerange: lstart = 0 lend = self.cline + 1 - caller.msg("Search-replaced %s -> %s for lines %i-%i." % (self.arg1, self.arg2, lstart + 1 , lend)) + caller.msg("Search-replaced %s -> %s for lines %i-%i." % (self.arg1, self.arg2, lstart + 1, lend)) else: caller.msg("Search-replaced %s -> %s for %s." % (self.arg1, self.arg2, self.lstr)) sarea = "\n".join(linebuffer[lstart:lend]) @@ -585,7 +564,8 @@ class CmdEditorGroup(CmdEditorBase): regarg = self.arg1.strip("\'").strip('\"') if " " in regarg: regarg = regarg.replace(" ", " +") - sarea = re.sub(regex % (regarg, regarg, regarg, regarg, regarg), self.arg2.strip("\'").strip('\"'), sarea, re.MULTILINE) + sarea = re.sub(regex % (regarg, regarg, regarg, regarg, regarg), self.arg2.strip("\'").strip('\"'), + sarea, re.MULTILINE) buf = linebuffer[:lstart] + sarea.split("\n") + linebuffer[lend:] editor.update_buffer(buf) elif cmd == ":f": @@ -594,7 +574,7 @@ class CmdEditorGroup(CmdEditorBase): if not self.linerange: lstart = 0 lend = self.cline + 1 - caller.msg("Flood filled lines %i-%i." % (lstart + 1 , lend)) + caller.msg("Flood filled lines %i-%i." % (lstart + 1, lend)) else: caller.msg("Flood filled %s." % self.lstr) fbuf = "\n".join(linebuffer[lstart:lend]) @@ -605,7 +585,7 @@ class CmdEditorGroup(CmdEditorBase): # :f justify buffer of with as align (one of # f(ull), c(enter), r(ight) or l(left). Default is full. align_map = {"full": "f", "f": "f", "center": "c", "c": "c", - "right": "r", "r": "r", "left": "l", "l": "l"} + "right": "r", "r": "r", "left": "l", "l": "l"} align_name = {"f": "Full", "c": "Center", "l": "Left", "r": "Right"} width = _DEFAULT_WIDTH if self.arg1 and self.arg1.lower() not in align_map: @@ -628,7 +608,7 @@ class CmdEditorGroup(CmdEditorBase): if not self.linerange: lstart = 0 lend = self.cline + 1 - caller.msg("Indented lines %i-%i." % (lstart + 1 , lend)) + caller.msg("Indented lines %i-%i." % (lstart + 1, lend)) else: caller.msg("Indented %s." % self.lstr) fbuf = [indent + line for line in linebuffer[lstart:lend]] @@ -639,7 +619,7 @@ class CmdEditorGroup(CmdEditorBase): if not self.linerange: lstart = 0 lend = self.cline + 1 - caller.msg("Removed left margin (dedented) lines %i-%i." % (lstart + 1 , lend)) + caller.msg("Removed left margin (dedented) lines %i-%i." % (lstart + 1, lend)) else: caller.msg("Removed left margin (dedented) %s." % self.lstr) fbuf = "\n".join(linebuffer[lstart:lend]) @@ -693,18 +673,20 @@ class CmdEditorGroup(CmdEditorBase): class EvEditorCmdSet(CmdSet): - "CmdSet for the editor commands" + """CmdSet for the editor commands""" key = "editorcmdset" mergetype = "Replace" + def at_cmdset_creation(self): self.add(CmdLineInput()) self.add(CmdEditorGroup()) -#------------------------------------------------------------ +# ------------------------------------------------------------- # # Main Editor object # -#------------------------------------------------------------ +# ------------------------------------------------------------- + class EvEditor(object): """ @@ -790,12 +772,10 @@ class EvEditor(object): if persistent: # save in tuple {kwargs, other options} try: - caller.attributes.add("_eveditor_saved",( - {"loadfunc":loadfunc, "savefunc": savefunc, - "quitfunc": quitfunc, "codefunc": codefunc, - "key": key, "persistent": persistent}, - {"_pristine_buffer": self._pristine_buffer, - "_sep": self._sep})) + caller.attributes.add("_eveditor_saved", ( + dict(loadfunc=loadfunc, savefunc=savefunc, quitfunc=quitfunc, + codefunc=codefunc, key=key, persistent=persistent), + dict(_pristine_buffer=self._pristine_buffer, _sep=self._sep))) caller.attributes.add("_eveditor_buffer_temp", (self._buffer, self._undo_buffer)) caller.attributes.add("_eveditor_unsaved", False) caller.attributes.add("_eveditor_indent", 0) @@ -923,7 +903,7 @@ class EvEditor(object): self._undo_buffer = self._undo_buffer[:self._undo_pos + 1] + [self._buffer] self._undo_pos = len(self._undo_buffer) - 1 - def display_buffer(self, buf=None, offset=0, linenums=True, options={"raw":False}): + def display_buffer(self, buf=None, offset=0, linenums=True, options={"raw": False}): """ This displays the line editor buffer, or selected parts of it. @@ -933,7 +913,7 @@ class EvEditor(object): `offset` should define the actual starting line number, to get the linenum display right. linenums (bool, optional): Show line numbers in buffer. - raw (bool, optional): Tell protocol to not parse + options: raw (bool, optional): Tell protocol to not parse formatting information. """ @@ -949,10 +929,10 @@ class EvEditor(object): sep = self._sep header = "|n" + sep * 10 + "Line Editor [%s]" % self._key + sep * (_DEFAULT_WIDTH-20-len(self._key)) - footer = "|n" + sep * 10 + "[l:%02i w:%03i c:%04i]" % (nlines, nwords, nchars) \ - + sep * 12 + "(:h for help)" + sep * 28 + footer = "|n" + sep * 10 +\ + "[l:%02i w:%03i c:%04i]" % (nlines, nwords, nchars) + sep * 12 + "(:h for help)" + sep * 28 if linenums: - main = "\n".join("{b%02i|{n %s" % (iline + 1 + offset, line) for iline, line in enumerate(lines)) + main = "\n".join("|b%02i|||n %s" % (iline + 1 + offset, line) for iline, line in enumerate(lines)) else: main = "\n".join(lines) string = "%s\n%s\n%s" % (header, main, footer) @@ -1018,6 +998,7 @@ class EvEditor(object): self._indent += 1 if self._persistent: self._caller.attributes.add("_eveditor_indent", self._indent) + def swap_autoindent(self): """Swap automatic indentation on or off.""" if self._codefunc: From 37984adc5884788133a04aae54802730c70e3681 Mon Sep 17 00:00:00 2001 From: BlauFeuer Date: Mon, 20 Feb 2017 02:21:49 -0500 Subject: [PATCH 033/134] Markup, whitespace, docstring and comment updates --- evennia/utils/evmenu.py | 105 +++++++++++++++++++--------------------- 1 file changed, 51 insertions(+), 54 deletions(-) diff --git a/evennia/utils/evmenu.py b/evennia/utils/evmenu.py index f8af42e5f7..438b078a65 100644 --- a/evennia/utils/evmenu.py +++ b/evennia/utils/evmenu.py @@ -18,12 +18,12 @@ Example usage: Where `caller` is the Object to use the menu on - it will get a new cmdset while using the Menu. The menu_module_path is the python path -to a python module containing function defintions. By adjusting the +to a python module containing function definitions. By adjusting the keyword options of the Menu() initialization call you can start the menu at different places in the menu definition file, adjust if the menu command should overload the normal commands or not, etc. -The `perstent` keyword will make the menu survive a server reboot. +The `persistent` keyword will make the menu survive a server reboot. It is `False` by default. Note that if using persistent mode, every node and callback in the menu must be possible to be *pickled*, this excludes e.g. callables that are class methods or functions defined @@ -31,7 +31,7 @@ dynamically or as part of another function. In non-persistent mode no such restrictions exist. The menu is defined in a module (this can be the same module as the -command definition too) with function defintions: +command definition too) with function definitions: ```python @@ -181,8 +181,7 @@ _HELP_NO_OPTIONS = _("Commands: help, quit") _HELP_NO_OPTIONS_NO_QUIT = _("Commands: help") _HELP_NO_OPTION_MATCH = _("Choose an option or try 'help'.") -_ERROR_PERSISTENT_SAVING = \ -""" +_ERROR_PERSISTENT_SAVING = """ {error} |rThe menu state could not be saved for persistent mode. Switching @@ -190,10 +189,9 @@ to non-persistent mode (which means the menu session won't survive an eventual server reload).|n """ -_TRACE_PERSISTENT_SAVING = \ -"EvMenu persistent-mode error. Commonly, this is because one or " \ -"more of the EvEditor callbacks could not be pickled, for example " \ -"because it's a class method or is defined inside another function." +_TRACE_PERSISTENT_SAVING = "EvMenu persistent-mode error. Commonly, this is because one or " \ + "more of the EvEditor callbacks could not be pickled, for example " \ + "because it's a class method or is defined inside another function." class EvMenuError(RuntimeError): @@ -203,11 +201,12 @@ class EvMenuError(RuntimeError): """ pass -#------------------------------------------------------------ +# ------------------------------------------------------------- # # Menu command and command set # -#------------------------------------------------------------ +# ------------------------------------------------------------- + class CmdEvMenuNode(Command): """ @@ -230,7 +229,7 @@ class CmdEvMenuNode(Command): startnode_tuple = caller.attributes.get("_menutree_saved_startnode") try: startnode, startnode_input = startnode_tuple - except ValueError: # old form of startnode stor + except ValueError: # old form of startnode store startnode, startnode_input = startnode_tuple, "" if startnode: saved_options[1]["startnode"] = startnode @@ -257,7 +256,7 @@ class CmdEvMenuNode(Command): menu = caller.ndb._menutree if not menu: # can't restore from a session - err = "Menu object not found as %s.ndb._menutree!" % (orig_caller) + err = "Menu object not found as %s.ndb._menutree!" % orig_caller orig_caller.msg(err) # don't give the session as a kwarg here, direct to original raise EvMenuError(err) # we must do this after the caller with the menui has been correctly identified since it @@ -330,23 +329,23 @@ def evtable_options_formatter(optionlist, caller=None): # add a default white color to key table.append(" |lc%s|lt|w%s|n|le: %s" % (raw_key, raw_key, desc)) - ncols = (_MAX_TEXT_WIDTH // table_width_max) + 1 # number of ncols + ncols = (_MAX_TEXT_WIDTH // table_width_max) + 1 # number of 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 = nlist // nrows # number of full columns + nlastcol = nlist % nrows # number of elements in last column # 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)]) + table.extend([" " for _ in range(nrows - nlastcol)]) # build the actual table grid - table = [table[icol * nrows : (icol * nrows) + nrows] for icol in range(0, ncols)] + table = [table[icol * nrows: (icol * nrows) + nrows] for icol in range(0, ncols)] # adjust the width of each column for icol in range(len(table)): @@ -410,11 +409,12 @@ def evtable_parse_input(menuobject, raw_string, caller): # no options - we are at the end of the menu. menuobject.close_menu() -#------------------------------------------------------------ +# ------------------------------------------------------------- # # Menu main class # -#------------------------------------------------------------ +# ------------------------------------------------------------- + class EvMenu(object): """ @@ -593,15 +593,16 @@ class EvMenu(object): # save the menu to the database try: caller.attributes.add("_menutree_saved", - ((menudata, ), - {"startnode": startnode, - "cmdset_mergetype": cmdset_mergetype, - "cmdset_priority": cmdset_priority, - "auto_quit": auto_quit, "auto_look": auto_look, "auto_help": auto_help, - "cmd_on_exit": cmd_on_exit, - "nodetext_formatter": nodetext_formatter, "options_formatter": options_formatter, - "node_formatter": node_formatter, "input_parser": input_parser, - "persistent": persistent,})) + ((menudata, ), + {"startnode": startnode, + "cmdset_mergetype": cmdset_mergetype, + "cmdset_priority": cmdset_priority, + "auto_quit": auto_quit, "auto_look": auto_look, "auto_help": auto_help, + "cmd_on_exit": cmd_on_exit, + "nodetext_formatter": nodetext_formatter, + "options_formatter": options_formatter, + "node_formatter": node_formatter, "input_parser": input_parser, + "persistent": persistent, })) caller.attributes.add("_menutree_saved_startnode", (startnode, startnode_input)) except Exception as err: caller.msg(_ERROR_PERSISTENT_SAVING.format(error=err), session=self._session) @@ -670,7 +671,6 @@ class EvMenu(object): # format the entire node return self._node_formatter(nodetext, optionstext, self.caller) - def _execute_node(self, nodename, raw_string): """ Execute a node. @@ -707,15 +707,12 @@ class EvMenu(object): raise return nodetext, options - def display_nodetext(self): self.caller.msg(self.nodetext, session=self._session) - def display_helptext(self): self.caller.msg(self.helptext, session=self._session) - def callback_goto(self, callback, goto, raw_string): """ Call callback and goto in sequence. @@ -886,7 +883,7 @@ class CmdGetInput(Command): aliases = _CMD_NOINPUT def func(self): - "This is called when user enters anything." + """This is called when user enters anything.""" caller = self.caller try: getinput = caller.ndb._getinput @@ -897,7 +894,7 @@ class CmdGetInput(Command): caller.ndb._getinput._session = self.session prompt = caller.ndb._getinput._prompt - result = self.raw_string.strip() # we strip the ending line break caused by sending + result = self.raw_string.strip() # we strip the ending line break caused by sending ok = not callback(caller, prompt, result) if ok: @@ -924,12 +921,12 @@ class InputCmdSet(CmdSet): no_channels = False def at_cmdset_creation(self): - "called once at creation" + """called once at creation""" self.add(CmdGetInput()) class _Prompt(object): - "Dummy holder" + """Dummy holder""" pass @@ -988,11 +985,11 @@ def get_input(caller, prompt, callback, session=None): caller.msg(prompt, session=session) -#------------------------------------------------------------ +# ------------------------------------------------------------- # # test menu strucure and testing command # -#------------------------------------------------------------ +# ------------------------------------------------------------- def test_start_node(caller): menu = caller.ndb._menutree @@ -1008,17 +1005,17 @@ def test_start_node(caller): The menu was initialized with two variables: %s and %s. """ % (menu.testval, menu.testval2) - options = ({"key": ("{yS{net", "s"), + options = ({"key": ("|yS|net", "s"), "desc": "Set an attribute on yourself.", "exec": lambda caller: caller.attributes.add("menuattrtest", "Test value"), "goto": "test_set_node"}, - {"key": ("{yL{nook", "l"), + {"key": ("|yL|nook", "l"), "desc": "Look and see a custom message.", "goto": "test_look_node"}, - {"key": ("{yV{niew", "v"), + {"key": ("|yV|niew", "v"), "desc": "View your own name", "goto": "test_view_node"}, - {"key": ("{yQ{nuit", "quit", "q", "Q"), + {"key": ("|yQ|nuit", "quit", "q", "Q"), "desc": "Quit this menu example.", "goto": "test_end_node"}, {"key": "_default", @@ -1028,16 +1025,17 @@ def test_start_node(caller): def test_look_node(caller): text = "" - options = {"key": ("{yL{nook", "l"), + options = {"key": ("|yL|nook", "l"), "desc": "Go back to the previous menu.", "goto": "test_start_node"} return text, options + def test_set_node(caller): text = (""" The attribute 'menuattrtest' was set to - {w%s{n + |w%s|n (check it with examine after quitting the menu). @@ -1045,9 +1043,8 @@ def test_set_node(caller): string "_default", meaning it will catch any input, in this case to return to the main menu. So you can e.g. press to go back now. - """ % caller.db.menuattrtest, - # optional help text for this node - """ + """ % caller.db.menuattrtest, # optional help text for this node + """ This is the help entry for this node. It is created by returning the node text as a tuple - the second string in that tuple will be used as the help text. @@ -1061,7 +1058,7 @@ def test_set_node(caller): def test_view_node(caller): text = """ - Your name is {g%s{n! + Your name is |g%s|n! click |lclook|lthere|le to trigger a look command under MXP. This node's option has no explicit key (nor the "_default" key @@ -1074,11 +1071,11 @@ def test_view_node(caller): return text, options -def test_displayinput_node(caller, raw_string): +def test_displayinput_node(caller, raw_string): text = """ You entered the text: - "{w%s{n" + "|w%s|n" ... which could now be handled or stored here in some way if this was not just an example. @@ -1089,7 +1086,7 @@ def test_displayinput_node(caller, raw_string): to the start node. """ % raw_string options = {"key": "_default", - "goto": "test_start_node"} + "goto": "test_start_node"} return text, options @@ -1119,5 +1116,5 @@ class CmdTestMenu(Command): self.caller.msg("Usage: testmenu menumodule") return # start menu - EvMenu(self.caller, self.args.strip(), startnode="test_start_node", persistent=True, cmdset_mergetype="Replace", - testval="val", testval2="val2") + EvMenu(self.caller, self.args.strip(), startnode="test_start_node", persistent=True, + cmdset_mergetype="Replace", testval="val", testval2="val2") From 0dfac7f737cd0d9c23bc66d1d73b3fceb4612d19 Mon Sep 17 00:00:00 2001 From: BlauFeuer Date: Mon, 20 Feb 2017 02:32:01 -0500 Subject: [PATCH 034/134] Markup, whitespace, docstring and comments, code --- evennia/utils/evtable.py | 235 ++++++++++++++++++++++----------------- 1 file changed, 132 insertions(+), 103 deletions(-) diff --git a/evennia/utils/evtable.py b/evennia/utils/evtable.py index 3dfbd67fe6..748438beb2 100644 --- a/evennia/utils/evtable.py +++ b/evennia/utils/evtable.py @@ -126,6 +126,7 @@ from evennia.utils.ansi import ANSIString _DEFAULT_WIDTH = settings.CLIENT_DEFAULT_WIDTH + def _to_ansi(obj): """ convert to ANSIString. @@ -142,6 +143,8 @@ def _to_ansi(obj): _unicode = unicode _whitespace = '\t\n\x0b\x0c\r ' + + class ANSITextWrapper(TextWrapper): """ This is a wrapper work class for handling strings with ANSI tags @@ -158,8 +161,8 @@ class ANSITextWrapper(TextWrapper): becomes " foo bar baz". """ return text -##TODO: Ignore expand_tabs/replace_whitespace until ANSISTring handles them. -## - don't remove this code. /Griatch +# TODO: Ignore expand_tabs/replace_whitespace until ANSIString handles them. +# - don't remove this code. /Griatch # if self.expand_tabs: # text = text.expandtabs() # if self.replace_whitespace: @@ -169,7 +172,6 @@ class ANSITextWrapper(TextWrapper): # text = text.translate(self.unicode_whitespace_trans) # return text - def _split(self, text): """_split(text : string) -> [string] @@ -289,6 +291,7 @@ def wrap(text, width=_DEFAULT_WIDTH, **kwargs): w = ANSITextWrapper(width=width, **kwargs) return w.wrap(text) + def fill(text, width=_DEFAULT_WIDTH, **kwargs): """Fill a single paragraph of text, returning a new string. @@ -311,6 +314,7 @@ def fill(text, width=_DEFAULT_WIDTH, **kwargs): # EvCell class (see further down for the EvTable itself) + class EvCell(object): """ Holds a single data cell for the table. A cell has a certain width @@ -384,7 +388,7 @@ class EvCell(object): padwidth = int(padwidth) if padwidth is not None else None self.pad_left = int(kwargs.get("pad_left", padwidth if padwidth is not None else 1)) self.pad_right = int(kwargs.get("pad_right", padwidth if padwidth is not None else 1)) - self.pad_top = int( kwargs.get("pad_top", padwidth if padwidth is not None else 0)) + self.pad_top = int(kwargs.get("pad_top", padwidth if padwidth is not None else 0)) self.pad_bottom = int(kwargs.get("pad_bottom", padwidth if padwidth is not None else 0)) self.enforce_size = kwargs.get("enforce_size", False) @@ -429,7 +433,7 @@ class EvCell(object): self.align = kwargs.get("align", "l") self.valign = kwargs.get("valign", "c") - #self.data = self._split_lines(unicode(data)) + # self.data = self._split_lines(unicode(data)) self.data = self._split_lines(_to_ansi(data)) self.raw_width = max(m_len(line) for line in self.data) self.raw_height = len(self.data) @@ -442,20 +446,20 @@ class EvCell(object): if "width" in kwargs: width = kwargs.pop("width") self.width = width - self.pad_left - self.pad_right - self.border_left - self.border_right - if self.width <= 0 and self.raw_width > 0: + if self.width <= 0 < self.raw_width: raise Exception("Cell width too small - no space for data.") else: self.width = self.raw_width if "height" in kwargs: height = kwargs.pop("height") self.height = height - self.pad_top - self.pad_bottom - self.border_top - self.border_bottom - if self.height <= 0 and self.raw_height > 0: + if self.height <= 0 < self.raw_height: raise Exception("Cell height too small - no space for data.") else: self.height = self.raw_height # prepare data - #self.formatted = self._reformat() + # self.formatted = self._reformat() def _crop(self, text, width): """ @@ -512,8 +516,8 @@ class EvCell(object): if 0 < width < m_len(line): # replace_whitespace=False, expand_tabs=False is a # fix for ANSIString not supporting expand_tabs/translate - adjusted_data.extend([ANSIString(part + ANSIString("{n")) - for part in wrap(line, width=width, drop_whitespace=False)]) + adjusted_data.extend([ANSIString(part + ANSIString("|n")) + for part in wrap(line, width=width, drop_whitespace=False)]) else: adjusted_data.append(line) if self.enforce_size: @@ -526,7 +530,7 @@ class EvCell(object): adjusted_data[-1] = adjusted_data[-1][:-2] + ".." elif excess < 0: # too few lines. Fill to height. - adjusted_data.extend(["" for i in range(excess)]) + adjusted_data.extend(["" for _ in range(excess)]) return adjusted_data @@ -577,11 +581,14 @@ class EvCell(object): hfill_char = self.hfill_char width = self.width if align == "l": - lines= [(line.lstrip(" ") + " " if line.startswith(" ") and not line.startswith(" ") else line) + hfill_char * (width - m_len(line)) for line in data] + lines = [(line.lstrip(" ") + " " if line.startswith(" ") and not line.startswith(" ") + else line) + hfill_char * (width - m_len(line)) for line in data] return lines elif align == "r": - return [hfill_char * (width - m_len(line)) + (" " + line.rstrip(" ") if line.endswith(" ") and not line.endswith(" ") else line) for line in data] - else: # center, 'c' + return [hfill_char * (width - m_len(line)) + (" " + line.rstrip(" ") + if line.endswith(" ") and not line.endswith(" ") + else line) for line in data] + else: # center, 'c' return [self._center(line, self.width, self.hfill_char) for line in data] def _valign(self, data): @@ -605,11 +612,11 @@ class EvCell(object): return data # only care if we need to add new lines if valign == 't': - return data + [padline for i in range(excess)] + return data + [padline for _ in range(excess)] elif valign == 'b': - return [padline for i in range(excess)] + data - else: # center - narrowside = [padline for i in range(excess // 2)] + return [padline for _ in range(excess)] + data + else: # center + narrowside = [padline for _ in range(excess // 2)] widerside = narrowside + [padline] if excess % 2: # uneven padding @@ -635,8 +642,8 @@ class EvCell(object): left = self.hpad_char * self.pad_left right = self.hpad_char * self.pad_right vfill = (self.width + self.pad_left + self.pad_right) * self.vpad_char - top = [vfill for i in range(self.pad_top)] - bottom = [vfill for i in range(self.pad_bottom)] + top = [vfill for _ in range(self.pad_top)] + bottom = [vfill for _ in range(self.pad_bottom)] return top + [left + line + right for line in data] + bottom def _border(self, data): @@ -654,18 +661,17 @@ class EvCell(object): left = self.border_left_char * self.border_left + ANSIString('|n') right = ANSIString('|n') + self.border_right_char * self.border_right - cwidth = self.width + self.pad_left + self.pad_right + \ - max(0,self.border_left-1) + max(0, self.border_right-1) + cwidth = self.width + self.pad_left + self.pad_right + max(0, self.border_left-1) + max(0, self.border_right-1) vfill = self.corner_top_left_char if left else "" vfill += cwidth * self.border_top_char vfill += self.corner_top_right_char if right else "" - top = [vfill for i in range(self.border_top)] + top = [vfill for _ in range(self.border_top)] vfill = self.corner_bottom_left_char if left else "" vfill += cwidth * self.border_bottom_char vfill += self.corner_bottom_right_char if right else "" - bottom = [vfill for i in range(self.border_bottom)] + bottom = [vfill for _ in range(self.border_bottom)] return top + [left + line + right for line in data] + bottom @@ -699,7 +705,7 @@ class EvCell(object): natural_height (int): Height of cell. """ - return len(self.formatted) #if self.formatted else 0 + return len(self.formatted) # if self.formatted else 0 def get_width(self): """ @@ -709,7 +715,7 @@ class EvCell(object): natural_width (int): Width of cell. """ - return m_len(self.formatted[0]) #if self.formatted else 0 + return m_len(self.formatted[0]) # if self.formatted else 0 def replace_data(self, data, **kwargs): """ @@ -723,7 +729,7 @@ class EvCell(object): `EvCell.__init__`. """ - #self.data = self._split_lines(unicode(data)) + # self.data = self._split_lines(unicode(data)) self.data = self._split_lines(_to_ansi(data)) self.raw_width = max(m_len(line) for line in self.data) self.raw_height = len(self.data) @@ -746,7 +752,7 @@ class EvCell(object): padwidth = int(padwidth) if padwidth is not None else None self.pad_left = int(kwargs.pop("pad_left", padwidth if padwidth is not None else self.pad_left)) self.pad_right = int(kwargs.pop("pad_right", padwidth if padwidth is not None else self.pad_right)) - self.pad_top = int( kwargs.pop("pad_top", padwidth if padwidth is not None else self.pad_top)) + self.pad_top = int(kwargs.pop("pad_top", padwidth if padwidth is not None else self.pad_top)) self.pad_bottom = int(kwargs.pop("pad_bottom", padwidth if padwidth is not None else self.pad_bottom)) self.enforce_size = kwargs.get("enforce_size", False) @@ -764,22 +770,34 @@ class EvCell(object): self.vfill_char = vfill_char[0] if vfill_char else self.vfill_char borderwidth = kwargs.get("border_width", None) - self.border_left = kwargs.pop("border_left", borderwidth if borderwidth is not None else self.border_left) - self.border_right = kwargs.pop("border_right", borderwidth if borderwidth is not None else self.border_right) - self.border_top = kwargs.pop("border_top", borderwidth if borderwidth is not None else self.border_top) - self.border_bottom = kwargs.pop("border_bottom", borderwidth if borderwidth is not None else self.border_bottom) + self.border_left = kwargs.pop( + "border_left", borderwidth if borderwidth is not None else self.border_left) + self.border_right = kwargs.pop( + "border_right", borderwidth if borderwidth is not None else self.border_right) + self.border_top = kwargs.pop( + "border_top", borderwidth if borderwidth is not None else self.border_top) + self.border_bottom = kwargs.pop( + "border_bottom", borderwidth if borderwidth is not None else self.border_bottom) borderchar = kwargs.get("border_char", None) - self.border_left_char = kwargs.pop("border_left_char", borderchar if borderchar else self.border_left_char) - self.border_right_char = kwargs.pop("border_right_char", borderchar if borderchar else self.border_right_char) - self.border_top_char = kwargs.pop("border_topchar", borderchar if borderchar else self.border_top_char) - self.border_bottom_char = kwargs.pop("border_bottom_char", borderchar if borderchar else self.border_bottom_char) + self.border_left_char = kwargs.pop( + "border_left_char", borderchar if borderchar else self.border_left_char) + self.border_right_char = kwargs.pop( + "border_right_char", borderchar if borderchar else self.border_right_char) + self.border_top_char = kwargs.pop( + "border_topchar", borderchar if borderchar else self.border_top_char) + self.border_bottom_char = kwargs.pop( + "border_bottom_char", borderchar if borderchar else self.border_bottom_char) corner_char = kwargs.get("corner_char", None) - self.corner_top_left_char = kwargs.pop("corner_top_left", corner_char if corner_char is not None else self.corner_top_left_char) - self.corner_top_right_char = kwargs.pop("corner_top_right", corner_char if corner_char is not None else self.corner_top_right_char) - self.corner_bottom_left_char = kwargs.pop("corner_bottom_left", corner_char if corner_char is not None else self.corner_bottom_left_char) - self.corner_bottom_right_char = kwargs.pop("corner_bottom_right", corner_char if corner_char is not None else self.corner_bottom_right_char) + self.corner_top_left_char = kwargs.pop( + "corner_top_left", corner_char if corner_char is not None else self.corner_top_left_char) + self.corner_top_right_char = kwargs.pop( + "corner_top_right", corner_char if corner_char is not None else self.corner_top_right_char) + self.corner_bottom_left_char = kwargs.pop( + "corner_bottom_left", corner_char if corner_char is not None else self.corner_bottom_left_char) + self.corner_bottom_right_char = kwargs.pop( + "corner_bottom_right", corner_char if corner_char is not None else self.corner_bottom_right_char) # this is used by the table to adjust size of cells with borders in the middle # of the table @@ -793,13 +811,16 @@ class EvCell(object): # Handle sizes if "width" in kwargs: width = kwargs.pop("width") - self.width = width - self.pad_left - self.pad_right - self.border_left - self.border_right + self.trim_horizontal - if self.width <= 0 and self.raw_width > 0: + self.width = width - self.pad_left - self.pad_right\ + - self.border_left - self.border_right + self.trim_horizontal + # if self.width <= 0 and self.raw_width > 0: + if self.width <= 0 < self.raw_width: raise Exception("Cell width too small, no room for data.") if "height" in kwargs: height = kwargs.pop("height") - self.height = height - self.pad_top - self.pad_bottom - self.border_top - self.border_bottom + self.trim_vertical - if self.height <= 0 and self.raw_height > 0: + self.height = height - self.pad_top - self.pad_bottom\ + - self.border_top - self.border_bottom + self.trim_vertical + if self.height <= 0 < self.raw_height: raise Exception("Cell height too small, no room for data.") # reformat (to new sizes, padding, header and borders) @@ -868,8 +889,8 @@ class EvColumn(object): col = self.column kwargs.update(self.options) # use fixed width or adjust to the largest cell - if not "width" in kwargs: - [cell.reformat() for cell in col] # this is necessary to get initial widths of all cells + if "width" not in kwargs: + [cell.reformat() for cell in col] # this is necessary to get initial widths of all cells kwargs["width"] = max(cell.get_width() for cell in col) if col else 0 [cell.reformat(**kwargs) for cell in col] @@ -900,11 +921,11 @@ class EvColumn(object): ypos = min(len(self.column)-1, max(0, int(ypos))) new_cells = [EvCell(data, **self.options) for data in args] self.column = self.column[:ypos] + new_cells + self.column[ypos:] - #self._balance(**kwargs) + # self._balance(**kwargs) def reformat(self, **kwargs): """ - Change the options for the collumn. + Change the options for the column. Kwargs: Keywords as per `EvCell.__init__`. @@ -930,19 +951,24 @@ class EvColumn(object): def __repr__(self): return "" % ("\n ".join([repr(cell) for cell in self.column])) + def __len__(self): return len(self.column) + def __iter__(self): return iter(self.column) + def __getitem__(self, index): return self.column[index] + def __setitem__(self, index, value): self.column[index] = value + def __delitem__(self, index): del self.column[index] -## Main Evtable class +# Main Evtable class class EvTable(object): """ @@ -998,7 +1024,7 @@ class EvTable(object): height (int, optional): Fixed height of table. Defaults to being unset. Width is still given precedence. If given, table cells will crop text rather than expand vertically. - evenwidth (bool, optional): Used with the `width` keyword. Adjusts collumns to have as even width as + evenwidth (bool, optional): Used with the `width` keyword. Adjusts columns to have as even width as possible. This often looks best also for mixed-length tables. Default is `False`. maxwidth (int, optional): This will set a maximum width of the table while allowing it to be smaller. Only if it grows wider than this @@ -1025,10 +1051,10 @@ class EvTable(object): excess = len(header) - len(table) if excess > 0: # header bigger than table - table.extend([] for i in range(excess)) + table.extend([] for _ in range(excess)) elif excess < 0: # too short header - header.extend(_to_ansi(["" for i in range(abs(excess))])) + header.extend(_to_ansi(["" for _ in range(abs(excess))])) for ix, heading in enumerate(header): table[ix].insert(0, heading) else: @@ -1043,7 +1069,7 @@ class EvTable(object): border = kwargs.pop("border", "tablecols") if border is None: border = "none" - if not border in ("none", "table", "tablecols", + if border not in ("none", "table", "tablecols", "header", "incols", "cols", "rows", "cells"): raise Exception("Unsupported border type: '%s'" % border) self.border = border @@ -1052,10 +1078,14 @@ class EvTable(object): self.border_width = kwargs.get("border_width", 1) self.corner_char = kwargs.get("corner_char", "+") pcorners = kwargs.pop("pretty_corners", False) - self.corner_top_left_char = _to_ansi(kwargs.pop("corner_top_left_char", '.' if pcorners else self.corner_char)) - self.corner_top_right_char = _to_ansi(kwargs.pop("corner_top_right_char", '.' if pcorners else self.corner_char)) - self.corner_bottom_left_char = _to_ansi(kwargs.pop("corner_bottom_left_char", ' ' if pcorners else self.corner_char)) - self.corner_bottom_right_char = _to_ansi(kwargs.pop("corner_bottom_right_char", ' ' if pcorners else self.corner_char)) + self.corner_top_left_char = _to_ansi(kwargs.pop( + "corner_top_left_char", '.' if pcorners else self.corner_char)) + self.corner_top_right_char = _to_ansi(kwargs.pop( + "corner_top_right_char", '.' if pcorners else self.corner_char)) + self.corner_bottom_left_char = _to_ansi(kwargs.pop( + "corner_bottom_left_char", ' ' if pcorners else self.corner_char)) + self.corner_bottom_right_char = _to_ansi(kwargs.pop( + "corner_bottom_right_char", ' ' if pcorners else self.corner_char)) self.width = kwargs.pop("width", None) self.height = kwargs.pop("height", None) @@ -1079,7 +1109,7 @@ class EvTable(object): self.worktable = None # balance the table - #self._balance() + # self._balance() def _cellborders(self, ix, iy, nx, ny, **kwargs): """ @@ -1114,7 +1144,7 @@ class EvTable(object): headchar = self.header_line_char def corners(ret): - "Handle corners of table" + """Handle corners of table""" if ix == 0 and iy == 0: ret["corner_top_left_char"] = self.corner_top_left_char if ix == nx and iy == 0: @@ -1126,47 +1156,47 @@ class EvTable(object): return ret def left_edge(ret): - "add vertical border along left table edge" + """add vertical border along left table edge""" if ix == 0: ret["border_left"] = bwidth - #ret["trim_horizontal"] = bwidth + # ret["trim_horizontal"] = bwidth return ret def top_edge(ret): - "add border along top table edge" + """add border along top table edge""" if iy == 0: ret["border_top"] = bwidth - #ret["trim_vertical"] = bwidth + # ret["trim_vertical"] = bwidth return ret def right_edge(ret): - "add vertical border along right table edge" - if ix == nx:# and 0 < iy < ny: + """add vertical border along right table edge""" + if ix == nx: # and 0 < iy < ny: ret["border_right"] = bwidth - #ret["trim_horizontal"] = 0 + # ret["trim_horizontal"] = 0 return ret def bottom_edge(ret): - "add border along bottom table edge" + """add border along bottom table edge""" if iy == ny: ret["border_bottom"] = bwidth - #ret["trim_vertical"] = bwidth + # ret["trim_vertical"] = bwidth return ret def cols(ret): - "Adding vertical borders inside the table" + """Adding vertical borders inside the table""" if 0 <= ix < nx: ret["border_right"] = bwidth return ret def rows(ret): - "Adding horizontal borders inside the table" + """Adding horizontal borders inside the table""" if 0 <= iy < ny: ret["border_bottom"] = bwidth return ret def head(ret): - "Add header underline" + """Add header underline""" if iy == 0: # put different bottom line for header ret["border_bottom"] = bwidth @@ -1176,15 +1206,15 @@ class EvTable(object): # use the helper functions to define various # table "styles" - if border in ("table", "tablecols","cells"): + if border in ("table", "tablecols", "cells"): ret = bottom_edge(right_edge(top_edge(left_edge(corners(ret))))) if border in ("cols", "tablecols", "cells"): ret = cols(right_edge(left_edge(ret))) - if border in ("incols"): + if border in "incols": ret = cols(ret) if border in ("rows", "cells"): ret = rows(bottom_edge(top_edge(ret))) - if header and not border in ("none", None): + if header and border not in ("none", None): ret = head(ret) return ret @@ -1197,7 +1227,7 @@ class EvTable(object): options = self.options for ix, col in enumerate(self.worktable): for iy, cell in enumerate(col): - col.reformat_cell(iy, **self._cellborders(ix,iy,nx,ny,**options)) + col.reformat_cell(iy, **self._cellborders(ix, iy, nx, ny, **options)) def _balance(self): """ @@ -1222,7 +1252,7 @@ class EvTable(object): self.worktable[icol].reformat(**options) if nrow < nrowmax: # add more rows to too-short columns - empty_rows = ["" for i in range(nrowmax-nrow)] + empty_rows = ["" for _ in range(nrowmax-nrow)] self.worktable[icol].add_rows(*empty_rows) self.ncols = ncols self.nrows = nrowmax @@ -1251,16 +1281,16 @@ class EvTable(object): excess = width - cwmin if self.evenwidth: - # make each collumn of equal width - for i in range(excess): - # flood-fill the minimum table starting with the smallest collumns + # make each column of equal width + for _ in range(excess): + # flood-fill the minimum table starting with the smallest columns ci = cwidths_min.index(min(cwidths_min)) cwidths_min[ci] += 1 cwidths = cwidths_min else: - # make each collumn expand more proportional to their data size - for i in range(excess): - # fill wider collumns first + # make each column expand more proportional to their data size + for _ in range(excess): + # fill wider columns first ci = cwidths.index(max(cwidths)) cwidths_min[ci] += 1 cwidths[ci] -= 3 @@ -1280,8 +1310,9 @@ class EvTable(object): # if we are fixing the table height, it means cells must crop text instead of resizing. if nrowmax: - # get minimum possible cell heights for each collumn - cheights_min = [max(cell.get_min_height() for cell in (col[iy] for col in self.worktable)) for iy in range(nrowmax)] + # get minimum possible cell heights for each column + cheights_min = [max(cell.get_min_height() + for cell in (col[iy] for col in self.worktable)) for iy in range(nrowmax)] chmin = sum(cheights_min) if chmin > self.height: @@ -1294,9 +1325,9 @@ class EvTable(object): excess = self.height - chmin even = self.height % 2 == 0 - for i in range(excess): + for position in range(excess): # expand the cells with the most rows first - if 0 <= i < nrowmax and nrowmax > 1: + if 0 <= position < nrowmax and nrowmax > 1: # avoid adding to header first round (looks bad on very small tables) ci = cheights[1:].index(max(cheights[1:])) + 1 else: @@ -1318,7 +1349,7 @@ class EvTable(object): col.reformat_cell(iy, height=cheights[iy], **options) except Exception as e: msg = "ix=%s, iy=%s, height=%s: %s" % (ix, iy, cheights[iy], e.message) - raise Exception ("Error in vertical allign:\n %s" % msg) + raise Exception("Error in vertical align:\n %s" % msg) # calculate actual table width/height in characters self.cwidth = sum(cwidths) @@ -1387,12 +1418,12 @@ class EvTable(object): if excess > 0: # we need to add new rows to table for col in self.table: - empty_rows = ["" for i in range(excess)] + empty_rows = ["" for _ in range(excess)] col.add_rows(*empty_rows, **options) self.nrows += excess elif excess < 0: # we need to add new rows to new column - empty_rows = ["" for i in range(abs(excess))] + empty_rows = ["" for _ in range(abs(excess))] column.add_rows(*empty_rows, **options) self.nrows -= excess @@ -1411,7 +1442,7 @@ class EvTable(object): xpos = min(wtable-1, max(0, int(xpos))) self.table.insert(xpos, column) self.ncols += 1 - #self._balance() + # self._balance() def add_row(self, *args, **kwargs): """ @@ -1444,12 +1475,12 @@ class EvTable(object): if excess > 0: # we need to add new empty columns to table - empty_rows = ["" for i in range(htable)] - self.table.extend([EvColumn(*empty_rows, **options) for i in range(excess)]) + empty_rows = ["" for _ in range(htable)] + self.table.extend([EvColumn(*empty_rows, **options) for _ in range(excess)]) self.ncols += excess elif excess < 0: # we need to add more cells to row - row.extend(["" for i in range(abs(excess))]) + row.extend(["" for _ in range(abs(excess))]) self.ncols -= excess if ypos is None or ypos > htable - 1: @@ -1462,7 +1493,7 @@ class EvTable(object): for icol, col in enumerate(self.table): col.add_rows(row[icol], ypos=ypos, **options) self.nrows += 1 - #self._balance() + # self._balance() def reformat(self, **kwargs): """ @@ -1523,16 +1554,17 @@ class EvTable(object): return [line for line in self._generate_lines()] def __str__(self): - "print table (this also balances it)" - return str(unicode(ANSIString("\n").join([line for line in self._generate_lines()]))) + """print table (this also balances it)""" + return str(unicode(ANSIString("\n").join([line for line in self._generate_lines()]))) def __unicode__(self): - return unicode(ANSIString("\n").join([line for line in self._generate_lines()])) + return unicode(ANSIString("\n").join([line for line in self._generate_lines()])) + def _test(): - "Test" - table = EvTable("{yHeading1{n", "{gHeading2{n", table=[[1,2,3],[4,5,6],[7,8,9]], border="cells", align="l") - table.add_column("{rThis is long data{n", "{bThis is even longer data{n") + """Test""" + table = EvTable("|yHeading1|n", "|gHeading2|n", table=[[1, 2, 3], [4, 5, 6], [7, 8, 9]], border="cells", align="l") + table.add_column("|rThis is long data|n", "|bThis is even longer data|n") table.add_row("This is a single row") print(unicode(table)) table.reformat(width=50) @@ -1540,6 +1572,3 @@ def _test(): table.reformat_column(3, width=30, align='r') print(unicode(table)) return table - - - From 5b86cc9bf4f64d3a793ecc6de661def0071a968d Mon Sep 17 00:00:00 2001 From: BlauFeuer Date: Mon, 20 Feb 2017 02:45:31 -0500 Subject: [PATCH 035/134] Markup, whitespace, comment edits --- evennia/utils/tests.py | 140 +++++++++++++++++++++++++++-------------- 1 file changed, 92 insertions(+), 48 deletions(-) diff --git a/evennia/utils/tests.py b/evennia/utils/tests.py index 247c8ad0b3..d1063ee8a6 100644 --- a/evennia/utils/tests.py +++ b/evennia/utils/tests.py @@ -31,13 +31,11 @@ class ANSIStringTestCase(TestCase): """ Make sure the ANSIString is always constructed correctly. """ - clean = u'This isA{r testTest' - encoded = u'\x1b[1m\x1b[32mThis is\x1b[1m\x1b[31mA{r test\x1b[0mTest\x1b[0m' - target = ANSIString(r'{gThis is{rA{{r test{nTest{n') - char_table = [9, 10, 11, 12, 13, 14, 15, 25, 26, 27, 28, 29, 30, 31, - 32, 37, 38, 39, 40] - code_table = [0, 1, 2, 3, 4, 5, 6, 7, 8, 16, 17, 18, 19, 20, 21, 22, - 23, 24, 33, 34, 35, 36, 41, 42, 43, 44] + clean = u'This isA|r testTest' + encoded = u'\x1b[1m\x1b[32mThis is\x1b[1m\x1b[31mA|r test\x1b[0mTest\x1b[0m' + target = ANSIString(r'|gThis is|rA||r test|nTest|n') + char_table = [9, 10, 11, 12, 13, 14, 15, 25, 26, 27, 28, 29, 30, 31, 32, 37, 38, 39, 40] + code_table = [0, 1, 2, 3, 4, 5, 6, 7, 8, 16, 17, 18, 19, 20, 21, 22, 23, 24, 33, 34, 35, 36, 41, 42, 43, 44] self.checker(target, encoded, clean) self.table_check(target, char_table, code_table) self.checker(ANSIString(target), encoded, clean) @@ -54,7 +52,7 @@ class ANSIStringTestCase(TestCase): Verifies that slicing an ANSIString results in expected color code distribution. """ - target = ANSIString(r'{gTest{rTest{n') + target = ANSIString(r'|gTest|rTest|n') result = target[:3] self.checker(result, u'\x1b[1m\x1b[32mTes', u'Tes') result = target[:4] @@ -80,7 +78,7 @@ class ANSIStringTestCase(TestCase): Verifies that re.split and .split behave similarly and that color codes end up where they should. """ - target = ANSIString("{gThis is {nA split string{g") + target = ANSIString("|gThis is |nA split string|g") first = (u'\x1b[1m\x1b[32mThis is \x1b[0m', u'This is ') second = (u'\x1b[1m\x1b[32m\x1b[0m split string\x1b[1m\x1b[32m', u' split string') @@ -96,9 +94,9 @@ class ANSIStringTestCase(TestCase): Verify that joining a set of ANSIStrings works. """ # This isn't the desired behavior, but the expected one. Python - # concatinates the in-memory representation with the built-in string's + # concatenates the in-memory representation with the built-in string's # join. - l = [ANSIString("{gTest{r") for s in range(0, 3)] + l = [ANSIString("|gTest|r") for _ in range(0, 3)] # Force the generator to be evaluated. result = "".join(l) self.assertEqual(unicode(result), u'TestTestTest') @@ -112,14 +110,14 @@ class ANSIStringTestCase(TestCase): Make sure that length reporting on ANSIStrings does not include ANSI codes. """ - self.assertEqual(len(ANSIString('{gTest{n')), 4) + self.assertEqual(len(ANSIString('|gTest|n')), 4) def test_capitalize(self): """ Make sure that capitalization works. This is the simplest of the _transform functions. """ - target = ANSIString('{gtest{n') + target = ANSIString('|gtest|n') result = u'\x1b[1m\x1b[32mTest\x1b[0m' self.checker(target.capitalize(), result, u'Test') @@ -127,8 +125,8 @@ class ANSIStringTestCase(TestCase): """ Make sure MXP tags are not treated like ANSI codes, but normal text. """ - mxp1 = "{lclook{ltat{le" - mxp2 = "Start to {lclook here{ltclick somewhere here{le first" + mxp1 = "|lclook|ltat|le" + mxp2 = "Start to |lclook here|ltclick somewhere here|le first" self.assertEqual(15, len(ANSIString(mxp1))) self.assertEqual(53, len(ANSIString(mxp2))) # These would indicate an issue with the tables. @@ -139,17 +137,15 @@ class ANSIStringTestCase(TestCase): def test_add(self): """ - Verify concatination works correctly. + Verify concatenation works correctly. """ - a = ANSIString("{gTest") - b = ANSIString("{cString{n") + a = ANSIString("|gTest") + b = ANSIString("|cString|n") c = a + b result = u'\x1b[1m\x1b[32mTest\x1b[1m\x1b[36mString\x1b[0m' self.checker(c, result, u'TestString') char_table = [9, 10, 11, 12, 22, 23, 24, 25, 26, 27] - code_table = [ - 0, 1, 2, 3, 4, 5, 6, 7, 8, 13, 14, 15, 16, 17, 18, 19, 20, 21, 28, 29, 30, 31 - ] + code_table = [0, 1, 2, 3, 4, 5, 6, 7, 8, 13, 14, 15, 16, 17, 18, 19, 20, 21, 28, 29, 30, 31] self.table_check(c, char_table, code_table) def test_strip(self): @@ -166,7 +162,7 @@ class ANSIStringTestCase(TestCase): class TestIsIter(TestCase): def test_is_iter(self): - self.assertEqual(True, utils.is_iter([1,2,3,4])) + self.assertEqual(True, utils.is_iter([1, 2, 3, 4])) self.assertEqual(False, utils.is_iter("This is not an iterable")) @@ -213,10 +209,10 @@ class TestListToString(TestCase): [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" and "3"', utils.list_to_string([1,2,3], endsep="and", addquote=True)) + 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" and "3"', utils.list_to_string([1, 2, 3], endsep="and", addquote=True)) class TestMLen(TestCase): @@ -231,10 +227,10 @@ class TestMLen(TestCase): self.assertEqual(utils.m_len('|lclook|ltat|le'), 2) def test_mxp_ansi_string(self): - self.assertEqual(utils.m_len(ANSIString('|lcl|gook|ltat|le{n')), 2) + self.assertEqual(utils.m_len(ANSIString('|lcl|gook|ltat|le|n')), 2) def test_non_mxp_ansi_string(self): - self.assertEqual(utils.m_len(ANSIString('{gHello{n')), 5) + self.assertEqual(utils.m_len(ANSIString('{gHello{n')), 5) # TODO - cause this to fail by default. self.assertEqual(utils.m_len(ANSIString('|gHello|n')), 5) def test_list(self): @@ -246,6 +242,7 @@ class TestMLen(TestCase): from .text2html import TextToHTMLparser + class TestTextToHTMLparser(TestCase): def setUp(self): self.parser = TextToHTMLparser() @@ -255,77 +252,85 @@ class TestTextToHTMLparser(TestCase): def test_url_scheme_ftp(self): self.assertEqual(self.parser.convert_urls('ftp.example.com'), - 'ftp.example.com') + 'ftp.example.com') def test_url_scheme_www(self): self.assertEqual(self.parser.convert_urls('www.example.com'), - 'www.example.com') + 'www.example.com') def test_url_scheme_ftpproto(self): self.assertEqual(self.parser.convert_urls('ftp://ftp.example.com'), - 'ftp://ftp.example.com') + 'ftp://ftp.example.com') def test_url_scheme_http(self): self.assertEqual(self.parser.convert_urls('http://example.com'), - 'http://example.com') + 'http://example.com') def test_url_scheme_https(self): self.assertEqual(self.parser.convert_urls('https://example.com'), - 'https://example.com') + 'https://example.com') def test_url_chars_slash(self): self.assertEqual(self.parser.convert_urls('www.example.com/homedir'), - 'www.example.com/homedir') + 'www.example.com/homedir') def test_url_chars_colon(self): self.assertEqual(self.parser.convert_urls('https://example.com:8000/login/'), - 'https://example.com:8000/login/') + '' + 'https://example.com:8000/login/') def test_url_chars_querystring(self): self.assertEqual(self.parser.convert_urls('https://example.com/submitform?field1=val1+val3&field2=val2'), - 'https://example.com/submitform?field1=val1+val3&field2=val2') + '' + 'https://example.com/submitform?field1=val1+val3&field2=val2') def test_url_chars_anchor(self): self.assertEqual(self.parser.convert_urls('http://www.example.com/menu#section_1'), - 'http://www.example.com/menu#section_1') + '' + 'http://www.example.com/menu#section_1') def test_url_chars_exclam(self): - self.assertEqual(self.parser.convert_urls('https://groups.google.com/forum/?fromgroups#!categories/evennia/ainneve'), - 'https://groups.google.com/forum/?fromgroups#!categories/evennia/ainneve') + self.assertEqual(self.parser.convert_urls('https://groups.google.com/forum/' + '?fromgroups#!categories/evennia/ainneve'), + 'https://groups.google.com/forum/?fromgroups#!categories/evennia/ainneve') def test_url_edge_leadingw(self): self.assertEqual(self.parser.convert_urls('wwww.example.com'), - 'wwww.example.com') + 'wwww.example.com') def test_url_edge_following_period_eol(self): self.assertEqual(self.parser.convert_urls('www.example.com.'), - 'www.example.com.') + 'www.example.com.') def test_url_edge_following_period(self): self.assertEqual(self.parser.convert_urls('see www.example.com. '), - 'see www.example.com. ') + 'see www.example.com. ') def test_url_edge_brackets(self): self.assertEqual(self.parser.convert_urls('[http://example.com/]'), - '[http://example.com/]') + '[http://example.com/]') def test_url_edge_multiline(self): self.assertEqual(self.parser.convert_urls(' * http://example.com/info\n * bullet'), - ' * http://example.com/info\n * bullet') + ' * ' + 'http://example.com/info\n * bullet') def test_url_edge_following_htmlentity(self): self.assertEqual(self.parser.convert_urls('http://example.com/info<span>'), - 'http://example.com/info<span>') + 'http://example.com/info<span>') def test_url_edge_surrounded_spans(self): self.assertEqual(self.parser.convert_urls('http://example.com/'), - 'http://example.com/') + '' + 'http://example.com/') from evennia.utils import inlinefuncs + class TestInlineFuncs(TestCase): - "Test the nested inlinefunc module" + """Test the nested inlinefunc module""" def test_nofunc(self): self.assertEqual(inlinefuncs.parse_inlinefunc( "as$382ewrw w we w werw,|44943}"), @@ -358,11 +363,50 @@ class TestInlineFuncs(TestCase): from evennia.utils import evform + class TestEvForm(TestCase): def test_form(self): self.maxDiff = None self.assertEqual(evform._test(), - u'.------------------------------------------------.\n| |\n| Name: \x1b[0m\x1b[1m\x1b[32mTom\x1b[1m\x1b[32m \x1b[1m\x1b[32mthe\x1b[1m\x1b[32m \x1b[0m \x1b[0m Player: \x1b[0m\x1b[1m\x1b[33mGriatch \x1b[0m\x1b[0m\x1b[1m\x1b[32m\x1b[1m\x1b[32m\x1b[1m\x1b[32m\x1b[1m\x1b[32m\x1b[0m\x1b[0m |\n| \x1b[0m\x1b[1m\x1b[32mBouncer\x1b[0m \x1b[0m |\n| |\n >----------------------------------------------<\n| |\n| Desc: \x1b[0mA sturdy \x1b[0m \x1b[0m STR: \x1b[0m12 \x1b[0m\x1b[0m\x1b[0m\x1b[0m DEX: \x1b[0m10 \x1b[0m\x1b[0m\x1b[0m\x1b[0m |\n| \x1b[0mfellow\x1b[0m \x1b[0m INT: \x1b[0m5 \x1b[0m\x1b[0m\x1b[0m\x1b[0m STA: \x1b[0m18 \x1b[0m\x1b[0m\x1b[0m\x1b[0m |\n| \x1b[0m \x1b[0m LUC: \x1b[0m10 \x1b[0m\x1b[0m\x1b[0m MAG: \x1b[0m3 \x1b[0m\x1b[0m\x1b[0m |\n| |\n >----------.-----------------------------------<\n| | |\n| \x1b[0mHP\x1b[0m|\x1b[0mMV \x1b[0m|\x1b[0mMP\x1b[0m | \x1b[0mSkill \x1b[0m|\x1b[0mValue \x1b[0m|\x1b[0mExp \x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m |\n| ~~+~~~+~~ | ~~~~~~~~~~~+~~~~~~~~~~+~~~~~~~~~~~ |\n| \x1b[0m**\x1b[0m|\x1b[0m***\x1b[0m\x1b[0m|\x1b[0m**\x1b[0m\x1b[0m | \x1b[0mShooting \x1b[0m|\x1b[0m12 \x1b[0m|\x1b[0m550/1200 \x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m |\n| \x1b[0m \x1b[0m|\x1b[0m**\x1b[0m \x1b[0m|\x1b[0m*\x1b[0m \x1b[0m | \x1b[0mHerbalism \x1b[0m|\x1b[0m14 \x1b[0m|\x1b[0m990/1400 \x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m |\n| \x1b[0m \x1b[0m|\x1b[0m \x1b[0m|\x1b[0m \x1b[0m | \x1b[0mSmithing \x1b[0m|\x1b[0m9 \x1b[0m|\x1b[0m205/900 \x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m |\n| | |\n -----------`-------------------------------------\n') + u'.------------------------------------------------.\n' + u'| |\n' + u'| Name: \x1b[0m\x1b[1m\x1b[32mTom\x1b[1m\x1b[32m \x1b' + u'[1m\x1b[32mthe\x1b[1m\x1b[32m \x1b[0m \x1b[0m ' + u'Player: \x1b[0m\x1b[1m\x1b[33mGriatch ' + u'\x1b[0m\x1b[0m\x1b[1m\x1b[32m\x1b[1m\x1b[32m\x1b[1m\x1b[32m\x1b[1m\x1b[32m\x1b[0m\x1b[0m ' + u'|\n' + u'| \x1b[0m\x1b[1m\x1b[32mBouncer\x1b[0m \x1b[0m |\n' + u'| |\n' + u' >----------------------------------------------<\n' + u'| |\n' + u'| Desc: \x1b[0mA sturdy \x1b[0m \x1b[0m' + u' STR: \x1b[0m12 \x1b[0m\x1b[0m\x1b[0m\x1b[0m' + u' DEX: \x1b[0m10 \x1b[0m\x1b[0m\x1b[0m\x1b[0m |\n' + u'| \x1b[0mfellow\x1b[0m \x1b[0m' + u' INT: \x1b[0m5 \x1b[0m\x1b[0m\x1b[0m\x1b[0m' + u' STA: \x1b[0m18 \x1b[0m\x1b[0m\x1b[0m\x1b[0m |\n' + u'| \x1b[0m \x1b[0m' + u' LUC: \x1b[0m10 \x1b[0m\x1b[0m\x1b[0m' + u' MAG: \x1b[0m3 \x1b[0m\x1b[0m\x1b[0m |\n' + u'| |\n' + u' >----------.-----------------------------------<\n' + u'| | |\n' + u'| \x1b[0mHP\x1b[0m|\x1b[0mMV \x1b[0m|\x1b[0mMP\x1b[0m ' + u'| \x1b[0mSkill \x1b[0m|\x1b[0mValue \x1b[0m' + u'|\x1b[0mExp \x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m |\n' + u'| ~~+~~~+~~ | ~~~~~~~~~~~+~~~~~~~~~~+~~~~~~~~~~~ |\n' + u'| \x1b[0m**\x1b[0m|\x1b[0m***\x1b[0m\x1b[0m|\x1b[0m**\x1b[0m\x1b[0m ' + u'| \x1b[0mShooting \x1b[0m|\x1b[0m12 \x1b[0m' + u'|\x1b[0m550/1200 \x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m |\n' + u'| \x1b[0m \x1b[0m|\x1b[0m**\x1b[0m \x1b[0m|\x1b[0m*\x1b[0m \x1b[0m ' + u'| \x1b[0mHerbalism \x1b[0m|\x1b[0m14 \x1b[0m' + u'|\x1b[0m990/1400 \x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m |\n' + u'| \x1b[0m \x1b[0m|\x1b[0m \x1b[0m|\x1b[0m \x1b[0m ' + u'| \x1b[0mSmithing \x1b[0m|\x1b[0m9 \x1b[0m' + u'|\x1b[0m205/900 \x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m |\n' + u'| | |\n' + u' -----------`-------------------------------------\n') + def test_ansi_escape(self): # note that in a msg() call, the result would be the correct |-----, # in a print, ansi only gets called once, so ||----- is the result From c2fa13107f1c85d106d8a2dac917f42c4e449fe1 Mon Sep 17 00:00:00 2001 From: BlauFeuer Date: Mon, 20 Feb 2017 02:50:15 -0500 Subject: [PATCH 036/134] Markup, whitespace, and code cleanup --- evennia/utils/prettytable.py | 190 +++++++++++++++++++++++------------ 1 file changed, 128 insertions(+), 62 deletions(-) diff --git a/evennia/utils/prettytable.py b/evennia/utils/prettytable.py index cecd86c921..7e0a9a8a94 100644 --- a/evennia/utils/prettytable.py +++ b/evennia/utils/prettytable.py @@ -65,10 +65,7 @@ else: from cgi import escape # hrule styles -FRAME = 0 -ALL = 1 -NONE = 2 -HEADER = 3 +FRAME, ALL, NONE, HEADER = range(4) # Table styles DEFAULT = 10 @@ -78,12 +75,13 @@ RANDOM = 20 _re = re.compile("\033\[[0-9;]*m") + def _ansi(method): - "decorator for converting ansi in input" + """decorator for converting ansi in input""" def wrapper(self, *args, **kwargs): def convert(inp): if isinstance(inp, basestring): - return parse_ansi("{n%s{n" % inp) + return parse_ansi("|n%s|n" % inp) elif hasattr(inp, '__iter__'): li = [] for element in inp: @@ -96,19 +94,20 @@ def _ansi(method): return li return inp args = [convert(arg) for arg in args] - #kwargs = dict((key, convert(val)) for key, val in kwargs.items()) + # kwargs = dict((key, convert(val)) for key, val in kwargs.items()) return method(self, *args, **kwargs) return wrapper + def _get_size(text): lines = text.split("\n") height = len(lines) width = max([_str_block_width(line) for line in lines]) - return (width, height) + return width, height + class PrettyTable(object): - @_ansi def __init__(self, field_names=None, **kwargs): @@ -153,9 +152,12 @@ class PrettyTable(object): self._widths = [] # Options - self._options = "start end fields header border sortby reversesort sort_key attributes format hrules vrules".split() - self._options.extend("int_format float_format padding_width left_padding_width right_padding_width".split()) - self._options.extend("vertical_char horizontal_char junction_char header_style valign xhtml print_empty".split()) + self._options = "start end fields header border sortby reversesort" \ + " sort_key attributes format hrules vrules".split() + self._options.extend("int_format float_format padding_width " + "left_padding_width right_padding_width".split()) + self._options.extend("vertical_char horizontal_char junction_char" + " header_style valign xhtml print_empty".split()) for option in self._options: if option in kwargs: self._validate_option(option, kwargs[option]) @@ -263,10 +265,10 @@ class PrettyTable(object): if py3k: def __str__(self): - return self.__unicode__() + return self.__unicode__() else: def __str__(self): - return self.__unicode__().encode(self.encoding) + return self.__unicode__().encode(self.encoding) def __unicode__(self): return self.get_string() @@ -283,31 +285,32 @@ class PrettyTable(object): # Secondly, in the _get_options method, where keyword arguments are mixed with persistent settings def _validate_option(self, option, val): - if option in ("field_names"): + if option in "field_names": self._validate_field_names(val) - elif option in ("start", "end", "max_width", "padding_width", "left_padding_width", "right_padding_width", "format"): + elif option in ("start", "end", "max_width", "padding_width", + "left_padding_width", "right_padding_width", "format"): self._validate_nonnegative_int(option, val) - elif option in ("sortby"): + elif option in "sortby": self._validate_field_name(option, val) - elif option in ("sort_key"): + elif option in "sort_key": self._validate_function(option, val) - elif option in ("hrules"): + elif option in "hrules": self._validate_hrules(option, val) - elif option in ("vrules"): + elif option in "vrules": self._validate_vrules(option, val) - elif option in ("fields"): + elif option in "fields": self._validate_all_field_names(option, val) elif option in ("header", "border", "reversesort", "xhtml", "print_empty"): self._validate_true_or_false(option, val) - elif option in ("header_style"): + elif option in "header_style": self._validate_header_style(val) - elif option in ("int_format"): + elif option in "int_format": self._validate_int_format(option, val) - elif option in ("float_format"): + elif option in "float_format": self._validate_float_format(option, val) elif option in ("vertical_char", "horizontal_char", "junction_char"): self._validate_single_char(option, val) - elif option in ("attributes"): + elif option in "attributes": self._validate_attributes(option, val) else: raise Exception("Unrecognised option: %s!" % option) @@ -316,14 +319,16 @@ class PrettyTable(object): # Check for appropriate length if self._field_names: try: - assert len(val) == len(self._field_names) + assert len(val) == len(self._field_names) except AssertionError: - raise Exception("Field name list has incorrect number of values, (actual) %d!=%d (expected)" % (len(val), len(self._field_names))) + raise Exception("Field name list has incorrect number of values, (actual) %d!=%d (expected)" + % (len(val), len(self._field_names))) if self._rows: try: - assert len(val) == len(self._rows[0]) + assert len(val) == len(self._rows[0]) except AssertionError: - raise Exception("Field name list has incorrect number of values, (actual) %d!=%d (expected)" % (len(val), len(self._rows[0]))) + raise Exception("Field name list has incorrect number of values, (actual) %d!=%d (expected)" + % (len(val), len(self._rows[0]))) # Check for uniqueness try: assert len(val) == len(set(val)) @@ -338,13 +343,13 @@ class PrettyTable(object): def _validate_align(self, val): try: - assert val in ["l","c","r"] + assert val in ["l", "c", "r"] except AssertionError: raise Exception("Alignment %s is invalid, use l, c or r!" % val) def _validate_valign(self, val): try: - assert val in ["t","m","b",None] + assert val in ["t", "m", "b", None] except AssertionError: raise Exception("Alignment %s is invalid, use t, m, b or None!" % val) @@ -436,6 +441,7 @@ class PrettyTable(object): fields - list or tuple of field names""" return self._field_names + def _set_field_names(self, val): val = [self._unicode(x) for x in val] self._validate_option("field_names", val) @@ -461,30 +467,37 @@ class PrettyTable(object): else: for field in self._field_names: self._valign[field] = "t" + field_names = property(_get_field_names, _set_field_names) def _get_align(self): return self._align + def _set_align(self, val): self._validate_align(val) for field in self._field_names: self._align[field] = val + align = property(_get_align, _set_align) def _get_valign(self): return self._valign + def _set_valign(self, val): self._validate_valign(val) for field in self._field_names: self._valign[field] = val + valign = property(_get_valign, _set_valign) def _get_max_width(self): return self._max_width + def _set_max_width(self, val): self._validate_option("max_width", val) for field in self._field_names: self._max_width[field] = val + max_width = property(_get_max_width, _set_max_width) def _get_fields(self): @@ -494,9 +507,11 @@ class PrettyTable(object): fields - list or tuple of field names to include in displays""" return self._fields + def _set_fields(self, val): self._validate_option("fields", val) self._fields = val + fields = property(_get_fields, _set_fields) def _get_start(self): @@ -510,6 +525,7 @@ class PrettyTable(object): def _set_start(self, val): self._validate_option("start", val) self._start = val + start = property(_get_start, _set_start) def _get_end(self): @@ -519,9 +535,11 @@ class PrettyTable(object): end - index of last data row to include in output PLUS ONE (list slice style)""" return self._end + def _set_end(self, val): self._validate_option("end", val) self._end = val + end = property(_get_end, _set_end) def _get_sortby(self): @@ -531,9 +549,11 @@ class PrettyTable(object): sortby - field name to sort by""" return self._sortby + def _set_sortby(self, val): self._validate_option("sortby", val) self._sortby = val + sortby = property(_get_sortby, _set_sortby) def _get_reversesort(self): @@ -543,9 +563,11 @@ class PrettyTable(object): reveresort - set to True to sort by descending order, or False to sort by ascending order""" return self._reversesort + def _set_reversesort(self, val): self._validate_option("reversesort", val) self._reversesort = val + reversesort = property(_get_reversesort, _set_reversesort) def _get_sort_key(self): @@ -555,9 +577,11 @@ class PrettyTable(object): sort_key - a function which takes one argument and returns something to be sorted""" return self._sort_key + def _set_sort_key(self, val): self._validate_option("sort_key", val) self._sort_key = val + sort_key = property(_get_sort_key, _set_sort_key) def _get_header(self): @@ -567,9 +591,11 @@ class PrettyTable(object): header - print a header showing field names (True or False)""" return self._header + def _set_header(self, val): self._validate_option("header", val) self._header = val + header = property(_get_header, _set_header) def _get_header_style(self): @@ -579,9 +605,11 @@ class PrettyTable(object): header_style - stylisation to apply to field names in header ("cap", "title", "upper", "lower" or None)""" return self._header_style + def _set_header_style(self, val): self._validate_header_style(val) self._header_style = val + header_style = property(_get_header_style, _set_header_style) def _get_border(self): @@ -591,9 +619,11 @@ class PrettyTable(object): border - print a border around the table (True or False)""" return self._border + def _set_border(self, val): self._validate_option("border", val) self._border = val + border = property(_get_border, _set_border) def _get_hrules(self): @@ -603,9 +633,11 @@ class PrettyTable(object): hrules - horizontal rules style. Allowed values: FRAME, ALL, HEADER, NONE""" return self._hrules + def _set_hrules(self, val): self._validate_option("hrules", val) self._hrules = val + hrules = property(_get_hrules, _set_hrules) def _get_vrules(self): @@ -615,9 +647,11 @@ class PrettyTable(object): vrules - vertical rules style. Allowed values: FRAME, ALL, NONE""" return self._vrules + def _set_vrules(self, val): self._validate_option("vrules", val) self._vrules = val + vrules = property(_get_vrules, _set_vrules) def _get_int_format(self): @@ -626,10 +660,12 @@ class PrettyTable(object): int_format - integer format string""" return self._int_format + def _set_int_format(self, val): -# self._validate_option("int_format", val) + # self._validate_option("int_format", val) for field in self._field_names: self._int_format[field] = val + int_format = property(_get_int_format, _set_int_format) def _get_float_format(self): @@ -638,10 +674,12 @@ class PrettyTable(object): float_format - floating point format string""" return self._float_format + def _set_float_format(self, val): -# self._validate_option("float_format", val) + # self._validate_option("float_format", val) for field in self._field_names: self._float_format[field] = val + float_format = property(_get_float_format, _set_float_format) def _get_padding_width(self): @@ -651,9 +689,11 @@ class PrettyTable(object): padding_width - number of spaces, must be a positive integer""" return self._padding_width + def _set_padding_width(self, val): self._validate_option("padding_width", val) self._padding_width = val + padding_width = property(_get_padding_width, _set_padding_width) def _get_left_padding_width(self): @@ -663,9 +703,11 @@ class PrettyTable(object): left_padding - number of spaces, must be a positive integer""" return self._left_padding_width + def _set_left_padding_width(self, val): self._validate_option("left_padding_width", val) self._left_padding_width = val + left_padding_width = property(_get_left_padding_width, _set_left_padding_width) def _get_right_padding_width(self): @@ -675,9 +717,11 @@ class PrettyTable(object): right_padding - number of spaces, must be a positive integer""" return self._right_padding_width + def _set_right_padding_width(self, val): self._validate_option("right_padding_width", val) self._right_padding_width = val + right_padding_width = property(_get_right_padding_width, _set_right_padding_width) def _get_vertical_char(self): @@ -687,10 +731,12 @@ class PrettyTable(object): vertical_char - single character string used to draw vertical lines""" return self._vertical_char + def _set_vertical_char(self, val): val = self._unicode(val) self._validate_option("vertical_char", val) self._vertical_char = val + vertical_char = property(_get_vertical_char, _set_vertical_char) def _get_horizontal_char(self): @@ -700,10 +746,12 @@ class PrettyTable(object): horizontal_char - single character string used to draw horizontal lines""" return self._horizontal_char + def _set_horizontal_char(self, val): val = self._unicode(val) self._validate_option("horizontal_char", val) self._horizontal_char = val + horizontal_char = property(_get_horizontal_char, _set_horizontal_char) def _get_junction_char(self): @@ -713,10 +761,12 @@ class PrettyTable(object): junction_char - single character string used to draw line junctions""" return self._junction_char + def _set_junction_char(self, val): val = self._unicode(val) self._validate_option("vertical_char", val) self._junction_char = val + junction_char = property(_get_junction_char, _set_junction_char) def _get_format(self): @@ -726,9 +776,11 @@ class PrettyTable(object): format - True or False""" return self._format + def _set_format(self, val): self._validate_option("format", val) self._format = val + format = property(_get_format, _set_format) def _get_print_empty(self): @@ -738,9 +790,11 @@ class PrettyTable(object): print_empty - True or False""" return self._print_empty + def _set_print_empty(self, val): self._validate_option("print_empty", val) self._print_empty = val + print_empty = property(_get_print_empty, _set_print_empty) def _get_attributes(self): @@ -750,9 +804,11 @@ class PrettyTable(object): attributes - dictionary of attributes""" return self._attributes + def _set_attributes(self, val): self._validate_option("attributes", val) self._attributes = val + attributes = property(_get_attributes, _set_attributes) ############################## @@ -825,8 +881,8 @@ class PrettyTable(object): self.border = random.choice((True, False)) self._hrules = random.choice((ALL, FRAME, HEADER, NONE)) self._vrules = random.choice((ALL, FRAME, NONE)) - self.left_padding_width = random.randint(0,5) - self.right_padding_width = random.randint(0,5) + self.left_padding_width = random.randint(0, 5) + self.right_padding_width = random.randint(0, 5) self.vertical_char = random.choice("~!@#$%^&*()_+|-=\{}[];':\",./;<>?") self.horizontal_char = random.choice("~!@#$%^&*()_+|-=\{}[];':\",./;<>?") self.junction_char = random.choice("~!@#$%^&*()_+|-=\{}[];':\",./;<>?") @@ -846,9 +902,10 @@ class PrettyTable(object): has fields""" if self._field_names and len(row) != len(self._field_names): - raise Exception("Row has incorrect number of values, (actual) %d!=%d (expected)" %(len(row),len(self._field_names))) + raise Exception("Row has incorrect number of values, (actual) %d!=%d (expected)" + % (len(row), len(self._field_names))) if not self._field_names: - self.field_names = [("Field %d" % (n+1)) for n in range(0,len(row))] + self.field_names = [("Field %d" % (n+1)) for n in range(0, len(row))] self._rows.append(list(row)) def del_row(self, row_index): @@ -860,7 +917,7 @@ class PrettyTable(object): row_index - The index of the row you want to delete. Indexing starts at 0.""" if row_index > len(self._rows)-1: - raise Exception("Cant delete row at index %d, table only has %d rows!" % (row_index, len(self._rows))) + raise Exception("Can't delete row at index %d, table only has %d rows!" % (row_index, len(self._rows))) del self._rows[row_index] @_ansi @@ -1113,7 +1170,7 @@ class PrettyTable(object): def _stringify_row(self, row, options): - for index, field, value, width, in zip(range(0,len(row)), self._field_names, row, self._widths): + for index, field, value, width, in zip(range(0, len(row)), self._field_names, row, self._widths): # Enforce max widths lines = value.split("\n") new_lines = [] @@ -1145,14 +1202,14 @@ class PrettyTable(object): valign = self._valign[field] lines = value.split("\n") - dHeight = row_height - len(lines) - if dHeight: + dheight = row_height - len(lines) + if dheight: if valign == "m": - lines = [""] * (dHeight // 2) + lines + [""] * (dHeight - (dHeight // 2)) + lines = [""] * (dheight // 2) + lines + [""] * (dheight - (dheight // 2)) elif valign == "b": - lines = [""] * dHeight + lines + lines = [""] * dheight + lines else: - lines = lines + [""] * dHeight + lines += [""] * dheight y = 0 for l in lines: @@ -1174,7 +1231,7 @@ class PrettyTable(object): bits[y].pop() bits[y].append(options["vertical_char"]) - if options["border"] and options["hrules"]== ALL: + if options["border"] and options["hrules"] == ALL: bits[row_height-1].append("\n") bits[row_height-1].append(self._hrule) @@ -1227,8 +1284,7 @@ class PrettyTable(object): else: linebreak = "
" - open_tag = [] - open_tag.append("%s" % (lpad, rpad, escape(field).replace("\n", linebreak))) + lines.append(" %s" + % (lpad, rpad, escape(field).replace("\n", linebreak))) lines.append(" ") # Data @@ -1306,14 +1363,16 @@ class PrettyTable(object): aligns = [] valigns = [] for field in self._field_names: - aligns.append({ "l" : "left", "r" : "right", "c" : "center" }[self._align[field]]) - valigns.append({"t" : "top", "m" : "middle", "b" : "bottom"}[self._valign[field]]) + aligns.append(dict(l="left", r="right", c="center")[self._align[field]]) + valigns.append(dict(t="top", m="middle", b="bottom")[self._valign[field]]) for row in formatted_rows: lines.append(" ") for field, datum, align, valign in zip(self._field_names, row, aligns, valigns): if options["fields"] and field not in options["fields"]: continue - lines.append(" %s" % (lpad, rpad, align, valign, escape(datum).replace("\n", linebreak))) + lines.append(" %s" + % (lpad, rpad, align, valign, escape(datum).replace("\n", linebreak))) lines.append(" ") lines.append("") @@ -1323,10 +1382,11 @@ class PrettyTable(object): # UNICODE WIDTH FUNCTIONS # ############################## + def _char_block_width(char): # Basic Latin, which is probably the most common case - #if char in xrange(0x0021, 0x007e): - #if char >= 0x0021 and char <= 0x007e: + # if char in xrange(0x0021, 0x007e): + # if char >= 0x0021 and char <= 0x007e: if 0x0021 <= char <= 0x007e: return 1 # Chinese, Japanese, Korean (common) @@ -1356,6 +1416,7 @@ def _char_block_width(char): # Take a guess return 1 + def _str_block_width(val): return sum(itermap(_char_block_width, itermap(ord, _re.sub("", val)))) @@ -1364,7 +1425,8 @@ def _str_block_width(val): # TABLE FACTORIES # ############################## -def from_csv(fp, field_names = None, **kwargs): + +def from_csv(fp, field_names=None, **kwargs): dialect = csv.Sniffer().sniff(fp.read(1024)) fp.seek(0) @@ -1381,6 +1443,7 @@ def from_csv(fp, field_names = None, **kwargs): return table + def from_db_cursor(cursor, **kwargs): if cursor.description: @@ -1390,6 +1453,7 @@ def from_db_cursor(cursor, **kwargs): table.add_row(row) return table + class TableHandler(HTMLParser): def __init__(self, **kwargs): @@ -1403,12 +1467,12 @@ class TableHandler(HTMLParser): self.last_content = "" self.is_last_row_header = False - def handle_starttag(self,tag, attrs): + def handle_starttag(self, tag, attrs): self.active = tag if tag == "th": self.is_last_row_header = True - def handle_endtag(self,tag): + def handle_endtag(self, tag): if tag in ["th", "td"]: stripped_content = self.last_content.strip() self.last_row.append(stripped_content) @@ -1425,7 +1489,6 @@ class TableHandler(HTMLParser): self.last_content = " " self.active = None - def handle_data(self, data): self.last_content += data @@ -1437,10 +1500,10 @@ class TableHandler(HTMLParser): for row in self.rows: if len(row[0]) < self.max_row_width: appends = self.max_row_width - len(row[0]) - for i in range(1,appends): + for _ in range(1, appends): row[0].append("-") - if row[1] == True: + if row[1] is True: self.make_fields_unique(row[0]) table.field_names = row[0] else: @@ -1456,6 +1519,7 @@ class TableHandler(HTMLParser): if fields[i] == fields[j]: fields[j] += "'" + def from_html(html_code, **kwargs): """ Generates a list of PrettyTables from a string of HTML code. Each in @@ -1466,6 +1530,7 @@ def from_html(html_code, **kwargs): parser.feed(html_code) return parser.tables + def from_html_one(html_code, **kwargs): """ Generates a PrettyTables from a string of HTML code which contains only a @@ -1483,6 +1548,7 @@ def from_html_one(html_code, **kwargs): # MAIN (TEST FUNCTION) # ############################## + def main(): x = PrettyTable(["City name", "Area", "Population", "Annual Rainfall"]) @@ -1490,7 +1556,7 @@ def main(): x.reversesort = True x.int_format["Area"] = "04d" x.float_format = "6.1f" - x.align["City name"] = "l" # Left align city names + x.align["City name"] = "l" # Left align city names x.add_row(["Adelaide", 1295, 1158259, 600.5]) x.add_row(["Brisbane", 5905, 1857594, 1146.4]) x.add_row(["Darwin", 112, 120900, 1714.7]) From 6ac3e0b4c907732901f93350e86f5766dbafbfbe Mon Sep 17 00:00:00 2001 From: BlauFeuer Date: Mon, 20 Feb 2017 02:52:23 -0500 Subject: [PATCH 037/134] Remove legacy default screen - no longer used --- evennia/commands/connection_screen.py | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 evennia/commands/connection_screen.py diff --git a/evennia/commands/connection_screen.py b/evennia/commands/connection_screen.py deleted file mode 100644 index 36f227433b..0000000000 --- a/evennia/commands/connection_screen.py +++ /dev/null @@ -1,21 +0,0 @@ -# -# This is Evennia's default connection screen. It is imported -# and run from server/conf/connection_screens.py. -# - -from django.conf import settings -from evennia.utils import utils - -DEFAULT_SCREEN = \ -"""{b=============================================================={n - Welcome to {g%s{n, version %s! - - If you have an existing account, connect to it by typing: - {wconnect {n - If you need to create an account, type (without the <>'s): - {wcreate {n - - If you have spaces in your username, enclose it in double quotes. - Enter {whelp{n for more info. {wlook{n will re-show this screen. -{b=============================================================={n""" \ -% (settings.SERVERNAME, utils.get_evennia_version()) From 6fae85c32adde433283787fbdba9ac097dd831a0 Mon Sep 17 00:00:00 2001 From: BlauFeuer Date: Mon, 20 Feb 2017 21:53:49 -0500 Subject: [PATCH 038/134] Markup string constant change, whitespace adjust --- evennia/server/initial_setup.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/evennia/server/initial_setup.py b/evennia/server/initial_setup.py index 4cdfc8763d..185973ed48 100644 --- a/evennia/server/initial_setup.py +++ b/evennia/server/initial_setup.py @@ -15,8 +15,7 @@ from evennia.server.models import ServerConfig from evennia.utils import create, logger -ERROR_NO_SUPERUSER = \ - """ +ERROR_NO_SUPERUSER = """ No superuser exists yet. The superuser is the 'owner' account on the Evennia server. Create a new superuser using the command @@ -26,16 +25,14 @@ ERROR_NO_SUPERUSER = \ """ -LIMBO_DESC = \ - _(""" -Welcome to your new {wEvennia{n-based game! Visit http://www.evennia.com if you need +LIMBO_DESC = _(""" +Welcome to your new |wEvennia|n-based game! Visit http://www.evennia.com if you need help, want to contribute, report issues or just join the community. -As Player #1 you can create a demo/tutorial area with {w@batchcommand tutorial_world.build{n. +As Player #1 you can create a demo/tutorial area with |w@batchcommand tutorial_world.build|n. """) -WARNING_POSTGRESQL_FIX = \ - """ +WARNING_POSTGRESQL_FIX = """ PostgreSQL-psycopg2 compatibility fix: The in-game channels {chan1}, {chan2} and {chan3} were created, but the superuser was not yet connected to them. Please use in From f14667cdd69a0911f583a594dc13f8dcf1d33b64 Mon Sep 17 00:00:00 2001 From: BlauFeuer Date: Mon, 20 Feb 2017 21:56:56 -0500 Subject: [PATCH 039/134] Markup change, whitespace and typo fixes --- evennia/server/portal/irc.py | 43 +++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/evennia/server/portal/irc.py b/evennia/server/portal/irc.py index e6d761025d..274f7bc83d 100644 --- a/evennia/server/portal/irc.py +++ b/evennia/server/portal/irc.py @@ -40,18 +40,23 @@ IRC_MAGENTA = "13" IRC_DGREY = "14" IRC_GRAY = "15" -# test: +# obsolete test: # {rred {ggreen {yyellow {bblue {mmagenta {ccyan {wwhite {xdgrey # {Rdred {Gdgreen {Ydyellow {Bdblue {Mdmagenta {Cdcyan {Wlgrey {Xblack # {[rredbg {[ggreenbg {[yyellowbg {[bbluebg {[mmagentabg {[ccyanbg {[wlgreybg {[xblackbg +# test: +# |rred |ggreen |yyellow |bblue |mmagenta |ccyan |wwhite |xdgrey +# |Rdred |Gdgreen |Ydyellow |Bdblue |Mdmagenta |Cdcyan |Wlgrey |Xblack +# |[rredbg |[ggreenbg |[yyellowbg |[bbluebg |[mmagentabg |[ccyanbg |[wlgreybg |[xblackbg + IRC_COLOR_MAP = dict([ # obs - {-type colors are deprecated but still used in many places. - (r'{n', IRC_RESET), # reset + (r'{n', IRC_RESET), # reset (r'{/', ""), # line break - (r'{-', " "), # tab - (r'{_', " "), # space - (r'{*', ""), # invert + (r'{-', " "), # tab + (r'{_', " "), # space + (r'{*', ""), # invert (r'{^', ""), # blinking text (r'{r', IRC_COLOR + IRC_RED), @@ -69,7 +74,7 @@ IRC_COLOR_MAP = dict([ (r'{B', IRC_COLOR + IRC_DBLUE), (r'{M', IRC_COLOR + IRC_DMAGENTA), (r'{C', IRC_COLOR + IRC_DCYAN), - (r'{W', IRC_COLOR + IRC_GRAY), # light grey + (r'{W', IRC_COLOR + IRC_GRAY), # light grey (r'{X', IRC_COLOR + IRC_BLACK), # pure black (r'{[r', IRC_COLOR + IRC_NORMAL + "," + IRC_DRED), @@ -79,14 +84,14 @@ IRC_COLOR_MAP = dict([ (r'{[m', IRC_COLOR + IRC_NORMAL + "," + IRC_DMAGENTA), (r'{[c', IRC_COLOR + IRC_NORMAL + "," + IRC_DCYAN), (r'{[w', IRC_COLOR + IRC_NORMAL + "," + IRC_GRAY), # light grey background - (r'{[x', IRC_COLOR + IRC_NORMAL + "," + IRC_BLACK), # pure black background + (r'{[x', IRC_COLOR + IRC_NORMAL + "," + IRC_BLACK), # pure black background # |-type formatting is the thing to use. - (r'|n', IRC_RESET), # reset + (r'|n', IRC_RESET), # reset (r'|/', ""), # line break - (r'|-', " "), # tab - (r'|_', " "), # space - (r'|*', ""), # invert + (r'|-', " "), # tab + (r'|_', " "), # space + (r'|*', ""), # invert (r'|^', ""), # blinking text (r'|r', IRC_COLOR + IRC_RED), @@ -104,7 +109,7 @@ IRC_COLOR_MAP = dict([ (r'|B', IRC_COLOR + IRC_DBLUE), (r'|M', IRC_COLOR + IRC_DMAGENTA), (r'|C', IRC_COLOR + IRC_DCYAN), - (r'|W', IRC_COLOR + IRC_GRAY), # light grey + (r'|W', IRC_COLOR + IRC_GRAY), # light grey (r'|X', IRC_COLOR + IRC_BLACK), # pure black (r'|[r', IRC_COLOR + IRC_NORMAL + "," + IRC_DRED), @@ -114,12 +119,13 @@ IRC_COLOR_MAP = dict([ (r'|[m', IRC_COLOR + IRC_NORMAL + "," + IRC_DMAGENTA), (r'|[c', IRC_COLOR + IRC_NORMAL + "," + IRC_DCYAN), (r'|[w', IRC_COLOR + IRC_NORMAL + "," + IRC_GRAY), # light grey background - (r'|[x', IRC_COLOR + IRC_NORMAL + "," + IRC_BLACK) # pure black background + (r'|[x', IRC_COLOR + IRC_NORMAL + "," + IRC_BLACK) # pure black background ]) RE_IRC_COLOR = re.compile(r"|".join([re.escape(key) for key in viewkeys(IRC_COLOR_MAP)]), re.DOTALL) -RE_MXP = re.compile(r'\{lc(.*?)\{lt(.*?)\{le', re.DOTALL) +RE_MXP = re.compile(r'\|lc(.*?)\|lt(.*?)\|le', re.DOTALL) RE_ANSI_ESCAPES = re.compile(r"(%s)" % "|".join(("{{", "%%", "\\\\")), re.DOTALL) + def sub_irc(ircmatch): """ Substitute irc color info. Used by re.sub. @@ -133,6 +139,7 @@ def sub_irc(ircmatch): """ return IRC_COLOR_MAP.get(ircmatch.group(), "") + def parse_irc_colors(string): """ Parse {-type syntax and replace with IRC color markers @@ -156,9 +163,10 @@ def parse_irc_colors(string): # IRC bot + class IRCBot(irc.IRCClient, Session): """ - An IRC bot that tracks actitivity in a channel as well + An IRC bot that tracks activity in a channel as well as sends text to it when prompted """ @@ -246,14 +254,14 @@ class IRCBot(irc.IRCClient, Session): self.sendLine("NAMES %s" % self.channel) def irc_RPL_NAMREPLY(self, prefix, params): - "Handles IRC NAME request returns (nicklist)" + """"Handles IRC NAME request returns (nicklist)""" channel = params[2].lower() if channel != self.channel.lower(): return self.nicklist += params[3].split(' ') def irc_RPL_ENDOFNAMES(self, prefix, params): - "Called when the nicklist has finished being returned." + """Called when the nicklist has finished being returned.""" channel = params[1].lower() if channel != self.channel.lower(): return @@ -271,7 +279,6 @@ class IRCBot(irc.IRCClient, Session): """ self.data_in(text="", type="ping", user="server", channel=self.channel, timing=time) - def data_in(self, text=None, **kwargs): """ Data IRC -> Server. From 9be2db3f9351c829a06e3c14a556d188d25d5b98 Mon Sep 17 00:00:00 2001 From: BlauFeuer Date: Mon, 20 Feb 2017 22:01:50 -0500 Subject: [PATCH 040/134] Markup change, whitespace and comment edits --- evennia/server/portal/ssh.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/evennia/server/portal/ssh.py b/evennia/server/portal/ssh.py index 6a42da4b1d..1d70f14cfb 100644 --- a/evennia/server/portal/ssh.py +++ b/evennia/server/portal/ssh.py @@ -32,7 +32,7 @@ packages). try: from twisted.conch.ssh.keys import Key except ImportError: - raise ImportError (_SSH_IMPORT_ERROR) + raise ImportError(_SSH_IMPORT_ERROR) from twisted.conch.ssh.userauth import SSHUserAuthServer from twisted.conch.ssh import common @@ -49,7 +49,7 @@ from evennia.players.models import PlayerDB from evennia.utils import ansi from evennia.utils.utils import to_str -_RE_N = re.compile(r"\{n$") +_RE_N = re.compile(r"\|n$") _RE_SCREENREADER_REGEX = re.compile(r"%s" % settings.SCREENREADER_REGEX_STRIP, re.DOTALL + re.MULTILINE) _GAME_DIR = settings.GAME_DIR @@ -206,7 +206,7 @@ class SshProtocol(Manhole, session.Session): """ for line in string.split('\n'): - #this is the telnet-specific method for sending + # the telnet-specific method for sending self.terminal.write(line) self.terminal.nextLine() @@ -255,7 +255,7 @@ class SshProtocol(Manhole, session.Session): Note that it must be actively turned back on again! """ - #print "telnet.send_text", args,kwargs + # print "telnet.send_text", args,kwargs # DEBUG text = args[0] if args else "" if text is None: return @@ -268,8 +268,8 @@ class SshProtocol(Manhole, session.Session): useansi = options.get("ansi", flags.get('ANSI', True)) raw = options.get("raw", flags.get("RAW", False)) nocolor = options.get("nocolor", flags.get("NOCOLOR") or not (xterm256 or useansi)) - #echo = options.get("echo", None) - screenreader = options.get("screenreader", flags.get("SCREENREADER", False)) + # echo = options.get("echo", None) # DEBUG + screenreader = options.get("screenreader", flags.get("SCREENREADER", False)) if screenreader: # screenreader mode cleans up output @@ -283,7 +283,7 @@ class SshProtocol(Manhole, session.Session): else: # we need to make sure to kill the color at the end in order # to match the webclient output. - linetosend = ansi.parse_ansi(_RE_N.sub("", text) + "{n", strip_ansi=nocolor, xterm256=xterm256, mxp=False) + linetosend = ansi.parse_ansi(_RE_N.sub("", text) + "|n", strip_ansi=nocolor, xterm256=xterm256, mxp=False) self.sendLine(linetosend) def send_prompt(self, *args, **kwargs): @@ -453,11 +453,10 @@ def makeFactory(configdict): factory.publicKeys = {'ssh-rsa': publicKey} factory.privateKeys = {'ssh-rsa': privateKey} except Exception as err: - print ( "getKeyPair error: {err}\n WARNING: Evennia could not " \ - "auto-generate SSH keypair. Using conch default keys instead.\n" \ - "If this error persists, create {pub} and " \ - "{priv} yourself using third-party tools.".format( - err=err, pub=pubkeyfile, priv=privkeyfile)) + print("getKeyPair error: {err}\n WARNING: Evennia could not " + "auto-generate SSH keypair. Using conch default keys instead.\n" + "If this error persists, create {pub} and " + "{priv} yourself using third-party tools.".format(err=err, pub=pubkeyfile, priv=privkeyfile)) factory.services = factory.services.copy() factory.services['ssh-userauth'] = ExtraInfoAuthServer From 0dfd474eff2a70c373a2f291f72a0c99c46b7ad8 Mon Sep 17 00:00:00 2001 From: BlauFeuer Date: Mon, 20 Feb 2017 22:02:54 -0500 Subject: [PATCH 041/134] Markup change, whitespace, comment, docstring edit --- evennia/server/portal/telnet.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/evennia/server/portal/telnet.py b/evennia/server/portal/telnet.py index 5ccb23ddd2..6b432876c2 100644 --- a/evennia/server/portal/telnet.py +++ b/evennia/server/portal/telnet.py @@ -19,12 +19,13 @@ from evennia.server.portal.mxp import Mxp, mxp_parse from evennia.utils import ansi from evennia.utils.utils import to_str -_RE_N = re.compile(r"\{n$") +_RE_N = re.compile(r"\|n$") _RE_LEND = re.compile(r"\n$|\r$|\r\n$|\r\x00$|", re.MULTILINE) _RE_LINEBREAK = re.compile(r"\n\r|\r\n|\n|\r", re.DOTALL + re.MULTILINE) _RE_SCREENREADER_REGEX = re.compile(r"%s" % settings.SCREENREADER_REGEX_STRIP, re.DOTALL + re.MULTILINE) _IDLE_COMMAND = settings.IDLE_COMMAND + "\n" + class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): """ Each player connecting over telnet (ie using most traditional mud @@ -46,7 +47,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): client_address = client_address[0] if client_address else None # this number is counted down for every handshake that completes. # when it reaches 0 the portal/server syncs their data - self.handshakes = 7 # naws, ttype, mccp, mssp, msdp, gmcp, mxp + self.handshakes = 7 # naws, ttype, mccp, mssp, msdp, gmcp, mxp self.init_session(self.protocol_name, client_address, self.factory.sessionhandler) # negotiate client size @@ -79,7 +80,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): self.toggle_nop_keepalive() def _send_nop_keepalive(self): - "Send NOP keepalive unless flag is set" + """Send NOP keepalive unless flag is set""" if self.protocol_flags.get("NOPKEEPALIVE"): self._write(IAC + NOP) @@ -178,7 +179,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): directly. Args: - string (str): Incoming data. + data (str): Incoming data. """ if not data: @@ -205,7 +206,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): self.data_in(text=dat + "\n") def _write(self, data): - "hook overloading the one used in plain telnet" + """hook overloading the one used in plain telnet""" data = data.replace('\n', '\r\n').replace('\r\r\n', '\r\n') super(TelnetProtocol, self)._write(mccp_compress(self, data)) @@ -217,12 +218,11 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): line (str): Line to send. """ - #escape IAC in line mode, and correctly add \r\n + # escape IAC in line mode, and correctly add \r\n line += self.delimiter line = line.replace(IAC, IAC + IAC).replace('\n', '\r\n') return self.transport.write(mccp_compress(self, line)) - # Session hooks def disconnect(self, reason=None): @@ -245,8 +245,8 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): kwargs (any): Options from the protocol. """ - #from evennia.server.profiling.timetrace import timetrace - #text = timetrace(text, "telnet.data_in") + # from evennia.server.profiling.timetrace import timetrace # DEBUG + # text = timetrace(text, "telnet.data_in") # DEBUG self.sessionhandler.data_in(self, **kwargs) @@ -297,7 +297,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): nocolor = options.get("nocolor", flags.get("NOCOLOR") or not (xterm256 or useansi)) echo = options.get("echo", None) mxp = options.get("mxp", flags.get("MXP", False)) - screenreader = options.get("screenreader", flags.get("SCREENREADER", False)) + screenreader = options.get("screenreader", flags.get("SCREENREADER", False)) if screenreader: # screenreader mode cleans up output @@ -308,7 +308,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): # send a prompt instead. if not raw: # processing - prompt = ansi.parse_ansi(_RE_N.sub("", text) + "{n", strip_ansi=nocolor, xterm256=xterm256) + prompt = ansi.parse_ansi(_RE_N.sub("", text) + "|n", strip_ansi=nocolor, xterm256=xterm256) if mxp: prompt = mxp_parse(prompt) prompt = prompt.replace(IAC, IAC + IAC).replace('\n', '\r\n') @@ -335,7 +335,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): else: # we need to make sure to kill the color at the end in order # to match the webclient output. - linetosend = ansi.parse_ansi(_RE_N.sub("", text) + "{n", strip_ansi=nocolor, xterm256=xterm256, mxp=mxp) + linetosend = ansi.parse_ansi(_RE_N.sub("", text) + "|n", strip_ansi=nocolor, xterm256=xterm256, mxp=mxp) if mxp: linetosend = mxp_parse(linetosend) self.sendLine(linetosend) @@ -348,7 +348,6 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): kwargs["options"].update({"send_prompt": True}) self.send_text(*args, **kwargs) - def send_default(self, cmdname, *args, **kwargs): """ Send other oob data From bacb63bfdfea041708b149e9d66f60e1183333d2 Mon Sep 17 00:00:00 2001 From: Griatch Date: Thu, 23 Feb 2017 13:26:29 +0100 Subject: [PATCH 042/134] Update changelog to latest. --- CHANGELOG.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c961ff2197..d0385453c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,23 @@ # Evennia Changelog +# Feb 2017: +New devel branch created, to lead up to Evennia 0.7. + +## Dec 2016: +Lots of bugfixes and considerable uptick in contributors. Unittest coverage +and PEP8 adoption and refactoring. + +## May 2016: +Evennia 0.6 with completely reworked Out-of-band system, making +the message path completely flexible and built around input/outputfuncs. +A completely new webclient, split into the evennia.js library and a +gui library, making it easier to customize. + +## Feb 2016: +Added the new EvMenu and EvMore utilities, updated EvEdit and cleaned up +a lot of the batchcommand functionality. Started work on new Devel branch. + +## Sept 2015: +Evennia 0.5. Merged devel branch, full library format implemented. ## Feb 2015: Development currently in devel/ branch. Moved typeclasses to use From effa6182683c81289b4822fcb0eee03f5d205b1a Mon Sep 17 00:00:00 2001 From: Griatch Date: Thu, 23 Feb 2017 13:26:48 +0100 Subject: [PATCH 043/134] Fix typo in changelog. --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d0385453c5..ab9b5ae728 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ # Evennia Changelog -# Feb 2017: +## Feb 2017: New devel branch created, to lead up to Evennia 0.7. ## Dec 2016: From b04a50fe2827fe09d03ed02971081254e786d115 Mon Sep 17 00:00:00 2001 From: Griatch Date: Mon, 27 Feb 2017 22:01:08 +0100 Subject: [PATCH 044/134] Shut down existing IRC/RSS bots if starting with IRC/RSS_ENABLED=False. Resolves #1238. --- evennia/players/bots.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/evennia/players/bots.py b/evennia/players/bots.py index f4a0c469ba..5f6e327ce2 100644 --- a/evennia/players/bots.py +++ b/evennia/players/bots.py @@ -13,6 +13,9 @@ from evennia.utils import utils _IDLE_TIMEOUT = settings.IDLE_TIMEOUT +_IRC_ENABLED = settings.IRC_ENABLED +_RSS_ENABLED = settings.RSS_ENABLED + _SESSIONS = None @@ -157,6 +160,12 @@ class IRCBot(Bot): irc_ssl (bool): Indicates whether to use SSL connection. """ + if not _IRC_ENABLED: + # the bot was created, then IRC was turned off. We delete + # ourselves (this will also kill the start script) + self.delete() + return + global _SESSIONS if not _SESSIONS: from evennia.server.sessionhandler import SESSIONS as _SESSIONS @@ -360,6 +369,11 @@ class RSSBot(Bot): RuntimeError: If `ev_channel` does not exist. """ + if not _RSS_EMABLED: + # The bot was created, then RSS was turned off. Delete ourselves. + self.delete() + return + global _SESSIONS if not _SESSIONS: from evennia.server.sessionhandler import SESSIONS as _SESSIONS From d301ddd921d90e4b123ade17f88227b174f8ef3a Mon Sep 17 00:00:00 2001 From: Griatch Date: Mon, 27 Feb 2017 22:07:17 +0100 Subject: [PATCH 045/134] Add possible fix for some unittest time issues, related to #1234. --- evennia/contrib/tests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/evennia/contrib/tests.py b/evennia/contrib/tests.py index 8d878ef203..f1a6551fca 100644 --- a/evennia/contrib/tests.py +++ b/evennia/contrib/tests.py @@ -180,8 +180,8 @@ class TestExtendedRoom(CommandTest): def setUp(self): super(TestExtendedRoom, self).setUp() - self.room1.ndb.last_timeslot = "afternoon" - self.room1.ndb.last_season = "winter" + self.room1.ndb.last_timeslot = "evening" + self.room1.ndb.last_season = "spring" self.room1.db.details = {'testdetail': self.DETAIL_DESC} self.room1.db.spring_desc = self.SPRING_DESC self.room1.db.desc = self.OLD_DESC From 86e0db7ea94e9f644f0a6d2d89ed17d713d3e77a Mon Sep 17 00:00:00 2001 From: Griatch Date: Mon, 27 Feb 2017 22:11:47 +0100 Subject: [PATCH 046/134] Implement suggested fix for cmdrate limiter. Resolves #1231. --- evennia/server/portal/portalsessionhandler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evennia/server/portal/portalsessionhandler.py b/evennia/server/portal/portalsessionhandler.py index 93bb7a95e3..9d55959239 100644 --- a/evennia/server/portal/portalsessionhandler.py +++ b/evennia/server/portal/portalsessionhandler.py @@ -365,7 +365,7 @@ class PortalSessionHandler(SessionHandler): pass if session: now = time.time() - if self.command_counter > _MAX_COMMAND_RATE: + if self.command_counter > _MAX_COMMAND_RATE > 0: # data throttle (anti DoS measure) dT = now - self.command_counter_reset self.command_counter = 0 From ce15140bdd27c5f9528adb377487d59d8cb3be66 Mon Sep 17 00:00:00 2001 From: BlauFeuer Date: Tue, 21 Feb 2017 10:21:36 -0500 Subject: [PATCH 047/134] banlist to use evtable, markup, LGTM, PEP 8 --- evennia/commands/default/admin.py | 70 +++++++++++++++---------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/evennia/commands/default/admin.py b/evennia/commands/default/admin.py index 9befacd327..28e7f6b011 100644 --- a/evennia/commands/default/admin.py +++ b/evennia/commands/default/admin.py @@ -9,7 +9,7 @@ import re from django.conf import settings from evennia.server.sessionhandler import SESSIONS from evennia.server.models import ServerConfig -from evennia.utils import prettytable, search, class_from_module +from evennia.utils import evtable, search, class_from_module COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS) @@ -40,7 +40,7 @@ class CmdBoot(COMMAND_DEFAULT_CLASS): help_category = "Admin" def func(self): - "Implementing the function" + """Implementing the function""" caller = self.caller args = self.args @@ -86,7 +86,7 @@ class CmdBoot(COMMAND_DEFAULT_CLASS): # Carry out the booting of the sessions in the boot list. feedback = None - if not 'quiet' in self.switches: + if 'quiet' not in self.switches: feedback = "You have been disconnected by %s.\n" % caller.name if reason: feedback += "\nReason given: %s" % reason @@ -108,13 +108,12 @@ def list_bans(banlist): if not banlist: return "No active bans were found." - table = prettytable.PrettyTable(["{wid", "{wname/ip", "{wdate", "{wreason"]) + table = evtable.EvTable("|wid", "|wname/ip", "|wdate", "|wreason") for inum, ban in enumerate(banlist): - table.add_row([str(inum + 1), - ban[0] and ban[0] or ban[1], - ban[3], ban[4]]) - string = "{wActive bans:{n\n%s" % table - return string + table.add_row(str(inum + 1), + ban[0] and ban[0] or ban[1], + ban[3], ban[4]) + return "|wActive bans:|n\n%s" % table class CmdBan(COMMAND_DEFAULT_CLASS): @@ -174,7 +173,7 @@ class CmdBan(COMMAND_DEFAULT_CLASS): if not self.args or (self.switches and not any(switch in ('ip', 'name') - for switch in self.switches)): + for switch in self.switches)): self.caller.msg(list_bans(banlist)) return @@ -202,7 +201,7 @@ class CmdBan(COMMAND_DEFAULT_CLASS): # save updated banlist banlist.append(bantup) ServerConfig.objects.conf('server_bans', banlist) - self.caller.msg("%s-Ban {w%s{n was added." % (typ, ban)) + self.caller.msg("%s-Ban |w%s|n was added." % (typ, ban)) class CmdUnban(COMMAND_DEFAULT_CLASS): @@ -223,7 +222,7 @@ class CmdUnban(COMMAND_DEFAULT_CLASS): help_category = "Admin" def func(self): - "Implement unbanning" + """Implement unbanning""" banlist = ServerConfig.objects.conf('server_bans') @@ -240,14 +239,14 @@ class CmdUnban(COMMAND_DEFAULT_CLASS): if not banlist: self.caller.msg("There are no bans to clear.") elif not (0 < num < len(banlist) + 1): - self.caller.msg("Ban id {w%s{x was not found." % self.args) + self.caller.msg("Ban id |w%s|x was not found." % self.args) else: # all is ok, clear ban ban = banlist[num - 1] del banlist[num - 1] ServerConfig.objects.conf('server_bans', banlist) self.caller.msg("Cleared ban %s: %s" % - (num, " ".join([s for s in ban[:2]]))) + (num, " ".join([s for s in ban[:2]]))) class CmdDelPlayer(COMMAND_DEFAULT_CLASS): @@ -270,7 +269,7 @@ class CmdDelPlayer(COMMAND_DEFAULT_CLASS): help_category = "Admin" def func(self): - "Implements the command." + """Implements the command.""" caller = self.caller args = self.args @@ -346,7 +345,7 @@ class CmdEmit(COMMAND_DEFAULT_CLASS): help_category = "Admin" def func(self): - "Implement the command" + """Implement the command""" caller = self.caller args = self.args @@ -382,7 +381,7 @@ class CmdEmit(COMMAND_DEFAULT_CLASS): obj = caller.search(objname, global_search=True) if not obj: return - if rooms_only and not obj.location is None: + if rooms_only and obj.location is not None: caller.msg("%s is not a room. Ignored." % objname) continue if players_only and not obj.has_player: @@ -414,7 +413,7 @@ class CmdNewPassword(COMMAND_DEFAULT_CLASS): help_category = "Admin" def func(self): - "Implement the function." + """Implement the function.""" caller = self.caller @@ -455,7 +454,7 @@ class CmdPerm(COMMAND_DEFAULT_CLASS): help_category = "Admin" def func(self): - "Implement function" + """Implement function""" caller = self.caller switches = self.switches @@ -481,14 +480,14 @@ class CmdPerm(COMMAND_DEFAULT_CLASS): caller.msg("You are not allowed to examine this object.") return - string = "Permissions on {w%s{n: " % obj.key + string = "Permissions on |w%s|n: " % obj.key if not obj.permissions.all(): string += "" else: string += ", ".join(obj.permissions.all()) if (hasattr(obj, 'player') and hasattr(obj.player, 'is_superuser') and - obj.player.is_superuser): + obj.player.is_superuser): string += "\n(... but this object is currently controlled by a SUPERUSER! " string += "All access checks are passed automatically.)" caller.msg(string) @@ -497,21 +496,21 @@ class CmdPerm(COMMAND_DEFAULT_CLASS): # we supplied an argument on the form obj = perm locktype = "edit" if playermode else "control" if not obj.access(caller, locktype): - caller.msg("You are not allowed to edit this %s's permissions." % - ("player" if playermode else "object")) + caller.msg("You are not allowed to edit this %s's permissions." + % ("player" if playermode else "object")) return - cstring = "" - tstring = "" + caller_result = [] + target_result = [] if 'del' in switches: # delete the given permission(s) from object. for perm in self.rhslist: obj.permissions.remove(perm) if obj.permissions.get(perm): - cstring += "\nPermissions %s could not be removed from %s." % (perm, obj.name) + caller_result.append("\nPermissions %s could not be removed from %s." % (perm, obj.name)) else: - cstring += "\nPermission %s removed from %s (if they existed)." % (perm, obj.name) - tstring += "\n%s revokes the permission(s) %s from you." % (caller.name, perm) + caller_result.append("\nPermission %s removed from %s (if they existed)." % (perm, obj.name)) + target_result.append("\n%s revokes the permission(s) %s from you." % (caller.name, perm)) else: # add a new permission permissions = obj.permissions.all() @@ -526,15 +525,16 @@ class CmdPerm(COMMAND_DEFAULT_CLASS): return if perm in permissions: - cstring += "\nPermission '%s' is already defined on %s." % (rhs, obj.name) + caller_result.append("\nPermission '%s' is already defined on %s." % (rhs, obj.name)) else: obj.permissions.add(perm) plystring = "the Player" if playermode else "the Object/Character" - cstring += "\nPermission '%s' given to %s (%s)." % (rhs, obj.name, plystring) - tstring += "\n%s gives you (%s, %s) the permission '%s'." % (caller.name, obj.name, plystring, rhs) - caller.msg(cstring.strip()) - if tstring: - obj.msg(tstring.strip()) + caller_result.append("\nPermission '%s' given to %s (%s)." % (rhs, obj.name, plystring)) + target_result.append("\n%s gives you (%s, %s) the permission '%s'." + % (caller.name, obj.name, plystring, rhs)) + caller.msg("".join(caller_result).strip()) + if target_result: + obj.msg("".join(target_result).strip()) class CmdWall(COMMAND_DEFAULT_CLASS): @@ -551,7 +551,7 @@ class CmdWall(COMMAND_DEFAULT_CLASS): help_category = "Admin" def func(self): - "Implements command" + """Implements command""" if not self.args: self.caller.msg("Usage: @wall ") return From 1937c314bbcf7c771c562f843694545868503c23 Mon Sep 17 00:00:00 2001 From: BlauFeuer Date: Tue, 21 Feb 2017 10:34:15 -0500 Subject: [PATCH 048/134] Markup and PEP 8 updates --- evennia/commands/default/batchprocess.py | 76 ++++++++++++------------ 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/evennia/commands/default/batchprocess.py b/evennia/commands/default/batchprocess.py index cefeba6baa..6fbcfc2ec8 100644 --- a/evennia/commands/default/batchprocess.py +++ b/evennia/commands/default/batchprocess.py @@ -29,15 +29,13 @@ from evennia.utils import logger, utils _RE_COMMENT = re.compile(r"^#.*?$", re.MULTILINE + re.DOTALL) _RE_CODE_START = re.compile(r"^# batchcode code:", re.MULTILINE) _COMMAND_DEFAULT_CLASS = utils.class_from_module(settings.COMMAND_DEFAULT_CLASS) -#from evennia.commands.default.muxcommand import _COMMAND_DEFAULT_CLASS # limit symbols for API inclusion __all__ = ("CmdBatchCommands", "CmdBatchCode") _HEADER_WIDTH = 70 -_UTF8_ERROR = \ -""" - {rDecode error in '%s'.{n +_UTF8_ERROR = """ + |rDecode error in '%s'.|n This file contains non-ascii character(s). This is common if you wrote some input in a language that has more letters and special @@ -83,9 +81,9 @@ print "leaving run ..." """ -#------------------------------------------------------------ +# ------------------------------------------------------------- # Helper functions -#------------------------------------------------------------ +# ------------------------------------------------------------- def format_header(caller, entry): """ @@ -100,7 +98,7 @@ def format_header(caller, entry): header = utils.crop(entry, width=width) ptr = caller.ndb.batch_stackptr + 1 stacklen = len(caller.ndb.batch_stack) - header = "{w%02i/%02i{G: %s{n" % (ptr, stacklen, header) + header = "|w%02i/%02i|G: %s|n" % (ptr, stacklen, header) # add extra space to the side for padding. header = "%s%s" % (header, " " * (width - len(header))) header = header.replace('\n', '\\n') @@ -114,7 +112,7 @@ def format_code(entry): """ code = "" for line in entry.split('\n'): - code += "\n{G>>>{n %s" % line + code += "\n|G>>>|n %s" % line return code.strip() @@ -164,9 +162,9 @@ def step_pointer(caller, step=1): stack = caller.ndb.batch_stack nstack = len(stack) if ptr + step <= 0: - caller.msg("{RBeginning of batch file.") + caller.msg("|RBeginning of batch file.") if ptr + step >= nstack: - caller.msg("{REnd of batch file.") + caller.msg("|REnd of batch file.") caller.ndb.batch_stackptr = max(0, min(nstack - 1, ptr + step)) @@ -186,10 +184,10 @@ def show_curr(caller, showall=False): string = format_header(caller, entry) codeall = entry.strip() - string += "{G(hh for help)" + string += "|G(hh for help)" if showall: for line in codeall.split('\n'): - string += "\n{G|{n %s" % line + string += "\n|G||n %s" % line caller.msg(string) @@ -217,9 +215,9 @@ def purge_processor(caller): caller.scripts.validate() # this will purge interactive mode -#------------------------------------------------------------ +# ------------------------------------------------------------- # main access commands -#------------------------------------------------------------ +# ------------------------------------------------------------- class CmdBatchCommands(_COMMAND_DEFAULT_CLASS): @@ -243,7 +241,7 @@ class CmdBatchCommands(_COMMAND_DEFAULT_CLASS): help_category = "Building" def func(self): - "Starts the processor." + """Starts the processor.""" caller = self.caller @@ -253,7 +251,7 @@ class CmdBatchCommands(_COMMAND_DEFAULT_CLASS): return python_path = self.args - #parse indata file + # parse indata file try: commands = BATCHCMD.parse_file(python_path) @@ -289,7 +287,8 @@ class CmdBatchCommands(_COMMAND_DEFAULT_CLASS): caller.msg("\nBatch-command processor - Interactive mode for %s ..." % python_path) show_curr(caller) else: - caller.msg("Running Batch-command processor - Automatic mode for %s (this might take some time) ..." % python_path) + caller.msg("Running Batch-command processor - Automatic mode for %s (this might take some time) ..." + % python_path) procpool = False if "PythonProcPool" in utils.server_services(): @@ -301,11 +300,11 @@ class CmdBatchCommands(_COMMAND_DEFAULT_CLASS): if procpool: # run in parallel process def callback(r): - caller.msg(" {GBatchfile '%s' applied." % python_path) + caller.msg(" |GBatchfile '%s' applied." % python_path) purge_processor(caller) def errback(e): - caller.msg(" {RError from processor: '%s'" % e) + caller.msg(" |RError from processor: '%s'" % e) purge_processor(caller) utils.run_async(_PROCPOOL_BATCHCMD_SOURCE, @@ -315,7 +314,7 @@ class CmdBatchCommands(_COMMAND_DEFAULT_CLASS): at_err=errback) else: # run in-process (might block) - for inum in range(len(commands)): + for _ in range(len(commands)): # loop through the batch file if not batch_cmd_exec(caller): return @@ -323,7 +322,7 @@ class CmdBatchCommands(_COMMAND_DEFAULT_CLASS): # clean out the safety cmdset and clean out all other # temporary attrs. string = " Batchfile '%s' applied." % python_path - caller.msg("{G%s" % string) + caller.msg("|G%s" % string) purge_processor(caller) @@ -352,7 +351,7 @@ class CmdBatchCode(_COMMAND_DEFAULT_CLASS): help_category = "Building" def func(self): - "Starts the processor." + """Starts the processor.""" caller = self.caller @@ -363,7 +362,7 @@ class CmdBatchCode(_COMMAND_DEFAULT_CLASS): python_path = self.args debug = 'debug' in self.switches - #parse indata file + # parse indata file try: codes = BATCHCODE.parse_file(python_path) except UnicodeDecodeError as err: @@ -410,11 +409,11 @@ class CmdBatchCode(_COMMAND_DEFAULT_CLASS): if procpool: # run in parallel process def callback(r): - caller.msg(" {GBatchfile '%s' applied." % python_path) + caller.msg(" |GBatchfile '%s' applied." % python_path) purge_processor(caller) def errback(e): - caller.msg(" {RError from processor: '%s'" % e) + caller.msg(" |RError from processor: '%s'" % e) purge_processor(caller) utils.run_async(_PROCPOOL_BATCHCODE_SOURCE, codes=codes, @@ -423,7 +422,7 @@ class CmdBatchCode(_COMMAND_DEFAULT_CLASS): at_err=errback) else: # un in-process (will block) - for inum in range(len(codes)): + for _ in range(len(codes)): # loop through the batch file if not batch_code_exec(caller): return @@ -431,14 +430,14 @@ class CmdBatchCode(_COMMAND_DEFAULT_CLASS): # clean out the safety cmdset and clean out all other # temporary attrs. string = " Batchfile '%s' applied." % python_path - caller.msg("{G%s" % string) + caller.msg("|G%s" % string) purge_processor(caller) -#------------------------------------------------------------ +# ------------------------------------------------------------- # State-commands for the interactive batch processor modes # (these are the same for both processors) -#------------------------------------------------------------ +# ------------------------------------------------------------- class CmdStateAbort(_COMMAND_DEFAULT_CLASS): """ @@ -453,7 +452,7 @@ class CmdStateAbort(_COMMAND_DEFAULT_CLASS): locks = "cmd:perm(batchcommands)" def func(self): - "Exit back to default." + """Exit back to default.""" purge_processor(self.caller) self.caller.msg("Exited processor and reset out active cmdset back to the default one.") @@ -472,6 +471,7 @@ class CmdStateLL(_COMMAND_DEFAULT_CLASS): def func(self): show_curr(self.caller, showall=True) + class CmdStatePP(_COMMAND_DEFAULT_CLASS): """ pp @@ -644,7 +644,7 @@ class CmdStateSS(_COMMAND_DEFAULT_CLASS): else: step = 1 - for istep in range(step): + for _ in range(step): if caller.ndb.batch_batchmode == "batch_code": batch_code_exec(caller) else: @@ -673,7 +673,7 @@ class CmdStateSL(_COMMAND_DEFAULT_CLASS): else: step = 1 - for istep in range(step): + for _ in range(step): if caller.ndb.batch_batchmode == "batch_code": batch_code_exec(caller) else: @@ -699,7 +699,7 @@ class CmdStateCC(_COMMAND_DEFAULT_CLASS): ptr = caller.ndb.batch_stackptr step = nstack - ptr - for istep in range(step): + for _ in range(step): if caller.ndb.batch_batchmode == "batch_code": batch_code_exec(caller) else: @@ -775,7 +775,7 @@ class CmdStateQQ(_COMMAND_DEFAULT_CLASS): class CmdStateHH(_COMMAND_DEFAULT_CLASS): - "Help command" + """Help command""" key = "hh" help_category = "BatchProcess" @@ -810,12 +810,12 @@ class CmdStateHH(_COMMAND_DEFAULT_CLASS): self.caller.msg(string) -#------------------------------------------------------------ +# ------------------------------------------------------------- # # Defining the cmdsets for the interactive batchprocessor # mode (same for both processors) # -#------------------------------------------------------------ +# ------------------------------------------------------------- class BatchSafeCmdSet(CmdSet): """ @@ -827,7 +827,7 @@ class BatchSafeCmdSet(CmdSet): priority = 150 # override other cmdsets. def at_cmdset_creation(self): - "Init the cmdset" + """Init the cmdset""" self.add(CmdStateAbort()) @@ -839,7 +839,7 @@ class BatchInteractiveCmdSet(CmdSet): priority = 104 def at_cmdset_creation(self): - "init the cmdset" + """init the cmdset""" self.add(CmdStateAbort()) self.add(CmdStateLL()) self.add(CmdStatePP()) From bd7388c03d274af7cbb326c998b9101015de4ce3 Mon Sep 17 00:00:00 2001 From: BlauFeuer Date: Tue, 21 Feb 2017 17:42:50 -0500 Subject: [PATCH 049/134] Whitespace and LGTM alert edits --- evennia/players/players.py | 55 +++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/evennia/players/players.py b/evennia/players/players.py index 92d5dbcf24..ce726a8c71 100644 --- a/evennia/players/players.py +++ b/evennia/players/players.py @@ -41,6 +41,7 @@ _MAX_NR_CHARACTERS = settings.MAX_NR_CHARACTERS _CMDSET_PLAYER = settings.CMDSET_PLAYER _CONNECT_CHANNEL = None + class PlayerSessionHandler(object): """ Manages the session(s) attached to a player. @@ -99,7 +100,6 @@ class PlayerSessionHandler(object): return len(self.get()) - class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)): """ This is the base Typeclass for all Players. Players represent @@ -188,7 +188,6 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)): def sessions(self): return PlayerSessionHandler(self) - # session-related methods def disconnect_session_from_player(self, session): @@ -337,8 +336,7 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)): by this Player. """ - return list(set(session.puppet for session in self.sessions.all() - if session.puppet)) + return list(set(session.puppet for session in self.sessions.all() if session.puppet)) def __get_single_puppet(self): """ @@ -383,7 +381,7 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)): self.nicks.clear() self.aliases.clear() super(DefaultPlayer, self).delete(*args, **kwargs) - ## methods inherited from database model + # methods inherited from database model def msg(self, text=None, from_obj=None, session=None, options=None, **kwargs): """ @@ -447,8 +445,7 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)): """ raw_string = to_unicode(raw_string) - raw_string = self.nicks.nickreplace(raw_string, - categories=("inputline", "channel"), include_player=False) + raw_string = self.nicks.nickreplace(raw_string, categories=("inputline", "channel"), include_player=False) if not session and _MULTISESSION_MODE in (0, 1): # for these modes we use the first/only session sessions = self.sessions.get() @@ -557,7 +554,7 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)): return time.time() - float(min(conn)) return None - ## player hooks + # player hooks def basetype_setup(self): """ @@ -600,7 +597,6 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)): """ pass - # Note that the hooks below also exist in the character object's # typeclass. You can often ignore these and rely on the character # ones instead, unless you are implementing a multi-character game @@ -847,34 +843,36 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)): is_su = self.is_superuser # text shown when looking in the ooc area - string = "Account |g%s|n (you are Out-of-Character)" % (self.key) + result = ["Account |g%s|n (you are Out-of-Character)" % self.key] nsess = len(sessions) - string += nsess == 1 and "\n\n|wConnected session:|n" or "\n\n|wConnected sessions (%i):|n" % nsess + result.append(nsess == 1 and "\n\n|wConnected session:|n" or "\n\n|wConnected sessions (%i):|n" % nsess) for isess, sess in enumerate(sessions): csessid = sess.sessid - addr = "%s (%s)" % (sess.protocol_key, isinstance(sess.address, tuple) and str(sess.address[0]) or str(sess.address)) - string += "\n %s %s" % (session.sessid == csessid and "|w* %s|n" % (isess + 1) or " %s" % (isess + 1), addr) - string += "\n\n |whelp|n - more commands" - string += "\n |wooc |n - talk on public channel" + addr = "%s (%s)" % (sess.protocol_key, isinstance(sess.address, tuple) + and str(sess.address[0]) or str(sess.address)) + result.append("\n %s %s" % (session.sessid == csessid and "|w* %s|n" % (isess + 1) + or " %s" % (isess + 1), addr)) + result.append("\n\n |whelp|n - more commands") + result.append("\n |wooc |n - talk on public channel") charmax = _MAX_NR_CHARACTERS if _MULTISESSION_MODE > 1 else 1 if is_su or len(characters) < charmax: if not characters: - string += "\n\n You don't have any characters yet. See |whelp @charcreate|n for creating one." + result.append("\n\n You don't have any characters yet. See |whelp @charcreate|n for creating one.") else: - string += "\n |w@charcreate [=description]|n - create new character" - string += "\n |w@chardelete |n - delete a character (cannot be undone!)" + result.append("\n |w@charcreate [=description]|n - create new character") + result.append("\n |w@chardelete |n - delete a character (cannot be undone!)") if characters: string_s_ending = len(characters) > 1 and "s" or "" - string += "\n |w@ic |n - enter the game (|w@ooc|n to get back here)" + result.append("\n |w@ic |n - enter the game (|w@ooc|n to get back here)") if is_su: - string += "\n\nAvailable character%s (%i/unlimited):" % (string_s_ending, len(characters)) + result.append("\n\nAvailable character%s (%i/unlimited):" % (string_s_ending, len(characters))) else: - string += "\n\nAvailable character%s%s:" % (string_s_ending, - charmax > 1 and " (%i/%i)" % (len(characters), charmax) or "") + result.append("\n\nAvailable character%s%s:" + % (string_s_ending, charmax > 1 and " (%i/%i)" % (len(characters), charmax) or "")) for char in characters: csessions = char.sessions.all() @@ -883,14 +881,16 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)): # character is already puppeted sid = sess in sessions and sessions.index(sess) + 1 if sess and sid: - string += "\n - |G%s|n [%s] (played by you in session %i)" % (char.key, ", ".join(char.permissions.all()), sid) + result.append("\n - |G%s|n [%s] (played by you in session %i)" + % (char.key, ", ".join(char.permissions.all()), sid)) else: - string += "\n - |R%s|n [%s] (played by someone else)" % (char.key, ", ".join(char.permissions.all())) + result.append("\n - |R%s|n [%s] (played by someone else)" + % (char.key, ", ".join(char.permissions.all()))) else: # character is "free to puppet" - string += "\n - %s [%s]" % (char.key, ", ".join(char.permissions.all())) - string = ("-" * 68) + "\n" + string + "\n" + ("-" * 68) - return string + result.append("\n - %s [%s]" % (char.key, ", ".join(char.permissions.all()))) + look_string = ("-" * 68) + "\n" + "".join(result) + "\n" + ("-" * 68) + return look_string class DefaultGuest(DefaultPlayer): @@ -910,7 +910,6 @@ class DefaultGuest(DefaultPlayer): self._send_to_connect_channel("|G%s connected|n" % self.key) self.puppet_object(session, self.db._last_puppet) - def at_server_shutdown(self): """ We repeat the functionality of `at_disconnect()` here just to From 541d54ecc17d42bea94ed9cb1245866f62bd3c08 Mon Sep 17 00:00:00 2001 From: BlauFeuer Date: Tue, 21 Feb 2017 17:44:25 -0500 Subject: [PATCH 050/134] prettytable to EvTable and whitespace edits --- evennia/commands/default/player.py | 74 ++++++++++++++---------------- 1 file changed, 34 insertions(+), 40 deletions(-) diff --git a/evennia/commands/default/player.py b/evennia/commands/default/player.py index 8892e8d5a6..55919970bb 100644 --- a/evennia/commands/default/player.py +++ b/evennia/commands/default/player.py @@ -23,7 +23,7 @@ from builtins import range import time from django.conf import settings from evennia.server.sessionhandler import SESSIONS -from evennia.utils import utils, create, search, prettytable, evtable +from evennia.utils import utils, create, search, evtable COMMAND_DEFAULT_CLASS = utils.class_from_module(settings.COMMAND_DEFAULT_CLASS) @@ -356,20 +356,18 @@ class CmdSessions(COMMAND_DEFAULT_CLASS): """Implement function""" player = self.player sessions = player.sessions.all() - - table = prettytable.PrettyTable(["|wsessid", - "|wprotocol", - "|whost", - "|wpuppet/character", - "|wlocation"]) + table = evtable.EvTable("|wsessid", + "|wprotocol", + "|whost", + "|wpuppet/character", + "|wlocation") for sess in sorted(sessions, key=lambda x: x.sessid): char = player.get_puppet(sess) - table.add_row([str(sess.sessid), str(sess.protocol_key), - type(sess.address) == tuple and sess.address[0] or sess.address, - char and str(char) or "None", - char and str(char.location) or "N/A"]) - string = "|wYour current session(s):|n\n%s" % table - self.msg(string) + table.add_row(str(sess.sessid), str(sess.protocol_key), + type(sess.address) == tuple and sess.address[0] or sess.address, + char and str(char) or "None", + char and str(char.location) or "N/A") + self.msg("|wYour current session(s):|n\n%s" % table) class CmdWho(COMMAND_DEFAULT_CLASS): @@ -409,14 +407,14 @@ class CmdWho(COMMAND_DEFAULT_CLASS): nplayers = (SESSIONS.player_count()) if show_session_data: # privileged info - table = prettytable.PrettyTable(["|wPlayer Name", - "|wOn for", - "|wIdle", - "|wPuppeting", - "|wRoom", - "|wCmds", - "|wProtocol", - "|wHost"]) + table = evtable.EvTable("|wPlayer Name", + "|wOn for", + "|wIdle", + "|wPuppeting", + "|wRoom", + "|wCmds", + "|wProtocol", + "|wHost") for session in session_list: if not session.logged_in: continue @@ -425,31 +423,29 @@ class CmdWho(COMMAND_DEFAULT_CLASS): player = session.get_player() puppet = session.get_puppet() location = puppet.location.key if puppet and puppet.location else "None" - table.add_row([utils.crop(player.name, width=25), - utils.time_format(delta_conn, 0), - utils.time_format(delta_cmd, 1), - utils.crop(puppet.key if puppet else "None", width=25), - utils.crop(location, width=25), - session.cmd_total, - session.protocol_key, - isinstance(session.address, tuple) and session.address[0] or session.address]) + table.add_row(utils.crop(player.name, width=25), + utils.time_format(delta_conn, 0), + utils.time_format(delta_cmd, 1), + utils.crop(puppet.key if puppet else "None", width=25), + utils.crop(location, width=25), + session.cmd_total, + session.protocol_key, + isinstance(session.address, tuple) and session.address[0] or session.address) else: # unprivileged - table = prettytable.PrettyTable(["|wPlayer name", "|wOn for", "|wIdle"]) + table = evtable.EvTable("|wPlayer name", "|wOn for", "|wIdle") for session in session_list: if not session.logged_in: continue delta_cmd = time.time() - session.cmd_last_visible delta_conn = time.time() - session.conn_time player = session.get_player() - table.add_row([utils.crop(player.key, width=25), - utils.time_format(delta_conn, 0), - utils.time_format(delta_cmd, 1)]) - + table.add_row(utils.crop(player.key, width=25), + utils.time_format(delta_conn, 0), + utils.time_format(delta_cmd, 1)) is_one = nplayers == 1 - string = "|wPlayers:|n\n%s\n%s unique account%s logged in." \ - % (table, "One" if is_one else nplayers, "" if is_one else "s") - self.msg(string) + self.msg("|wPlayers:|n\n%s\n%s unique account%s logged in." + % (table, "One" if is_one else nplayers, "" if is_one else "s")) class CmdOption(COMMAND_DEFAULT_CLASS): @@ -524,7 +520,6 @@ class CmdOption(COMMAND_DEFAULT_CLASS): changed = "|y*|n" if key in saved_options and flags[key] != saved_options[key] else "" row.append("%s%s" % (saved, changed)) table.add_row(*row) - self.msg("|wClient settings (%s):|n\n%s|n" % (self.session.protocol_key, table)) return @@ -599,10 +594,9 @@ class CmdOption(COMMAND_DEFAULT_CLASS): for key in optiondict: self.player.attributes.get("_saved_protocol_flags", {}).pop(key, None) self.msg("|gCleared saved %s." % key) - - self.session.update_flags(**optiondict) + class CmdPassword(COMMAND_DEFAULT_CLASS): """ change your password From 4424734867be555688d4b69d3d0eb2b0dc6fa8ad Mon Sep 17 00:00:00 2001 From: Griatch Date: Mon, 27 Feb 2017 22:26:47 +0100 Subject: [PATCH 051/134] Reverting change to unittest, cannot replicate issue #1234. --- evennia/contrib/tests.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/evennia/contrib/tests.py b/evennia/contrib/tests.py index f1a6551fca..42088c2ce3 100644 --- a/evennia/contrib/tests.py +++ b/evennia/contrib/tests.py @@ -180,13 +180,13 @@ class TestExtendedRoom(CommandTest): def setUp(self): super(TestExtendedRoom, self).setUp() - self.room1.ndb.last_timeslot = "evening" - self.room1.ndb.last_season = "spring" + self.room1.ndb.last_timeslot = "afternoon" + self.room1.ndb.last_season = "winter" self.room1.db.details = {'testdetail': self.DETAIL_DESC} self.room1.db.spring_desc = self.SPRING_DESC self.room1.db.desc = self.OLD_DESC - # mock gametime to return 7th month, 10 in morning - gametime.gametime = Mock(return_value=2975000766) # spring evening + # mock gametime to return April 9, 2064, at 21:06 (spring evening) + gametime.gametime = Mock(return_value=2975000766) def test_return_appearance(self): # get the appearance of a non-extended room for contrast purposes From 657b3585f895bd4a7181a273903c441202eab482 Mon Sep 17 00:00:00 2001 From: Griatch Date: Mon, 27 Feb 2017 22:39:14 +0100 Subject: [PATCH 052/134] Remove the deprecated .characters property from Player model, it was a leftover from another time (there is no such hard-coded relation; use sessions instead). Closes #1233." --- evennia/players/models.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/evennia/players/models.py b/evennia/players/models.py index bfa1824bcb..d33985de35 100644 --- a/evennia/players/models.py +++ b/evennia/players/models.py @@ -104,17 +104,6 @@ class PlayerDB(TypedObject, AbstractUser): class Meta(object): verbose_name = 'Player' - # alias to the objs property - def __characters_get(self): - return self.objs - - def __characters_set(self, value): - self.objs = value - - def __characters_del(self): - raise Exception("Cannot delete name") - characters = property(__characters_get, __characters_set, __characters_del) - # cmdset_storage property # This seems very sensitive to caching, so leaving it be for now /Griatch #@property From 1b24bacc12c3eaafb1d5ca2228047f5ecab00d33 Mon Sep 17 00:00:00 2001 From: BlauFeuer Date: Thu, 23 Feb 2017 15:56:27 -0500 Subject: [PATCH 053/134] Suggested fix for #1231 + whitespace/docstring --- evennia/server/portal/portalsessionhandler.py | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/evennia/server/portal/portalsessionhandler.py b/evennia/server/portal/portalsessionhandler.py index 9d55959239..6136283cc7 100644 --- a/evennia/server/portal/portalsessionhandler.py +++ b/evennia/server/portal/portalsessionhandler.py @@ -28,9 +28,11 @@ _CONNECTION_QUEUE = deque() DUMMYSESSION = namedtuple('DummySession', ['sessid'])(0) -#------------------------------------------------------------ +# ------------------------------------------------------------- # Portal-SessionHandler class -#------------------------------------------------------------ +# ------------------------------------------------------------- + + class PortalSessionHandler(SessionHandler): """ This object holds the sessions connected to the portal at any time. @@ -95,7 +97,7 @@ class PortalSessionHandler(SessionHandler): if len(_CONNECTION_QUEUE) > 1: session.data_out(text=[["%s DoS protection is active. You are queued to connect in %g seconds ..." % ( settings.SERVERNAME, - len(_CONNECTION_QUEUE)*_MIN_TIME_BETWEEN_CONNECTS)],{}]) + len(_CONNECTION_QUEUE)*_MIN_TIME_BETWEEN_CONNECTS)], {}]) now = time.time() if (now - self.connection_last < _MIN_TIME_BETWEEN_CONNECTS) or not self.portal.amp_protocol: if not session or not self.connection_task: @@ -176,8 +178,7 @@ class PortalSessionHandler(SessionHandler): del self[session.sessid] # Tell the Server to disconnect its version of the Session as well. - self.portal.amp_protocol.send_AdminPortal2Server(session, - operation=PDISCONN) + self.portal.amp_protocol.send_AdminPortal2Server(session, operation=PDISCONN) def disconnect_all(self): """ @@ -194,7 +195,7 @@ class PortalSessionHandler(SessionHandler): # inform Server; wait until finished sending before we continue # removing all the sessions. self.portal.amp_protocol.send_AdminPortal2Server(DUMMYSESSION, - operation=PDISCONNALL).addCallback(_callback, self) + operation=PDISCONNALL).addCallback(_callback, self) def server_connect(self, protocol_path="", config=dict()): """ @@ -233,8 +234,8 @@ class PortalSessionHandler(SessionHandler): Called by server to force a disconnect by sessid. Args: - sessid (int): Session id to disconnect. - reason (str, optional): Motivation for disconect. + session (portalsession): Session to disconnect. + reason (str, optional): Motivation for disconnect. """ if session: @@ -335,7 +336,7 @@ class PortalSessionHandler(SessionHandler): """ for session in self.values(): - self.data_out(session, text=[[message],{}]) + self.data_out(session, text=[[message], {}]) def data_in(self, session, **kwargs): """ @@ -352,8 +353,8 @@ class PortalSessionHandler(SessionHandler): Data is serialized before passed on. """ - #from evennia.server.profiling.timetrace import timetrace - #text = timetrace(text, "portalsessionhandler.data_in") + # from evennia.server.profiling.timetrace import timetrace # DEBUG + # text = timetrace(text, "portalsessionhandler.data_in") # DEBUG try: text = kwargs['text'] if (_MAX_CHAR_LIMIT > 0) and len(text) > _MAX_CHAR_LIMIT: @@ -367,14 +368,14 @@ class PortalSessionHandler(SessionHandler): now = time.time() if self.command_counter > _MAX_COMMAND_RATE > 0: # data throttle (anti DoS measure) - dT = now - self.command_counter_reset + delta_time = now - self.command_counter_reset self.command_counter = 0 self.command_counter_reset = now - self.command_overflow = dT < 1.0 + self.command_overflow = delta_time < 1.0 if self.command_overflow: reactor.callLater(1.0, self.data_in, None) if self.command_overflow: - self.data_out(session, text=[[_ERROR_COMMAND_OVERFLOW],{}]) + self.data_out(session, text=[[_ERROR_COMMAND_OVERFLOW], {}]) return # scrub data kwargs = self.clean_senddata(session, kwargs) @@ -385,7 +386,7 @@ class PortalSessionHandler(SessionHandler): self.portal.amp_protocol.send_MsgPortal2Server(session, **kwargs) else: - # called by the callLater callback + # called by the callLater callback if self.command_overflow: self.command_overflow = False reactor.callLater(1.0, self.data_in, None) @@ -405,8 +406,8 @@ class PortalSessionHandler(SessionHandler): method exixts, it sends the data to a method send_default. """ - #from evennia.server.profiling.timetrace import timetrace - #text = timetrace(text, "portalsessionhandler.data_out") + # from evennia.server.profiling.timetrace import timetrace # DEBUG + # text = timetrace(text, "portalsessionhandler.data_out") # DEBUG # distribute outgoing data to the correct session methods. if session: From f58ac47b63b8f76d78d94adc31899579c314d0da Mon Sep 17 00:00:00 2001 From: CloudKeeper1 Date: Sun, 26 Feb 2017 19:04:06 +1100 Subject: [PATCH 054/134] Adjusted suggested use so that it works. Previously gave an incomplete path leading to an importerror. --- evennia/contrib/tutorial_examples/bodyfunctions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evennia/contrib/tutorial_examples/bodyfunctions.py b/evennia/contrib/tutorial_examples/bodyfunctions.py index 70bdf6de6f..ea478cbb40 100644 --- a/evennia/contrib/tutorial_examples/bodyfunctions.py +++ b/evennia/contrib/tutorial_examples/bodyfunctions.py @@ -3,7 +3,7 @@ Example script for testing. This adds a simple timer that has your character make observations and notices at irregular intervals. To test, use - @script me = examples.bodyfunctions.BodyFunctions + @script me = tutorial_examples.bodyfunctions.BodyFunctions The script will only send messages to the object it is stored on, so make sure to put it on yourself or you won't see any messages! From f0ef1a648ece07530044588452d21520b9d4aafd Mon Sep 17 00:00:00 2001 From: Griatch Date: Mon, 27 Feb 2017 23:59:47 +0100 Subject: [PATCH 055/134] Still cannot replicate #1234; test to force-set UTC time zone to see if that helps. --- evennia/contrib/tests.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/evennia/contrib/tests.py b/evennia/contrib/tests.py index 42088c2ce3..53d1aa8f71 100644 --- a/evennia/contrib/tests.py +++ b/evennia/contrib/tests.py @@ -168,6 +168,7 @@ class TestRPSystem(EvenniaTest): # Testing of ExtendedRoom contrib +from django.conf import settings from evennia.contrib import extended_room from evennia import gametime from evennia.objects.objects import DefaultRoom @@ -177,6 +178,7 @@ class TestExtendedRoom(CommandTest): DETAIL_DESC = "A test detail." SPRING_DESC = "A spring description." OLD_DESC = "Old description." + settings.TIME_ZONE = "UTC" def setUp(self): super(TestExtendedRoom, self).setUp() From f48e8e1fabeddd682167feeaaa83ede27f072e54 Mon Sep 17 00:00:00 2001 From: Griatch Date: Tue, 28 Feb 2017 00:07:05 +0100 Subject: [PATCH 056/134] Make note of the timezone dependence of datetime.datetime.fromtimestamp. --- evennia/contrib/extended_room.py | 1 + 1 file changed, 1 insertion(+) diff --git a/evennia/contrib/extended_room.py b/evennia/contrib/extended_room.py index e5a8fdd934..6bbd79548b 100644 --- a/evennia/contrib/extended_room.py +++ b/evennia/contrib/extended_room.py @@ -133,6 +133,7 @@ class ExtendedRoom(DefaultRoom): # get the current time as parts of year and parts of day. # we assume a standard calendar here and use 24h format. timestamp = gametime.gametime(absolute=True) + # note that fromtimestamp includes the effects of server time zone! datestamp = datetime.datetime.fromtimestamp(timestamp) season = float(datestamp.month) / MONTHS_PER_YEAR timeslot = float(datestamp.hour) / HOURS_PER_DAY From e556c5e5d3d0ea9d5d856c08269c650859667c0b Mon Sep 17 00:00:00 2001 From: Vincent Le Goff Date: Mon, 27 Feb 2017 16:51:35 -0800 Subject: [PATCH 057/134] Fix #1234: use a mock datetime class to force UTC --- evennia/contrib/tests.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/evennia/contrib/tests.py b/evennia/contrib/tests.py index 53d1aa8f71..f831489fb9 100644 --- a/evennia/contrib/tests.py +++ b/evennia/contrib/tests.py @@ -4,6 +4,7 @@ Testing suite for contrib folder """ +import datetime from evennia.commands.default.tests import CommandTest from evennia.utils.test_resources import EvenniaTest from mock import Mock, patch @@ -173,6 +174,16 @@ from evennia.contrib import extended_room from evennia import gametime from evennia.objects.objects import DefaultRoom +class ForceUTCDatetime(datetime.datetime): + + """Force UTC datetime.""" + + @classmethod + def fromtimestamp(cls, timestamp): + """Force fromtimestamp to run with naive datetimes.""" + return datetime.datetime.utcfromtimestamp(timestamp) + +@patch('evennia.contrib.extended_room.datetime.datetime', ForceUTCDatetime) class TestExtendedRoom(CommandTest): room_typeclass = extended_room.ExtendedRoom DETAIL_DESC = "A test detail." From c15d5a26c4106ed48c42524f1aed1789214494d0 Mon Sep 17 00:00:00 2001 From: Griatch Date: Tue, 28 Feb 2017 08:08:33 +0100 Subject: [PATCH 058/134] Add another errback handler for error in #1207. --- evennia/server/webserver.py | 4 +++- evennia/utils/evtable.py | 10 ++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/evennia/server/webserver.py b/evennia/server/webserver.py index beda2d5ecf..e974226f7f 100644 --- a/evennia/server/webserver.py +++ b/evennia/server/webserver.py @@ -23,6 +23,8 @@ from twisted.web.wsgi import WSGIResource from django.conf import settings from django.core.handlers.wsgi import WSGIHandler +from evennia.utils import logger + _UPSTREAM_IPS = settings.UPSTREAM_IPS _DEBUG = settings.DEBUG @@ -70,7 +72,7 @@ class EvenniaReverseProxyResource(ReverseProxyResource): resource (EvenniaReverseProxyResource): A proxy resource. """ - request.notifyFinish().addErrback(lambda f: f.cancel()) + request.notifyFinish().addErrback(lambda f: logger.log_trace("%s\nCaught errback in webserver.py:75." % f)) return EvenniaReverseProxyResource( self.host, self.port, self.path + '/' + urlquote(path, safe=""), self.reactor) diff --git a/evennia/utils/evtable.py b/evennia/utils/evtable.py index 748438beb2..3606ad729a 100644 --- a/evennia/utils/evtable.py +++ b/evennia/utils/evtable.py @@ -1241,6 +1241,9 @@ class EvTable(object): # actual table. This allows us to add columns/rows # and re-balance over and over without issue. self.worktable = deepcopy(self.table) + + self._borders() + return options = copy(self.options) # balance number of rows to make a rectangular table @@ -1572,3 +1575,10 @@ def _test(): table.reformat_column(3, width=30, align='r') print(unicode(table)) return table + +def _test2(): + table = EvTable("|yHeading1|n", "|B|[GHeading2|n", "Heading3") + for i in range(100): + table.add_row("This is col 0, row %i" % i, "|gThis is col 1, row |w%i|n|g.|n" % i, "This is col 2, row %i" % i) + return table + From 5aba9cf253457673dedd72cc67d02bfbf1d6388a Mon Sep 17 00:00:00 2001 From: Griatch Date: Tue, 28 Feb 2017 13:45:12 +0100 Subject: [PATCH 059/134] Remove accidentally left-in evtable debugs that messed up unittests. --- evennia/utils/evtable.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/evennia/utils/evtable.py b/evennia/utils/evtable.py index 3606ad729a..6587776de4 100644 --- a/evennia/utils/evtable.py +++ b/evennia/utils/evtable.py @@ -1241,9 +1241,8 @@ class EvTable(object): # actual table. This allows us to add columns/rows # and re-balance over and over without issue. self.worktable = deepcopy(self.table) - - self._borders() - return +# self._borders() +# return options = copy(self.options) # balance number of rows to make a rectangular table From 7c9aff23c8bb64f926335eac4cc671986e015703 Mon Sep 17 00:00:00 2001 From: Griatch Date: Tue, 28 Feb 2017 15:30:22 +0100 Subject: [PATCH 060/134] Add ability to send a specific command instance to the cmdhandler, bypassing cmdset lookup. Add the cmdobj and cmdobj_key keywords. --- evennia/commands/cmdhandler.py | 167 +++++++++++++++++++-------------- 1 file changed, 97 insertions(+), 70 deletions(-) diff --git a/evennia/commands/cmdhandler.py b/evennia/commands/cmdhandler.py index 2719e3602b..e8bb898cb3 100644 --- a/evennia/commands/cmdhandler.py +++ b/evennia/commands/cmdhandler.py @@ -391,7 +391,8 @@ def get_and_merge_cmdsets(caller, session, player, obj, callertype, raw_string): @inlineCallbacks -def cmdhandler(called_by, raw_string, _testing=False, callertype="session", session=None, **kwargs): +def cmdhandler(called_by, raw_string, _testing=False, callertype="session", session=None, + cmdobj=None, cmdobj_key=None, **kwargs): """ This is the main mechanism that handles any string sent to the engine. @@ -413,6 +414,15 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess precendence for same-name and same-prio commands. session (Session, optional): Relevant if callertype is "player" - the session will help retrieve the correct cmdsets from puppeted objects. + cmdobj (Command, optional): If given a command instance, this will be executed using + `called_by` as the caller, `raw_string` representing its arguments and (optionally) + `cmdobj_key` as its input command name. No cmdset lookup will be performed but + all other options apply as normal. This allows for running a specific Command + within the command system mechanism. + cmdobj_key (string, optional): Used together with `cmdobj` keyword to specify + which cmdname should be assigned when calling the specified Command instance. This + is made available as `self.cmdstring` when the Command runs. + If not given, the command will be assumed to be called as `cmdobj.key`. Kwargs: kwargs (any): other keyword arguments will be assigned as named variables on the @@ -428,17 +438,20 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess """ @inlineCallbacks - def _run_command(cmd, cmdname, args, raw_string): + def _run_command(cmd, cmdname, args, raw_string, cmdset, session, player): """ Helper function: This initializes and runs the Command instance once the parser has identified it as either a normal command or one of the system commands. Args: - cmd (Command): Command object - cmdname (str): Name of command - args (str): Extra text entered after the identified command - raw_string (str): Full input string, only used for debugging. + cmd (Command): Command object. + cmdname (str): Name of command. + args (str): Extra text entered after the identified command. + raw_string (str): Full input string. + cmdset (CmdSet): Command sert the command belongs to (if any).. + session (Session): Session of caller (if any). + player (Player): Player of caller (if any). Returns: deferred (Deferred): this will fire with the return of the @@ -457,7 +470,7 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess cmd.cmdset = cmdset cmd.session = session cmd.player = player - cmd.raw_string = unformatted_raw_string + cmd.raw_string = raw_string #cmd.obj # set via on-object cmdset handler for each command, # since this may be different for every command when # merging multuple cmdsets @@ -478,7 +491,7 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess _COMMAND_NESTING[called_by] += 1 if _COMMAND_NESTING[called_by] > _COMMAND_RECURSION_LIMIT: err = _ERROR_RECURSION_LIMIT.format(recursion_limit=_COMMAND_RECURSION_LIMIT, - raw_string=unformatted_raw_string, + raw_string=raw_string, cmdclass=cmd.__class__) raise RuntimeError(err) @@ -539,76 +552,89 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess try: # catch bugs in cmdhandler itself try: # catch special-type commands + if cmdobj: + # the command object is already given - cmdset = yield get_and_merge_cmdsets(caller, session, player, obj, - callertype, raw_string) - if not cmdset: - # this is bad and shouldn't happen. - raise NoCmdSets - unformatted_raw_string = raw_string - raw_string = raw_string.strip() - if not raw_string: - # Empty input. Test for system command instead. - syscmd = yield cmdset.get(CMD_NOINPUT) - sysarg = "" - raise ExecSystemCommand(syscmd, sysarg) - # Parse the input string and match to available cmdset. - # This also checks for permissions, so all commands in match - # are commands the caller is allowed to call. - matches = yield _COMMAND_PARSER(raw_string, cmdset, caller) + cmd = cmdobj() if callable(cmdobj) else cmdobj + cmdname = cmdobj_key if cmdobj_key else cmd.key + args = raw_string + unformatted_raw_string = "%s%s" % (cmdname, args) + cmdset = None + session = session + player = player - # Deal with matches + else: + # no explicit cmdobject given, figure it out - if len(matches) > 1: - # We have a multiple-match - syscmd = yield cmdset.get(CMD_MULTIMATCH) - sysarg = _("There were multiple matches.") - if syscmd: - # use custom CMD_MULTIMATCH - syscmd.matches = matches - else: - # fall back to default error handling - sysarg = yield _SEARCH_AT_RESULT([match[2] for match in matches], caller, query=matches[0][0]) - raise ExecSystemCommand(syscmd, sysarg) + cmdset = yield get_and_merge_cmdsets(caller, session, player, obj, + callertype, raw_string) + if not cmdset: + # this is bad and shouldn't happen. + raise NoCmdSets + unformatted_raw_string = raw_string + raw_string = raw_string.strip() + if not raw_string: + # Empty input. Test for system command instead. + syscmd = yield cmdset.get(CMD_NOINPUT) + sysarg = "" + raise ExecSystemCommand(syscmd, sysarg) + # Parse the input string and match to available cmdset. + # This also checks for permissions, so all commands in match + # are commands the caller is allowed to call. + matches = yield _COMMAND_PARSER(raw_string, cmdset, caller) - cmdname, args, cmd = "", "", None - if len(matches) == 1: - # We have a unique command match. But it may still be invalid. - match = matches[0] - cmdname, args, cmd = match[0], match[1], match[2] + # Deal with matches - if not matches: - # No commands match our entered command - syscmd = yield cmdset.get(CMD_NOMATCH) - if syscmd: - # use custom CMD_NOMATCH command - sysarg = raw_string - else: - # fallback to default error text - sysarg = _("Command '%s' is not available.") % raw_string - suggestions = string_suggestions(raw_string, - cmdset.get_all_cmd_keys_and_aliases(caller), - cutoff=0.7, maxnum=3) - if suggestions: - sysarg += _(" Maybe you meant %s?") % utils.list_to_string(suggestions, _('or'), addquote=True) + if len(matches) > 1: + # We have a multiple-match + syscmd = yield cmdset.get(CMD_MULTIMATCH) + sysarg = _("There were multiple matches.") + if syscmd: + # use custom CMD_MULTIMATCH + syscmd.matches = matches else: - sysarg += _(" Type \"help\" for help.") - raise ExecSystemCommand(syscmd, sysarg) + # fall back to default error handling + sysarg = yield _SEARCH_AT_RESULT([match[2] for match in matches], caller, query=matches[0][0]) + raise ExecSystemCommand(syscmd, sysarg) - # Check if this is a Channel-cmd match. - if hasattr(cmd, 'is_channel') and cmd.is_channel: - # even if a user-defined syscmd is not defined, the - # found cmd is already a system command in its own right. - syscmd = yield cmdset.get(CMD_CHANNEL) - if syscmd: - # replace system command with custom version - cmd = syscmd - cmd.session = session - sysarg = "%s:%s" % (cmdname, args) - raise ExecSystemCommand(cmd, sysarg) + cmdname, args, cmd = "", "", None + if len(matches) == 1: + # We have a unique command match. But it may still be invalid. + match = matches[0] + cmdname, args, cmd = match[0], match[1], match[2] + + if not matches: + # No commands match our entered command + syscmd = yield cmdset.get(CMD_NOMATCH) + if syscmd: + # use custom CMD_NOMATCH command + sysarg = raw_string + else: + # fallback to default error text + sysarg = _("Command '%s' is not available.") % raw_string + suggestions = string_suggestions(raw_string, + cmdset.get_all_cmd_keys_and_aliases(caller), + cutoff=0.7, maxnum=3) + if suggestions: + sysarg += _(" Maybe you meant %s?") % utils.list_to_string(suggestions, _('or'), addquote=True) + else: + sysarg += _(" Type \"help\" for help.") + raise ExecSystemCommand(syscmd, sysarg) + + # Check if this is a Channel-cmd match. + if hasattr(cmd, 'is_channel') and cmd.is_channel: + # even if a user-defined syscmd is not defined, the + # found cmd is already a system command in its own right. + syscmd = yield cmdset.get(CMD_CHANNEL) + if syscmd: + # replace system command with custom version + cmd = syscmd + cmd.session = session + sysarg = "%s:%s" % (cmdname, args) + raise ExecSystemCommand(cmd, sysarg) # A normal command. - ret = yield _run_command(cmd, cmdname, args, raw_string) + ret = yield _run_command(cmd, cmdname, args, unformatted_raw_string, cmdset, session, player) returnValue(ret) except ErrorReported as exc: @@ -623,7 +649,8 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess sysarg = exc.sysarg if syscmd: - ret = yield _run_command(syscmd, syscmd.key, sysarg, raw_string) + ret = yield _run_command(syscmd, syscmd.key, sysarg, + unformatted_raw_string, cmdset, session, player) returnValue(ret) elif sysarg: # return system arg From bb027fc208c933bf41ece431c405776af455abd2 Mon Sep 17 00:00:00 2001 From: Griatch Date: Tue, 28 Feb 2017 19:31:25 +0100 Subject: [PATCH 061/134] Make correctly consider edit locks. --- evennia/commands/default/building.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/evennia/commands/default/building.py b/evennia/commands/default/building.py index 9702ee8560..49b47e5bf0 100644 --- a/evennia/commands/default/building.py +++ b/evennia/commands/default/building.py @@ -591,9 +591,11 @@ class CmdDesc(COMMAND_DEFAULT_CLASS): if not obj: return desc = self.args - - obj.db.desc = desc - caller.msg("The description was set on %s." % obj.get_display_name(caller)) + if obj.access(caller, "edit"): + obj.db.desc = desc + caller.msg("The description was set on %s." % obj.get_display_name(caller)) + else: + caller.msg("You don't have permission to edit the description of %s." % obj.key) class CmdDestroy(COMMAND_DEFAULT_CLASS): From 270a10e8c777a8d42644f5569fcbaf42e867ea98 Mon Sep 17 00:00:00 2001 From: CloudKeeper1 Date: Tue, 28 Feb 2017 21:41:51 +1100 Subject: [PATCH 062/134] Added Whisper Command. Command to allow private speech between players in the same location. --- evennia/commands/default/general.py | 40 ++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/evennia/commands/default/general.py b/evennia/commands/default/general.py index 797aa8719e..cf45b34107 100644 --- a/evennia/commands/default/general.py +++ b/evennia/commands/default/general.py @@ -10,7 +10,7 @@ COMMAND_DEFAULT_CLASS = utils.class_from_module(settings.COMMAND_DEFAULT_CLASS) # limit symbol import for API __all__ = ("CmdHome", "CmdLook", "CmdNick", "CmdInventory", "CmdGet", "CmdDrop", "CmdGive", - "CmdSay", "CmdPose", "CmdAccess") + "CmdSay", "CmdWhisper", "CmdPose", "CmdAccess") class CmdHome(COMMAND_DEFAULT_CLASS): @@ -417,6 +417,44 @@ class CmdSay(COMMAND_DEFAULT_CLASS): caller.location.msg_contents(emit_string, exclude=caller, from_obj=caller) +class CmdWhisper(COMMAND_DEFAULT_CLASS): + """ + Speak privately as your character to another + + Usage: + whisper = + + Talk privately to those in your current location. + """ + + key = "whisper" + locks = "cmd:all()" + + def func(self): + """Run the whisper command""" + + caller = self.caller + + if not self.lhs or not self.rhs: + caller.msg("Usage: whisper = ") + return + + receiver = caller.search(self.lhs, + nofound_string="'%s' cannot be found." % self.lhs) + if caller == receiver: + caller.msg("You can't whisper yourself.") + return + + speech = self.rhs + + # Feedback for the object doing the talking. + caller.msg('You whisper %s, "%s|n"' % (receiver.key, speech)) + + # Build the string to emit to receiver. + emit_string = '%s whispers, "%s|n"' % (caller.name, speech) + receiver.msg(emit_string, from_obj=caller) + + class CmdPose(COMMAND_DEFAULT_CLASS): """ strike a pose From b3140b6608f790f2a10d0d74fa479b8b38b1a494 Mon Sep 17 00:00:00 2001 From: CloudKeeper1 Date: Tue, 28 Feb 2017 21:43:48 +1100 Subject: [PATCH 063/134] Added new Whisper command to Cmdset Added command for use. --- evennia/commands/default/cmdset_character.py | 1 + 1 file changed, 1 insertion(+) diff --git a/evennia/commands/default/cmdset_character.py b/evennia/commands/default/cmdset_character.py index a8216db73b..bb0d2e343b 100644 --- a/evennia/commands/default/cmdset_character.py +++ b/evennia/commands/default/cmdset_character.py @@ -30,6 +30,7 @@ class CharacterCmdSet(CmdSet): self.add(general.CmdDrop()) self.add(general.CmdGive()) self.add(general.CmdSay()) + self.add(general.CmdWhisper()) self.add(general.CmdAccess()) # The help system From 44fdbc49cba9f2d2f168539598bfe85c4cd6aa55 Mon Sep 17 00:00:00 2001 From: CloudKeeper1 Date: Tue, 28 Feb 2017 22:07:18 +1100 Subject: [PATCH 064/134] Adjusted typos and indentation. --- evennia/commands/default/general.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/evennia/commands/default/general.py b/evennia/commands/default/general.py index cf45b34107..e2b543e502 100644 --- a/evennia/commands/default/general.py +++ b/evennia/commands/default/general.py @@ -439,11 +439,15 @@ class CmdWhisper(COMMAND_DEFAULT_CLASS): caller.msg("Usage: whisper = ") return - receiver = caller.search(self.lhs, - nofound_string="'%s' cannot be found." % self.lhs) + receiver = caller.search(self.lhs) + + if not receiver: + caller.msg("Usage: whisper = ") + return + if caller == receiver: - caller.msg("You can't whisper yourself.") - return + caller.msg("You can't whisper to yourself.") + return speech = self.rhs From 23a053c2139ac6efee86be9bdd56ab105dee2369 Mon Sep 17 00:00:00 2001 From: CloudKeeper1 Date: Tue, 28 Feb 2017 22:17:33 +1100 Subject: [PATCH 065/134] Added test for whisper command. --- evennia/commands/default/tests.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/evennia/commands/default/tests.py b/evennia/commands/default/tests.py index 1fc6b210e7..111eba635a 100644 --- a/evennia/commands/default/tests.py +++ b/evennia/commands/default/tests.py @@ -117,6 +117,9 @@ class TestGeneral(CommandTest): def test_say(self): self.call(general.CmdSay(), "Testing", "You say, \"Testing\"") + def test_whisper(self): + self.call(general.CmdWhisper(), "Obj = Testing", "You whisper Obj, \"Testing\"") + def test_access(self): self.call(general.CmdAccess(), "", "Permission Hierarchy (climbing):") From b73a0f8f8c86c79e214c771151b0b7302c4c792d Mon Sep 17 00:00:00 2001 From: Griatch Date: Wed, 1 Mar 2017 08:08:19 +0100 Subject: [PATCH 066/134] Some minor language fixes. --- evennia/commands/default/general.py | 10 +++++----- evennia/commands/default/tests.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/evennia/commands/default/general.py b/evennia/commands/default/general.py index e2b543e502..38d7241269 100644 --- a/evennia/commands/default/general.py +++ b/evennia/commands/default/general.py @@ -424,7 +424,8 @@ class CmdWhisper(COMMAND_DEFAULT_CLASS): Usage: whisper = - Talk privately to those in your current location. + Talk privately to those in your current location, without + others being informed. """ key = "whisper" @@ -438,13 +439,12 @@ class CmdWhisper(COMMAND_DEFAULT_CLASS): if not self.lhs or not self.rhs: caller.msg("Usage: whisper = ") return - + receiver = caller.search(self.lhs) if not receiver: - caller.msg("Usage: whisper = ") return - + if caller == receiver: caller.msg("You can't whisper to yourself.") return @@ -452,7 +452,7 @@ class CmdWhisper(COMMAND_DEFAULT_CLASS): speech = self.rhs # Feedback for the object doing the talking. - caller.msg('You whisper %s, "%s|n"' % (receiver.key, speech)) + caller.msg('You whisper to %s, "%s|n"' % (receiver.key, speech)) # Build the string to emit to receiver. emit_string = '%s whispers, "%s|n"' % (caller.name, speech) diff --git a/evennia/commands/default/tests.py b/evennia/commands/default/tests.py index 111eba635a..1d09a56c70 100644 --- a/evennia/commands/default/tests.py +++ b/evennia/commands/default/tests.py @@ -118,7 +118,7 @@ class TestGeneral(CommandTest): self.call(general.CmdSay(), "Testing", "You say, \"Testing\"") def test_whisper(self): - self.call(general.CmdWhisper(), "Obj = Testing", "You whisper Obj, \"Testing\"") + self.call(general.CmdWhisper(), "Obj = Testing", "You whisper to Obj, \"Testing\"") def test_access(self): self.call(general.CmdAccess(), "", "Permission Hierarchy (climbing):") From 357e829d4e02ef385e697e52db4a2458b44d19c2 Mon Sep 17 00:00:00 2001 From: Griatch Date: Wed, 1 Mar 2017 15:20:30 +0100 Subject: [PATCH 067/134] Make at_get_cmdset get receive a parallel kwarg. --- evennia/commands/cmdhandler.py | 2 +- evennia/objects/objects.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/evennia/commands/cmdhandler.py b/evennia/commands/cmdhandler.py index e8bb898cb3..3281687377 100644 --- a/evennia/commands/cmdhandler.py +++ b/evennia/commands/cmdhandler.py @@ -235,7 +235,7 @@ def get_and_merge_cmdsets(caller, session, player, obj, callertype, raw_string): for lobj in local_objlist: try: # call hook in case we need to do dynamic changing to cmdset - _GA(lobj, "at_cmdset_get")() + _GA(lobj, "at_cmdset_get")(caller=caller) except Exception: logger.log_trace() # the call-type lock is checked here, it makes sure a player diff --git a/evennia/objects/objects.py b/evennia/objects/objects.py index b22ce84d74..d2934f4dfc 100644 --- a/evennia/objects/objects.py +++ b/evennia/objects/objects.py @@ -1033,8 +1033,8 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): have no cmdsets. Kwargs: - Usually not set but could be used e.g. to force rebuilding - of a dynamically created cmdset or similar. + caller (Session, Object or Player): The caller requesting + this cmdset. """ pass From d995e708270b18ed6198caf0ed6cdc5dd1c1f324 Mon Sep 17 00:00:00 2001 From: BlauFeuer Date: Fri, 3 Mar 2017 17:54:37 -0500 Subject: [PATCH 068/134] Escape markup character on last line edge case Also changes default reason argument type for simplicity and consistency with default reason on other similar methods. --- evennia/server/portal/telnet.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/evennia/server/portal/telnet.py b/evennia/server/portal/telnet.py index 6b432876c2..691b185024 100644 --- a/evennia/server/portal/telnet.py +++ b/evennia/server/portal/telnet.py @@ -141,7 +141,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): enable (bool): If this option should be enabled. """ - return (option == MCCP or option==ECHO) + return option == MCCP or option == ECHO def disableLocal(self, option): """ @@ -225,16 +225,16 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): # Session hooks - def disconnect(self, reason=None): + def disconnect(self, reason=""): """ generic hook for the engine to call in order to disconnect this protocol. Args: - reason (str): Reason for disconnecting. + reason (str, optional): Reason for disconnecting. """ - self.data_out(text=((reason or "",), {})) + self.data_out(text=((reason,), {})) self.connectionLost(reason) def data_in(self, **kwargs): @@ -306,9 +306,11 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): if options.get("send_prompt"): # send a prompt instead. + prompt = text if not raw: # processing - prompt = ansi.parse_ansi(_RE_N.sub("", text) + "|n", strip_ansi=nocolor, xterm256=xterm256) + prompt = ansi.parse_ansi(_RE_N.sub("", prompt) + ("|n" if prompt[-1] != "|" else "||n"), + strip_ansi=nocolor, xterm256=xterm256) if mxp: prompt = mxp_parse(prompt) prompt = prompt.replace(IAC, IAC + IAC).replace('\n', '\r\n') @@ -335,7 +337,8 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): else: # we need to make sure to kill the color at the end in order # to match the webclient output. - linetosend = ansi.parse_ansi(_RE_N.sub("", text) + "|n", strip_ansi=nocolor, xterm256=xterm256, mxp=mxp) + linetosend = ansi.parse_ansi(_RE_N.sub("", text) + ("|n" if text[-1] != "|" else "||n"), + strip_ansi=nocolor, xterm256=xterm256, mxp=mxp) if mxp: linetosend = mxp_parse(linetosend) self.sendLine(linetosend) From 1e3d79c343c69873d407b8f34e1e6bc42b3d09dd Mon Sep 17 00:00:00 2001 From: BlauFeuer Date: Fri, 3 Mar 2017 17:58:04 -0500 Subject: [PATCH 069/134] PEP 8 whitespace, remove unused assignment --- evennia/server/portal/ttype.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/evennia/server/portal/ttype.py b/evennia/server/portal/ttype.py index de7d1b4c11..5e9ea39977 100644 --- a/evennia/server/portal/ttype.py +++ b/evennia/server/portal/ttype.py @@ -27,6 +27,7 @@ MTTS = [(128, 'PROXY'), (2, 'VT100'), (1, 'ANSI')] + class Ttype(object): """ Handles ttype negotiations. Called and initiated by the @@ -104,22 +105,21 @@ class Ttype(object): # use name to identify support for xterm256. Many of these # only support after a certain version, but all support # it since at least 4 years. We assume recent client here for now. - xterm256 = False cupper = clientname.upper() if cupper.startswith("MUDLET"): # supports xterm256 stably since 1.1 (2010?) - xterm256 = cupper.split("MUDLET",1)[1].strip() >= "1.1" + xterm256 = cupper.split("MUDLET", 1)[1].strip() >= "1.1" else: xterm256 = (cupper.startswith("XTERM") or cupper.endswith("-256COLOR") or cupper in ("ATLANTIS", # > 0.9.9.0 (aug 2009) - "CMUD", # > 3.04 (mar 2009) - "KILDCLIENT", # > 2.2.0 (sep 2005) - "MUDLET", # > beta 15 (sep 2009) - "MUSHCLIENT", # > 4.02 (apr 2007) - "PUTTY", # > 0.58 (apr 2005) - "BEIP", # > 2.00.206 (late 2009) (BeipMu) - "POTATO")) # > 2.00 (maybe earlier) + "CMUD", # > 3.04 (mar 2009) + "KILDCLIENT", # > 2.2.0 (sep 2005) + "MUDLET", # > beta 15 (sep 2009) + "MUSHCLIENT", # > 4.02 (apr 2007) + "PUTTY", # > 0.58 (apr 2005) + "BEIP", # > 2.00.206 (late 2009) (BeipMu) + "POTATO")) # > 2.00 (maybe earlier) # all clients supporting TTYPE at all seem to support ANSI self.protocol.protocol_flags['ANSI'] = True From 7e6adb2fd911b1fe7508a8084e67c6d405d83644 Mon Sep 17 00:00:00 2001 From: BlauFeuer Date: Fri, 3 Mar 2017 18:01:27 -0500 Subject: [PATCH 070/134] Escape markup character when it ends a line --- evennia/server/portal/ssh.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/evennia/server/portal/ssh.py b/evennia/server/portal/ssh.py index 1d70f14cfb..07e6fe64e4 100644 --- a/evennia/server/portal/ssh.py +++ b/evennia/server/portal/ssh.py @@ -283,7 +283,8 @@ class SshProtocol(Manhole, session.Session): else: # we need to make sure to kill the color at the end in order # to match the webclient output. - linetosend = ansi.parse_ansi(_RE_N.sub("", text) + "|n", strip_ansi=nocolor, xterm256=xterm256, mxp=False) + linetosend = ansi.parse_ansi(_RE_N.sub("", text) + ("|n" if text[-1] != "|" else "||n"), + strip_ansi=nocolor, xterm256=xterm256, mxp=False) self.sendLine(linetosend) def send_prompt(self, *args, **kwargs): From 239b04d139cb2b85da87c1282dbf52c59ccc96cb Mon Sep 17 00:00:00 2001 From: BlauFeuer Date: Fri, 3 Mar 2017 18:04:47 -0500 Subject: [PATCH 071/134] Disconnect reason argument default passed on and made consistent with other places in code where it just uses an empty string. --- evennia/server/portal/irc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/evennia/server/portal/irc.py b/evennia/server/portal/irc.py index 274f7bc83d..f8aa7e43a0 100644 --- a/evennia/server/portal/irc.py +++ b/evennia/server/portal/irc.py @@ -198,7 +198,7 @@ class IRCBot(irc.IRCClient, Session): logger.log_info("IRC bot '%s' connected to %s at %s:%s." % (self.nickname, self.channel, self.network, self.port)) - def disconnect(self, reason=None): + def disconnect(self, reason=""): """ Called by sessionhandler to disconnect this protocol. @@ -206,7 +206,7 @@ class IRCBot(irc.IRCClient, Session): reason (str): Motivation for the disconnect. """ - self.sessionhandler.disconnect(self) + self.sessionhandler.disconnect(self, reason=reason) self.stopping = True self.transport.loseConnection() From 42b41a68899a8863433a35552e00962061297903 Mon Sep 17 00:00:00 2001 From: BlauFeuer Date: Fri, 3 Mar 2017 18:06:18 -0500 Subject: [PATCH 072/134] minor PEP 8 whitespace edit --- evennia/server/portal/ssl.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/evennia/server/portal/ssl.py b/evennia/server/portal/ssl.py index c48cbf0a1a..a3d78fbefe 100644 --- a/evennia/server/portal/ssl.py +++ b/evennia/server/portal/ssl.py @@ -54,6 +54,7 @@ class SSLProtocol(TelnetProtocol): super(SSLProtocol, self).__init__(*args, **kwargs) self.protocol_name = "ssl" + def verify_SSL_key_and_cert(keyfile, certfile): """ This function looks for RSA key and certificate in the current @@ -82,7 +83,7 @@ def verify_SSL_key_and_cert(keyfile, certfile): # try to create the certificate CERT_EXPIRE = 365 * 20 # twenty years validity # default: - #openssl req -new -x509 -key ssl.key -out ssl.cert -days 7300 + # openssl req -new -x509 -key ssl.key -out ssl.cert -days 7300 exestring = "openssl req -new -x509 -key %s -out %s -days %s" % (keyfile, certfile, CERT_EXPIRE) try: subprocess.call(exestring) From 0af6f2498ed35b322efdd2faba402e91c3a646b1 Mon Sep 17 00:00:00 2001 From: BlauFeuer Date: Fri, 3 Mar 2017 18:08:47 -0500 Subject: [PATCH 073/134] PEP 8 coding, whitespace, docstring/comment edits Marking debug code, fix typo in docstring --- evennia/server/portal/telnet_oob.py | 82 ++++++++++++++--------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/evennia/server/portal/telnet_oob.py b/evennia/server/portal/telnet_oob.py index 24f74f0779..a479320c92 100644 --- a/evennia/server/portal/telnet_oob.py +++ b/evennia/server/portal/telnet_oob.py @@ -32,12 +32,12 @@ from evennia.utils.utils import to_str # MSDP-relevant telnet cmd/opt-codes MSDP = chr(69) -MSDP_VAR = chr(1) #^A -MSDP_VAL = chr(2) #^B -MSDP_TABLE_OPEN = chr(3) #^C -MSDP_TABLE_CLOSE = chr(4) #^D -MSDP_ARRAY_OPEN = chr(5) #^E -MSDP_ARRAY_CLOSE = chr(6) #^F +MSDP_VAR = chr(1) # ^A +MSDP_VAL = chr(2) # ^B +MSDP_TABLE_OPEN = chr(3) # ^C +MSDP_TABLE_CLOSE = chr(4) # ^D +MSDP_ARRAY_OPEN = chr(5) # ^E +MSDP_ARRAY_CLOSE = chr(6) # ^F # GMCP GMCP = chr(201) @@ -51,13 +51,15 @@ force_str = lambda inp: to_str(inp, force_string=True) # pre-compiled regexes # returns 2-tuple -msdp_regex_table = re.compile(r"%s\s*(\w*?)\s*%s\s*%s(.*?)%s" % (MSDP_VAR, MSDP_VAL, - MSDP_TABLE_OPEN, - MSDP_TABLE_CLOSE)) +msdp_regex_table = re.compile(r"%s\s*(\w*?)\s*%s\s*%s(.*?)%s" + % (MSDP_VAR, MSDP_VAL, + MSDP_TABLE_OPEN, + MSDP_TABLE_CLOSE)) # returns 2-tuple -msdp_regex_array = re.compile(r"%s\s*(\w*?)\s*%s\s*%s(.*?)%s" % (MSDP_VAR, MSDP_VAL, - MSDP_ARRAY_OPEN, - MSDP_ARRAY_CLOSE)) +msdp_regex_array = re.compile(r"%s\s*(\w*?)\s*%s\s*%s(.*?)%s" + % (MSDP_VAR, MSDP_VAL, + MSDP_ARRAY_OPEN, + MSDP_ARRAY_CLOSE)) msdp_regex_var = re.compile(r"%s" % MSDP_VAR) msdp_regex_val = re.compile(r"%s" % MSDP_VAL) @@ -67,9 +69,8 @@ EVENNIA_TO_GMCP = {"client_options": "Core.Supports.Get", "repeat": "Char.Repeat.Update", "monitor": "Char.Monitor.Update"} -# Msdp object handler -class TelnetOOB(object): +class TelnetOOB(object): # Msdp object handler """ Implements the MSDP and GMCP protocols. """ @@ -100,7 +101,7 @@ class TelnetOOB(object): Client reports No msdp supported or wanted. Args: - options (Option): Not used. + option (Option): Not used. """ # no msdp, check GMCP @@ -173,7 +174,7 @@ class TelnetOOB(object): if not (args or kwargs): return msdp_cmdname - #print "encode_msdp in:", cmdname, args, kwargs + # print("encode_msdp in:", cmdname, args, kwargs) # DEBUG msdp_args = '' if args: @@ -182,30 +183,30 @@ class TelnetOOB(object): msdp_args += args[0] else: msdp_args += "{msdp_array_open}" \ - "{msdp_args}" \ - "{msdp_array_close}".format( - msdp_array_open=MSDP_ARRAY_OPEN, - msdp_array_close=MSDP_ARRAY_CLOSE, - msdp_args= "".join("%s%s" % ( - MSDP_VAL, json.dumps(val)) - for val in args)) - + "{msdp_args}" \ + "{msdp_array_close}".format( + msdp_array_open=MSDP_ARRAY_OPEN, + msdp_array_close=MSDP_ARRAY_CLOSE, + msdp_args="".join("%s%s" + % (MSDP_VAL, json.dumps(val)) + for val in args)) msdp_kwargs = "" if kwargs: msdp_kwargs = msdp_cmdname msdp_kwargs += "{msdp_table_open}" \ - "{msdp_kwargs}" \ - "{msdp_table_close}".format( - msdp_table_open=MSDP_TABLE_OPEN, - msdp_table_close=MSDP_TABLE_CLOSE, - msdp_kwargs = "".join("%s%s%s%s" % ( - MSDP_VAR, key, MSDP_VAL, json.dumps(val)) - for key, val in kwargs.iteritems())) + "{msdp_kwargs}" \ + "{msdp_table_close}".format( + msdp_table_open=MSDP_TABLE_OPEN, + msdp_table_close=MSDP_TABLE_CLOSE, + msdp_kwargs="".join("%s%s%s%s" + % (MSDP_VAR, key, MSDP_VAL, + json.dumps(val)) + for key, val in kwargs.iteritems())) msdp_string = msdp_args + msdp_kwargs - #print "msdp_string:", msdp_string + # print("msdp_string:", msdp_string) # DEBUG return msdp_string def encode_gmcp(self, cmdname, *args, **kwargs): @@ -238,10 +239,10 @@ class TelnetOOB(object): gmcp_string = "%s %s" % (cmdname, json.dumps([args, kwargs])) else: gmcp_string = "%s %s" % (cmdname, json.dumps(args)) - else: # only kwargs + else: # only kwargs gmcp_string = "%s %s" % (cmdname, json.dumps(kwargs)) - #print "gmcp string", gmcp_string + # print("gmcp string", gmcp_string) # DEBUG return gmcp_string def decode_msdp(self, data): @@ -271,7 +272,7 @@ class TelnetOOB(object): if hasattr(data, "__iter__"): data = "".join(data) - #print "decode_msdp in:", data + # print("decode_msdp in:", data) # DEBUG tables = {} arrays = {} @@ -279,7 +280,7 @@ class TelnetOOB(object): # decode tables for key, table in msdp_regex_table.findall(data): - tables[key] = {} if not key in tables else tables[key] + tables[key] = {} if key not in tables else tables[key] for varval in msdp_regex_var.split(table)[1:]: var, val = msdp_regex_val.split(varval, 1) if var: @@ -288,7 +289,7 @@ class TelnetOOB(object): # decode arrays from all that was not a table data_no_tables = msdp_regex_table.sub("", data) for key, array in msdp_regex_array.findall(data_no_tables): - arrays[key] = [] if not key in arrays else arrays[key] + arrays[key] = [] if key not in arrays else arrays[key] parts = msdp_regex_val.split(array) if len(parts) == 2: arrays[key].append(parts[1]) @@ -326,10 +327,9 @@ class TelnetOOB(object): for key, var in variables.iteritems(): cmds[key] = [[var], {}] - #print "msdp data in:", cmds + # print("msdp data in:", cmds) # DEBUG self.protocol.data_in(**cmds) - def decode_gmcp(self, data): """ Decodes incoming GMCP data on the form 'varname '. @@ -353,7 +353,7 @@ class TelnetOOB(object): if hasattr(data, "__iter__"): data = "".join(data) - #print "decode_gmcp in:", data + # print("decode_gmcp in:", data) # DEBUG if data: try: cmdname, structure = data.split(None, 1) @@ -368,7 +368,7 @@ class TelnetOOB(object): args, kwargs = [], {} if hasattr(structure, "__iter__"): if isinstance(structure, dict): - kwargs = {key: value for key, value in structure.iteritems() if key } + kwargs = {key: value for key, value in structure.iteritems() if key} else: args = list(structure) else: From dd8468aab697c15b68f233b451b40048f11714de Mon Sep 17 00:00:00 2001 From: BlauFeuer Date: Fri, 3 Mar 2017 18:11:44 -0500 Subject: [PATCH 074/134] minor PEP 8 whitespace, docstring, comment edits --- evennia/server/serversession.py | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/evennia/server/serversession.py b/evennia/server/serversession.py index ddbd4b3d40..b3492fe037 100644 --- a/evennia/server/serversession.py +++ b/evennia/server/serversession.py @@ -32,8 +32,9 @@ from django.utils.translation import ugettext as _ # Handlers for Session.db/ndb operation + class NDbHolder(object): - "Holder for allowing property access of attributes" + """Holder for allowing property access of attributes""" def __init__(self, obj, name, manager_name='attributes'): _SA(self, name, _GA(obj, manager_name)) _SA(self, 'name', name) @@ -145,9 +146,9 @@ class NAttributeHandler(object): return [key for key in self._store if not key.startswith("_")] -#------------------------------------------------------------ +# ------------------------------------------------------------- # Server Session -#------------------------------------------------------------ +# ------------------------------------------------------------- class ServerSession(Session): """ @@ -160,7 +161,7 @@ class ServerSession(Session): """ def __init__(self): - "Initiate to avoid AttributeErrors down the line" + """Initiate to avoid AttributeErrors down the line""" self.puppet = None self.player = None self.cmdset_storage_string = "" @@ -203,7 +204,7 @@ class ServerSession(Session): obj.player = self.player self.puid = obj.id self.puppet = obj - #obj.scripts.validate() + # obj.scripts.validate() obj.locks.cache_lock_bypass(obj) def at_login(self, player): @@ -264,7 +265,6 @@ class ServerSession(Session): MONITOR_HANDLER.remove(player, "_saved_webclient_options", self.sessid) - def get_player(self): """ Get the player associated with this session @@ -364,7 +364,6 @@ class ServerSession(Session): self.protocol_flags.update(kwargs) self.sessionhandler.session_portal_sync(self) - def data_out(self, **kwargs): """ Sending data from Evennia->Client @@ -437,7 +436,7 @@ class ServerSession(Session): self.sessionhandler.data_in(self, **kwargs) def __eq__(self, other): - "Handle session comparisons" + """Handle session comparisons""" try: return self.address == other.address except AttributeError: @@ -462,11 +461,9 @@ class ServerSession(Session): return "%s%s@%s" % (self.uname, symbol, address) def __unicode__(self): - "Unicode representation" + """Unicode representation""" return u"%s" % str(self) - - # Dummy API hooks for use during non-loggedin operation def at_cmdset_get(self, **kwargs): @@ -488,7 +485,7 @@ class ServerSession(Session): def attributes(self): return self.nattributes - #@property + # @property def ndb_get(self): """ A non-persistent store (ndb: NonDataBase). Everything stored @@ -503,7 +500,7 @@ class ServerSession(Session): self._ndb_holder = NDbHolder(self, "nattrhandler", manager_name="nattributes") return self._ndb_holder - #@ndb.setter + # @ndb.setter def ndb_set(self, value): """ Stop accidentally replacing the db object @@ -516,9 +513,9 @@ class ServerSession(Session): string += "Use ndb.attr=value instead." raise Exception(string) - #@ndb.deleter + # @ndb.deleter def ndb_del(self): - "Stop accidental deletion." + """Stop accidental deletion.""" raise Exception("Cannot delete the ndb object!") ndb = property(ndb_get, ndb_set, ndb_del) db = property(ndb_get, ndb_set, ndb_del) @@ -526,5 +523,5 @@ class ServerSession(Session): # Mock access method for the session (there is no lock info # at this stage, so we just present a uniform API) def access(self, *args, **kwargs): - "Dummy method to mimic the logged-in API." + """Dummy method to mimic the logged-in API.""" return True From dee1cdb646cc03d6878bbd8d16c98b8a9c2501fc Mon Sep 17 00:00:00 2001 From: BlauFeuer Date: Fri, 3 Mar 2017 18:15:23 -0500 Subject: [PATCH 075/134] minor PEP 8 whitespace edits --- evennia/server/portal/portal.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/evennia/server/portal/portal.py b/evennia/server/portal/portal.py index 5de5d5bd8f..3a8fb5338f 100644 --- a/evennia/server/portal/portal.py +++ b/evennia/server/portal/portal.py @@ -37,9 +37,9 @@ if os.name == 'nt': # For Windows we need to handle pid files manually. PORTAL_PIDFILE = os.path.join(settings.GAME_DIR, "server", 'portal.pid') -#------------------------------------------------------------ +# ------------------------------------------------------------- # Evennia Portal settings -#------------------------------------------------------------ +# ------------------------------------------------------------- VERSION = get_evennia_version() @@ -76,6 +76,8 @@ AMP_ENABLED = AMP_HOST and AMP_PORT and AMP_INTERFACE # Maintenance function - this is called repeatedly by the portal. _IDLE_TIMEOUT = settings.IDLE_TIMEOUT + + def _portal_maintenance(): """ The maintenance function handles repeated checks and updates that @@ -94,12 +96,12 @@ def _portal_maintenance(): if _IDLE_TIMEOUT > 0: # only start the maintenance task if we care about idling. _maintenance_task = LoopingCall(_portal_maintenance) - _maintenance_task.start(60) # called every minute + _maintenance_task.start(60) # called every minute -#------------------------------------------------------------ +# ------------------------------------------------------------- # Portal Service object -#------------------------------------------------------------ +# ------------------------------------------------------------- class Portal(object): """ @@ -180,11 +182,11 @@ class Portal(object): self.shutdown_complete = True reactor.callLater(0, reactor.stop) -#------------------------------------------------------------ +# ------------------------------------------------------------- # # Start the Portal proxy server and add all active services # -#------------------------------------------------------------ +# ------------------------------------------------------------- # twistd requires us to define the variable 'application' so it knows # what to execute from. From 9aa8bfdf7b0d38af356348204518ce13bf6e761a Mon Sep 17 00:00:00 2001 From: BlauFeuer Date: Tue, 7 Mar 2017 07:36:53 -0500 Subject: [PATCH 076/134] Fix Typos, whitespace, docstring, long lines --- evennia/server/amp.py | 48 +++++++++++++++++++++---------------------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/evennia/server/amp.py b/evennia/server/amp.py index 621600e158..4b4435b318 100644 --- a/evennia/server/amp.py +++ b/evennia/server/amp.py @@ -7,7 +7,7 @@ The separation works like this: Portal - (AMP client) handles protocols. It contains a list of connected sessions in a dictionary for identifying the respective player - connected. If it looses the AMP connection it will automatically + connected. If it loses the AMP connection it will automatically try to reconnect. Server - (AMP server) Handles all mud operations. The server holds its own list @@ -32,33 +32,33 @@ from twisted.internet import protocol from twisted.internet.defer import Deferred from evennia.utils import logger from evennia.utils.utils import to_str, variable_from_module +import zlib # Used in Compressed class DUMMYSESSION = namedtuple('DummySession', ['sessid'])(0) # communication bits # (chr(9) and chr(10) are \t and \n, so skipping them) -PCONN = chr(1) # portal session connect -PDISCONN = chr(2) # portal session disconnect -PSYNC = chr(3) # portal session sync -SLOGIN = chr(4) # server session login -SDISCONN = chr(5) # server session disconnect -SDISCONNALL = chr(6) # server session disconnect all -SSHUTD = chr(7) # server shutdown -SSYNC = chr(8) # server session sync +PCONN = chr(1) # portal session connect +PDISCONN = chr(2) # portal session disconnect +PSYNC = chr(3) # portal session sync +SLOGIN = chr(4) # server session login +SDISCONN = chr(5) # server session disconnect +SDISCONNALL = chr(6) # server session disconnect all +SSHUTD = chr(7) # server shutdown +SSYNC = chr(8) # server session sync SCONN = chr(11) # server creating new connection (for irc bots and etc) -PCONNSYNC = chr(12) # portal post-syncing a session -PDISCONNALL = chr(13) # portal session disconnect all +PCONNSYNC = chr(12) # portal post-syncing a session +PDISCONNALL = chr(13) # portal session disconnect all AMP_MAXLEN = amp.MAX_VALUE_LENGTH # max allowed data length in AMP protocol (cannot be changed) -BATCH_RATE = 250 # max commands/sec before switching to batch-sending -BATCH_TIMEOUT = 0.5 # how often to poll to empty batch queue, in seconds +BATCH_RATE = 250 # max commands/sec before switching to batch-sending +BATCH_TIMEOUT = 0.5 # how often to poll to empty batch queue, in seconds # buffers _SENDBATCH = defaultdict(list) _MSGBUFFER = defaultdict(list) -import zlib def get_restart_mode(restart_file): """ @@ -323,9 +323,9 @@ dumps = lambda data: to_str(pickle.dumps(to_str(data), pickle.HIGHEST_PROTOCOL)) loads = lambda data: pickle.loads(to_str(data)) -#------------------------------------------------------------ +# ------------------------------------------------------------- # Core AMP protocol for communication Server <-> Portal -#------------------------------------------------------------ +# ------------------------------------------------------------- class AMPProtocol(amp.AMP): """ @@ -385,7 +385,6 @@ class AMPProtocol(amp.AMP): """ pass - # Error handling def errback(self, e, info): @@ -447,7 +446,7 @@ class AMPProtocol(amp.AMP): Access method called by the Portal and executed on the Portal. Args: - sessid (int): Unique Session id. + session (session): Session kwargs (any, optional): Optional data. Returns: @@ -473,7 +472,6 @@ class AMPProtocol(amp.AMP): self.factory.portal.sessions.data_out(session, **kwargs) return {} - def send_MsgServer2Portal(self, session, **kwargs): """ Access method - executed on the Server for sending data @@ -506,7 +504,7 @@ class AMPProtocol(amp.AMP): # create a new session and sync it server_sessionhandler.portal_connect(kwargs.get("sessiondata")) - elif operation == PCONNSYNC: #portal_session_sync + elif operation == PCONNSYNC: # portal_session_sync server_sessionhandler.portal_session_sync(kwargs.get("sessiondata")) elif operation == PDISCONN: # portal_session_disconnect @@ -515,7 +513,7 @@ class AMPProtocol(amp.AMP): if session: server_sessionhandler.portal_disconnect(session) - elif operation == PDISCONNALL: # portal_disconnect_all + elif operation == PDISCONNALL: # portal_disconnect_all # portal orders all sessions to close server_sessionhandler.portal_disconnect_all() @@ -545,7 +543,7 @@ class AMPProtocol(amp.AMP): """ return self.send_data(AdminPortal2Server, session.sessid, operation=operation, **kwargs) - # Portal administraton from the Server side + # Portal administration from the Server side @AdminServer2Portal.responder def portal_receive_adminserver2portal(self, packed_data): @@ -562,7 +560,6 @@ class AMPProtocol(amp.AMP): operation = kwargs.pop("operation") portal_sessionhandler = self.factory.portal.sessions - if operation == SLOGIN: # server_session_login # a session has authenticated; sync it. session = portal_sessionhandler.get(sessid) @@ -591,7 +588,7 @@ class AMPProtocol(amp.AMP): # set a flag in case we are about to shut down soon self.factory.server_restart_mode = True - elif operation == SCONN: # server_force_connection (for irc/etc) + elif operation == SCONN: # server_force_connection (for irc/etc) portal_sessionhandler.server_connect(**kwargs) else: @@ -665,4 +662,5 @@ class AMPProtocol(amp.AMP): module=modulepath, function=functionname, args=dumps(args), - kwargs=dumps(kwargs)).addCallback(lambda r: loads(r["result"])).addErrback(self.errback, "FunctionCall") + kwargs=dumps(kwargs)).addCallback( + lambda r: loads(r["result"])).addErrback(self.errback, "FunctionCall") From 78a6a2fc1a20cb7b22ede5414fc2fc9958e54395 Mon Sep 17 00:00:00 2001 From: BlauFeuer Date: Wed, 8 Mar 2017 00:25:33 -0500 Subject: [PATCH 077/134] comment typo --- evennia/server/portal/telnet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evennia/server/portal/telnet.py b/evennia/server/portal/telnet.py index 691b185024..e9734f14a6 100644 --- a/evennia/server/portal/telnet.py +++ b/evennia/server/portal/telnet.py @@ -189,7 +189,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): # legacy clients. There should never be a reason to send a # lone NULL character so this seems to be a safe thing to # support for backwards compatibility. It also stops the - # NULL from continously popping up as an unknown command. + # NULL from continuously popping up as an unknown command. data = [_IDLE_COMMAND] else: data = _RE_LINEBREAK.split(data) From 5d61602c38933ef3f8d03a2766f9ac04502b0882 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sun, 19 Mar 2017 17:47:42 +0100 Subject: [PATCH 078/134] Fix unicode error in unittest for mapbuilder. --- evennia/contrib/mapbuilder.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/evennia/contrib/mapbuilder.py b/evennia/contrib/mapbuilder.py index 1b00630cc3..71d883c671 100644 --- a/evennia/contrib/mapbuilder.py +++ b/evennia/contrib/mapbuilder.py @@ -98,6 +98,7 @@ from typeclasses import rooms, exits from random import randint import random + # A map with a temple (▲) amongst mountains (n,∩) in a forest (♣,♠) on an # island surrounded by water (≈). By giving no instructions for the water # characters we effectively skip it and create no rooms for those squares. @@ -322,7 +323,8 @@ def build_map(caller, game_map, legend, iterations=1, build_exits=True): for y in xrange(len(game_map)): for x in xrange(len(game_map[y])): for key in legend: - if game_map[y][x] in key: + # obs - we must use == for unicode + if utils.to_unicode(game_map[y][x]) == utils.to_unicode(key): room = legend[key](x, y, iteration=iteration, room_dict=room_dict, caller=caller) From af5560f9e9c8040684f0abb1e4d4772cc2ef7727 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sun, 19 Mar 2017 16:21:41 +0100 Subject: [PATCH 079/134] Minor doc fix. --- evennia/server/portal/telnet_oob.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evennia/server/portal/telnet_oob.py b/evennia/server/portal/telnet_oob.py index 24f74f0779..df200d4d28 100644 --- a/evennia/server/portal/telnet_oob.py +++ b/evennia/server/portal/telnet_oob.py @@ -67,7 +67,7 @@ EVENNIA_TO_GMCP = {"client_options": "Core.Supports.Get", "repeat": "Char.Repeat.Update", "monitor": "Char.Monitor.Update"} -# Msdp object handler +# MSDP/GMCP communication handler class TelnetOOB(object): """ From e762441d4deaf05b4d95fbebb1a39a5ca19fd6ac Mon Sep 17 00:00:00 2001 From: BlauFeuer Date: Fri, 3 Mar 2017 20:48:25 -0500 Subject: [PATCH 080/134] Remove unreachable code, PEP 8 whitespace, mark debug code in comments --- evennia/typeclasses/attributes.py | 108 +++++++++++++++--------------- 1 file changed, 53 insertions(+), 55 deletions(-) diff --git a/evennia/typeclasses/attributes.py b/evennia/typeclasses/attributes.py index ef34a4ce3b..58755b4955 100644 --- a/evennia/typeclasses/attributes.py +++ b/evennia/typeclasses/attributes.py @@ -24,11 +24,12 @@ from evennia.utils.utils import lazy_property, to_str, make_iter _TYPECLASS_AGGRESSIVE_CACHE = settings.TYPECLASS_AGGRESSIVE_CACHE -#------------------------------------------------------------ +# ------------------------------------------------------------- # # Attributes # -#------------------------------------------------------------ +# ------------------------------------------------------------- + class Attribute(SharedMemoryModel): """ @@ -90,7 +91,7 @@ class Attribute(SharedMemoryModel): 'date_created', editable=False, auto_now_add=True) # Database manager - #objects = managers.AttributeManager() + # objects = managers.AttributeManager() @lazy_property def locks(self): @@ -110,12 +111,15 @@ class Attribute(SharedMemoryModel): def __lock_storage_get(self): return self.db_lock_storage + def __lock_storage_set(self, value): self.db_lock_storage = value self.save(update_fields=["db_lock_storage"]) + def __lock_storage_del(self): self.db_lock_storage = "" self.save(update_fields=["db_lock_storage"]) + lock_storage = property(__lock_storage_get, __lock_storage_set, __lock_storage_del) # Wrapper properties to easily set database fields. These are @@ -127,7 +131,7 @@ class Attribute(SharedMemoryModel): # is the object in question). # value property (wraps db_value) - #@property + # @property def __value_get(self): """ Getter. Allows for `value = self.value`. @@ -137,19 +141,19 @@ class Attribute(SharedMemoryModel): """ return from_pickle(self.db_value, db_obj=self) - #@value.setter + # @value.setter def __value_set(self, new_value): """ Setter. Allows for self.value = value. We cannot cache here, see self.__value_get. """ self.db_value = to_pickle(new_value) - #print "value_set, self.db_value:", repr(self.db_value) + # print("value_set, self.db_value:", repr(self.db_value)) # DEBUG self.save(update_fields=["db_value"]) - #@value.deleter + # @value.deleter def __value_del(self): - "Deleter. Allows for del attr.value. This removes the entire attribute." + """Deleter. Allows for del attr.value. This removes the entire attribute.""" self.delete() value = property(__value_get, __value_set, __value_del) @@ -163,7 +167,7 @@ class Attribute(SharedMemoryModel): return smart_str("%s(%s)" % (self.db_key, self.id)) def __unicode__(self): - return u"%s(%s)" % (self.db_key,self.id) + return u"%s(%s)" % (self.db_key, self.id) def access(self, accessing_obj, access_type='read', default=False, **kwargs): """ @@ -202,7 +206,7 @@ class AttributeHandler(object): _attrtype = None def __init__(self, obj): - "Initialize handler." + """Initialize handler.""" self.obj = obj self._objid = obj.id self._model = to_str(obj.__dbclass__.__name__.lower()) @@ -213,10 +217,10 @@ class AttributeHandler(object): self._cache_complete = False def _fullcache(self): - "Cache all attributes of this object" - query = {"%s__id" % self._model : self._objid, - "attribute__db_model" : self._model, - "attribute__db_attrtype" : self._attrtype} + """Cache all attributes of this object""" + query = {"%s__id" % self._model: self._objid, + "attribute__db_model": self._model, + "attribute__db_attrtype": self._attrtype} attrs = [conn.attribute for conn in getattr(self.obj, self._m2m_fieldname).through.objects.filter(**query)] self._cache = dict(("%s-%s" % (to_str(attr.db_key).lower(), attr.db_category.lower() if attr.db_category else None), @@ -264,15 +268,15 @@ class AttributeHandler(object): del self._cache[cachekey] if cachefound: if attr: - return [attr] # return cached entity + return [attr] # return cached entity else: return [] # no such attribute: return an empty list else: - query = {"%s__id" % self._model : self._objid, - "attribute__db_model" : self._model, - "attribute__db_attrtype" : self._attrtype, - "attribute__db_key__iexact" : key.lower(), - "attribute__db_category__iexact" : category.lower() if category else None} + query = {"%s__id" % self._model: self._objid, + "attribute__db_model": self._model, + "attribute__db_attrtype": self._attrtype, + "attribute__db_key__iexact": key.lower(), + "attribute__db_category__iexact": category.lower() if category else None} conn = getattr(self.obj, self._m2m_fieldname).through.objects.filter(**query) if conn: attr = conn[0].attribute @@ -293,12 +297,12 @@ class AttributeHandler(object): return [attr for key, attr in self._cache.items() if key.endswith(catkey) and attr] else: # we have to query to make this category up-date in the cache - query = {"%s__id" % self._model : self._objid, - "attribute__db_model" : self._model, - "attribute__db_attrtype" : self._attrtype, - "attribute__db_category__iexact" : category.lower() if category else None} - attrs = [conn.attribute for conn in getattr(self.obj, - self._m2m_fieldname).through.objects.filter(**query)] + query = {"%s__id" % self._model: self._objid, + "attribute__db_model": self._model, + "attribute__db_attrtype": self._attrtype, + "attribute__db_category__iexact": category.lower() if category else None} + attrs = [conn.attribute for conn + in getattr(self.obj, self._m2m_fieldname).through.objects.filter(**query)] for attr in attrs: if attr.pk: cachekey = "%s-%s" % (attr.db_key, category) @@ -306,7 +310,6 @@ class AttributeHandler(object): # mark category cache as up-to-date self._catcache[catkey] = True return attrs - return [] def _setcache(self, key, category, attr_obj): """ @@ -318,7 +321,7 @@ class AttributeHandler(object): attr_obj (Attribute): The newly saved attribute """ - if not key: # don't allow an empty key in cache + if not key: # don't allow an empty key in cache return cachekey = "%s-%s" % (key, category) catkey = "-%s" % category @@ -342,7 +345,7 @@ class AttributeHandler(object): self._cache.pop(cachekey, None) else: self._cache = {key: attrobj for key, attrobj in - self._cache.items() if not key.endswith(catkey)} + self._cache.items() if not key.endswith(catkey)} # mark that the category cache is no longer up-to-date self._catcache.pop(catkey, None) self._cache_complete = False @@ -402,6 +405,7 @@ class AttributeHandler(object): accessing_obj (object, optional): If set, an `attrread` permission lock will be checked before returning each looked-after Attribute. + default_access (bool, optional): Returns: result (any, Attribute or list): This will be the value of the found @@ -416,7 +420,7 @@ class AttributeHandler(object): """ class RetDefault(object): - "Holds default values" + """Holds default values""" def __init__(self): self.key = None self.value = default @@ -444,8 +448,7 @@ class AttributeHandler(object): ret = ret if return_obj else [attr.value for attr in ret if attr] if not ret: return ret if len(key) > 1 else default - return ret[0] if len(ret)==1 else ret - + return ret[0] if len(ret) == 1 else ret def add(self, key, value, category=None, lockstring="", strattr=False, accessing_obj=None, default_access=True): @@ -470,8 +473,7 @@ class AttributeHandler(object): `attrcreate` is defined on the Attribute in question. """ - if accessing_obj and not self.obj.access(accessing_obj, - self._attrcreate, default=default_access): + if accessing_obj and not self.obj.access(accessing_obj, self._attrcreate, default=default_access): # check create access return @@ -494,21 +496,20 @@ class AttributeHandler(object): attr_obj.value = value else: # create a new Attribute (no OOB handlers can be notified) - kwargs = {"db_key" : keystr, - "db_category" : category, - "db_model" : self._model, - "db_attrtype" : self._attrtype, - "db_value" : None if strattr else to_pickle(value), - "db_strvalue" : value if strattr else None} + kwargs = {"db_key": keystr, + "db_category": category, + "db_model": self._model, + "db_attrtype": self._attrtype, + "db_value": None if strattr else to_pickle(value), + "db_strvalue": value if strattr else None} new_attr = Attribute(**kwargs) new_attr.save() getattr(self.obj, self._m2m_fieldname).add(new_attr) # update cache self._setcache(keystr, category, new_attr) - def batch_add(self, key, value, category=None, lockstring="", - strattr=False, accessing_obj=None, default_access=True): + strattr=False, accessing_obj=None, default_access=True): """ Batch-version of `add()`. This is more efficient than repeat-calling add when having many Attributes to add. @@ -535,8 +536,7 @@ class AttributeHandler(object): RuntimeError: If `key` and `value` lists are not of the same lengths. """ - if accessing_obj and not self.obj.access(accessing_obj, - self._attrcreate, default=default_access): + if accessing_obj and not self.obj.access(accessing_obj, self._attrcreate, default=default_access): # check create access return @@ -564,12 +564,12 @@ class AttributeHandler(object): attr_obj.value = new_value else: # create a new Attribute (no OOB handlers can be notified) - kwargs = {"db_key" : keystr, - "db_category" : category, + kwargs = {"db_key": keystr, + "db_category": category, "db_model": self._model, - "db_attrtype" : self._attrtype, - "db_value" : None if strattr else to_pickle(new_value), - "db_strvalue" : value if strattr else None} + "db_attrtype": self._attrtype, + "db_value": None if strattr else to_pickle(new_value), + "db_strvalue": value if strattr else None} new_attr = Attribute(**kwargs) new_attr.save() new_attrobjs.append(new_attr) @@ -578,7 +578,6 @@ class AttributeHandler(object): # Add new objects to m2m field all at once getattr(self.obj, self._m2m_fieldname).add(*new_attrobjs) - def remove(self, key, raise_exception=False, category=None, accessing_obj=None, default_access=True): """ @@ -664,7 +663,7 @@ class AttributeHandler(object): key=lambda o: o.id) if accessing_obj: return [attr for attr in attrs - if attr.access(accessing_obj, self._attredit, default=default_access)] + if attr.access(accessing_obj, self._attredit, default=default_access)] else: return attrs @@ -729,7 +728,6 @@ def initialize_nick_templates(in_template, out_template): """ - # create the regex for in_template regex_string = fnmatch.translate(in_template) # we must account for a possible line break coming over the wire @@ -876,12 +874,12 @@ class NickHandler(AttributeHandler): nicks = {} for category in make_iter(categories): nicks.update({nick.key: nick - for nick in make_iter(self.get(category=category, return_obj=True)) if nick and nick.key}) + for nick in make_iter(self.get(category=category, return_obj=True)) if nick and nick.key}) if include_player and self.obj.has_player: for category in make_iter(categories): nicks.update({nick.key: nick - for nick in make_iter(self.obj.player.nicks.get(category=category, return_obj=True)) - if nick and nick.key}) + for nick in make_iter(self.obj.player.nicks.get(category=category, return_obj=True)) + if nick and nick.key}) for key, nick in nicks.iteritems(): nick_regex, template, _, _ = nick.value regex = self._regex_cache.get(nick_regex) From 16d0002780bd8ab453f0fdf81835debe16c3d33f Mon Sep 17 00:00:00 2001 From: BlauFeuer Date: Fri, 3 Mar 2017 20:58:39 -0500 Subject: [PATCH 081/134] Move unreachable code after exception raise Also moves module import to top of file with comment. --- evennia/contrib/email_login.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/evennia/contrib/email_login.py b/evennia/contrib/email_login.py index 0684e91514..5495361b60 100644 --- a/evennia/contrib/email_login.py +++ b/evennia/contrib/email_login.py @@ -40,6 +40,7 @@ from evennia.commands.cmdset import CmdSet from evennia.utils import create, logger, utils, ansi from evennia.commands.default.muxcommand import MuxCommand from evennia.commands.cmdhandler import CMD_LOGINSTART +from evennia.commands.default import unloggedin as default_unloggedin # Used in CmdUnconnectedCreate # limit symbol import for API __all__ = ("CmdUnconnectedConnect", "CmdUnconnectedCreate", @@ -118,7 +119,7 @@ class CmdUnconnectedConnect(MuxCommand): # actually do the login. This will call all hooks. session.sessionhandler.login(session, player) -from evennia.commands.default import unloggedin as default_unloggedin + class CmdUnconnectedCreate(MuxCommand): """ Create a new account. @@ -237,9 +238,10 @@ class CmdUnconnectedCreate(MuxCommand): # We are in the middle between logged in and -not, so we have # to handle tracebacks ourselves at this point. If we don't, # we won't see any errors at all. - raise - session.msg("%sAn error occurred. Please e-mail an admin if the problem persists.") + session.msg("An error occurred. Please e-mail an admin if the problem persists.") logger.log_trace() + raise + class CmdUnconnectedQuit(MuxCommand): """ From 8343b617723405d4bbde3e2b92256fc4a96a2dfa Mon Sep 17 00:00:00 2001 From: Vincent Le Goff Date: Sun, 12 Mar 2017 15:42:12 -0700 Subject: [PATCH 082/134] Add a style 4 for the time_format utility --- evennia/utils/utils.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/evennia/utils/utils.py b/evennia/utils/utils.py index 22f5b2d326..79add32052 100644 --- a/evennia/utils/utils.py +++ b/evennia/utils/utils.py @@ -345,6 +345,7 @@ def time_format(seconds, style=0): 1. "1d" 2. "1 day, 8 hours, 30 minutes" 3. "1 day, 8 hours, 30 minutes, 10 seconds" + 4. highest unit (like "3 years" or "8 months" or "1 second") Returns: timeformatted (str): A pretty time string. """ @@ -433,6 +434,38 @@ def time_format(seconds, style=0): else: seconds_str = '%i seconds ' % seconds retval = '%s%s%s%s' % (days_str, hours_str, minutes_str, seconds_str) + elif style == 4: + """ + Only return the highest unit. + """ + if days >= 730: # Several years + return "{} years".format(days // 365) + elif days >= 365: # One year + return "a year" + elif days >= 62: # Several months + return "{} months".format(days // 31) + elif days >= 31: # One month + return "a month" + elif days >= 2: # Several days + return "{} days".format(days) + elif days > 0: + return "a day" + elif hours >= 2: # Several hours + return "{} hours".format(hours) + elif hours > 0: # One hour + return "an hour" + elif minutes >= 2: # Several minutes + return "{} minutes".format(minutes) + elif minutes > 0: # One minute + return "a minute" + elif seconds >= 2: # Several seconds + return "{} seconds".format(seconds) + elif seconds == 1: + return "a second" + else: + return "0 seconds" + else: + raise ValueError("Unknown style for time format: %s" % style) return retval.strip() From 083d6d16009b532b3a874e1f802c1a558eb6a9d7 Mon Sep 17 00:00:00 2001 From: Vincent Le Goff Date: Sun, 12 Mar 2017 15:42:35 -0700 Subject: [PATCH 083/134] Add unittests for the time_format utility --- evennia/utils/tests.py | 95 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/evennia/utils/tests.py b/evennia/utils/tests.py index d1063ee8a6..72da47e918 100644 --- a/evennia/utils/tests.py +++ b/evennia/utils/tests.py @@ -411,3 +411,98 @@ class TestEvForm(TestCase): # note that in a msg() call, the result would be the correct |-----, # in a print, ansi only gets called once, so ||----- is the result self.assertEqual(unicode(evform.EvForm(form={"FORM":"\n||-----"})), "||-----") + +class TestTimeformat(TestCase): + """ + Default function header from utils.py: + time_format(seconds, style=0) + + """ + + def test_style_0(self): + """Test the style 0 of time_format.""" + self.assertEqual(utils.time_format(0, 0), "00:00") + self.assertEqual(utils.time_format(28, 0), "00:00") + self.assertEqual(utils.time_format(92, 0), "00:01") + self.assertEqual(utils.time_format(300, 0), "00:05") + self.assertEqual(utils.time_format(660, 0), "00:11") + self.assertEqual(utils.time_format(3600, 0), "01:00") + self.assertEqual(utils.time_format(3725, 0), "01:02") + self.assertEqual(utils.time_format(86350, 0), "23:59") + self.assertEqual(utils.time_format(86800, 0), "1d 00:06") + self.assertEqual(utils.time_format(130800, 0), "1d 12:20") + self.assertEqual(utils.time_format(530800, 0), "6d 03:26") + + def test_style_1(self): + """Test the style 1 of time_format.""" + self.assertEqual(utils.time_format(0, 1), "0s") + self.assertEqual(utils.time_format(28, 1), "28s") + self.assertEqual(utils.time_format(92, 1), "1m") + self.assertEqual(utils.time_format(300, 1), "5m") + self.assertEqual(utils.time_format(660, 1), "11m") + self.assertEqual(utils.time_format(3600, 1), "1h") + self.assertEqual(utils.time_format(3725, 1), "1h") + self.assertEqual(utils.time_format(86350, 1), "23h") + self.assertEqual(utils.time_format(86800, 1), "1d") + self.assertEqual(utils.time_format(130800, 1), "1d") + self.assertEqual(utils.time_format(530800, 1), "6d") + + def test_style_2(self): + """Test the style 2 of time_format.""" + self.assertEqual(utils.time_format(0, 2), "0 minutes") + self.assertEqual(utils.time_format(28, 2), "0 minutes") + self.assertEqual(utils.time_format(92, 2), "1 minute") + self.assertEqual(utils.time_format(300, 2), "5 minutes") + self.assertEqual(utils.time_format(660, 2), "11 minutes") + self.assertEqual(utils.time_format(3600, 2), "1 hour, 0 minutes") + self.assertEqual(utils.time_format(3725, 2), "1 hour, 2 minutes") + self.assertEqual(utils.time_format(86350, 2), "23 hours, 59 minutes") + self.assertEqual(utils.time_format(86800, 2), + "1 day, 0 hours, 6 minutes") + self.assertEqual(utils.time_format(130800, 2), + "1 day, 12 hours, 20 minutes") + self.assertEqual(utils.time_format(530800, 2), + "6 days, 3 hours, 26 minutes") + + def test_style_3(self): + """Test the style 3 of time_format.""" + self.assertEqual(utils.time_format(0, 3), "") + self.assertEqual(utils.time_format(28, 3), "28 seconds") + self.assertEqual(utils.time_format(92, 3), "1 minute 32 seconds") + self.assertEqual(utils.time_format(300, 3), "5 minutes 0 seconds") + self.assertEqual(utils.time_format(660, 3), "11 minutes 0 seconds") + self.assertEqual(utils.time_format(3600, 3), + "1 hour, 0 minutes") + self.assertEqual(utils.time_format(3725, 3), + "1 hour, 2 minutes 5 seconds") + self.assertEqual(utils.time_format(86350, 3), + "23 hours, 59 minutes 10 seconds") + self.assertEqual(utils.time_format(86800, 3), + "1 day, 0 hours, 6 minutes 40 seconds") + self.assertEqual(utils.time_format(130800, 3), + "1 day, 12 hours, 20 minutes 0 seconds") + self.assertEqual(utils.time_format(530800, 3), + "6 days, 3 hours, 26 minutes 40 seconds") + + def test_style_4(self): + """Test the style 4 of time_format.""" + self.assertEqual(utils.time_format(0, 4), "0 seconds") + self.assertEqual(utils.time_format(28, 4), "28 seconds") + self.assertEqual(utils.time_format(92, 4), "a minute") + self.assertEqual(utils.time_format(300, 4), "5 minutes") + self.assertEqual(utils.time_format(660, 4), "11 minutes") + self.assertEqual(utils.time_format(3600, 4), "an hour") + self.assertEqual(utils.time_format(3725, 4), "an hour") + self.assertEqual(utils.time_format(86350, 4), "23 hours") + self.assertEqual(utils.time_format(86800, 4), "a day") + self.assertEqual(utils.time_format(130800, 4), "a day") + self.assertEqual(utils.time_format(530800, 4), "6 days") + self.assertEqual(utils.time_format(3030800, 4), "a month") + self.assertEqual(utils.time_format(7030800, 4), "2 months") + self.assertEqual(utils.time_format(40030800, 4), "a year") + self.assertEqual(utils.time_format(90030800, 4), "2 years") + + def test_unknown_format(self): + """Test that unknown formats raise exceptions.""" + self.assertRaises(ValueError, utils.time_format, 0, 5) + self.assertRaises(ValueError, utils.time_format, 0, "u") From 3383cd4c6bda8b8f31be3719892a952fec1f582c Mon Sep 17 00:00:00 2001 From: BlauFeuer Date: Mon, 13 Mar 2017 04:57:53 -0400 Subject: [PATCH 084/134] Add URL for BSD 3-Clause license Suggested to me by a user, since the URL names the license type, it serves as a direct replacement for the text. --- evennia/commands/default/system.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evennia/commands/default/system.py b/evennia/commands/default/system.py index 4a645ff68a..68a0ed8997 100644 --- a/evennia/commands/default/system.py +++ b/evennia/commands/default/system.py @@ -613,7 +613,7 @@ class CmdAbout(COMMAND_DEFAULT_CLASS): |cEvennia|n %s|n MUD/MUX/MU* development system - |wLicence|n BSD 3-Clause Licence + |wLicence|n https://opensource.org/licenses/BSD-3-Clause |wWeb|n http://www.evennia.com |wIrc|n #evennia on FreeNode |wForum|n http://www.evennia.com/discussions From 03ab95f57fcaaf7c8c00a3756cf3e9af69f411f5 Mon Sep 17 00:00:00 2001 From: BlauFeuer Date: Mon, 13 Mar 2017 05:07:09 -0400 Subject: [PATCH 085/134] Update system.py --- evennia/commands/default/system.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evennia/commands/default/system.py b/evennia/commands/default/system.py index 68a0ed8997..608817e801 100644 --- a/evennia/commands/default/system.py +++ b/evennia/commands/default/system.py @@ -607,7 +607,7 @@ class CmdAbout(COMMAND_DEFAULT_CLASS): help_category = "System" def func(self): - """Show the version""" + """Display information about server or target""" string = """ |cEvennia|n %s|n From 00b9393b3963a72b425589d15dce748d07c78f23 Mon Sep 17 00:00:00 2001 From: BlauFeuer Date: Thu, 16 Mar 2017 10:32:02 -0400 Subject: [PATCH 086/134] Fix typo in call to msg method. Reported by thranduil when attempting `@rename *thranduil = Thranduil1 (throws an ex and gives a stack trace. Character has no attribute 'mgs'") --- evennia/commands/default/building.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evennia/commands/default/building.py b/evennia/commands/default/building.py index 49b47e5bf0..5a19f86190 100644 --- a/evennia/commands/default/building.py +++ b/evennia/commands/default/building.py @@ -1141,7 +1141,7 @@ class CmdName(ObjManipCommand): caller.msg("No name defined!") return if not (obj.access(caller, "control") or obj.access(caller, "edit")): - caller.mgs("You don't have right to edit this player %s." % obj) + caller.msg("You don't have right to edit this player %s." % obj) return obj.username = newname obj.save() From 8b0232d610e0c3fbc076e4612de2721a996d0e8e Mon Sep 17 00:00:00 2001 From: BlauFeuer Date: Thu, 16 Mar 2017 10:37:43 -0400 Subject: [PATCH 087/134] Update word usage in weather text for tut world Thranduil says "gush" of wind should be "gust" (with a T)" ( gush = liquid") --- evennia/contrib/tutorial_world/rooms.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/evennia/contrib/tutorial_world/rooms.py b/evennia/contrib/tutorial_world/rooms.py index 2ec387032d..3b131019bd 100644 --- a/evennia/contrib/tutorial_world/rooms.py +++ b/evennia/contrib/tutorial_world/rooms.py @@ -279,7 +279,7 @@ class TutorialRoom(DefaultRoom): # These are rainy weather strings WEATHER_STRINGS = ( "The rain coming down from the iron-grey sky intensifies.", - "A gush of wind throws the rain right in your face. Despite your cloak you shiver.", + "A gust of wind throws the rain right in your face. Despite your cloak you shiver.", "The rainfall eases a bit and the sky momentarily brightens.", "For a moment it looks like the rain is slowing, then it begins anew with renewed force.", "The rain pummels you with large, heavy drops. You hear the rumble of thunder in the distance.", @@ -590,7 +590,7 @@ class BridgeCmdSet(CmdSet): BRIDGE_WEATHER = ( "The rain intensifies, making the planks of the bridge even more slippery.", - "A gush of wind throws the rain right in your face.", + "A gust of wind throws the rain right in your face.", "The rainfall eases a bit and the sky momentarily brightens.", "The bridge shakes under the thunder of a closeby thunder strike.", "The rain pummels you with large, heavy drops. You hear the distinct howl of a large hound in the distance.", From 8700659097579140a256123fad69923f286c8170 Mon Sep 17 00:00:00 2001 From: BlauFeuer Date: Sun, 19 Mar 2017 11:52:09 -0400 Subject: [PATCH 088/134] Typo, whitespace, comments to PEP 8 comply --- evennia/utils/spawner.py | 63 +++++++++++++++++++++------------------- 1 file changed, 33 insertions(+), 30 deletions(-) diff --git a/evennia/utils/spawner.py b/evennia/utils/spawner.py index 1dd2361e54..a0d5f5601f 100644 --- a/evennia/utils/spawner.py +++ b/evennia/utils/spawner.py @@ -82,9 +82,9 @@ many traits with a normal *goblin*. from __future__ import print_function import copy -#TODO -#sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) -#os.environ['DJANGO_SETTINGS_MODULE'] = 'game.settings' +# TODO +# sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) +# os.environ['DJANGO_SETTINGS_MODULE'] = 'game.settings' from django.conf import settings from random import randint @@ -131,9 +131,10 @@ def _get_prototype(dic, prot, protparents): new_prot = _get_prototype(protparents.get(prototype, {}), prot, protparents) prot.update(new_prot) prot.update(dic) - prot.pop("prototype", None) # we don't need this anymore + prot.pop("prototype", None) # we don't need this anymore return prot + def _batch_create_object(*objparams): """ This is a cut-down version of the create_object() function, @@ -141,7 +142,7 @@ def _batch_create_object(*objparams): so make sure the spawned Typeclass works before using this! Args: - objsparams (any): Aach argument should be a tuple of arguments + objsparams (any): Each argument should be a tuple of arguments for the respective creation/add handlers in the following order: (create, permissions, locks, aliases, nattributes, attributes) @@ -153,8 +154,8 @@ def _batch_create_object(*objparams): # bulk create all objects in one go # unfortunately this doesn't work since bulk_create doesn't creates pks; - # the result are double objects at the next stage - #dbobjs = _ObjectDB.objects.bulk_create(dbobjs) + # the result are database objects at the next stage + # dbobjs = _ObjectDB.objects.bulk_create(dbobjs) dbobjs = [ObjectDB(**objparam[0]) for objparam in objparams] objs = [] @@ -167,7 +168,7 @@ def _batch_create_object(*objparams): "aliases": objparam[3], "nattributes": objparam[4], "attributes": objparam[5], - "tags":objparam[6]} + "tags": objparam[6]} # this triggers all hooks obj.save() # run eventual extra code @@ -201,9 +202,9 @@ def spawn(*prototypes, **kwargs): if not protmodules and hasattr(settings, "PROTOTYPE_MODULES"): protmodules = make_iter(settings.PROTOTYPE_MODULES) for prototype_module in protmodules: - protparents.update(dict((key, val) - for key, val in all_from_module(prototype_module).items() if isinstance(val, dict))) - #overload module's protparents with specifically given protparents + protparents.update(dict((key, val) for key, val in + all_from_module(prototype_module).items() if isinstance(val, dict))) + # overload module's protparents with specifically given protparents protparents.update(kwargs.get("prototype_parents", {})) for key, prototype in protparents.items(): _validate_prototype(key, prototype, protparents, []) @@ -223,10 +224,10 @@ def spawn(*prototypes, **kwargs): # extract the keyword args we need to create the object itself. If we get a callable, # call that to get the value (don't catch errors) create_kwargs = {} - keyval = prot.pop("key", "Spawned Object %06i" % randint(1,100000)) + keyval = prot.pop("key", "Spawned Object %06i" % randint(1, 100000)) create_kwargs["db_key"] = keyval() if callable(keyval) else keyval - locval = prot.pop("location", None) + locval = prot.pop("location", None) create_kwargs["db_location"] = locval() if callable(locval) else _handle_dbref(locval) homval = prot.pop("home", settings.DEFAULT_HOME) @@ -244,7 +245,7 @@ def spawn(*prototypes, **kwargs): lockval = prot.pop("locks", "") lock_string = lockval() if callable(lockval) else lockval aliasval = prot.pop("aliases", "") - alias_string = aliasval() if callable(aliasval) else aliasval + alias_string = aliasval() if callable(aliasval) else aliasval tagval = prot.pop("tags", "") tags = tagval() if callable(tagval) else tagval exval = prot.pop("exec", "") @@ -252,16 +253,16 @@ def spawn(*prototypes, **kwargs): # extract ndb assignments nattributes = dict((key.split("_", 1)[1], value() if callable(value) else value) - for key, value in prot.items() if key.startswith("ndb_")) + for key, value in prot.items() if key.startswith("ndb_")) # the rest are attributes attributes = dict((key, value() if callable(value) else value) - for key, value in prot.items() - if not (key in _CREATE_OBJECT_KWARGS or key.startswith("ndb_"))) + for key, value in prot.items() + if not (key in _CREATE_OBJECT_KWARGS or key.startswith("ndb_"))) # pack for call into _batch_create_object - objsparams.append( (create_kwargs, permission_string, lock_string, - alias_string, nattributes, attributes, tags, execs) ) + objsparams.append((create_kwargs, permission_string, lock_string, + alias_string, nattributes, attributes, tags, execs)) return _batch_create_object(*objsparams) @@ -271,33 +272,35 @@ if __name__ == "__main__": protparents = { "NOBODY": {}, - #"INFINITE" : { - # "prototype":"INFINITE" - #}, - "GOBLIN" : { + # "INFINITE" : { + # "prototype":"INFINITE" + # }, + "GOBLIN": { "key": "goblin grunt", - "health": lambda: randint(20,30), + "health": lambda: randint(20, 30), "resists": ["cold", "poison"], "attacks": ["fists"], "weaknesses": ["fire", "light"] }, - "GOBLIN_WIZARD" : { + "GOBLIN_WIZARD": { "prototype": "GOBLIN", "key": "goblin wizard", "spells": ["fire ball", "lighting bolt"] }, - "GOBLIN_ARCHER" : { + "GOBLIN_ARCHER": { "prototype": "GOBLIN", "key": "goblin archer", "attacks": ["short bow"] }, - "ARCHWIZARD" : { + "ARCHWIZARD": { "attacks": ["archwizard staff"], }, - "GOBLIN_ARCHWIZARD" : { + "GOBLIN_ARCHWIZARD": { "key": "goblin archwizard", - "prototype" : ("GOBLIN_WIZARD", "ARCHWIZARD") + "prototype": ("GOBLIN_WIZARD", "ARCHWIZARD") } } # test - print([o.key for o in spawn(protparents["GOBLIN"], protparents["GOBLIN_ARCHWIZARD"], prototype_parents=protparents)]) + print([o.key for o in spawn(protparents["GOBLIN"], + protparents["GOBLIN_ARCHWIZARD"], + prototype_parents=protparents)]) From 0f72f0f74c5dbfd0cc374e0bc686c26470442114 Mon Sep 17 00:00:00 2001 From: BlauFeuer Date: Sun, 19 Mar 2017 11:55:26 -0400 Subject: [PATCH 089/134] PEP 8 whitespace comply --- evennia/utils/search.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/evennia/utils/search.py b/evennia/utils/search.py index e46b2a55c2..3f75cdd694 100644 --- a/evennia/utils/search.py +++ b/evennia/utils/search.py @@ -46,9 +46,9 @@ HelpEntry = ContentType.objects.get(app_label="help", model="helpentry").model_c Tag = ContentType.objects.get(app_label="typeclasses", model="tag").model_class() -#------------------------------------------------------------------ +# ------------------------------------------------------------------- # Search manager-wrappers -#------------------------------------------------------------------ +# ------------------------------------------------------------------- # # Search objects as a character @@ -199,10 +199,16 @@ help_entries = search_help def search_object_attribute(key=None, category=None, value=None, strvalue=None): return ObjectDB.objects.get_by_attribute(key=key, category=category, value=value, strvalue=strvalue) + + def search_player_attribute(key=None, category=None, value=None, strvalue=None): return PlayerDB.objects.get_by_attribute(key=key, category=category, value=value, strvalue=strvalue) + + def search_script_attribute(key=None, category=None, value=None, strvalue=None): return ScriptDB.objects.get_by_attribute(key=key, category=category, value=value, strvalue=strvalue) + + def search_channel_attribute(key=None, category=None, value=None, strvalue=None): return Channel.objects.get_by_attribute(key=key, category=category, value=value, strvalue=strvalue) @@ -218,6 +224,8 @@ search_attribute_object = ObjectDB.objects.get_attribute # Note that this returns the object attached to the tag, not the tag # object itself (this is usually what you want) + + def search_object_by_tag(key=None, category=None): """ Find object based on tag or category. @@ -235,7 +243,9 @@ def search_object_by_tag(key=None, category=None): """ return ObjectDB.objects.get_by_tag(key=key, category=category) -search_tag = search_object_by_tag # this is the most common case +search_tag = search_object_by_tag # this is the most common case + + def search_player_tag(key=None, category=None): """ Find player based on tag or category. @@ -253,6 +263,8 @@ def search_player_tag(key=None, category=None): """ return PlayerDB.objects.get_by_tag(key=key, category=category) + + def search_script_tag(key=None, category=None): """ Find script based on tag or category. @@ -270,6 +282,8 @@ def search_script_tag(key=None, category=None): """ return ScriptDB.objects.get_by_tag(key=key, category=category) + + def search_channel_tag(key=None, category=None): """ Find channel based on tag or category. From 3d268eaa39f98556406bda050c2d8bdc10dcbef4 Mon Sep 17 00:00:00 2001 From: BlauFeuer Date: Sun, 19 Mar 2017 12:00:26 -0400 Subject: [PATCH 090/134] PEP 8 whitespace comply, visual align --- evennia/utils/gametime.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/evennia/utils/gametime.py b/evennia/utils/gametime.py index b419bd3423..b2674fed59 100644 --- a/evennia/utils/gametime.py +++ b/evennia/utils/gametime.py @@ -39,6 +39,7 @@ _GAME_EPOCH = None # Helper Script dealing in gametime (created by `schedule` function # below). + class TimeScript(DefaultScript): """Gametime-sensitive script.""" @@ -60,6 +61,7 @@ class TimeScript(DefaultScript): # Access functions + def runtime(): """ Get the total runtime of the server since first start (minus @@ -134,8 +136,9 @@ def gametime(absolute=False): gtime = epoch + (runtime() - GAME_TIME_OFFSET) * TIMEFACTOR return gtime -def real_seconds_until(sec=None, min=None, hour=None, day=None, - month=None, year=None): + +def real_seconds_until(sec=None, min=None, hour=None, + day=None, month=None, year=None): """ Return the real seconds until game time. @@ -187,8 +190,9 @@ def real_seconds_until(sec=None, min=None, hour=None, day=None, seconds = (projected - current).total_seconds() return seconds / TIMEFACTOR -def schedule(callback, repeat=False, sec=None, min=None, hour=None, - day=None, month=None, year=None): + +def schedule(callback, repeat=False, sec=None, min=None, + hour=None, day=None, month=None, year=None): """ Call a callback at a given in-game time. @@ -212,12 +216,12 @@ def schedule(callback, repeat=False, sec=None, min=None, hour=None, schedule(func, min=5, sec=0) # Will call 5 minutes past the next (in-game) hour. schedule(func, hour=2, min=30, sec=0) # Will call the next (in-game) day at 02:30. """ - seconds = real_seconds_until(sec=sec, min=min, hour=hour, day=day, - month=month, year=year) + seconds = real_seconds_until(sec=sec, min=min, hour=hour, + day=day, month=month, year=year) script = create_script("evennia.utils.gametime.TimeScript", - key="TimeScript", desc="A gametime-sensitive script", - interval=seconds, start_delay=True, - repeats=-1 if repeat else 1) + key="TimeScript", desc="A gametime-sensitive script", + interval=seconds, start_delay=True, + repeats=-1 if repeat else 1) script.db.callback = callback script.db.gametime = { "sec": sec, @@ -229,6 +233,7 @@ def schedule(callback, repeat=False, sec=None, min=None, hour=None, } return script + def reset_gametime(): """ Resets the game time to make it start from the current time. Note that @@ -238,5 +243,3 @@ def reset_gametime(): global GAME_TIME_OFFSET GAME_TIME_OFFSET = runtime() ServerConfig.objects.conf("gametime_offset", GAME_TIME_OFFSET) - - From 4d8c5964a41bccadc0631e095162ceb044c6fb36 Mon Sep 17 00:00:00 2001 From: BlauFeuer Date: Sun, 19 Mar 2017 12:07:43 -0400 Subject: [PATCH 091/134] PEP 8 comply, docstring typo --- evennia/utils/logger.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/evennia/utils/logger.py b/evennia/utils/logger.py index f1f5cdced8..a46c549dec 100644 --- a/evennia/utils/logger.py +++ b/evennia/utils/logger.py @@ -26,6 +26,7 @@ from twisted.internet.threads import deferToThread _LOGDIR = None _TIMEZONE = None + def timeformat(when=None): """ This helper function will format the current time in the same @@ -88,7 +89,7 @@ def log_err(errmsg): Prints/logs an error message to the server log. Args: - errormsg (str): The message to be logged. + errmsg (str): The message to be logged. """ try: @@ -97,7 +98,7 @@ def log_err(errmsg): errmsg = str(e) for line in errmsg.splitlines(): log.msg('[EE] %s' % line) - #log.err('ERROR: %s' % (errormsg,)) + # log.err('ERROR: %s' % (errmsg,)) log_errmsg = log_err @@ -115,7 +116,7 @@ def log_warn(warnmsg): warnmsg = str(e) for line in warnmsg.splitlines(): log.msg('[WW] %s' % line) - #log.msg('WARNING: %s' % (warnmsg,)) + # log.msg('WARNING: %s' % (warnmsg,)) log_warnmsg = log_warn @@ -152,7 +153,8 @@ log_depmsg = log_dep # Arbitrary file logger -_LOG_FILE_HANDLES = {} # holds open log handles +_LOG_FILE_HANDLES = {} # holds open log handles + def _open_log_file(filename): """ @@ -171,7 +173,7 @@ def _open_log_file(filename): return _LOG_FILE_HANDLES[filename] else: try: - filehandle = open(filename, "a+") # append mode + reading + filehandle = open(filename, "a+") # append mode + reading _LOG_FILE_HANDLES[filename] = filehandle return filehandle except IOError: @@ -184,13 +186,14 @@ def log_file(msg, filename="game.log"): Arbitrary file logger using threads. Args: + msg (str): String to append to logfile. filename (str, optional): Defaults to 'game.log'. All logs will appear in the logs directory and log entries will start on new lines following datetime info. """ def callback(filehandle, msg): - "Writing to file and flushing result" + """Writing to file and flushing result""" msg = "\n%s [-] %s" % (timeformat(), msg.strip()) filehandle.write(msg) # since we don't close the handle, we need to flush @@ -199,7 +202,7 @@ def log_file(msg, filename="game.log"): filehandle.flush() def errback(failure): - "Catching errors to normal log" + """Catching errors to normal log""" log_trace() # save to server/logs/ directory @@ -230,7 +233,7 @@ def tail_log_file(filename, offset, nlines, callback=None): """ def seek_file(filehandle, offset, nlines, callback): - "step backwards in chunks and stop only when we have enough lines" + """step backwards in chunks and stop only when we have enough lines""" lines_found = [] buffer_size = 4098 block_count = -1 @@ -254,7 +257,7 @@ def tail_log_file(filename, offset, nlines, callback=None): return lines_found def errback(failure): - "Catching errors to normal log" + """Catching errors to normal log""" log_trace() filehandle = _open_log_file(filename) From c27c96f423711460246294c3315f046008e61db1 Mon Sep 17 00:00:00 2001 From: BlauFeuer Date: Sun, 19 Mar 2017 12:17:14 -0400 Subject: [PATCH 092/134] PEP 8 comply --- evennia/utils/dbserialize.py | 55 +++++++++++++++++++----------------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/evennia/utils/dbserialize.py b/evennia/utils/dbserialize.py index 886bf6528b..f290635806 100644 --- a/evennia/utils/dbserialize.py +++ b/evennia/utils/dbserialize.py @@ -33,10 +33,11 @@ from evennia.utils.utils import to_str, uses_database from evennia.utils import logger __all__ = ("to_pickle", "from_pickle", "do_pickle", "do_unpickle", - "dbserialize", "dbunserialize") + "dbserialize", "dbunserialize") PICKLE_PROTOCOL = 2 + def _get_mysql_db_version(): """ This is a helper method for specifically getting the version @@ -93,7 +94,7 @@ def _TO_DATESTRING(obj): def _init_globals(): - "Lazy importing to avoid circular import issues" + """Lazy importing to avoid circular import issues""" global _FROM_MODEL_MAP, _TO_MODEL_MAP, _SESSION_HANDLER if not _FROM_MODEL_MAP: _FROM_MODEL_MAP = defaultdict(str) @@ -110,7 +111,7 @@ def _init_globals(): def _save(method): - "method decorator that saves data to Attribute" + """method decorator that saves data to Attribute""" def save_wrapper(self, *args, **kwargs): self.__doc__ = method.__doc__ ret = method(self, *args, **kwargs) @@ -127,17 +128,17 @@ class _SaverMutable(object): will not save the updated value to the database. """ def __init__(self, *args, **kwargs): - "store all properties for tracking the tree" + """store all properties for tracking the tree""" self._parent = kwargs.pop("_parent", None) self._db_obj = kwargs.pop("_db_obj", None) self._data = None def __nonzero__(self): - "Make sure to evaluate as False if empty" + """Make sure to evaluate as False if empty""" return bool(self._data) def _save_tree(self): - "recursively traverse back up the tree, save when we reach the root" + """recursively traverse back up the tree, save when we reach the root""" if self._parent: self._parent._save_tree() elif self._db_obj: @@ -146,9 +147,9 @@ class _SaverMutable(object): logger.log_err("_SaverMutable %s has no root Attribute to save to." % self) def _convert_mutables(self, data): - "converts mutables to Saver* variants and assigns ._parent property" + """converts mutables to Saver* variants and assigns ._parent property""" def process_tree(item, parent): - "recursively populate the tree, storing parents" + """recursively populate the tree, storing parents""" dtype = type(item) if dtype in (basestring, int, float, bool, tuple): return item @@ -218,7 +219,6 @@ class _SaverList(_SaverMutable, MutableSequence): return self._data.index(value, *args) - class _SaverDict(_SaverMutable, MutableMapping): """ A dict that stores changes to an Attribute when updated @@ -290,8 +290,10 @@ class _SaverDeque(_SaverMutable): # maxlen property def _getmaxlen(self): return self._data.maxlen + def _setmaxlen(self, value): self._data.maxlen = value + def _delmaxlen(self): del self._data.maxlen maxlen = property(_getmaxlen, _setmaxlen, _delmaxlen) @@ -314,7 +316,7 @@ class _SaverDeque(_SaverMutable): # # serialization helpers -# + def pack_dbobj(item): """ @@ -335,7 +337,7 @@ def pack_dbobj(item): # build the internal representation as a tuple # ("__packed_dbobj__", key, creation_time, id) return natural_key and ('__packed_dbobj__', natural_key, - _TO_DATESTRING(obj), _GA(obj, "id")) or item + _TO_DATESTRING(obj), _GA(obj, "id")) or item def unpack_dbobj(item): @@ -391,10 +393,11 @@ def pack_session(item): # to be accepted as actually being a session (sessids gets # reused all the time). return item.conn_time and item.sessid and ('__packed_session__', - _GA(item, "sessid"), - _GA(item, "conn_time")) + _GA(item, "sessid"), + _GA(item, "conn_time")) return None + def unpack_session(item): """ Check and convert internal representations back to Sessions. @@ -419,7 +422,7 @@ def unpack_session(item): # # Access methods -# + def to_pickle(data): """ @@ -437,7 +440,7 @@ def to_pickle(data): """ def process_item(item): - "Recursive processor and identification of data" + """Recursive processor and identification of data""" dtype = type(item) if dtype in (basestring, int, float, bool): return item @@ -466,7 +469,7 @@ def to_pickle(data): return process_item(data) -#@transaction.autocommit +# @transaction.autocommit def from_pickle(data, db_obj=None): """ This should be fed a just de-pickled data object. It will be converted back @@ -489,7 +492,7 @@ def from_pickle(data, db_obj=None): """ def process_item(item): - "Recursive processor and identification of data" + """Recursive processor and identification of data""" dtype = type(item) if dtype in (basestring, int, float, bool): return item @@ -518,7 +521,7 @@ def from_pickle(data, db_obj=None): return item def process_tree(item, parent): - "Recursive processor, building a parent-tree from iterable data" + """Recursive processor, building a parent-tree from iterable data""" dtype = type(item) if dtype in (basestring, int, float, bool): return item @@ -534,7 +537,7 @@ def from_pickle(data, db_obj=None): elif dtype == dict: dat = _SaverDict(_parent=parent) dat._data.update((process_item(key), process_tree(val, dat)) - for key, val in item.items()) + for key, val in item.items()) return dat elif dtype == set: dat = _SaverSet(_parent=parent) @@ -543,7 +546,7 @@ def from_pickle(data, db_obj=None): elif dtype == OrderedDict: dat = _SaverOrderedDict(_parent=parent) dat._data.update((process_item(key), process_tree(val, dat)) - for key, val in item.items()) + for key, val in item.items()) return dat elif dtype == deque: dat = _SaverDeque(_parent=parent) @@ -571,7 +574,7 @@ def from_pickle(data, db_obj=None): elif dtype == dict: dat = _SaverDict(_db_obj=db_obj) dat._data.update((process_item(key), process_tree(val, dat)) - for key, val in data.items()) + for key, val in data.items()) return dat elif dtype == set: dat = _SaverSet(_db_obj=db_obj) @@ -580,7 +583,7 @@ def from_pickle(data, db_obj=None): elif dtype == OrderedDict: dat = _SaverOrderedDict(_db_obj=db_obj) dat._data.update((process_item(key), process_tree(val, dat)) - for key, val in data.items()) + for key, val in data.items()) return dat elif dtype == deque: dat = _SaverDeque(_db_obj=db_obj) @@ -590,20 +593,20 @@ def from_pickle(data, db_obj=None): def do_pickle(data): - "Perform pickle to string" + """Perform pickle to string""" return to_str(dumps(data, protocol=PICKLE_PROTOCOL)) def do_unpickle(data): - "Retrieve pickle from pickled string" + """Retrieve pickle from pickled string""" return loads(to_str(data)) def dbserialize(data): - "Serialize to pickled form in one step" + """Serialize to pickled form in one step""" return do_pickle(to_pickle(data)) def dbunserialize(data, db_obj=None): - "Un-serialize in one step. See from_pickle for help db_obj." + """Un-serialize in one step. See from_pickle for help db_obj.""" return from_pickle(do_unpickle(data), db_obj=db_obj) From dee0bfe0103a730f117ccfc25716ad0354485c0a Mon Sep 17 00:00:00 2001 From: Griatch Date: Sun, 19 Mar 2017 20:27:23 +0100 Subject: [PATCH 093/134] Clarify bulk-create comment in spawner. --- evennia/utils/spawner.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/evennia/utils/spawner.py b/evennia/utils/spawner.py index a0d5f5601f..28274e5531 100644 --- a/evennia/utils/spawner.py +++ b/evennia/utils/spawner.py @@ -154,8 +154,9 @@ def _batch_create_object(*objparams): # bulk create all objects in one go # unfortunately this doesn't work since bulk_create doesn't creates pks; - # the result are database objects at the next stage - # dbobjs = _ObjectDB.objects.bulk_create(dbobjs) + # the result would be duplicate objects at the next stage, so we comment + # it out for now: + # dbobjs = _ObjectDB.objects.bulk_create(dbobjs) dbobjs = [ObjectDB(**objparam[0]) for objparam in objparams] objs = [] From 62bd9fb4ab01ef8b9ce8664a36b2e81848967e73 Mon Sep 17 00:00:00 2001 From: Vincent Le Goff Date: Sun, 19 Mar 2017 12:33:09 -0700 Subject: [PATCH 094/134] The ansi.raw utility now escapes vertical bars --- evennia/utils/ansi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evennia/utils/ansi.py b/evennia/utils/ansi.py index f445f2d002..dead3e00bf 100644 --- a/evennia/utils/ansi.py +++ b/evennia/utils/ansi.py @@ -548,7 +548,7 @@ def raw(string): string (str): The raw, escaped string. """ - return string.replace('{', '{{') + return string.replace('{', '{{').replace('|', '||') def group(lst, n): From 63c7ea3ab7370d40f68a24dd45ea84a1e9a8f087 Mon Sep 17 00:00:00 2001 From: BlauFeuer Date: Mon, 20 Mar 2017 05:45:18 -0400 Subject: [PATCH 095/134] Indent fixes to address #1258 PEP 8 whitespace and docstring edits, also --- evennia/players/bots.py | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/evennia/players/bots.py b/evennia/players/bots.py index 5f6e327ce2..feb488b070 100644 --- a/evennia/players/bots.py +++ b/evennia/players/bots.py @@ -81,8 +81,10 @@ class BotStarter(DefaultScript): """ self.db.started = False +# # Bot base class + class Bot(DefaultPlayer): """ A Bot will start itself when the server starts (it will generally @@ -196,9 +198,9 @@ class IRCBot(Bot): # instruct the server and portal to create a new session with # the stored configuration - configdict = {"uid":self.dbid, + configdict = {"uid": self.dbid, "botname": self.db.irc_botname, - "channel": self.db.irc_channel , + "channel": self.db.irc_channel, "network": self.db.irc_network, "port": self.db.irc_port, "ssl": self.db.irc_ssl} @@ -325,31 +327,32 @@ class IRCBot(Bot): whos.append("%s (%s/%s)" % (utils.crop("|w%s|n" % player.name, width=25), utils.time_format(delta_conn, 0), utils.time_format(delta_cmd, 1))) - text = "Who list (online/idle): %s" % ", ".join(sorted(whos, key=lambda w:w.lower())) + text = "Who list (online/idle): %s" % ", ".join(sorted(whos, key=lambda w: w.lower())) elif txt.lower().startswith("about"): # some bot info text = "This is an Evennia IRC bot connecting from '%s'." % settings.SERVERNAME else: text = "I understand 'who' and 'about'." - super(IRCBot, self).msg(privmsg=((text,), {"user":user})) + super(IRCBot, self).msg(privmsg=((text,), {"user": user})) else: # something to send to the main channel if kwargs["type"] == "action": # An action (irc pose) text = "%s@%s %s" % (kwargs["user"], kwargs["channel"], txt) - else: # msg - A normal channel message text = "%s@%s: %s" % (kwargs["user"], kwargs["channel"], txt) - if not self.ndb.ev_channel and self.db.ev_channel: - # cache channel lookup - self.ndb.ev_channel = self.db.ev_channel - if self.ndb.ev_channel: - self.ndb.ev_channel.msg(text, senders=self.id) + if not self.ndb.ev_channel and self.db.ev_channel: + # cache channel lookup + self.ndb.ev_channel = self.db.ev_channel + if self.ndb.ev_channel: + self.ndb.ev_channel.msg(text, senders=self.id) +# # RSS + class RSSBot(Bot): """ An RSS relayer. The RSS protocol itself runs a ticker to update @@ -363,7 +366,7 @@ class RSSBot(Bot): Args: ev_channel (str): Key of the Evennia channel to connect to. rss_url (str): Full URL to the RSS feed to subscribe to. - rss_update_rate (int): How often for the feedreader to update. + rss_rate (int): How often for the feedreader to update. Raises: RuntimeError: If `ev_channel` does not exist. @@ -371,8 +374,8 @@ class RSSBot(Bot): """ if not _RSS_EMABLED: # The bot was created, then RSS was turned off. Delete ourselves. - self.delete() - return + self.delete() + return global _SESSIONS if not _SESSIONS: @@ -404,7 +407,7 @@ class RSSBot(Bot): Args: session (Session, optional): Session responsible for this command. - text (str, optional): Command string. + txt (str, optional): Command string. kwargs (dict, optional): Additional Information passed from bot. Not used by the RSSbot by default. From 7738d06fea85623f732390796bba0d2079a6ecef Mon Sep 17 00:00:00 2001 From: BlauFeuer Date: Mon, 20 Mar 2017 06:06:04 -0400 Subject: [PATCH 096/134] Typo in code that prevented RSS toggle off/delete --- evennia/players/bots.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evennia/players/bots.py b/evennia/players/bots.py index feb488b070..c415b78c69 100644 --- a/evennia/players/bots.py +++ b/evennia/players/bots.py @@ -372,7 +372,7 @@ class RSSBot(Bot): RuntimeError: If `ev_channel` does not exist. """ - if not _RSS_EMABLED: + if not _RSS_ENABLED: # The bot was created, then RSS was turned off. Delete ourselves. self.delete() return From a83ea62df8d1259828cbc5da1cdb93f245a4c064 Mon Sep 17 00:00:00 2001 From: CloudKeeper1 Date: Tue, 21 Mar 2017 22:14:36 +1100 Subject: [PATCH 097/134] Fixed Typo in code. ...cdmset... to ...cmdset... --- evennia/utils/evmenu.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evennia/utils/evmenu.py b/evennia/utils/evmenu.py index 438b078a65..a59855716d 100644 --- a/evennia/utils/evmenu.py +++ b/evennia/utils/evmenu.py @@ -906,7 +906,7 @@ class CmdGetInput(Command): # make sure to clean up cmdset if something goes wrong caller.msg("|rError in get_input. Choice not confirmed (report to admin)|n") logger.log_trace("Error in get_input") - caller.cdmset.remove(InputCmdSet) + caller.cmdset.remove(InputCmdSet) class InputCmdSet(CmdSet): From ff3b293a4a8d645e15a475a7cc14a45f160244b7 Mon Sep 17 00:00:00 2001 From: BlauFeuer Date: Tue, 21 Mar 2017 19:03:34 -0400 Subject: [PATCH 098/134] Only parse as switches when directly after command --- evennia/commands/default/muxcommand.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evennia/commands/default/muxcommand.py b/evennia/commands/default/muxcommand.py index 24e15ecdee..1fb431292d 100644 --- a/evennia/commands/default/muxcommand.py +++ b/evennia/commands/default/muxcommand.py @@ -99,7 +99,7 @@ class MuxCommand(Command): # split out switches switches = [] - if args and len(args) > 1 and args[0] == "/": + if args and len(args) > 1 and raw[0] == "/": # we have a switch, or a set of switches. These end with a space. switches = args[1:].split(None, 1) if len(switches) > 1: From 4e25c938889d35e305039180354370886a9cef59 Mon Sep 17 00:00:00 2001 From: BattleJenkins Date: Wed, 22 Mar 2017 00:19:58 -0700 Subject: [PATCH 099/134] Added at_give() hook to default object Added another hook to the default object, at_give(), which will be called by the default give command. --- evennia/objects/objects.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/evennia/objects/objects.py b/evennia/objects/objects.py index d2934f4dfc..cf90321692 100644 --- a/evennia/objects/objects.py +++ b/evennia/objects/objects.py @@ -1416,6 +1416,22 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): """ pass + + def at_give(self, giver, getter): + """ + Called by the default `give` command when this object has been + given. + + Args: + giver (Object): The object giving this object. + getter (Object): The object getting this object. + + Notes: + This hook cannot stop the give from happening. Use + permissions for that. + + """ + pass def at_drop(self, dropper): """ @@ -1426,7 +1442,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): dropper (Object): The object which just dropped this object. Notes: - This hook cannot stop the pickup from happening. Use + This hook cannot stop the drop from happening. Use permissions from that. """ From 96ba31f8584c9e024e49e7d3436b25f6af1e5ab0 Mon Sep 17 00:00:00 2001 From: BattleJenkins Date: Wed, 22 Mar 2017 00:22:52 -0700 Subject: [PATCH 100/134] Implemented default object's at_give() hook Calls a new hook on the default object, at_give(), which passes the giving and receiving objects as arguments. --- evennia/commands/default/general.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/evennia/commands/default/general.py b/evennia/commands/default/general.py index 38d7241269..21dc70e57b 100644 --- a/evennia/commands/default/general.py +++ b/evennia/commands/default/general.py @@ -353,6 +353,8 @@ class CmdGive(COMMAND_DEFAULT_CLASS): caller.msg("You give %s to %s." % (to_give.key, target.key)) to_give.move_to(target, quiet=True) target.msg("%s gives you %s." % (caller.key, to_give.key)) + # Call the object script's at_give() method. + obj.at_give(caller, target) class CmdDesc(COMMAND_DEFAULT_CLASS): From c4519d84e7497c2f99fc0319985cbeff334b120f Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 25 Mar 2017 10:01:44 +0100 Subject: [PATCH 101/134] Remove spurious whitespace. --- evennia/objects/objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evennia/objects/objects.py b/evennia/objects/objects.py index cf90321692..4deeb12a83 100644 --- a/evennia/objects/objects.py +++ b/evennia/objects/objects.py @@ -1416,7 +1416,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): """ pass - + def at_give(self, giver, getter): """ Called by the default `give` command when this object has been From a019a9d65a7a9f400e4a90600bb04181bb664019 Mon Sep 17 00:00:00 2001 From: BlauFeuer Date: Thu, 23 Mar 2017 10:59:41 -0400 Subject: [PATCH 102/134] Docstring update suggestions More explicit installation instructions, and help entry update: The "forward" switch requires a message number. --- evennia/contrib/mail.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/evennia/contrib/mail.py b/evennia/contrib/mail.py index 4482542de2..a1eb7be1d1 100644 --- a/evennia/contrib/mail.py +++ b/evennia/contrib/mail.py @@ -6,7 +6,8 @@ Evennia Contribution - grungies1138 2016 A simple Brandymail style @mail system that uses the Msg class from Evennia Core. Installation: - import MailCommand from this module into the default Player or Character command set + import CmdMail from this module (from evennia.contrib.mail import CmdMail), + and add into the default Player or Character command set (self.add(CmdMail)). """ @@ -51,7 +52,7 @@ class CmdMail(default_cmds.MuxCommand): @mail 2 @mail Griatch=New mail/Hey man, I am sending you a message! @mail/delete 6 - @mail/forward feend78 Griatch=You guys should read this. + @mail/forward feend78 Griatch=4/You guys should read this. @mail/reply 9=Thanks for the info! """ key = "@mail" From 465a6336c5e5e6a6fa272c7b31dbb1b3ce3ba1f7 Mon Sep 17 00:00:00 2001 From: Vincent Le Goff Date: Thu, 23 Mar 2017 10:54:51 -0700 Subject: [PATCH 103/134] The TestCommand.call() now returns the received message --- evennia/commands/default/tests.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/evennia/commands/default/tests.py b/evennia/commands/default/tests.py index 1d09a56c70..78d9c748a3 100644 --- a/evennia/commands/default/tests.py +++ b/evennia/commands/default/tests.py @@ -48,6 +48,10 @@ class CommandTest(EvenniaTest): cmdobj.at_post_cmd() The msgreturn value is compared to eventual output sent to caller.msg in the game + + Returns: + msg (str): The received message that was sent to the caller. + """ caller = caller if caller else self.char1 receiver = receiver if receiver else caller @@ -61,6 +65,7 @@ class CommandTest(EvenniaTest): cmdobj.obj = obj or (caller if caller else self.char1) # test old_msg = receiver.msg + returned_msg = "" try: receiver.msg = Mock() cmdobj.at_pre_cmd() @@ -84,6 +89,8 @@ class CommandTest(EvenniaTest): finally: receiver.msg = old_msg + return returned_msg + # ------------------------------------------------------------ # Individual module Tests # ------------------------------------------------------------ From 8520d5f78c89b48de1e9fa36f070746f344b85ad Mon Sep 17 00:00:00 2001 From: Eldritch Semblance Date: Thu, 23 Mar 2017 19:13:11 +0000 Subject: [PATCH 104/134] Escape pipes in @gender usage, fix gender substitution regex to respect escaped pipes --- evennia/contrib/gendersub.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/evennia/contrib/gendersub.py b/evennia/contrib/gendersub.py index 2d3b13de86..40059d1813 100644 --- a/evennia/contrib/gendersub.py +++ b/evennia/contrib/gendersub.py @@ -50,7 +50,7 @@ _GENDER_PRONOUN_MAP = {"male": {"s": "he", "p": "their", "a": "theirs"} } -_RE_GENDER_PRONOUN = re.compile(r'(\|s|\|S|\|o|\|O|\|p|\|P|\|a|\|A)') +_RE_GENDER_PRONOUN = re.compile(r'(? Date: Thu, 23 Mar 2017 19:14:37 +0000 Subject: [PATCH 105/134] gender character message don't try to substitute text that lacks gender info --- evennia/contrib/gendersub.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/evennia/contrib/gendersub.py b/evennia/contrib/gendersub.py index 40059d1813..0160728fb2 100644 --- a/evennia/contrib/gendersub.py +++ b/evennia/contrib/gendersub.py @@ -134,5 +134,8 @@ class GenderCharacter(DefaultCharacter): """ # pre-process the text before continuing - text = _RE_GENDER_PRONOUN.sub(self._get_pronoun, text) + try: + text = _RE_GENDER_PRONOUN.sub(self._get_pronoun, text) + except TypeError: + pass super(GenderCharacter, self).msg(text, from_obj=from_obj, session=session, **kwargs) From b0ac80922998a8738c0454ab5b10a4c0cb267178 Mon Sep 17 00:00:00 2001 From: BlauFeuer Date: Thu, 23 Mar 2017 23:37:37 -0400 Subject: [PATCH 106/134] CmdLook use_dbref by Builders perm group #1251 Only allow referencing targets to view by #dbid if viewer is in Builders permission group --- evennia/commands/default/general.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/evennia/commands/default/general.py b/evennia/commands/default/general.py index 21dc70e57b..c55f591889 100644 --- a/evennia/commands/default/general.py +++ b/evennia/commands/default/general.py @@ -60,16 +60,17 @@ class CmdLook(COMMAND_DEFAULT_CLASS): """ Handle the looking. """ + caller = self.caller if not self.args: - target = self.caller.location + target = caller.location if not target: - self.caller.msg("You have no location to look at!") + caller.msg("You have no location to look at!") return else: - target = self.caller.search(self.args) + target = caller.search(self.args, use_dbref=caller.check_permstring("Builders")) if not target: return - self.msg(self.caller.at_look(target)) + self.msg(caller.at_look(target)) class CmdNick(COMMAND_DEFAULT_CLASS): From 338899b8b19b083956b5db7422207ef3e4af3b2b Mon Sep 17 00:00:00 2001 From: BlauFeuer Date: Fri, 24 Mar 2017 00:16:59 -0400 Subject: [PATCH 107/134] Docstring, comments, indent/whitespace edits --- evennia/objects/objects.py | 92 ++++++++++++++++++-------------------- 1 file changed, 43 insertions(+), 49 deletions(-) diff --git a/evennia/objects/objects.py b/evennia/objects/objects.py index 4deeb12a83..0a765fd07b 100644 --- a/evennia/objects/objects.py +++ b/evennia/objects/objects.py @@ -34,6 +34,7 @@ _SESSID_MAX = 16 if _MULTISESSION_MODE in (1, 3) else 1 from django.utils.translation import ugettext as _ + class ObjectSessionHandler(object): """ Handles the get/setting of the sessid @@ -133,7 +134,7 @@ class ObjectSessionHandler(object): Remove session from handler. Args: - sessid (Session or int): Session or session id to remove. + session (Session or int): Session or session id to remove. """ try: @@ -144,7 +145,7 @@ class ObjectSessionHandler(object): sessid_cache = self._sessid_cache if sessid in sessid_cache: sessid_cache.remove(sessid) - self.obj.db_sessid = ",".join(str(val) for val in sessid_cache) + self.obj.db_sessid = ",".join(str(val) for val in sessid_cache) self.obj.save(update_fields=["db_sessid"]) def clear(self): @@ -167,10 +168,9 @@ class ObjectSessionHandler(object): return len(self._sessid_cache) - # # Base class to inherit from. -# + class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): """ @@ -221,7 +221,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): """ return self.db_player and self.db_player.is_superuser \ - and not self.db_player.attributes.get("_quell") + and not self.db_player.attributes.get("_quell") def contents_get(self, exclude=None): """ @@ -241,7 +241,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): """ con = self.contents_cache.get(exclude=exclude) - #print "contents_get:", self, con, id(self), calledby() + # print "contents_get:", self, con, id(self), calledby() # DEBUG return con contents = property(contents_get) @@ -370,8 +370,8 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): # do nick-replacement on search searchdata = self.nicks.nickreplace(searchdata, categories=("object", "player"), include_player=True) - if(global_search or (is_string and searchdata.startswith("#") and - len(searchdata) > 1 and searchdata[1:].isdigit())): + if (global_search or (is_string and searchdata.startswith("#") and + len(searchdata) > 1 and searchdata[1:].isdigit())): # only allow exact matching if searching the entire database # or unique #dbrefs exact = True @@ -403,8 +403,8 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): use_dbref=use_dbref) if quiet: return results - return _AT_SEARCH_RESULT(results, self, query=searchdata, - nofound_string=nofound_string, multimatch_string=multimatch_string) + return _AT_SEARCH_RESULT(results, self, query=searchdata, + nofound_string=nofound_string, multimatch_string=multimatch_string) def search_player(self, searchdata, quiet=False): """ @@ -474,11 +474,9 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): # nick replacement - we require full-word matching. # do text encoding conversion raw_string = to_unicode(raw_string) - raw_string = self.nicks.nickreplace(raw_string, - categories=("inputline", "channel"), include_player=True) + raw_string = self.nicks.nickreplace(raw_string, categories=("inputline", "channel"), include_player=True) return cmdhandler.cmdhandler(self, raw_string, callertype="object", session=session, **kwargs) - def msg(self, text=None, from_obj=None, session=None, options=None, **kwargs): """ Emits something to a session attached to the object. @@ -593,9 +591,8 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): for obj in contents: if mapping: substitutions = {t: sub.get_display_name(obj) - if hasattr(sub, 'get_display_name') - else str(sub) - for t, sub in mapping.items()} + if hasattr(sub, 'get_display_name') + else str(sub) for t, sub in mapping.items()} obj.msg(message.format(**substitutions), from_obj=from_obj, **kwargs) else: obj.msg(message, from_obj=from_obj, **kwargs) @@ -643,7 +640,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): """ def logerr(string="", err=None): - "Simple log helper method" + """Simple log helper method""" logger.log_trace() self.msg("%s%s" % (string, "" if err is None else " (%s)" % err)) return @@ -685,7 +682,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): return False if not quiet: - #tell the old room we are leaving + # tell the old room we are leaving try: self.announce_move_from(destination) except Exception as err: @@ -705,7 +702,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): self.announce_move_to(source_location) except Exception as err: logerr(errtxt % "announce_move_to()", err) - return False + return False if move_hooks: # Perform eventual extra commands on the receiving location @@ -780,7 +777,6 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): obj.msg(_(string)) obj.move_to(home) - def copy(self, new_key=None): """ Makes an identical copy of this object, identical except for a @@ -804,15 +800,15 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): """ key = self.key num = 1 - for obj in (obj for obj in self.location.contents - if obj.key.startswith(key) and - obj.key.lstrip(key).isdigit()): + for _ in (obj for obj in self.location.contents + if obj.key.startswith(key) and obj.key.lstrip(key).isdigit()): num += 1 return "%s%03i" % (key, num) new_key = new_key or find_clone_key() return ObjectDB.objects.copy_object(self, new_key=new_key) delete_iter = 0 + def delete(self): """ Deletes this object. Before deletion, this method makes sure @@ -863,7 +859,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): self.attributes.clear() self.nicks.clear() self.aliases.clear() - self.location = None # this updates contents_cache for our location + self.location = None # this updates contents_cache for our location # Perform the deletion of the object super(DefaultObject, self).delete() @@ -956,8 +952,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): self.basetype_posthook_setup() - - ## hooks called by the game engine + # hooks called by the game engine # def basetype_setup(self): """ @@ -974,15 +969,15 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): # controller, for example) self.locks.add(";".join([ - "control:perm(Immortals)", # edit locks/permissions, delete - "examine:perm(Builders)", # examine properties - "view:all()", # look at object (visibility) - "edit:perm(Wizards)", # edit properties/attributes - "delete:perm(Wizards)", # delete object - "get:all()", # pick up object - "call:true()", # allow to call commands on this object - "tell:perm(Wizards)", # allow emits to this object - "puppet:pperm(Immortals)"])) # lock down puppeting only to staff by default + "control:perm(Immortals)", # edit locks/permissions, delete + "examine:perm(Builders)", # examine properties + "view:all()", # look at object (visibility) + "edit:perm(Wizards)", # edit properties/attributes + "delete:perm(Wizards)", # delete object + "get:all()", # pick up object + "call:true()", # allow to call commands on this object + "tell:perm(Wizards)", # allow emits to this object + "puppet:pperm(Immortals)"])) # lock down puppeting only to staff by default def basetype_posthook_setup(self): """ @@ -1119,9 +1114,8 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): Args: result (bool): The outcome of the access call. - accessing_obj (Object or Player): The entity trying to - gain access. access_type (str): The type of access that - was requested. + accessing_obj (Object or Player): The entity trying to gain access. + access_type (str): The type of access that was requested. Kwargs: Not used by default, added for possible expandability in a @@ -1141,14 +1135,14 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): destination (Object): The object we are moving to Returns: - shouldmove (bool): If we should move or not. + (bool): If we should move or not. Notes: If this method returns False/None, the move is cancelled before it is even started. """ - #return has_perm(self, destination, "can_move") + # return has_perm(self, destination, "can_move") return True def announce_move_from(self, destination): @@ -1289,7 +1283,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): check for this. . Consider this a pre-processing method before msg is passed on - to the user sesssion. If this method returns False, the msg + to the user session. If this method returns False, the msg will not be passed on. Args: @@ -1342,7 +1336,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): return "" # get and identify all objects visible = (con for con in self.contents if con != looker and - con.access(looker, "view")) + con.access(looker, "view")) exits, users, things = [], [], [] for con in visible: key = con.get_display_name(looker) @@ -1490,7 +1484,7 @@ class DefaultCharacter(DefaultObject): """ super(DefaultCharacter, self).basetype_setup() self.locks.add(";".join(["get:false()", # noone can pick up the character - "call:false()"])) # no commands can be called on character from outside + "call:false()"])) # no commands can be called on character from outside # add the default cmdset self.cmdset.add_default(settings.CMDSET_CHARACTER, permanent=True) @@ -1582,7 +1576,7 @@ class DefaultCharacter(DefaultObject): # # Base Room object -# + class DefaultRoom(DefaultObject): """ @@ -1598,7 +1592,7 @@ class DefaultRoom(DefaultObject): super(DefaultRoom, self).basetype_setup() self.locks.add(";".join(["get:false()", - "puppet:false()"])) # would be weird to puppet a room ... + "puppet:false()"])) # would be weird to puppet a room ... self.location = None @@ -1648,7 +1642,7 @@ class ExitCommand(command.Command): # # Base Exit object -# + class DefaultExit(DefaultObject): """ @@ -1700,8 +1694,8 @@ class DefaultExit(DefaultObject): exit_cmdset.add(cmd) return exit_cmdset - # Command hooks + def basetype_setup(self): """ Setup exit-security @@ -1713,8 +1707,8 @@ class DefaultExit(DefaultObject): super(DefaultExit, self).basetype_setup() # setting default locks (overload these in at_object_creation() - self.locks.add(";".join(["puppet:false()", # would be weird to puppet an exit ... - "traverse:all()", # who can pass through exit by default + self.locks.add(";".join(["puppet:false()", # would be weird to puppet an exit ... + "traverse:all()", # who can pass through exit by default "get:false()"])) # noone can pick up the exit # an exit should have a destination (this is replaced at creation time) From 99a24a9647c6661cfecec5151de070e96d8b4761 Mon Sep 17 00:00:00 2001 From: BlauFeuer Date: Fri, 24 Mar 2017 00:20:07 -0400 Subject: [PATCH 108/134] Docstring, comment, indent/whitespace edits use of dict() to improve dictionary readability and shorten code --- evennia/utils/create.py | 48 +++++++++++++++++------------------------ 1 file changed, 20 insertions(+), 28 deletions(-) diff --git a/evennia/utils/create.py b/evennia/utils/create.py index 0ba36cefa9..fe5aa4eaa3 100644 --- a/evennia/utils/create.py +++ b/evennia/utils/create.py @@ -50,11 +50,11 @@ _GA = object.__getattribute__ # # Game Object creation -# -def create_object(typeclass=None, key=None, location=None, - home=None, permissions=None, locks=None, - aliases=None, tags=None, destination=None, report_to=None, nohome=False): + +def create_object(typeclass=None, key=None, location=None, home=None, + permissions=None, locks=None, aliases=None, tags=None, + destination=None, report_to=None, nohome=False): """ Create a new in-game object. @@ -110,26 +110,24 @@ def create_object(typeclass=None, key=None, location=None, # create new instance new_object = typeclass(db_key=key, db_location=location, - db_destination=destination, db_home=home, - db_typeclass_path=typeclass.path) + db_destination=destination, db_home=home, + db_typeclass_path=typeclass.path) # store the call signature for the signal - new_object._createdict = {"key":key, "location":location, "destination":destination, - "home":home, "typeclass":typeclass.path, "permissions":permissions, - "locks":locks, "aliases":aliases, "tags": tags, - "report_to":report_to, "nohome":nohome} + new_object._createdict = dict(key=key, location=location, destination=destination, home=home, + typeclass=typeclass.path, permissions=permissions, locks=locks, + aliases=aliases, tags=tags, report_to=report_to, nohome=nohome) # this will trigger the save signal which in turn calls the # at_first_save hook on the typeclass, where the _createdict can be # used. new_object.save() return new_object -#alias for create_object +# alias for create_object object = create_object # # Script creation -# def create_script(typeclass=None, key=None, obj=None, player=None, locks=None, interval=None, start_delay=None, repeats=None, @@ -194,19 +192,16 @@ def create_script(typeclass=None, key=None, obj=None, player=None, locks=None, new_script = typeclass(**kwarg) # store the call signature for the signal - new_script._createdict = {"key":key, "obj":obj, "player":player, - "locks":locks, "interval":interval, - "start_delay":start_delay, "repeats":repeats, - "persistent":persistent, "autostart":autostart, - "report_to":report_to} - + new_script._createdict = dict(key=key, obj=obj, player=player, locks=locks, interval=interval, + start_delay=start_delay, repeats=repeats, persistent=persistent, + autostart=autostart, report_to=report_to) # this will trigger the save signal which in turn calls the # at_first_save hook on the typeclass, where the _createdict # can be used. new_script.save() return new_script -#alias +# alias script = create_script @@ -227,6 +222,7 @@ def create_help_entry(key, entrytext, category="General", locks=None, aliases=No entrytext (str): The body of te help entry category (str, optional): The help category of the entry. locks (str, optional): A lockstring to restrict access. + aliases (list of str): List of alternative (likely shorter) keynames. Returns: help (HelpEntry): A newly created help entry. @@ -260,10 +256,8 @@ help_entry = create_help_entry # # Comm system methods -# -def create_message(senderobj, message, channels=None, - receivers=None, locks=None, header=None): +def create_message(senderobj, message, channels=None, receivers=None, locks=None, header=None): """ Create a new communication Msg. Msgs represent a unit of database-persistent communication between entites. @@ -325,7 +319,7 @@ def create_channel(key, aliases=None, desc=None, key (str): This must be unique. Kwargs: - aliases (list): List of alternative (likely shorter) keynames. + aliases (list of str): List of alternative (likely shorter) keynames. desc (str): A description of the channel, for use in listings. locks (str): Lockstring. keep_log (bool): Log channel throughput. @@ -346,8 +340,7 @@ def create_channel(key, aliases=None, desc=None, new_channel = typeclass(db_key=key) # store call signature for the signal - new_channel._createdict = {"key":key, "aliases":aliases, - "desc":desc, "locks":locks, "keep_log":keep_log} + new_channel._createdict = dict(key=key, aliases=aliases, desc=desc, locks=locks, keep_log=keep_log) # this will trigger the save signal which in turn calls the # at_first_save hook on the typeclass, where the _createdict can be @@ -358,11 +351,11 @@ def create_channel(key, aliases=None, desc=None, channel = create_channel - # # Player creation methods # + def create_player(key, email, password, typeclass=None, is_superuser=False, @@ -426,8 +419,7 @@ def create_player(key, email, password, is_staff=is_superuser, is_superuser=is_superuser, last_login=now, date_joined=now) new_player.set_password(password) - new_player._createdict = {"locks":locks, "permissions":permissions, - "report_to":report_to} + new_player._createdict = dict(locks=locks, permissions=permissions, report_to=report_to) # saving will trigger the signal that calls the # at_first_save hook on the typeclass, where the _createdict # can be used. From 6266401c2d4fe048330295e8c312a4993f67424b Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 25 Mar 2017 11:40:55 +0100 Subject: [PATCH 109/134] Re-add bool return name, it is required by API generator. --- evennia/objects/objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evennia/objects/objects.py b/evennia/objects/objects.py index 0a765fd07b..d866f2e9af 100644 --- a/evennia/objects/objects.py +++ b/evennia/objects/objects.py @@ -1135,7 +1135,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): destination (Object): The object we are moving to Returns: - (bool): If we should move or not. + shouldmove (bool): If we should move or not. Notes: If this method returns False/None, the move is cancelled From 66f52292402ac8ebfd9ae1de8fa7919c46a47572 Mon Sep 17 00:00:00 2001 From: Eldritch Semblance Date: Sat, 25 Mar 2017 03:41:39 +0000 Subject: [PATCH 110/134] change reference of self.caller.player to self.player --- evennia/commands/default/help.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evennia/commands/default/help.py b/evennia/commands/default/help.py index e2113be688..4590ab74c6 100644 --- a/evennia/commands/default/help.py +++ b/evennia/commands/default/help.py @@ -60,7 +60,7 @@ class CmdHelp(Command): if self.session.protocol_key in ("websocket", "ajax/comet"): try: - options = self.caller.player.db._saved_webclient_options + options = self.player.db._saved_webclient_options if options and options["helppopup"]: usemore = False except KeyError: From 4c4b97f994c6ef4da26361c8b096de37db97caa9 Mon Sep 17 00:00:00 2001 From: Vincent Le Goff Date: Sat, 25 Mar 2017 13:08:59 -0700 Subject: [PATCH 111/134] Make sure CommandTest.call() returns the received message as is --- evennia/commands/default/tests.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/evennia/commands/default/tests.py b/evennia/commands/default/tests.py index 78d9c748a3..39f2f8841f 100644 --- a/evennia/commands/default/tests.py +++ b/evennia/commands/default/tests.py @@ -77,15 +77,18 @@ class CommandTest(EvenniaTest): for name, args, kwargs in receiver.msg.mock_calls] # Get the first element of a tuple if msg received a tuple instead of a string stored_msg = [smsg[0] if isinstance(smsg, tuple) else smsg for smsg in stored_msg] - returned_msg = "||".join(_RE.sub("", mess) for mess in stored_msg) - returned_msg = ansi.parse_ansi(returned_msg, strip_ansi=noansi).strip() if msg is not None: + returned_msg = "||".join(_RE.sub("", mess) for mess in stored_msg) + returned_msg = ansi.parse_ansi(returned_msg, strip_ansi=noansi).strip() if msg == "" and returned_msg or not returned_msg.startswith(msg.strip()): sep1 = "\n" + "="*30 + "Wanted message" + "="*34 + "\n" sep2 = "\n" + "="*30 + "Returned message" + "="*32 + "\n" sep3 = "\n" + "="*78 retval = sep1 + msg.strip() + sep2 + returned_msg + sep3 raise AssertionError(retval) + else: + returned_msg = "\n".join(stored_msg) + returned_msg = ansi.parse_ansi(returned_msg, strip_ansi=noansi).strip() finally: receiver.msg = old_msg From 12e0e2e4fd18dee5c6383900c381eb1e3ea50b95 Mon Sep 17 00:00:00 2001 From: Xelaadryth Date: Sun, 26 Mar 2017 20:42:03 -0700 Subject: [PATCH 112/134] Fixing typo of local variable --- evennia/commands/default/general.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evennia/commands/default/general.py b/evennia/commands/default/general.py index c55f591889..5070915555 100644 --- a/evennia/commands/default/general.py +++ b/evennia/commands/default/general.py @@ -355,7 +355,7 @@ class CmdGive(COMMAND_DEFAULT_CLASS): to_give.move_to(target, quiet=True) target.msg("%s gives you %s." % (caller.key, to_give.key)) # Call the object script's at_give() method. - obj.at_give(caller, target) + to_give.at_give(caller, target) class CmdDesc(COMMAND_DEFAULT_CLASS): From 1ed559a71cff01364b14c21e5a6875cf78700bc1 Mon Sep 17 00:00:00 2001 From: eldritchsemblance Date: Mon, 27 Mar 2017 00:03:51 -0400 Subject: [PATCH 113/134] Default email domain to example.com per RFC2606 --- evennia/utils/create.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/evennia/utils/create.py b/evennia/utils/create.py index fe5aa4eaa3..7445fa20a5 100644 --- a/evennia/utils/create.py +++ b/evennia/utils/create.py @@ -369,7 +369,7 @@ def create_player(key, email, password, key (str): The player's name. This should be unique. email (str): Email on valid addr@addr.domain form. This is technically required but if set to `None`, an email of - `dummy@dummy.com` will be used as a placeholder. + `dummy@example.com` will be used as a placeholder. password (str): Password in cleartext. Kwargs: @@ -404,7 +404,7 @@ def create_player(key, email, password, # correctly when each object is recovered). if not email: - email = "dummy@dummy.com" + email = "dummy@example.com" if _PlayerDB.objects.filter(username__iexact=key): raise ValueError("A Player with the name '%s' already exists." % key) From 6f30ab09f54ac62725b8c4f9c1c7a24e6660b646 Mon Sep 17 00:00:00 2001 From: Griatch Date: Mon, 27 Mar 2017 22:37:03 +0200 Subject: [PATCH 114/134] Check slow exit deferred for .called state to avoid edge case errback. Resolves #1278. --- evennia/contrib/slow_exit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evennia/contrib/slow_exit.py b/evennia/contrib/slow_exit.py index 0a1097ed42..f8216c4d93 100644 --- a/evennia/contrib/slow_exit.py +++ b/evennia/contrib/slow_exit.py @@ -135,7 +135,7 @@ class CmdStop(Command): stored deferred from the exit traversal above. """ currently_moving = self.caller.ndb.currently_moving - if currently_moving: + if currently_moving and not currently_moving.called: currently_moving.cancel() self.caller.msg("You stop moving.") for observer in self.caller.location.contents_get(self.caller): From 21e72e416c11d30cd86065be6e81c75a25387abe Mon Sep 17 00:00:00 2001 From: Griatch Date: Mon, 27 Mar 2017 22:40:40 +0200 Subject: [PATCH 115/134] Fix error in more edge calculation when using client with SCREENWIDTH=0. Resolves #1257. --- evennia/utils/evmore.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evennia/utils/evmore.py b/evennia/utils/evmore.py index 808be6b3fb..6662d22411 100644 --- a/evennia/utils/evmore.py +++ b/evennia/utils/evmore.py @@ -181,7 +181,7 @@ class EvMore(object): lines.append(line) # always limit number of chars to 10 000 per page - height = min(10000 // width, height) + height = min(10000 // max(1, width), height) self._pages = ["\n".join(lines[i:i+height]) for i in range(0, len(lines), height)] self._npages = len(self._pages) From a4e2744821977aca703d6e140d81ba3104f50b06 Mon Sep 17 00:00:00 2001 From: Eldritch Semblance Date: Wed, 29 Mar 2017 02:46:44 +0000 Subject: [PATCH 116/134] check if ooc or puppeting before finding player object --- evennia/contrib/mail.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/evennia/contrib/mail.py b/evennia/contrib/mail.py index a1eb7be1d1..d817b07125 100644 --- a/evennia/contrib/mail.py +++ b/evennia/contrib/mail.py @@ -88,7 +88,11 @@ class CmdMail(default_cmds.MuxCommand): """ # mail_messages = Msg.objects.get_by_tag(category="mail") # messages = [] - messages = Msg.objects.get_by_tag(category="mail", raw_queryset=True).filter(db_receivers_players=self.caller) + try: + player = self.caller.player + except AttributeError: + player = self.caller + messages = Msg.objects.get_by_tag(category="mail", raw_queryset=True).filter(db_receivers_players=player) return messages def send_mail(self, recipients, subject, message, caller): From 534f4bcbe1c471faa0d0560cfdc0d9443d507315 Mon Sep 17 00:00:00 2001 From: Eldritch Semblance Date: Wed, 29 Mar 2017 02:52:46 +0000 Subject: [PATCH 117/134] Removed insulting no mail message --- evennia/contrib/mail.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evennia/contrib/mail.py b/evennia/contrib/mail.py index d817b07125..176229fe85 100644 --- a/evennia/contrib/mail.py +++ b/evennia/contrib/mail.py @@ -252,5 +252,5 @@ class CmdMail(default_cmds.MuxCommand): self.caller.msg(unicode(table)) self.caller.msg(_HEAD_CHAR * _WIDTH) else: - self.caller.msg("Sorry, you don't have any messages. What a pathetic loser!") + self.caller.msg("There are no messages in your inbox.") From 6224f90f74d879bbfc4127833a5ed9362f45e8c8 Mon Sep 17 00:00:00 2001 From: Eldritch Semblance Date: Wed, 29 Mar 2017 02:57:09 +0000 Subject: [PATCH 118/134] Changed unit test's expected text output --- evennia/contrib/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evennia/contrib/tests.py b/evennia/contrib/tests.py index f831489fb9..62f71281bb 100644 --- a/evennia/contrib/tests.py +++ b/evennia/contrib/tests.py @@ -540,7 +540,7 @@ from evennia.contrib import mail class TestMail(CommandTest): def test_mail(self): self.call(mail.CmdMail(), "2", "'2' is not a valid mail id.", caller=self.player) - self.call(mail.CmdMail(), "", "Sorry, you don't have any messages.", caller=self.player) + self.call(mail.CmdMail(), "", "There are no messages in your inbox.", caller=self.player) self.call(mail.CmdMail(), "Char=Message 1", "You have received a new @mail from Char|You sent your message.", caller=self.char1) self.call(mail.CmdMail(), "Char=Message 2", "You sent your message.", caller=self.char2) self.call(mail.CmdMail(), "TestPlayer2=Message 2", From 6cfbec83f1d3fb5720dac79583dee9a85f21c75d Mon Sep 17 00:00:00 2001 From: CloudKeeper1 Date: Tue, 7 Mar 2017 21:58:07 +1100 Subject: [PATCH 119/134] Inclusion of type kwarg for common communications Including a type kwarg for the say, whisper and pose commands allows the receivers of those messages to treat them differently depending on type. IMO This is a wide enough concern that it goes beyond a game specific requirement. --- evennia/commands/default/general.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/evennia/commands/default/general.py b/evennia/commands/default/general.py index 5070915555..8bee6e8d4b 100644 --- a/evennia/commands/default/general.py +++ b/evennia/commands/default/general.py @@ -417,7 +417,7 @@ class CmdSay(COMMAND_DEFAULT_CLASS): # Build the string to emit to neighbors. emit_string = '%s says, "%s|n"' % (caller.name, speech) - caller.location.msg_contents(emit_string, exclude=caller, from_obj=caller) + caller.location.msg_contents(emit_string, exclude=caller, from_obj=caller, type="say") class CmdWhisper(COMMAND_DEFAULT_CLASS): @@ -459,7 +459,7 @@ class CmdWhisper(COMMAND_DEFAULT_CLASS): # Build the string to emit to receiver. emit_string = '%s whispers, "%s|n"' % (caller.name, speech) - receiver.msg(emit_string, from_obj=caller) + receiver.msg(emit_string, from_obj=caller, type="whisper") class CmdPose(COMMAND_DEFAULT_CLASS): @@ -502,7 +502,7 @@ class CmdPose(COMMAND_DEFAULT_CLASS): self.caller.msg(msg) else: msg = "%s%s" % (self.caller.name, self.args) - self.caller.location.msg_contents(msg, from_obj=self.caller) + self.caller.location.msg_contents(msg, from_obj=self.caller, type="pose") class CmdAccess(COMMAND_DEFAULT_CLASS): From 375cd7cecb5df91d692e8831da891578bd7c545e Mon Sep 17 00:00:00 2001 From: CloudKeeper1 Date: Thu, 9 Mar 2017 12:09:39 +1100 Subject: [PATCH 120/134] Changed kwarg to options entry Due to the set up of sessionhandler.clean_senddata the information is more appropriately stored in options. --- evennia/commands/default/general.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/evennia/commands/default/general.py b/evennia/commands/default/general.py index 8bee6e8d4b..2dc4312432 100644 --- a/evennia/commands/default/general.py +++ b/evennia/commands/default/general.py @@ -417,7 +417,8 @@ class CmdSay(COMMAND_DEFAULT_CLASS): # Build the string to emit to neighbors. emit_string = '%s says, "%s|n"' % (caller.name, speech) - caller.location.msg_contents(emit_string, exclude=caller, from_obj=caller, type="say") + caller.location.msg_contents(emit_string, exclude=caller, + from_obj=caller, options={"type": "say"}) class CmdWhisper(COMMAND_DEFAULT_CLASS): @@ -459,7 +460,7 @@ class CmdWhisper(COMMAND_DEFAULT_CLASS): # Build the string to emit to receiver. emit_string = '%s whispers, "%s|n"' % (caller.name, speech) - receiver.msg(emit_string, from_obj=caller, type="whisper") + receiver.msg(emit_string, from_obj=caller, options={"type": "whisper"}) class CmdPose(COMMAND_DEFAULT_CLASS): @@ -502,7 +503,8 @@ class CmdPose(COMMAND_DEFAULT_CLASS): self.caller.msg(msg) else: msg = "%s%s" % (self.caller.name, self.args) - self.caller.location.msg_contents(msg, from_obj=self.caller, type="pose") + self.caller.location.msg_contents(msg, from_obj=self.caller, + options={"type": "pose"}) class CmdAccess(COMMAND_DEFAULT_CLASS): From 344a7244795405714b39d6db0597547355ec9bde Mon Sep 17 00:00:00 2001 From: CloudKeeper1 Date: Sun, 26 Mar 2017 13:58:10 +1100 Subject: [PATCH 121/134] Changed option entry to msg output kwarg. Griatch - "That way it can still be parsed by the protocol if so needed, but it is also be passed on so the client can do custom stuff with it if it can." --- evennia/commands/default/general.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/evennia/commands/default/general.py b/evennia/commands/default/general.py index 2dc4312432..e6f0a5f7ed 100644 --- a/evennia/commands/default/general.py +++ b/evennia/commands/default/general.py @@ -417,8 +417,8 @@ class CmdSay(COMMAND_DEFAULT_CLASS): # Build the string to emit to neighbors. emit_string = '%s says, "%s|n"' % (caller.name, speech) - caller.location.msg_contents(emit_string, exclude=caller, - from_obj=caller, options={"type": "say"}) + caller.location.msg_contents(text=(emit_string, {"type": "say"}), + exclude=caller, from_obj=caller) class CmdWhisper(COMMAND_DEFAULT_CLASS): @@ -460,7 +460,7 @@ class CmdWhisper(COMMAND_DEFAULT_CLASS): # Build the string to emit to receiver. emit_string = '%s whispers, "%s|n"' % (caller.name, speech) - receiver.msg(emit_string, from_obj=caller, options={"type": "whisper"}) + receiver.msg(text=(emit_string, {"type": "Whisper"}), from_obj=caller) class CmdPose(COMMAND_DEFAULT_CLASS): @@ -503,8 +503,8 @@ class CmdPose(COMMAND_DEFAULT_CLASS): self.caller.msg(msg) else: msg = "%s%s" % (self.caller.name, self.args) - self.caller.location.msg_contents(msg, from_obj=self.caller, - options={"type": "pose"}) + self.caller.location.msg_contents(text=(msg, {"type": "pose"}), + from_obj=self.caller) class CmdAccess(COMMAND_DEFAULT_CLASS): From 4bbba7615509945f90c61094f7b503ec5893cc76 Mon Sep 17 00:00:00 2001 From: CloudKeeper1 Date: Tue, 28 Mar 2017 21:21:33 +1100 Subject: [PATCH 122/134] msg_contents doesn't take the text keyword in arguments. Adjusted code so it works now... --- evennia/commands/default/general.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/evennia/commands/default/general.py b/evennia/commands/default/general.py index e6f0a5f7ed..f6323fda13 100644 --- a/evennia/commands/default/general.py +++ b/evennia/commands/default/general.py @@ -417,7 +417,7 @@ class CmdSay(COMMAND_DEFAULT_CLASS): # Build the string to emit to neighbors. emit_string = '%s says, "%s|n"' % (caller.name, speech) - caller.location.msg_contents(text=(emit_string, {"type": "say"}), + caller.location.msg_contents((emit_string, {"type": "say"}), exclude=caller, from_obj=caller) @@ -460,7 +460,7 @@ class CmdWhisper(COMMAND_DEFAULT_CLASS): # Build the string to emit to receiver. emit_string = '%s whispers, "%s|n"' % (caller.name, speech) - receiver.msg(text=(emit_string, {"type": "Whisper"}), from_obj=caller) + receiver.msg((emit_string, {"type": "Whisper"}), from_obj=caller) class CmdPose(COMMAND_DEFAULT_CLASS): @@ -503,7 +503,7 @@ class CmdPose(COMMAND_DEFAULT_CLASS): self.caller.msg(msg) else: msg = "%s%s" % (self.caller.name, self.args) - self.caller.location.msg_contents(text=(msg, {"type": "pose"}), + self.caller.location.msg_contents((msg, {"type": "pose"}), from_obj=self.caller) From c12306ff726b4f6f12c893d14c26506764f073d1 Mon Sep 17 00:00:00 2001 From: CloudKeeper1 Date: Tue, 28 Mar 2017 21:36:33 +1100 Subject: [PATCH 123/134] Adjustments to argument capitalisation. Adjustments to argument capitalisation. --- evennia/commands/default/general.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/evennia/commands/default/general.py b/evennia/commands/default/general.py index f6323fda13..f72b3968d4 100644 --- a/evennia/commands/default/general.py +++ b/evennia/commands/default/general.py @@ -417,7 +417,7 @@ class CmdSay(COMMAND_DEFAULT_CLASS): # Build the string to emit to neighbors. emit_string = '%s says, "%s|n"' % (caller.name, speech) - caller.location.msg_contents((emit_string, {"type": "say"}), + caller.location.msg_contents((emit_string, {"type": "Say"}), exclude=caller, from_obj=caller) @@ -503,7 +503,7 @@ class CmdPose(COMMAND_DEFAULT_CLASS): self.caller.msg(msg) else: msg = "%s%s" % (self.caller.name, self.args) - self.caller.location.msg_contents((msg, {"type": "pose"}), + self.caller.location.msg_contents((msg, {"type": "Pose"}), from_obj=self.caller) From f6e230f1a6be1283066896ed9c6494ea2d9bd379 Mon Sep 17 00:00:00 2001 From: Griatch Date: Wed, 29 Mar 2017 21:50:51 +0200 Subject: [PATCH 124/134] Change text optional kwarg type name to lowercase to match all other communications variables. Expand msg_contents to handle a tuple. --- evennia/commands/default/general.py | 6 ++-- evennia/objects/objects.py | 43 ++++++++++++++++++++--------- 2 files changed, 33 insertions(+), 16 deletions(-) diff --git a/evennia/commands/default/general.py b/evennia/commands/default/general.py index f72b3968d4..18bd5d0dde 100644 --- a/evennia/commands/default/general.py +++ b/evennia/commands/default/general.py @@ -417,7 +417,7 @@ class CmdSay(COMMAND_DEFAULT_CLASS): # Build the string to emit to neighbors. emit_string = '%s says, "%s|n"' % (caller.name, speech) - caller.location.msg_contents((emit_string, {"type": "Say"}), + caller.location.msg_contents(text=(emit_string, {"type": "say"}), exclude=caller, from_obj=caller) @@ -460,7 +460,7 @@ class CmdWhisper(COMMAND_DEFAULT_CLASS): # Build the string to emit to receiver. emit_string = '%s whispers, "%s|n"' % (caller.name, speech) - receiver.msg((emit_string, {"type": "Whisper"}), from_obj=caller) + receiver.msg(text=(emit_string, {"type": "whisper"}), from_obj=caller) class CmdPose(COMMAND_DEFAULT_CLASS): @@ -503,7 +503,7 @@ class CmdPose(COMMAND_DEFAULT_CLASS): self.caller.msg(msg) else: msg = "%s%s" % (self.caller.name, self.args) - self.caller.location.msg_contents((msg, {"type": "Pose"}), + self.caller.location.msg_contents(text=(msg, {"type": "pose"}), from_obj=self.caller) diff --git a/evennia/objects/objects.py b/evennia/objects/objects.py index d866f2e9af..f0508bd18f 100644 --- a/evennia/objects/objects.py +++ b/evennia/objects/objects.py @@ -21,7 +21,7 @@ from evennia.commands.cmdsethandler import CmdSetHandler from evennia.commands import cmdhandler from evennia.utils import logger from evennia.utils.utils import (variable_from_module, lazy_property, - make_iter, to_unicode, calledby) + make_iter, to_unicode, calledby, is_iter) _MULTISESSION_MODE = settings.MULTISESSION_MODE @@ -547,21 +547,28 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): for obj in contents: func(obj, **kwargs) - def msg_contents(self, message, exclude=None, from_obj=None, mapping=None, **kwargs): + def msg_contents(self, text=None, exclude=None, from_obj=None, mapping=None, **kwargs): """ Emits a message to all objects inside this object. - Args: - message (str): Message to send. + Argsu: + text (str or tuple): Message to send. If a tuple, this should be + on the valid OOB outmessage form `(message, {kwargs})`, + where kwargs are optional data passed to the `text` + outputfunc. exclude (list, optional): A list of objects not to send to. from_obj (Object, optional): An object designated as the "sender" of the message. See `DefaultObject.msg()` for more info. mapping (dict, optional): A mapping of formatting keys `{"key":, "key2":,...}. The keys - must match `{key}` markers in `message` and will be + must match `{key}` markers in the `text` if this is a string or + in the internal `message` if `text` is a tuple. These + formatting statements will be replaced by the return of `.get_display_name(looker)` - for every looker that is messaged. + for every looker in contents that receives the + message. This allows for every object to potentially + get its own customized string. Kwargs: Keyword arguments will be passed on to `obj.msg()` for all messaged objects. @@ -575,15 +582,23 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): not have `get_display_name()`, its string value will be used. Example: - Say char is a Character object and npc is an NPC object: + Say Char is a Character object and Npc is an NPC object: - action = 'kicks' char.location.msg_contents( - "{attacker} {action} {defender}", - mapping=dict(attacker=char, defender=npc, action=action), - exclude=(char, npc)) + "{attacker} kicks {defender}", + mapping=dict(attacker=char, defender=npc), exclude=(char, npc)) + + This will result in everyone in the room seeing 'Char kicks NPC' + where everyone may potentially see different results for Char and Npc + depending on the results of `char.get_display_name(looker)` and + `npc.get_display_name(looker)` for each particular onlooker """ + # we also accept an outcommand on the form (message, {kwargs}) + is_outcmd = text and is_iter(text) + inmessage = text[0] if is_outcmd else text + outkwargs = text[1] if is_outcmd and len(text) > 1 else {} + contents = self.contents if exclude: exclude = make_iter(exclude) @@ -593,9 +608,11 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): substitutions = {t: sub.get_display_name(obj) if hasattr(sub, 'get_display_name') else str(sub) for t, sub in mapping.items()} - obj.msg(message.format(**substitutions), from_obj=from_obj, **kwargs) + outmessage = inmessage.format(**substitutions) else: - obj.msg(message, from_obj=from_obj, **kwargs) + outmessage = inmessage + + obj.msg(text=((outmessage,), outkwargs), from_obj=from_obj, **kwargs) def move_to(self, destination, quiet=False, emit_to_obj=None, use_destination=True, to_none=False, move_hooks=True): From d811d7577ec6760c0406285aeab0190edbf66539 Mon Sep 17 00:00:00 2001 From: Griatch Date: Wed, 29 Mar 2017 22:00:27 +0200 Subject: [PATCH 125/134] Fix unittest compliance. --- evennia/objects/objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evennia/objects/objects.py b/evennia/objects/objects.py index f0508bd18f..96539afce8 100644 --- a/evennia/objects/objects.py +++ b/evennia/objects/objects.py @@ -612,7 +612,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): else: outmessage = inmessage - obj.msg(text=((outmessage,), outkwargs), from_obj=from_obj, **kwargs) + obj.msg(text=(outmessage, outkwargs), from_obj=from_obj, **kwargs) def move_to(self, destination, quiet=False, emit_to_obj=None, use_destination=True, to_none=False, move_hooks=True): From 4f41582127f85e06d246ccd88a84f24dd33689e7 Mon Sep 17 00:00:00 2001 From: Vincent Le Goff Date: Mon, 27 Mar 2017 17:04:31 -0700 Subject: [PATCH 126/134] Update Object.announce_move_* to take advantage of mapping --- evennia/objects/objects.py | 73 +++++++++++++++++++++++++++++--------- 1 file changed, 57 insertions(+), 16 deletions(-) diff --git a/evennia/objects/objects.py b/evennia/objects/objects.py index 96539afce8..23eb55aef8 100644 --- a/evennia/objects/objects.py +++ b/evennia/objects/objects.py @@ -1162,7 +1162,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): # return has_perm(self, destination, "can_move") return True - def announce_move_from(self, destination): + def announce_move_from(self, destination, msg=None): """ Called if the move is to be announced. This is called while we are still standing in the old @@ -1170,25 +1170,51 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): Args: destination (Object): The place we are going to. + msg (str, optional): a replacement message. + + You can override this method and call its parent with a + message to simply change the default message. In the string, + you can use the following as mappings (between braces): + character: the character who is moving. + exit: the exit from which the character is moving (if found). + origin: the location of the character before the move. + destination: the location of the character after moving. """ if not self.location: return - string = "%s is leaving %s, heading for %s." - location = self.location - for obj in self.location.contents: - if obj != self: - obj.msg(string % (self.get_display_name(obj), - location.get_display_name(obj) if location else "nowhere", - destination.get_display_name(obj))) + if msg: + string = msg + else: + string = "{character} is leaving {origin}, heading for {destination}." - def announce_move_to(self, source_location): + location = self.location + exits = [o for o in location.contents if o.location is location and o.destination is destination] + mapping = { + "character": self, + "exit": exits[0] if exits else "somwhere", + "origin": location or "nowhere", + "destination": destination or "nowhere", + } + + location.msg_contents(string, exclude=(self, ), mapping=mapping) + + def announce_move_to(self, source_location, msg=None): """ Called after the move if the move was not quiet. At this point we are standing in the new location. Args: source_location (Object): The place we came from + msg (str, optional): the replacement message if location. + + You can override this method and call its parent with a + message to simply change the default message. In the string, + you can use the following as mappings (between braces): + character: the character who is moving. + exit: the exit from which the character is moving (if found). + origin: the location of the character before the move. + destination: the location of the character after moving. """ @@ -1199,13 +1225,28 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): self.location.msg(string) return - string = "%s arrives to %s%s." - location = self.location - for obj in self.location.contents: - if obj != self: - obj.msg(string % (self.get_display_name(obj), - location.get_display_name(obj) if location else "nowhere", - " from %s" % source_location.get_display_name(obj) if source_location else "")) + if source_location: + if msg: + string = msg + else: + string = "{character} arrives to {destination} from {origin}." + else: + string = "{character} arrives to {destination}." + + origin = source_location + destination = self.location + exits = [] + if origin: + exits = [o for o in destination.contents if o.location is destination and o.destination is origin] + + mapping = { + "character": self, + "exit": exits[0] if exits else "somewhere", + "origin": origin or "nowhere", + "destination": destination or "nowhere", + } + + destination.msg_contents(string, exclude=(self, ), mapping=mapping) def at_after_move(self, source_location): """ From f1d34a28aadd3ea442d5a53592ced9357d18ba3d Mon Sep 17 00:00:00 2001 From: Vincent Le Goff Date: Mon, 27 Mar 2017 22:52:43 -0700 Subject: [PATCH 127/134] Add additional mapping to announce_move_* hooks --- evennia/objects/objects.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/evennia/objects/objects.py b/evennia/objects/objects.py index 23eb55aef8..eca147ff76 100644 --- a/evennia/objects/objects.py +++ b/evennia/objects/objects.py @@ -1162,7 +1162,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): # return has_perm(self, destination, "can_move") return True - def announce_move_from(self, destination, msg=None): + def announce_move_from(self, destination, msg=None, mapping=None): """ Called if the move is to be announced. This is called while we are still standing in the old @@ -1171,6 +1171,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): Args: destination (Object): The place we are going to. msg (str, optional): a replacement message. + mapping (dict, optional): additional mapping objects. You can override this method and call its parent with a message to simply change the default message. In the string, @@ -1190,16 +1191,19 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): location = self.location exits = [o for o in location.contents if o.location is location and o.destination is destination] - mapping = { + if not mapping: + mapping = {} + + mapping.update({ "character": self, "exit": exits[0] if exits else "somwhere", "origin": location or "nowhere", "destination": destination or "nowhere", - } + }) location.msg_contents(string, exclude=(self, ), mapping=mapping) - def announce_move_to(self, source_location, msg=None): + def announce_move_to(self, source_location, msg=None, mapping=None): """ Called after the move if the move was not quiet. At this point we are standing in the new location. @@ -1207,6 +1211,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): Args: source_location (Object): The place we came from msg (str, optional): the replacement message if location. + mapping (dict, optional): additional mapping objects. You can override this method and call its parent with a message to simply change the default message. In the string, @@ -1239,12 +1244,15 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): if origin: exits = [o for o in destination.contents if o.location is destination and o.destination is origin] - mapping = { + if not mapping: + mapping = {} + + mapping.update({ "character": self, "exit": exits[0] if exits else "somewhere", "origin": origin or "nowhere", "destination": destination or "nowhere", - } + }) destination.msg_contents(string, exclude=(self, ), mapping=mapping) From 3811c074c62eec5f0562544132e0083d4fdd8a30 Mon Sep 17 00:00:00 2001 From: Vincent Le Goff Date: Tue, 28 Mar 2017 10:22:23 -0700 Subject: [PATCH 128/134] Replace {character} by {object} in mapping of announce_move_* --- evennia/objects/objects.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/evennia/objects/objects.py b/evennia/objects/objects.py index eca147ff76..90c5cf8470 100644 --- a/evennia/objects/objects.py +++ b/evennia/objects/objects.py @@ -1176,10 +1176,10 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): You can override this method and call its parent with a message to simply change the default message. In the string, you can use the following as mappings (between braces): - character: the character who is moving. - exit: the exit from which the character is moving (if found). - origin: the location of the character before the move. - destination: the location of the character after moving. + object: the object which is moving. + exit: the exit from which the object is moving (if found). + origin: the location of the object before the move. + destination: the location of the object after moving. """ if not self.location: @@ -1187,7 +1187,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): if msg: string = msg else: - string = "{character} is leaving {origin}, heading for {destination}." + string = "{object} is leaving {origin}, heading for {destination}." location = self.location exits = [o for o in location.contents if o.location is location and o.destination is destination] @@ -1195,7 +1195,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): mapping = {} mapping.update({ - "character": self, + "object": self, "exit": exits[0] if exits else "somwhere", "origin": location or "nowhere", "destination": destination or "nowhere", @@ -1216,10 +1216,10 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): You can override this method and call its parent with a message to simply change the default message. In the string, you can use the following as mappings (between braces): - character: the character who is moving. - exit: the exit from which the character is moving (if found). - origin: the location of the character before the move. - destination: the location of the character after moving. + object: the object which is moving. + exit: the exit from which the object is moving (if found). + origin: the location of the object before the move. + destination: the location of the object after moving. """ @@ -1234,9 +1234,9 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): if msg: string = msg else: - string = "{character} arrives to {destination} from {origin}." + string = "{object} arrives to {destination} from {origin}." else: - string = "{character} arrives to {destination}." + string = "{object} arrives to {destination}." origin = source_location destination = self.location @@ -1248,7 +1248,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): mapping = {} mapping.update({ - "character": self, + "object": self, "exit": exits[0] if exits else "somewhere", "origin": origin or "nowhere", "destination": destination or "nowhere", From 6133280154ab5dd509c49506e305d6b65cf3b4e3 Mon Sep 17 00:00:00 2001 From: BlauFeuer Date: Thu, 30 Mar 2017 01:27:34 -0400 Subject: [PATCH 129/134] Explict bool return for move_to, typo fix --- evennia/objects/objects.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/evennia/objects/objects.py b/evennia/objects/objects.py index 90c5cf8470..4a56df1ea1 100644 --- a/evennia/objects/objects.py +++ b/evennia/objects/objects.py @@ -551,7 +551,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): """ Emits a message to all objects inside this object. - Argsu: + Args: text (str or tuple): Message to send. If a tuple, this should be on the valid OOB outmessage form `(message, {kwargs})`, where kwargs are optional data passed to the `text` @@ -611,7 +611,6 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): outmessage = inmessage.format(**substitutions) else: outmessage = inmessage - obj.msg(text=(outmessage, outkwargs), from_obj=from_obj, **kwargs) def move_to(self, destination, quiet=False, @@ -673,7 +672,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): self.location = None return True emit_to_obj.msg(_("The destination doesn't exist.")) - return + return False if destination.destination and use_destination: # traverse exits destination = destination.destination @@ -682,7 +681,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): if move_hooks: try: if not self.at_before_move(destination): - return + return False except Exception as err: logerr(errtxt % "at_before_move()", err) return False From 4aba1b59b38ed05301daa47e078e947521a7422d Mon Sep 17 00:00:00 2001 From: Griatch Date: Thu, 30 Mar 2017 22:04:54 +0200 Subject: [PATCH 130/134] Add a brief mention of the superuser bypassing the locks of the simpledoor contrib. Resolves #1269. --- evennia/contrib/simpledoor.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/evennia/contrib/simpledoor.py b/evennia/contrib/simpledoor.py index 12a6b4ed49..ad6cc8b0a7 100644 --- a/evennia/contrib/simpledoor.py +++ b/evennia/contrib/simpledoor.py @@ -5,7 +5,11 @@ Contribution - Griatch 2016 A simple two-way exit that represents a door that can be opened and closed. Can easily be expanded from to make it lockable, destroyable -etc. +etc. Note that the simpledoor is based on Evennia locks, so it will +not work for a superuser (which bypasses all locks) - the superuser +will always appear to be able to close/open the door over and over +without the locks stopping you. To use the door, use `@quell` or a +non-superuser account. Installation: From 3fdd7e0907411b46e088de3b75ec72259b885c74 Mon Sep 17 00:00:00 2001 From: BlauFeuer Date: Thu, 30 Mar 2017 18:32:50 -0400 Subject: [PATCH 131/134] PEP 8, whitespace, LGTM, typo fixes --- evennia/objects/models.py | 42 +++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/evennia/objects/models.py b/evennia/objects/models.py index b000fd0cb0..1f1e97df06 100644 --- a/evennia/objects/models.py +++ b/evennia/objects/models.py @@ -51,8 +51,7 @@ class ContentsHandler(object): Re-initialize the content cache """ - self._pkcache.update(dict((obj.pk, None) for obj in - ObjectDB.objects.filter(db_location=self.obj) if obj.pk)) + self._pkcache.update(dict((obj.pk, None) for obj in ObjectDB.objects.filter(db_location=self.obj) if obj.pk)) def get(self, exclude=None): """ @@ -79,7 +78,7 @@ class ContentsHandler(object): return [self._idcache[pk] for pk in pks] except KeyError: # this means an actual failure of caching. Return real database match. - logger.log_err("contents cache failed for %s." % (self.obj.key)) + logger.log_err("contents cache failed for %s." % self.obj.key) return list(ObjectDB.objects.filter(db_location=self.obj)) def add(self, obj): @@ -110,11 +109,12 @@ class ContentsHandler(object): self._pkcache = {} self.init() -#------------------------------------------------------------ +# ------------------------------------------------------------- # # ObjectDB # -#------------------------------------------------------------ +# ------------------------------------------------------------- + class ObjectDB(TypedObject): """ @@ -173,17 +173,18 @@ class ObjectDB(TypedObject): help_text='a Player connected to this object, if any.') # the session id associated with this player, if any db_sessid = models.CommaSeparatedIntegerField(null=True, max_length=32, verbose_name="session id", - help_text="csv list of session ids of connected Player, if any.") + help_text="csv list of session ids of connected Player, if any.") # The location in the game world. Since this one is likely # to change often, we set this with the 'location' property # to transparently handle Typeclassing. db_location = models.ForeignKey('self', related_name="locations_set", db_index=True, on_delete=models.SET_NULL, - blank=True, null=True, verbose_name='game location') + blank=True, null=True, verbose_name='game location') # a safety location, this usually don't change much. db_home = models.ForeignKey('self', related_name="homes_set", on_delete=models.SET_NULL, - blank=True, null=True, verbose_name='home location') + blank=True, null=True, verbose_name='home location') # destination of this object - primarily used by exits. - db_destination = models.ForeignKey('self', related_name="destinations_set", db_index=True, on_delete=models.SET_NULL, + db_destination = models.ForeignKey('self', related_name="destinations_set", + db_index=True, on_delete=models.SET_NULL, blank=True, null=True, verbose_name='destination', help_text='a destination, used only by exit objects.') # database storage of persistant cmdsets. @@ -204,28 +205,28 @@ class ObjectDB(TypedObject): # cmdset_storage property handling def __cmdset_storage_get(self): - "getter" + """getter""" storage = self.db_cmdset_storage return [path.strip() for path in storage.split(',')] if storage else [] def __cmdset_storage_set(self, value): - "setter" - self.db_cmdset_storage = ",".join(str(val).strip() for val in make_iter(value)) + """"setter""" + self.db_cmdset_storage = ",".join(str(val).strip() for val in make_iter(value)) self.save(update_fields=["db_cmdset_storage"]) def __cmdset_storage_del(self): - "deleter" + """deleter""" self.db_cmdset_storage = None self.save(update_fields=["db_cmdset_storage"]) cmdset_storage = property(__cmdset_storage_get, __cmdset_storage_set, __cmdset_storage_del) # location getsetter def __location_get(self): - "Get location" + """Get location""" return self.db_location def __location_set(self, location): - "Set location, checking for loops and allowing dbref" + """Set location, checking for loops and allowing dbref""" if isinstance(location, (basestring, int)): # allow setting of #dbref dbid = dbref(location, reqhash=False) @@ -237,9 +238,9 @@ class ObjectDB(TypedObject): pass try: def is_loc_loop(loc, depth=0): - "Recursively traverse target location, trying to catch a loop." + """Recursively traverse target location, trying to catch a loop.""" if depth > 10: - return + return None elif loc == self: raise RuntimeError elif loc is None: @@ -248,7 +249,7 @@ class ObjectDB(TypedObject): try: is_loc_loop(location) except RuntimeWarning: - # we caught a infitite location loop! + # we caught an infinite location loop! # (location1 is in location2 which is in location1 ...) pass @@ -281,7 +282,7 @@ class ObjectDB(TypedObject): return def __location_del(self): - "Cleanly delete the location reference" + """Cleanly delete the location reference""" self.db_location = None self.save(update_fields=["db_location"]) location = property(__location_get, __location_set, __location_del) @@ -311,7 +312,6 @@ class ObjectDB(TypedObject): [o.contents_cache.init() for o in self.__dbclass__.get_all_cached_instances()] class Meta(object): - "Define Django meta options" + """Define Django meta options""" verbose_name = "Object" verbose_name_plural = "Objects" - From c62f701626af83a00bf2a43b421616c52c4d4d92 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 1 Apr 2017 14:44:29 +0200 Subject: [PATCH 132/134] Remove an extra quote marker typo. --- evennia/objects/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evennia/objects/models.py b/evennia/objects/models.py index 1f1e97df06..009bae0b93 100644 --- a/evennia/objects/models.py +++ b/evennia/objects/models.py @@ -210,7 +210,7 @@ class ObjectDB(TypedObject): return [path.strip() for path in storage.split(',')] if storage else [] def __cmdset_storage_set(self, value): - """"setter""" + """setter""" self.db_cmdset_storage = ",".join(str(val).strip() for val in make_iter(value)) self.save(update_fields=["db_cmdset_storage"]) From 684f163fd70d80834cc4eb00e9ffdf7403a6404a Mon Sep 17 00:00:00 2001 From: BlauFeuer Date: Thu, 30 Mar 2017 19:10:24 -0400 Subject: [PATCH 133/134] Fold long lines, PEP 8 whitespace Keeping lines under 120 characters long, adjusting whitepace for closer PEP 8 compliance. --- evennia/objects/admin.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/evennia/objects/admin.py b/evennia/objects/admin.py index 894e07d54f..396800e8ca 100644 --- a/evennia/objects/admin.py +++ b/evennia/objects/admin.py @@ -40,16 +40,21 @@ class ObjectCreateForm(forms.ModelForm): fields = '__all__' db_key = forms.CharField(label="Name/Key", widget=forms.TextInput(attrs={'size': '78'}), - help_text="Main identifier, like 'apple', 'strong guy', 'Elizabeth' etc. If creating a Character, check so the name is unique among characters!",) + help_text="Main identifier, like 'apple', 'strong guy', 'Elizabeth' etc. " + "If creating a Character, check so the name is unique among characters!",) db_typeclass_path = forms.CharField(label="Typeclass", initial=settings.BASE_OBJECT_TYPECLASS, widget=forms.TextInput(attrs={'size': '78'}), - help_text="This defines what 'type' of entity this is. This variable holds a Python path to a module with a valid Evennia Typeclass. If you are creating a Character you should use the typeclass defined by settings.BASE_CHARACTER_TYPECLASS or one derived from that.") + help_text="This defines what 'type' of entity this is. This variable holds a " + "Python path to a module with a valid Evennia Typeclass. If you are " + "creating a Character you should use the typeclass defined by " + "settings.BASE_CHARACTER_TYPECLASS or one derived from that.") db_cmdset_storage = forms.CharField(label="CmdSet", initial="", required=False, widget=forms.TextInput(attrs={'size': '78'}), - help_text="Most non-character objects don't need a cmdset and can leave this field blank.") + help_text="Most non-character objects don't need a cmdset" + " and can leave this field blank.") raw_id_fields = ('db_destination', 'db_location', 'db_home') @@ -63,8 +68,10 @@ class ObjectEditForm(ObjectCreateForm): fields = '__all__' db_lock_storage = forms.CharField(label="Locks", required=False, - widget=forms.Textarea(attrs={'cols':'100', 'rows':'2'}), - help_text="In-game lock definition string. If not given, defaults will be used. This string should be on the form type:lockfunction(args);type2:lockfunction2(args);...") + widget=forms.Textarea(attrs={'cols': '100', 'rows': '2'}), + help_text="In-game lock definition string. If not given, defaults will be used. " + "This string should be on the form " + "type:lockfunction(args);type2:lockfunction2(args);...") class ObjectDBAdmin(admin.ModelAdmin): @@ -90,15 +97,15 @@ class ObjectDBAdmin(admin.ModelAdmin): form = ObjectEditForm fieldsets = ( (None, { - 'fields': (('db_key','db_typeclass_path'), ('db_lock_storage', ), - ('db_location', 'db_home'), 'db_destination','db_cmdset_storage' + 'fields': (('db_key', 'db_typeclass_path'), ('db_lock_storage', ), + ('db_location', 'db_home'), 'db_destination', 'db_cmdset_storage' )}), ) add_form = ObjectCreateForm add_fieldsets = ( (None, { - 'fields': (('db_key','db_typeclass_path'), + 'fields': (('db_key', 'db_typeclass_path'), ('db_location', 'db_home'), 'db_destination', 'db_cmdset_storage' )}), ) From 7010f998f17e2a051ef0cba7bcb8bd59bc1f351c Mon Sep 17 00:00:00 2001 From: BlauFeuer Date: Thu, 30 Mar 2017 21:27:29 -0400 Subject: [PATCH 134/134] Fold long lines, PEP 8, comparision to None +Indent, comment and typo edits --- evennia/objects/manager.py | 65 +++++++++++++++++++++++--------------- 1 file changed, 40 insertions(+), 25 deletions(-) diff --git a/evennia/objects/manager.py b/evennia/objects/manager.py index 9bbd847f8f..a21a23de52 100644 --- a/evennia/objects/manager.py +++ b/evennia/objects/manager.py @@ -21,6 +21,7 @@ _MULTIMATCH_REGEX = re.compile(settings.SEARCH_MULTIMATCH_REGEX, re.I + re.U) # Try to use a custom way to parse id-tagged multimatches. + class ObjectDBManager(TypedObjectManager): """ This ObjectManager implements methods for searching @@ -79,11 +80,13 @@ class ObjectDBManager(TypedObjectManager): if dbref: return dbref # not a dbref. Search by name. - cand_restriction = candidates != None and Q(pk__in=[_GA(obj, "id") for obj in make_iter(candidates) if obj]) or Q() + cand_restriction = candidates is not None and Q(pk__in=[_GA(obj, "id") for obj in make_iter(candidates) + if obj]) or Q() if exact: return self.filter(cand_restriction & Q(db_player__username__iexact=ostring)) - else: # fuzzy matching - ply_cands = self.filter(cand_restriction & Q(playerdb__username__istartswith=ostring)).values_list("db_key", flat=True) + else: # fuzzy matching + ply_cands = self.filter(cand_restriction & Q(playerdb__username__istartswith=ostring) + ).values_list("db_key", flat=True) if candidates: index_matches = string_partial_matching(ply_cands, ostring, ret_index=True) return [obj for ind, obj in enumerate(make_iter(candidates)) if ind in index_matches] @@ -103,7 +106,8 @@ class ObjectDBManager(TypedObjectManager): Returns: matches (list): The matching objects. """ - cand_restriction = candidates != None and Q(pk__in=[_GA(obj, "id") for obj in make_iter(candidates) if obj]) or Q() + cand_restriction = candidates is not None and Q(pk__in=[_GA(obj, "id") for obj in make_iter(candidates) + if obj]) or Q() return self.filter(cand_restriction & Q(db_key__iexact=oname, db_typeclass_path__exact=otypeclass_path)) # attr/property related @@ -121,7 +125,9 @@ class ObjectDBManager(TypedObjectManager): matches (list): All objects having the given attribute_name defined at all. """ - cand_restriction = candidates != None and Q(db_attributes__db_obj__pk__in=[_GA(obj, "id") for obj in make_iter(candidates) if obj]) or Q() + cand_restriction = candidates is not None and Q(db_attributes__db_obj__pk__in=[_GA(obj, "id") for obj + in make_iter(candidates) + if obj]) or Q() return list(self.filter(cand_restriction & Q(db_attributes__db_key=attribute_name))) @returns_typeclass_list @@ -144,20 +150,23 @@ class ObjectDBManager(TypedObjectManager): cannot be indexed, searching by Attribute key is to be preferred whenever possible. """ - cand_restriction = candidates != None and Q(pk__in=[_GA(obj, "id") for obj in make_iter(candidates) if obj]) or Q() + cand_restriction = candidates is not None and Q(pk__in=[_GA(obj, "id") for obj in make_iter(candidates) + if obj]) or Q() type_restriction = typeclasses and Q(db_typeclass_path__in=make_iter(typeclasses)) or Q() - ## This doesn't work if attribute_value is an object. Workaround below + # This doesn't work if attribute_value is an object. Workaround below if isinstance(attribute_value, (basestring, int, float, bool)): - return self.filter(cand_restriction & type_restriction & Q(db_attributes__db_key=attribute_name, db_attributes__db_value=attribute_value)) + return self.filter(cand_restriction & type_restriction & Q(db_attributes__db_key=attribute_name, + db_attributes__db_value=attribute_value)) else: - # We have to loop for safety since the referenced lookup gives deepcopy error if attribute value is an object. + # We must loop for safety since the referenced lookup gives deepcopy error if attribute value is an object. global _ATTR if not _ATTR: from evennia.typeclasses.models import Attribute as _ATTR cands = list(self.filter(cand_restriction & type_restriction & Q(db_attributes__db_key=attribute_name))) - results = [attr.objectdb_set.all() for attr in _ATTR.objects.filter(objectdb__in=cands, db_value=attribute_value)] + results = [attr.objectdb_set.all() for attr in _ATTR.objects.filter(objectdb__in=cands, + db_value=attribute_value)] return chain(*results) @returns_typeclass_list @@ -174,8 +183,9 @@ class ObjectDBManager(TypedObjectManager): """ property_name = "db_%s" % property_name.lstrip('db_') - cand_restriction = candidates != None and Q(pk__in=[_GA(obj, "id") for obj in make_iter(candidates) if obj]) or Q() - querykwargs = {property_name:None} + cand_restriction = candidates is not None and Q(pk__in=[_GA(obj, "id") for obj in make_iter(candidates) + if obj]) or Q() + querykwargs = {property_name: None} try: return list(self.filter(cand_restriction).exclude(Q(**querykwargs))) except exceptions.FieldError: @@ -198,8 +208,9 @@ class ObjectDBManager(TypedObjectManager): if isinstance(property_name, basestring): if not property_name.startswith('db_'): property_name = "db_%s" % property_name - querykwargs = {property_name:property_value} - cand_restriction = candidates != None and Q(pk__in=[_GA(obj, "id") for obj in make_iter(candidates) if obj]) or Q() + querykwargs = {property_name: property_value} + cand_restriction = candidates is not None and Q(pk__in=[_GA(obj, "id") for obj in make_iter(candidates) + if obj]) or Q() type_restriction = typeclasses and Q(db_typeclass_path__in=make_iter(typeclasses)) or Q() try: return list(self.filter(cand_restriction & type_restriction & Q(**querykwargs))) @@ -207,7 +218,8 @@ class ObjectDBManager(TypedObjectManager): return [] except ValueError: from evennia.utils import logger - logger.log_err("The property '%s' does not support search criteria of the type %s." % (property_name, type(property_value))) + logger.log_err("The property '%s' does not support search criteria of the type %s." % + (property_name, type(property_value))) return [] @returns_typeclass_list @@ -228,7 +240,7 @@ class ObjectDBManager(TypedObjectManager): @returns_typeclass_list def get_objs_with_key_or_alias(self, ostring, exact=True, - candidates=None, typeclasses=None): + candidates=None, typeclasses=None): """ Args: ostring (str): A search criterion. @@ -253,7 +265,7 @@ class ObjectDBManager(TypedObjectManager): # build query objects candidates_id = [_GA(obj, "id") for obj in make_iter(candidates) if obj] - cand_restriction = candidates != None and Q(pk__in=candidates_id) or Q() + cand_restriction = candidates is not None and Q(pk__in=candidates_id) or Q() type_restriction = typeclasses and Q(db_typeclass_path__in=make_iter(typeclasses)) or Q() if exact: # exact match - do direct search @@ -264,7 +276,8 @@ class ObjectDBManager(TypedObjectManager): search_candidates = self.filter(cand_restriction & type_restriction) else: # fuzzy without supplied candidates - we select our own candidates - search_candidates = self.filter(type_restriction & (Q(db_key__istartswith=ostring) | Q(db_tags__db_key__istartswith=ostring))).distinct() + search_candidates = self.filter(type_restriction & (Q(db_key__istartswith=ostring) | + Q(db_tags__db_key__istartswith=ostring))).distinct() # fuzzy matching key_strings = search_candidates.values_list("db_key", flat=True).order_by("id") @@ -275,10 +288,10 @@ class ObjectDBManager(TypedObjectManager): else: # match by alias rather than by key search_candidates = search_candidates.filter(db_tags__db_tagtype__iexact="alias", - db_tags__db_key__icontains=ostring) + db_tags__db_key__icontains=ostring) alias_strings = [] alias_candidates = [] - #TODO create the alias_strings and alias_candidates lists more effiently? + # TODO create the alias_strings and alias_candidates lists more efficiently? for candidate in search_candidates: for alias in candidate.aliases.all(): alias_strings.append(alias) @@ -343,13 +356,16 @@ class ObjectDBManager(TypedObjectManager): """ if attribute_name: # attribute/property search (always exact). - matches = self.get_objs_with_db_property_value(attribute_name, searchdata, candidates=candidates, typeclasses=typeclass) + matches = self.get_objs_with_db_property_value(attribute_name, searchdata, + candidates=candidates, typeclasses=typeclass) if matches: return matches - return self.get_objs_with_attr_value(attribute_name, searchdata, candidates=candidates, typeclasses=typeclass) + return self.get_objs_with_attr_value(attribute_name, searchdata, + candidates=candidates, typeclasses=typeclass) else: # normal key/alias search - return self.get_objs_with_key_or_alias(searchdata, exact=exact, candidates=candidates, typeclasses=typeclass) + return self.get_objs_with_key_or_alias(searchdata, exact=exact, + candidates=candidates, typeclasses=typeclass) if not searchdata and searchdata != 0: return [] @@ -372,7 +388,7 @@ class ObjectDBManager(TypedObjectManager): candidates = [cand for cand in make_iter(candidates) if cand] if typeclass: candidates = [cand for cand in candidates - if _GA(cand, "db_typeclass_path") in typeclass] + if _GA(cand, "db_typeclass_path") in typeclass] dbref = not attribute_name and exact and use_dbref and self.dbref(searchdata) if dbref: @@ -418,7 +434,6 @@ class ObjectDBManager(TypedObjectManager): # # ObjectManager Copy method - # def copy_object(self, original_object, new_key=None, new_location=None, new_home=None,