diff --git a/evennia/accounts/accounts.py b/evennia/accounts/accounts.py index 602ccc2a69..9442625029 100644 --- a/evennia/accounts/accounts.py +++ b/evennia/accounts/accounts.py @@ -421,7 +421,7 @@ class DefaultAccount(with_metaclass(TypeclassBase, AccountDB)): kwargs["options"] = options - if text and not (isinstance(text, basestring) or isinstance(text, tuple)): + if not (isinstance(text, basestring) or isinstance(text, tuple)): # sanitize text before sending across the wire try: text = to_str(text, force_string=True) diff --git a/evennia/commands/default/account.py b/evennia/commands/default/account.py index 20cc374542..40805f7465 100644 --- a/evennia/commands/default/account.py +++ b/evennia/commands/default/account.py @@ -455,7 +455,7 @@ class CmdOption(COMMAND_DEFAULT_CLASS): Usage: @option[/save] [name = value] - Switch: + Switches: save - Save the current option settings for future logins. clear - Clear the saved options. @@ -467,6 +467,7 @@ class CmdOption(COMMAND_DEFAULT_CLASS): """ key = "@option" aliases = "@options" + switch_options = ("save", "clear") locks = "cmd:all()" # this is used by the parent @@ -650,6 +651,7 @@ class CmdQuit(COMMAND_DEFAULT_CLASS): game. Use the /all switch to disconnect from all sessions. """ key = "@quit" + switch_options = ("all",) locks = "cmd:all()" # this is used by the parent diff --git a/evennia/commands/default/admin.py b/evennia/commands/default/admin.py index 8b694ffd8f..4e517b77f9 100644 --- a/evennia/commands/default/admin.py +++ b/evennia/commands/default/admin.py @@ -36,6 +36,7 @@ class CmdBoot(COMMAND_DEFAULT_CLASS): """ key = "@boot" + switch_options = ("quiet", "sid") locks = "cmd:perm(boot) or perm(Admin)" help_category = "Admin" @@ -265,6 +266,7 @@ class CmdDelAccount(COMMAND_DEFAULT_CLASS): """ key = "@delaccount" + switch_options = ("delobj",) locks = "cmd:perm(delaccount) or perm(Developer)" help_category = "Admin" @@ -329,9 +331,9 @@ class CmdEmit(COMMAND_DEFAULT_CLASS): @pemit [, , ... =] Switches: - room : limit emits to rooms only (default) - accounts : limit emits to accounts only - contents : send to the contents of matched objects too + room - limit emits to rooms only (default) + accounts - limit emits to accounts only + contents - send to the contents of matched objects too Emits a message to the selected objects or to your immediate surroundings. If the object is a room, @@ -341,6 +343,7 @@ class CmdEmit(COMMAND_DEFAULT_CLASS): """ key = "@emit" aliases = ["@pemit", "@remit"] + switch_options = ("room", "accounts", "contents") locks = "cmd:perm(emit) or perm(Builder)" help_category = "Admin" @@ -442,14 +445,15 @@ class CmdPerm(COMMAND_DEFAULT_CLASS): @perm[/switch] * [= [,,...]] Switches: - del : delete the given permission from or . - account : set permission on an account (same as adding * to name) + del - delete the given permission from or . + account - set permission on an account (same as adding * to name) This command sets/clears individual permission strings on an object or account. If no permission is given, list all permissions on . """ key = "@perm" aliases = "@setperm" + switch_options = ("del", "account") locks = "cmd:perm(perm) or perm(Developer)" help_category = "Admin" diff --git a/evennia/commands/default/batchprocess.py b/evennia/commands/default/batchprocess.py index f0b117a816..5970d35887 100644 --- a/evennia/commands/default/batchprocess.py +++ b/evennia/commands/default/batchprocess.py @@ -237,6 +237,7 @@ class CmdBatchCommands(_COMMAND_DEFAULT_CLASS): """ key = "@batchcommands" aliases = ["@batchcommand", "@batchcmd"] + switch_options = ("interactive",) locks = "cmd:perm(batchcommands) or perm(Developer)" help_category = "Building" @@ -347,6 +348,7 @@ class CmdBatchCode(_COMMAND_DEFAULT_CLASS): """ key = "@batchcode" aliases = ["@batchcodes"] + switch_options = ("interactive", "debug") locks = "cmd:superuser()" help_category = "Building" diff --git a/evennia/commands/default/building.py b/evennia/commands/default/building.py index 9e8199d546..beaaeba5a5 100644 --- a/evennia/commands/default/building.py +++ b/evennia/commands/default/building.py @@ -123,6 +123,7 @@ class CmdSetObjAlias(COMMAND_DEFAULT_CLASS): key = "@alias" aliases = "@setobjalias" + switch_options = ("category",) locks = "cmd:perm(setobjalias) or perm(Builder)" help_category = "Building" @@ -217,6 +218,7 @@ class CmdCopy(ObjManipCommand): """ key = "@copy" + switch_options = ("reset",) locks = "cmd:perm(copy) or perm(Builder)" help_category = "Building" @@ -298,6 +300,7 @@ class CmdCpAttr(ObjManipCommand): If you don't supply a source object, yourself is used. """ key = "@cpattr" + switch_options = ("move",) locks = "cmd:perm(cpattr) or perm(Builder)" help_category = "Building" @@ -439,6 +442,7 @@ class CmdMvAttr(ObjManipCommand): object. If you don't supply a source object, yourself is used. """ key = "@mvattr" + switch_options = ("copy",) locks = "cmd:perm(mvattr) or perm(Builder)" help_category = "Building" @@ -487,6 +491,7 @@ class CmdCreate(ObjManipCommand): """ key = "@create" + switch_options = ("drop",) locks = "cmd:perm(create) or perm(Builder)" help_category = "Building" @@ -572,6 +577,7 @@ class CmdDesc(COMMAND_DEFAULT_CLASS): """ key = "@desc" aliases = "@describe" + switch_options = ("edit",) locks = "cmd:perm(desc) or perm(Builder)" help_category = "Building" @@ -633,11 +639,11 @@ class CmdDestroy(COMMAND_DEFAULT_CLASS): Usage: @destroy[/switches] [obj, obj2, obj3, [dbref-dbref], ...] - switches: + Switches: override - The @destroy command will usually avoid accidentally destroying account objects. This switch overrides this safety. force - destroy without confirmation. - examples: + Examples: @destroy house, roof, door, 44-78 @destroy 5-10, flower, 45 @destroy/force north @@ -650,6 +656,7 @@ class CmdDestroy(COMMAND_DEFAULT_CLASS): key = "@destroy" aliases = ["@delete", "@del"] + switch_options = ("override", "force") locks = "cmd:perm(destroy) or perm(Builder)" help_category = "Building" @@ -773,6 +780,7 @@ class CmdDig(ObjManipCommand): would be 'north;no;n'. """ key = "@dig" + switch_options = ("teleport",) locks = "cmd:perm(dig) or perm(Builder)" help_category = "Building" @@ -882,7 +890,7 @@ class CmdDig(ObjManipCommand): new_back_exit.dbref, alias_string) caller.msg("%s%s%s" % (room_string, exit_to_string, exit_back_string)) - if new_room and ('teleport' in self.switches or "tel" in self.switches): + if new_room and 'teleport' in self.switches: caller.move_to(new_room) @@ -915,6 +923,7 @@ class CmdTunnel(COMMAND_DEFAULT_CLASS): key = "@tunnel" aliases = ["@tun"] + switch_options = ("oneway", "tel") locks = "cmd: perm(tunnel) or perm(Builder)" help_category = "Building" @@ -2295,6 +2304,7 @@ class CmdFind(COMMAND_DEFAULT_CLASS): key = "@find" aliases = "@search, @locate" + switch_options = ("room", "exit", "char", "exact", "loc") locks = "cmd:perm(find) or perm(Builder)" help_category = "Building" @@ -2331,7 +2341,7 @@ class CmdFind(COMMAND_DEFAULT_CLASS): restrictions = "" if self.switches: - restrictions = ", %s" % (",".join(self.switches)) + restrictions = ", %s" % (", ".join(self.switches)) if is_dbref or is_account: @@ -2411,7 +2421,7 @@ class CmdTeleport(COMMAND_DEFAULT_CLASS): teleport object to another location Usage: - @tel/switch [ =] + @tel/switch [ to||=] Examples: @tel Limbo @@ -2435,6 +2445,8 @@ class CmdTeleport(COMMAND_DEFAULT_CLASS): """ key = "@tel" aliases = "@teleport" + switch_options = ("quiet", "intoexit", "tonone", "loc") + rhs_split = ("=", " to ") # Prefer = delimiter, but allow " to " usage. locks = "cmd:perm(teleport) or perm(Builder)" help_category = "Building" @@ -2542,6 +2554,7 @@ class CmdScript(COMMAND_DEFAULT_CLASS): key = "@script" aliases = "@addscript" + switch_options = ("start", "stop") locks = "cmd:perm(script) or perm(Builder)" help_category = "Building" @@ -2641,6 +2654,7 @@ class CmdTag(COMMAND_DEFAULT_CLASS): key = "@tag" aliases = ["@tags"] + options = ("search", "del") locks = "cmd:perm(tag) or perm(Builder)" help_category = "Building" arg_regex = r"(/\w+?(\s|$))|\s|$" @@ -2796,6 +2810,7 @@ class CmdSpawn(COMMAND_DEFAULT_CLASS): """ key = "@spawn" + switch_options = ("noloc", ) locks = "cmd:perm(spawn) or perm(Builder)" help_category = "Building" diff --git a/evennia/commands/default/comms.py b/evennia/commands/default/comms.py index 37a4934d16..d9fe0b0d20 100644 --- a/evennia/commands/default/comms.py +++ b/evennia/commands/default/comms.py @@ -377,7 +377,7 @@ class CmdCBoot(COMMAND_DEFAULT_CLASS): Usage: @cboot[/quiet] = [:reason] - Switches: + Switch: quiet - don't notify the channel Kicks an account or object from a channel you control. @@ -385,6 +385,7 @@ class CmdCBoot(COMMAND_DEFAULT_CLASS): """ key = "@cboot" + switch_options = ("quiet",) locks = "cmd: not pperm(channel_banned)" help_category = "Comms" @@ -453,6 +454,7 @@ class CmdCemit(COMMAND_DEFAULT_CLASS): key = "@cemit" aliases = ["@cmsg"] + switch_options = ("sendername", "quiet") locks = "cmd: not pperm(channel_banned) and pperm(Player)" help_category = "Comms" @@ -683,6 +685,7 @@ class CmdPage(COMMAND_DEFAULT_CLASS): key = "page" aliases = ['tell'] + switch_options = ("last", "list") locks = "cmd:not pperm(page_banned)" help_category = "Comms" @@ -850,6 +853,7 @@ class CmdIRC2Chan(COMMAND_DEFAULT_CLASS): """ key = "@irc2chan" + switch_options = ("delete", "remove", "disconnect", "list", "ssl") locks = "cmd:serversetting(IRC_ENABLED) and pperm(Developer)" help_category = "Comms" @@ -1016,6 +1020,7 @@ class CmdRSS2Chan(COMMAND_DEFAULT_CLASS): """ key = "@rss2chan" + switch_options = ("disconnect", "remove", "list") locks = "cmd:serversetting(RSS_ENABLED) and pperm(Developer)" help_category = "Comms" diff --git a/evennia/commands/default/general.py b/evennia/commands/default/general.py index d61e99b908..eafb7670b0 100644 --- a/evennia/commands/default/general.py +++ b/evennia/commands/default/general.py @@ -88,8 +88,7 @@ class CmdNick(COMMAND_DEFAULT_CLASS): Switches: inputline - replace on the inputline (default) object - replace on object-lookup - account - replace on account-lookup - + account - replace on account-lookup list - show all defined aliases (also "nicks" works) delete - remove nick by index in /list clearall - clear all nicks @@ -118,6 +117,7 @@ class CmdNick(COMMAND_DEFAULT_CLASS): """ key = "nick" + switch_options = ("inputline", "object", "account", "list", "delete", "clearall") aliases = ["nickname", "nicks"] locks = "cmd:all()" @@ -442,12 +442,13 @@ class CmdGive(COMMAND_DEFAULT_CLASS): give away something to someone Usage: - give = + give Gives an items from your inventory to another character, placing it in their inventory. """ key = "give" + rhs_split = ("=", " to ") # Prefer = delimiter, but allow " to " usage. locks = "cmd:all()" arg_regex = r"\s|$" diff --git a/evennia/commands/default/help.py b/evennia/commands/default/help.py index ebce460748..d2011629c1 100644 --- a/evennia/commands/default/help.py +++ b/evennia/commands/default/help.py @@ -317,6 +317,7 @@ class CmdSetHelp(COMMAND_DEFAULT_CLASS): """ key = "@sethelp" + switch_options = ("edit", "replace", "append", "extend", "delete") locks = "cmd:perm(Helper)" help_category = "Building" diff --git a/evennia/commands/default/muxcommand.py b/evennia/commands/default/muxcommand.py index b3a0d066d5..349679f5bd 100644 --- a/evennia/commands/default/muxcommand.py +++ b/evennia/commands/default/muxcommand.py @@ -79,6 +79,13 @@ class MuxCommand(Command): it here). The rest of the command is stored in self.args, which can start with the switch indicator /. + Optional variables to aid in parsing, if set: + self.switch_options - (tuple of valid /switches expected by this + command (without the /)) + self.rhs_split - Alternate string delimiter or tuple of strings + to separate left/right hand sides. tuple form + gives priority split to first string delimiter. + This parser breaks self.args into its constituents and stores them in the following variables: self.switches = [list of /switches (without the /)] @@ -97,9 +104,18 @@ class MuxCommand(Command): """ raw = self.args args = raw.strip() + # Without explicitly setting these attributes, they assume default values: + if not hasattr(self, "switch_options"): + self.switch_options = None + if not hasattr(self, "rhs_split"): + self.rhs_split = "=" + if not hasattr(self, "account_caller"): + self.account_caller = False # split out switches - switches = [] + switches, delimiters = [], self.rhs_split + if self.switch_options: + self.switch_options = [opt.lower() for opt in self.switch_options] 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) @@ -109,16 +125,50 @@ class MuxCommand(Command): else: args = "" switches = switches[0].split('/') + # If user-provides switches, parse them with parser switch options. + if switches and self.switch_options: + valid_switches, unused_switches, extra_switches = [], [], [] + for element in switches: + option_check = [opt for opt in self.switch_options if opt == element] + if not option_check: + option_check = [opt for opt in self.switch_options if opt.startswith(element)] + match_count = len(option_check) + if match_count > 1: + extra_switches.extend(option_check) # Either the option provided is ambiguous, + elif match_count == 1: + valid_switches.extend(option_check) # or it is a valid option abbreviation, + elif match_count == 0: + unused_switches.append(element) # or an extraneous option to be ignored. + if extra_switches: # User provided switches + self.msg('|g%s|n: |wAmbiguous switch supplied: Did you mean /|C%s|w?' % + (self.cmdstring, ' |nor /|C'.join(extra_switches))) + if unused_switches: + plural = '' if len(unused_switches) == 1 else 'es' + self.msg('|g%s|n: |wExtra switch%s "/|C%s|w" ignored.' % + (self.cmdstring, plural, '|n, /|C'.join(unused_switches))) + switches = valid_switches # Only include valid_switches in command function call arglist = [arg.strip() for arg in args.split()] # check for arg1, arg2, ... = argA, argB, ... constructs - lhs, rhs = args, None - lhslist, rhslist = [arg.strip() for arg in args.split(',') if arg], [] - if args and '=' in args: - lhs, rhs = [arg.strip() for arg in args.split('=', 1)] - lhslist = [arg.strip() for arg in lhs.split(',')] - rhslist = [arg.strip() for arg in rhs.split(',')] - + lhs, rhs = args.strip(), None + if lhs: + if delimiters and hasattr(delimiters, '__iter__'): # If delimiter is iterable, + best_split = delimiters[0] # (default to first delimiter) + for this_split in delimiters: # try each delimiter + if this_split in lhs: # to find first successful split + best_split = this_split # to be the best split. + break + else: + best_split = delimiters + # Parse to separate left into left/right sides using best_split delimiter string + if best_split in lhs: + lhs, rhs = lhs.split(best_split, 1) + # Trim user-injected whitespace + rhs = rhs.strip() if rhs is not None else None + lhs = lhs.strip() + # Further split left/right sides by comma delimiter + lhslist = [arg.strip() for arg in lhs.split(',')] if lhs is not None else "" + rhslist = [arg.strip() for arg in rhs.split(',')] if rhs is not None else "" # save to object properties: self.raw = raw self.switches = switches @@ -133,7 +183,7 @@ class MuxCommand(Command): # sure that self.caller is always the account if possible. We also create # a special property "character" for the puppeted object, if any. This # is convenient for commands defined on the Account only. - if hasattr(self, "account_caller") and self.account_caller: + if self.account_caller: if utils.inherits_from(self.caller, "evennia.objects.objects.DefaultObject"): # caller is an Object/Character self.character = self.caller @@ -169,6 +219,8 @@ class MuxCommand(Command): 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 += "cmd options (self.switch_options): |w%s|n\n" % self.switch_options + string += "cmd parse left/right using (self.rhs_split): |w%s|n\n" % self.rhs_split 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 @@ -193,18 +245,4 @@ class MuxAccountCommand(MuxCommand): character is actually attached to this Account and Session. """ - def parse(self): - """ - We run the parent parser as usual, then fix the result - """ - super(MuxAccountCommand, self).parse() - - if utils.inherits_from(self.caller, "evennia.objects.objects.DefaultObject"): - # caller is an Object/Character - self.character = self.caller - self.caller = self.caller.account - elif utils.inherits_from(self.caller, "evennia.accounts.accounts.DefaultAccount"): - # caller was already an Account - self.character = self.caller.get_puppet(self.session) - else: - self.character = None + account_caller = True # Using MuxAccountCommand explicitly defaults the caller to an account diff --git a/evennia/commands/default/system.py b/evennia/commands/default/system.py index 7bd092bad5..c454de9aff 100644 --- a/evennia/commands/default/system.py +++ b/evennia/commands/default/system.py @@ -245,6 +245,7 @@ class CmdPy(COMMAND_DEFAULT_CLASS): """ key = "@py" aliases = ["!"] + switch_options = ("time", "edit") locks = "cmd:perm(py) or perm(Developer)" help_category = "System" @@ -328,6 +329,7 @@ class CmdScripts(COMMAND_DEFAULT_CLASS): """ key = "@scripts" aliases = ["@globalscript", "@listscripts"] + switch_options = ("start", "stop", "kill", "validate") locks = "cmd:perm(listscripts) or perm(Admin)" help_category = "System" @@ -521,6 +523,7 @@ class CmdService(COMMAND_DEFAULT_CLASS): key = "@service" aliases = ["@services"] + switch_options = ("list", "start", "stop", "delete") locks = "cmd:perm(service) or perm(Developer)" help_category = "System" @@ -672,7 +675,7 @@ class CmdServerLoad(COMMAND_DEFAULT_CLASS): Usage: @server[/mem] - Switch: + Switches: mem - return only a string of the current memory usage flushmem - flush the idmapper cache @@ -703,6 +706,7 @@ class CmdServerLoad(COMMAND_DEFAULT_CLASS): """ key = "@server" aliases = ["@serverload", "@serverprocess"] + switch_options = ("mem", "flushmem") locks = "cmd:perm(list) or perm(Developer)" help_category = "System" diff --git a/evennia/commands/default/tests.py b/evennia/commands/default/tests.py index dba1d329cc..bf01ac3039 100644 --- a/evennia/commands/default/tests.py +++ b/evennia/commands/default/tests.py @@ -21,6 +21,7 @@ from mock import Mock, mock from evennia.commands.default.cmdset_character import CharacterCmdSet from evennia.utils.test_resources import EvenniaTest from evennia.commands.default import help, general, system, admin, account, building, batchprocess, comms +from evennia.commands.default.muxcommand import MuxCommand from evennia.commands.command import Command, InterruptCommand from evennia.utils import ansi, utils from evennia.server.sessionhandler import SESSIONS @@ -72,7 +73,6 @@ 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() @@ -126,9 +126,12 @@ class TestGeneral(CommandTest): self.call(general.CmdPose(), "looks around", "Char looks around") def test_nick(self): - self.call(general.CmdNick(), "testalias = testaliasedstring1", "Inputlinenick 'testalias' mapped to 'testaliasedstring1'.") - self.call(general.CmdNick(), "/account testalias = testaliasedstring2", "Accountnick 'testalias' mapped to 'testaliasedstring2'.") - self.call(general.CmdNick(), "/object testalias = testaliasedstring3", "Objectnick 'testalias' mapped to 'testaliasedstring3'.") + self.call(general.CmdNick(), "testalias = testaliasedstring1", + "Inputlinenick 'testalias' mapped to 'testaliasedstring1'.") + self.call(general.CmdNick(), "/account testalias = testaliasedstring2", + "Accountnick 'testalias' mapped to 'testaliasedstring2'.") + self.call(general.CmdNick(), "/object testalias = testaliasedstring3", + "Objectnick 'testalias' mapped to 'testaliasedstring3'.") self.assertEqual(u"testaliasedstring1", self.char1.nicks.get("testalias")) self.assertEqual(u"testaliasedstring2", self.char1.nicks.get("testalias", category="account")) self.assertEqual(None, self.char1.account.nicks.get("testalias", category="account")) @@ -138,6 +141,33 @@ class TestGeneral(CommandTest): self.call(general.CmdGet(), "Obj", "You pick up Obj.") self.call(general.CmdDrop(), "Obj", "You drop Obj.") + def test_give(self): + self.call(general.CmdGive(), "Obj to Char2", "You aren't carrying Obj.") + self.call(general.CmdGive(), "Obj = Char2", "You aren't carrying Obj.") + self.call(general.CmdGet(), "Obj", "You pick up Obj.") + self.call(general.CmdGive(), "Obj to Char2", "You give") + self.call(general.CmdGive(), "Obj = Char", "You give", caller=self.char2) + + def test_mux_command(self): + + class CmdTest(MuxCommand): + key = 'test' + switch_options = ('test', 'testswitch', 'testswitch2') + + def func(self): + self.msg("Switches matched: {}".format(self.switches)) + + self.call(CmdTest(), "/test/testswitch/testswitch2", "Switches matched: ['test', 'testswitch', 'testswitch2']") + self.call(CmdTest(), "/test", "Switches matched: ['test']") + self.call(CmdTest(), "/test/testswitch", "Switches matched: ['test', 'testswitch']") + self.call(CmdTest(), "/testswitch/testswitch2", "Switches matched: ['testswitch', 'testswitch2']") + self.call(CmdTest(), "/testswitch", "Switches matched: ['testswitch']") + self.call(CmdTest(), "/testswitch2", "Switches matched: ['testswitch2']") + self.call(CmdTest(), "/t", "test: Ambiguous switch supplied: " + "Did you mean /test or /testswitch or /testswitch2?|Switches matched: []") + self.call(CmdTest(), "/tests", "test: Ambiguous switch supplied: " + "Did you mean /testswitch or /testswitch2?|Switches matched: []") + def test_say(self): self.call(general.CmdSay(), "Testing", "You say, \"Testing\"") @@ -225,7 +255,8 @@ class TestAccount(CommandTest): self.call(account.CmdColorTest(), "ansi", "ANSI colors:", caller=self.account) def test_char_create(self): - self.call(account.CmdCharCreate(), "Test1=Test char", "Created new character Test1. Use @ic Test1 to enter the game", caller=self.account) + self.call(account.CmdCharCreate(), "Test1=Test char", + "Created new character Test1. Use @ic Test1 to enter the game", caller=self.account) def test_quell(self): self.call(account.CmdQuell(), "", "Quelling to current puppet's permissions (developer).", caller=self.account) @@ -234,7 +265,8 @@ class TestAccount(CommandTest): class TestBuilding(CommandTest): def test_create(self): name = settings.BASE_OBJECT_TYPECLASS.rsplit('.', 1)[1] - self.call(building.CmdCreate(), "/drop TestObj1", "You create a new %s: TestObj1." % name) + self.call(building.CmdCreate(), "/d TestObj1", # /d switch is abbreviated form of /drop + "You create a new %s: TestObj1." % name) def test_examine(self): self.call(building.CmdExamine(), "Obj", "Name/key: Obj") @@ -244,7 +276,8 @@ class TestBuilding(CommandTest): self.call(building.CmdSetObjAlias(), "Obj = TestObj1b", "Alias(es) for 'Obj(#4)' set to 'testobj1b'.") def test_copy(self): - self.call(building.CmdCopy(), "Obj = TestObj2;TestObj2b, TestObj3;TestObj3b", "Copied Obj to 'TestObj3' (aliases: ['TestObj3b']") + self.call(building.CmdCopy(), "Obj = TestObj2;TestObj2b, TestObj3;TestObj3b", + "Copied Obj to 'TestObj3' (aliases: ['TestObj3b']") def test_attribute_commands(self): self.call(building.CmdSetAttribute(), "Obj/test1=\"value1\"", "Created attribute Obj/test1 = 'value1'") @@ -287,7 +320,8 @@ class TestBuilding(CommandTest): def test_typeclass(self): self.call(building.CmdTypeclass(), "Obj = evennia.objects.objects.DefaultExit", - "Obj changed typeclass from evennia.objects.objects.DefaultObject to evennia.objects.objects.DefaultExit.") + "Obj changed typeclass from evennia.objects.objects.DefaultObject " + "to evennia.objects.objects.DefaultExit.") def test_lock(self): self.call(building.CmdLock(), "Obj = test:perm(Developer)", "Added lock 'test:perm(Developer)' to Obj.") @@ -297,15 +331,24 @@ class TestBuilding(CommandTest): expect = "One Match(#1#7, loc):\n " +\ "Char2(#7) evennia.objects.objects.DefaultCharacter (location: Room(#1))" self.call(building.CmdFind(), "Char2", expect, cmdstring="locate") + self.call(building.CmdFind(), "/ex Char2", # /ex is an ambiguous switch + "locate: Ambiguous switch supplied: Did you mean /exit or /exact?|" + expect, + cmdstring="locate") self.call(building.CmdFind(), "Char2", expect, cmdstring="@locate") - self.call(building.CmdFind(), "/loc Char2", expect, cmdstring="find") + self.call(building.CmdFind(), "/l Char2", expect, cmdstring="find") # /l switch is abbreviated form of /loc self.call(building.CmdFind(), "Char2", "One Match", cmdstring="@find") def test_script(self): self.call(building.CmdScript(), "Obj = scripts.Script", "Script scripts.Script successfully added") def test_teleport(self): - self.call(building.CmdTeleport(), "Room2", "Room2(#2)\n|Teleported to Room2.") + self.call(building.CmdTeleport(), "/quiet Room2", "Room2(#2)\n|Teleported to Room2.") + self.call(building.CmdTeleport(), "/t", # /t switch is abbreviated form of /tonone + "Cannot teleport a puppeted object (Char, puppeted by TestAccount(account 1)) to a Nonelocation.") + self.call(building.CmdTeleport(), "/l Room2", # /l switch is abbreviated form of /loc + "Destination has no location.") + self.call(building.CmdTeleport(), "/q me to Room2", # /q switch is abbreviated form of /quiet + "Char is already at Room2.") def test_spawn(self): def getObject(commandTest, objKeyStr): @@ -321,7 +364,7 @@ class TestBuilding(CommandTest): self.call(building.CmdSpawn(), " ", "Usage: @spawn") # Tests "@spawn " without specifying location. - self.call(building.CmdSpawn(), \ + self.call(building.CmdSpawn(), "{'key':'goblin', 'typeclass':'evennia.DefaultCharacter'}", "Spawned goblin") goblin = getObject(self, "goblin") @@ -340,8 +383,8 @@ class TestBuilding(CommandTest): # char1's default location in the future... spawnLoc = self.room1 - self.call(building.CmdSpawn(), \ - "{'prototype':'GOBLIN', 'key':'goblin', 'location':'%s'}" \ + self.call(building.CmdSpawn(), + "{'prototype':'GOBLIN', 'key':'goblin', 'location':'%s'}" % spawnLoc.dbref, "Spawned goblin") goblin = getObject(self, "goblin") self.assertEqual(goblin.location, spawnLoc) @@ -354,70 +397,81 @@ class TestBuilding(CommandTest): self.assertIsInstance(ball, DefaultObject) ball.delete() - # Tests "@spawn/noloc ..." without specifying a location. + # Tests "@spawn/n ..." without specifying a location. # Location should be "None". - self.call(building.CmdSpawn(), "/noloc 'BALL'", "Spawned Ball") + self.call(building.CmdSpawn(), "/n 'BALL'", "Spawned Ball") # /n switch is abbreviated form of /noloc ball = getObject(self, "Ball") self.assertIsNone(ball.location) ball.delete() # Tests "@spawn/noloc ...", but DO specify a location. # Location should be the specified location. - self.call(building.CmdSpawn(), \ - "/noloc {'prototype':'BALL', 'location':'%s'}" \ + self.call(building.CmdSpawn(), + "/noloc {'prototype':'BALL', 'location':'%s'}" % spawnLoc.dbref, "Spawned Ball") ball = getObject(self, "Ball") self.assertEqual(ball.location, spawnLoc) ball.delete() # test calling spawn with an invalid prototype. - self.call(building.CmdSpawn(), \ - "'NO_EXIST'", "No prototype named 'NO_EXIST'") + self.call(building.CmdSpawn(), "'NO_EXIST'", "No prototype named 'NO_EXIST'") class TestComms(CommandTest): def setUp(self): super(CommandTest, self).setUp() - self.call(comms.CmdChannelCreate(), "testchan;test=Test Channel", "Created channel testchan and connected to it.", receiver=self.account) + self.call(comms.CmdChannelCreate(), "testchan;test=Test Channel", + "Created channel testchan and connected to it.", receiver=self.account) def test_toggle_com(self): - self.call(comms.CmdAddCom(), "tc = testchan", "You are already connected to channel testchan. You can now", receiver=self.account) + self.call(comms.CmdAddCom(), "tc = testchan", + "You are already connected to channel testchan. You can now", receiver=self.account) self.call(comms.CmdDelCom(), "tc", "Your alias 'tc' for channel testchan was cleared.", receiver=self.account) def test_channels(self): - self.call(comms.CmdChannels(), "", "Available channels (use comlist,addcom and delcom to manage", receiver=self.account) + self.call(comms.CmdChannels(), "", + "Available channels (use comlist,addcom and delcom to manage", receiver=self.account) def test_all_com(self): - self.call(comms.CmdAllCom(), "", "Available channels (use comlist,addcom and delcom to manage", receiver=self.account) + self.call(comms.CmdAllCom(), "", + "Available channels (use comlist,addcom and delcom to manage", receiver=self.account) def test_clock(self): - self.call(comms.CmdClock(), "testchan=send:all()", "Lock(s) applied. Current locks on testchan:", receiver=self.account) + self.call(comms.CmdClock(), + "testchan=send:all()", "Lock(s) applied. Current locks on testchan:", receiver=self.account) def test_cdesc(self): - self.call(comms.CmdCdesc(), "testchan = Test Channel", "Description of channel 'testchan' set to 'Test Channel'.", receiver=self.account) + self.call(comms.CmdCdesc(), "testchan = Test Channel", + "Description of channel 'testchan' set to 'Test Channel'.", receiver=self.account) def test_cemit(self): - self.call(comms.CmdCemit(), "testchan = Test Message", "[testchan] Test Message|Sent to channel testchan: Test Message", receiver=self.account) + self.call(comms.CmdCemit(), "testchan = Test Message", + "[testchan] Test Message|Sent to channel testchan: Test Message", receiver=self.account) def test_cwho(self): self.call(comms.CmdCWho(), "testchan", "Channel subscriptions\ntestchan:\n TestAccount", receiver=self.account) def test_page(self): - self.call(comms.CmdPage(), "TestAccount2 = Test", "TestAccount2 is offline. They will see your message if they list their pages later.|You paged TestAccount2 with: 'Test'.", receiver=self.account) + self.call(comms.CmdPage(), "TestAccount2 = Test", + "TestAccount2 is offline. They will see your message if they list their pages later." + "|You paged TestAccount2 with: 'Test'.", receiver=self.account) def test_cboot(self): # No one else connected to boot self.call(comms.CmdCBoot(), "", "Usage: @cboot[/quiet] = [:reason]", receiver=self.account) def test_cdestroy(self): - self.call(comms.CmdCdestroy(), "testchan", "[testchan] TestAccount: testchan is being destroyed. Make sure to change your aliases.|Channel 'testchan' was destroyed.", receiver=self.account) + self.call(comms.CmdCdestroy(), "testchan", + "[testchan] TestAccount: testchan is being destroyed. Make sure to change your aliases." + "|Channel 'testchan' was destroyed.", receiver=self.account) class TestBatchProcess(CommandTest): def test_batch_commands(self): # cannot test batchcode here, it must run inside the server process - self.call(batchprocess.CmdBatchCommands(), "example_batch_cmds", "Running Batchcommand processor Automatic mode for example_batch_cmds") + self.call(batchprocess.CmdBatchCommands(), "example_batch_cmds", + "Running Batchcommand processor Automatic mode for example_batch_cmds") # we make sure to delete the button again here to stop the running reactor confirm = building.CmdDestroy.confirm building.CmdDestroy.confirm = False diff --git a/evennia/contrib/extended_room.py b/evennia/contrib/extended_room.py index 755c2ac22e..6cacca980c 100644 --- a/evennia/contrib/extended_room.py +++ b/evennia/contrib/extended_room.py @@ -367,6 +367,7 @@ class CmdExtendedDesc(default_cmds.CmdDesc): """ aliases = ["describe", "detail"] + switch_options = () # Inherits from default_cmds.CmdDesc, but unused here def reset_times(self, obj): """By deleteting the caches we force a re-load.""" diff --git a/evennia/objects/objects.py b/evennia/objects/objects.py index 6cb948da03..1c9b2dcafd 100644 --- a/evennia/objects/objects.py +++ b/evennia/objects/objects.py @@ -569,8 +569,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): except Exception: logger.log_trace() - - if text and not (isinstance(text, basestring) or isinstance(text, tuple)): + if not (isinstance(text, basestring) or isinstance(text, tuple)): # sanitize text before sending across the wire try: text = to_str(text, force_string=True)