Simplify list_node decorator

This commit is contained in:
Griatch 2018-04-15 00:03:36 +02:00
parent c340c08adb
commit a5e90de43e

View file

@ -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