Refactor spawner menu

This commit is contained in:
Griatch 2018-03-24 17:28:56 +01:00
parent ad4b58a6cf
commit 5ed765d664
2 changed files with 171 additions and 91 deletions

View file

@ -43,13 +43,18 @@ command definition too) with function definitions:
def node_with_other_name(caller, input_string): def node_with_other_name(caller, input_string):
# code # code
return text, options return text, options
def another_node(caller, input_string, **kwargs):
# code
return text, options
``` ```
Where caller is the object using the menu and input_string is the Where caller is the object using the menu and input_string is the
command entered by the user on the *previous* node (the command command entered by the user on the *previous* node (the command
entered to get to this node). The node function code will only be entered to get to this node). The node function code will only be
executed once per node-visit and the system will accept nodes with executed once per node-visit and the system will accept nodes with
both one or two arguments interchangeably. both one or two arguments interchangeably. It also accepts nodes
that takes **kwargs.
The menu tree itself is available on the caller as The menu tree itself is available on the caller as
`caller.ndb._menutree`. This makes it a convenient place to store `caller.ndb._menutree`. This makes it a convenient place to store
@ -82,12 +87,14 @@ menu is immediately exited and the default "look" command is called.
the callable. Those kwargs will also be passed into the next node if possible. the callable. Those kwargs will also be passed into the next node if possible.
Such a callable should return either a str or a (str, dict), where the Such a callable should return either a str or a (str, dict), where the
string is the name of the next node to go to and the dict is the new, string is the name of the next node to go to and the dict is the new,
(possibly modified) kwarg to pass into the next node. (possibly modified) kwarg to pass into the next node. If the callable returns
None or the empty string, the current node will be revisited.
- `exec` (str, callable or tuple, optional): This takes the same input as `goto` above - `exec` (str, callable or tuple, optional): This takes the same input as `goto` above
and runs before it. If given a node name, the node will be executed but will not and runs before it. If given a node name, the node will be executed but will not
be considered the next node. If node/callback returns str or (str, dict), these will be considered the next node. If node/callback returns str or (str, dict), these will
replace the `goto` step (`goto` callbacks will not fire), with the string being the replace the `goto` step (`goto` callbacks will not fire), with the string being the
next node name and the optional dict acting as the kwargs-input for the next node. next node name and the optional dict acting as the kwargs-input for the next node.
If an exec callable returns the empty string (only), the current node is re-run.
If key is not given, the option will automatically be identified by If key is not given, the option will automatically be identified by
its number 1..N. its number 1..N.
@ -669,6 +676,9 @@ class EvMenu(object):
if isinstance(ret, basestring): if isinstance(ret, basestring):
# only return a value if a string (a goto target), ignore all other returns # only return a value if a string (a goto target), ignore all other returns
if not ret:
# an empty string - rerun the same node
return self.nodename
return ret, kwargs return ret, kwargs
return None return None
@ -718,6 +728,9 @@ class EvMenu(object):
raise EvMenuError( raise EvMenuError(
"{}: goto callable must return str or (str, dict)".format(inp_nodename)) "{}: goto callable must return str or (str, dict)".format(inp_nodename))
nodename, kwargs = nodename[:2] nodename, kwargs = nodename[:2]
if not nodename:
# no nodename return. Re-run current node
nodename = self.nodename
try: try:
# execute the found node, make use of the returns. # execute the found node, make use of the returns.
nodetext, options = self._execute_node(nodename, raw_string, **kwargs) nodetext, options = self._execute_node(nodename, raw_string, **kwargs)

View file

