Validate prototype parent before chosing it

This commit is contained in:
Griatch 2018-07-25 19:51:48 +02:00
parent e09576812f
commit f27673b741
3 changed files with 54 additions and 31 deletions

View file

@ -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:
if prot_parent: for pkey in utils.make_iter(prot_parent_keys):
text = text.format( prot_parent = protlib.search_prototype(pkey)
current="Current parent prototype is {}:\n{}".format( if prot_parent:
protlib.prototype_to_str(prot_parent))) prot_parent = prot_parent[0]
else: ptexts.append("|c -- {pkey} -- |n\n{prot}".format(
text = text.format( pkey=pkey,
current="Current parent prototype |r{prototype}|n " prot=protlib.prototype_to_str(prot_parent)))
"does not appear to exist.".format(prot_parent_key)) else:
else: ptexts.append("Prototype parent |r{pkey} was not found.".format(pkey=pkey))
text = text.format(current="Parent prototype is not set")
if not ptexts:
ptexts.append("[No prototype_parent 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

View file

@ -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))

View file

@ -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 {}