Final unit tests for evmenu templating

This commit is contained in:
Griatch 2020-10-04 21:26:07 +02:00
parent a94e723d6b
commit 1746aaf06b
3 changed files with 77 additions and 30 deletions

View file

@ -290,10 +290,20 @@ def goto_command_demo_room(caller, raw_string, **kwargs):
_maintain_demo_room(caller)
caller.cmdset.remove(DemoCommandSetHelp)
caller.cmdset.remove(DemoCommandSetComms)
caller.cmdset.add(DemoCommandSetRoom) # TODO - make persistent
caller.cmdset.add(DemoCommandSetRoom)
return "command_demo_room"
def goto_cleanup_cmdsets(caller, raw_strings, **kwargs):
"""
Cleanup all cmdsets.
"""
caller.cmdset.remove(DemoCommandSetHelp)
caller.cmdset.remove(DemoCommandSetComms)
caller.cmdset.remove(DemoCommandSetRoom)
return kwargs.get("gotonode")
# register all callables that can be used in the menu template
GOTO_CALLABLES = {
@ -302,6 +312,7 @@ GOTO_CALLABLES = {
"goto_command_demo_help": goto_command_demo_help,
"goto_command_demo_comms": goto_command_demo_comms,
"goto_command_demo_room": goto_command_demo_room,
"goto_cleanup_cmdsets": goto_cleanup_cmdsets,
}
@ -365,7 +376,7 @@ gaming style you like and possibly any new ones you can come up with!
## OPTIONS
next;n: About Evennia -> about_evennia
back to start;start;t: start
back to start;back;start;t: start
>: about_evennia
# ---------------------------------------------------------------------------------
@ -498,7 +509,7 @@ those channels ...
## OPTIONS
next;n: Talk on Channels -> talk on channels
back;b: Using the webclient -> using webclient
back;b: Using the webclient -> goto_cleanup_cmdsets(gotonode='using webclient')
back to start;start: start
>: talk on channels
@ -556,7 +567,7 @@ include other people/objects in the emote, reference things by a short-descripti
## OPTIONS
next;n: Paging people -> paging_people
back;b: Talk on Channels -> talk on channels
back;b: Talk on Channels -> goto_command_demo_help(gotonode='talk on channels')
back to start;start: start
>: paging_people
@ -627,7 +638,7 @@ color codes printed, try
## OPTIONS
next;n: Moving and Exploring -> goto_command_demo_room()
back;b: Paging people -> paging_people
back;b: Paging people -> goto_command_demo_comms(gotonode='paging_people')
back to start;start: start
>: goto_command_demo_room()
@ -650,7 +661,7 @@ around in. Explore a little and use |ynext|n when you are done.
## OPTIONS
next;n: Conclusions -> conclusions
back;b: Channel commands -> testing_colors
back;b: Channel commands -> goto_command_demo_comms(gotonode='testing_colors')
back to start;start: start
>: conclusions
@ -667,13 +678,11 @@ if you get stuck!
Write |ynext|n to end this wizard and continue to the tutorial-world quest!
If you want there is also some |wextra|n info for where to go beyond that.
Good luck!
## OPTIONS
extra: Some more help on where to go next -> post scriptum
next;next;n: end
back;b: goto_command_demo_room()
extra: Where to go next -> post scriptum
next;next;n: End -> end
back;b: Moving and Exploring -> goto_command_demo_room()
back to start;start: start
>: end
@ -718,7 +727,7 @@ back: conclusions
## NODE end
Thanks for trying out the tutorial!
|gGood luck!|n
"""

View file

@ -1634,7 +1634,7 @@ _RE_NODE = re.compile(r"##\s*?NODE\s+?(?P<nodename>\S[\S\s]*?)$", re.I + re.M)
_RE_OPTIONS_SEP = re.compile(r"##\s*?OPTIONS\s*?$", re.I + re.M)
_RE_CALLABLE = re.compile(r"\S+?\(\)", re.I + re.M)
_RE_CALLABLE = re.compile(
r"(?P<funcname>\S+?)(?:\((?P<kwargs>[\S\s]+?=[\S\s]+?)\)|\(\))", re.I + re.M
r"(?P<funcname>\S+?)(?:\((?P<kwargs>[\S\s]+?)\)|\(\))", re.I + re.M
)
_HELP_NO_OPTION_MATCH = _("Choose an option or try 'help'.")
@ -1664,23 +1664,23 @@ def _process_callable(caller, goto, goto_callables, raw_string,
gotokwargs = match.group("kwargs") or ""
if gotofunc in goto_callables:
for kwarg in gotokwargs.split(","):
if kwarg and "=" in kwarg:
key, value = [part.strip() for part in kwarg.split("=", 1)]
if key in ("evmenu_goto", "evmenu_gotomap", "_current_nodename",
"evmenu_current_nodename", "evmenu_goto_callables"):
raise RuntimeError(
f"EvMenu template error: goto-callable '{goto}' uses a "
f"kwarg ({key}) that is reserved for the EvMenu templating "
"system. Rename the kwarg.")
try:
key = literal_eval(key)
except ValueError:
pass
try:
value = literal_eval(value)
except ValueError:
pass
kwargs[key] = value
key, value = [part.strip() for part in kwarg.split("=", 1)]
if key in ("evmenu_goto", "evmenu_gotomap", "_current_nodename",
"evmenu_current_nodename", "evmenu_goto_callables"):
raise RuntimeError(
f"EvMenu template error: goto-callable '{goto}' uses a "
f"kwarg ({kwarg}) that is reserved for the EvMenu templating "
"system. Rename the kwarg.")
try:
key = literal_eval(key)
except ValueError:
pass
try:
value = literal_eval(value)
except ValueError:
pass
kwargs[key] = value
goto = goto_callables[gotofunc](caller, raw_string, **kwargs)
if goto is None:
return goto, {"generated_nodename": current_nodename}
@ -1755,6 +1755,23 @@ def parse_menu_template(caller, menu_template, goto_callables=None):
dict: A `{"node": nodefunc}` menutree suitable to pass into EvMenu.
"""
def _validate_kwarg(goto, kwarg):
"""
Validate goto-callable kwarg is on correct form.
"""
if not "=" in kwarg:
raise RuntimeError(
f"EvMenu template error: goto-callable '{goto}' has a "
f"non-kwarg argument ({kwarg}). All callables in the "
"template must have only keyword-arguments, or no "
"args at all.")
key, _ = [part.strip() for part in kwarg.split("=", 1)]
if key in ("evmenu_goto", "evmenu_gotomap", "_current_nodename",
"evmenu_current_nodename", "evmenu_goto_callables"):
raise RuntimeError(
f"EvMenu template error: goto-callable '{goto}' uses a "
f"kwarg ({kwarg}) that is reserved for the EvMenu templating "
"system. Rename the kwarg.")
def _parse_options(nodename, optiontxt, goto_callables):
"""
@ -1779,6 +1796,14 @@ def parse_menu_template(caller, menu_template, goto_callables=None):
if _OPTION_CALL_MARKER in goto:
desc, goto = [part.strip() for part in goto.split(_OPTION_CALL_MARKER, 1)]
# validate callable
match = _RE_CALLABLE.match(goto)
if match:
kwargs = match.group("kwargs")
if kwargs:
for kwarg in kwargs.split(','):
_validate_kwarg(goto, kwarg)
# parse key [;aliases|pattern]
key = [part.strip() for part in key.split(_OPTION_ALIAS_MARKER)]
if not key:

View file

@ -327,3 +327,16 @@ class TestMenuTemplateParse(EvenniaTest):
def test_template2menu(self):
evmenu.template2menu(self.char1, self.menu_template, self.goto_callables)
def test_parse_menu_fail(self):
template = """
## NODE
Text
## OPTIONS
next: callnode2(invalid)
"""
with self.assertRaises(RuntimeError):
evmenu.parse_menu_template(self.char1, template, self.goto_callables)