@ -671,6 +671,10 @@ def _get_menu_metaprot(caller):
return metaproto return metaproto
def _is_new_prototype(caller):
return hasattr(caller.ndb._menutree, "olc_new")
def _set_menu_metaprot(caller, field, value): def _set_menu_metaprot(caller, field, value):
metaprot = _get_menu_metaprot(caller) metaprot = _get_menu_metaprot(caller)
kwargs = dict(metaprot.__dict__) kwargs = dict(metaprot.__dict__)
@ -678,30 +682,121 @@ def _set_menu_metaprot(caller, field, value):
caller.ndb._menutree.olc_metaprot = build_metaproto(**kwargs) caller.ndb._menutree.olc_metaprot = build_metaproto(**kwargs)
def node_meta_index(caller): def _format_property(key, required=False, metaprot=None, prototype=None):
metaprot = _get_menu_metaprot(caller) key = key.lower()
key = "|g{}|n".format( if metaprot is not None:
crop(metaprot.key, _MENU_CROP_WIDTH)) if metaprot.key else "|rundefined, required|n" prop = getattr(metaprot, key) or ''
desc = "|g{}|n".format( elif prototype is not None:
crop(metaprot.desc, _MENU_CROP_WIDTH)) if metaprot.desc else "''" prop = prototype.get(key, '')
tags = "|g{}|n".format(
crop(", ".join(metaprot.tags), _MENU_CROP_WIDTH)) if metaprot.tags else [] out = prop
locks = "|g{}|n".format( if callable(prop):
crop(", ".join(metaprot.locks), _MENU_CROP_WIDTH)) if metaprot.tags else [] if hasattr(prop, '__name__'):
prot = "|gdefined|n" if metaprot.prototype else "|rundefined, required|n" out = "<{}>".format(prop.__name__)
else:
out = repr(prop)
if is_iter(prop):
out = ", ".join(str(pr) for pr in prop)
if not out and required:
out = "|rrequired"
return " ({}|n)".format(crop(out, _MENU_CROP_WIDTH))
def _set_property(caller, raw_string, **kwargs):
"""
Update a property. To be called by the 'goto' option variable.
Args:
caller (Object, Account): The user of the wizard.
raw_string (str): Input from user on given node - the new value to set.
Kwargs:
prop (str): Property name to edit with `raw_string`.
processor (callable): Converts `raw_string` to a form suitable for saving.
next_node (str): Where to redirect to after this has run.
Returns:
next_node (str): Next node to go to.
"""
prop = kwargs.get("prop", "meta_key")
processor = kwargs.get("processor", None)
next_node = kwargs.get("next_node", "node_index")
propname_low = prop.strip().lower()
meta = propname_low.startswith("meta_")
if meta:
propname_low = propname_low[5:]
raw_string = raw_string.strip()
if callable(processor):
try:
value = processor(raw_string)
except Exception as err:
caller.msg("Could not set {prop} to {value} ({err})".format(
prop=prop.replace("_", "-").capitalize(), value=raw_string, err=str(err)))
# this means we'll re-run the current node.
return None
else:
value = raw_string
if meta:
_set_menu_metaprot(caller, propname_low, value)
else:
metaprot = _get_menu_metaprot(caller)
prototype = metaprot.prototype
prototype[propname_low] = value
_set_menu_metaprot(caller, "prototype", prototype)
caller.msg("Set {prop} to {value}.".format(
prop=prop.replace("_", "-").capitalize(), value=str(value)))
return next_node
def _wizard_options(prev_node, next_node):
options = [{"desc": "forward ({})".format(next_node.replace("_", "-")),
"goto": "node_{}".format(next_node)},
{"desc": "back ({})".format(prev_node.replace("_", "-")),
"goto": "node_{}".format(prev_node)}]
if "index" not in (prev_node, next_node):
options.append({"desc": "index",
"goto": "node_index"})
return options
def node_index(caller):
metaprot = _get_menu_metaprot(caller)
prototype = metaprot.prototype
text = ("|c --- Prototype wizard --- |n\n\n"
"Define properties of the prototype. All prototype values can be over-ridden at "
"the time of spawning an instance of the prototype, but some are required.\n\n"
"'Meta'-properties are not used in the prototype itself but are used to organize and "
"list prototypes. The 'Meta-Key' uniquely identifies the prototype and allows you to "
"edit an existing prototype or save a new one for use by you or others later.\n\n"
"(make choice; q to abort. If unsure, start from 1.)")
options = []
# The meta-key goes first
options.append(
{"desc": "|WMeta-Key|n|n{}".format(_format_property("Key", True, metaprot, None)),
"goto": "node_meta_key"})
for key in ('Prototype', 'Typeclass', 'Key', 'Aliases', 'Home', 'Destination',
'Permissions', 'Locks', 'Location', 'Tags', 'Attrs'):
req = False
if key in ("Prototype", "Typeclass"):
req = "prototype" not in prototype and "typeclass" not in prototype
options.append(
{"desc": "|w{}|n{}".format(key, _format_property(key, req, None, prototype)),
"goto": "node_{}".format(key.lower())})
for key in ('Desc', 'Tags', 'Locks'):
options.append(
{"desc": "|WMeta-{}|n|n{}".format(key, _format_property(key, req, metaprot, None)),
"goto": "node_meta_{}".format(key.lower())})
text = ("|c --- Prototype wizard --- |n\n"
"(make choice; q to abort, h for help)")
options = (
{"desc": "Key ({})".format(key), "goto": "node_meta_key"},
{"desc": "Description ({})".format(desc), "goto": "node_meta_desc"},
{"desc": "Tags ({})".format(tags), "goto": "node_meta_tags"},
{"desc": "Locks ({})".format(locks), "goto": "node_meta_locks"},
{"desc": "Prototype[menu] ({})".format(prot), "goto": "node_prototype_index"})
return text, options return text, options
def _node_check_key(caller, key): def _check_meta_key(caller, key):
old_metaprot = search_prototype(key) old_metaprot = search_prototype(key)
olc_new = caller.ndb._menutree.olc_new olc_new = caller.ndb._menutree.olc_new
key = key.strip().lower() key = key.strip().lower()
@ -719,15 +814,12 @@ def _node_check_key(caller, key):
caller.msg("Prototype already exists. Reloading.") caller.msg("Prototype already exists. Reloading.")
return "node_meta_index" return "node_meta_index"
# continue on return _set_property(caller, key, prop='meta_key', next_node="node_meta_desc")
_set_menu_metaprot(caller, 'key', key)
caller.msg("Key '{key}' was set.".format(key=key))
return "node_meta_desc"
def node_meta_key(caller): def node_meta_key(caller):
metaprot = _get_menu_metaprot(caller) metaprot = _get_menu_metaprot(caller)
text = ["The prototype name, or |ckey|n, uniquely identifies the prototype. " text = ["The prototype name, or |cmeta-key|n, uniquely identifies the prototype. "
"It is used to find and use the prototype to spawn new entities. " "It is used to find and use the prototype to spawn new entities. "
"It is not case sensitive."] "It is not case sensitive."]
old_key = metaprot.key old_key = metaprot.key
@ -735,25 +827,14 @@ def node_meta_key(caller):
text.append("Current key is '|y{key}|n'".format(key=old_key)) text.append("Current key is '|y{key}|n'".format(key=old_key))
else: else:
text.append("The key is currently unset.") text.append("The key is currently unset.")
text.append("Enter text or make a choice (q for quit, h for help)") text.append("Enter text or make a choice (q for quit)")
text = "\n".join(text) text = "\n\n".join(text)
options = ({"desc": "forward (desc)", options = _wizard_options("index", "meta_desc")
"goto": "node_meta_desc"}, options.append({"key": "_default",
{"desc": "back (index)", "goto": _check_meta_key})
"goto": "node_meta_index"},
{"key": "_default",
"desc": "enter a key",
"goto": _node_check_key})
return text, options return text, options
def _node_check_desc(caller, desc):
desc = desc.strip()
_set_menu_metaprot(caller, 'desc', desc)
caller.msg("Description was set to '{desc}'.".format(desc=desc))
return "node_meta_tags"
def node_meta_desc(caller): def node_meta_desc(caller):
metaprot = _get_menu_metaprot(caller) metaprot = _get_menu_metaprot(caller)
@ -764,25 +845,14 @@ def node_meta_desc(caller):
text.append("The current meta desc is:\n\"|y{desc}|n\"".format(desc=desc)) text.append("The current meta desc is:\n\"|y{desc}|n\"".format(desc=desc))
else: else:
text.append("Description is currently unset.") text.append("Description is currently unset.")
text = "\n".join(text) text = "\n\n".join(text)
options = ({"desc": "forward (tags)", options = _wizard_options("meta_key", "meta_tags")
"goto": "node_meta_tags"}, options.append({"key": "_default",
{"desc": "back (key)", "goto": (_set_property,
"goto": "node_meta_key"}, dict(prop='meta_desc', next_node="node_meta_tags"))})
{"key": "_default",
"desc": "enter a description",
"goto": _node_check_desc})
return text, options return text, options
def _node_check_tags(caller, tags):
tags = [part.strip().lower() for part in tags.split(",")]
_set_menu_metaprot(caller, 'tags', tags)
caller.msg("Tags {tags} were set".format(tags=tags))
return "node_meta_locks"
def node_meta_tags(caller): def node_meta_tags(caller):
metaprot = _get_menu_metaprot(caller) metaprot = _get_menu_metaprot(caller)
text = ["|cTags|n can be used to classify and find prototypes. Tags are case-insensitive. " text = ["|cTags|n can be used to classify and find prototypes. Tags are case-insensitive. "
@ -793,24 +863,16 @@ def node_meta_tags(caller):
text.append("The current tags are:\n|y{tags}|n".format(tags=tags)) text.append("The current tags are:\n|y{tags}|n".format(tags=tags))
else: else:
text.append("No tags are currently set.") text.append("No tags are currently set.")
text = "\n".join(text) text = "\n\n".join(text)
options = ({"desc": "forward (locks)", options = _wizard_options("meta_desc", "meta_locks")
"goto": "node_meta_locks"}, options.append({"key": "_default",
{"desc": "back (desc)", "goto": (_set_property,
"goto": "node_meta_desc"}, dict(prop="meta_tags",
{"key": "_default", processor=lambda s: [str(part.strip()) for part in s.split(",")],
"desc": "enter tags separated by commas", next_node="node_meta_locks"))})
"goto": _node_check_tags})
return text, options return text, options
def _node_check_locks(caller, lockstring):
# TODO - have a way to validate lock string here
_set_menu_metaprot(caller, 'locks', lockstring)
caller.msg("Set lockstring '{lockstring}'.".format(lockstring=lockstring))
return "node_prototype_index"
def node_meta_locks(caller): def node_meta_locks(caller):
metaprot = _get_menu_metaprot(caller) metaprot = _get_menu_metaprot(caller)
text = ["Set |ylocks|n on the prototype. There are two valid lock types: " text = ["Set |ylocks|n on the prototype. There are two valid lock types: "
@ -822,24 +884,30 @@ def node_meta_locks(caller):
else: else:
text.append("Lock unset - if not changed the default lockstring will be set as\n" text.append("Lock unset - if not changed the default lockstring will be set as\n"
" |y'use:all(); edit:id({dbref}) or perm(Admin)'|n".format(dbref=caller.id)) " |y'use:all(); edit:id({dbref}) or perm(Admin)'|n".format(dbref=caller.id))
text = "\n".join(text) text = "\n\n".join(text)
options = ({"desc": "forward (prototype)", options = _wizard_options("meta_tags", "prototype")
"goto": "node_prototype_index"}, options.append({"key": "_default",
{"desc": "back (tags)", "desc": "enter lockstring",
"goto": "node_meta_tags"}, "goto": (_set_property,
{"key": "_default", dict(prop="meta_locks",
"desc": "enter lockstring", next_node="node_key"))})
"goto": _node_check_locks})
return text, options return text, options
def node_prototype_index(caller): def node_key(caller):
metaprot = _get_menu_metaprot(caller) metaprot = _get_menu_metaprot(caller)
text = [" |c--- Prototype menu --- |n" prot = metaprot.prototype
] key = prot.get("key")
pass text = ["Set the prototype's |ykey|n."]
if key:
text.append("Current key value is '|y{}|n'.")
else:
text.append("Key is currently unset.")
text = "\n\n".join(text)
options = _wizard_options("meta_locks",
return "\n".join(text), options
def start_olc(caller, session=None, metaproto=None): def start_olc(caller, session=None, metaproto=None):
@ -853,14 +921,13 @@ def start_olc(caller, session=None, metaproto=None):
prototype rather than creating a new one. prototype rather than creating a new one.
""" """
menudata = {"node_index": node_index,
menudata = {"node_meta_index": node_meta_index,
"node_meta_key": node_meta_key, "node_meta_key": node_meta_key,
"node_meta_desc": node_meta_desc, "node_meta_desc": node_meta_desc,
"node_meta_tags": node_meta_tags, "node_meta_tags": node_meta_tags,
"node_meta_locks": node_meta_locks, "node_meta_locks": node_meta_locks,
"node_prototype_index": node_prototype_index} "node_key": node_key}
EvMenu(caller, menudata, startnode='node_meta_index', session=session, olc_metaproto=metaproto) EvMenu(caller, menudata, startnode='node_index', session=session, olc_metaproto=metaproto)
# Testing # Testing