Extended evmenu with optional callbacks for node formatting. Replaces and Resolves #826.

This commit is contained in:
Griatch 2015-10-18 11:06:19 +02:00
parent 609b1777c7
commit 36337f9853

View file

@ -234,7 +234,9 @@ class EvMenu(object):
"""
def __init__(self, caller, menudata, startnode="start",
cmdset_mergetype="Replace", cmdset_priority=1,
allow_quit=True, cmd_on_quit="look"):
allow_quit=True, cmd_on_quit="look",
nodetext_formatter=None, options_formatter=None,
node_formatter=None):
"""
Initialize the menu tree and start the caller onto the first node.
@ -264,13 +266,35 @@ class EvMenu(object):
allow_quit (bool, optional): Allow user to use quit or
exit to leave the menu at any point. Recommended during
development!
cmd_on_quit (callback, str or None, optional): When exiting the menu
cmd_on_quit (callable, str or None, optional): When exiting the menu
(either by reaching a node with no options or by using the
in-built quit command (activated with `allow_quit`), this
callback function or command string will be executed.
The callback function takes two parameters, the caller then the
EvMenu object. This is called after cleanup is complete.
Set to None to not call any command.
nodetext_formatter (callable, optional): This callable should be on
the form `function(nodetext, has_options)`, where `nodetext` is the
node text string and `has_options` a boolean specifying if there
are options associated with this node. It must return a formatted
string.
options_formatter (callable, optional): This callable should be on
the form `function(optionlist)`, where ` optionlist is a list
of option dictionaries, like
[{"key":..., "desc",..., "goto": ..., "exec",...}, ...]
Each dictionary describes each possible option. Note that this
will also be called if there are no options, and so should be
able to handle an empty list. This should
be formatted into an options list and returned as a string,
including the required separator to use between the node text
and the options. If not given the default EvMenu style will be used.
node_formatter (callable, optional): This callable should be on the
form `func(nodetext, optionstext)` where the arguments are strings
representing the node text and options respectively (possibly prepared
by `nodetext_formatter`/`options_formatter` or by the default styles).
It should return a string representing the final look of the node. This
can e.g. be used to create line separators that take into account the
dynamic width of the parts.
Raises:
EvMenuError: If the start/end node is not found in menu tree.
@ -280,6 +304,10 @@ class EvMenu(object):
self._startnode = startnode
self._menutree = self._parse_menudata(menudata)
self._nodetext_formatter = nodetext_formatter
self._options_formatter = nodetext_formatter
self._node_formatter = node_formatter
if startnode not in self._menutree:
raise EvMenuError("Start node '%s' not in menu tree!" % startnode)
@ -296,6 +324,7 @@ class EvMenu(object):
self.helptext = None
self.options = None
# store ourself on the object
self._caller.ndb._menutree = self
@ -354,20 +383,21 @@ class EvMenu(object):
# handle the node text
#
if self._nodetext_formatter:
# use custom formatter
nodetext = self._nodetext_formatter(nodetext, len(optionlist))
else:
nodetext = dedent(nodetext).strip()
nodetext_width_max = max(m_len(line) for line in nodetext.split("\n"))
if not optionlist:
# return the node text "naked".
separator1 = "_" * nodetext_width_max + "\n\n" if nodetext_width_max else ""
separator2 = "\n" if nodetext_width_max else "" + "_" * nodetext_width_max
return separator1 + nodetext + separator2
#
# handle the options
#
if self._options_formatter:
# use custom formatter
optionstext = self._options_formatter(optionlist)
else:
# column separation distance
colsep = 4
@ -408,20 +438,27 @@ class EvMenu(object):
table = [table[icol*nrows:(icol*nrows) + nrows] for icol in xrange(0, ncols)]
# adjust the width of each column
total_width = 0
options_total_width = 0
for icol in xrange(len(table)):
col_width = max(max(m_len(p) for p in part.split("\n")) for part in table[icol]) + colsep
table[icol] = [pad(part, width=col_width + colsep, align="l") for part in table[icol]]
total_width += col_width
options_total_width += col_width
# format the table into columns
table = EvTable(table=table, border="none")
optionstext = unicode(EvTable(table=table, border="none"))
#
# format the entire node
#
if self._node_formatter:
# use custom formatter
return self._node_formatter(nodetext, optionstext)
else:
# build the page
total_width = max(total_width, nodetext_width_max)
total_width = max(options_total_width, nodetext_width_max)
separator1 = "_" * total_width + "\n\n" if nodetext_width_max else ""
separator2 = "\n" + "_" * total_width + "\n\n" if total_width else ""
return separator1 + nodetext + separator2 + unicode(table)
return separator1 + nodetext + separator2 + optionstext
def _execute_node(self, nodename, raw_string):
"""