From 6edc7d2cb9594996f443eaada324d991d2f51b98 Mon Sep 17 00:00:00 2001 From: Aaron McMillin Date: Wed, 18 Sep 2019 21:36:00 -0400 Subject: [PATCH] [#1928] Create and access data structures --- evennia/commands/default/building.py | 22 ++++++++++++-- evennia/commands/default/tests.py | 45 +++++++++++++++++++++++++++- 2 files changed, 63 insertions(+), 4 deletions(-) diff --git a/evennia/commands/default/building.py b/evennia/commands/default/building.py index 531dafe61f..de974a7f08 100644 --- a/evennia/commands/default/building.py +++ b/evennia/commands/default/building.py @@ -1642,13 +1642,25 @@ class CmdSetAttribute(ObjManipCommand): ("nested['asdf'][0]", []), ] """ + quotes = '"\'' + + def clean_key(val): + val = val.strip('[]') + if val[0] in quotes: + return val.strip(quotes) + + try: + return int(val) + except ValueError: + return val + parts = self.nested_re.findall(attr) base_attr = '' if parts: base_attr = attr[:attr.find(parts[0])] for index, part in enumerate(parts): - yield (base_attr, [p.strip('"\'[]') for p in parts[index:]]) + yield (base_attr, [clean_key(p) for p in parts[index:]]) base_attr += part yield (attr, []) @@ -1665,8 +1677,12 @@ class CmdSetAttribute(ObjManipCommand): """ Look up the value of an attribute and return a string displaying it. """ - if obj.attributes.has(attr): - return "\nAttribute %s/%s = %s" % (obj.name, attr, obj.attributes.get(attr)) + for key, nested_keys in self.split_nested_attr(attr): + 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) diff --git a/evennia/commands/default/tests.py b/evennia/commands/default/tests.py index 3de3c03a8b..0f44dcbba8 100644 --- a/evennia/commands/default/tests.py +++ b/evennia/commands/default/tests.py @@ -535,6 +535,42 @@ class TestBuilding(CommandTest): self.call(building.CmdWipe(), "Obj2/test2/test3", "Wiped attributes test2,test3 on Obj2.") self.call(building.CmdWipe(), "Obj2", "Wiped all attributes on Obj2.") + def test_nested_attribute_commands(self): + # adding white space proves real parsing + self.call(building.CmdSetAttribute(), "Obj/test1=[1,2]", "Created attribute Obj/test1 = [1, 2]") + self.call(building.CmdSetAttribute(), "Obj/test1", "Attribute Obj/test1 = [1, 2]") + self.call(building.CmdSetAttribute(), "Obj/test1[0]", "Attribute Obj/test1[0] = 1") + + # removing white space proves real parsing + self.call(building.CmdSetAttribute(), + "Obj/test2={ 'one': 1 }", "Created attribute Obj/test2 = {'one': 1}") + self.call(building.CmdSetAttribute(), "Obj/test2", "Attribute Obj/test2 = {'one': 1}") + 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[0]", "Obj has no attribute 'test2[0]'.") + + # Deaper nesting + self.call(building.CmdSetAttribute(), + "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}") + + # Naughty keys + self.call(building.CmdSetAttribute(), + "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}]") + 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}") + self.call(building.CmdSetAttribute(), "Obj/test4[0]['one']", "Attribute Obj/test4[0]['one'] = 1") + self.call(building.CmdWipe(), "Obj/test4", "Wiped attributes test4 on Obj.") + # Restored access + 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']'.") + def test_split_nested_attr(self): split_nested_attr = building.CmdSetAttribute().split_nested_attr test_cases = { @@ -542,8 +578,13 @@ class TestBuilding(CommandTest): 'test2["dict"]': [('test2', ['dict']), ('test2["dict"]', [])], # Quotes not actually required 'test3[dict]': [('test3', ['dict']), ('test3[dict]', [])], + 'test4["dict]': [('test4', ['dict']), ('test4["dict]', [])], # duplicate keys don't cause issues - 'test4[0][0]': [('test4', ['0', '0']), ('test4[0]', ['0']), ('test4[0][0]', [])], + 'test5[0][0]': [('test5', [0, 0]), ('test5[0]', [0]), ('test5[0][0]', [])], + # String ints preserved + 'test6["0"][0]': [('test6', ['0', 0]), ('test6["0"]', [0]), ('test6["0"][0]', [])], + # Unmatched [] + 'test7[dict': [('test7[dict', [])], } for attr, result in test_cases.items(): @@ -562,8 +603,10 @@ class TestBuilding(CommandTest): do_test_single([], 'test1', not_found) do_test_single([1], 'test2', not_found) do_test_single([], 0, not_found) + do_test_single([], '0', not_found) do_test_single([1], 2, not_found) do_test_single([1], 0, 1) + do_test_single([1], '0', not_found) # str key is str not int do_test_single({}, 'test3', not_found) do_test_single({}, 0, not_found) do_test_single({'foo': 'bar'}, 'foo', 'bar')