From ba3db1731d621ecbb2ea1822f40a85ddcfadf4a3 Mon Sep 17 00:00:00 2001 From: Aaron McMillin Date: Sun, 29 Sep 2019 21:45:33 -0400 Subject: [PATCH] [#1928] PR feedback, documentation, error handling --- evennia/commands/default/building.py | 31 +++++++---- evennia/commands/default/tests.py | 78 ++++++++++++++++++++-------- 2 files changed, 77 insertions(+), 32 deletions(-) diff --git a/evennia/commands/default/building.py b/evennia/commands/default/building.py index 5ce0f44d12..fee4f75214 100644 --- a/evennia/commands/default/building.py +++ b/evennia/commands/default/building.py @@ -1570,7 +1570,7 @@ class CmdSetAttribute(ObjManipCommand): set / = set / = set / - set */attr = + set */ = Switch: edit: Open the line editor (string values only) @@ -1585,7 +1585,7 @@ class CmdSetAttribute(ObjManipCommand): Sets attributes on objects. The second example form above clears a previously set attribute while the third form inspects the current value of the attribute (if any). The last one (with the star) is a shortcut for - operatin on a player Account rather than an Object. + operating on a player Account rather than an Object. The most common data to save with this command are strings and numbers. You can however also set Python primitives such as lists, @@ -1593,8 +1593,10 @@ class CmdSetAttribute(ObjManipCommand): the functionality of certain custom objects). This is indicated by you starting your value with one of |c'|n, |c"|n, |c(|n, |c[|n or |c{ |n. - Note that you should leave a space after starting a dictionary ('{ ') - so as to not confuse the dictionary start with a colour code like \{g. + + Once you have stored a Python primative as noted above, you can include + |c[]|n in to reference nested values. + Remember that if you use Python primitives like this, you must write proper Python syntax too - notably you must include quotes around your strings or you will get an error. @@ -1680,20 +1682,26 @@ class CmdSetAttribute(ObjManipCommand): """ Look up the value of an attribute and return a string displaying it. """ + nested = False for key, nested_keys in self.split_nested_attr(attr): + nested = True if obj.attributes.has(key): val = obj.attributes.get(key) val = self.do_nested_lookup(val, *nested_keys) if val is not self.not_found: return "\nAttribute %s/%s = %s" % (obj.name, attr, val) - else: - return "\n%s has no attribute '%s'." % (obj.name, attr) + error = "\n%s has no attribute '%s'." % (obj.name, attr) + if nested: + error += ' (Nested lookups attempted)' + return error def rm_attr(self, obj, attr): """ Remove an attribute from the object, or a nested data structure, and report back. """ + nested = False for key, nested_keys in self.split_nested_attr(attr): + nested = True if obj.attributes.has(key): if nested_keys: del_key = nested_keys[-1] @@ -1709,7 +1717,10 @@ class CmdSetAttribute(ObjManipCommand): exists = obj.attributes.has(key) obj.attributes.remove(attr) return "\nDeleted attribute '%s' (= %s) from %s." % (attr, exists, obj.name) - return "\n%s has no attribute '%s'." % (obj.name, attr) + error = "\n%s has no attribute '%s'." % (obj.name, attr) + if nested: + error += ' (Nested lookups attempted)' + return error def set_attr(self, obj, attr, value): done = False @@ -1720,9 +1731,9 @@ class CmdSetAttribute(ObjManipCommand): deep = self.do_nested_lookup(lookup_value, *nested_keys[:-1]) if deep is not self.not_found: # To support appending and inserting to lists - # a key that starts with @ will insert a new item at that + # a key that starts with LIST_APPEND_CHAR will insert a new item at that # location, and move the other elements down. - # Just '@' will append to the list + # Using LIST_APPEND_CHAR alone will append to the list if isinstance(acc_key, str) and acc_key[0] == LIST_APPEND_CHAR: try: if len(acc_key) > 1: @@ -1730,7 +1741,7 @@ class CmdSetAttribute(ObjManipCommand): deep.insert(where, value) else: deep.append(value) - except AttributeError: + except (ValueError, AttributeError): pass else: value = lookup_value diff --git a/evennia/commands/default/tests.py b/evennia/commands/default/tests.py index 478633d8fe..b3148dfa3f 100644 --- a/evennia/commands/default/tests.py +++ b/evennia/commands/default/tests.py @@ -545,40 +545,65 @@ class TestBuilding(CommandTest): self.call(building.CmdSetAttribute(), "Obj/test1[0]", "Attribute Obj/test1[0] = 99") # list delete self.call(building.CmdSetAttribute(), - "Obj/test1[0] =", "Deleted attribute 'test1[0]' (= nested) from Obj.") + "Obj/test1[0] =", + "Deleted attribute 'test1[0]' (= nested) from Obj.") self.call(building.CmdSetAttribute(), "Obj/test1[0]", "Attribute Obj/test1[0] = 2") - self.call(building.CmdSetAttribute(), "Obj/test1[1]", "Obj has no attribute 'test1[1]'.") + self.call(building.CmdSetAttribute(), + "Obj/test1[1]", + "Obj has no attribute 'test1[1]'. (Nested lookups attempted)") # Delete non-existent self.call(building.CmdSetAttribute(), - "Obj/test1[5] =", "Obj has no attribute 'test1[5]'.") + "Obj/test1[5] =", + "Obj has no attribute 'test1[5]'. (Nested lookups attempted)") # Append self.call(building.CmdSetAttribute(), - "Obj/test1[+] = 42", "Modified attribute Obj/test1 = [2, 42]") + "Obj/test1[+] = 42", + "Modified attribute Obj/test1 = [2, 42]") self.call(building.CmdSetAttribute(), - "Obj/test1[+0] = -1", "Modified attribute Obj/test1 = [-1, 2, 42]") + "Obj/test1[+0] = -1", + "Modified attribute Obj/test1 = [-1, 2, 42]") # dict - removing white space proves real parsing self.call(building.CmdSetAttribute(), - "Obj/test2={ 'one': 1, 'two': 2 }", "Created attribute Obj/test2 = {'one': 1, 'two': 2}") + "Obj/test2={ 'one': 1, 'two': 2 }", + "Created attribute Obj/test2 = {'one': 1, 'two': 2}") self.call(building.CmdSetAttribute(), "Obj/test2", "Attribute Obj/test2 = {'one': 1, 'two': 2}") self.call(building.CmdSetAttribute(), "Obj/test2['one']", "Attribute Obj/test2['one'] = 1") self.call(building.CmdSetAttribute(), "Obj/test2['one]", "Attribute Obj/test2['one] = 1") self.call(building.CmdSetAttribute(), - "Obj/test2['one']=99", "Modified attribute Obj/test2 = {'one': 99, 'two': 2}") + "Obj/test2['one']=99", + "Modified attribute Obj/test2 = {'one': 99, 'two': 2}") self.call(building.CmdSetAttribute(), "Obj/test2['one']", "Attribute Obj/test2['one'] = 99") self.call(building.CmdSetAttribute(), "Obj/test2['two']", "Attribute Obj/test2['two'] = 2") self.call(building.CmdSetAttribute(), - "Obj/test2['three']=3", "Modified attribute Obj/test2 = {'one': 99, 'two': 2, 'three': 3}") + "Obj/test2[+'three']", + "Obj has no attribute 'test2[+'three']'. (Nested lookups attempted)") + self.call(building.CmdSetAttribute(), + "Obj/test2[+'three'] = 3", + "Modified attribute Obj/test2 = {'one': 99, 'two': 2, \"+'three'\": 3}") + self.call(building.CmdSetAttribute(), + "Obj/test2[+'three'] =", + "Deleted attribute 'test2[+'three']' (= nested) from Obj.") + self.call(building.CmdSetAttribute(), + "Obj/test2['three']=3", + "Modified attribute Obj/test2 = {'one': 99, 'two': 2, 'three': 3}") # Dict delete self.call(building.CmdSetAttribute(), - "Obj/test2['two'] =", "Deleted attribute 'test2['two']' (= nested) from Obj.") - self.call(building.CmdSetAttribute(), "Obj/test2['two']", "Obj has no attribute 'test2['two']'.") + "Obj/test2['two'] =", + "Deleted attribute 'test2['two']' (= nested) from Obj.") + self.call(building.CmdSetAttribute(), + "Obj/test2['two']", + "Obj has no attribute 'test2['two']'. (Nested lookups attempted)") self.call(building.CmdSetAttribute(), "Obj/test2", "Attribute Obj/test2 = {'one': 99, 'three': 3}") - self.call(building.CmdSetAttribute(), "Obj/test2[0]", "Obj has no attribute 'test2[0]'.") self.call(building.CmdSetAttribute(), - "Obj/test2['five'] =", "Obj has no attribute 'test2['five']'.") + "Obj/test2[0]", + "Obj has no attribute 'test2[0]'. (Nested lookups attempted)") self.call(building.CmdSetAttribute(), - "Obj/test2[+]=42", "Modified attribute Obj/test2 = {'one': 99, 'three': 3, '+': 42}") + "Obj/test2['five'] =", + "Obj has no attribute 'test2['five']'. (Nested lookups attempted)") + self.call(building.CmdSetAttribute(), + "Obj/test2[+]=42", + "Modified attribute Obj/test2 = {'one': 99, 'three': 3, '+': 42}") self.call(building.CmdSetAttribute(), "Obj/test2[+1]=33", "Modified attribute Obj/test2 = {'one': 99, 'three': 3, '+': 42, '+1': 33}") @@ -586,31 +611,39 @@ class TestBuilding(CommandTest): # tuple self.call(building.CmdSetAttribute(), "Obj/tup = (1,2)", "Created attribute Obj/tup = (1, 2)") self.call(building.CmdSetAttribute(), - "Obj/tup[1] = 99", "'tuple' object does not support item assignment - (1, 2)") + "Obj/tup[1] = 99", + "'tuple' object does not support item assignment - (1, 2)") self.call(building.CmdSetAttribute(), - "Obj/tup[+] = 99", "'tuple' object does not support item assignment - (1, 2)") + "Obj/tup[+] = 99", + "'tuple' object does not support item assignment - (1, 2)") self.call(building.CmdSetAttribute(), - "Obj/tup[+1] = 99", "'tuple' object does not support item assignment - (1, 2)") + "Obj/tup[+1] = 99", + "'tuple' object does not support item assignment - (1, 2)") self.call(building.CmdSetAttribute(), # Special case for tuple, could have a better message - "Obj/tup[1] = ", "Obj has no attribute 'tup[1]'.") + "Obj/tup[1] = ", + "Obj has no attribute 'tup[1]'. (Nested lookups attempted)") # Deaper nesting self.call(building.CmdSetAttribute(), - "Obj/test3=[{'one': 1}]", "Created attribute Obj/test3 = [{'one': 1}]") + "Obj/test3=[{'one': 1}]", + "Created attribute Obj/test3 = [{'one': 1}]") self.call(building.CmdSetAttribute(), "Obj/test3[0]['one']", "Attribute Obj/test3[0]['one'] = 1") self.call(building.CmdSetAttribute(), "Obj/test3[0]", "Attribute Obj/test3[0] = {'one': 1}") self.call(building.CmdSetAttribute(), - "Obj/test3[0]['one'] =", "Deleted attribute 'test3[0]['one']' (= nested) from Obj.") + "Obj/test3[0]['one'] =", + "Deleted attribute 'test3[0]['one']' (= nested) from Obj.") self.call(building.CmdSetAttribute(), "Obj/test3[0]", "Attribute Obj/test3[0] = {}") self.call(building.CmdSetAttribute(), "Obj/test3", "Attribute Obj/test3 = [{}]") # Naughty keys self.call(building.CmdSetAttribute(), - "Obj/test4[0]='foo'", "Created attribute Obj/test4[0] = 'foo'") + "Obj/test4[0]='foo'", + "Created attribute Obj/test4[0] = 'foo'") self.call(building.CmdSetAttribute(), "Obj/test4[0]", "Attribute Obj/test4[0] = foo") self.call(building.CmdSetAttribute(), - "Obj/test4=[{'one': 1}]", "Created attribute Obj/test4 = [{'one': 1}]") + "Obj/test4=[{'one': 1}]", + "Created attribute Obj/test4 = [{'one': 1}]") self.call(building.CmdSetAttribute(), "Obj/test4[0]['one']", "Attribute Obj/test4[0]['one'] = 1") # Prefer nested items self.call(building.CmdSetAttribute(), "Obj/test4[0]", "Attribute Obj/test4[0] = {'one': 1}") @@ -619,7 +652,8 @@ class TestBuilding(CommandTest): self.call(building.CmdWipe(), "Obj/test4", "Wiped attributes test4 on Obj.") self.call(building.CmdSetAttribute(), "Obj/test4[0]", "Attribute Obj/test4[0] = foo") self.call(building.CmdSetAttribute(), - "Obj/test4[0]['one']", "Obj has no attribute 'test4[0]['one']'.") + "Obj/test4[0]['one']", + "Obj has no attribute 'test4[0]['one']'.") def test_split_nested_attr(self): split_nested_attr = building.CmdSetAttribute().split_nested_attr