Improvements to cmdsethandler/cmdset debugging

This commit is contained in:
Griatch 2020-10-03 20:33:31 +02:00
parent dda2493dba
commit 2eaa947ed4
9 changed files with 904 additions and 108 deletions

View file

@ -5,7 +5,7 @@
- new `drop:holds()` lock default to limit dropping nonsensical things. Access check
defaults to True for backwards-compatibility in 0.9, will be False in 1.0
### Already in master
### Evennia 0.95 (master)
- `is_typeclass(obj (Object), exact (bool))` now defaults to exact=False
- `py` command now reroutes stdout to output results in-game client. `py`
without arguments starts a full interactive Python console.
@ -74,7 +74,12 @@ without arguments starts a full interactive Python console.
pagination (e.g. to create EvTables for every page instead of splittine one table)
- Using `EvMore pagination`, dramatically improves performance of `spawn/list` and `scripts` listings
(100x speed increase for displaying 1000+ prototypes/scripts).
- `EvMenu` now uses the more logically named `.ndb._evmenu` instead of `.ndb._menutree` to store itself.
Both still work for backward compatibility, but `_menutree` is deprecated.
- `EvMenu.msg(txt)` added as a central place to send text to the user, makes it easier to override.
Default `EvMenu.msg` sends with OOB type="menu" for use with OOB and webclient pane-redirects.
- New EvMenu templating system for quickly building simpler EvMenus without as much code.
## Evennia 0.9 (2018-2019)

View file

@ -494,6 +494,11 @@ def get_and_merge_cmdsets(caller, session, account, obj, callertype, raw_string)
cmdset = None
for cset in (cset for cset in local_obj_cmdsets if cset):
cset.duplicates = cset.old_duplicates
# important - this syncs the CmdSetHandler's .current field with the
# true current cmdset!
if cmdset:
caller.cmdset.current = cmdset
returnValue(cmdset)
except ErrorReported:
raise

View file

@ -106,9 +106,9 @@ class CmdSet(object, metaclass=_CmdSetMeta):
commands preference.
duplicates - determines what happens when two sets of equal
priority merge. Default has the first of them in the
priority merge (only). Defaults to None and has the first of them in the
merger (i.e. A above) automatically taking
precedence. But if allow_duplicates is true, the
precedence. But if `duplicates` is true, the
result will be a merger with more than one of each
name match. This will usually lead to the account
receiving a multiple-match error higher up the road,
@ -119,6 +119,16 @@ class CmdSet(object, metaclass=_CmdSetMeta):
select which ball to kick ... Allowing duplicates
only makes sense for Union and Intersect, the setting
is ignored for the other mergetypes.
Note that the `duplicates` flag is *not* propagated in
a cmdset merger. So `A + B = C` will result in
a cmdset with duplicate commands, but C.duplicates will
be `None`. For duplication to apply to a whole cmdset
stack merge, _all_ cmdsets in the stack must have
`.duplicates=True` set.
Finally, if a final cmdset has `.duplicates=None` (the normal
unless created alone with another value), the cmdhandler
will assume True for object-based cmdsets and False for
all other. This is usually the most intuitive outcome.
key_mergetype (dict) - allows the cmdset to define a unique
mergetype for particular cmdsets. Format is
@ -144,14 +154,27 @@ class CmdSet(object, metaclass=_CmdSetMeta):
mergetype = "Union"
priority = 0
# These flags, if set to None, will allow "pass-through" of lower-prio settings
# of True/False. If set to True/False, will override lower-prio settings.
# These flags, if set to None should be interpreted as 'I don't care' and,
# will allow "pass-through" even of lower-prio cmdsets' explicitly True/False
# options. If this is set to True/False however, priority matters.
no_exits = None
no_objs = None
no_channels = None
# same as above, but if left at None in the final merged set, the
# cmdhandler will auto-assume True for Objects and stay False for all
# other entities.
# The .duplicates setting does not propagate and since duplicates can only happen
# on same-prio cmdsets, there is no concept of passthrough on `None`.
# The merger of two cmdsets always return in a cmdset with `duplicates=None`
# (even if the result may have duplicated commands).
# If a final cmdset has `duplicates=None` (normal, unless the cmdset is
# created on its own with the flag set), the cmdhandler will auto-assume it to be
# True for Object-based cmdsets and stay None/False for all other entities.
#
# Example:
# A and C has .duplicates=True, B has .duplicates=None (or False)
# B + A = BA, where BA will have duplicate cmds, but BA.duplicates = None
# BA + C = BAC, where BAC will have more duplication, but BAC.duplicates = None
#
# Basically, for the `.duplicate` setting to survive throughout a
# merge-stack, every cmdset in the stack must have `duplicates` set explicitly.
duplicates = None
permanent = False
@ -334,7 +357,15 @@ class CmdSet(object, metaclass=_CmdSetMeta):
commands (str): Representation of commands in Cmdset.
"""
return ", ".join([str(cmd) for cmd in sorted(self.commands, key=lambda o: o.key)])
perm = "perm" if self.permanent else "non-perm"
options = ", ".join([
"{}:{}".format(opt, "T" if getattr(self, opt) else "F")
for opt in ("no_exits", "no_objs", "no_channels", "duplicates")
if getattr(self, opt) is not None
])
options = (", " + options) if options else ""
return f"<CmdSet {self.key}, {self.mergetype}, {perm}, prio {self.priority}{options}>: " + ", ".join(
[str(cmd) for cmd in sorted(self.commands, key=lambda o: o.key)])
def __iter__(self):
"""
@ -401,12 +432,15 @@ class CmdSet(object, metaclass=_CmdSetMeta):
# pass through options whenever they are set, unless the merging or higher-prio
# set changes the setting (i.e. has a non-None value). We don't pass through
# the duplicates setting; that is per-merge
# the duplicates setting; that is per-merge; the resulting .duplicates value
# is always None (so merging cmdsets must all have explicit values if wanting
# to cause duplicates).
cmdset_c.no_channels = (
self.no_channels if cmdset_a.no_channels is None else cmdset_a.no_channels
)
cmdset_c.no_exits = self.no_exits if cmdset_a.no_exits is None else cmdset_a.no_exits
cmdset_c.no_objs = self.no_objs if cmdset_a.no_objs is None else cmdset_a.no_objs
cmdset_c.duplicates = None
else:
# B higher priority than A
@ -428,12 +462,15 @@ class CmdSet(object, metaclass=_CmdSetMeta):
# pass through options whenever they are set, unless the higher-prio
# set changes the setting (i.e. has a non-None value). We don't pass through
# the duplicates setting; that is per-merge
# the duplicates setting; that is per-merge; the resulting .duplicates value#
# is always None (so merging cmdsets must all have explicit values if wanting
# to cause duplicates).
cmdset_c.no_channels = (
cmdset_a.no_channels if self.no_channels is None else self.no_channels
)
cmdset_c.no_exits = cmdset_a.no_exits if self.no_exits is None else self.no_exits
cmdset_c.no_objs = cmdset_a.no_objs if self.no_objs is None else self.no_objs
cmdset_c.duplicates = None
# we store actual_mergetype since key_mergetypes
# might be different from the main mergetype.

View file

@ -293,7 +293,10 @@ class CmdSetHandler(object):
# the id of the "merged" current cmdset for easy access.
self.key = None
# this holds the "merged" current command set
# this holds the "merged" current command set. Note that while the .update
# method updates this field in order to have it synced when operating on
# cmdsets in-code, when the game runs, this field is kept up-to-date by
# the cmdsethandler's get_and_merge_cmdsets!
self.current = None
# this holds a history of CommandSets
self.cmdset_stack = [_EmptyCmdSet(cmdsetobj=self.obj)]
@ -311,27 +314,13 @@ class CmdSetHandler(object):
Display current commands
"""
string = ""
strings = ["<CmdSetHandler> stack:"]
mergelist = []
if len(self.cmdset_stack) > 1:
# We have more than one cmdset in stack; list them all
for snum, cmdset in enumerate(self.cmdset_stack):
mergetype = self.mergetype_stack[snum]
permstring = "non-perm"
if cmdset.permanent:
permstring = "perm"
if mergetype != cmdset.mergetype:
mergetype = "%s^" % (mergetype)
string += "\n %i: <%s (%s, prio %i, %s)>: %s" % (
snum,
cmdset.key,
mergetype,
cmdset.priority,
permstring,
cmdset,
)
mergelist.append(str(snum))
string += "\n"
mergelist.append(str(snum + 1))
strings.append(f" {snum + 1}: {cmdset}")
# Display the currently active cmdset, limited by self.obj's permissions
mergetype = self.mergetype_stack[-1]
@ -339,27 +328,15 @@ class CmdSetHandler(object):
merged_on = self.cmdset_stack[-2].key
mergetype = _("custom {mergetype} on cmdset '{cmdset}'")
mergetype = mergetype.format(mergetype=mergetype, cmdset=merged_on)
if mergelist:
tmpstring = _(" <Merged {mergelist} {mergetype}, prio {prio}>: {current}")
string += tmpstring.format(
mergelist="+".join(mergelist),
mergetype=mergetype,
prio=self.current.priority,
current=self.current,
)
# current is a result of mergers
mergelist="+".join(mergelist)
strings.append(f" <Merged {mergelist}>: {self.current}")
else:
permstring = "non-perm"
if self.current.permanent:
permstring = "perm"
tmpstring = _(" <{key} ({mergetype}, prio {prio}, {permstring})>:\n {keylist}")
string += tmpstring.format(
key=self.current.key,
mergetype=mergetype,
prio=self.current.priority,
permstring=permstring,
keylist=", ".join(cmd.key for cmd in sorted(self.current, key=lambda o: o.key)),
)
return string.strip()
# current is a single cmdset
strings.append(" " + str(self.current))
return "\n".join(strings).rstrip()
def _import_cmdset(self, cmdset_path, emit_to_obj=None):
"""
@ -381,12 +358,22 @@ class CmdSetHandler(object):
def update(self, init_mode=False):
"""
Re-adds all sets in the handler to have an updated current
set.
Args:
init_mode (bool, optional): Used automatically right after
this handler was created; it imports all permanent cmdsets
from the database.
Notes:
This method is necessary in order to always have a `.current`
cmdset when working with the cmdsethandler in code. But the
CmdSetHandler doesn't (cannot) consider external cmdsets and game
state. This means that the .current calculated from this method
will likely not match the true current cmdset as determined at
run-time by `cmdhandler.get_and_merge_cmdsets()`. So in a running
game the responsibility of keeping `.current` upt-to-date belongs
to the central `cmdhandler.get_and_merge_cmdsets()`!
"""
if init_mode:
# reimport all permanent cmdsets

View file

@ -2429,13 +2429,13 @@ class CmdExamine(ObjManipCommand):
)
return output
def format_output(self, obj, avail_cmdset):
def format_output(self, obj, current_cmdset):
"""
Helper function that creates a nice report about an object.
Args:
obj (any): Object to analyze.
avail_cmdset (CmdSet): Current cmdset for object.
current_cmdset (CmdSet): Current cmdset for object.
Returns:
str: The formatted string.
@ -2513,15 +2513,36 @@ class CmdExamine(ObjManipCommand):
# cmdsets
if not (len(obj.cmdset.all()) == 1 and obj.cmdset.current.key == "_EMPTY_CMDSET"):
# all() returns a 'stack', so make a copy to sort.
def _format_options(cmdset):
"""helper for cmdset-option display"""
def _truefalse(string, value):
if value is None:
return ""
if value:
return f"{string}: T"
return f"{string}: F"
options = ", ".join(
_truefalse(opt, getattr(cmdset, opt))
for opt in ("no_exits", "no_objs", "no_channels", "duplicates")
if getattr(cmdset, opt) is not None
)
options = ", " + options if options else ""
return options
# cmdset stored on us
stored_cmdsets = sorted(obj.cmdset.all(), key=lambda x: x.priority, reverse=True)
output["Stored Cmdset(s)"] = "\n " + "\n ".join(
f"{cmdset.path} [{cmdset.key}] ({cmdset.mergetype}, prio {cmdset.priority})"
for cmdset in stored_cmdsets
if cmdset.key != "_EMPTY_CMDSET"
)
stored = []
for cmdset in stored_cmdsets:
if cmdset.key == "_EMPTY_CMDSET":
continue
options = _format_options(cmdset)
stored.append(
f"{cmdset.path} [{cmdset.key}] ({cmdset.mergetype}, prio {cmdset.priority}{options})")
output["Stored Cmdset(s)"] = "\n " + "\n ".join(stored)
# this gets all components of the currently merged set
all_cmdsets = [(cmdset.key, cmdset) for cmdset in avail_cmdset.merged_from]
all_cmdsets = [(cmdset.key, cmdset) for cmdset in current_cmdset.merged_from]
# we always at least try to add account- and session sets since these are ignored
# if we merge on the object level.
if hasattr(obj, "account") and obj.account:
@ -2551,15 +2572,24 @@ class CmdExamine(ObjManipCommand):
pass
all_cmdsets = [cmdset for cmdset in dict(all_cmdsets).values()]
all_cmdsets.sort(key=lambda x: x.priority, reverse=True)
output["Merged Cmdset(s)"] = "\n " + "\n ".join(
f"{cmdset.path} [{cmdset.key}] ({cmdset.mergetype} prio {cmdset.priority})"
for cmdset in all_cmdsets
)
# list the commands available to this object
avail_cmdset = sorted([cmd.key for cmd in avail_cmdset if cmd.access(obj, "cmd")])
cmdsetstr = "\n" + utils.fill(", ".join(avail_cmdset), indent=2)
# the resulting merged cmdset
options = _format_options(current_cmdset)
merged = [
f"<Current merged cmdset> ({current_cmdset.mergetype} prio {current_cmdset.priority}{options})"]
# the merge stack
for cmdset in all_cmdsets:
options = _format_options(cmdset)
merged.append(
f"{cmdset.path} [{cmdset.key}] ({cmdset.mergetype} prio {cmdset.priority}{options})")
output["Merged Cmdset(s)"] = "\n " + "\n ".join(merged)
# list the commands available to this object
current_commands = sorted([cmd.key for cmd in current_cmdset if cmd.access(obj, "cmd")])
cmdsetstr = "\n" + utils.fill(", ".join(current_commands), indent=2)
output[f"Commands available to {obj.key} (result of Merged CmdSets)"] = str(cmdsetstr)
# scripts
if hasattr(obj, "scripts") and hasattr(obj.scripts, "all") and obj.scripts.all():
output["Scripts"] = "\n " + f"{obj.scripts}"

View file

@ -971,7 +971,8 @@ class TestBuilding(CommandTest):
self.call(building.CmdSetHome(), "Obj = Room2", "Home location of Obj was set to Room")
def test_list_cmdsets(self):
self.call(building.CmdListCmdSets(), "", "<DefaultCharacter (Union, prio 0, perm)>:")
self.call(building.CmdListCmdSets(), "",
"<CmdSetHandler> stack:\n <CmdSet DefaultCharacter, Union, perm, prio 0>:")
self.call(building.CmdListCmdSets(), "NotFound", "Could not find 'NotFound'")
def test_typeclass(self):

View file

@ -194,27 +194,71 @@ class TestCmdSetMergers(TestCase):
self.assertEqual(len(cmdset_f.commands), 4)
self.assertTrue(all(True for cmd in cmdset_f.commands if cmd.from_cmdset == "A"))
def test_option_transfer(self):
"Test transfer of cmdset options"
class TestOptionTransferTrue(TestCase):
"""
Test cmdset-merge transfer of the cmdset-special options
(no_exits/channels/objs/duplicates etc)
cmdset A has all True options
"""
def setUp(self):
super().setUp()
self.cmdset_a = _CmdSetA()
self.cmdset_b = _CmdSetB()
self.cmdset_c = _CmdSetC()
self.cmdset_d = _CmdSetD()
self.cmdset_a.priority = 0
self.cmdset_b.priority = 0
self.cmdset_c.priority = 0
self.cmdset_d.priority = 0
self.cmdset_a.no_exits = True
self.cmdset_a.no_objs = True
self.cmdset_a.no_channels = True
self.cmdset_a.duplicates = True
def test_option_transfer__reverse_sameprio_passthrough(self):
"""
A has all True options, merges last (normal reverse merge), same prio.
The options should pass through to F since none of the other cmdsets
care to change the setting from their default None.
Since A.duplicates = True, the final result is an union of duplicate
pairs (8 commands total).
"""
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
# the options should pass through since none of the other cmdsets care
# to change the setting from None.
a.no_exits = True
a.no_objs = True
a.no_channels = True
a.duplicates = True
cmdset_f = d + c + b + a # reverse, same-prio
self.assertTrue(cmdset_f.no_exits)
self.assertTrue(cmdset_f.no_objs)
self.assertTrue(cmdset_f.no_channels)
self.assertTrue(cmdset_f.duplicates)
self.assertIsNone(cmdset_f.duplicates)
self.assertEqual(len(cmdset_f.commands), 8)
def test_option_transfer__forward_sameprio_passthrough(self):
"""
A has all True options, merges first (forward merge), same prio. This
should pass those options through since the other all have options set
to None. The exception is `duplicates` since that is determined by
the two last mergers in the chain both being True.
"""
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
cmdset_f = a + b + c + d # forward, same-prio
self.assertTrue(cmdset_f.no_exits)
self.assertTrue(cmdset_f.no_objs)
self.assertTrue(cmdset_f.no_channels)
self.assertFalse(cmdset_f.duplicates)
self.assertIsNone(cmdset_f.duplicates)
self.assertEqual(len(cmdset_f.commands), 4)
def test_option_transfer__reverse_highprio_passthrough(self):
"""
A has all True options, merges last (normal reverse merge) with the
highest prio. This should also pass through.
"""
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
a.priority = 2
b.priority = 1
c.priority = 0
@ -223,14 +267,35 @@ class TestCmdSetMergers(TestCase):
self.assertTrue(cmdset_f.no_exits)
self.assertTrue(cmdset_f.no_objs)
self.assertTrue(cmdset_f.no_channels)
self.assertTrue(cmdset_f.duplicates)
self.assertIsNone(cmdset_f.duplicates)
self.assertEqual(len(cmdset_f.commands), 4)
def test_option_transfer__forward_highprio_passthrough(self):
"""
A has all True options, merges first (forward merge). This is a bit
synthetic since it will never happen in practice, but logic should
still make it pass through.
"""
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
a.priority = 2
b.priority = 1
c.priority = 0
d.priority = -1
cmdset_f = a + b + c + d # forward, A top priority. This never happens in practice.
self.assertTrue(cmdset_f.no_exits)
self.assertTrue(cmdset_f.no_objs)
self.assertTrue(cmdset_f.no_channels)
self.assertTrue(cmdset_f.duplicates)
self.assertIsNone(cmdset_f.duplicates)
self.assertEqual(len(cmdset_f.commands), 4)
def test_option_transfer__reverse_lowprio_passthrough(self):
"""
A has all True options, merges last (normal reverse merge) with the lowest
prio. This never happens (it would always merge first) but logic should hold
and pass through since the other cmdsets have None.
"""
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
a.priority = -1
b.priority = 0
c.priority = 1
@ -239,32 +304,678 @@ class TestCmdSetMergers(TestCase):
self.assertTrue(cmdset_f.no_exits)
self.assertTrue(cmdset_f.no_objs)
self.assertTrue(cmdset_f.no_channels)
self.assertFalse(cmdset_f.duplicates)
self.assertIsNone(cmdset_f.duplicates)
self.assertEqual(len(cmdset_f.commands), 4)
def test_option_transfer__forward_lowprio_passthrough(self):
"""
A has all True options, merges first (forward merge) with lowest prio. This
is the normal behavior for a low-prio cmdset. Passthrough should happen.
"""
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
a.priority = -1
b.priority = 0
c.priority = 1
d.priority = 2
cmdset_f = a + b + c + d # forward, A low prio
self.assertTrue(cmdset_f.no_exits)
self.assertTrue(cmdset_f.no_objs)
self.assertTrue(cmdset_f.no_channels)
self.assertFalse(cmdset_f.duplicates)
self.assertIsNone(cmdset_f.duplicates)
self.assertEqual(len(cmdset_f.commands), 4)
def test_option_transfer__reverse_highprio_block_passthrough(self):
"""
A has all True options, other cmdsets has False. A merges last with high
prio. A should retain its option values and override the others
"""
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
a.priority = 2
b.priority = 1
c.priority = 0
d.priority = -1
c.no_exits = False
b.no_objs = False
d.duplicates = False
# higher-prio sets will change the option up the chain
cmdset_f = d + c + b + a # reverse, high prio
self.assertTrue(cmdset_f.no_exits)
self.assertTrue(cmdset_f.no_objs)
self.assertTrue(cmdset_f.no_channels)
self.assertIsNone(cmdset_f.duplicates)
self.assertEqual(len(cmdset_f.commands), 4)
def test_option_transfer__forward_highprio_block_passthrough(self):
"""
A has all True options, other cmdsets has False. A merges last with high
prio. This situation should never happen, but logic should hold - the highest
prio's options should survive the merge process.
"""
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
a.priority = 2
b.priority = 1
c.priority = 0
d.priority = -1
c.no_exits = False
b.no_channels = False
b.no_objs = False
d.duplicates = False
# higher-prio sets will change the option up the chain
cmdset_f = a + b + c + d # forward, high prio, never happens
self.assertTrue(cmdset_f.no_exits)
self.assertTrue(cmdset_f.no_objs)
self.assertTrue(cmdset_f.no_channels)
self.assertIsNone(cmdset_f.duplicates)
self.assertEqual(len(cmdset_f.commands), 4)
def test_option_transfer__forward_lowprio_block(self):
"""
A has all True options, other cmdsets has False. A merges last with low
prio. This should result in its values being blocked and come out False.
"""
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
a.priority = -1
b.priority = 0
c.priority = 1
d.priority = 2
c.no_exits = False
c.no_channels = False
b.no_objs = False
d.duplicates = False
# higher-prio sets will change the option up the chain
cmdset_f = a + b + c + d # forward, A low prio
self.assertFalse(cmdset_f.no_exits)
self.assertFalse(cmdset_f.no_objs)
self.assertFalse(cmdset_f.no_channels)
self.assertIsNone(cmdset_f.duplicates)
self.assertEqual(len(cmdset_f.commands), 4)
def test_option_transfer__forward_lowprio_block_partial(self):
"""
A has all True options, other cmdsets has False excet C which has a None
for `no_channels`. A merges last with low
prio. This should result in its values being blocked and come out False
except for no_channels which passes through.
"""
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
a.priority = -1
b.priority = 0
c.priority = 1
d.priority = 2
c.no_exits = False
c.no_channels = None # passthrough
b.no_objs = False
d.duplicates = False
# higher-prio sets will change the option up the chain
cmdset_f = a + b + c + d # forward, A low prio
self.assertFalse(cmdset_f.no_exits)
self.assertFalse(cmdset_f.no_objs)
self.assertTrue(cmdset_f.no_channels)
self.assertFalse(cmdset_f.duplicates)
self.assertIsNone(cmdset_f.duplicates)
self.assertEqual(len(cmdset_f.commands), 4)
a.priority = 0
b.priority = 0
def test_option_transfer__reverse_highprio_sameprio_order_last(self):
"""
A has all True options and highest prio, D has False and lowest prio,
others are passthrough. B has the same prio as A, with passthrough.
Since A is merged last, this should give prio to A's options
"""
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
a.priority = 2
b.priority = 2
c.priority = 0
d.priority = 0
d.priority = -1
d.no_channels = False
d.no_exits = False
d.no_objs = None
d.duplicates = False
# higher-prio sets will change the option up the chain
cmdset_f = d + c + b + a # reverse, A same prio, merged after b
self.assertTrue(cmdset_f.no_exits)
self.assertTrue(cmdset_f.no_objs)
self.assertTrue(cmdset_f.no_channels)
self.assertIsNone(cmdset_f.duplicates)
self.assertEqual(len(cmdset_f.commands), 8)
def test_option_transfer__reverse_highprio_sameprio_order_first(self):
"""
A has all True options and highest prio, D has False and lowest prio,
others are passthrough. B has the same prio as A, with passthrough.
While B, with None-values, is merged after A, A's options should have
replaced those of D at that point, and since B has passthrough the
final result should contain A's True options.
Note that despite A having duplicates=True, there is no duplication in
the DB + A merger since they have different priorities.
"""
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
a.priority = 2
b.priority = 2
c.priority = 0
d.priority = -1
d.no_channels = False
d.no_exits = False
d.no_objs = False
d.duplicates = False
# higher-prio sets will change the option up the chain
cmdset_f = d + c + a + b # reverse, A same prio, merged before b
self.assertTrue(cmdset_f.no_exits)
self.assertTrue(cmdset_f.no_objs)
self.assertTrue(cmdset_f.no_channels)
self.assertIsNone(cmdset_f.duplicates)
self.assertEqual(len(cmdset_f.commands), 4)
def test_option_transfer__reverse_lowprio_block(self):
"""
A has all True options, other cmdsets has False. A merges last with low
prio. This usually doesn't happen- it should merge last. But logic should
hold and the low-prio cmdset's values should be blocked and come out False.
"""
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
a.priority = -1
b.priority = 0
c.priority = 1
d.priority = 2
c.no_exits = False
d.no_channels = False
b.no_objs = False
d.duplicates = False
# higher-prio sets will change the option up the chain
cmdset_f = d + c + b + a # reverse, A low prio, never happens
self.assertFalse(cmdset_f.no_exits)
self.assertFalse(cmdset_f.no_objs)
self.assertFalse(cmdset_f.no_channels)
self.assertIsNone(cmdset_f.duplicates)
self.assertEqual(len(cmdset_f.commands), 4)
class TestOptionTransferFalse(TestCase):
"""
Test cmdset-merge transfer of the cmdset-special options
(no_exits/channels/objs/duplicates etc)
cmdset A has all False options
"""
def setUp(self):
super().setUp()
self.cmdset_a = _CmdSetA()
self.cmdset_b = _CmdSetB()
self.cmdset_c = _CmdSetC()
self.cmdset_d = _CmdSetD()
self.cmdset_a.priority = 0
self.cmdset_b.priority = 0
self.cmdset_c.priority = 0
self.cmdset_d.priority = 0
self.cmdset_a.no_exits = False
self.cmdset_a.no_objs = False
self.cmdset_a.no_channels = False
self.cmdset_a.duplicates = False
def test_option_transfer__reverse_sameprio_passthrough(self):
"""
A has all False options, merges last (normal reverse merge), same prio.
The options should pass through to F since none of the other cmdsets
care to change the setting from their default None.
Since A has duplicates=False, the result is a unique union of 4 cmds.
"""
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
cmdset_f = d + c + b + a # reverse, same-prio
self.assertFalse(cmdset_f.no_exits)
self.assertFalse(cmdset_f.no_objs)
self.assertFalse(cmdset_f.no_channels)
self.assertIsNone(cmdset_f.duplicates)
self.assertEqual(len(cmdset_f.commands), 4)
def test_option_transfer__forward_sameprio_passthrough(self):
"""
A has all False options, merges first (forward merge), same prio. This
should pass those options through since the other all have options set
to None. The exception is `duplicates` since that is determined by
the two last mergers in the chain both being .
"""
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
cmdset_f = a + b + c + d # forward, same-prio
self.assertFalse(cmdset_f.no_exits)
self.assertFalse(cmdset_f.no_objs)
self.assertFalse(cmdset_f.no_channels)
self.assertIsNone(cmdset_f.duplicates)
self.assertEqual(len(cmdset_f.commands), 4)
def test_option_transfer__reverse_highprio_passthrough(self):
"""
A has all False options, merges last (normal reverse merge) with the
highest prio. This should also pass through.
"""
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
a.priority = 2
b.priority = 1
c.priority = 0
d.priority = -1
cmdset_f = d + c + b + a # reverse, A top priority
self.assertFalse(cmdset_f.no_exits)
self.assertFalse(cmdset_f.no_objs)
self.assertFalse(cmdset_f.no_channels)
self.assertIsNone(cmdset_f.duplicates)
self.assertEqual(len(cmdset_f.commands), 4)
def test_option_transfer__forward_highprio_passthrough(self):
"""
A has all False options, merges first (forward merge). This is a bit
synthetic since it will never happen in practice, but logic should
still make it pass through.
"""
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
a.priority = 2
b.priority = 1
c.priority = 0
d.priority = -1
cmdset_f = a + b + c + d # forward, A top priority. This never happens in practice.
self.assertFalse(cmdset_f.no_exits)
self.assertFalse(cmdset_f.no_objs)
self.assertFalse(cmdset_f.no_channels)
self.assertIsNone(cmdset_f.duplicates)
self.assertEqual(len(cmdset_f.commands), 4)
def test_option_transfer__reverse_lowprio_passthrough(self):
"""
A has all False options, merges last (normal reverse merge) with the lowest
prio. This never happens (it would always merge first) but logic should hold
and pass through since the other cmdsets have None.
"""
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
a.priority = -1
b.priority = 0
c.priority = 1
d.priority = 2
cmdset_f = d + c + b + a # reverse, A low prio. This never happens in practice.
self.assertFalse(cmdset_f.no_exits)
self.assertFalse(cmdset_f.no_objs)
self.assertFalse(cmdset_f.no_channels)
self.assertIsNone(cmdset_f.duplicates)
self.assertEqual(len(cmdset_f.commands), 4)
def test_option_transfer__forward_lowprio_passthrough(self):
"""
A has all False options, merges first (forward merge) with lowest prio. This
is the normal behavior for a low-prio cmdset. Passthrough should happen.
"""
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
a.priority = -1
b.priority = 0
c.priority = 1
d.priority = 2
cmdset_f = a + b + c + d # forward, A low prio
self.assertFalse(cmdset_f.no_exits)
self.assertFalse(cmdset_f.no_objs)
self.assertFalse(cmdset_f.no_channels)
self.assertIsNone(cmdset_f.duplicates)
self.assertEqual(len(cmdset_f.commands), 4)
def test_option_transfer__reverse_highprio_block_passthrough(self):
"""
A has all False options, other cmdsets has True. A merges last with high
prio. A should retain its option values and override the others
"""
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
a.priority = 2
b.priority = 1
c.priority = 0
d.priority = -1
c.no_exits = True
b.no_objs = True
d.duplicates = True
# higher-prio sets will change the option up the chain
cmdset_f = d + c + b + a # reverse, high prio
self.assertFalse(cmdset_f.no_exits)
self.assertFalse(cmdset_f.no_objs)
self.assertFalse(cmdset_f.no_channels)
self.assertIsNone(cmdset_f.duplicates)
self.assertEqual(len(cmdset_f.commands), 4)
def test_option_transfer__forward_highprio_block_passthrough(self):
"""
A has all False options, other cmdsets has True. A merges last with high
prio. This situation should never happen, but logic should hold - the highest
prio's options should survive the merge process.
"""
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
a.priority = 2
b.priority = 1
c.priority = 0
d.priority = -1
c.no_exits = True
b.no_channels = True
b.no_objs = True
d.duplicates = True
# higher-prio sets will change the option up the chain
cmdset_f = a + b + c + d # forward, high prio, never happens
self.assertFalse(cmdset_f.no_exits)
self.assertFalse(cmdset_f.no_objs)
self.assertFalse(cmdset_f.no_channels)
self.assertIsNone(cmdset_f.duplicates)
self.assertEqual(len(cmdset_f.commands), 4)
def test_option_transfer__forward_lowprio_block(self):
"""
A has all False options, other cmdsets has True. A merges last with low
prio. This should result in its values being blocked and come out False.
"""
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
a.priority = -1
b.priority = 0
c.priority = 1
d.priority = 2
c.no_exits = True
c.no_channels = True
b.no_objs = True
d.duplicates = True
# higher-prio sets will change the option up the chain
cmdset_f = a + b + c + d # forward, A low prio
self.assertTrue(cmdset_f.no_exits)
self.assertTrue(cmdset_f.no_objs)
self.assertTrue(cmdset_f.no_channels)
self.assertIsNone(cmdset_f.duplicates)
self.assertEqual(len(cmdset_f.commands), 4)
def test_option_transfer__forward_lowprio_block_partial(self):
"""
A has all False options, other cmdsets has True excet C which has a None
for `no_channels`. A merges last with low
prio. This should result in its values being blocked and come out True
except for no_channels which passes through.
"""
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
a.priority = -1
b.priority = 0
c.priority = 1
d.priority = 2
c.no_exits = True
c.no_channels = None # passthrough
b.no_objs = True
d.duplicates = True
# higher-prio sets will change the option up the chain
cmdset_f = a + b + c + d # forward, A low prio
self.assertTrue(cmdset_f.no_exits)
self.assertTrue(cmdset_f.no_objs)
self.assertFalse(cmdset_f.no_channels)
self.assertIsNone(cmdset_f.duplicates)
self.assertEqual(len(cmdset_f.commands), 4)
def test_option_transfer__reverse_sameprio_order_last(self):
"""
A has all False options and highest prio, D has True and lowest prio,
others are passthrough. B has the same prio as A, with passthrough.
Since A is merged last, this should give prio to A's False options
"""
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
a.priority = 2
b.priority = 2
c.priority = 0
d.priority = -1
d.no_channels = True
d.no_exits = True
d.no_objs = True
d.duplicates = False
# higher-prio sets will change the option up the chain
cmdset_f = d + c + b + a # reverse, A high prio, merged after b
self.assertFalse(cmdset_f.no_exits)
self.assertFalse(cmdset_f.no_objs)
self.assertFalse(cmdset_f.no_channels)
self.assertIsNone(cmdset_f.duplicates)
self.assertEqual(len(cmdset_f.commands), 4)
def test_option_transfer__reverse_sameprio_order_first(self):
"""
A has all False options and highest prio, D has True and lowest prio,
others are passthrough. B has the same prio as A, with passthrough.
While B, with None-values, is merged after A, A's options should have
replaced those of D at that point, and since B has passthrough the
final result should contain A's False options.
"""
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
a.priority = 2
b.priority = 2
c.priority = 0
d.priority = -1
d.no_channels = True
d.no_exits = True
d.no_objs = True
d.duplicates = False
# higher-prio sets will change the option up the chain
cmdset_f = d + c + a + b # reverse, A high prio, merged before b
self.assertFalse(cmdset_f.no_exits)
self.assertFalse(cmdset_f.no_objs)
self.assertFalse(cmdset_f.no_channels)
self.assertIsNone(cmdset_f.duplicates)
self.assertEqual(len(cmdset_f.commands), 4)
def test_option_transfer__reverse_lowprio_block(self):
"""
A has all False options, other cmdsets has True. A merges last with low
prio. This usually doesn't happen- it should merge last. But logic should
hold and the low-prio cmdset's values should be blocked and come out True.
"""
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
a.priority = -1
b.priority = 0
c.priority = 1
d.priority = 2
c.no_exits = True
d.no_channels = True
b.no_objs = True
d.duplicates = True
# higher-prio sets will change the option up the chain
cmdset_f = d + c + b + a # reverse, A low prio, never happens
self.assertTrue(cmdset_f.no_exits)
self.assertTrue(cmdset_f.no_objs)
self.assertTrue(cmdset_f.no_channels)
self.assertIsNone(cmdset_f.duplicates)
self.assertEqual(len(cmdset_f.commands), 4)
class TestDuplicateBehavior(TestCase):
"""
Test behavior of .duplicate option, which is a bit special in that it
doesn't propagate.
`A.duplicates=True` for all tests.
"""
def setUp(self):
super().setUp()
self.cmdset_a = _CmdSetA()
self.cmdset_b = _CmdSetB()
self.cmdset_c = _CmdSetC()
self.cmdset_d = _CmdSetD()
self.cmdset_a.priority = 0
self.cmdset_b.priority = 0
self.cmdset_c.priority = 0
self.cmdset_d.priority = 0
self.cmdset_a.duplicates = True
def test_reverse_sameprio_duplicate(self):
"""
Test of `duplicates` transfer which does not propagate. Only
A has duplicates=True.
D + B = DB (no duplication, DB.duplication=None)
DB + C = DBC (no duplication, DBC.duplication=None)
DBC + A = final (duplication, final.duplication=None)
"""
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
cmdset_f = d + b + c + a # two last mergers duplicates=True
self.assertIsNone(cmdset_f.duplicates)
self.assertEqual(len(cmdset_f.commands), 8)
def test_reverse_sameprio_duplicate(self):
"""
Test of `duplicates` transfer, which does not propagate.
C.duplication=True
D + B = DB (no duplication, DB.duplication=None)
DB + C = DBC (duplication, DBC.duplication=None)
DBC + A = final (duplication, final.duplication=None)
"""
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
c.duplicates = True
cmdset_f = d + b + c + a # two last mergers duplicates=True
self.assertIsNone(cmdset_f.duplicates)
self.assertEqual(len(cmdset_f.commands), 10)
def test_forward_sameprio_duplicate(self):
"""
Test of `duplicates` transfer which does not propagate.
C.duplication=True, merges later than A
D + B = DB (no duplication, DB.duplication=None)
DB + A = DBA (duplication, DBA.duplication=None)
DBA + C = final (duplication, final.duplication=None)
"""
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
c.duplicates = True
cmdset_f = d + b + a + c # two last mergers duplicates=True
self.assertIsNone(cmdset_f.duplicates)
self.assertEqual(len(cmdset_f.commands), 10)
def test_reverse_sameprio_duplicate_reverse(self):
"""
Test of `duplicates` transfer which does not propagate.
C.duplication=False (explicit), merges before A. This behavior is the
same as if C.duplication=None, since A merges later and takes
precedence.
D + B = DB (no duplication, DB.duplication=None)
DB + C = DBC (no duplication, DBC.duplication=None)
DBC + A = final (duplication, final.duplication=None)
"""
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
c.duplicates = False
cmdset_f = d + b + c + a # a merges last, takes precedence
self.assertIsNone(cmdset_f.duplicates)
self.assertEqual(len(cmdset_f.commands), 8)
def test_reverse_sameprio_duplicate_forward(self):
"""
Test of `duplicates` transfer which does not propagate.
C.duplication=False (explicit), merges after A. This just means
only A causes duplicates, earlier in the chain.
D + B = DB (no duplication, DB.duplication=None)
DB + A = DBA (duplication, DBA.duplication=None)
DBA + C = final (no duplication, final.duplication=None)
Note that DBA has 8 cmds due to A merging onto DB with duplication,
but since C merges onto this with no duplication, the union will hold
6 commands, since C has two commands that replaces the 4 duplicates
with uniques copies from C.
"""
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
c.duplicates = False
cmdset_f = d + b + a + c # a merges before c
self.assertIsNone(cmdset_f.duplicates)
self.assertEqual(len(cmdset_f.commands), 6)
class TestOptionTransferReplace(TestCase):
"""
Test option transfer through more complex merge types.
"""
def setUp(self):
super().setUp()
self.cmdset_a = _CmdSetA()
self.cmdset_b = _CmdSetB()
self.cmdset_c = _CmdSetC()
self.cmdset_d = _CmdSetD()
self.cmdset_a.priority = 0
self.cmdset_b.priority = 0
self.cmdset_c.priority = 0
self.cmdset_d.priority = 0
self.cmdset_a.no_exits = True
self.cmdset_a.no_objs = True
self.cmdset_a.no_channels = True
self.cmdset_a.duplicates = True
def test_option_transfer__replace_reverse_highprio(self):
"""
A has all options True and highest priority. C has them False and is
Replace-type.
"""
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
a.priority = 2
b.priority = 2
c.priority = 0
c.mergetype = "Replace"
c.no_channels = False
c.no_exits = False
c.no_objs = False
c.duplicates = False
d.priority = -1
cmdset_f = d + c + b + a # reverse, A high prio, C Replace
self.assertTrue(cmdset_f.no_exits)
self.assertTrue(cmdset_f.no_objs)
self.assertTrue(cmdset_f.no_channels)
self.assertIsNone(cmdset_f.duplicates)
self.assertEqual(len(cmdset_f.commands), 7)
def test_option_transfer__replace_reverse_highprio_from_false(self):
"""
Inverse of previous test: A has all options False and highest priority.
C has them True and is Replace-type.
"""
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
a.no_exits = False
a.no_objs = False
a.no_channels = False
a.duplicates = False
a.priority = 2
b.priority = 2
c.priority = 0
c.mergetype = "Replace"
c.no_channels = True
c.no_exits = True
c.no_objs = True
c.duplicates = True
d.priority = -1
cmdset_f = d + c + b + a # reverse, A high prio, C Replace
self.assertFalse(cmdset_f.no_exits)
self.assertFalse(cmdset_f.no_objs)
self.assertFalse(cmdset_f.no_channels)
self.assertIsNone(cmdset_f.duplicates)
self.assertEqual(len(cmdset_f.commands), 4)
# test cmdhandler functions

View file

@ -179,6 +179,7 @@ class DemoCommandSetRoom(CmdSet):
key = "cmd_demo_cmdset_room"
priority = 2
no_exits = False
no_objs = False
def at_cmdset_creation(self):
from evennia import default_cmds

View file

@ -57,7 +57,7 @@ both one or two arguments interchangeably. It also accepts nodes
that takes **kwargs.
The menu tree itself is available on the caller as
`caller.ndb._menutree`. This makes it a convenient place to store
`caller.ndb._evmenu`. This makes it a convenient place to store
temporary state variables between nodes, since this NAttribute is
deleted when the menu is exited.
@ -390,28 +390,28 @@ class CmdEvMenuNode(Command):
caller = self.caller
# we store Session on the menu since this can be hard to
# get in multisession environemtns if caller is an Account.
menu = caller.ndb._menutree
menu = caller.ndb._evmenu
if not menu:
if _restore(caller):
return
orig_caller = caller
caller = caller.account if hasattr(caller, "account") else None
menu = caller.ndb._menutree if caller else None
menu = caller.ndb._evmenu if caller else None
if not menu:
if caller and _restore(caller):
return
caller = self.session
menu = caller.ndb._menutree
menu = caller.ndb._evmenu
if not menu:
# can't restore from a session
err = "Menu object not found as %s.ndb._menutree!" % orig_caller
err = "Menu object not found as %s.ndb._evmenu!" % orig_caller
orig_caller.msg(
err
) # don't give the session as a kwarg here, direct to original
raise EvMenuError(err)
# we must do this after the caller with the menu has been correctly identified since it
# can be either Account, Object or Session (in the latter case this info will be superfluous).
caller.ndb._menutree._session = self.session
caller.ndb._evmenu._session = self.session
# we have a menu, use it.
menu.parse_input(self.raw_string)
@ -539,16 +539,16 @@ class EvMenu:
`persistent` flag is deactivated.
Kwargs:
any (any): All kwargs will become initialization variables on `caller.ndb._menutree`,
any (any): All kwargs will become initialization variables on `caller.ndb._evmenu`,
to be available at run.
Raises:
EvMenuError: If the start/end node is not found in menu tree.
Notes:
While running, the menu is stored on the caller as `caller.ndb._menutree`. Also
While running, the menu is stored on the caller as `caller.ndb._evmenu`. Also
the current Session (from the Command, so this is still valid in multisession
environments) is available through `caller.ndb._menutree._session`. The `_menutree`
environments) is available through `caller.ndb._evmenu._session`. The `_evmenu`
property is a good one for storing intermediary data on between nodes since it
will be automatically deleted when the menu closes.
@ -621,15 +621,18 @@ class EvMenu:
for key, val in kwargs.items():
setattr(self, key, val)
if self.caller.ndb._menutree:
if self.caller.ndb._evmenu:
# an evmenu already exists - we try to close it cleanly. Note that this will
# not fire the previous menu's end node.
try:
self.caller.ndb._menutree.close_menu()
self.caller.ndb._evmenu.close_menu()
except Exception:
pass
# store ourself on the object
self.caller.ndb._evmenu = self
# DEPRECATED - for backwards-compatibility
self.caller.ndb._menutree = self
if persistent:
@ -649,7 +652,7 @@ class EvMenu:
caller.attributes.add("_menutree_saved", (self.__class__, (menudata,), calldict))
caller.attributes.add("_menutree_saved_startnode", (startnode, startnode_input))
except Exception as err:
caller.msg(_ERROR_PERSISTENT_SAVING.format(error=err), session=self._session)
self.msg(_ERROR_PERSISTENT_SAVING.format(error=err))
logger.log_trace(_TRACE_PERSISTENT_SAVING)
persistent = False
@ -761,7 +764,7 @@ class EvMenu:
ret = callback(self.caller)
except EvMenuError:
errmsg = _ERR_GENERAL.format(nodename=callback)
self.caller.msg(errmsg, self._session)
self.msg(errmsg)
logger.log_trace()
raise
@ -786,7 +789,7 @@ class EvMenu:
try:
node = self._menutree[nodename]
except KeyError:
self.caller.msg(_ERR_NOT_IMPLEMENTED.format(nodename=nodename), session=self._session)
self.msg(_ERR_NOT_IMPLEMENTED.format(nodename=nodename))
raise EvMenuError
try:
kwargs["_current_nodename"] = nodename
@ -796,11 +799,11 @@ class EvMenu:
else:
nodetext, options = ret, None
except KeyError:
self.caller.msg(_ERR_NOT_IMPLEMENTED.format(nodename=nodename), session=self._session)
self.msg(_ERR_NOT_IMPLEMENTED.format(nodename=nodename))
logger.log_trace()
raise EvMenuError
except Exception:
self.caller.msg(_ERR_GENERAL.format(nodename=nodename), session=self._session)
self.msg(_ERR_GENERAL.format(nodename=nodename))
logger.log_trace()
raise
@ -810,6 +813,23 @@ class EvMenu:
return nodetext, options
def msg(self, txt):
"""
This is a central point for sending return texts to the caller. It
allows for a central point to add custom messaging when creating custom
EvMenu overrides.
Args:
txt (str): The text to send.
Notes:
By default this will send to the same session provided to EvMenu
(if `session` kwarg was provided to `EvMenu.__init__`). It will
also send it with a `type=menu` for the benefit of OOB/webclient.
"""
self.caller.msg(text=(txt, {"type": "menu"}), session=self._session)
def run_exec(self, nodename, raw_string, **kwargs):
"""
NOTE: This is deprecated. Use `goto` directly instead.
@ -856,7 +876,7 @@ class EvMenu:
ret, kwargs = ret[:2]
except EvMenuError as err:
errmsg = "Error in exec '%s' (input: '%s'): %s" % (nodename, raw_string.rstrip(), err)
self.caller.msg("|r%s|n" % errmsg)
self.msg("|r%s|n" % errmsg)
logger.log_trace(errmsg)
return
@ -1038,7 +1058,7 @@ class EvMenu:
# avoid multiple calls from different sources
self._quitting = True
self.caller.cmdset.remove(EvMenuCmdSet)
del self.caller.ndb._menutree
del self.caller.ndb._evmenu
if self._persistent:
self.caller.attributes.remove("_menutree_saved")
self.caller.attributes.remove("_menutree_saved_startnode")
@ -1102,7 +1122,7 @@ class EvMenu:
)
+ "\n |y... END MENU DEBUG|n"
)
self.caller.msg(debugtxt)
self.msg(debugtxt)
def parse_input(self, raw_string):
"""
@ -1137,17 +1157,17 @@ class EvMenu:
goto, goto_kwargs, execfunc, exec_kwargs = self.default
self.run_exec_then_goto(execfunc, goto, raw_string, exec_kwargs, goto_kwargs)
else:
self.caller.msg(_HELP_NO_OPTION_MATCH, session=self._session)
self.msg(_HELP_NO_OPTION_MATCH)
except EvMenuGotoAbortMessage as err:
# custom interrupt from inside a goto callable - print the message and
# stay on the current node.
self.caller.msg(str(err), session=self._session)
self.msg(str(err))
def display_nodetext(self):
self.caller.msg(self.nodetext, session=self._session)
self.msg(self.nodetext)
def display_helptext(self):
self.caller.msg(self.helptext, session=self._session)
self.msg(self.helptext)
# formatters - override in a child class
@ -1732,7 +1752,6 @@ def parse_menu_template(caller, menu_template, goto_callables=None):
# if we have a pattern, build the arguments for _default later
pattern = main_key[len(_OPTION_INPUT_MARKER):].strip()
inputparsemap[pattern] = goto
print(f"main_key {main_key} {pattern} {goto}")
else:
# a regular goto string/callable target
option = {