Add the setattr choice building menu as a default

This commit is contained in:
Vincent Le Goff 2018-03-27 20:42:25 +02:00
parent 28960a1f8a
commit bfe9dde655

View file

@ -48,6 +48,7 @@ def show_exits(menu
""" """
from inspect import getargspec from inspect import getargspec
from textwrap import dedent
from django.conf import settings from django.conf import settings
from evennia import Command, CmdSet from evennia import Command, CmdSet
@ -123,7 +124,8 @@ class Choice(object):
"""A choice object, created by `add_choice`.""" """A choice object, created by `add_choice`."""
def __init__(self, title, key=None, aliases=None, attr=None, callback=None, text=None, brief=None, menu=None, caller=None, obj=None): def __init__(self, title, key=None, aliases=None, attr=None, on_select=None, on_nomatch=None, text=None, brief=None,
menu=None, caller=None, obj=None):
"""Constructor. """Constructor.
Args: Args:
@ -132,9 +134,8 @@ class Choice(object):
the sub-neu. If not set, try to guess it based on the title. the sub-neu. If not set, try to guess it based on the title.
aliases (list of str, optional): the allowed aliases for this choice. aliases (list of str, optional): the allowed aliases for this choice.
attr (str, optional): the name of the attribute of 'obj' to set. attr (str, optional): the name of the attribute of 'obj' to set.
callback (callable, optional): the function to call before the input on_select (callable, optional): a callable to call when the choice is selected.
is set in `attr`. If `attr` is not set, you should on_nomatch (callable, optional): a callable to call when no match is entered in the choice.
specify a function that both callback and set the value in `obj`.
text (str or callable, optional): a text to be displayed when text (str or callable, optional): a text to be displayed when
the menu is opened It can be a callable. the menu is opened It can be a callable.
brief (str or callable, optional): a brief summary of the brief (str or callable, optional): a brief summary of the
@ -150,7 +151,8 @@ class Choice(object):
self.key = key self.key = key
self.aliases = aliases self.aliases = aliases
self.attr = attr self.attr = attr
self.callback = callback self.on_select = on_select
self.on_nomatch = on_nomatch
self.text = text self.text = text
self.brief = brief self.brief = brief
self.menu = menu self.menu = menu
@ -160,10 +162,30 @@ class Choice(object):
def __repr__(self): def __repr__(self):
return "<Choice (title={}, key={})>".format(self.title, self.key) return "<Choice (title={}, key={})>".format(self.title, self.key)
def trigger(self, string): def select(self, string):
"""Call the trigger callback, is specified.""" """Called when the user opens the choice."""
if self.callback: if self.on_select:
_call_or_get(self.callback, menu=self.menu, choice=self, string=string, caller=self.caller, obj=self.obj) _call_or_get(self.on_select, menu=self.menu, choice=self, string=string, caller=self.caller, obj=self.obj)
# Display the text if there is some
if self.text:
self.caller.msg(_call_or_get(self.text, menu=self.menu, choice=self, string=string, caller=self.caller, obj=self.obj))
def nomatch(self, string):
"""Called when the user entered something that wasn't a command in a given choice.
Args:
string (str): the entered string.
"""
if self.on_nomatch:
_call_or_get(self.on_nomatch, menu=self.menu, choice=self, string=string, caller=self.caller, obj=self.obj)
def display_text(self):
"""Display the choice text to the caller."""
text = _call_or_get(self.text, menu=self.menu, choice=self, string="", caller=self.caller, obj=self.obj)
return text.format(obj=self.obj, caller=self.caller)
class BuildingMenu(object): class BuildingMenu(object):
@ -241,7 +263,7 @@ class BuildingMenu(object):
""" """
pass pass
def add_choice(self, title, key=None, aliases=None, attr=None, callback=None, text=None, brief=None): def add_choice(self, title, key=None, aliases=None, attr=None, on_select=None, on_nomatch=None, text=None, brief=None):
"""Add a choice, a valid sub-menu, in the current builder menu. """Add a choice, a valid sub-menu, in the current builder menu.
Args: Args:
@ -250,7 +272,8 @@ class BuildingMenu(object):
the sub-neu. If not set, try to guess it based on the title. the sub-neu. If not set, try to guess it based on the title.
aliases (list of str, optional): the allowed aliases for this choice. aliases (list of str, optional): the allowed aliases for this choice.
attr (str, optional): the name of the attribute of 'obj' to set. attr (str, optional): the name of the attribute of 'obj' to set.
callback (callable, optional): the function to call before the input on_select (callable, optional): a callable to call when the choice is selected.
on_nomatch (callable, optional): a callable to call when no match is entered in the choice.
is set in `attr`. If `attr` is not set, you should is set in `attr`. If `attr` is not set, you should
specify a function that both callback and set the value in `obj`. specify a function that both callback and set the value in `obj`.
text (str or callable, optional): a text to be displayed when text (str or callable, optional): a text to be displayed when
@ -275,16 +298,21 @@ class BuildingMenu(object):
key = key.lower() key = key.lower()
aliases = aliases or [] aliases = aliases or []
aliases = [a.lower() for a in aliases] aliases = [a.lower() for a in aliases]
if callback is None: if on_select is None and on_nomatch is None:
if attr is None: if attr is None:
raise ValueError("The choice {} has neither attr nor callback, specify one of these as arguments".format(title)) raise ValueError("The choice {} has neither attr nor callback, specify one of these as arguments".format(title))
callback = menu_setattr if attr and on_nomatch is None:
on_nomatch = menu_setattr
if isinstance(text, basestring):
text = dedent(text.strip("\n"))
if key and key in self.cmds: if key and key in self.cmds:
raise ValueError("A conflict exists between {} and {}, both use key or alias {}".format(self.cmds[key], title, repr(key))) raise ValueError("A conflict exists between {} and {}, both use key or alias {}".format(self.cmds[key], title, repr(key)))
choice = Choice(title, key, aliases, attr, callback, text, brief, menu=self, caller=self.caller, obj=self.obj) choice = Choice(title, key=key, aliases=aliases, attr=attr, on_select=on_select, on_nomatch=on_nomatch, text=text,
brief=brief, menu=self, caller=self.caller, obj=self.obj)
self.choices.append(choice) self.choices.append(choice)
if key: if key:
self.cmds[key] = choice self.cmds[key] = choice
@ -305,7 +333,7 @@ class BuildingMenu(object):
This is just a shortcut method, calling `add_choice`. This is just a shortcut method, calling `add_choice`.
""" """
return self.add_choice(title, key=key, aliases=aliases, callback=menu_quit) return self.add_choice(title, key=key, aliases=aliases, on_select=menu_quit)
def _generate_commands(self, cmdset): def _generate_commands(self, cmdset):
""" """
@ -318,7 +346,7 @@ class BuildingMenu(object):
if self.key is None: if self.key is None:
for choice in self.choices: for choice in self.choices:
cmd = MenuCommand(key=choice.key, aliases=choice.aliases, building_menu=self, choice=choice) cmd = MenuCommand(key=choice.key, aliases=choice.aliases, building_menu=self, choice=choice)
cmd.get_help = lambda cmd, caller: _call_or_get(choice.text, menu=self, choice=choice, obj=self.obj, caller=self.caller) cmd.get_help = lambda cmd, caller: choice.display_text()
cmdset.add(cmd) cmdset.add(cmd)
def _save(self): def _save(self):
@ -427,13 +455,20 @@ class MenuCommand(Command):
def func(self): def func(self):
"""Function body.""" """Function body."""
if self.choice is None: if self.choice is None or self.menu is None:
log_err("Command: {}, no choice has been specified".format(self.key)) log_err("Command: {}, no choice has been specified".format(self.key))
self.msg("An unexpected error occurred. Closing the menu.") self.msg("|rAn unexpected error occurred. Closing the menu.|n")
self.caller.cmdset.delete(BuildingMenuCmdSet) self.caller.cmdset.delete(BuildingMenuCmdSet)
return return
self.choice.trigger(self.args) self.menu.key = self.choice.key
self.menu._save()
for cmdset in self.caller.cmdset.get():
if isinstance(cmdset, BuildingMenuCmdSet):
for command in cmdset:
cmdset.remove(command)
break
self.choice.select(self.raw_string)
class CmdNoInput(MenuCommand): class CmdNoInput(MenuCommand):
@ -444,16 +479,20 @@ class CmdNoInput(MenuCommand):
locks = "cmd:all()" locks = "cmd:all()"
def func(self): def func(self):
"""Redisplay the screen, if any.""" """Display the menu or choice text."""
if self.menu: if self.menu:
self.menu.display() choice = self.menu.cmds.get(self.menu.key)
if self.menu.key and choice:
choice.display_text()
else:
self.menu.display()
else: else:
log_err("When CMDNOMATCH was called, the building menu couldn't be found") log_err("When CMDNOINPUT was called, the building menu couldn't be found")
self.caller.msg("The building menu couldn't be found, remove the CmdSet") self.caller.msg("|rThe building menu couldn't be found, remove the CmdSet.|n")
self.caller.cmdset.delete(BuildingMenuCmdSet) self.caller.cmdset.delete(BuildingMenuCmdSet)
class CmdNoMatch(Command): class CmdNoMatch(MenuCommand):
"""No input has been found.""" """No input has been found."""
@ -463,7 +502,26 @@ class CmdNoMatch(Command):
def func(self): def func(self):
"""Redirect most inputs to the screen, if found.""" """Redirect most inputs to the screen, if found."""
raw_string = self.raw_string.rstrip() raw_string = self.raw_string.rstrip()
self.msg("No match") choice = self.menu.cmds.get(self.menu.key) if self.menu else None
cmdset = None
for cset in self.caller.cmdset.get():
if isinstance(cset, BuildingMenuCmdSet):
cmdset = cset
break
if self.menu is None:
log_err("When CMDNOMATCH was called, the building menu couldn't be found")
self.caller.msg("|rThe building menu couldn't be found, remove the CmdSet.|n")
self.caller.cmdset.delete(BuildingMenuCmdSet)
elif self.args == "/" and self.menu.key:
self.menu.key = None
self.menu._save()
self.menu._generate_commands(cmdset)
self.menu.display()
elif self.menu.key:
choice.nomatch(raw_string)
choice.display_text()
else:
self.menu.display()
class BuildingMenuCmdSet(CmdSet): class BuildingMenuCmdSet(CmdSet):
@ -507,7 +565,7 @@ def menu_setattr(menu, choice, obj, string):
Note: Note:
This function is supposed to be used as a default to This function is supposed to be used as a default to
`BuildingMenu.add_choice`, when an attribute name is specified `BuildingMenu.add_choice`, when an attribute name is specified
but no function to callback the said value. but no function to call `on_nomatch` the said value.
""" """
attr = getattr(choice, "attr", None) attr = getattr(choice, "attr", None)
@ -519,7 +577,6 @@ def menu_setattr(menu, choice, obj, string):
obj = getattr(obj, part) obj = getattr(obj, part)
setattr(obj, attr.split(".")[-1], string) setattr(obj, attr.split(".")[-1], string)
menu.display()
def menu_quit(caller): def menu_quit(caller):
""" """