Fix parsing precedence of long cmd-aliases. Resolve #3090

This commit is contained in:
Griatch 2023-02-26 20:15:34 +01:00
parent a2ebc720bf
commit 86f18c6686
3 changed files with 49 additions and 10 deletions

View file

@ -2,6 +2,9 @@
## Main branch (git) ## Main branch (git)
- Bug fix: Make sure command parser gives precedence to longer cmd-aliases. So
if sending `smile at` and the cmd `smile` has alias `smile at`, the match is
ordered so the result is never interpreted as `smile` with an argument `at`.
- Bug fix: || (escaped color tags) were parsed too early in help entries, - Bug fix: || (escaped color tags) were parsed too early in help entries,
leading to colors when wanting a | separator leading to colors when wanting a | separator
- Bug fix: Make sure spawned objects get `typeclass_path` pointing to the true - Bug fix: Make sure spawned objects get `typeclass_path` pointing to the true

View file

@ -11,7 +11,6 @@ import re
from django.conf import settings from django.conf import settings
from django.urls import reverse from django.urls import reverse
from django.utils.text import slugify from django.utils.text import slugify
from evennia.locks.lockhandler import LockHandler from evennia.locks.lockhandler import LockHandler
from evennia.utils.ansi import ANSIString from evennia.utils.ansi import ANSIString
from evennia.utils.evtable import EvTable from evennia.utils.evtable import EvTable
@ -54,10 +53,14 @@ def _init_command(cls, **kwargs):
cls.aliases = [] cls.aliases = []
cls.aliases = list(set(alias for alias in cls.aliases if alias and alias != cls.key)) cls.aliases = list(set(alias for alias in cls.aliases if alias and alias != cls.key))
# optimization - a set is much faster to match against than a list # optimization - a set is much faster to match against than a list. This is useful
# for 'does any match' kind of queries
cls._matchset = set([cls.key] + cls.aliases) cls._matchset = set([cls.key] + cls.aliases)
# optimization for looping over keys+aliases # optimization for looping over keys+aliases. We sort by longest entry first, since we
cls._keyaliases = tuple(cls._matchset) # want to be able to catch commands with spaces in the alias/key (so if you have key 'smile'
# and alias 'smile at', writing 'smile at' should not risk being interpreted as 'smile'
# with an argument 'at')
cls._keyaliases = tuple(sorted([cls.key] + cls.aliases, key=len, reverse=True))
# by default we don't save the command between runs # by default we don't save the command between runs
if not hasattr(cls, "save_for_next"): if not hasattr(cls, "save_for_next"):
@ -303,10 +306,13 @@ class Command(metaclass=CommandMeta):
matches.extend(x.lower() for x in self.aliases) matches.extend(x.lower() for x in self.aliases)
self._matchset = set(matches) self._matchset = set(matches)
# optimization for looping over keys+aliases # optimization for looping over keys+aliases. We sort by longest entry first, since we
self._keyaliases = tuple(self._matchset) # want to be able to catch commands with spaces in the alias/key (so if you have key 'smile'
# and alias 'smile at', writing 'smile at' should not risk being interpreted as 'smile'
# with an argument 'at')
self._keyaliases = tuple(sorted(matches, key=len, reverse=True))
self._noprefix_aliases = {x.lstrip(CMD_IGNORE_PREFIXES): x for x in matches} self._noprefix_aliases = {x.lstrip(CMD_IGNORE_PREFIXES): x for x in self._keyaliases}
def set_key(self, new_key): def set_key(self, new_key):
""" """

View file

@ -4,7 +4,6 @@ Unit testing for the Command system itself.
""" """
from django.test import override_settings from django.test import override_settings
from evennia.commands import cmdparser from evennia.commands import cmdparser
from evennia.commands.cmdset import CmdSet from evennia.commands.cmdset import CmdSet
from evennia.commands.command import Command from evennia.commands.command import Command
@ -991,9 +990,8 @@ class TestOptionTransferReplace(TestCase):
import sys import sys
from twisted.trial.unittest import TestCase as TwistedTestCase
from evennia.commands import cmdhandler from evennia.commands import cmdhandler
from twisted.trial.unittest import TestCase as TwistedTestCase
def _mockdelay(time, func, *args, **kwargs): def _mockdelay(time, func, *args, **kwargs):
@ -1232,3 +1230,35 @@ class TestCmdSet(BaseEvenniaTest):
result = test_cmd_set.get("another command") result = test_cmd_set.get("another command")
self.assertIsInstance(result, _CmdTest2) self.assertIsInstance(result, _CmdTest2)
class _CmdG(Command):
key = "smile"
aliases = ["smile at", "grin", "grin at"]
class _CmdSetG(CmdSet):
def at_cmdset_creation(self):
self.add(_CmdG())
class TestIssue3090(BaseEvenniaTest):
"""
Command aliases should be prioritized longest-match to shortest-match.
https://github.com/evennia/evennia/issues/3090
"""
def test_long_aliases(self):
cmdset_g = _CmdSetG()
# print(cmdset_g.commands[0]._keyaliases)
result = cmdparser.cmdparser("smile at", cmdset_g, None)[0]
self.assertEqual(result[0], "smile at")
self.assertEqual(result[1], "")
self.assertEqual(result[2].__class__, _CmdG)
self.assertEqual(result[3], 8)
self.assertEqual(result[4], 1.0)
self.assertEqual(result[5], "smile at")