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", def __init__(self, caller, menudata, startnode="start",
cmdset_mergetype="Replace", cmdset_priority=1, 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. 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 allow_quit (bool, optional): Allow user to use quit or
exit to leave the menu at any point. Recommended during exit to leave the menu at any point. Recommended during
development! 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 (either by reaching a node with no options or by using the
in-built quit command (activated with `allow_quit`), this in-built quit command (activated with `allow_quit`), this
callback function or command string will be executed. callback function or command string will be executed.
The callback function takes two parameters, the caller then the The callback function takes two parameters, the caller then the
EvMenu object. This is called after cleanup is complete. EvMenu object. This is called after cleanup is complete.
Set to None to not call any command. 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: Raises:
EvMenuError: If the start/end node is not found in menu tree. EvMenuError: If the start/end node is not found in menu tree.
@ -280,6 +304,10 @@ class EvMenu(object):
self._startnode = startnode self._startnode = startnode
self._menutree = self._parse_menudata(menudata) 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: if startnode not in self._menutree:
raise EvMenuError("Start node '%s' not in menu tree!" % startnode) raise EvMenuError("Start node '%s' not in menu tree!" % startnode)
@ -296,6 +324,7 @@ class EvMenu(object):
self.helptext = None self.helptext = None
self.options = None self.options = None
# store ourself on the object # store ourself on the object
self._caller.ndb._menutree = self self._caller.ndb._menutree = self
@ -354,20 +383,21 @@ class EvMenu(object):
# handle the node text # handle the node text
# #
if self._nodetext_formatter:
# use custom formatter
nodetext = self._nodetext_formatter(nodetext, len(optionlist))
else:
nodetext = dedent(nodetext).strip() nodetext = dedent(nodetext).strip()
nodetext_width_max = max(m_len(line) for line in nodetext.split("\n")) 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 # handle the options
# #
if self._options_formatter:
# use custom formatter
optionstext = self._options_formatter(optionlist)
else:
# column separation distance # column separation distance
colsep = 4 colsep = 4
@ -408,20 +438,27 @@ class EvMenu(object):
table = [table[icol*nrows:(icol*nrows) + nrows] for icol in xrange(0, ncols)] table = [table[icol*nrows:(icol*nrows) + nrows] for icol in xrange(0, ncols)]
# adjust the width of each column # adjust the width of each column
total_width = 0 options_total_width = 0
for icol in xrange(len(table)): 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 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]] 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 # 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 # 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 "" separator1 = "_" * total_width + "\n\n" if nodetext_width_max else ""
separator2 = "\n" + "_" * total_width + "\n\n" if total_width 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): def _execute_node(self, nodename, raw_string):
""" """