Make the ContribRPObject.search() fully accept all kwargs of normal obj.search. Resolves #1017.

This commit is contained in:
Griatch 2016-08-19 22:35:33 +02:00
parent f5ba4f6204
commit 89fbc44d15

View file

@ -74,11 +74,13 @@ from builtins import object
import re
from re import escape as re_escape
import itertools
from evennia import DefaultObject, DefaultCharacter
from django.conf import settings
from evennia import DefaultObject, DefaultCharacter, ObjectDB
from evennia import Command, CmdSet
from evennia import ansi
from evennia.utils.utils import lazy_property
from evennia.utils.utils import lazy_property, make_iter, variable_from_module
_AT_SEARCH_RESULT = variable_from_module(*settings.SEARCH_AT_RESULT.rsplit('.', 1))
#------------------------------------------------------------
# Emote parser
#------------------------------------------------------------
@ -1056,67 +1058,149 @@ class ContribRPObject(DefaultObject):
self.db.pose = ""
self.db.pose_default = "is here."
def search(self, searchdata, **kwargs):
def search(self, searchdata,
global_search=False,
use_nicks=True,
typeclass=None,
location=None,
attribute_name=None,
quiet=False,
exact=False,
candidates=None,
nofound_string=None,
multimatch_string=None,
use_dbref=None):
"""
This version of search will pre-parse searchdata for eventual
matches against recogs and sdescs of candidates in the same
location.
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): Search string.
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 mathing 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 Builders. 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:
Recog/sdesc matching is always turned off if the keyword
`global_search` is set or `candidates` are given.
To find Players, use eg. `evennia.player_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.
"""
if (isinstance(searchdata, basestring) and not
(kwargs.get("global_search") or
kwargs.get("candidates"))):
# searchdata is a string; common self-references
is_string = isinstance(searchdata, basestring)
if is_string:
# searchdata is a string; wrap some common self-references
if searchdata.lower() in ("here", ):
return [self.location] if "quiet" in kwargs else self.location
return [self.location] if quiet else self.location
if searchdata.lower() in ("me", "self",):
return [self] if "quiet" in kwargs else self
if searchdata.lower() == self.key.lower():
return [self] if "quiet" in kwargs else self
return [self] if quiet else self
# sdesc/recog matching
candidates = self.location.contents + self.contents
matches = parse_sdescs_and_recogs(self, candidates,
_PREFIX + searchdata, search_mode=True)
nmatches = len(matches)
if nmatches == 1:
return matches[0]
elif nmatches > 1:
# multimatch
reflist = ["%s%s%s (%s%s)" % (inum+1, _NUM_SEP, searchdata, self.recog.get(obj),
" (%s)" % self.key if self == obj else "")
for inum, obj in enumerate(matches)]
self.msg(_EMOTE_MULTIMATCH_ERROR.format(ref=searchdata,reflist="\n ".join(reflist)))
return
# No matches. At this point we can't pass this on to the
# normal search mechanism just like that, since that will lead to a
# security hole in the sdesc lookup: The normal search
# mechanism will search by key+alias, so that means if we
# were to guess a Character's key (or #dbref), their
# object would be returned regardless of their sdesc. So
# we limit the access to the parent search to builders.
# A side effect of this is that all objects searchable
# with this mechanism must be possible to search by sdesc.
if use_nicks:
# do nick-replacement on search
searchdata = self.nicks.nickreplace(searchdata, categories=("object", "player"), include_player=True)
if not self.locks.check_lockstring(self, "perm(Builders)"):
# we block lookup unless we have access to continue
if "nofound_string" in kwargs:
self.msg(kwargs["nofound_string"])
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 not candidates:
# 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:
self.msg("There is no '%s' here." % searchdata)
return
# normally we don't need this since we are
# included in location.contents
candidates.append(self)
# fall back to normal search
return super(ContribRPObject, self).search(searchdata, **kwargs)
# the sdesc-related substitution
if use_dbref is None:
use_dbref = self.locks.check_lockstring(self, "perm(Builders)")
if candidates:
candidates = parse_sdescs_and_recogs(self, candidates,
_PREFIX + searchdata, search_mode=True)
results = []
for candidate in candidates:
results.extend(ObjectDB.objects.object_search(candidate.key,
attribute_name=attribute_name,
typeclass=typeclass,
candidates=candidates,
exact=exact,
use_dbref=use_dbref))
else:
results = ObjectDB.objects.object_search(searchdata,
attribute_name=attribute_name,
typeclass=typeclass,
candidates=candidates,
exact=exact,
use_dbref=use_dbref)
if quiet:
return results
return _AT_SEARCH_RESULT(results, self, query=searchdata,
nofound_string=nofound_string, multimatch_string=multimatch_string)
def get_display_name(self, looker, **kwargs):
"""