From f9bd07c3eda29cce11f10a631849a9cc6ecd7e53 Mon Sep 17 00:00:00 2001 From: Aaron McMillin Date: Mon, 16 Sep 2019 17:50:38 -0400 Subject: [PATCH] [#1928] Helper functions and tests --- evennia/commands/default/building.py | 34 ++++++++++++++++++++++++++ evennia/commands/default/tests.py | 36 ++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/evennia/commands/default/building.py b/evennia/commands/default/building.py index 2441a90e5c..531dafe61f 100644 --- a/evennia/commands/default/building.py +++ b/evennia/commands/default/building.py @@ -1603,6 +1603,8 @@ class CmdSetAttribute(ObjManipCommand): key = "set" locks = "cmd:perm(set) or perm(Builder)" help_category = "Building" + nested_re = re.compile(r'\[.*?\]') + not_found = object() def check_obj(self, obj): """ @@ -1627,6 +1629,38 @@ class CmdSetAttribute(ObjManipCommand): """ return attr_name + def split_nested_attr(self, attr): + """ + Yields tuples of (possible attr name, nested keys on that attr). + For performance, this is biased to the deepest match, but allows compatability + with older attrs that might have been named with `[]`'s. + + > list(split_nested_attr("nested['asdf'][0]")) + [ + ('nested', ['asdf', 0]), + ("nested['asdf']", [0]), + ("nested['asdf'][0]", []), + ] + """ + 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:]]) + base_attr += part + yield (attr, []) + + def do_nested_lookup(self, value, *keys): + result = value + for key in keys: + try: + result = result.__getitem__(key) + except (IndexError, KeyError, TypeError): + return self.not_found + return result + def view_attr(self, obj, attr): """ Look up the value of an attribute and return a string displaying it. diff --git a/evennia/commands/default/tests.py b/evennia/commands/default/tests.py index 752cae6fc3..3de3c03a8b 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_split_nested_attr(self): + split_nested_attr = building.CmdSetAttribute().split_nested_attr + test_cases = { + 'test1': [('test1', [])], + 'test2["dict"]': [('test2', ['dict']), ('test2["dict"]', [])], + # Quotes not actually required + 'test3[dict]': [('test3', ['dict']), ('test3[dict]', [])], + # duplicate keys don't cause issues + 'test4[0][0]': [('test4', ['0', '0']), ('test4[0]', ['0']), ('test4[0][0]', [])], + } + + for attr, result in test_cases.items(): + self.assertEqual(list(split_nested_attr(attr)), result) + + def test_do_nested_lookup(self): + do_nested_lookup = building.CmdSetAttribute().do_nested_lookup + not_found = building.CmdSetAttribute.not_found + + def do_test_single(value, key, result): + self.assertEqual(do_nested_lookup(value, key), result) + + def do_test_multi(value, keys, result): + self.assertEqual(do_nested_lookup(value, *keys), result) + + do_test_single([], 'test1', not_found) + do_test_single([1], 'test2', not_found) + do_test_single([], 0, not_found) + do_test_single([1], 2, not_found) + do_test_single([1], 0, 1) + do_test_single({}, 'test3', not_found) + do_test_single({}, 0, not_found) + do_test_single({'foo': 'bar'}, 'foo', 'bar') + + do_test_multi({'one': [1, 2, 3]}, ('one', 0), 1) + do_test_multi([{}, {'two': 2}, 3], (1, 'two'), 2) + def test_name(self): self.call(building.CmdName(), "", "Usage: ") self.call(building.CmdName(), "Obj2=Obj3", "Object's name changed to 'Obj3'.")