Simplify list_node decorator
This commit is contained in:
parent
c340c08adb
commit
a5e90de43e
1 changed files with 102 additions and 275 deletions
|
|
@ -987,137 +987,6 @@ class EvMenu(object):
|
||||||
return separator1 + "|n" + nodetext + "|n" + separator2 + "|n" + optionstext
|
return separator1 + "|n" + nodetext + "|n" + separator2 + "|n" + optionstext
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------
|
|
||||||
#
|
|
||||||
# Edit node (decorator turning a node into an editing
|
|
||||||
# point for a given resource
|
|
||||||
#
|
|
||||||
# -----------------------------------------------------------
|
|
||||||
|
|
||||||
def edit_node(edit_text, add_text, edit_callback, add_callback, get_choices=None):
|
|
||||||
"""
|
|
||||||
Decorator for turning an EvMenu node into an editing
|
|
||||||
page. Will add new options, prepending those options
|
|
||||||
added in the node.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
edit_text (str or callable): Will be used as text for the edit node. If
|
|
||||||
callable, it will be called as edittext(selection)
|
|
||||||
and should return the node text for the edit-node, probably listing
|
|
||||||
the current value of all editable propnames, if possible.
|
|
||||||
add_text (str) or callable: Gives text for node in add-mode. If a callable,
|
|
||||||
called as add_text() and should return the text for the node.
|
|
||||||
edit_callback (callable): Will be called as edit_callback(editable, raw_string)
|
|
||||||
and should return a boolean True/False if the setting of the property
|
|
||||||
succeeded or not. The value will always be a string and should be
|
|
||||||
converted as needed.
|
|
||||||
add_callback (callable): Will be called as add_callback(raw_string) and
|
|
||||||
should return a boolean True/False if the addition succeded.
|
|
||||||
|
|
||||||
get_choices (callable): Produce the available editable choices. If this
|
|
||||||
is not given, the `goto` callable must have been provided with the
|
|
||||||
kwarg `available_choices` by the decorated node.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
def decorator(func):
|
|
||||||
|
|
||||||
def _setter_goto(caller, raw_string, **kwargs):
|
|
||||||
editable = kwargs.get("editable")
|
|
||||||
mode = kwargs.get("edit_node_mode")
|
|
||||||
try:
|
|
||||||
if mode == 'edit':
|
|
||||||
is_ok = edit_callback(editable, raw_string)
|
|
||||||
else:
|
|
||||||
is_ok = add_callback(raw_string)
|
|
||||||
except Exception:
|
|
||||||
logger.log_trace()
|
|
||||||
if not is_ok:
|
|
||||||
caller.msg("|rValue could not be set.")
|
|
||||||
return None
|
|
||||||
|
|
||||||
def _patch_goto(caller, raw_string, **kwargs):
|
|
||||||
|
|
||||||
# parse incoming string to figure out if there is a match to edit/add
|
|
||||||
match = re.search(r"(^[a-zA-Z]*)\s*([0-9]*)$", raw_string)
|
|
||||||
cmd, number = match.groups()
|
|
||||||
edit_mode = None
|
|
||||||
available_choices = None
|
|
||||||
selection = None
|
|
||||||
|
|
||||||
if get_choices:
|
|
||||||
available_choices = make_iter(get_choices(caller, raw_string, **kwargs))
|
|
||||||
if not available_choices:
|
|
||||||
available_choices = kwargs.get("available_choices", [])
|
|
||||||
|
|
||||||
if available_choices and cmd.startswith("e"):
|
|
||||||
try:
|
|
||||||
index = int(cmd) - 1
|
|
||||||
selection = available_choices[index]
|
|
||||||
edit_mode = 'edit'
|
|
||||||
except (IndexError, TypeError):
|
|
||||||
caller.msg("|rNot a valid 'edit' command.")
|
|
||||||
|
|
||||||
if cmd.startswith("a") and not number:
|
|
||||||
# add mode
|
|
||||||
edit_mode = "add"
|
|
||||||
|
|
||||||
if edit_mode:
|
|
||||||
# replace with edit text/options
|
|
||||||
text = edit_text(selection) if edit_mode == "edit" else add_text()
|
|
||||||
options = ({"key": "_default",
|
|
||||||
"goto": (_setter_goto,
|
|
||||||
{"selection": selection,
|
|
||||||
"edit_node_mode": edit_mode})})
|
|
||||||
return text, options
|
|
||||||
|
|
||||||
# no matches - pass through to the original decorated goto instruction
|
|
||||||
|
|
||||||
decorated_opt = kwargs.get("decorated_opt")
|
|
||||||
|
|
||||||
if decorated_opt:
|
|
||||||
# use EvMenu's parser to get the goto/goto-kwargs out of
|
|
||||||
# the decorated option structure
|
|
||||||
dec_goto, dec_goto_kwargs, _, _ = \
|
|
||||||
caller.ndb._menutree.extract_goto_exec("edit-node", decorated_opt)
|
|
||||||
|
|
||||||
if callable(dec_goto):
|
|
||||||
try:
|
|
||||||
return dec_goto(caller, raw_string,
|
|
||||||
**{dec_goto_kwargs if dec_goto_kwargs else {}})
|
|
||||||
except Exception:
|
|
||||||
caller.msg("|rThere was an error in the edit node.")
|
|
||||||
logger.log_trace()
|
|
||||||
return None
|
|
||||||
|
|
||||||
def _edit_node(caller, raw_string, **kwargs):
|
|
||||||
|
|
||||||
text, options = func(caller, raw_string, **kwargs)
|
|
||||||
|
|
||||||
if options:
|
|
||||||
# find eventual _default in options and patch it with a handler for
|
|
||||||
# catching editing
|
|
||||||
|
|
||||||
decorated_opt = None
|
|
||||||
iopt = 0
|
|
||||||
for iopt, optdict in enumerate(options):
|
|
||||||
if optdict.get('key') == "_default":
|
|
||||||
decorated_opt = optdict
|
|
||||||
break
|
|
||||||
|
|
||||||
if decorated_opt:
|
|
||||||
# inject our wrapper over the original goto instruction for the
|
|
||||||
# _default action (save the original)
|
|
||||||
options[iopt]["goto"] = (_patch_goto,
|
|
||||||
{"decorated_opt": decorated_opt})
|
|
||||||
|
|
||||||
return text, options
|
|
||||||
|
|
||||||
return _edit_node
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------
|
# -----------------------------------------------------------
|
||||||
#
|
#
|
||||||
# List node (decorator turning a node into a list with
|
# List node (decorator turning a node into a list with
|
||||||
|
|
@ -1125,7 +994,7 @@ def edit_node(edit_text, add_text, edit_callback, add_callback, get_choices=None
|
||||||
#
|
#
|
||||||
# -----------------------------------------------------------
|
# -----------------------------------------------------------
|
||||||
|
|
||||||
def list_node(option_generator, select=None, examine=None, edit=None, add=None, pagesize=10):
|
def list_node(option_generator, select=None, pagesize=10):
|
||||||
"""
|
"""
|
||||||
Decorator for making an EvMenu node into a multi-page list node. Will add new options,
|
Decorator for making an EvMenu node into a multi-page list node. Will add new options,
|
||||||
prepending those options added in the node.
|
prepending those options added in the node.
|
||||||
|
|
@ -1135,25 +1004,22 @@ def list_node(option_generator, select=None, examine=None, edit=None, add=None,
|
||||||
that is called as option_generator(caller) to produce such a list.
|
that is called as option_generator(caller) to produce such a list.
|
||||||
select (callable, option): Will be called as select(caller, menuchoice)
|
select (callable, option): Will be called as select(caller, menuchoice)
|
||||||
where menuchoice is the chosen option as a string. Should return the target node to
|
where menuchoice is the chosen option as a string. Should return the target node to
|
||||||
goto after this selection. Note that if this is not given, the decorated node must
|
goto after this selection (or None to repeat the list-node). Note that if this is not
|
||||||
itself provide a way to continue from the node!
|
given, the decorated node must itself provide a way to continue from the node!
|
||||||
examine (callable, optional): If given, allows for examining options in detail. Will
|
|
||||||
be called with examine(caller, menuchoice) and should return a text string to
|
|
||||||
display in-place in the node.
|
|
||||||
edit (callable, optional): If given, this callable will be called as edit(caller,
|
|
||||||
menuchoice, **kwargs) and should return a complete (text, options) tuple (like a node).
|
|
||||||
add (callable optional): If given, this callable will be called as add(caller, menuchoice,
|
|
||||||
**kwargs) and should return a complete (text, options) tuple (like a node).
|
|
||||||
|
|
||||||
pagesize (int): How many options to show per page.
|
pagesize (int): How many options to show per page.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
@list_node(['foo', 'bar'], examine, select)
|
@list_node(['foo', 'bar'], select)
|
||||||
def node_index(caller):
|
def node_index(caller):
|
||||||
text = "describing the list"
|
text = "describing the list"
|
||||||
return text, []
|
return text, []
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
All normal `goto` or `exec` callables returned from the decorated nodes will, if they accept
|
||||||
|
**kwargs, get a new kwarg 'available_choices' injected. These are the ordered list of named
|
||||||
|
options (descs) visible on the current node page.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def decorator(func):
|
def decorator(func):
|
||||||
|
|
@ -1177,53 +1043,44 @@ def list_node(option_generator, select=None, examine=None, edit=None, add=None,
|
||||||
logger.log_trace()
|
logger.log_trace()
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _input_parser(caller, raw_string, **kwargs):
|
# def _input_parser(caller, raw_string, **kwargs):
|
||||||
"""
|
# """
|
||||||
Parse which input was given, select from option_list.
|
# Parse which input was given, select from option_list.
|
||||||
|
#
|
||||||
Understood input is [cmd]<num>, where [cmd] is either empty (`select`)
|
#
|
||||||
or one of the supported actions `look`, `edit` or `add` depending on
|
# """
|
||||||
which processors are available.
|
# mode, selection, args = None, None, None
|
||||||
|
# available_choices = kwargs.get("available_choices", [])
|
||||||
"""
|
#
|
||||||
mode, selection, args = None, None, None
|
# cmd, args = re.search(r"(^[a-zA-Z]*)\s*(.*?)$", raw_string).groups()
|
||||||
available_choices = kwargs.get("available_choices", [])
|
#
|
||||||
|
# cmd = cmd.lower().strip()
|
||||||
cmd, args = re.search(r"(^[a-zA-Z]*)\s*(.*?)$", raw_string).groups()
|
# if cmd.startswith('a') and add:
|
||||||
|
# mode = "add"
|
||||||
cmd = cmd.lower().strip()
|
# else:
|
||||||
if cmd.startswith('a') and add:
|
# selection, args = re.search(r"(^[0-9]*)\s*(.*?)$", args).groups()
|
||||||
mode = "add"
|
# try:
|
||||||
else:
|
# selection = int(selection) - 1
|
||||||
selection, args = re.search(r"(^[0-9]*)\s*(.*?)$", args).groups()
|
# except ValueError:
|
||||||
try:
|
# mode = "look"
|
||||||
selection = int(selection) - 1
|
# else:
|
||||||
except ValueError:
|
# # edits are on the form 'edit <num> <args>
|
||||||
mode = "look"
|
# if cmd.startswith("e") and edit:
|
||||||
else:
|
# mode = "edit"
|
||||||
# edits are on the form 'edit <num> <args>
|
# elif examine:
|
||||||
if cmd.startswith("e") and edit:
|
# mode = "look"
|
||||||
mode = "edit"
|
# try:
|
||||||
elif examine:
|
# selection = available_choices[selection]
|
||||||
mode = "look"
|
# except IndexError:
|
||||||
try:
|
# caller.msg("|rInvalid index|n")
|
||||||
selection = available_choices[selection]
|
# mode = None
|
||||||
except IndexError:
|
#
|
||||||
caller.msg("|rInvalid index|n")
|
# return mode, selection, args
|
||||||
mode = None
|
|
||||||
|
|
||||||
return mode, selection, args
|
|
||||||
|
|
||||||
def _relay_to_edit_or_add(caller, raw_string, **kwargs):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def _list_node(caller, raw_string, **kwargs):
|
def _list_node(caller, raw_string, **kwargs):
|
||||||
|
|
||||||
mode = kwargs.get("list_mode", None)
|
|
||||||
option_list = option_generator(caller) if callable(option_generator) else option_generator
|
option_list = option_generator(caller) if callable(option_generator) else option_generator
|
||||||
|
|
||||||
print("option_list: {}, {}".format(option_list, mode))
|
|
||||||
|
|
||||||
npages = 0
|
npages = 0
|
||||||
page_index = 0
|
page_index = 0
|
||||||
page = []
|
page = []
|
||||||
|
|
@ -1231,58 +1088,16 @@ def list_node(option_generator, select=None, examine=None, edit=None, add=None,
|
||||||
|
|
||||||
if option_list:
|
if option_list:
|
||||||
nall_options = len(option_list)
|
nall_options = len(option_list)
|
||||||
pages = [option_list[ind:ind + pagesize] for ind in range(0, nall_options, pagesize)]
|
pages = [option_list[ind:ind + pagesize]
|
||||||
|
for ind in range(0, nall_options, pagesize)]
|
||||||
npages = len(pages)
|
npages = len(pages)
|
||||||
|
|
||||||
page_index = max(0, min(npages - 1, kwargs.get("optionpage_index", 0)))
|
page_index = max(0, min(npages - 1, kwargs.get("optionpage_index", 0)))
|
||||||
page = pages[page_index]
|
page = pages[page_index]
|
||||||
|
|
||||||
text = ""
|
text = ""
|
||||||
selection = None
|
|
||||||
extra_text = None
|
extra_text = None
|
||||||
|
|
||||||
if mode == "arbitrary":
|
|
||||||
# freeform input, we must parse it for the allowed commands (look/edit)
|
|
||||||
mode, selection, args = _input_parser(caller, raw_string,
|
|
||||||
**{"available_choices": page})
|
|
||||||
|
|
||||||
if examine and mode == "look":
|
|
||||||
# look mode - we are examining a given entry
|
|
||||||
try:
|
|
||||||
text = examine(caller, selection)
|
|
||||||
except Exception:
|
|
||||||
logger.log_trace()
|
|
||||||
text = "|rCould not view."
|
|
||||||
options.extend([{"key": ("|wb|Wack|n", "b"),
|
|
||||||
"goto": (lambda caller: None,
|
|
||||||
{"optionpage_index": page_index})},
|
|
||||||
{"key": "_default",
|
|
||||||
"goto": (lambda caller: None,
|
|
||||||
{"optionpage_index": page_index})}])
|
|
||||||
return text, options
|
|
||||||
|
|
||||||
elif add and mode == 'add':
|
|
||||||
# add mode - the selection is the new value
|
|
||||||
try:
|
|
||||||
text, options = add(caller, args)
|
|
||||||
except Exception:
|
|
||||||
logger.log_trace()
|
|
||||||
text = "|rCould not add."
|
|
||||||
return text, options
|
|
||||||
|
|
||||||
elif edit and mode == 'edit':
|
|
||||||
try:
|
|
||||||
text, options = edit(caller, selection, args)
|
|
||||||
except Exception:
|
|
||||||
logger.log_trace()
|
|
||||||
text = "|Could not edit."
|
|
||||||
return text, options
|
|
||||||
|
|
||||||
else:
|
|
||||||
# normal mode - list
|
|
||||||
|
|
||||||
# We have a processor to handle selecting an entry
|
|
||||||
|
|
||||||
# dynamic, multi-page option list. Each selection leads to the `select`
|
# dynamic, multi-page option list. Each selection leads to the `select`
|
||||||
# callback being called with a result from the available choices
|
# callback being called with a result from the available choices
|
||||||
options.extend([{"desc": opt,
|
options.extend([{"desc": opt,
|
||||||
|
|
@ -1306,32 +1121,44 @@ def list_node(option_generator, select=None, examine=None, edit=None, add=None,
|
||||||
"goto": (lambda caller: None,
|
"goto": (lambda caller: None,
|
||||||
{"optionpage_index": page_index + 1})})
|
{"optionpage_index": page_index + 1})})
|
||||||
|
|
||||||
# this catches arbitrary input and reruns this node with the 'arbitrary' mode
|
|
||||||
# this could mean input on the form 'look <num>' or 'edit <num>'
|
|
||||||
options.append({"key": "_default",
|
|
||||||
"goto": (lambda caller: None,
|
|
||||||
{"optionpage_index": page_index,
|
|
||||||
"available_choices": page,
|
|
||||||
"list_mode": "arbitrary"})})
|
|
||||||
|
|
||||||
# add data from the decorated node
|
# add data from the decorated node
|
||||||
|
|
||||||
extra_options = []
|
decorated_options = []
|
||||||
try:
|
try:
|
||||||
text, extra_options = func(caller, raw_string)
|
text, decorated_options = func(caller, raw_string)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
try:
|
try:
|
||||||
text, extra_options = func(caller)
|
text, decorated_options = func(caller)
|
||||||
except Exception:
|
except Exception:
|
||||||
raise
|
raise
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.log_trace()
|
logger.log_trace()
|
||||||
print("extra_options:", extra_options)
|
|
||||||
else:
|
else:
|
||||||
if isinstance(extra_options, {}):
|
if isinstance(decorated_options, {}):
|
||||||
extra_options = [extra_options]
|
decorated_options = [decorated_options]
|
||||||
else:
|
else:
|
||||||
extra_options = make_iter(extra_options)
|
decorated_options = make_iter(decorated_options)
|
||||||
|
|
||||||
|
extra_options = []
|
||||||
|
for eopt in decorated_options:
|
||||||
|
cback = ("goto" in eopt and "goto") or ("exec" in eopt and "exec") or None
|
||||||
|
if cback:
|
||||||
|
signature = eopt[cback]
|
||||||
|
if callable(signature):
|
||||||
|
# callable with no kwargs defined
|
||||||
|
eopt[cback] = (signature, {"available_choices": page})
|
||||||
|
elif is_iter(signature):
|
||||||
|
if len(signature) > 1 and isinstance(signature[1], dict):
|
||||||
|
signature[1]["available_choices"] = page
|
||||||
|
eopt[cback] = signature
|
||||||
|
elif signature:
|
||||||
|
# a callable alone in a tuple (i.e. no previous kwargs)
|
||||||
|
eopt[cback] = (signature[0], {"available_choices": page})
|
||||||
|
else:
|
||||||
|
# malformed input.
|
||||||
|
logger.log_err("EvMenu @list_node decorator found "
|
||||||
|
"malformed option to decorate: {}".format(eopt))
|
||||||
|
extra_options.append(eopt)
|
||||||
|
|
||||||
options.extend(extra_options)
|
options.extend(extra_options)
|
||||||
text = text + "\n\n" + extra_text if extra_text else text
|
text = text + "\n\n" + extra_text if extra_text else text
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue