diff --git a/LICENSE b/LICENSE index cee8df8309..af657ab518 100644 --- a/LICENSE +++ b/LICENSE @@ -30,7 +30,7 @@ Definitions 5. You may charge a distribution fee for any distribution of this Package. If you offer support for this Package, you may charge any fee you choose for that support. You may not charge a license fee for the right to use this Package itself. You may distribute this Package in aggregate with other (possibly commercial and possibly nonfree) programs as part of a larger (possibly commercial and possibly nonfree) software distribution, and charge license fees for other parts of that software distribution, provided that you do not advertise this Package as a product of your own. If the Package includes an interpreter, You may embed this Package's interpreter within an executable of yours (by linking); this shall be construed as a mere form of aggregation, provided that the complete Standard Version of the interpreter is so embedded. -6. The scripts and library files supplied as input to or produced as output from the programs of this Package do not automatically fall under the copyright of this Package, but belong to whoever generated them, and may be sold commercially, and may be aggregated with this Package. If such scripts or library files are aggregated with this Package via the so-called "undump" or "unexec" methods of producing a binary executable image, then distribution of such an image shall neither be construed as a distribution of this Package nor shall it fall under the restrictions of Paragraphs 3 and 4, provided that you do not represent such an executable image as a Standard Version of this Package. +6. The scripts and library files (especially the game-specific files created for use with the Package) supplied as input to or produced as output from the programs of this Package do not automatically fall under the copyright of this Package, but belong to whoever generated them, and may be sold commercially, and may be aggregated with this Package. If such scripts or library files are aggregated with this Package via the so-called "undump" or "unexec" methods of producing a binary executable image, then distribution of such an image shall neither be construed as a distribution of this Package nor shall it fall under the restrictions of Paragraphs 3 and 4, provided that you do not represent such an executable image as a Standard Version of this Package. 7. C subroutines (or comparably compiled subroutines in other languages) supplied by you and linked into this Package in order to emulate subroutines and variables of the language defined by this Package shall not be considered part of this Package, but are the equivalent of input as in Paragraph 6, provided these subroutines do not change the language in any way that would cause it to fail the regression tests for the language. diff --git a/src/commands/default/building.py b/src/commands/default/building.py index bd475243d0..c5bc9dcaae 100644 --- a/src/commands/default/building.py +++ b/src/commands/default/building.py @@ -74,7 +74,7 @@ class CmdSetObjAlias(MuxCommand): Adding permanent aliases Usage: - @alias = [alias[,alias,alias,...]] + @alias [= [alias[,alias,alias,...]]] Assigns aliases to an object so it can be referenced by more than one name. Assign empty to remove all aliases from object. @@ -91,11 +91,15 @@ class CmdSetObjAlias(MuxCommand): def func(self): "Set the aliases." + caller = self.caller - objname, aliases = self.lhs, self.rhslist - if not objname: - caller.msg("Usage: @alias = ,,...") - return + + if not self.lhs: + string = "Usage: @alias [= [alias[,alias ...]]]" + self.caller.msg(string) + return + objname = self.lhs + # Find the object to receive aliases obj = caller.search(objname, global_search=True) if not obj: @@ -108,10 +112,12 @@ class CmdSetObjAlias(MuxCommand): else: caller.msg("No aliases exist for '%s'." % obj.key) return + if not obj.access(caller, 'edit'): caller.msg("You don't have permission to do that.") return - if not aliases or not aliases[0]: + + if not self.rhs: # we have given an empty =, so delete aliases old_aliases = obj.aliases if old_aliases: @@ -120,10 +126,10 @@ class CmdSetObjAlias(MuxCommand): else: caller.msg("No aliases to clear.") return + # merge the old and new aliases (if any) old_aliases = obj.aliases - new_aliases = [str(alias).strip().lower() - for alias in aliases if alias.strip()] + new_aliases = [alias.strip().lower() for alias in self.rhs.split(',') if alias.strip()] # make the aliases only appear once old_aliases.extend(new_aliases) aliases = list(set(old_aliases)) @@ -180,8 +186,7 @@ class CmdCopy(ObjManipCommand): from_obj = caller.search(from_obj_name) if not from_obj: return - for objdef in self.rhs_objs: - print objdef.items() + for objdef in self.rhs_objs: # loop through all possible copy-to targets to_obj_name = objdef['name'] to_obj_aliases = objdef['aliases'] @@ -202,16 +207,18 @@ class CmdCopy(ObjManipCommand): # we are done, echo to user caller.msg(string) -# NOT YET INCLUDED IN SET. -class CmdCpAttr(MuxCommand): +class CmdCpAttr(ObjManipCommand): """ @cpattr - copy attributes Usage: - @cpattr / = / [,/,/,...] - @cpattr / = [,,,...] - @cpattr = / [,/,/,...] - @cpattr = [,,,...] + @cpattr[/switch] / = / [,/,/,...] + @cpattr[/switch] / = [,,,...] + @cpattr[/switch] = / [,/,/,...] + @cpattr[/switch] = [,,,...] + + Switches: + move - delete the attribute from the source object after copying. Example: @cpattr coolness = Anna/chillout, Anna/nicety, Tom/nicety @@ -219,7 +226,8 @@ class CmdCpAttr(MuxCommand): copies the coolness attribute (defined on yourself), to attributes on Anna and Tom. - Copy the attribute one object to one or more attributes on another object. + Copy the attribute one object to one or more attributes on another object. If + you don't supply a source object, yourself is used. """ key = "@cpattr" locks = "cmd:perm(cpattr) or perm(Builders)" @@ -233,10 +241,10 @@ class CmdCpAttr(MuxCommand): if not self.rhs: string = """Usage: - @cpattr / = / [,/,/,...] - @cpattr / = [,,,...] - @cpattr = / [,/,/,...] - @cpattr = [,,,...]""" + @cpattr[/switch] / = / [,/,/,...] + @cpattr[/switch] / = [,,,...] + @cpattr[/switch] = / [,/,/,...] + @cpattr[/switch] = [,,,...]""" caller.msg(string) return @@ -248,19 +256,26 @@ class CmdCpAttr(MuxCommand): if not from_obj_attrs: # this means the from_obj_name is actually an attribute name on self. from_obj_attrs = [from_obj_name] - from_obj = self - from_obj_name = self.name + from_obj = self.caller + from_obj_name = self.caller.name else: from_obj = caller.search(from_obj_name) if not from_obj or not to_objs: - caller.msg("Have to supply both source object and target(s).") + caller.msg("You have to supply both source object and target(s).") return - srcvalue = from_obj.attr(from_obj_attrs[0]) + if not from_obj.has_attribute(from_obj_attrs[0]): + caller.msg("%s doesn't have an attribute %s." % (from_obj_name, from_obj_attrs[0])) + return + srcvalue = from_obj.get_attribute(from_obj_attrs[0]) #copy to all to_obj:ects - string = "Copying %s=%s (with value %s) ..." % (from_obj_name, - from_obj_attrs[0], srcvalue) - for to_obj in to_objs: + if "move" in self.switches: + string = "Moving " + else: + string = "Copying " + string += "%s/%s (with value %s) ..." % (from_obj_name, from_obj_attrs[0], srcvalue) + + for to_obj in to_objs: to_obj_name = to_obj['name'] to_obj_attrs = to_obj['attrs'] to_obj = caller.search(to_obj_name) @@ -274,12 +289,54 @@ class CmdCpAttr(MuxCommand): # if there are too few attributes given # on the to_obj, we copy the original name instead. to_attr = from_attr - to_obj.attr(to_attr, srcvalue) - string += "\nCopied %s.%s -> %s.%s." % (from_obj.name, from_attr, - to_obj_name, to_attr) + to_obj.set_attribute(to_attr, srcvalue) + if "move" in self.switches and not (from_obj == to_obj and from_attr == to_attr): + from_obj.del_attribute(from_attr) + string += "\nMoved %s.%s -> %s.%s." % (from_obj.name, from_attr, + to_obj_name, to_attr) + else: + string += "\nCopied %s.%s -> %s.%s." % (from_obj.name, from_attr, + to_obj_name, to_attr) caller.msg(string) +class CmdMvAttr(ObjManipCommand): + """ + @mvattr - move attributes + Usage: + @mvattr[/switch] / = / [,/,/,...] + @mvattr[/switch] / = [,,,...] + @mvattr[/switch] = / [,/,/,...] + @mvattr[/switch] = [,,,...] + + Switches: + copy - Don't delete the original after moving. + + Move an attribute from one object to one or more attributes on another object. If + you don't supply a source object, yourself is used. + """ + key = "@mvattr" + locks = "cmd:perm(mvattr) or perm(Builders)" + help_category = "Building" + + def func(self): + """ + Do the moving + """ + if not self.rhs: + string = """Usage: + @mvattr[/switch] / = / [,/,/,...] + @mvattr[/switch] / = [,,,...] + @mvattr[/switch] = / [,/,/,...] + @mvattr[/switch] = [,,,...]""" + self.caller.msg(string) + return + + # simply use @cpattr for all the functionality + if "copy" in self.switches: + self.caller.execute_cmd("@cpattr %s" % self.args) + else: + self.caller.execute_cmd("@cpattr/move %s" % self.args) class CmdCreate(ObjManipCommand): """ @@ -492,20 +549,21 @@ class CmdDestroy(MuxCommand): for objname in self.lhslist: obj = caller.search(objname) if not obj: - continue + self.caller.msg(" (Objects to destroy must either be local or specified with a unique dbref.)") + return objname = obj.name if not obj.access(caller, 'delete'): - string = "You don't have permission to delete %s." % objname + string += "\nYou don't have permission to delete %s." % objname continue if obj.player and not 'override' in self.switches: - string = "Object %s is controlled by an active player. Use /override to delete anyway." % objname + string += "\nObject %s is controlled by an active player. Use /override to delete anyway." % objname continue # do the deletion okay = obj.delete() if not okay: - string = "ERROR: %s NOT deleted, probably because at_obj_delete() returned False." % objname + string += "\nERROR: %s not deleted, probably because at_obj_delete() returned False." % objname else: - string = "%s was deleted." % objname + string += "\n%s was deleted." % objname if string: caller.msg(string.strip()) @@ -659,7 +717,7 @@ class CmdLink(MuxCommand): @link[/switches] Switch: - twoway - connect two exits. For this to, BOTH + twoway - connect two exits. For this to work, BOTH and must be exit objects. If is an exit, set its destination to . Two-way operation @@ -803,7 +861,7 @@ class CmdHome(CmdLink): if not home: string = "This object has no home location set!" else: - string = "%s's home is set to %s(%s)." % (obj, home, home.dbref) + string = "%s's current home is %s(%s)." % (obj, home, home.dbref) else: # set a home location new_home = self.caller.search(self.rhs, global_search=True) @@ -845,79 +903,6 @@ class CmdListCmdSets(MuxCommand): string = "%s" % obj.cmdset caller.msg(string) - -class CmdMvAttr(ObjManipCommand): - """ - @mvattr - move attributes - - Usage: - @mvattr /attr[/attr/attr...] = [/attr/attr/...] - - Moves attributes around. If the target object's attribute names are given, - the source attributes will be moved into those attributes instead. The - old attribute(s) will be deleted from the source object (unless source - and target are the same, in which case this is like a copy operation) - """ - key = "@mvattr" - locks = "cmd:perm(mvattr) or perm(Builders)" - help_category = "Building" - - def func(self): - "We use the parsed values from ObjManipCommand.parse()." - - caller = self.caller - - if not self.lhs or not self.rhs: - caller.msg("Usage: @mvattr /attr[/attr/..] = [/attr/attr..]") - return - - from_obj_name = self.lhs_objattr[0]['name'] - from_obj_attrs = self.lhs_objattr[0]['attrs'] - to_obj_name = self.rhs_objattr[0]['name'] - to_obj_attrs = self.rhs_objattr[0]['name'] - - # find from-object - from_obj = caller.search(from_obj_name) - if not from_obj: - return - #find to-object - to_obj = caller.search_for_object(to_obj_name) - if not to_obj: - return - - # if we copy on the same object, we have to - # be more careful. - same_object = to_obj == from_obj - - #do the moving - string = "" - for inum, from_attr in enumerate(from_obj_attrs): - from_value = from_obj.attr(from_attr) - if not from_value: - string += "\nAttribute '%s' not found on source object %s." - string = string % (from_attr, from_obj.name) - else: - try: - to_attr = to_obj_attrs[inum] - except KeyError: - # too few attributes on the target, so we add the - # source attrname instead - if same_object: - # we can't do that on the same object though, - # it would be just copying to itself. - string += "\nToo few attribute names on target, and " - string += "can't copy same-named attribute to itself." - continue - to_attr = from_attr - # Do the move - to_obj.attr(to_attr, from_value) - from_obj.attr(from_attr, delete=True) - string += "\nMoved %s.%s -> %s.%s." % (from_obj_name, from_attr, - to_obj_name, to_attr) - caller.msg(string) - - - class CmdName(ObjManipCommand): """ cname - change the name and/or aliases of an object @@ -1410,7 +1395,7 @@ class CmdExamine(ObjManipCommand): If object is not specified, the current location is examined. """ key = "@examine" - aliases = ["@ex","ex", "exam"] + aliases = ["@ex","ex", "exam", "examine"] locks = "cmd:perm(examine) or perm(Builders)" help_category = "Building" @@ -1617,7 +1602,7 @@ class CmdFind(MuxCommand): else: # Not a player/dbref search but a wider search; build a queryset. - results = ObjectDB.objects.filter(db_key__istartswith=searchstring, id__gte=low, id__lte=high) + results = ObjectDB.objects.filter(db_key__istartswith=searchstring, id__gte=low, id__lte=high) if "room" in switches: results = results.filter(db_location__isnull=True) if "exit" in switches: @@ -1625,11 +1610,24 @@ class CmdFind(MuxCommand): if "char" in switches: results = results.filter(db_typeclass_path=CHAR_TYPECLASS) nresults = results.count() + if not nresults: + # no matches on the keys. Try aliases instead. + results = results = ObjectDB.alias_set.related.model.objects.filter(db_key=searchstring) + if "room" in switches: + results = results.filter(db_obj__db_location__isnull=True) + if "exit" in switches: + results = results.filter(db_obj__db_destination__isnull=False) + if "char" in switches: + results = results.filter(db_obj__db_typeclass_path=CHAR_TYPECLASS) + # we have to parse alias -> real object here + results = [result.db_obj for result in results] + nresults = len(results) + restrictions = "" if self.switches: restrictions = ", %s" % (",".join(self.switches)) if nresults: - # convert result to typeclasses. Database is not hit until this point! + # convert result to typeclasses. results = [result.typeclass(result) for result in results] if nresults > 1: string = "{w%i Matches{n(#%i-#%i%s):" % (nresults, low, high, restrictions) diff --git a/src/commands/default/cmdset_default.py b/src/commands/default/cmdset_default.py index d4ae6552de..5b23b6a1d6 100644 --- a/src/commands/default/cmdset_default.py +++ b/src/commands/default/cmdset_default.py @@ -64,10 +64,10 @@ class DefaultCmdSet(CmdSet): self.add(building.CmdSetAttribute()) self.add(building.CmdName()) self.add(building.CmdDesc()) - #self.add(building.CmdCpAttr()) #TODO - need testing/debugging - #self.add(building.CmdMvAttr()) #TODO - need testing/debugging + self.add(building.CmdCpAttr()) + self.add(building.CmdMvAttr()) + self.add(building.CmdCopy()) self.add(building.CmdFind()) - self.add(building.CmdCopy()) #TODO - need testing/debugging self.add(building.CmdOpen()) self.add(building.CmdLink()) self.add(building.CmdUnLink()) diff --git a/src/commands/default/comms.py b/src/commands/default/comms.py index d073d32493..2c77617b22 100644 --- a/src/commands/default/comms.py +++ b/src/commands/default/comms.py @@ -9,14 +9,15 @@ from src.utils import create, utils from src.commands.default.muxcommand import MuxCommand from src.server.sessionhandler import SESSIONS -def find_channel(caller, channelname, silent=False): +def find_channel(caller, channelname, silent=False, noaliases=False): """ Helper function for searching for a single channel with some error handling. """ channels = Channel.objects.channel_search(channelname) if not channels: - channels = [chan for chan in Channel.objects.all() if channelname in chan.aliases] + if not noaliases: + channels = [chan for chan in Channel.objects.all() if channelname in chan.aliases] if channels: return channels[0] if not silent: @@ -126,7 +127,7 @@ class CmdDelCom(MuxCommand): return ostring = self.args.lower() - channel = find_channel(caller, ostring, silent=True) + channel = find_channel(caller, ostring, silent=True, noaliases=True) if channel: # we have given a channel name - unsubscribe if not channel.has_connection(player): @@ -147,9 +148,12 @@ class CmdDelCom(MuxCommand): if not channel: caller.msg("No channel with alias '%s' was found." % ostring) else: - caller.nicks.delete(ostring, nick_type="channel") - caller.msg("Your alias '%s' for channel %s was cleared." % (ostring, channel.key)) - + if caller.nicks.has(ostring, nick_type="channel"): + caller.nicks.delete(ostring, nick_type="channel") + caller.msg("Your alias '%s' for channel %s was cleared." % (ostring, channel.key)) + else: + caller.msg("You had no such alias defined for this channel.") + class CmdAllCom(MuxCommand): """ allcom - operate on all channels diff --git a/src/commands/default/tests.py b/src/commands/default/tests.py index 8a5f14add0..02c390a47d 100644 --- a/src/commands/default/tests.py +++ b/src/commands/default/tests.py @@ -23,6 +23,7 @@ from src.utils import create, ansi from src.server import session, sessionhandler from src.locks.lockhandler import LockHandler from src.server.models import ServerConfig +from src.comms.models import Channel, Msg, PlayerChannelConnection #------------------------------------------------------------ # Command testing @@ -30,7 +31,6 @@ from src.server.models import ServerConfig # print all feedback from test commands (can become very verbose!) VERBOSE = False -NOMANGLE = False class FakeSession(session.Session): """ @@ -75,6 +75,9 @@ class CommandTest(TestCase): Inherit new tests from this. """ + + NOMANGLE = False # mangle command input for extra testing + def setUp(self): "sets up the testing environment" ServerConfig.objects.conf("default_home", 2) @@ -107,7 +110,7 @@ class CommandTest(TestCase): self.obj2 = create.create_object(settings.BASE_OBJECT_TYPECLASS, key="obj2", location=self.room1) self.exit1 = create.create_object(settings.BASE_EXIT_TYPECLASS, key="exit1", location=self.room1) self.exit2 = create.create_object(settings.BASE_EXIT_TYPECLASS, key="exit2", location=self.room2) - + def get_cmd(self, cmd_class, argument_string=""): """ Obtain a cmd instance from a class and an input string @@ -121,13 +124,13 @@ class CommandTest(TestCase): cmd.obj = self.char1 return cmd - def execute_cmd(self, raw_string, wanted_return_string=None): + def execute_cmd(self, raw_string, wanted_return_string=None, nomangle=False): """ Creates the command through faking a normal command call; This also mangles the input in various ways to test if the command will be fooled. """ - if not VERBOSE and not NOMANGLE: + if not nomangle and not VERBOSE and not self.NOMANGLE: # only mangle if not VERBOSE, to make fewer return lines test1 = re.sub(r'\s', '', raw_string) # remove all whitespace inside it test2 = "%s/åäö öäö;-:$£@*~^' 'test" % raw_string # inserting weird characters in call @@ -142,11 +145,21 @@ class CommandTest(TestCase): except AssertionError, e: self.fail(e) self.char1.ndb.return_string = None + +class BuildTest(CommandTest): + """ + We need to turn of mangling for build commands since + it creates arbitrary objects that mess up tests later. + """ + NOMANGLE = True + + + #------------------------------------------------------------ # Default set Command testing #------------------------------------------------------------ -# general.py tests +# # general.py tests class TestLook(CommandTest): def test_call(self): @@ -175,9 +188,9 @@ class TestNick(CommandTest): self.execute_cmd("nickname testalias = testaliasedstring1") self.execute_cmd("nickname/player testalias = testaliasedstring2") self.execute_cmd("nickname/object testalias = testaliasedstring3") - self.assertEquals(u"testaliasedstring1", self.char1.nicks.get("testalias")) - self.assertEquals(u"testaliasedstring2", self.char1.nicks.get("testalias",nick_type="player")) - self.assertEquals(u"testaliasedstring3", self.char1.nicks.get("testalias",nick_type="object")) + self.assertEqual(u"testaliasedstring1", self.char1.nicks.get("testalias")) + self.assertEqual(u"testaliasedstring2", self.char1.nicks.get("testalias",nick_type="player")) + self.assertEqual(u"testaliasedstring3", self.char1.nicks.get("testalias",nick_type="object")) class TestGet(CommandTest): def test_call(self): self.obj1.location = self.room1 @@ -207,8 +220,7 @@ class TestEncoding(CommandTest): class TestHelpSystem(CommandTest): def test_call(self): - global NOMANGLE - NOMANGLE = True + self.NOMANGLE = True sep = "-"*78 + "\n" self.execute_cmd("@help/add TestTopic,TestCategory = Test1", ) self.execute_cmd("help TestTopic",sep + "Help topic for Testtopic\nTest1" + "\n" + sep) @@ -218,7 +230,6 @@ class TestHelpSystem(CommandTest): self.execute_cmd("help TestTopic",sep + "Help topic for Testtopic\nTest1 Test2\n\nTest3") self.execute_cmd("@help/delete TestTopic","Deleted the help entry") self.execute_cmd("help TestTopic","No help entry found for 'TestTopic'") - NOMANGLE = False # system.py command tests class TestPy(CommandTest): @@ -265,7 +276,7 @@ class TestUserPassword(CommandTest): class TestPerm(CommandTest): def test_call(self): self.execute_cmd("@perm TestChar2 = Builders", "Permission 'Builders' given to") -# cannot test this at the moment, screws up the test suite +# cannot test this here; screws up the test suite #class TestPuppet(CommandTest): # def test_call(self): # self.execute_cmd("@puppet TestChar3", "You now control TestChar3.") @@ -274,10 +285,161 @@ class TestWall(CommandTest): def test_call(self): self.execute_cmd("@wall = This is a test message", "TestChar shouts") + # building.py command tests -class TestScript(CommandTest): +class TestObjAlias(BuildTest): + def test_call(self): + self.execute_cmd("@alias obj1 = obj1alias, obj1alias2", "Aliases for") + self.execute_cmd("look obj1alias2", "obj1") +class TestCopy(BuildTest): + def test_call(self): + self.execute_cmd("@copy obj1 = obj1_copy;alias1;alias2", "Copied obj1 to 'obj1_copy'") + self.execute_cmd("look alias2","obj1_copy") +class TestSet(BuildTest): + def test_call(self): + self.execute_cmd("@set obj1/test = value", "Created attribute obj1/test = value") + self.execute_cmd("@set obj1/test", "Attribute obj1/test = value") + self.assertEqual(self.obj1.db.test, u"value") +class TestCpAttr(BuildTest): + def test_call(self): + self.execute_cmd("@set obj1/test = value") + self.execute_cmd("@set me/test2 = value2") + self.execute_cmd("@cpattr obj1/test = obj2/test") + self.execute_cmd("@cpattr test2 = obj2") + self.assertEqual(self.obj2.db.test, u"value") + self.assertEqual(self.obj2.db.test2, u"value2") +class TestMvAttr(BuildTest): + def test_call(self): + self.execute_cmd("@set obj1/test = value") + self.execute_cmd("@mvattr obj1/test = obj2") + self.assertEqual(self.obj2.db.test, u"value") + self.assertEqual(self.obj1.db.test, None) +class TestCreate(BuildTest): + def test_call(self): + self.execute_cmd("@create testobj;alias1;alias2") + self.execute_cmd("look alias1", "testobj") +class TestDebug(BuildTest): + def test_call(self): + self.execute_cmd("@debug/obj obj1") +class TestDesc(BuildTest): + def test_call(self): + self.execute_cmd("@desc obj1 = Test object", "The description was set on") + self.assertEqual(self.obj1.db.desc, u"Test object") +class TestDestroy(BuildTest): + def test_call(self): + self.execute_cmd("@destroy obj1, obj2", "obj1 was deleted.\nobj2 was deleted") +class TestFind(BuildTest): + def test_call(self): + self.execute_cmd("@find obj1", "One Match") +class TestDig(BuildTest): + def test_call(self): + self.execute_cmd("@dig room3;roomalias1;roomalias2 = north;n,south;s") + self.execute_cmd("@find room3", "One Match") + self.execute_cmd("@find roomalias1", "One Match") + self.execute_cmd("@find roomalias2", "One Match") + self.execute_cmd("@find/room roomalias2", "One Match") + self.execute_cmd("@find/exit south", "One Match") + self.execute_cmd("@find/exit n", "One Match") +class TestUnLink(BuildTest): + def test_call(self): + self.execute_cmd("@dig room3;roomalias1, north, south") + self.execute_cmd("@unlink north") +class TestLink(BuildTest): + def test_call(self): + self.execute_cmd("@dig room3;roomalias1, north, south") + self.execute_cmd("@unlink north") + self.execute_cmd("@link north = room3") +class TestHome(BuildTest): + def test_call(self): + self.obj1.db_home = self.obj2.dbobj + self.obj1.save() + self.execute_cmd("@home obj1") + self.assertEqual(self.obj1.db_home, self.obj2.dbobj) +class TestCmdSets(BuildTest): + def test_call(self): + self.execute_cmd("@cmdsets") + self.execute_cmd("@cmdsets obj1") +class TestDesc(BuildTest): + def test_call(self): + self.execute_cmd("@name obj1 = Test object", "Object's name changed to 'Test object'.") + self.assertEqual(self.obj1.key, u"Test object") +class TestOpen(BuildTest): + def test_call(self): + self.execute_cmd("@dig room4;roomalias4") + self.execute_cmd("@open testexit4;aliasexit4 = roomalias4", "Created new Exit") +class TestScript(BuildTest): + def test_call(self): + self.execute_cmd("@typeclass obj1 = src.objects.objects.Character", "obj's type is now") + self.assertEqual(self.obj1.db_typeclass_path, u"src.objects.objects.Character") +class TestScript(BuildTest): + def test_call(self): + self.execute_cmd("@set box1/test = value") + self.execute_cmd("@wipe box1", "Wiped") + self.assertEqual(self.obj1.db.all(), []) +class TestLock(BuildTest): + # lock functionality itseld is tested separately + def test_call(self): + self.char1.permissions = ["TestPerm"] + self.execute_cmd("@lock obj1 = test:perm(TestPerm)") + self.assertEqual(True, self.obj1.access(self.char1, u"test")) +class TestExamine(BuildTest): + def test_call(self): + self.execute_cmd("examine obj1", "------------") +class TestTeleport(BuildTest): + def test_call(self): + self.execute_cmd("@tel obj1 = obj2") + self.assertEqual(self.obj1.location, self.obj2.dbobj) +class TestScript(BuildTest): def test_call(self): self.execute_cmd("@script TestChar = examples.bodyfunctions.BodyFunctions", "Script successfully added") -#TODO +# Comms commands + +class TestChannelCreate(CommandTest): + def test_call(self): + self.execute_cmd("@ccreate testchannel1;testchan1;testchan1b = This is a test channel") + self.execute_cmd("testchan1 Hello", "[testchannel1] TestChar: Hello") +class TestAddCom(CommandTest): + def test_call(self): + self.execute_cmd("@ccreate testchannel1;testchan1;testchan1b = This is a test channel") + self.execute_cmd("addcom chan1 = testchannel1") + self.execute_cmd("addcom chan2 = testchan1") + self.execute_cmd("delcom testchannel1") + self.execute_cmd("addcom testchannel1" "You now listen to the channel channel.") + +class TestDelCom(CommandTest): + def test_call(self): + self.execute_cmd("@ccreate testchannel1;testchan1;testchan1b = This is a test channel") + self.execute_cmd("addcom chan1 = testchan1") + self.execute_cmd("addcom chan2 = testchan1b") + self.execute_cmd("addcom chan3 = testchannel1") + self.execute_cmd("delcom chan1", "Your alias 'chan1' for ") + self.execute_cmd("delcom chan2", "Your alias 'chan2' for ") + self.execute_cmd("delcom testchannel1" "You stop listening to") +class TestAllCom(CommandTest): + def test_call(self): + self.execute_cmd("@ccreate testchannel1;testchan1;testchan1b = This is a test channel") + self.execute_cmd("@ccreate testchannel1;testchan1;testchan1b = This is a test channel") + self.execute_cmd("allcom off") + self.execute_cmd("allcom on") + self.execute_cmd("allcom destroy") +class TestChannels(CommandTest): + def test_call(self): + self.execute_cmd("@ccreate testchannel1;testchan1;testchan1b = This is a test channel") + self.execute_cmd("@cdestroy testchannel1", "Channel 'testchannel1'") +class TestCBoot(CommandTest): + def test_call(self): + self.execute_cmd("@ccreate testchannel1;testchan1;testchan1b = This is a test channel") + self.execute_cmd("@cboot testchannel1 = TestChar", "TestChar boots TestChar from channel.") +class TestCemit(CommandTest): + def test_call(self): + self.execute_cmd("@ccreate testchannel1;testchan1;testchan1b = This is a test channel") + self.execute_cmd("@cemit testchan1 = Testing!", "[testchannel1] Testing!") +class TestCwho(CommandTest): + def test_call(self): + self.execute_cmd("@ccreate testchannel1;testchan1;testchan1b = This is a test channel") + self.execute_cmd("@cwho testchan1b", "Channel subscriptions") + +# Unloggedin commands +# these cannot be tested from here. diff --git a/src/objects/manager.py b/src/objects/manager.py index bcf89d486f..ff7bb3215e 100644 --- a/src/objects/manager.py +++ b/src/objects/manager.py @@ -6,6 +6,7 @@ from django.contrib.auth.models import User from django.db.models.fields import exceptions from src.typeclasses.managers import TypedObjectManager from src.typeclasses.managers import returns_typeclass, returns_typeclass_list +from src.utils import utils # Try to use a custom way to parse id-tagged multimatches. IDPARSER_PATH = getattr(settings, 'ALTERNATE_OBJECT_SEARCH_MULTIMATCH_PARSER', 'src.objects.object_search_funcs') @@ -60,7 +61,7 @@ class ObjectManager(TypedObjectManager): the search criterion (e.g. in local_and_global_search). search_string: (string) The name or dbref to search for. """ - search_string = str(search_string).lstrip('*') + search_string = utils.to_unicode(search_string).lstrip('*') dbref = self.dbref(search_string) if not dbref: # not a dbref. Search by name. @@ -99,7 +100,7 @@ class ObjectManager(TypedObjectManager): if exact: return [attr.obj for attr in attrs if attribute_value == attr.value] else: - return [attr.obj for attr in attrs if str(attribute_value) in str(attr.value)] + return [attr.obj for attr in attrs if utils.to_unicode(attribute_value) in str(attr.value)] @returns_typeclass_list def get_objs_with_db_property(self, property_name, location=None): @@ -144,10 +145,12 @@ class ObjectManager(TypedObjectManager): lstring_key = ", db_location=location" lstring_alias = ", db_obj__db_location=location" if exact: - estring = "iexact" - matches = eval("self.filter(db_key__%s=ostring%s)" % (estring, lstring_key)) + estring = "__iexact" + else: + estring = "__istartswith" + matches = eval("self.filter(db_key%s=ostring%s)" % (estring, lstring_key)) if not matches: - alias_matches = eval("self.model.alias_set.related.model.objects.filter(db_key__%s=ostring%s)" % (estring, lstring_alias)) + alias_matches = eval("self.model.alias_set.related.model.objects.filter(db_key%s=ostring%s)" % (estring, lstring_alias)) matches = [alias.db_obj for alias in alias_matches] return matches @@ -205,7 +208,7 @@ class ObjectManager(TypedObjectManager): # Test if we are looking for a player object - if str(ostring).startswith("*"): + if utils.to_unicode(ostring).startswith("*"): # Player search - try to find obj by its player's name player_match = self.get_object_with_player(ostring) if player_match is not None: diff --git a/src/objects/models.py b/src/objects/models.py index dc62a152b7..34f9f3ee11 100644 --- a/src/objects/models.py +++ b/src/objects/models.py @@ -25,7 +25,7 @@ from src.server.models import ServerConfig from src.commands.cmdsethandler import CmdSetHandler from src.scripts.scripthandler import ScriptHandler from src.utils import logger -from src.utils.utils import is_iter +from src.utils.utils import is_iter, to_unicode FULL_PERSISTENCE = settings.FULL_PERSISTENCE @@ -151,6 +151,9 @@ class NickHandler(object): return nick else: return Nick.objects.filter(db_obj=self.obj) + def has(self, nick, nick_type="inputline"): + "Returns true/false if this nick is defined or not" + return Nick.objects.filter(db_obj=self.obj, db_nick__iexact=nick, db_type__iexact=nick_type).count() #------------------------------------------------------------ # @@ -579,6 +582,10 @@ class ObjectDB(TypedObject): raw_string - raw command input coming from the command line. """ # nick replacement - we require full-word matching. + + # do text encoding conversion + raw_string = to_unicode(raw_string) + raw_list = raw_string.split(None) raw_list = [" ".join(raw_list[:i+1]) for i in range(len(raw_list)) if raw_list[:i+1]] for nick in Nick.objects.filter(db_obj=self, db_type__in=("inputline","channel")): diff --git a/src/objects/objects.py b/src/objects/objects.py index 23b6937ab1..b275042041 100644 --- a/src/objects/objects.py +++ b/src/objects/objects.py @@ -195,7 +195,7 @@ class Object(TypeClass): string = "{c%s{n" % self.name desc = self.attr("desc") if desc: - string += ":\n %s" % desc + string += "\n %s" % desc exits = [] users = [] things = [] diff --git a/src/typeclasses/models.py b/src/typeclasses/models.py index 4b576464c0..1ebba244ca 100644 --- a/src/typeclasses/models.py +++ b/src/typeclasses/models.py @@ -441,7 +441,7 @@ class TypedObject(SharedMemoryModel): def permissions_set(self, value): "Setter. Allows for self.name = value. Stores as a comma-separated string." if is_iter(value): - value = ",".join([str(val).strip() for val in value]) + value = ",".join([utils.to_unicode(val).strip() for val in value]) self.db_permissions = value self.save() #@permissions.deleter diff --git a/src/utils/ansi.py b/src/utils/ansi.py index 38325e6963..a9c02544f4 100644 --- a/src/utils/ansi.py +++ b/src/utils/ansi.py @@ -14,6 +14,7 @@ user. """ import re +from src.utils import utils class ANSITable(object): """ @@ -140,7 +141,7 @@ class ANSIParser(object): """ if not string: return '' - string = str(string) + string = utils.to_str(string) for sub in self.ansi_sub: # go through all available mappings and translate them string = sub[0].sub(sub[1], string) diff --git a/src/utils/create.py b/src/utils/create.py index e4a17f82f7..69564efcbf 100644 --- a/src/utils/create.py +++ b/src/utils/create.py @@ -21,7 +21,7 @@ Models covered: from django.conf import settings from django.contrib.auth.models import User from django.db import IntegrityError -from src.utils import logger +from src.utils import logger, utils from src.utils.utils import is_iter, has_parent # @@ -62,7 +62,7 @@ def create_object(typeclass, key=None, location=None, # the type mechanism will automatically assign # the BASE_OBJECT_TYPE from settings. if typeclass: - typeclass = str(typeclass) + typeclass = utils.to_unicode(typeclass) new_db_object.typeclass_path = typeclass new_db_object.save() # this will either load the typeclass or the default one @@ -100,9 +100,7 @@ def create_object(typeclass, key=None, location=None, if permissions: new_object.permissions = permissions if aliases: - if not is_iter(aliases): - aliases = [aliases] - new_object.aliases = ",".join([alias.strip() for alias in aliases]) + new_object.aliases = aliases if locks: new_object.locks.add(locks) @@ -160,7 +158,7 @@ def create_script(typeclass, key=None, obj=None, locks=None, autostart=True): if not callable(typeclass): # try to load this in case it's a path if typeclass: - typeclass = str(typeclass) + typeclass = utils.to_unicode(typeclass) new_db_object.typeclass_path = typeclass new_db_object.save() # this will load either the typeclass or the default one @@ -321,7 +319,7 @@ def create_channel(key, aliases=None, desc=None, if aliases: if not is_iter(aliases): aliases = [aliases] - new_channel.aliases = ",".join([str(alias) for alias in aliases]) + new_channel.aliases = ",".join([alias for alias in aliases]) new_channel.desc = desc new_channel.keep_log = keep_log except IntegrityError: