Cleanup/refactoring of olc menus

This commit is contained in:
Griatch 2018-08-11 11:49:10 +02:00
parent e49993fbb5
commit 298b2c23c6
6 changed files with 216 additions and 92 deletions

View file

@ -214,6 +214,10 @@ def _wizard_options(curr_node, prev_node, next_node, color="|W", search=False):
return options
def _set_actioninfo(caller, string):
caller.ndb._menutree.actioninfo = string
def _path_cropper(pythonpath):
"Crop path to only the last component"
return pythonpath.split('.')[-1]
@ -278,30 +282,65 @@ def _format_list_actions(*args, **kwargs):
prefix = kwargs.get('prefix', "|WSelect with |w<num>|W. Other actions:|n ")
for action in args:
actions.append("|w{}|n|W{} |w<num>|n".format(action[0], action[1:]))
return prefix + "|W,|n ".join(actions)
return prefix + " |W|||n ".join(actions)
def _get_current_value(caller, keyname, formatter=str, only_inherit=False):
"Return current value, marking if value comes from parent or set in this prototype"
prot = _get_menu_prototype(caller)
if keyname in prot:
# value in current prot
def _get_current_value(caller, keyname, comparer=None, formatter=str, only_inherit=False):
"""
Return current value, marking if value comes from parent or set in this prototype.
Args:
keyname (str): Name of prototoype key to get current value of.
comparer (callable, optional): This will be called as comparer(prototype_value,
flattened_value) and is expected to return the value to show as the current
or inherited one. If not given, a straight comparison is used and what is returned
depends on the only_inherit setting.
formatter (callable, optional)): This will be called with the result of comparer.
only_inherit (bool, optional): If a current value should only be shown if all
the values are inherited from the prototype parent (otherwise, show an empty string).
Returns:
current (str): The current value.
"""
def _default_comparer(protval, flatval):
if only_inherit:
return ''
return "Current {}: {}".format(keyname, formatter(prot[keyname]))
flat_prot = _get_flat_menu_prototype(caller)
if keyname in flat_prot:
# value in flattened prot
if keyname == 'prototype_key':
# we don't inherit prototype_keys
return "[No prototype_key set] (|rnot inherited|n)"
return "" if protval else flatval
else:
ret = "Current {} (|binherited|n): {}".format(keyname, formatter(flat_prot[keyname]))
if only_inherit:
return "{}\n\n".format(ret)
return ret
return protval if protval else flatval
return "[No {} set]".format(keyname)
if not callable(comparer):
comparer = _default_comparer
prot = _get_menu_prototype(caller)
flat_prot = _get_flat_menu_prototype(caller)
out = ""
if keyname in prot:
if keyname in flat_prot:
out = formatter(comparer(prot[keyname], flat_prot[keyname]))
if only_inherit:
if out:
return "|WCurrent|n {} |W(|binherited|W):|n {}".format(keyname, out)
return ""
else:
if out:
return "|WCurrent|n {}|W:|n {}".format(keyname, out)
return "|W[No {} set]|n".format(keyname)
elif only_inherit:
return ""
else:
out = formatter(prot[keyname])
return "|WCurrent|n {}|W:|n {}".format(keyname, out)
elif keyname in flat_prot:
out = formatter(flat_prot[keyname])
if out:
return "|WCurrent|n {} |W(|n|binherited|W):|n {}".format(keyname, out)
else:
return ""
elif only_inherit:
return ""
else:
return "|W[No {} set]|n".format(keyname)
def _default_parse(raw_inp, choices, *args):
@ -491,10 +530,9 @@ def node_search_object(caller, raw_inp, **kwargs):
text = """
Found {num} match{post}.
{actions}
(|RWarning: creating a prototype will |roverwrite|r |Rthe current prototype!)|n""".format(
num=nmatches, post="es" if nmatches > 1 else "",
actions=_format_list_actions(
num=nmatches, post="es" if nmatches > 1 else "")
_set_actioninfo(caller, _format_list_actions(
"examine", "create prototype from object", prefix="Actions: "))
else:
text = "Enter search criterion."
@ -758,8 +796,6 @@ def node_prototype_parent(caller):
parent is given, this prototype must define the typeclass (next menu node).
{current}
{actions}
"""
helptext = """
Prototypes can inherit from one another. Changes in the child replace any values set in a
@ -767,6 +803,8 @@ def node_prototype_parent(caller):
prototype to be valid.
"""
_set_actioninfo(caller, _format_list_actions("examine", "add", "remove"))
ptexts = []
if prot_parent_keys:
for pkey in utils.make_iter(prot_parent_keys):
@ -782,8 +820,7 @@ def node_prototype_parent(caller):
if not ptexts:
ptexts.append("[No prototype_parent set]")
text = text.format(current="\n\n".join(ptexts),
actions=_format_list_actions("examine", "add", "remove"))
text = text.format(current="\n\n".join(ptexts))
text = (text, helptext)
@ -854,8 +891,6 @@ def node_typeclass(caller):
one of the prototype's |cparents|n.
{current}
{actions}
""".format(current=_get_current_value(caller, "typeclass"),
actions="|WSelect with |w<num>|W. Other actions: "
"|we|Wxamine |w<num>|W, |wr|Wemove selection")
@ -962,11 +997,15 @@ def node_aliases(caller):
|cAliases|n are alternative ways to address an object, next to its |cKey|n. Aliases are not
case sensitive.
{actions}
{current}
""".format(actions=_format_list_actions("remove",
prefix="|w<text>|W to add new alias. Other action: "),
current)
""".format(current=_get_current_value(
caller, 'aliases',
comparer=lambda propval, flatval: [al for al in flatval if al not in propval],
formatter=lambda lst: "\n" + ", ".join(lst), only_inherit=True))
_set_actioninfo(caller,
_format_list_actions(
"remove",
prefix="|w<text>|W to add new alias. Other action: "))
helptext = """
Aliases are fixed alternative identifiers and are stored with the new object.
@ -1009,14 +1048,13 @@ def _display_attribute(attr_tuple):
attrkey, value, category, locks = attr_tuple
value = protlib.protfunc_parser(value)
typ = type(value)
out = ("|cAttribute key:|n '{attrkey}' "
"(|ccategory:|n {category}, "
"|clocks:|n {locks})\n"
"|cValue|n |W(parsed to {typ})|n:\n{value}").format(
attrkey=attrkey,
category=category if category else "|wNone|n",
locks=locks if locks else "|wNone|n",
typ=typ, value=value)
out = ("{attrkey} |c=|n {value} |W({typ}{category}{locks})|n".format(
attrkey=attrkey,
value=value,
typ=typ,
category=", category={}".format(category) if category else '',
locks=", locks={}".format(";".join(locks)) if any(locks) else ''))
return out
@ -1130,6 +1168,12 @@ def _attrs_actions(caller, raw_inp, **kwargs):
@list_node(_caller_attrs, _attr_select)
def node_attrs(caller):
def _currentcmp(propval, flatval):
"match by key + category"
cmp1 = [(tup[0].lower(), tup[2].lower() if tup[2] else None) for tup in propval]
return [tup for tup in flatval if (tup[0].lower(), tup[2].lower()
if tup[2] else None) not in cmp1]
text = """
|cAttributes|n are custom properties of the object. Enter attributes on one of these forms:
@ -1140,8 +1184,14 @@ def node_attrs(caller):
To give an attribute without a category but with a lockstring, leave that spot empty
(attrname;;lockstring=value). Attribute values can have embedded $protfuncs.
{actions}
""".format(actions=_format_list_actions("examine", "remove", prefix="Actions: "))
{current}
""".format(
current=_get_current_value(
caller, "attrs",
comparer=_currentcmp,
formatter=lambda lst: "\n" + "\n".join(_display_attribute(tup) for tup in lst),
only_inherit=True))
_set_actioninfo(caller, _format_list_actions("examine", "remove", prefix="Actions: "))
helptext = """
Most commonly, Attributes don't need any categories or locks. If using locks, the lock-types
@ -1290,6 +1340,13 @@ def _tags_actions(caller, raw_inp, **kwargs):
@list_node(_caller_tags, _tag_select)
def node_tags(caller):
def _currentcmp(propval, flatval):
"match by key + category"
cmp1 = [(tup[0].lower(), tup[1].lower() if tup[2] else None) for tup in propval]
return [tup for tup in flatval if (tup[0].lower(), tup[1].lower()
if tup[1] else None) not in cmp1]
text = """
|cTags|n are used to group objects so they can quickly be found later. Enter tags on one of
the following forms:
@ -1297,8 +1354,14 @@ def node_tags(caller):
tagname;category
tagname;category;data
{actions}
""".format(actions=_format_list_actions("examine", "remove", prefix="Actions: "))
{current}
""".format(
current=_get_current_value(
caller, 'tags',
comparer=_currentcmp,
formatter=lambda lst: "\n" + "\n".join(_display_tag(tup) for tup in lst),
only_inherit=True))
_set_actioninfo(caller, _format_list_actions("examine", "remove", prefix="Actions: "))
helptext = """
Tags are shared between all objects with that tag. So the 'data' field (which is not
@ -1325,18 +1388,7 @@ def _caller_locks(caller):
def _locks_display(caller, lock):
try:
locktype, lockdef = lock.split(":", 1)
except ValueError:
txt = "Malformed lock string - Missing ':'"
else:
txt = ("{lockstr}\n\n"
"|WLocktype: |w{locktype}|n\n"
"|WLock def: |w{lockdef}|n\n").format(
lockstr=lock,
locktype=locktype,
lockdef=lockdef)
return txt
return lock
def _lock_select(caller, lockstr):
@ -1395,6 +1447,11 @@ def _locks_actions(caller, raw_inp, **kwargs):
@list_node(_caller_locks, _lock_select)
def node_locks(caller):
def _currentcmp(propval, flatval):
"match by locktype"
cmp1 = [lck.split(":", 1)[0] for lck in propval.split(';')]
return ";".join(lstr for lstr in flatval.split(';') if lstr.split(':', 1)[0] not in cmp1)
text = """
The |cLock string|n defines limitations for accessing various properties of the object once
it's spawned. The string should be on one of the following forms:
@ -1402,8 +1459,15 @@ def node_locks(caller):
locktype:[NOT] lockfunc(args)
locktype: [NOT] lockfunc(args) [AND|OR|NOT] lockfunc(args) [AND|OR|NOT] ...
{action}
""".format(action=_format_list_actions("examine", "remove", prefix="Actions: "))
{current}{action}
""".format(
current=_get_current_value(
caller, 'locks',
comparer=_currentcmp,
formatter=lambda lockstr: "\n".join(_locks_display(caller, lstr)
for lstr in lockstr.split(';')),
only_inherit=True),
action=_format_list_actions("examine", "remove", prefix="Actions: "))
helptext = """
Here is an example of two lock strings:
@ -1438,16 +1502,17 @@ def _caller_permissions(caller):
return perms
def _display_perm(caller, permission):
def _display_perm(caller, permission, only_hierarchy=False):
hierarchy = settings.PERMISSION_HIERARCHY
perm_low = permission.lower()
txt = ''
if perm_low in [prm.lower() for prm in hierarchy]:
txt = "Permission (in hieararchy): {}".format(
", ".join(
["|w[{}]|n".format(prm)
if prm.lower() == perm_low else "|W{}|n".format(prm)
for prm in hierarchy]))
else:
elif not only_hierarchy:
txt = "Permission: '{}'".format(permission)
return txt
@ -1500,12 +1565,23 @@ def _permissions_actions(caller, raw_inp, **kwargs):
@list_node(_caller_permissions, _permission_select)
def node_permissions(caller):
def _currentcmp(pval, fval):
cmp1 = [perm.lower() for perm in pval]
return [perm for perm in fval if perm.lower() not in cmp1]
text = """
|cPermissions|n are simple strings used to grant access to this object. A permission is used
when a |clock|n is checked that contains the |wperm|n or |wpperm|n lock functions.
when a |clock|n is checked that contains the |wperm|n or |wpperm|n lock functions. Certain
permissions belong in the |cpermission hierarchy|n together with the |Wperm()|n lock
function.
{actions}
""".format(actions=_format_list_actions("examine", "remove"), prefix="Actions: ")
{current}
""".format(
current=_get_current_value(
caller, 'permissions',
comparer=_currentcmp,
formatter=lambda lst: "\n" + "\n".join(prm for prm in lst), only_inherit=True))
_set_actioninfo(caller, _format_list_actions("examine", "remove", prefix="Actions: "))
helptext = """
Any string can act as a permission as long as a lock is set to look for it. Depending on the
@ -1538,7 +1614,6 @@ def node_location(caller):
inventory of |c{caller}|n by default.
{current}
""".format(caller=caller.key, current=_get_current_value(caller, "location"))
helptext = """
@ -1734,9 +1809,13 @@ def node_prototype_tags(caller):
|cPrototype-Tags|n can be used to classify and find prototypes in listings Tag names are not
case-sensitive and can have not have a custom category.
{actions}
""".format(actions=_format_list_actions(
"remove", prefix="|w<text>|n|W to add Tag. Other Action:|n "))
{current}
""".format(
current=_get_current_value(
caller, 'prototype_tags',
formatter=lambda lst: ", ".join(tg for tg in lst), only_inherit=True))
_set_actioninfo(caller, _format_list_actions(
"remove", prefix="|w<text>|n|W to add Tag. Other Action:|n "))
helptext = """
Using prototype-tags is a good way to organize and group large numbers of prototypes by
genre, type etc. Under the hood, prototypes' tags will all be stored with the category
@ -1827,8 +1906,14 @@ def node_prototype_locks(caller):
If unsure, keep the open defaults.
{actions}
""".format(actions=_format_list_actions('examine', "remove", prefix="Actions: "))
{current}
""".format(
current=_get_current_value(
caller, 'prototype_locks',
formatter=lambda lstring: "\n".join(_locks_display(caller, lstr)
for lstr in lstring.split(';')),
only_inherit=True))
_set_actioninfo(caller, _format_list_actions('examine', "remove", prefix="Actions: "))
helptext = """
Prototype locks can be used to vary access for different tiers of builders. It also allows
@ -2204,9 +2289,8 @@ def node_prototype_load(caller, **kwargs):
text = """
Select a prototype to load. This will replace any prototype currently being edited!
{actions}
""".format(actions=_format_list_actions("examine", "delete"))
"""
_set_actioninfo(caller, _format_list_actions("examine", "delete"))
helptext = """
Loading a prototype will load it and return you to the main index. It can be a good idea
@ -2230,6 +2314,13 @@ class OLCMenu(EvMenu):
A custom EvMenu with a different formatting for the options.
"""
def nodetext_formatter(self, nodetext):
"""
Format the node text itself.
"""
return super(OLCMenu, self).nodetext_formatter(nodetext)
def options_formatter(self, optionlist):
"""
Split the options into two blocks - olc options and normal options
@ -2237,6 +2328,7 @@ class OLCMenu(EvMenu):
"""
olc_keys = ("index", "forward", "back", "previous", "next", "validate prototype",
"save prototype", "load prototype", "spawn prototype", "search objects")
actioninfo = self.actioninfo + "\n" if hasattr(self, 'actioninfo') else ''
olc_options = []
other_options = []
for key, desc in optionlist:
@ -2247,7 +2339,8 @@ class OLCMenu(EvMenu):
else:
other_options.append((key, desc))
olc_options = " | ".join(olc_options) + " | " + "|wQ|Wuit" if olc_options else ""
olc_options = actioninfo + \
" |W|||n ".join(olc_options) + " |W|||n " + "|wQ|Wuit" if olc_options else ""
other_options = super(OLCMenu, self).options_formatter(other_options)
sep = "\n\n" if olc_options and other_options else ""
@ -2257,10 +2350,10 @@ class OLCMenu(EvMenu):
"""
Show help text
"""
return "|c --- Help ---|n\n" + helptext
return "|c --- Help ---|n\n" + utils.dedent(helptext)
def display_helptext(self):
evmore.msg(self.caller, self.helptext, session=self._session)
evmore.msg(self.caller, self.helptext, session=self._session, exit_cmd='look')
def start_olc(caller, session=None, prototype=None):