Extended evmenu with optional callbacks for node formatting. Replaces and Resolves #826.
This commit is contained in:
parent
609b1777c7
commit
36337f9853
1 changed files with 93 additions and 56 deletions
|
|
@ -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,74 +383,82 @@ class EvMenu(object):
|
||||||
# handle the node text
|
# handle the node text
|
||||||
#
|
#
|
||||||
|
|
||||||
nodetext = dedent(nodetext).strip()
|
if self._nodetext_formatter:
|
||||||
|
# use custom formatter
|
||||||
nodetext_width_max = max(m_len(line) for line in nodetext.split("\n"))
|
nodetext = self._nodetext_formatter(nodetext, len(optionlist))
|
||||||
|
else:
|
||||||
if not optionlist:
|
nodetext = dedent(nodetext).strip()
|
||||||
# return the node text "naked".
|
nodetext_width_max = max(m_len(line) for line in nodetext.split("\n"))
|
||||||
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
|
||||||
#
|
#
|
||||||
|
|
||||||
# column separation distance
|
if self._options_formatter:
|
||||||
colsep = 4
|
# use custom formatter
|
||||||
|
optionstext = self._options_formatter(optionlist)
|
||||||
|
else:
|
||||||
|
# column separation distance
|
||||||
|
colsep = 4
|
||||||
|
|
||||||
nlist = len(optionlist)
|
nlist = len(optionlist)
|
||||||
|
|
||||||
# get the widest option line in the table.
|
# get the widest option line in the table.
|
||||||
table_width_max = -1
|
table_width_max = -1
|
||||||
table = []
|
table = []
|
||||||
for key, desc in optionlist:
|
for key, desc in optionlist:
|
||||||
table_width_max = max(table_width_max,
|
table_width_max = max(table_width_max,
|
||||||
max(m_len(p) for p in key.split("\n")) +
|
max(m_len(p) for p in key.split("\n")) +
|
||||||
max(m_len(p) for p in desc.split("\n")) + colsep)
|
max(m_len(p) for p in desc.split("\n")) + colsep)
|
||||||
raw_key = strip_ansi(key)
|
raw_key = strip_ansi(key)
|
||||||
if raw_key != key:
|
if raw_key != key:
|
||||||
# already decorations in key definition
|
# already decorations in key definition
|
||||||
table.append(ANSIString(" {lc%s{lt%s{le: %s" % (raw_key, key, desc)))
|
table.append(ANSIString(" {lc%s{lt%s{le: %s" % (raw_key, key, desc)))
|
||||||
else:
|
else:
|
||||||
# add a default white color to key
|
# add a default white color to key
|
||||||
table.append(ANSIString(" {lc%s{lt{w%s{n{le: %s" % (raw_key, raw_key, desc)))
|
table.append(ANSIString(" {lc%s{lt{w%s{n{le: %s" % (raw_key, raw_key, desc)))
|
||||||
|
|
||||||
ncols = (_MAX_TEXT_WIDTH // table_width_max) + 1 # number of ncols
|
ncols = (_MAX_TEXT_WIDTH // table_width_max) + 1 # number of ncols
|
||||||
nlastcol = nlist % ncols # number of elements left in last row
|
nlastcol = nlist % ncols # number of elements left in last row
|
||||||
|
|
||||||
# get the amount of rows needed (start with 4 rows)
|
# get the amount of rows needed (start with 4 rows)
|
||||||
nrows = 4
|
nrows = 4
|
||||||
while nrows * ncols < nlist:
|
while nrows * ncols < nlist:
|
||||||
nrows += 1
|
nrows += 1
|
||||||
ncols = nlist // nrows # number of full columns
|
ncols = nlist // nrows # number of full columns
|
||||||
nlastcol = nlist % nrows # number of elements in last column
|
nlastcol = nlist % nrows # number of elements in last column
|
||||||
|
|
||||||
# get the final column count
|
# get the final column count
|
||||||
ncols = ncols + 1 if nlastcol > 0 else ncols
|
ncols = ncols + 1 if nlastcol > 0 else ncols
|
||||||
if ncols > 1:
|
if ncols > 1:
|
||||||
# only extend if longer than one column
|
# only extend if longer than one column
|
||||||
table.extend([" " for i in xrange(nrows-nlastcol)])
|
table.extend([" " for i in xrange(nrows-nlastcol)])
|
||||||
|
|
||||||
# build the actual table grid
|
# build the actual table grid
|
||||||
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"))
|
||||||
|
|
||||||
# build the page
|
#
|
||||||
total_width = max(total_width, nodetext_width_max)
|
# format the entire node
|
||||||
separator1 = "_" * total_width + "\n\n" if nodetext_width_max else ""
|
#
|
||||||
separator2 = "\n" + "_" * total_width + "\n\n" if total_width else ""
|
if self._node_formatter:
|
||||||
return separator1 + nodetext + separator2 + unicode(table)
|
# use custom formatter
|
||||||
|
return self._node_formatter(nodetext, optionstext)
|
||||||
|
else:
|
||||||
|
# build the page
|
||||||
|
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 + optionstext
|
||||||
|
|
||||||
def _execute_node(self, nodename, raw_string):
|
def _execute_node(self, nodename, raw_string):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue