Cleanup, bug fixes, refactoring
This commit is contained in:
parent
174113b9ab
commit
a29b46d091
5 changed files with 404 additions and 302 deletions
|
|
@ -1092,7 +1092,7 @@ def _add_attr(caller, attr_string, **kwargs):
|
||||||
attrname, category = nameparts
|
attrname, category = nameparts
|
||||||
elif nparts > 2:
|
elif nparts > 2:
|
||||||
attrname, category, locks = nameparts
|
attrname, category, locks = nameparts
|
||||||
attr_tuple = (attrname, value, category, locks)
|
attr_tuple = (attrname, value, category, str(locks))
|
||||||
|
|
||||||
if attrname:
|
if attrname:
|
||||||
prot = _get_menu_prototype(caller)
|
prot = _get_menu_prototype(caller)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
"""
|
"""
|
||||||
|
|
||||||
Handling storage of prototypes, both database-based ones (DBPrototypes) and those defined in modules
|
Handling storage of prototypes, both database-based ones (DBPrototypes) and those defined in modules
|
||||||
(Read-only prototypes).
|
(Read-only prototypes). Also contains utility functions, formatters and manager functions.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
@ -31,7 +31,6 @@ _PROTOTYPE_TAG_CATEGORY = "from_prototype"
|
||||||
_PROTOTYPE_TAG_META_CATEGORY = "db_prototype"
|
_PROTOTYPE_TAG_META_CATEGORY = "db_prototype"
|
||||||
PROT_FUNCS = {}
|
PROT_FUNCS = {}
|
||||||
|
|
||||||
|
|
||||||
_RE_DBREF = re.compile(r"(?<!\$obj\()(#[0-9]+)")
|
_RE_DBREF = re.compile(r"(?<!\$obj\()(#[0-9]+)")
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -46,248 +45,29 @@ class ValidationError(RuntimeError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
# Protfunc parsing
|
def homogenize_prototype(prototype, custom_keys=None):
|
||||||
|
|
||||||
for mod in settings.PROT_FUNC_MODULES:
|
|
||||||
try:
|
|
||||||
callables = callables_from_module(mod)
|
|
||||||
PROT_FUNCS.update(callables)
|
|
||||||
except ImportError:
|
|
||||||
logger.log_trace()
|
|
||||||
raise
|
|
||||||
|
|
||||||
|
|
||||||
def protfunc_parser(value, available_functions=None, testing=False, stacktrace=False, **kwargs):
|
|
||||||
"""
|
"""
|
||||||
Parse a prototype value string for a protfunc and process it.
|
Homogenize the more free-form prototype (where undefined keys are non-category attributes)
|
||||||
|
into the stricter form using `attrs` required by the system.
|
||||||
Available protfuncs are specified as callables in one of the modules of
|
|
||||||
`settings.PROTFUNC_MODULES`, or specified on the command line.
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
value (any): The value to test for a parseable protfunc. Only strings will be parsed for
|
prototype (dict): Prototype.
|
||||||
protfuncs, all other types are returned as-is.
|
custom_keys (list, optional): Custom keys which should not be interpreted as attrs, beyond
|
||||||
available_functions (dict, optional): Mapping of name:protfunction to use for this parsing.
|
the default reserved keys.
|
||||||
If not set, use default sources.
|
|
||||||
testing (bool, optional): Passed to protfunc. If in a testing mode, some protfuncs may
|
|
||||||
behave differently.
|
|
||||||
stacktrace (bool, optional): If set, print the stack parsing process of the protfunc-parser.
|
|
||||||
|
|
||||||
Kwargs:
|
|
||||||
session (Session): Passed to protfunc. Session of the entity spawning the prototype.
|
|
||||||
protototype (dict): Passed to protfunc. The dict this protfunc is a part of.
|
|
||||||
current_key(str): Passed to protfunc. The key in the prototype that will hold this value.
|
|
||||||
any (any): Passed on to the protfunc.
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
testresult (tuple): If `testing` is set, returns a tuple (error, result) where error is
|
homogenized (dict): Prototype where all non-identified keys grouped as attributes.
|
||||||
either None or a string detailing the error from protfunc_parser or seen when trying to
|
|
||||||
run `literal_eval` on the parsed string.
|
|
||||||
any (any): A structure to replace the string on the prototype level. If this is a
|
|
||||||
callable or a (callable, (args,)) structure, it will be executed as if one had supplied
|
|
||||||
it to the prototype directly. This structure is also passed through literal_eval so one
|
|
||||||
can get actual Python primitives out of it (not just strings). It will also identify
|
|
||||||
eventual object #dbrefs in the output from the protfunc.
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if not isinstance(value, basestring):
|
reserved = _PROTOTYPE_RESERVED_KEYS + (custom_keys or ())
|
||||||
try:
|
attrs = list(prototype.get('attrs', [])) # break reference
|
||||||
value = value.dbref
|
homogenized = {}
|
||||||
except AttributeError:
|
for key, val in prototype.items():
|
||||||
pass
|
if key in reserved:
|
||||||
value = to_str(value, force_string=True)
|
homogenized[key] = val
|
||||||
|
|
||||||
available_functions = PROT_FUNCS if available_functions is None else available_functions
|
|
||||||
|
|
||||||
# insert $obj(#dbref) for #dbref
|
|
||||||
value = _RE_DBREF.sub("$obj(\\1)", value)
|
|
||||||
|
|
||||||
result = inlinefuncs.parse_inlinefunc(
|
|
||||||
value, available_funcs=available_functions,
|
|
||||||
stacktrace=stacktrace, testing=testing, **kwargs)
|
|
||||||
|
|
||||||
err = None
|
|
||||||
try:
|
|
||||||
result = literal_eval(result)
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
except Exception as err:
|
|
||||||
err = str(err)
|
|
||||||
if testing:
|
|
||||||
return err, result
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def format_available_protfuncs():
|
|
||||||
"""
|
|
||||||
Get all protfuncs in a pretty-formatted form.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
clr (str, optional): What coloration tag to use.
|
|
||||||
"""
|
|
||||||
out = []
|
|
||||||
for protfunc_name, protfunc in PROT_FUNCS.items():
|
|
||||||
out.append("- |c${name}|n - |W{docs}".format(
|
|
||||||
name=protfunc_name, docs=protfunc.__doc__.strip().replace("\n", "")))
|
|
||||||
return justify("\n".join(out), indent=8)
|
|
||||||
|
|
||||||
|
|
||||||
# helper functions
|
|
||||||
|
|
||||||
def value_to_obj(value, force=True):
|
|
||||||
"Always convert value(s) to Object, or None"
|
|
||||||
stype = type(value)
|
|
||||||
if is_iter(value):
|
|
||||||
if stype == dict:
|
|
||||||
return {value_to_obj_or_any(key): value_to_obj_or_any(val) for key, val in value.iter()}
|
|
||||||
else:
|
else:
|
||||||
return stype([value_to_obj_or_any(val) for val in value])
|
attrs.append((key, val, None, ''))
|
||||||
return dbid_to_obj(value, ObjectDB)
|
homogenized['attrs'] = attrs
|
||||||
|
return homogenized
|
||||||
|
|
||||||
def value_to_obj_or_any(value):
|
|
||||||
"Convert value(s) to Object if possible, otherwise keep original value"
|
|
||||||
stype = type(value)
|
|
||||||
if is_iter(value):
|
|
||||||
if stype == dict:
|
|
||||||
return {value_to_obj_or_any(key):
|
|
||||||
value_to_obj_or_any(val) for key, val in value.items()}
|
|
||||||
else:
|
|
||||||
return stype([value_to_obj_or_any(val) for val in value])
|
|
||||||
obj = dbid_to_obj(value, ObjectDB)
|
|
||||||
return obj if obj is not None else value
|
|
||||||
|
|
||||||
|
|
||||||
def prototype_to_str(prototype):
|
|
||||||
"""
|
|
||||||
Format a prototype to a nice string representation.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
prototype (dict): The prototype.
|
|
||||||
"""
|
|
||||||
|
|
||||||
header = """
|
|
||||||
|cprototype-key:|n {prototype_key}, |c-tags:|n {prototype_tags}, |c-locks:|n {prototype_locks}|n
|
|
||||||
|c-desc|n: {prototype_desc}
|
|
||||||
|cprototype-parent:|n {prototype_parent}
|
|
||||||
\n""".format(
|
|
||||||
prototype_key=prototype.get('prototype_key', '|r[UNSET](required)|n'),
|
|
||||||
prototype_tags=prototype.get('prototype_tags', '|wNone|n'),
|
|
||||||
prototype_locks=prototype.get('prototype_locks', '|wNone|n'),
|
|
||||||
prototype_desc=prototype.get('prototype_desc', '|wNone|n'),
|
|
||||||
prototype_parent=prototype.get('prototype_parent', '|wNone|n'))
|
|
||||||
|
|
||||||
key = prototype.get('key', '')
|
|
||||||
if key:
|
|
||||||
key = "|ckey:|n {key}".format(key=key)
|
|
||||||
aliases = prototype.get("aliases", '')
|
|
||||||
if aliases:
|
|
||||||
aliases = "|caliases:|n {aliases}".format(
|
|
||||||
aliases=", ".join(aliases))
|
|
||||||
attrs = prototype.get("attrs", '')
|
|
||||||
if attrs:
|
|
||||||
out = []
|
|
||||||
for (attrkey, value, category, locks) in attrs:
|
|
||||||
locks = ", ".join(lock for lock in locks if lock)
|
|
||||||
category = "|ccategory:|n {}".format(category) if category else ''
|
|
||||||
cat_locks = ""
|
|
||||||
if category or locks:
|
|
||||||
cat_locks = " (|ccategory:|n {category}, ".format(
|
|
||||||
category=category if category else "|wNone|n")
|
|
||||||
out.append(
|
|
||||||
"{attrkey}{cat_locks} |c=|n {value}".format(
|
|
||||||
attrkey=attrkey,
|
|
||||||
cat_locks=cat_locks,
|
|
||||||
locks=locks if locks else "|wNone|n",
|
|
||||||
value=value))
|
|
||||||
attrs = "|cattrs:|n\n {attrs}".format(attrs="\n ".join(out))
|
|
||||||
tags = prototype.get('tags', '')
|
|
||||||
if tags:
|
|
||||||
out = []
|
|
||||||
for (tagkey, category, data) in tags:
|
|
||||||
out.append("{tagkey} (category: {category}{dat})".format(
|
|
||||||
tagkey=tagkey, category=category, dat=", data: {}".format(data) if data else ""))
|
|
||||||
tags = "|ctags:|n\n {tags}".format(tags=", ".join(out))
|
|
||||||
locks = prototype.get('locks', '')
|
|
||||||
if locks:
|
|
||||||
locks = "|clocks:|n\n {locks}".format(locks=locks)
|
|
||||||
permissions = prototype.get("permissions", '')
|
|
||||||
if permissions:
|
|
||||||
permissions = "|cpermissions:|n {perms}".format(perms=", ".join(permissions))
|
|
||||||
location = prototype.get("location", '')
|
|
||||||
if location:
|
|
||||||
location = "|clocation:|n {location}".format(location=location)
|
|
||||||
home = prototype.get("home", '')
|
|
||||||
if home:
|
|
||||||
home = "|chome:|n {home}".format(home=home)
|
|
||||||
destination = prototype.get("destination", '')
|
|
||||||
if destination:
|
|
||||||
destination = "|cdestination:|n {destination}".format(destination=destination)
|
|
||||||
|
|
||||||
body = "\n".join(part for part in (key, aliases, attrs, tags, locks, permissions,
|
|
||||||
location, home, destination) if part)
|
|
||||||
|
|
||||||
return header.lstrip() + body.strip()
|
|
||||||
|
|
||||||
|
|
||||||
def check_permission(prototype_key, action, default=True):
|
|
||||||
"""
|
|
||||||
Helper function to check access to actions on given prototype.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
prototype_key (str): The prototype to affect.
|
|
||||||
action (str): One of "spawn" or "edit".
|
|
||||||
default (str): If action is unknown or prototype has no locks
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
passes (bool): If permission for action is granted or not.
|
|
||||||
|
|
||||||
"""
|
|
||||||
if action == 'edit':
|
|
||||||
if prototype_key in _MODULE_PROTOTYPES:
|
|
||||||
mod = _MODULE_PROTOTYPE_MODULES.get(prototype_key, "N/A")
|
|
||||||
logger.log_err("{} is a read-only prototype "
|
|
||||||
"(defined as code in {}).".format(prototype_key, mod))
|
|
||||||
return False
|
|
||||||
|
|
||||||
prototype = search_prototype(key=prototype_key)
|
|
||||||
if not prototype:
|
|
||||||
logger.log_err("Prototype {} not found.".format(prototype_key))
|
|
||||||
return False
|
|
||||||
|
|
||||||
lockstring = prototype.get("prototype_locks")
|
|
||||||
|
|
||||||
if lockstring:
|
|
||||||
return check_lockstring(None, lockstring, default=default, access_type=action)
|
|
||||||
return default
|
|
||||||
|
|
||||||
|
|
||||||
def init_spawn_value(value, validator=None):
|
|
||||||
"""
|
|
||||||
Analyze the prototype value and produce a value useful at the point of spawning.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
value (any): This can be:
|
|
||||||
callable - will be called as callable()
|
|
||||||
(callable, (args,)) - will be called as callable(*args)
|
|
||||||
other - will be assigned depending on the variable type
|
|
||||||
validator (callable, optional): If given, this will be called with the value to
|
|
||||||
check and guarantee the outcome is of a given type.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
any (any): The (potentially pre-processed value to use for this prototype key)
|
|
||||||
|
|
||||||
"""
|
|
||||||
value = protfunc_parser(value)
|
|
||||||
validator = validator if validator else lambda o: o
|
|
||||||
if callable(value):
|
|
||||||
return validator(value())
|
|
||||||
elif value and is_iter(value) and callable(value[0]):
|
|
||||||
# a structure (callable, (args, ))
|
|
||||||
args = value[1:]
|
|
||||||
return validator(value[0](*make_iter(args)))
|
|
||||||
else:
|
|
||||||
return validator(value)
|
|
||||||
|
|
||||||
|
|
||||||
# module-based prototypes
|
# module-based prototypes
|
||||||
|
|
@ -295,7 +75,8 @@ def init_spawn_value(value, validator=None):
|
||||||
for mod in settings.PROTOTYPE_MODULES:
|
for mod in settings.PROTOTYPE_MODULES:
|
||||||
# to remove a default prototype, override it with an empty dict.
|
# to remove a default prototype, override it with an empty dict.
|
||||||
# internally we store as (key, desc, locks, tags, prototype_dict)
|
# internally we store as (key, desc, locks, tags, prototype_dict)
|
||||||
prots = [(prototype_key.lower(), prot) for prototype_key, prot in all_from_module(mod).items()
|
prots = [(prototype_key.lower(), homogenize_prototype(prot))
|
||||||
|
for prototype_key, prot in all_from_module(mod).items()
|
||||||
if prot and isinstance(prot, dict)]
|
if prot and isinstance(prot, dict)]
|
||||||
# assign module path to each prototype_key for easy reference
|
# assign module path to each prototype_key for easy reference
|
||||||
_MODULE_PROTOTYPE_MODULES.update({prototype_key.lower(): mod for prototype_key, _ in prots})
|
_MODULE_PROTOTYPE_MODULES.update({prototype_key.lower(): mod for prototype_key, _ in prots})
|
||||||
|
|
@ -347,6 +128,8 @@ def save_prototype(**kwargs):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
kwargs = homogenize_prototype(kwargs)
|
||||||
|
|
||||||
def _to_batchtuple(inp, *args):
|
def _to_batchtuple(inp, *args):
|
||||||
"build tuple suitable for batch-creation"
|
"build tuple suitable for batch-creation"
|
||||||
if is_iter(inp):
|
if is_iter(inp):
|
||||||
|
|
@ -403,8 +186,7 @@ def save_prototype(**kwargs):
|
||||||
attributes=[("prototype", prototype)])
|
attributes=[("prototype", prototype)])
|
||||||
return stored_prototype.db.prototype
|
return stored_prototype.db.prototype
|
||||||
|
|
||||||
# alias
|
create_prototype = save_prototype # alias
|
||||||
create_prototype = save_prototype
|
|
||||||
|
|
||||||
|
|
||||||
def delete_prototype(prototype_key, caller=None):
|
def delete_prototype(prototype_key, caller=None):
|
||||||
|
|
@ -640,7 +422,8 @@ def validate_prototype(prototype, protkey=None, protparents=None,
|
||||||
_flags['warnings'].append("Prototype {} can only be used as a mixin since it lacks "
|
_flags['warnings'].append("Prototype {} can only be used as a mixin since it lacks "
|
||||||
"a typeclass or a prototype_parent.".format(protkey))
|
"a typeclass or a prototype_parent.".format(protkey))
|
||||||
|
|
||||||
if strict and typeclass and typeclass not in get_all_typeclasses("evennia.objects.models.ObjectDB"):
|
if (strict and typeclass and typeclass not
|
||||||
|
in get_all_typeclasses("evennia.objects.models.ObjectDB")):
|
||||||
_flags['errors'].append(
|
_flags['errors'].append(
|
||||||
"Prototype {} is based on typeclass {}, which could not be imported!".format(
|
"Prototype {} is based on typeclass {}, which could not be imported!".format(
|
||||||
protkey, typeclass))
|
protkey, typeclass))
|
||||||
|
|
@ -685,7 +468,8 @@ def validate_prototype(prototype, protkey=None, protparents=None,
|
||||||
|
|
||||||
# make sure prototype_locks are set to defaults
|
# make sure prototype_locks are set to defaults
|
||||||
prototype_locks = [lstring.split(":", 1)
|
prototype_locks = [lstring.split(":", 1)
|
||||||
for lstring in prototype.get("prototype_locks", "").split(';') if ":" in lstring]
|
for lstring in prototype.get("prototype_locks", "").split(';')
|
||||||
|
if ":" in lstring]
|
||||||
locktypes = [tup[0].strip() for tup in prototype_locks]
|
locktypes = [tup[0].strip() for tup in prototype_locks]
|
||||||
if "spawn" not in locktypes:
|
if "spawn" not in locktypes:
|
||||||
prototype_locks.append(("spawn", "all()"))
|
prototype_locks.append(("spawn", "all()"))
|
||||||
|
|
@ -693,3 +477,249 @@ def validate_prototype(prototype, protkey=None, protparents=None,
|
||||||
prototype_locks.append(("edit", "all()"))
|
prototype_locks.append(("edit", "all()"))
|
||||||
prototype_locks = ";".join(":".join(tup) for tup in prototype_locks)
|
prototype_locks = ";".join(":".join(tup) for tup in prototype_locks)
|
||||||
prototype['prototype_locks'] = prototype_locks
|
prototype['prototype_locks'] = prototype_locks
|
||||||
|
|
||||||
|
|
||||||
|
# Protfunc parsing (in-prototype functions)
|
||||||
|
|
||||||
|
for mod in settings.PROT_FUNC_MODULES:
|
||||||
|
try:
|
||||||
|
callables = callables_from_module(mod)
|
||||||
|
PROT_FUNCS.update(callables)
|
||||||
|
except ImportError:
|
||||||
|
logger.log_trace()
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
def protfunc_parser(value, available_functions=None, testing=False, stacktrace=False, **kwargs):
|
||||||
|
"""
|
||||||
|
Parse a prototype value string for a protfunc and process it.
|
||||||
|
|
||||||
|
Available protfuncs are specified as callables in one of the modules of
|
||||||
|
`settings.PROTFUNC_MODULES`, or specified on the command line.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value (any): The value to test for a parseable protfunc. Only strings will be parsed for
|
||||||
|
protfuncs, all other types are returned as-is.
|
||||||
|
available_functions (dict, optional): Mapping of name:protfunction to use for this parsing.
|
||||||
|
If not set, use default sources.
|
||||||
|
testing (bool, optional): Passed to protfunc. If in a testing mode, some protfuncs may
|
||||||
|
behave differently.
|
||||||
|
stacktrace (bool, optional): If set, print the stack parsing process of the protfunc-parser.
|
||||||
|
|
||||||
|
Kwargs:
|
||||||
|
session (Session): Passed to protfunc. Session of the entity spawning the prototype.
|
||||||
|
protototype (dict): Passed to protfunc. The dict this protfunc is a part of.
|
||||||
|
current_key(str): Passed to protfunc. The key in the prototype that will hold this value.
|
||||||
|
any (any): Passed on to the protfunc.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
testresult (tuple): If `testing` is set, returns a tuple (error, result) where error is
|
||||||
|
either None or a string detailing the error from protfunc_parser or seen when trying to
|
||||||
|
run `literal_eval` on the parsed string.
|
||||||
|
any (any): A structure to replace the string on the prototype level. If this is a
|
||||||
|
callable or a (callable, (args,)) structure, it will be executed as if one had supplied
|
||||||
|
it to the prototype directly. This structure is also passed through literal_eval so one
|
||||||
|
can get actual Python primitives out of it (not just strings). It will also identify
|
||||||
|
eventual object #dbrefs in the output from the protfunc.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if not isinstance(value, basestring):
|
||||||
|
try:
|
||||||
|
value = value.dbref
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
value = to_str(value, force_string=True)
|
||||||
|
|
||||||
|
available_functions = PROT_FUNCS if available_functions is None else available_functions
|
||||||
|
|
||||||
|
# insert $obj(#dbref) for #dbref
|
||||||
|
value = _RE_DBREF.sub("$obj(\\1)", value)
|
||||||
|
|
||||||
|
result = inlinefuncs.parse_inlinefunc(
|
||||||
|
value, available_funcs=available_functions,
|
||||||
|
stacktrace=stacktrace, testing=testing, **kwargs)
|
||||||
|
|
||||||
|
err = None
|
||||||
|
try:
|
||||||
|
result = literal_eval(result)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
except Exception as err:
|
||||||
|
err = str(err)
|
||||||
|
if testing:
|
||||||
|
return err, result
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
# Various prototype utilities
|
||||||
|
|
||||||
|
def format_available_protfuncs():
|
||||||
|
"""
|
||||||
|
Get all protfuncs in a pretty-formatted form.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
clr (str, optional): What coloration tag to use.
|
||||||
|
"""
|
||||||
|
out = []
|
||||||
|
for protfunc_name, protfunc in PROT_FUNCS.items():
|
||||||
|
out.append("- |c${name}|n - |W{docs}".format(
|
||||||
|
name=protfunc_name, docs=protfunc.__doc__.strip().replace("\n", "")))
|
||||||
|
return justify("\n".join(out), indent=8)
|
||||||
|
|
||||||
|
|
||||||
|
def prototype_to_str(prototype):
|
||||||
|
"""
|
||||||
|
Format a prototype to a nice string representation.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
prototype (dict): The prototype.
|
||||||
|
"""
|
||||||
|
|
||||||
|
prototype = homogenize_prototype(prototype)
|
||||||
|
|
||||||
|
header = """
|
||||||
|
|cprototype-key:|n {prototype_key}, |c-tags:|n {prototype_tags}, |c-locks:|n {prototype_locks}|n
|
||||||
|
|c-desc|n: {prototype_desc}
|
||||||
|
|cprototype-parent:|n {prototype_parent}
|
||||||
|
\n""".format(
|
||||||
|
prototype_key=prototype.get('prototype_key', '|r[UNSET](required)|n'),
|
||||||
|
prototype_tags=prototype.get('prototype_tags', '|wNone|n'),
|
||||||
|
prototype_locks=prototype.get('prototype_locks', '|wNone|n'),
|
||||||
|
prototype_desc=prototype.get('prototype_desc', '|wNone|n'),
|
||||||
|
prototype_parent=prototype.get('prototype_parent', '|wNone|n'))
|
||||||
|
|
||||||
|
key = prototype.get('key', '')
|
||||||
|
if key:
|
||||||
|
key = "|ckey:|n {key}".format(key=key)
|
||||||
|
aliases = prototype.get("aliases", '')
|
||||||
|
if aliases:
|
||||||
|
aliases = "|caliases:|n {aliases}".format(
|
||||||
|
aliases=", ".join(aliases))
|
||||||
|
attrs = prototype.get("attrs", '')
|
||||||
|
if attrs:
|
||||||
|
out = []
|
||||||
|
for (attrkey, value, category, locks) in attrs:
|
||||||
|
locks = ", ".join(lock for lock in locks if lock)
|
||||||
|
category = "|ccategory:|n {}".format(category) if category else ''
|
||||||
|
cat_locks = ""
|
||||||
|
if category or locks:
|
||||||
|
cat_locks = " (|ccategory:|n {category}, ".format(
|
||||||
|
category=category if category else "|wNone|n")
|
||||||
|
out.append(
|
||||||
|
"{attrkey}{cat_locks} |c=|n {value}".format(
|
||||||
|
attrkey=attrkey,
|
||||||
|
cat_locks=cat_locks,
|
||||||
|
locks=locks if locks else "|wNone|n",
|
||||||
|
value=value))
|
||||||
|
attrs = "|cattrs:|n\n {attrs}".format(attrs="\n ".join(out))
|
||||||
|
tags = prototype.get('tags', '')
|
||||||
|
if tags:
|
||||||
|
out = []
|
||||||
|
for (tagkey, category, data) in tags:
|
||||||
|
out.append("{tagkey} (category: {category}{dat})".format(
|
||||||
|
tagkey=tagkey, category=category, dat=", data: {}".format(data) if data else ""))
|
||||||
|
tags = "|ctags:|n\n {tags}".format(tags=", ".join(out))
|
||||||
|
locks = prototype.get('locks', '')
|
||||||
|
if locks:
|
||||||
|
locks = "|clocks:|n\n {locks}".format(locks=locks)
|
||||||
|
permissions = prototype.get("permissions", '')
|
||||||
|
if permissions:
|
||||||
|
permissions = "|cpermissions:|n {perms}".format(perms=", ".join(permissions))
|
||||||
|
location = prototype.get("location", '')
|
||||||
|
if location:
|
||||||
|
location = "|clocation:|n {location}".format(location=location)
|
||||||
|
home = prototype.get("home", '')
|
||||||
|
if home:
|
||||||
|
home = "|chome:|n {home}".format(home=home)
|
||||||
|
destination = prototype.get("destination", '')
|
||||||
|
if destination:
|
||||||
|
destination = "|cdestination:|n {destination}".format(destination=destination)
|
||||||
|
|
||||||
|
body = "\n".join(part for part in (key, aliases, attrs, tags, locks, permissions,
|
||||||
|
location, home, destination) if part)
|
||||||
|
|
||||||
|
return header.lstrip() + body.strip()
|
||||||
|
|
||||||
|
|
||||||
|
def check_permission(prototype_key, action, default=True):
|
||||||
|
"""
|
||||||
|
Helper function to check access to actions on given prototype.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
prototype_key (str): The prototype to affect.
|
||||||
|
action (str): One of "spawn" or "edit".
|
||||||
|
default (str): If action is unknown or prototype has no locks
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
passes (bool): If permission for action is granted or not.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if action == 'edit':
|
||||||
|
if prototype_key in _MODULE_PROTOTYPES:
|
||||||
|
mod = _MODULE_PROTOTYPE_MODULES.get(prototype_key, "N/A")
|
||||||
|
logger.log_err("{} is a read-only prototype "
|
||||||
|
"(defined as code in {}).".format(prototype_key, mod))
|
||||||
|
return False
|
||||||
|
|
||||||
|
prototype = search_prototype(key=prototype_key)
|
||||||
|
if not prototype:
|
||||||
|
logger.log_err("Prototype {} not found.".format(prototype_key))
|
||||||
|
return False
|
||||||
|
|
||||||
|
lockstring = prototype.get("prototype_locks")
|
||||||
|
|
||||||
|
if lockstring:
|
||||||
|
return check_lockstring(None, lockstring, default=default, access_type=action)
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
|
def init_spawn_value(value, validator=None):
|
||||||
|
"""
|
||||||
|
Analyze the prototype value and produce a value useful at the point of spawning.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value (any): This can be:
|
||||||
|
callable - will be called as callable()
|
||||||
|
(callable, (args,)) - will be called as callable(*args)
|
||||||
|
other - will be assigned depending on the variable type
|
||||||
|
validator (callable, optional): If given, this will be called with the value to
|
||||||
|
check and guarantee the outcome is of a given type.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
any (any): The (potentially pre-processed value to use for this prototype key)
|
||||||
|
|
||||||
|
"""
|
||||||
|
value = protfunc_parser(value)
|
||||||
|
validator = validator if validator else lambda o: o
|
||||||
|
if callable(value):
|
||||||
|
return validator(value())
|
||||||
|
elif value and is_iter(value) and callable(value[0]):
|
||||||
|
# a structure (callable, (args, ))
|
||||||
|
args = value[1:]
|
||||||
|
return validator(value[0](*make_iter(args)))
|
||||||
|
else:
|
||||||
|
return validator(value)
|
||||||
|
|
||||||
|
|
||||||
|
def value_to_obj_or_any(value):
|
||||||
|
"Convert value(s) to Object if possible, otherwise keep original value"
|
||||||
|
stype = type(value)
|
||||||
|
if is_iter(value):
|
||||||
|
if stype == dict:
|
||||||
|
return {value_to_obj_or_any(key):
|
||||||
|
value_to_obj_or_any(val) for key, val in value.items()}
|
||||||
|
else:
|
||||||
|
return stype([value_to_obj_or_any(val) for val in value])
|
||||||
|
obj = dbid_to_obj(value, ObjectDB)
|
||||||
|
return obj if obj is not None else value
|
||||||
|
|
||||||
|
|
||||||
|
def value_to_obj(value, force=True):
|
||||||
|
"Always convert value(s) to Object, or None"
|
||||||
|
stype = type(value)
|
||||||
|
if is_iter(value):
|
||||||
|
if stype == dict:
|
||||||
|
return {value_to_obj_or_any(key): value_to_obj_or_any(val) for key, val in value.iter()}
|
||||||
|
else:
|
||||||
|
return stype([value_to_obj_or_any(val) for val in value])
|
||||||
|
return dbid_to_obj(value, ObjectDB)
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,14 @@ The spawner takes input files containing object definitions in
|
||||||
dictionary forms. These use a prototype architecture to define
|
dictionary forms. These use a prototype architecture to define
|
||||||
unique objects without having to make a Typeclass for each.
|
unique objects without having to make a Typeclass for each.
|
||||||
|
|
||||||
The main function is `spawn(*prototype)`, where the `prototype`
|
There main function is `spawn(*prototype)`, where the `prototype`
|
||||||
is a dictionary like this:
|
is a dictionary like this:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
GOBLIN = {
|
from evennia.prototypes import prototypes
|
||||||
|
|
||||||
|
prot = {
|
||||||
|
"prototype_key": "goblin",
|
||||||
"typeclass": "types.objects.Monster",
|
"typeclass": "types.objects.Monster",
|
||||||
"key": "goblin grunt",
|
"key": "goblin grunt",
|
||||||
"health": lambda: randint(20,30),
|
"health": lambda: randint(20,30),
|
||||||
|
|
@ -19,6 +22,9 @@ GOBLIN = {
|
||||||
"tags": ["mob", "evil", ('greenskin','mob')]
|
"tags": ["mob", "evil", ('greenskin','mob')]
|
||||||
"attrs": [("weapon", "sword")]
|
"attrs": [("weapon", "sword")]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
prot = prototypes.create_prototype(**prot)
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Possible keywords are:
|
Possible keywords are:
|
||||||
|
|
@ -57,7 +63,7 @@ Possible keywords are:
|
||||||
form allows more complex Attributes to be set. Tuples at least specify `(key, value)`
|
form allows more complex Attributes to be set. Tuples at least specify `(key, value)`
|
||||||
but can also specify up to `(key, value, category, lockstring)`. If you want to specify a
|
but can also specify up to `(key, value, category, lockstring)`. If you want to specify a
|
||||||
lockstring but not a category, set the category to `None`.
|
lockstring but not a category, set the category to `None`.
|
||||||
ndb_<name> (any): value of a nattribute (ndb_ is stripped)
|
ndb_<name> (any): value of a nattribute (ndb_ is stripped) - this is of limited use.
|
||||||
other (any): any other name is interpreted as the key of an Attribute with
|
other (any): any other name is interpreted as the key of an Attribute with
|
||||||
its value. Such Attributes have no categories.
|
its value. Such Attributes have no categories.
|
||||||
|
|
||||||
|
|
@ -66,15 +72,16 @@ return the value to enter into the field and will be called every time
|
||||||
the prototype is used to spawn an object. Note, if you want to store
|
the prototype is used to spawn an object. Note, if you want to store
|
||||||
a callable in an Attribute, embed it in a tuple to the `args` keyword.
|
a callable in an Attribute, embed it in a tuple to the `args` keyword.
|
||||||
|
|
||||||
By specifying the "prototype" key, the prototype becomes a child of
|
By specifying the "prototype_parent" key, the prototype becomes a child of
|
||||||
that prototype, inheritng all prototype slots it does not explicitly
|
the given prototype, inheritng all prototype slots it does not explicitly
|
||||||
define itself, while overloading those that it does specify.
|
define itself, while overloading those that it does specify.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
import random
|
import random
|
||||||
|
|
||||||
|
|
||||||
GOBLIN_WIZARD = {
|
{
|
||||||
|
"prototype_key": "goblin_wizard",
|
||||||
"prototype_parent": GOBLIN,
|
"prototype_parent": GOBLIN,
|
||||||
"key": "goblin wizard",
|
"key": "goblin wizard",
|
||||||
"spells": ["fire ball", "lighting bolt"]
|
"spells": ["fire ball", "lighting bolt"]
|
||||||
|
|
@ -189,7 +196,9 @@ def flatten_prototype(prototype, validate=False):
|
||||||
flattened (dict): The final, flattened prototype.
|
flattened (dict): The final, flattened prototype.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if prototype:
|
if prototype:
|
||||||
|
prototype = protlib.homogenize_prototype(prototype)
|
||||||
protparents = {prot['prototype_key'].lower(): prot for prot in protlib.search_prototype()}
|
protparents = {prot['prototype_key'].lower(): prot for prot in protlib.search_prototype()}
|
||||||
protlib.validate_prototype(prototype, None, protparents,
|
protlib.validate_prototype(prototype, None, protparents,
|
||||||
is_prototype_base=validate, strict=validate)
|
is_prototype_base=validate, strict=validate)
|
||||||
|
|
@ -253,7 +262,7 @@ def prototype_from_object(obj):
|
||||||
for tag in obj.tags.get(return_tagobj=True, return_list=True) if tag]
|
for tag in obj.tags.get(return_tagobj=True, return_list=True) if tag]
|
||||||
if tags:
|
if tags:
|
||||||
prot['tags'] = tags
|
prot['tags'] = tags
|
||||||
attrs = [(attr.key, attr.value, attr.category, attr.locks.all())
|
attrs = [(attr.key, attr.value, attr.category, ';'.join(attr.locks.all()))
|
||||||
for attr in obj.attributes.get(return_obj=True, return_list=True) if attr]
|
for attr in obj.attributes.get(return_obj=True, return_list=True) if attr]
|
||||||
if attrs:
|
if attrs:
|
||||||
prot['attrs'] = attrs
|
prot['attrs'] = attrs
|
||||||
|
|
@ -261,7 +270,7 @@ def prototype_from_object(obj):
|
||||||
return prot
|
return prot
|
||||||
|
|
||||||
|
|
||||||
def prototype_diff(prototype1, prototype2):
|
def prototype_diff(prototype1, prototype2, maxdepth=2):
|
||||||
"""
|
"""
|
||||||
A 'detailed' diff specifies differences down to individual sub-sectiions
|
A 'detailed' diff specifies differences down to individual sub-sectiions
|
||||||
of the prototype, like individual attributes, permissions etc. It is used
|
of the prototype, like individual attributes, permissions etc. It is used
|
||||||
|
|
@ -270,6 +279,9 @@ def prototype_diff(prototype1, prototype2):
|
||||||
Args:
|
Args:
|
||||||
prototype1 (dict): Original prototype.
|
prototype1 (dict): Original prototype.
|
||||||
prototype2 (dict): Comparison prototype.
|
prototype2 (dict): Comparison prototype.
|
||||||
|
maxdepth (int, optional): The maximum depth into the diff we go before treating the elements
|
||||||
|
of iterables as individual entities to compare. This is important since a single
|
||||||
|
attr/tag (for example) are represented by a tuple.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
diff (dict): A structure detailing how to convert prototype1 to prototype2. All
|
diff (dict): A structure detailing how to convert prototype1 to prototype2. All
|
||||||
|
|
@ -280,7 +292,7 @@ def prototype_diff(prototype1, prototype2):
|
||||||
instruction can be one of "REMOVE", "ADD", "UPDATE" or "KEEP".
|
instruction can be one of "REMOVE", "ADD", "UPDATE" or "KEEP".
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def _recursive_diff(old, new):
|
def _recursive_diff(old, new, depth=0):
|
||||||
|
|
||||||
old_type = type(old)
|
old_type = type(old)
|
||||||
new_type = type(new)
|
new_type = type(new)
|
||||||
|
|
@ -292,14 +304,14 @@ def prototype_diff(prototype1, prototype2):
|
||||||
return (old, new, "ADD")
|
return (old, new, "ADD")
|
||||||
else:
|
else:
|
||||||
return (old, new, "UPDATE")
|
return (old, new, "UPDATE")
|
||||||
elif new_type == dict:
|
elif depth < maxdepth and new_type == dict:
|
||||||
all_keys = set(old.keys() + new.keys())
|
all_keys = set(old.keys() + new.keys())
|
||||||
return {key: _recursive_diff(old.get(key), new.get(key)) for key in all_keys}
|
return {key: _recursive_diff(old.get(key), new.get(key), depth=depth + 1) for key in all_keys}
|
||||||
elif is_iter(new):
|
elif depth < maxdepth and is_iter(new):
|
||||||
old_map = {part[0] if is_iter(part) else part: part for part in old}
|
old_map = {part[0] if is_iter(part) else part: part for part in old}
|
||||||
new_map = {part[0] if is_iter(part) else part: part for part in new}
|
new_map = {part[0] if is_iter(part) else part: part for part in new}
|
||||||
all_keys = set(old_map.keys() + new_map.keys())
|
all_keys = set(old_map.keys() + new_map.keys())
|
||||||
return {key: _recursive_diff(old_map.get(key), new_map.get(key)) for key in all_keys}
|
return {key: _recursive_diff(old_map.get(key), new_map.get(key), depth=depth + 1) for key in all_keys}
|
||||||
elif old != new:
|
elif old != new:
|
||||||
return (old, new, "UPDATE")
|
return (old, new, "UPDATE")
|
||||||
else:
|
else:
|
||||||
|
|
@ -346,13 +358,13 @@ def flatten_diff(diff):
|
||||||
typ = type(diffpart)
|
typ = type(diffpart)
|
||||||
if typ == tuple and len(diffpart) == 3 and diffpart[2] in valid_instructions:
|
if typ == tuple and len(diffpart) == 3 and diffpart[2] in valid_instructions:
|
||||||
out = [diffpart[2]]
|
out = [diffpart[2]]
|
||||||
elif type == dict:
|
elif typ == dict:
|
||||||
# all other are dicts
|
# all other are dicts
|
||||||
for val in diffpart.values():
|
for val in diffpart.values():
|
||||||
out.extend(_get_all_nested_diff_instructions(val))
|
out.extend(_get_all_nested_diff_instructions(val))
|
||||||
else:
|
else:
|
||||||
raise RuntimeError("Diff contains non-dicts that are not on the "
|
raise RuntimeError("Diff contains non-dicts that are not on the "
|
||||||
"form (old, new, inst): {}".format(diff))
|
"form (old, new, inst): {}".format(diffpart))
|
||||||
return out
|
return out
|
||||||
|
|
||||||
flat_diff = {}
|
flat_diff = {}
|
||||||
|
|
@ -402,7 +414,7 @@ def prototype_diff_from_object(prototype, obj):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
obj_prototype = prototype_from_object(obj)
|
obj_prototype = prototype_from_object(obj)
|
||||||
diff = prototype_diff(obj_prototype, prototype)
|
diff = prototype_diff(obj_prototype, protlib.homogenize_prototype(prototype))
|
||||||
return diff, obj_prototype
|
return diff, obj_prototype
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -421,6 +433,8 @@ def batch_update_objects_with_prototype(prototype, diff=None, objects=None):
|
||||||
changed (int): The number of objects that had changes applied to them.
|
changed (int): The number of objects that had changes applied to them.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
prototype = protlib.homogenize_prototype(prototype)
|
||||||
|
|
||||||
if isinstance(prototype, basestring):
|
if isinstance(prototype, basestring):
|
||||||
new_prototype = protlib.search_prototype(prototype)
|
new_prototype = protlib.search_prototype(prototype)
|
||||||
else:
|
else:
|
||||||
|
|
@ -439,7 +453,6 @@ def batch_update_objects_with_prototype(prototype, diff=None, objects=None):
|
||||||
|
|
||||||
# make sure the diff is flattened
|
# make sure the diff is flattened
|
||||||
diff = flatten_diff(diff)
|
diff = flatten_diff(diff)
|
||||||
|
|
||||||
changed = 0
|
changed = 0
|
||||||
for obj in objects:
|
for obj in objects:
|
||||||
do_save = False
|
do_save = False
|
||||||
|
|
@ -619,9 +632,9 @@ def spawn(*prototypes, **kwargs):
|
||||||
(no object creation) and return the create-kwargs.
|
(no object creation) and return the create-kwargs.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
object (Object, dict or list): Spawned object. If `only_validate` is given, return
|
object (Object, dict or list): Spawned object(s). If `only_validate` is given, return
|
||||||
a list of the creation kwargs to build the object(s) without actually creating it. If
|
a list of the creation kwargs to build the object(s) without actually creating it. If
|
||||||
`return_parents` is set, return dict of prototype parents.
|
`return_parents` is set, instead return dict of prototype parents.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# get available protparents
|
# get available protparents
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ class TestUtils(EvenniaTest):
|
||||||
self.obj1.tags.add('foo')
|
self.obj1.tags.add('foo')
|
||||||
new_prot = spawner.prototype_from_object(self.obj1)
|
new_prot = spawner.prototype_from_object(self.obj1)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
{'attrs': [('test', 'testval', None, [''])],
|
{'attrs': [('test', 'testval', None, '')],
|
||||||
'home': Something,
|
'home': Something,
|
||||||
'key': 'Obj',
|
'key': 'Obj',
|
||||||
'location': Something,
|
'location': Something,
|
||||||
|
|
@ -94,14 +94,15 @@ class TestUtils(EvenniaTest):
|
||||||
def test_update_objects_from_prototypes(self):
|
def test_update_objects_from_prototypes(self):
|
||||||
|
|
||||||
self.maxDiff = None
|
self.maxDiff = None
|
||||||
self.obj1.attributes.add('oldtest', 'to_remove')
|
self.obj1.attributes.add('oldtest', 'to_keep')
|
||||||
|
|
||||||
old_prot = spawner.prototype_from_object(self.obj1)
|
old_prot = spawner.prototype_from_object(self.obj1)
|
||||||
|
|
||||||
# modify object away from prototype
|
# modify object away from prototype
|
||||||
self.obj1.attributes.add('test', 'testval')
|
self.obj1.attributes.add('test', 'testval')
|
||||||
|
self.obj1.attributes.add('desc', 'changed desc')
|
||||||
self.obj1.aliases.add('foo')
|
self.obj1.aliases.add('foo')
|
||||||
self.obj1.key = 'NewObj'
|
self.obj1.tags.add('footag', 'foocategory')
|
||||||
|
|
||||||
# modify prototype
|
# modify prototype
|
||||||
old_prot['new'] = 'new_val'
|
old_prot['new'] = 'new_val'
|
||||||
|
|
@ -109,53 +110,111 @@ class TestUtils(EvenniaTest):
|
||||||
old_prot['permissions'] = 'Builder'
|
old_prot['permissions'] = 'Builder'
|
||||||
# this will not update, since we don't update the prototype on-disk
|
# this will not update, since we don't update the prototype on-disk
|
||||||
old_prot['prototype_desc'] = 'New version of prototype'
|
old_prot['prototype_desc'] = 'New version of prototype'
|
||||||
|
old_prot['attrs'] += (("fooattr", "fooattrval", None, ''),)
|
||||||
|
|
||||||
# diff obj/prototype
|
# diff obj/prototype
|
||||||
pdiff = spawner.prototype_diff_from_object(old_prot, self.obj1)
|
old_prot_copy = old_prot.copy()
|
||||||
|
|
||||||
|
pdiff, obj_prototype = spawner.prototype_diff_from_object(old_prot, self.obj1)
|
||||||
|
|
||||||
|
self.assertEqual(old_prot_copy, old_prot)
|
||||||
|
|
||||||
|
self.assertEqual(obj_prototype,
|
||||||
|
{'aliases': ['foo'],
|
||||||
|
'attrs': [('oldtest', 'to_keep', None, ''),
|
||||||
|
('test', 'testval', None, ''),
|
||||||
|
('desc', 'changed desc', None, '')],
|
||||||
|
'key': 'Obj',
|
||||||
|
'home': '#1',
|
||||||
|
'location': '#1',
|
||||||
|
'locks': 'call:true();control:perm(Developer);delete:perm(Admin);'
|
||||||
|
'edit:perm(Admin);examine:perm(Builder);get:all();'
|
||||||
|
'puppet:pperm(Developer);tell:perm(Admin);view:all()',
|
||||||
|
'prototype_desc': 'Built from Obj',
|
||||||
|
'prototype_key': Something,
|
||||||
|
'prototype_locks': 'spawn:all();edit:all()',
|
||||||
|
'prototype_tags': [],
|
||||||
|
'typeclass': 'evennia.objects.objects.DefaultObject'})
|
||||||
|
|
||||||
|
self.assertEqual(old_prot,
|
||||||
|
{'attrs': [('oldtest', 'to_keep', None, ''),
|
||||||
|
('fooattr', 'fooattrval', None, '')],
|
||||||
|
'home': '#1',
|
||||||
|
'key': 'Obj',
|
||||||
|
'location': '#1',
|
||||||
|
'locks': 'call:true();control:perm(Developer);delete:perm(Admin);'
|
||||||
|
'edit:perm(Admin);examine:perm(Builder);get:all();'
|
||||||
|
'puppet:pperm(Developer);tell:perm(Admin);view:all()',
|
||||||
|
'new': 'new_val',
|
||||||
|
'permissions': 'Builder',
|
||||||
|
'prototype_desc': 'New version of prototype',
|
||||||
|
'prototype_key': Something,
|
||||||
|
'prototype_locks': 'spawn:all();edit:all()',
|
||||||
|
'prototype_tags': [],
|
||||||
|
'test': 'testval_changed',
|
||||||
|
'typeclass': 'evennia.objects.objects.DefaultObject'})
|
||||||
|
|
||||||
|
# from evennia import set_trace; set_trace(term_size=(182, 50))
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
pdiff,
|
pdiff,
|
||||||
({'aliases': 'REMOVE',
|
{'home': ('#1', '#1', 'KEEP'),
|
||||||
|
'prototype_locks': ('spawn:all();edit:all()',
|
||||||
|
'spawn:all();edit:all()', 'KEEP'),
|
||||||
|
'prototype_key': (Something, Something, 'UPDATE'),
|
||||||
|
'location': ('#1', '#1', 'KEEP'),
|
||||||
|
'locks': ('call:true();control:perm(Developer);delete:perm(Admin);'
|
||||||
|
'edit:perm(Admin);examine:perm(Builder);get:all();'
|
||||||
|
'puppet:pperm(Developer);tell:perm(Admin);view:all()',
|
||||||
|
'call:true();control:perm(Developer);delete:perm(Admin);'
|
||||||
|
'edit:perm(Admin);examine:perm(Builder);get:all();'
|
||||||
|
'puppet:pperm(Developer);tell:perm(Admin);view:all()', 'KEEP'),
|
||||||
|
'prototype_tags': {},
|
||||||
|
'attrs': {'oldtest': (('oldtest', 'to_keep', None, ''),
|
||||||
|
('oldtest', 'to_keep', None, ''), 'KEEP'),
|
||||||
|
'test': (('test', 'testval', None, ''),
|
||||||
|
None, 'REMOVE'),
|
||||||
|
'desc': (('desc', 'changed desc', None, ''),
|
||||||
|
None, 'REMOVE'),
|
||||||
|
'fooattr': (None, ('fooattr', 'fooattrval', None, ''), 'ADD'),
|
||||||
|
'test': (('test', 'testval', None, ''),
|
||||||
|
('test', 'testval_changed', None, ''), 'UPDATE'),
|
||||||
|
'new': (None, ('new', 'new_val', None, ''), 'ADD')},
|
||||||
|
'key': ('Obj', 'Obj', 'KEEP'),
|
||||||
|
'typeclass': ('evennia.objects.objects.DefaultObject',
|
||||||
|
'evennia.objects.objects.DefaultObject', 'KEEP'),
|
||||||
|
'aliases': (['foo'], None, 'REMOVE'),
|
||||||
|
'prototype_desc': ('Built from Obj',
|
||||||
|
'New version of prototype', 'UPDATE'),
|
||||||
|
'permissions': (None, 'Builder', 'ADD')}
|
||||||
|
)
|
||||||
|
|
||||||
|
# from evennia import set_trace;set_trace()
|
||||||
|
self.assertEqual(
|
||||||
|
spawner.flatten_diff(pdiff),
|
||||||
|
{'aliases': 'REMOVE',
|
||||||
'attrs': 'REPLACE',
|
'attrs': 'REPLACE',
|
||||||
'home': 'KEEP',
|
'home': 'KEEP',
|
||||||
'key': 'UPDATE',
|
'key': 'KEEP',
|
||||||
'location': 'KEEP',
|
'location': 'KEEP',
|
||||||
'locks': 'KEEP',
|
'locks': 'KEEP',
|
||||||
'new': 'UPDATE',
|
|
||||||
'permissions': 'UPDATE',
|
'permissions': 'UPDATE',
|
||||||
'prototype_desc': 'UPDATE',
|
'prototype_desc': 'UPDATE',
|
||||||
'prototype_key': 'UPDATE',
|
'prototype_key': 'UPDATE',
|
||||||
'prototype_locks': 'KEEP',
|
'prototype_locks': 'KEEP',
|
||||||
'prototype_tags': 'KEEP',
|
'prototype_tags': 'KEEP',
|
||||||
'test': 'UPDATE',
|
'typeclass': 'KEEP'}
|
||||||
'typeclass': 'KEEP'},
|
|
||||||
{'attrs': [('oldtest', 'to_remove', None, ['']),
|
|
||||||
('test', 'testval', None, [''])],
|
|
||||||
'prototype_locks': 'spawn:all();edit:all()',
|
|
||||||
'prototype_key': Something,
|
|
||||||
'locks': ";".join([
|
|
||||||
'call:true()', 'control:perm(Developer)',
|
|
||||||
'delete:perm(Admin)', 'edit:perm(Admin)',
|
|
||||||
'examine:perm(Builder)', 'get:all()',
|
|
||||||
'puppet:pperm(Developer)', 'tell:perm(Admin)',
|
|
||||||
'view:all()']),
|
|
||||||
'prototype_tags': [],
|
|
||||||
'location': "#1",
|
|
||||||
'key': 'NewObj',
|
|
||||||
'home': '#1',
|
|
||||||
'typeclass': 'evennia.objects.objects.DefaultObject',
|
|
||||||
'prototype_desc': 'Built from NewObj',
|
|
||||||
'aliases': 'foo'})
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# apply diff
|
# apply diff
|
||||||
count = spawner.batch_update_objects_with_prototype(
|
count = spawner.batch_update_objects_with_prototype(
|
||||||
old_prot, diff=pdiff[0], objects=[self.obj1])
|
old_prot, diff=pdiff, objects=[self.obj1])
|
||||||
self.assertEqual(count, 1)
|
self.assertEqual(count, 1)
|
||||||
|
|
||||||
new_prot = spawner.prototype_from_object(self.obj1)
|
new_prot = spawner.prototype_from_object(self.obj1)
|
||||||
self.assertEqual({'attrs': [('test', 'testval_changed', None, ['']),
|
self.assertEqual({'attrs': [('oldtest', 'to_keep', None, ''),
|
||||||
('new', 'new_val', None, [''])],
|
('fooattr', 'fooattrval', None, ''),
|
||||||
|
('new', 'new_val', None, ''),
|
||||||
|
('test', 'testval_changed', None, '')],
|
||||||
'home': Something,
|
'home': Something,
|
||||||
'key': 'Obj',
|
'key': 'Obj',
|
||||||
'location': Something,
|
'location': Something,
|
||||||
|
|
|
||||||
|
|
@ -564,7 +564,7 @@ class AttributeHandler(object):
|
||||||
ntup = len(tup)
|
ntup = len(tup)
|
||||||
keystr = str(tup[0]).strip().lower()
|
keystr = str(tup[0]).strip().lower()
|
||||||
new_value = tup[1]
|
new_value = tup[1]
|
||||||
category = str(tup[2]).strip().lower() if ntup > 2 else None
|
category = str(tup[2]).strip().lower() if ntup > 2 and tup[2] is not None else None
|
||||||
lockstring = tup[3] if ntup > 3 else ""
|
lockstring = tup[3] if ntup > 3 else ""
|
||||||
|
|
||||||
attr_objs = self._getcache(keystr, category)
|
attr_objs = self._getcache(keystr, category)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue