Make set aware of Attribute categories
This commit is contained in:
parent
5b85ec128e
commit
72ad633071
5 changed files with 182 additions and 97 deletions
|
|
@ -126,6 +126,8 @@ Up requirements to Django 3.2+, Twisted 21+
|
||||||
since their work is now fully handled by the single `channel` command.
|
since their work is now fully handled by the single `channel` command.
|
||||||
- Expand `examine` command's code to much more extensible and modular. Show
|
- Expand `examine` command's code to much more extensible and modular. Show
|
||||||
attribute categories and value types (when not strings).
|
attribute categories and value types (when not strings).
|
||||||
|
- `AttributeHandler.remove(key, return_exception=False, category=None, ...)` changed
|
||||||
|
to `.remove(key, category=None, return_exception=False, ...)` for consistency.
|
||||||
|
|
||||||
### Evennia 0.9.5 (2019-2020)
|
### Evennia 0.9.5 (2019-2020)
|
||||||
|
|
||||||
|
|
@ -216,6 +218,7 @@ without arguments starts a full interactive Python console.
|
||||||
- Add `PermissionHandler.check` method for straight string perm-checks without needing lockstrings.
|
- Add `PermissionHandler.check` method for straight string perm-checks without needing lockstrings.
|
||||||
- Add `evennia.utils.utils.strip_unsafe_input` for removing html/newlines/tags from user input. The
|
- Add `evennia.utils.utils.strip_unsafe_input` for removing html/newlines/tags from user input. The
|
||||||
`INPUT_CLEANUP_BYPASS_PERMISSIONS` is a list of perms that bypass this safety stripping.
|
`INPUT_CLEANUP_BYPASS_PERMISSIONS` is a list of perms that bypass this safety stripping.
|
||||||
|
- Make default `set` and `examine` commands aware of Attribute categories.
|
||||||
|
|
||||||
|
|
||||||
## Evennia 0.9 (2018-2019)
|
## Evennia 0.9 (2018-2019)
|
||||||
|
|
|
||||||
|
|
@ -1577,10 +1577,10 @@ class CmdSetAttribute(ObjManipCommand):
|
||||||
set attribute on an object or account
|
set attribute on an object or account
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
set <obj>/<attr> = <value>
|
set[/switch] <obj>/<attr>[:category] = <value>
|
||||||
set <obj>/<attr> =
|
set[/switch] <obj>/<attr>[:category] = # delete attribute
|
||||||
set <obj>/<attr>
|
set[/switch] <obj>/<attr>[:category] # view attribute
|
||||||
set *<account>/<attr> = <value>
|
set[/switch] *<account>/<attr>[:category] = <value>
|
||||||
|
|
||||||
Switch:
|
Switch:
|
||||||
edit: Open the line editor (string values only)
|
edit: Open the line editor (string values only)
|
||||||
|
|
@ -1631,7 +1631,7 @@ class CmdSetAttribute(ObjManipCommand):
|
||||||
"""
|
"""
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def check_attr(self, obj, attr_name):
|
def check_attr(self, obj, attr_name, category):
|
||||||
"""
|
"""
|
||||||
This may be overridden by subclasses in case restrictions need to be
|
This may be overridden by subclasses in case restrictions need to be
|
||||||
placed on what attributes can be set by who beyond the normal lock.
|
placed on what attributes can be set by who beyond the normal lock.
|
||||||
|
|
@ -1688,7 +1688,7 @@ class CmdSetAttribute(ObjManipCommand):
|
||||||
return self.not_found
|
return self.not_found
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def view_attr(self, obj, attr):
|
def view_attr(self, obj, attr, category):
|
||||||
"""
|
"""
|
||||||
Look up the value of an attribute and return a string displaying it.
|
Look up the value of an attribute and return a string displaying it.
|
||||||
"""
|
"""
|
||||||
|
|
@ -1699,45 +1699,49 @@ class CmdSetAttribute(ObjManipCommand):
|
||||||
val = obj.attributes.get(key)
|
val = obj.attributes.get(key)
|
||||||
val = self.do_nested_lookup(val, *nested_keys)
|
val = self.do_nested_lookup(val, *nested_keys)
|
||||||
if val is not self.not_found:
|
if val is not self.not_found:
|
||||||
return "\nAttribute %s/%s = %s" % (obj.name, attr, val)
|
return f"\nAttribute {obj.name}/|w{attr}|n [category:{category}] = {val}"
|
||||||
error = "\n%s has no attribute '%s'." % (obj.name, attr)
|
error = f"\nAttribute {obj.name}/|w{attr} [category:{category}] does not exist."
|
||||||
if nested:
|
if nested:
|
||||||
error += " (Nested lookups attempted)"
|
error += " (Nested lookups attempted)"
|
||||||
return error
|
return error
|
||||||
|
|
||||||
def rm_attr(self, obj, attr):
|
def rm_attr(self, obj, attr, category):
|
||||||
"""
|
"""
|
||||||
Remove an attribute from the object, or a nested data structure, and report back.
|
Remove an attribute from the object, or a nested data structure, and report back.
|
||||||
"""
|
"""
|
||||||
nested = False
|
nested = False
|
||||||
for key, nested_keys in self.split_nested_attr(attr):
|
for key, nested_keys in self.split_nested_attr(attr):
|
||||||
nested = True
|
nested = True
|
||||||
if obj.attributes.has(key):
|
if obj.attributes.has(key, category):
|
||||||
if nested_keys:
|
if nested_keys:
|
||||||
del_key = nested_keys[-1]
|
del_key = nested_keys[-1]
|
||||||
val = obj.attributes.get(key)
|
val = obj.attributes.get(key, category=category)
|
||||||
deep = self.do_nested_lookup(val, *nested_keys[:-1])
|
deep = self.do_nested_lookup(val, *nested_keys[:-1])
|
||||||
if deep is not self.not_found:
|
if deep is not self.not_found:
|
||||||
try:
|
try:
|
||||||
del deep[del_key]
|
del deep[del_key]
|
||||||
except (IndexError, KeyError, TypeError):
|
except (IndexError, KeyError, TypeError):
|
||||||
continue
|
continue
|
||||||
return "\nDeleted attribute '%s' (= nested) from %s." % (attr, obj.name)
|
return f"\nDeleted attribute {obj.name}/|w{attr}|n [category:{category}]."
|
||||||
else:
|
else:
|
||||||
exists = obj.attributes.has(key)
|
exists = obj.attributes.has(key, category)
|
||||||
obj.attributes.remove(attr)
|
if exists:
|
||||||
return "\nDeleted attribute '%s' (= %s) from %s." % (attr, exists, obj.name)
|
obj.attributes.remove(attr, category=category)
|
||||||
error = "\n%s has no attribute '%s'." % (obj.name, attr)
|
return f"\nDeleted attribute {obj.name}/|w{attr}|n [category:{category}]."
|
||||||
|
else:
|
||||||
|
return (f"\nNo attribute {obj.name}/|w{attr}|n [category: {category}] "
|
||||||
|
"was found to delete.")
|
||||||
|
error = f"\nNo attribute {obj.name}/|w{attr}|n [category: {category}] was found to delete."
|
||||||
if nested:
|
if nested:
|
||||||
error += " (Nested lookups attempted)"
|
error += " (Nested lookups attempted)"
|
||||||
return error
|
return error
|
||||||
|
|
||||||
def set_attr(self, obj, attr, value):
|
def set_attr(self, obj, attr, value, category):
|
||||||
done = False
|
done = False
|
||||||
for key, nested_keys in self.split_nested_attr(attr):
|
for key, nested_keys in self.split_nested_attr(attr):
|
||||||
if obj.attributes.has(key) and nested_keys:
|
if obj.attributes.has(key, category) and nested_keys:
|
||||||
acc_key = nested_keys[-1]
|
acc_key = nested_keys[-1]
|
||||||
lookup_value = obj.attributes.get(key)
|
lookup_value = obj.attributes.get(key, category)
|
||||||
deep = self.do_nested_lookup(lookup_value, *nested_keys[:-1])
|
deep = self.do_nested_lookup(lookup_value, *nested_keys[:-1])
|
||||||
if deep is not self.not_found:
|
if deep is not self.not_found:
|
||||||
# To support appending and inserting to lists
|
# To support appending and inserting to lists
|
||||||
|
|
@ -1764,7 +1768,7 @@ class CmdSetAttribute(ObjManipCommand):
|
||||||
deep[acc_key] = value
|
deep[acc_key] = value
|
||||||
except TypeError as err:
|
except TypeError as err:
|
||||||
# Tuples can't be modified
|
# Tuples can't be modified
|
||||||
return "\n%s - %s" % (err, deep)
|
return f"\n{err} - {deep}"
|
||||||
|
|
||||||
value = lookup_value
|
value = lookup_value
|
||||||
attr = key
|
attr = key
|
||||||
|
|
@ -1774,8 +1778,8 @@ class CmdSetAttribute(ObjManipCommand):
|
||||||
verb = "Modified" if obj.attributes.has(attr) else "Created"
|
verb = "Modified" if obj.attributes.has(attr) else "Created"
|
||||||
try:
|
try:
|
||||||
if not done:
|
if not done:
|
||||||
obj.attributes.add(attr, value)
|
obj.attributes.add(attr, value, category)
|
||||||
return "\n%s attribute %s/%s = %s" % (verb, obj.name, attr, repr(value))
|
return f"\n{verb} attribute {obj.name}/|w{attr}|n [category:{category}] = {value}"
|
||||||
except SyntaxError:
|
except SyntaxError:
|
||||||
# this means literal_eval tried to parse a faulty string
|
# this means literal_eval tried to parse a faulty string
|
||||||
return (
|
return (
|
||||||
|
|
@ -1861,13 +1865,14 @@ class CmdSetAttribute(ObjManipCommand):
|
||||||
|
|
||||||
caller = self.caller
|
caller = self.caller
|
||||||
if not self.args:
|
if not self.args:
|
||||||
caller.msg("Usage: set obj/attr = value. Use empty value to clear.")
|
caller.msg("Usage: set obj/attr[:category] = value. Use empty value to clear.")
|
||||||
return
|
return
|
||||||
|
|
||||||
# get values prepared by the parser
|
# get values prepared by the parser
|
||||||
value = self.rhs
|
value = self.rhs
|
||||||
objname = self.lhs_objattr[0]["name"]
|
objname = self.lhs_objattr[0]["name"]
|
||||||
attrs = self.lhs_objattr[0]["attrs"]
|
attrs = self.lhs_objattr[0]["attrs"]
|
||||||
|
category = self.lhs_objs[0].get("option") # None if unset
|
||||||
|
|
||||||
obj = self.search_for_obj(objname)
|
obj = self.search_for_obj(objname)
|
||||||
if not obj:
|
if not obj:
|
||||||
|
|
@ -1897,11 +1902,11 @@ class CmdSetAttribute(ObjManipCommand):
|
||||||
if self.rhs is None:
|
if self.rhs is None:
|
||||||
# no = means we inspect the attribute(s)
|
# no = means we inspect the attribute(s)
|
||||||
if not attrs:
|
if not attrs:
|
||||||
attrs = [attr.key for attr in obj.attributes.all()]
|
attrs = [attr.key for attr in obj.attributes.get(category=None)]
|
||||||
for attr in attrs:
|
for attr in attrs:
|
||||||
if not self.check_attr(obj, attr):
|
if not self.check_attr(obj, attr, category):
|
||||||
continue
|
continue
|
||||||
result.append(self.view_attr(obj, attr))
|
result.append(self.view_attr(obj, attr, category))
|
||||||
# we view it without parsing markup.
|
# we view it without parsing markup.
|
||||||
self.caller.msg("".join(result).strip(), options={"raw": True})
|
self.caller.msg("".join(result).strip(), options={"raw": True})
|
||||||
return
|
return
|
||||||
|
|
@ -1911,19 +1916,19 @@ class CmdSetAttribute(ObjManipCommand):
|
||||||
caller.msg("You don't have permission to edit %s." % obj.key)
|
caller.msg("You don't have permission to edit %s." % obj.key)
|
||||||
return
|
return
|
||||||
for attr in attrs:
|
for attr in attrs:
|
||||||
if not self.check_attr(obj, attr):
|
if not self.check_attr(obj, attr, category):
|
||||||
continue
|
continue
|
||||||
result.append(self.rm_attr(obj, attr))
|
result.append(self.rm_attr(obj, attr, category))
|
||||||
else:
|
else:
|
||||||
# setting attribute(s). Make sure to convert to real Python type before saving.
|
# setting attribute(s). Make sure to convert to real Python type before saving.
|
||||||
if not (obj.access(self.caller, "control") or obj.access(self.caller, "edit")):
|
if not (obj.access(self.caller, "control") or obj.access(self.caller, "edit")):
|
||||||
caller.msg("You don't have permission to edit %s." % obj.key)
|
caller.msg("You don't have permission to edit %s." % obj.key)
|
||||||
return
|
return
|
||||||
for attr in attrs:
|
for attr in attrs:
|
||||||
if not self.check_attr(obj, attr):
|
if not self.check_attr(obj, attr, category):
|
||||||
continue
|
continue
|
||||||
value = _convert_from_string(self, value)
|
value = _convert_from_string(self, value)
|
||||||
result.append(self.set_attr(obj, attr, value))
|
result.append(self.set_attr(obj, attr, value, category))
|
||||||
# send feedback
|
# send feedback
|
||||||
caller.msg("".join(result).strip("\n"))
|
caller.msg("".join(result).strip("\n"))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -965,15 +965,17 @@ class TestBuilding(CommandTest):
|
||||||
self.call(
|
self.call(
|
||||||
building.CmdSetAttribute(),
|
building.CmdSetAttribute(),
|
||||||
'Obj/test1="value1"',
|
'Obj/test1="value1"',
|
||||||
"Created attribute Obj/test1 = 'value1'",
|
"Created attribute Obj/test1 [category:None] = value1",
|
||||||
)
|
)
|
||||||
self.call(
|
self.call(
|
||||||
building.CmdSetAttribute(),
|
building.CmdSetAttribute(),
|
||||||
'Obj2/test2="value2"',
|
'Obj2/test2="value2"',
|
||||||
"Created attribute Obj2/test2 = 'value2'",
|
"Created attribute Obj2/test2 [category:None] = value2",
|
||||||
)
|
)
|
||||||
self.call(building.CmdSetAttribute(), "Obj2/test2", "Attribute Obj2/test2 = value2")
|
self.call(building.CmdSetAttribute(),
|
||||||
self.call(building.CmdSetAttribute(), "Obj2/NotFound", "Obj2 has no attribute 'notfound'.")
|
"Obj2/test2", "Attribute Obj2/test2 [category:None] = value2")
|
||||||
|
self.call(building.CmdSetAttribute(),
|
||||||
|
"Obj2/NotFound", "Attribute Obj2/notfound [category:None] does not exist.")
|
||||||
|
|
||||||
with patch("evennia.commands.default.building.EvEditor") as mock_ed:
|
with patch("evennia.commands.default.building.EvEditor") as mock_ed:
|
||||||
self.call(building.CmdSetAttribute(), "/edit Obj2/test3")
|
self.call(building.CmdSetAttribute(), "/edit Obj2/test3")
|
||||||
|
|
@ -982,14 +984,18 @@ class TestBuilding(CommandTest):
|
||||||
self.call(
|
self.call(
|
||||||
building.CmdSetAttribute(),
|
building.CmdSetAttribute(),
|
||||||
'Obj2/test3="value3"',
|
'Obj2/test3="value3"',
|
||||||
"Created attribute Obj2/test3 = 'value3'",
|
"Created attribute Obj2/test3 [category:None] = value3",
|
||||||
)
|
)
|
||||||
self.call(
|
self.call(
|
||||||
building.CmdSetAttribute(),
|
building.CmdSetAttribute(),
|
||||||
"Obj2/test3 = ",
|
"Obj2/test3 = ",
|
||||||
"Deleted attribute 'test3' (= True) from Obj2.",
|
"Deleted attribute Obj2/test3 [category:None].",
|
||||||
|
)
|
||||||
|
self.call(
|
||||||
|
building.CmdSetAttribute(),
|
||||||
|
"Obj2/test4:Foo = 'Bar'",
|
||||||
|
"Created attribute Obj2/test4 [category:Foo] = Bar",
|
||||||
)
|
)
|
||||||
|
|
||||||
self.call(
|
self.call(
|
||||||
building.CmdCpAttr(),
|
building.CmdCpAttr(),
|
||||||
"/copy Obj2/test2 = Obj2/test3",
|
"/copy Obj2/test2 = Obj2/test3",
|
||||||
|
|
@ -1008,123 +1014,162 @@ class TestBuilding(CommandTest):
|
||||||
def test_nested_attribute_commands(self):
|
def test_nested_attribute_commands(self):
|
||||||
# list - adding white space proves real parsing
|
# list - adding white space proves real parsing
|
||||||
self.call(
|
self.call(
|
||||||
building.CmdSetAttribute(), "Obj/test1=[1,2]", "Created attribute Obj/test1 = [1, 2]"
|
building.CmdSetAttribute(),
|
||||||
|
"Obj/test1=[1,2]", "Created attribute Obj/test1 [category:None] = [1, 2]"
|
||||||
)
|
)
|
||||||
self.call(building.CmdSetAttribute(), "Obj/test1", "Attribute Obj/test1 = [1, 2]")
|
self.call(building.CmdSetAttribute(),
|
||||||
self.call(building.CmdSetAttribute(), "Obj/test1[0]", "Attribute Obj/test1[0] = 1")
|
"Obj/test1",
|
||||||
self.call(building.CmdSetAttribute(), "Obj/test1[1]", "Attribute Obj/test1[1] = 2")
|
"Attribute Obj/test1 [category:None] = [1, 2]")
|
||||||
|
self.call(building.CmdSetAttribute(),
|
||||||
|
"Obj/test1[0]",
|
||||||
|
"Attribute Obj/test1[0] [category:None] = 1")
|
||||||
|
self.call(building.CmdSetAttribute(),
|
||||||
|
"Obj/test1[1]",
|
||||||
|
"Attribute Obj/test1[1] [category:None] = 2")
|
||||||
self.call(
|
self.call(
|
||||||
building.CmdSetAttribute(),
|
building.CmdSetAttribute(),
|
||||||
"Obj/test1[0] = 99",
|
"Obj/test1[0] = 99",
|
||||||
"Modified attribute Obj/test1 = [99, 2]",
|
"Modified attribute Obj/test1 [category:None] = [99, 2]",
|
||||||
|
)
|
||||||
|
self.call(
|
||||||
|
building.CmdSetAttribute(),
|
||||||
|
"Obj/test1[0]",
|
||||||
|
"Attribute Obj/test1[0] [category:None] = 99"
|
||||||
)
|
)
|
||||||
self.call(building.CmdSetAttribute(), "Obj/test1[0]", "Attribute Obj/test1[0] = 99")
|
|
||||||
# list delete
|
# list delete
|
||||||
self.call(
|
self.call(
|
||||||
building.CmdSetAttribute(),
|
building.CmdSetAttribute(),
|
||||||
"Obj/test1[0] =",
|
"Obj/test1[0] =",
|
||||||
"Deleted attribute 'test1[0]' (= nested) from Obj.",
|
"Deleted attribute Obj/test1[0] [category:None].",
|
||||||
|
)
|
||||||
|
self.call(
|
||||||
|
building.CmdSetAttribute(),
|
||||||
|
"Obj/test1[0]",
|
||||||
|
"Attribute Obj/test1[0] [category:None] = 2"
|
||||||
)
|
)
|
||||||
self.call(building.CmdSetAttribute(), "Obj/test1[0]", "Attribute Obj/test1[0] = 2")
|
|
||||||
self.call(
|
self.call(
|
||||||
building.CmdSetAttribute(),
|
building.CmdSetAttribute(),
|
||||||
"Obj/test1[1]",
|
"Obj/test1[1]",
|
||||||
"Obj has no attribute 'test1[1]'. (Nested lookups attempted)",
|
"Attribute Obj/test1[1] [category:None] does not exist. (Nested lookups attempted)",
|
||||||
)
|
)
|
||||||
# Delete non-existent
|
# Delete non-existent
|
||||||
self.call(
|
self.call(
|
||||||
building.CmdSetAttribute(),
|
building.CmdSetAttribute(),
|
||||||
"Obj/test1[5] =",
|
"Obj/test1[5] =",
|
||||||
"Obj has no attribute 'test1[5]'. (Nested lookups attempted)",
|
"No attribute Obj/test1[5] [category: None] was found to "
|
||||||
|
"delete. (Nested lookups attempted)"
|
||||||
)
|
)
|
||||||
# Append
|
# Append
|
||||||
self.call(
|
self.call(
|
||||||
building.CmdSetAttribute(),
|
building.CmdSetAttribute(),
|
||||||
"Obj/test1[+] = 42",
|
"Obj/test1[+] = 42",
|
||||||
"Modified attribute Obj/test1 = [2, 42]",
|
"Modified attribute Obj/test1 [category:None] = [2, 42]",
|
||||||
)
|
)
|
||||||
self.call(
|
self.call(
|
||||||
building.CmdSetAttribute(),
|
building.CmdSetAttribute(),
|
||||||
"Obj/test1[+0] = -1",
|
"Obj/test1[+0] = -1",
|
||||||
"Modified attribute Obj/test1 = [-1, 2, 42]",
|
"Modified attribute Obj/test1 [category:None] = [-1, 2, 42]",
|
||||||
)
|
)
|
||||||
|
|
||||||
# dict - removing white space proves real parsing
|
# dict - removing white space proves real parsing
|
||||||
self.call(
|
self.call(
|
||||||
building.CmdSetAttribute(),
|
building.CmdSetAttribute(),
|
||||||
"Obj/test2={ 'one': 1, 'two': 2 }",
|
"Obj/test2={ 'one': 1, 'two': 2 }",
|
||||||
"Created attribute Obj/test2 = {'one': 1, 'two': 2}",
|
"Created attribute Obj/test2 [category:None] = {'one': 1, 'two': 2}",
|
||||||
)
|
)
|
||||||
self.call(
|
self.call(
|
||||||
building.CmdSetAttribute(), "Obj/test2", "Attribute Obj/test2 = {'one': 1, 'two': 2}"
|
building.CmdSetAttribute(),
|
||||||
|
"Obj/test2", "Attribute Obj/test2 [category:None] = {'one': 1, 'two': 2}"
|
||||||
|
)
|
||||||
|
self.call(
|
||||||
|
building.CmdSetAttribute(),
|
||||||
|
"Obj/test2['one']",
|
||||||
|
"Attribute Obj/test2['one'] [category:None] = 1"
|
||||||
|
)
|
||||||
|
self.call(
|
||||||
|
building.CmdSetAttribute(),
|
||||||
|
"Obj/test2['one]",
|
||||||
|
"Attribute Obj/test2['one] [category:None] = 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(
|
self.call(
|
||||||
building.CmdSetAttribute(),
|
building.CmdSetAttribute(),
|
||||||
"Obj/test2['one']=99",
|
"Obj/test2['one']=99",
|
||||||
"Modified attribute Obj/test2 = {'one': 99, 'two': 2}",
|
"Modified attribute Obj/test2 [category:None] = {'one': 99, 'two': 2}",
|
||||||
|
)
|
||||||
|
self.call(
|
||||||
|
building.CmdSetAttribute(),
|
||||||
|
"Obj/test2['one']",
|
||||||
|
"Attribute Obj/test2['one'] [category:None] = 99"
|
||||||
|
)
|
||||||
|
self.call(
|
||||||
|
building.CmdSetAttribute(),
|
||||||
|
"Obj/test2['two']",
|
||||||
|
"Attribute Obj/test2['two'] [category:None] = 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(
|
self.call(
|
||||||
building.CmdSetAttribute(),
|
building.CmdSetAttribute(),
|
||||||
"Obj/test2[+'three']",
|
"Obj/test2[+'three']",
|
||||||
"Obj has no attribute 'test2[+'three']'. (Nested lookups attempted)",
|
"Attribute Obj/test2[+'three'] [category:None] does not exist. (Nested lookups attempted)"
|
||||||
)
|
)
|
||||||
self.call(
|
self.call(
|
||||||
building.CmdSetAttribute(),
|
building.CmdSetAttribute(),
|
||||||
"Obj/test2[+'three'] = 3",
|
"Obj/test2[+'three'] = 3",
|
||||||
"Modified attribute Obj/test2 = {'one': 99, 'two': 2, \"+'three'\": 3}",
|
"Modified attribute Obj/test2 [category:None] = {'one': 99, 'two': 2, \"+'three'\": 3}",
|
||||||
)
|
)
|
||||||
self.call(
|
self.call(
|
||||||
building.CmdSetAttribute(),
|
building.CmdSetAttribute(),
|
||||||
"Obj/test2[+'three'] =",
|
"Obj/test2[+'three'] =",
|
||||||
"Deleted attribute 'test2[+'three']' (= nested) from Obj.",
|
"Deleted attribute Obj/test2[+'three'] [category:None]."
|
||||||
)
|
)
|
||||||
self.call(
|
self.call(
|
||||||
building.CmdSetAttribute(),
|
building.CmdSetAttribute(),
|
||||||
"Obj/test2['three']=3",
|
"Obj/test2['three']=3",
|
||||||
"Modified attribute Obj/test2 = {'one': 99, 'two': 2, 'three': 3}",
|
"Modified attribute Obj/test2 [category:None] = {'one': 99, 'two': 2, 'three': 3}",
|
||||||
)
|
)
|
||||||
# Dict delete
|
# Dict delete
|
||||||
self.call(
|
self.call(
|
||||||
building.CmdSetAttribute(),
|
building.CmdSetAttribute(),
|
||||||
"Obj/test2['two'] =",
|
"Obj/test2['two'] =",
|
||||||
"Deleted attribute 'test2['two']' (= nested) from Obj.",
|
"Deleted attribute Obj/test2['two'] [category:None].",
|
||||||
)
|
)
|
||||||
self.call(
|
self.call(
|
||||||
building.CmdSetAttribute(),
|
building.CmdSetAttribute(),
|
||||||
"Obj/test2['two']",
|
"Obj/test2['two']",
|
||||||
"Obj has no attribute 'test2['two']'. (Nested lookups attempted)",
|
"Attribute Obj/test2['two'] [category:None] does not exist. (Nested lookups attempted)"
|
||||||
)
|
)
|
||||||
self.call(
|
self.call(
|
||||||
building.CmdSetAttribute(), "Obj/test2", "Attribute Obj/test2 = {'one': 99, 'three': 3}"
|
building.CmdSetAttribute(),
|
||||||
|
"Obj/test2",
|
||||||
|
"Attribute Obj/test2 [category:None] = {'one': 99, 'three': 3}"
|
||||||
)
|
)
|
||||||
self.call(
|
self.call(
|
||||||
building.CmdSetAttribute(),
|
building.CmdSetAttribute(),
|
||||||
"Obj/test2[0]",
|
"Obj/test2[0]",
|
||||||
"Obj has no attribute 'test2[0]'. (Nested lookups attempted)",
|
"Attribute Obj/test2[0] [category:None] does not exist. (Nested lookups attempted)"
|
||||||
)
|
)
|
||||||
self.call(
|
self.call(
|
||||||
building.CmdSetAttribute(),
|
building.CmdSetAttribute(),
|
||||||
"Obj/test2['five'] =",
|
"Obj/test2['five'] =",
|
||||||
"Obj has no attribute 'test2['five']'. (Nested lookups attempted)",
|
"No attribute Obj/test2['five'] [category: None] "
|
||||||
|
"was found to delete. (Nested lookups attempted)"
|
||||||
)
|
)
|
||||||
self.call(
|
self.call(
|
||||||
building.CmdSetAttribute(),
|
building.CmdSetAttribute(),
|
||||||
"Obj/test2[+]=42",
|
"Obj/test2[+]=42",
|
||||||
"Modified attribute Obj/test2 = {'one': 99, 'three': 3, '+': 42}",
|
"Modified attribute Obj/test2 [category:None] = {'one': 99, 'three': 3, '+': 42}",
|
||||||
)
|
)
|
||||||
self.call(
|
self.call(
|
||||||
building.CmdSetAttribute(),
|
building.CmdSetAttribute(),
|
||||||
"Obj/test2[+1]=33",
|
"Obj/test2[+1]=33",
|
||||||
"Modified attribute Obj/test2 = {'one': 99, 'three': 3, '+': 42, '+1': 33}",
|
"Modified attribute Obj/test2 [category:None] = "
|
||||||
|
"{'one': 99, 'three': 3, '+': 42, '+1': 33}",
|
||||||
)
|
)
|
||||||
|
|
||||||
# tuple
|
# tuple
|
||||||
self.call(
|
self.call(
|
||||||
building.CmdSetAttribute(), "Obj/tup = (1,2)", "Created attribute Obj/tup = (1, 2)"
|
building.CmdSetAttribute(),
|
||||||
|
"Obj/tup = (1,2)",
|
||||||
|
"Created attribute Obj/tup [category:None] = (1, 2)"
|
||||||
)
|
)
|
||||||
self.call(
|
self.call(
|
||||||
building.CmdSetAttribute(),
|
building.CmdSetAttribute(),
|
||||||
|
|
@ -1145,54 +1190,85 @@ class TestBuilding(CommandTest):
|
||||||
building.CmdSetAttribute(),
|
building.CmdSetAttribute(),
|
||||||
# Special case for tuple, could have a better message
|
# Special case for tuple, could have a better message
|
||||||
"Obj/tup[1] = ",
|
"Obj/tup[1] = ",
|
||||||
"Obj has no attribute 'tup[1]'. (Nested lookups attempted)",
|
"No attribute Obj/tup[1] [category: None] "
|
||||||
|
"was found to delete. (Nested lookups attempted)"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Deaper nesting
|
# Deaper nesting
|
||||||
self.call(
|
self.call(
|
||||||
building.CmdSetAttribute(),
|
building.CmdSetAttribute(),
|
||||||
"Obj/test3=[{'one': 1}]",
|
"Obj/test3=[{'one': 1}]",
|
||||||
"Created attribute Obj/test3 = [{'one': 1}]",
|
"Created attribute Obj/test3 [category:None] = [{'one': 1}]",
|
||||||
)
|
)
|
||||||
self.call(
|
self.call(
|
||||||
building.CmdSetAttribute(), "Obj/test3[0]['one']", "Attribute Obj/test3[0]['one'] = 1"
|
building.CmdSetAttribute(),
|
||||||
|
"Obj/test3[0]['one']",
|
||||||
|
"Attribute Obj/test3[0]['one'] [category:None] = 1"
|
||||||
|
)
|
||||||
|
self.call(
|
||||||
|
building.CmdSetAttribute(),
|
||||||
|
"Obj/test3[0]",
|
||||||
|
"Attribute Obj/test3[0] [category:None] = {'one': 1}"
|
||||||
)
|
)
|
||||||
self.call(building.CmdSetAttribute(), "Obj/test3[0]", "Attribute Obj/test3[0] = {'one': 1}")
|
|
||||||
self.call(
|
self.call(
|
||||||
building.CmdSetAttribute(),
|
building.CmdSetAttribute(),
|
||||||
"Obj/test3[0]['one'] =",
|
"Obj/test3[0]['one'] =",
|
||||||
"Deleted attribute 'test3[0]['one']' (= nested) from Obj.",
|
"Deleted attribute Obj/test3[0]['one'] [category:None]."
|
||||||
|
)
|
||||||
|
self.call(
|
||||||
|
building.CmdSetAttribute(),
|
||||||
|
"Obj/test3[0]",
|
||||||
|
"Attribute Obj/test3[0] [category:None] = {}")
|
||||||
|
self.call(
|
||||||
|
building.CmdSetAttribute(),
|
||||||
|
"Obj/test3",
|
||||||
|
"Attribute Obj/test3 [category:None] = [{}]"
|
||||||
)
|
)
|
||||||
self.call(building.CmdSetAttribute(), "Obj/test3[0]", "Attribute Obj/test3[0] = {}")
|
|
||||||
self.call(building.CmdSetAttribute(), "Obj/test3", "Attribute Obj/test3 = [{}]")
|
|
||||||
|
|
||||||
# Naughty keys
|
# Naughty keys
|
||||||
self.call(
|
self.call(
|
||||||
building.CmdSetAttribute(),
|
building.CmdSetAttribute(),
|
||||||
"Obj/test4[0]='foo'",
|
"Obj/test4[0]='foo'",
|
||||||
"Created attribute Obj/test4[0] = 'foo'",
|
"Created attribute Obj/test4[0] [category:None] = foo",
|
||||||
)
|
)
|
||||||
self.call(building.CmdSetAttribute(), "Obj/test4[0]", "Attribute Obj/test4[0] = foo")
|
self.call(
|
||||||
|
building.CmdSetAttribute(),
|
||||||
|
"Obj/test4[0]",
|
||||||
|
"Attribute Obj/test4[0] [category:None] = foo")
|
||||||
self.call(
|
self.call(
|
||||||
building.CmdSetAttribute(),
|
building.CmdSetAttribute(),
|
||||||
"Obj/test4=[{'one': 1}]",
|
"Obj/test4=[{'one': 1}]",
|
||||||
"Created attribute Obj/test4 = [{'one': 1}]",
|
"Created attribute Obj/test4 [category:None] = [{'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"
|
|
||||||
)
|
|
||||||
# Restored access
|
|
||||||
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(
|
self.call(
|
||||||
building.CmdSetAttribute(),
|
building.CmdSetAttribute(),
|
||||||
"Obj/test4[0]['one']",
|
"Obj/test4[0]['one']",
|
||||||
"Obj has no attribute 'test4[0]['one']'.",
|
"Attribute Obj/test4[0]['one'] [category:None] = 1"
|
||||||
|
)
|
||||||
|
# Prefer nested items
|
||||||
|
self.call(
|
||||||
|
building.CmdSetAttribute(),
|
||||||
|
"Obj/test4[0]",
|
||||||
|
"Attribute Obj/test4[0] [category:None] = {'one': 1}"
|
||||||
|
)
|
||||||
|
self.call(
|
||||||
|
building.CmdSetAttribute(),
|
||||||
|
"Obj/test4[0]['one']",
|
||||||
|
"Attribute Obj/test4[0]['one'] [category:None] = 1"
|
||||||
|
)
|
||||||
|
# Restored access
|
||||||
|
self.call(
|
||||||
|
building.CmdWipe(),
|
||||||
|
"Obj/test4",
|
||||||
|
"Wiped attributes test4 on Obj.")
|
||||||
|
self.call(
|
||||||
|
building.CmdSetAttribute(),
|
||||||
|
"Obj/test4[0]",
|
||||||
|
"Attribute Obj/test4[0] [category:None] = foo")
|
||||||
|
self.call(
|
||||||
|
building.CmdSetAttribute(),
|
||||||
|
"Obj/test4[0]['one']",
|
||||||
|
"Attribute Obj/test4[0]['one'] [category:None] does not exist. (Nested lookups attempted)"
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_split_nested_attr(self):
|
def test_split_nested_attr(self):
|
||||||
|
|
@ -1864,7 +1940,7 @@ class TestBuilding(CommandTest):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
import evennia.commands.default.comms as cmd_comms # noqa
|
||||||
from evennia.utils.create import create_channel # noqa
|
from evennia.utils.create import create_channel # noqa
|
||||||
|
|
||||||
class TestCommsChannel(CommandTest):
|
class TestCommsChannel(CommandTest):
|
||||||
|
|
@ -1878,7 +1954,7 @@ class TestCommsChannel(CommandTest):
|
||||||
key="testchannel",
|
key="testchannel",
|
||||||
desc="A test channel")
|
desc="A test channel")
|
||||||
self.channel.connect(self.char1)
|
self.channel.connect(self.char1)
|
||||||
self.cmdchannel = comms.CmdChannel
|
self.cmdchannel = cmd_comms.CmdChannel
|
||||||
self.cmdchannel.account_caller = False
|
self.cmdchannel.account_caller = False
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
|
|
|
||||||
|
|
@ -1248,8 +1248,8 @@ class AttributeHandler:
|
||||||
def remove(
|
def remove(
|
||||||
self,
|
self,
|
||||||
key=None,
|
key=None,
|
||||||
raise_exception=False,
|
|
||||||
category=None,
|
category=None,
|
||||||
|
raise_exception=False,
|
||||||
accessing_obj=None,
|
accessing_obj=None,
|
||||||
default_access=True,
|
default_access=True,
|
||||||
):
|
):
|
||||||
|
|
@ -1260,11 +1260,11 @@ class AttributeHandler:
|
||||||
key (str or list, optional): An Attribute key to remove or a list of keys. If
|
key (str or list, optional): An Attribute key to remove or a list of keys. If
|
||||||
multiple keys, they must all be of the same `category`. If None and
|
multiple keys, they must all be of the same `category`. If None and
|
||||||
category is not given, remove all Attributes.
|
category is not given, remove all Attributes.
|
||||||
|
category (str, optional): The category within which to
|
||||||
|
remove the Attribute.
|
||||||
raise_exception (bool, optional): If set, not finding the
|
raise_exception (bool, optional): If set, not finding the
|
||||||
Attribute to delete will raise an exception instead of
|
Attribute to delete will raise an exception instead of
|
||||||
just quietly failing.
|
just quietly failing.
|
||||||
category (str, optional): The category within which to
|
|
||||||
remove the Attribute.
|
|
||||||
accessing_obj (object, optional): An object to check
|
accessing_obj (object, optional): An object to check
|
||||||
against the `attredit` lock. If not given, the check will
|
against the `attredit` lock. If not given, the check will
|
||||||
be skipped.
|
be skipped.
|
||||||
|
|
|
||||||
|
|
@ -395,12 +395,13 @@ def iter_to_str(iterable, endsep=", and", addquote=False):
|
||||||
"""
|
"""
|
||||||
if not iterable:
|
if not iterable:
|
||||||
return ""
|
return ""
|
||||||
|
iterable = list(make_iter(iterable))
|
||||||
len_iter = len(iterable)
|
len_iter = len(iterable)
|
||||||
|
|
||||||
if addquote:
|
if addquote:
|
||||||
iterable = tuple(f'"{val}"' for val in make_iter(iterable))
|
iterable = tuple(f'"{val}"' for val in iterable)
|
||||||
else:
|
else:
|
||||||
iterable = tuple(str(val) for val in make_iter(iterable))
|
iterable = tuple(str(val) for val in iterable)
|
||||||
|
|
||||||
if endsep.startswith(","):
|
if endsep.startswith(","):
|
||||||
# oxford comma alternative
|
# oxford comma alternative
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue