Validate prototype parent before chosing it
This commit is contained in:
parent
e09576812f
commit
f27673b741
3 changed files with 54 additions and 31 deletions
|
|
@ -42,14 +42,15 @@ def _get_menu_prototype(caller):
|
||||||
return prototype
|
return prototype
|
||||||
|
|
||||||
|
|
||||||
def _get_flat_menu_prototype(caller, refresh=False):
|
def _get_flat_menu_prototype(caller, refresh=False, validate=False):
|
||||||
"""Return prototype where parent values are included"""
|
"""Return prototype where parent values are included"""
|
||||||
flat_prototype = None
|
flat_prototype = None
|
||||||
if not refresh and hasattr(caller.ndb._menutree, "olc_flat_prototype"):
|
if not refresh and hasattr(caller.ndb._menutree, "olc_flat_prototype"):
|
||||||
flat_prototype = caller.ndb._menutree.olc_flat_prototype
|
flat_prototype = caller.ndb._menutree.olc_flat_prototype
|
||||||
if not flat_prototype:
|
if not flat_prototype:
|
||||||
prot = _get_menu_prototype(caller)
|
prot = _get_menu_prototype(caller)
|
||||||
caller.ndb._menutree.olc_flat_prototype = flat_prototype = spawner.flatten_prototype(prot)
|
caller.ndb._menutree.olc_flat_prototype = \
|
||||||
|
flat_prototype = spawner.flatten_prototype(prot, validate=validate)
|
||||||
return flat_prototype
|
return flat_prototype
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -305,11 +306,11 @@ def node_index(caller):
|
||||||
{"desc": "|WPrototype-Key|n|n{}".format(
|
{"desc": "|WPrototype-Key|n|n{}".format(
|
||||||
_format_option_value("Key", "prototype_key" not in prototype, prototype, None)),
|
_format_option_value("Key", "prototype_key" not in prototype, prototype, None)),
|
||||||
"goto": "node_prototype_key"})
|
"goto": "node_prototype_key"})
|
||||||
for key in ('Prototype-parent', 'Typeclass', 'Key', 'Aliases', 'Attrs', 'Tags', 'Locks',
|
for key in ('Prototype_parent', 'Typeclass', 'Key', 'Aliases', 'Attrs', 'Tags', 'Locks',
|
||||||
'Permissions', 'Location', 'Home', 'Destination'):
|
'Permissions', 'Location', 'Home', 'Destination'):
|
||||||
required = False
|
required = False
|
||||||
cropper = None
|
cropper = None
|
||||||
if key in ("Prototype-parent", "Typeclass"):
|
if key in ("Prototype_parent", "Typeclass"):
|
||||||
required = ("prototype_parent" not in prototype) and ("typeclass" not in prototype)
|
required = ("prototype_parent" not in prototype) and ("typeclass" not in prototype)
|
||||||
if key == 'Typeclass':
|
if key == 'Typeclass':
|
||||||
cropper = _path_cropper
|
cropper = _path_cropper
|
||||||
|
|
@ -429,11 +430,24 @@ def _prototype_parent_examine(caller, prototype_name):
|
||||||
caller.msg("Prototype not registered.")
|
caller.msg("Prototype not registered.")
|
||||||
|
|
||||||
|
|
||||||
def _prototype_parent_select(caller, prototype):
|
def _prototype_parent_select(caller, new_parent):
|
||||||
ret = _set_property(caller, "",
|
|
||||||
prop="prototype_parent", processor=str, next_node="node_typeclass")
|
|
||||||
caller.msg("Selected prototype |y{}|n.".format(prototype))
|
|
||||||
|
|
||||||
|
ret = None
|
||||||
|
prototype_parent = protlib.search_prototype(new_parent)
|
||||||
|
try:
|
||||||
|
if prototype_parent:
|
||||||
|
spawner.flatten_prototype(prototype_parent[0], validate=True)
|
||||||
|
else:
|
||||||
|
raise RuntimeError("Not found.")
|
||||||
|
except RuntimeError as err:
|
||||||
|
caller.msg("Selected prototype parent {} "
|
||||||
|
"caused Error(s):\n|r{}|n".format(new_parent, err))
|
||||||
|
else:
|
||||||
|
ret = _set_property(caller, new_parent,
|
||||||
|
prop="prototype_parent",
|
||||||
|
processor=str, next_node="node_prototype_parent")
|
||||||
|
_get_flat_menu_prototype(caller, refresh=True)
|
||||||
|
caller.msg("Selected prototype parent |c{}|n.".format(new_parent))
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -441,12 +455,12 @@ def _prototype_parent_select(caller, prototype):
|
||||||
def node_prototype_parent(caller):
|
def node_prototype_parent(caller):
|
||||||
prototype = _get_menu_prototype(caller)
|
prototype = _get_menu_prototype(caller)
|
||||||
|
|
||||||
prot_parent_key = prototype.get('prototype')
|
prot_parent_keys = prototype.get('prototype_parent')
|
||||||
|
|
||||||
text = """
|
text = """
|
||||||
The |cPrototype Parent|n allows you to |winherit|n prototype values from another named
|
The |cPrototype Parent|n allows you to |winherit|n prototype values from another named
|
||||||
prototype (given as that prototype's |wprototype_key|). If not changing these values in the
|
prototype (given as that prototype's |wprototype_key|n). If not changing these values in
|
||||||
current prototype, the parent's value will be used. Pick the available prototypes below.
|
the current prototype, the parent's value will be used. Pick the available prototypes below.
|
||||||
|
|
||||||
Note that somewhere in the prototype's parentage, a |ctypeclass|n must be specified. If no
|
Note that somewhere in the prototype's parentage, a |ctypeclass|n must be specified. If no
|
||||||
parent is given, this prototype must define the typeclass (next menu node).
|
parent is given, this prototype must define the typeclass (next menu node).
|
||||||
|
|
@ -459,18 +473,23 @@ def node_prototype_parent(caller):
|
||||||
prototype to be valid.
|
prototype to be valid.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if prot_parent_key:
|
ptexts = []
|
||||||
prot_parent = protlib.search_prototype(prot_parent_key)
|
if prot_parent_keys:
|
||||||
|
for pkey in utils.make_iter(prot_parent_keys):
|
||||||
|
prot_parent = protlib.search_prototype(pkey)
|
||||||
if prot_parent:
|
if prot_parent:
|
||||||
text = text.format(
|
prot_parent = prot_parent[0]
|
||||||
current="Current parent prototype is {}:\n{}".format(
|
ptexts.append("|c -- {pkey} -- |n\n{prot}".format(
|
||||||
protlib.prototype_to_str(prot_parent)))
|
pkey=pkey,
|
||||||
|
prot=protlib.prototype_to_str(prot_parent)))
|
||||||
else:
|
else:
|
||||||
text = text.format(
|
ptexts.append("Prototype parent |r{pkey} was not found.".format(pkey=pkey))
|
||||||
current="Current parent prototype |r{prototype}|n "
|
|
||||||
"does not appear to exist.".format(prot_parent_key))
|
if not ptexts:
|
||||||
else:
|
ptexts.append("[No prototype_parent set]")
|
||||||
text = text.format(current="Parent prototype is not set")
|
|
||||||
|
text = text.format(current="\n\n".join(ptexts))
|
||||||
|
|
||||||
text = (text, helptext)
|
text = (text, helptext)
|
||||||
|
|
||||||
options = _wizard_options("prototype_parent", "prototype_key", "typeclass", color="|W")
|
options = _wizard_options("prototype_parent", "prototype_key", "typeclass", color="|W")
|
||||||
|
|
@ -993,7 +1012,7 @@ def node_destination(caller):
|
||||||
the exit 'leads to'. It's usually unset for all other types of objects.
|
the exit 'leads to'. It's usually unset for all other types of objects.
|
||||||
|
|
||||||
{current}
|
{current}
|
||||||
""".format(current=_get_current_node(caller, "destination"))
|
""".format(current=_get_current_value(caller, "destination"))
|
||||||
|
|
||||||
helptext = """
|
helptext = """
|
||||||
The destination can be given as a #dbref but can also be explicitly searched for using
|
The destination can be given as a #dbref but can also be explicitly searched for using
|
||||||
|
|
|
||||||
|
|
@ -539,7 +539,7 @@ def list_prototypes(caller, key=None, tags=None, show_non_use=False, show_non_ed
|
||||||
|
|
||||||
|
|
||||||
def validate_prototype(prototype, protkey=None, protparents=None,
|
def validate_prototype(prototype, protkey=None, protparents=None,
|
||||||
is_prototype_base=True, _flags=None):
|
is_prototype_base=True, strict=True, _flags=None):
|
||||||
"""
|
"""
|
||||||
Run validation on a prototype, checking for inifinite regress.
|
Run validation on a prototype, checking for inifinite regress.
|
||||||
|
|
||||||
|
|
@ -552,6 +552,8 @@ def validate_prototype(prototype, protkey=None, protparents=None,
|
||||||
is_prototype_base (bool, optional): We are trying to create a new object *based on this
|
is_prototype_base (bool, optional): We are trying to create a new object *based on this
|
||||||
object*. This means we can't allow 'mixin'-style prototypes without typeclass/parent
|
object*. This means we can't allow 'mixin'-style prototypes without typeclass/parent
|
||||||
etc.
|
etc.
|
||||||
|
strict (bool, optional): If unset, don't require needed keys, only check against infinite
|
||||||
|
recursion etc.
|
||||||
_flags (dict, optional): Internal work dict that should not be set externally.
|
_flags (dict, optional): Internal work dict that should not be set externally.
|
||||||
Raises:
|
Raises:
|
||||||
RuntimeError: If prototype has invalid structure.
|
RuntimeError: If prototype has invalid structure.
|
||||||
|
|
@ -570,14 +572,14 @@ def validate_prototype(prototype, protkey=None, protparents=None,
|
||||||
|
|
||||||
protkey = protkey and protkey.lower() or prototype.get('prototype_key', None)
|
protkey = protkey and protkey.lower() or prototype.get('prototype_key', None)
|
||||||
|
|
||||||
if not bool(protkey):
|
if strict and not bool(protkey):
|
||||||
_flags['errors'].append("Prototype lacks a `prototype_key`.")
|
_flags['errors'].append("Prototype lacks a `prototype_key`.")
|
||||||
protkey = "[UNSET]"
|
protkey = "[UNSET]"
|
||||||
|
|
||||||
typeclass = prototype.get('typeclass')
|
typeclass = prototype.get('typeclass')
|
||||||
prototype_parent = prototype.get('prototype_parent', [])
|
prototype_parent = prototype.get('prototype_parent', [])
|
||||||
|
|
||||||
if not (typeclass or prototype_parent):
|
if strict and not (typeclass or prototype_parent):
|
||||||
if is_prototype_base:
|
if is_prototype_base:
|
||||||
_flags['errors'].append("Prototype {} requires `typeclass` "
|
_flags['errors'].append("Prototype {} requires `typeclass` "
|
||||||
"or 'prototype_parent'.".format(protkey))
|
"or 'prototype_parent'.".format(protkey))
|
||||||
|
|
@ -585,7 +587,7 @@ 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 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))
|
||||||
|
|
@ -615,7 +617,7 @@ def validate_prototype(prototype, protkey=None, protparents=None,
|
||||||
_flags['typeclass'] = typeclass
|
_flags['typeclass'] = typeclass
|
||||||
|
|
||||||
# if we get back to the current level without a typeclass it's an error.
|
# if we get back to the current level without a typeclass it's an error.
|
||||||
if is_prototype_base and _flags['depth'] <= 0 and not _flags['typeclass']:
|
if strict and is_prototype_base and _flags['depth'] <= 0 and not _flags['typeclass']:
|
||||||
_flags['errors'].append("Prototype {} has no `typeclass` defined anywhere in its parent "
|
_flags['errors'].append("Prototype {} has no `typeclass` defined anywhere in its parent "
|
||||||
"chain. Add `typeclass`, or a `prototype_parent` pointing to a "
|
"chain. Add `typeclass`, or a `prototype_parent` pointing to a "
|
||||||
"prototype with a typeclass.".format(protkey))
|
"prototype with a typeclass.".format(protkey))
|
||||||
|
|
|
||||||
|
|
@ -161,13 +161,14 @@ def _get_prototype(dic, prot, protparents):
|
||||||
return prot
|
return prot
|
||||||
|
|
||||||
|
|
||||||
def flatten_prototype(prototype):
|
def flatten_prototype(prototype, validate=False):
|
||||||
"""
|
"""
|
||||||
Produce a 'flattened' prototype, where all prototype parents in the inheritance tree have been
|
Produce a 'flattened' prototype, where all prototype parents in the inheritance tree have been
|
||||||
merged into a final prototype.
|
merged into a final prototype.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
prototype (dict): Prototype to flatten. Its `prototype_parent` field will be parsed.
|
prototype (dict): Prototype to flatten. Its `prototype_parent` field will be parsed.
|
||||||
|
validate (bool, optional): Validate for valid keys etc.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
flattened (dict): The final, flattened prototype.
|
flattened (dict): The final, flattened prototype.
|
||||||
|
|
@ -175,7 +176,8 @@ def flatten_prototype(prototype):
|
||||||
"""
|
"""
|
||||||
if prototype:
|
if 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, is_prototype_base=True)
|
protlib.validate_prototype(prototype, None, protparents,
|
||||||
|
is_prototype_base=validate, strict=validate)
|
||||||
return _get_prototype(prototype, {}, protparents)
|
return _get_prototype(prototype, {}, protparents)
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue