Break up Object.search into multiple methods for easier overloading. Resolve #3417
This commit is contained in:
parent
32e9520db9
commit
015698d06f
7 changed files with 459 additions and 331 deletions
|
|
@ -154,18 +154,12 @@ from string import punctuation
|
|||
|
||||
import inflect
|
||||
from django.conf import settings
|
||||
|
||||
from evennia.commands.cmdset import CmdSet
|
||||
from evennia.commands.command import Command
|
||||
from evennia.objects.models import ObjectDB
|
||||
from evennia.objects.objects import DefaultCharacter, DefaultObject
|
||||
from evennia.utils import ansi, logger
|
||||
from evennia.utils.utils import (
|
||||
iter_to_str,
|
||||
lazy_property,
|
||||
make_iter,
|
||||
variable_from_module,
|
||||
)
|
||||
from evennia.utils.utils import iter_to_str, lazy_property, make_iter, variable_from_module
|
||||
|
||||
_INFLECT = inflect.engine()
|
||||
|
||||
|
|
@ -179,7 +173,7 @@ _AT_SEARCH_RESULT = variable_from_module(*settings.SEARCH_AT_RESULT.rsplit(".",
|
|||
# The prefix is the (single-character) symbol used to find the start
|
||||
# of a object reference, such as /tall (note that
|
||||
# the system will understand multi-word references like '/a tall man' too).
|
||||
_PREFIX = getattr(settings, 'RPSYSTEM_EMOTE_PREFIX', "/")
|
||||
_PREFIX = getattr(settings, "RPSYSTEM_EMOTE_PREFIX", "/")
|
||||
|
||||
# The num_sep is the (single-character) symbol used to separate the
|
||||
# sdesc from the number when trying to separate identical sdescs from
|
||||
|
|
@ -1317,145 +1311,25 @@ class ContribRPObject(DefaultObject):
|
|||
self.db.pose_default = "is here."
|
||||
self.db._sdesc = ""
|
||||
|
||||
def search(
|
||||
def get_search_result(
|
||||
self,
|
||||
searchdata,
|
||||
global_search=False,
|
||||
use_nicks=True,
|
||||
typeclass=None,
|
||||
location=None,
|
||||
attribute_name=None,
|
||||
quiet=False,
|
||||
exact=False,
|
||||
typeclass=None,
|
||||
candidates=None,
|
||||
nofound_string=None,
|
||||
multimatch_string=None,
|
||||
exact=False,
|
||||
use_dbref=None,
|
||||
tags=None,
|
||||
**kwargs,
|
||||
):
|
||||
"""
|
||||
Returns an Object matching a search string/condition, taking
|
||||
sdescs into account.
|
||||
|
||||
Perform a standard object search in the database, handling
|
||||
multiple results and lack thereof gracefully. By default, only
|
||||
objects in the current `location` of `self` or its inventory are searched for.
|
||||
|
||||
Args:
|
||||
searchdata (str or obj): Primary search criterion. Will be matched
|
||||
against `object.key` (with `object.aliases` second) unless
|
||||
the keyword attribute_name specifies otherwise.
|
||||
**Special strings:**
|
||||
- `#<num>`: search by unique dbref. This is always
|
||||
a global search.
|
||||
- `me,self`: self-reference to this object
|
||||
- `<num>-<string>` - can be used to differentiate
|
||||
between multiple same-named matches
|
||||
global_search (bool): Search all objects globally. This is overruled
|
||||
by `location` keyword.
|
||||
use_nicks (bool): Use nickname-replace (nicktype "object") on `searchdata`.
|
||||
typeclass (str or Typeclass, or list of either): Limit search only
|
||||
to `Objects` with this typeclass. May be a list of typeclasses
|
||||
for a broader search.
|
||||
location (Object or list): Specify a location or multiple locations
|
||||
to search. Note that this is used to query the *contents* of a
|
||||
location and will not match for the location itself -
|
||||
if you want that, don't set this or use `candidates` to specify
|
||||
exactly which objects should be searched.
|
||||
attribute_name (str): Define which property to search. If set, no
|
||||
key+alias search will be performed. This can be used
|
||||
to search database fields (db_ will be automatically
|
||||
appended), and if that fails, it will try to return
|
||||
objects having Attributes with this name and value
|
||||
equal to searchdata. A special use is to search for
|
||||
"key" here if you want to do a key-search without
|
||||
including aliases.
|
||||
quiet (bool): don't display default error messages - this tells the
|
||||
search method that the user wants to handle all errors
|
||||
themselves. It also changes the return value type, see
|
||||
below.
|
||||
exact (bool): if unset (default) - prefers to match to beginning of
|
||||
string rather than not matching at all. If set, requires
|
||||
exact matching of entire string.
|
||||
candidates (list of objects): this is an optional custom list of objects
|
||||
to search (filter) between. It is ignored if `global_search`
|
||||
is given. If not set, this list will automatically be defined
|
||||
to include the location, the contents of location and the
|
||||
caller's contents (inventory).
|
||||
nofound_string (str): optional custom string for not-found error message.
|
||||
multimatch_string (str): optional custom string for multimatch error header.
|
||||
use_dbref (bool or None): If None, only turn off use_dbref if we are of a lower
|
||||
permission than Builder. Otherwise, honor the True/False value.
|
||||
|
||||
Returns:
|
||||
match (Object, None or list): will return an Object/None if `quiet=False`,
|
||||
otherwise it will return a list of 0, 1 or more matches.
|
||||
|
||||
Notes:
|
||||
To find Accounts, use eg. `evennia.account_search`. If
|
||||
`quiet=False`, error messages will be handled by
|
||||
`settings.SEARCH_AT_RESULT` and echoed automatically (on
|
||||
error, return will be `None`). If `quiet=True`, the error
|
||||
messaging is assumed to be handled by the caller.
|
||||
Override of the parent method for producing search results that understands sdescs.
|
||||
These are used in the main .search() method of the parent class.
|
||||
|
||||
"""
|
||||
is_string = isinstance(searchdata, str)
|
||||
|
||||
if is_string:
|
||||
# searchdata is a string; wrap some common self-references
|
||||
if searchdata.lower() in ("here",):
|
||||
return [self.location] if quiet else self.location
|
||||
if searchdata.lower() in ("me", "self"):
|
||||
return [self] if quiet else self
|
||||
|
||||
if use_nicks:
|
||||
# do nick-replacement on search
|
||||
searchdata = self.nicks.nickreplace(
|
||||
searchdata, categories=("object", "account"), include_account=True
|
||||
)
|
||||
|
||||
if global_search or (
|
||||
is_string
|
||||
and searchdata.startswith("#")
|
||||
and len(searchdata) > 1
|
||||
and searchdata[1:].isdigit()
|
||||
):
|
||||
# only allow exact matching if searching the entire database
|
||||
# or unique #dbrefs
|
||||
exact = True
|
||||
elif candidates is None:
|
||||
# no custom candidates given - get them automatically
|
||||
if location:
|
||||
# location(s) were given
|
||||
candidates = []
|
||||
for obj in make_iter(location):
|
||||
candidates.extend(obj.contents)
|
||||
else:
|
||||
# local search. Candidates are taken from
|
||||
# self.contents, self.location and
|
||||
# self.location.contents
|
||||
location = self.location
|
||||
candidates = self.contents
|
||||
if location:
|
||||
candidates = candidates + [location] + location.contents
|
||||
else:
|
||||
# normally we don't need this since we are
|
||||
# included in location.contents
|
||||
candidates.append(self)
|
||||
|
||||
# the sdesc-related substitution
|
||||
# we also want to use the default search method
|
||||
search_obj = super().get_search_result
|
||||
is_builder = self.locks.check_lockstring(self, "perm(Builder)")
|
||||
use_dbref = is_builder if use_dbref is None else use_dbref
|
||||
|
||||
def search_obj(string):
|
||||
"helper wrapper for searching"
|
||||
return ObjectDB.objects.object_search(
|
||||
string,
|
||||
attribute_name=attribute_name,
|
||||
typeclass=typeclass,
|
||||
candidates=candidates,
|
||||
exact=exact,
|
||||
use_dbref=use_dbref,
|
||||
)
|
||||
|
||||
if candidates:
|
||||
candidates = parse_sdescs_and_recogs(
|
||||
|
|
@ -1478,16 +1352,7 @@ class ContribRPObject(DefaultObject):
|
|||
# only done in code, so is controlled, #dbrefs are turned off
|
||||
# for non-Builders.
|
||||
results = search_obj(searchdata)
|
||||
|
||||
if quiet:
|
||||
return results
|
||||
return _AT_SEARCH_RESULT(
|
||||
results,
|
||||
self,
|
||||
query=searchdata,
|
||||
nofound_string=nofound_string,
|
||||
multimatch_string=multimatch_string,
|
||||
)
|
||||
return results
|
||||
|
||||
def get_posed_sdesc(self, sdesc, **kwargs):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -5,8 +5,7 @@ Tests for RP system
|
|||
import time
|
||||
|
||||
from anything import Anything
|
||||
|
||||
from evennia import create_object
|
||||
from evennia import DefaultObject, create_object, default_cmds
|
||||
from evennia.commands.default.tests import BaseEvenniaCommandTest
|
||||
from evennia.utils.test_resources import BaseEvenniaTest
|
||||
|
||||
|
|
@ -157,12 +156,11 @@ class TestRPSystem(BaseEvenniaTest):
|
|||
self.assertEqual(
|
||||
rpsystem.parse_language(self.speaker, language_emote),
|
||||
(
|
||||
'For a change of pace, /me says, {##0}',
|
||||
{"##0": ('elvish', '"This is in elvish!"')},
|
||||
"For a change of pace, /me says, {##0}",
|
||||
{"##0": ("elvish", '"This is in elvish!"')},
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def test_parse_sdescs_and_recogs(self):
|
||||
speaker = self.speaker
|
||||
speaker.sdesc.add(sdesc0)
|
||||
|
|
@ -251,18 +249,24 @@ class TestRPSystem(BaseEvenniaTest):
|
|||
rpsystem.send_emote(speaker, receivers, emote, case_sensitive=False)
|
||||
self.assertEqual(
|
||||
self.out0[0],
|
||||
"With a flair, |mSender|n looks at |bThe first receiver of emotes.|n "
|
||||
'and |bAnother nice colliding sdesc-guy for tests|n. She says |w"This is a test."|n',
|
||||
(
|
||||
"With a flair, |mSender|n looks at |bThe first receiver of emotes.|n "
|
||||
'and |bAnother nice colliding sdesc-guy for tests|n. She says |w"This is a test."|n'
|
||||
),
|
||||
)
|
||||
self.assertEqual(
|
||||
self.out1[0],
|
||||
"With a flair, |bA nice sender of emotes|n looks at |mReceiver1|n and "
|
||||
'|bAnother nice colliding sdesc-guy for tests|n. She says |w"This is a test."|n',
|
||||
(
|
||||
"With a flair, |bA nice sender of emotes|n looks at |mReceiver1|n and "
|
||||
'|bAnother nice colliding sdesc-guy for tests|n. She says |w"This is a test."|n'
|
||||
),
|
||||
)
|
||||
self.assertEqual(
|
||||
self.out2[0],
|
||||
"With a flair, |bA nice sender of emotes|n looks at |bThe first "
|
||||
'receiver of emotes.|n and |mReceiver2|n. She says |w"This is a test."|n',
|
||||
(
|
||||
"With a flair, |bA nice sender of emotes|n looks at |bThe first "
|
||||
'receiver of emotes.|n and |mReceiver2|n. She says |w"This is a test."|n'
|
||||
),
|
||||
)
|
||||
|
||||
def test_send_emote_fallback(self):
|
||||
|
|
@ -287,8 +291,10 @@ class TestRPSystem(BaseEvenniaTest):
|
|||
)
|
||||
self.assertEqual(
|
||||
self.out2[0],
|
||||
"|bA nice sender of emotes|n is distracted from |bthe first receiver of emotes.|n by"
|
||||
" something.",
|
||||
(
|
||||
"|bA nice sender of emotes|n is distracted from |bthe first receiver of emotes.|n"
|
||||
" by something."
|
||||
),
|
||||
)
|
||||
|
||||
def test_send_case_sensitive_emote(self):
|
||||
|
|
@ -306,21 +312,27 @@ class TestRPSystem(BaseEvenniaTest):
|
|||
rpsystem.send_emote(speaker, receivers, case_emote)
|
||||
self.assertEqual(
|
||||
self.out0[0],
|
||||
"|mSender|n looks at |bthe first receiver of emotes.|n. Then, |mSender|n "
|
||||
"looks at |bTHE FIRST RECEIVER OF EMOTES.|n, |bThe first receiver of emotes.|n "
|
||||
"and |bAnother nice colliding sdesc-guy for tests|n twice.",
|
||||
(
|
||||
"|mSender|n looks at |bthe first receiver of emotes.|n. Then, |mSender|n "
|
||||
"looks at |bTHE FIRST RECEIVER OF EMOTES.|n, |bThe first receiver of emotes.|n "
|
||||
"and |bAnother nice colliding sdesc-guy for tests|n twice."
|
||||
),
|
||||
)
|
||||
self.assertEqual(
|
||||
self.out1[0],
|
||||
"|bA nice sender of emotes|n looks at |mReceiver1|n. Then, "
|
||||
"|ba nice sender of emotes|n looks at |mReceiver1|n, |mReceiver1|n "
|
||||
"and |bAnother nice colliding sdesc-guy for tests|n twice.",
|
||||
(
|
||||
"|bA nice sender of emotes|n looks at |mReceiver1|n. Then, "
|
||||
"|ba nice sender of emotes|n looks at |mReceiver1|n, |mReceiver1|n "
|
||||
"and |bAnother nice colliding sdesc-guy for tests|n twice."
|
||||
),
|
||||
)
|
||||
self.assertEqual(
|
||||
self.out2[0],
|
||||
"|bA nice sender of emotes|n looks at |bthe first receiver of emotes.|n. "
|
||||
"Then, |ba nice sender of emotes|n looks at |bTHE FIRST RECEIVER OF EMOTES.|n, "
|
||||
"|bThe first receiver of emotes.|n and |mReceiver2|n twice.",
|
||||
(
|
||||
"|bA nice sender of emotes|n looks at |bthe first receiver of emotes.|n. "
|
||||
"Then, |ba nice sender of emotes|n looks at |bTHE FIRST RECEIVER OF EMOTES.|n, "
|
||||
"|bThe first receiver of emotes.|n and |mReceiver2|n twice."
|
||||
),
|
||||
)
|
||||
|
||||
def test_rpsearch(self):
|
||||
|
|
@ -371,8 +383,10 @@ class TestRPSystemCommands(BaseEvenniaCommandTest):
|
|||
self.call(
|
||||
rpsystem.CmdRecog(),
|
||||
"",
|
||||
"Currently recognized (use 'recog <sdesc> as <alias>' to add new "
|
||||
"and 'forget <alias>' to remove):\n friend (BarFoo Character)",
|
||||
(
|
||||
"Currently recognized (use 'recog <sdesc> as <alias>' to add new "
|
||||
"and 'forget <alias>' to remove):\n friend (BarFoo Character)"
|
||||
),
|
||||
)
|
||||
self.call(
|
||||
rpsystem.CmdRecog(),
|
||||
|
|
@ -382,3 +396,31 @@ class TestRPSystemCommands(BaseEvenniaCommandTest):
|
|||
)
|
||||
|
||||
self.call(rpsystem.CmdSdesc(), "clear", 'Cleared sdesc, using name "Char".', inputs=["Y"])
|
||||
|
||||
def test_multi_match_search(self):
|
||||
"""
|
||||
Test that the multi-match search works as expected
|
||||
|
||||
"""
|
||||
mushroom1 = create_object(rpsystem.ContribRPObject, key="Mushroom", location=self.room1)
|
||||
mushroom1.db.desc = "The first mushroom is brown."
|
||||
mushroom2 = create_object(rpsystem.ContribRPObject, key="Mushroom", location=self.room1)
|
||||
mushroom2.db.desc = "The second mushroom is red."
|
||||
|
||||
# check locations and contents
|
||||
self.assertEqual(self.char1.location, self.room1)
|
||||
self.assertTrue(set(self.room1.contents).intersection(set([mushroom1, mushroom2])))
|
||||
|
||||
expected_first_call = [
|
||||
"More than one match for 'Mushroom' (please narrow target):",
|
||||
f" Mushroom({mushroom1.dbref})-1 []",
|
||||
f" Mushroom({mushroom2.dbref})-2 []",
|
||||
]
|
||||
|
||||
self.call(default_cmds.CmdLook(), "Mushroom", "\n".join(expected_first_call)) # PASSES
|
||||
|
||||
expected_second_call = f"Mushroom({mushroom1.dbref})\nThe first mushroom is brown."
|
||||
self.call(default_cmds.CmdLook(), "Mushroom-1", expected_second_call) # FAILS
|
||||
|
||||
expected_third_call = f"Mushroom({mushroom2.dbref})\nThe second mushroom is red."
|
||||
self.call(default_cmds.CmdLook(), "Mushroom-2", expected_third_call) # FAILS
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue