Removed SEARCH_AT_MULTIMATCH_INPUT and SEARCH_AT_MULTIMATCH_CMD settings and connected functions - these are no longer individually overloadable. SEARCH_AT_RESULT function now handles all error reporting. Also added SEARCH_MULTIMATCH_SEPARATOR to make it easy to replace the character used to separate multi-matches (1-box, 2-box is using '-' by default), in response to #795. Also moved the default SEARCH_AT_RESULT function from the cmdparser to evennia.utils.utils.
This commit is contained in:
parent
5429ede5f7
commit
2743f98fb0
10 changed files with 116 additions and 199 deletions
|
|
@ -71,7 +71,7 @@ CMD_CHANNEL = "__send_to_channel_command"
|
|||
CMD_LOGINSTART = "__unloggedin_look_command"
|
||||
|
||||
# Function for handling multiple command matches.
|
||||
_AT_MULTIMATCH_CMD = utils.variable_from_module(*settings.SEARCH_AT_MULTIMATCH_CMD.rsplit('.', 1))
|
||||
_SEARCH_AT_RESULT = utils.variable_from_module(*settings.SEARCH_AT_RESULT.rsplit('.', 1))
|
||||
|
||||
# Output strings
|
||||
|
||||
|
|
@ -488,7 +488,7 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess
|
|||
syscmd.matches = matches
|
||||
else:
|
||||
# fall back to default error handling
|
||||
sysarg = yield _AT_MULTIMATCH_CMD(caller, matches)
|
||||
sysarg = yield _SEARCH_AT_RESULT([match[2] for match in matches], caller, query=match[0])
|
||||
raise ExecSystemCommand(syscmd, sysarg)
|
||||
|
||||
if len(matches) == 1:
|
||||
|
|
|
|||
|
|
@ -6,9 +6,13 @@ same inputs as the default one.
|
|||
|
||||
"""
|
||||
|
||||
from django.utils.translation import ugettext as _
|
||||
import re
|
||||
from django.conf import settings
|
||||
from evennia.utils.logger import log_trace
|
||||
|
||||
_MULTIMATCH_SEPARATOR = settings.SEARCH_MULTIMATCH_SEPARATOR
|
||||
_MULTIMATCH_REGEX = re.compile(r"([0-9]+)%s(.*)" % _MULTIMATCH_SEPARATOR, re.I + re.U)
|
||||
|
||||
def cmdparser(raw_string, cmdset, caller, match_index=None):
|
||||
"""
|
||||
This function is called by the cmdhandler once it has
|
||||
|
|
@ -83,16 +87,14 @@ def cmdparser(raw_string, cmdset, caller, match_index=None):
|
|||
log_trace("cmdhandler error. raw_input:%s" % raw_string)
|
||||
|
||||
if not matches:
|
||||
# no matches found.
|
||||
if '-' in raw_string:
|
||||
# This could be due to the user trying to identify the
|
||||
# command with a #num-<command> style syntax.
|
||||
mindex, new_raw_string = raw_string.split("-", 1)
|
||||
if mindex.isdigit():
|
||||
mindex = int(mindex) - 1
|
||||
# feed result back to parser iteratively
|
||||
return cmdparser(new_raw_string, cmdset,
|
||||
caller, match_index=mindex)
|
||||
# no matches found
|
||||
num_ref_match = _MULTIMATCH_REGEX.match(raw_string)
|
||||
if num_ref_match:
|
||||
# the user might be trying to identify the command
|
||||
# with a #num-command style syntax.
|
||||
mindex, new_raw_string = num_ref_match.groups()
|
||||
return cmdparser(new_raw_string, cmdset,
|
||||
caller, match_index=int(mindex))
|
||||
|
||||
# only select command matches we are actually allowed to call.
|
||||
matches = [match for match in matches if match[2].access(caller, 'cmd')]
|
||||
|
|
@ -127,161 +129,3 @@ def cmdparser(raw_string, cmdset, caller, match_index=None):
|
|||
# no matter what we have at this point, we have to return it.
|
||||
return matches
|
||||
|
||||
#------------------------------------------------------------
|
||||
# Search parsers and support methods
|
||||
#------------------------------------------------------------
|
||||
#
|
||||
# Default functions for formatting and processing searches.
|
||||
#
|
||||
# You can replace these from the settings file by setting the variables
|
||||
#
|
||||
# SEARCH_AT_RESULT
|
||||
# SEARCH_AT_MULTIMATCH_INPUT
|
||||
# SEARCH_AT_MULTIMATCH_CMD
|
||||
#
|
||||
# The the replacing functions must have the same inputs and outputs as
|
||||
# those in this module.
|
||||
#
|
||||
def at_search_result(msg_obj, ostring, results, global_search=False,
|
||||
nofound_string=None, multimatch_string=None, quiet=False):
|
||||
"""
|
||||
Called by search methods after a result of any type has been found.
|
||||
Takes a search result (a list) and formats eventual errors.
|
||||
|
||||
Args:
|
||||
msg_obj (Object): Object to receive feedback.
|
||||
ostring (str): Original search string
|
||||
results (list): List of found matches (0, 1 or more)
|
||||
global_search (bool, optional): I this was a global_search or not (if it
|
||||
is, there might be an idea of supplying dbrefs instead of only
|
||||
numbers)
|
||||
nofound_string (str, optional): Custom string for not-found error message.
|
||||
multimatch_string (str, optional): Custom string for multimatch error header
|
||||
quiet (bool, optional): Work normally, but don't echo to caller, just return the
|
||||
results.
|
||||
|
||||
Returns:
|
||||
result (Object or None): The filtered object. If None, it suggests a
|
||||
nofound/multimatch error and the error message was sent directly to `msg_obj`. If
|
||||
the `multimatch_strin` was not given, the multimatch error will be returned as
|
||||
|
||||
```
|
||||
1-object
|
||||
2-object
|
||||
3-object
|
||||
etc
|
||||
```
|
||||
|
||||
"""
|
||||
string = ""
|
||||
if not results:
|
||||
# no results.
|
||||
if nofound_string:
|
||||
# custom return string
|
||||
string = nofound_string
|
||||
else:
|
||||
string = _("Could not find '%s'." % ostring)
|
||||
results = None
|
||||
|
||||
elif len(results) > 1:
|
||||
# we have more than one match. We will display a
|
||||
# list of the form 1-objname, 2-objname etc.
|
||||
|
||||
if multimatch_string:
|
||||
# custom header
|
||||
string = multimatch_string
|
||||
else:
|
||||
string = "More than one match for '%s'" % ostring
|
||||
string += " (please narrow target):"
|
||||
string = _(string)
|
||||
|
||||
for num, result in enumerate(results):
|
||||
string += "\n %i-%s%s" % (num + 1, result.name, result.get_extra_info(msg_obj))
|
||||
results = None
|
||||
else:
|
||||
# we have exactly one match.
|
||||
results = results[0]
|
||||
|
||||
if string and not quiet:
|
||||
msg_obj.msg(string.strip())
|
||||
return results
|
||||
|
||||
|
||||
def at_multimatch_input(ostring):
|
||||
"""
|
||||
Parse number-identifiers.
|
||||
|
||||
This parser will be called by the engine when a user supplies
|
||||
a search term. The search term must be analyzed to determine
|
||||
if the user wants to differentiate between multiple matches
|
||||
(usually found during a previous search).
|
||||
|
||||
Args:
|
||||
ostring (str): The search criterion. The parser will specifically
|
||||
understand input on a form like `2-object` to separate
|
||||
multimatches from each other.
|
||||
|
||||
Returns:
|
||||
selection (tuple): This is on the form (index, ostring).
|
||||
|
||||
Notes:
|
||||
This method should separate out any identifiers from the search
|
||||
string used to differentiate between same-named objects. The
|
||||
result should be a tuple (index, search_string) where the index
|
||||
gives which match among multiple matches should be used (1 being
|
||||
the lowest number, rather than 0 as in Python).
|
||||
|
||||
This will be parsed to (2, "object") and, if applicable, will tell
|
||||
the engine to pick the second from a list of same-named matches of
|
||||
objects called "object".
|
||||
|
||||
Example:
|
||||
> look
|
||||
You see: ball, ball, ball and ball.
|
||||
> get ball
|
||||
There where multiple matches for ball:
|
||||
1-ball
|
||||
2-ball
|
||||
3-ball
|
||||
4-ball
|
||||
> get 3-ball
|
||||
You get the ball.
|
||||
|
||||
"""
|
||||
|
||||
if not isinstance(ostring, basestring):
|
||||
return (None, ostring)
|
||||
if not '-' in ostring:
|
||||
return (None, ostring)
|
||||
try:
|
||||
index = ostring.find('-')
|
||||
number = int(ostring[:index]) - 1
|
||||
return (number, ostring[index + 1:])
|
||||
except ValueError:
|
||||
#not a number; this is not an identifier.
|
||||
return (None, ostring)
|
||||
except IndexError:
|
||||
return (None, ostring)
|
||||
|
||||
|
||||
def at_multimatch_cmd(caller, matches):
|
||||
"""
|
||||
Format multiple command matches to a useful error.
|
||||
|
||||
Args:
|
||||
caller (Object): Calling object.
|
||||
matches (list): A list of matchtuples `(num, Command)`.
|
||||
|
||||
Returns:
|
||||
formatted (str): A nicely formatted string, including
|
||||
eventual errors.
|
||||
|
||||
"""
|
||||
string = "There were multiple matches:"
|
||||
for num, match in enumerate(matches):
|
||||
# each match is a tuple (candidate, cmd)
|
||||
cmdname, arg, cmd, dum, dum = match
|
||||
|
||||
get_extra_info = cmd.get_extra_info(caller)
|
||||
string += "\n %s-%s%s" % (num + 1, cmdname, get_extra_info)
|
||||
return string
|
||||
|
|
|
|||
|
|
@ -400,18 +400,22 @@ class Command(object):
|
|||
|
||||
def get_extra_info(self, caller, **kwargs):
|
||||
"""
|
||||
Display some extra information that may help distinguish this command from others, for instance,
|
||||
in a disambiguity prompt.
|
||||
Display some extra information that may help distinguish this
|
||||
command from others, for instance, in a disambiguity prompt.
|
||||
|
||||
If this command is a potential match in an ambiguous situation, one distinguishing
|
||||
feature may be its attachment to a nearby object, so we include this if available.
|
||||
If this command is a potential match in an ambiguous
|
||||
situation, one distinguishing feature may be its attachment to
|
||||
a nearby object, so we include this if available.
|
||||
|
||||
Args:
|
||||
caller (TypedObject): The caller who typed an ambiguous term handed to the search function.
|
||||
caller (TypedObject): The caller who typed an ambiguous
|
||||
term handed to the search function.
|
||||
|
||||
Returns:
|
||||
A string with identifying information to disambiguate the object, conventionally with a preceding space.
|
||||
A string with identifying information to disambiguate the
|
||||
object, conventionally with a preceding space.
|
||||
|
||||
"""
|
||||
if hasattr(self, 'obj') and self.obj != caller:
|
||||
return " (%s)" % self.obj.get_display_name(caller)
|
||||
return " (%s)" % self.obj.get_display_name(caller).strip()
|
||||
return ""
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
"""
|
||||
Custom manager for Objects.
|
||||
"""
|
||||
import re
|
||||
from itertools import chain
|
||||
from django.db.models import Q
|
||||
from django.conf import settings
|
||||
from django.db.models.fields import exceptions
|
||||
from evennia.typeclasses.managers import TypedObjectManager, TypeclassManager
|
||||
from evennia.typeclasses.managers import returns_typeclass, returns_typeclass_list
|
||||
from evennia.utils import utils
|
||||
from evennia.utils.utils import to_unicode, is_iter, make_iter, string_partial_matching
|
||||
|
||||
__all__ = ("ObjectManager",)
|
||||
|
|
@ -16,12 +16,11 @@ _GA = object.__getattribute__
|
|||
# delayed import
|
||||
_ATTR = None
|
||||
|
||||
_MULTIMATCH_REGEX = re.compile(r"([0-9]+)%s(.*)" %
|
||||
settings.SEARCH_MULTIMATCH_SEPARATOR, re.I + re.U)
|
||||
|
||||
# Try to use a custom way to parse id-tagged multimatches.
|
||||
|
||||
_AT_MULTIMATCH_INPUT = utils.variable_from_module(*settings.SEARCH_AT_MULTIMATCH_INPUT.rsplit('.', 1))
|
||||
|
||||
|
||||
class ObjectDBManager(TypedObjectManager):
|
||||
"""
|
||||
This ObjectManager implements methods for searching
|
||||
|
|
@ -379,9 +378,15 @@ class ObjectDBManager(TypedObjectManager):
|
|||
if not matches:
|
||||
# no matches found - check if we are dealing with N-keyword
|
||||
# query - if so, strip it.
|
||||
match_number, searchdata = _AT_MULTIMATCH_INPUT(searchdata)
|
||||
# run search again, with the exactness set by call
|
||||
match = _MULTIMATCH_REGEX.match(searchdata)
|
||||
match_number = None
|
||||
if match:
|
||||
# strips the number
|
||||
match_number, searchdata = match.groups()
|
||||
match_number = int(match_number) - 1
|
||||
match_number = match_number if match_number >= 0 else None
|
||||
if match_number is not None or not exact:
|
||||
# run search again, with the exactness set by call
|
||||
matches = _searcher(searchdata, candidates, typeclass, exact=exact)
|
||||
|
||||
# deal with result
|
||||
|
|
|
|||
|
|
@ -334,7 +334,8 @@ class DefaultObject(ObjectDB):
|
|||
exact=exact)
|
||||
if quiet:
|
||||
return results
|
||||
return _AT_SEARCH_RESULT(self, searchdata, results, global_search, nofound_string, multimatch_string)
|
||||
return _AT_SEARCH_RESULT(results, self, query=searchdata,
|
||||
nofound_string=nofound_string, multimatch_string=multimatch_string)
|
||||
|
||||
def search_player(self, searchdata, quiet=False):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -458,7 +458,7 @@ class DefaultPlayer(PlayerDB):
|
|||
Notes:
|
||||
Extra keywords are ignored, but are allowed in call in
|
||||
order to make API more consistent with
|
||||
objects.models.TypedObject.search.
|
||||
objects.objects.DefaultObject.search.
|
||||
|
||||
"""
|
||||
# handle me, self and *me, *self
|
||||
|
|
@ -467,7 +467,7 @@ class DefaultPlayer(PlayerDB):
|
|||
if searchdata.lower() in ("me", "*me", "self", "*self",):
|
||||
return self
|
||||
matches = self.__class__.objects.player_search(searchdata)
|
||||
matches = _AT_SEARCH_RESULT(self, searchdata, matches, global_search=True,
|
||||
matches = _AT_SEARCH_RESULT(matches, self, query=searchdata,
|
||||
nofound_string=nofound_string,
|
||||
multimatch_string=multimatch_string)
|
||||
if matches and return_puppet:
|
||||
|
|
|
|||
|
|
@ -699,8 +699,6 @@ def error_check_python_modules():
|
|||
# core modules
|
||||
imp(settings.COMMAND_PARSER)
|
||||
imp(settings.SEARCH_AT_RESULT)
|
||||
imp(settings.SEARCH_AT_MULTIMATCH_INPUT)
|
||||
imp(settings.SEARCH_AT_MULTIMATCH_CMD)
|
||||
imp(settings.CONNECTION_SCREEN_MODULE)
|
||||
#imp(settings.AT_INITIAL_SETUP_HOOK_MODULE, split=False)
|
||||
for path in settings.LOCK_FUNC_MODULES:
|
||||
|
|
|
|||
|
|
@ -236,16 +236,16 @@ CONN_MAX_AGE = 3600 * 7
|
|||
# The command parser module to use. See the default module for which
|
||||
# functions it must implement
|
||||
COMMAND_PARSER = "evennia.commands.cmdparser.cmdparser"
|
||||
# The handler that outputs errors when searching
|
||||
# objects using object.search().
|
||||
SEARCH_AT_RESULT = "evennia.commands.cmdparser.at_search_result"
|
||||
# The parser used in order to separate multiple
|
||||
# object matches (so you can separate between same-named
|
||||
# objects without using dbrefs).
|
||||
SEARCH_AT_MULTIMATCH_INPUT = "evennia.commands.cmdparser.at_multimatch_input"
|
||||
# The parser used in order to separate multiple
|
||||
# command matches (so you can separate between same-named commands)
|
||||
SEARCH_AT_MULTIMATCH_CMD = "evennia.commands.cmdparser.at_multimatch_cmd"
|
||||
# On a multi-match when search objects or commands, the user has the
|
||||
# ability to search again with an index marker that differentiates
|
||||
# the results. If multiple "box" objects are found, they can by
|
||||
# default use 1-box, 2-box etc to refine the search. Below you
|
||||
# can change the index separator character used.
|
||||
SEARCH_MULTIMATCH_SEPARATOR = '-'
|
||||
# The handler that outputs errors when using any API-level search
|
||||
# (not manager methods). This function should correctly report errors
|
||||
# both for command- and object-searches.
|
||||
SEARCH_AT_RESULT = "evennia.utils.utils.at_search_result"
|
||||
# The module holding text strings for the connection screen.
|
||||
# This module should contain one or more variables
|
||||
# with strings defining the look of the screen.
|
||||
|
|
|
|||
|
|
@ -45,6 +45,11 @@ Channel = ContentType.objects.get(app_label="comms", model="channeldb").model_cl
|
|||
HelpEntry = ContentType.objects.get(app_label="help", model="helpentry").model_class()
|
||||
Tag = ContentType.objects.get(app_label="typeclasses", model="tag").model_class()
|
||||
|
||||
|
||||
#------------------------------------------------------------------
|
||||
# Search manager-wrappers
|
||||
#------------------------------------------------------------------
|
||||
|
||||
#
|
||||
# Search objects as a character
|
||||
#
|
||||
|
|
|
|||
|
|
@ -21,6 +21,9 @@ from collections import defaultdict
|
|||
from twisted.internet import threads, defer, reactor
|
||||
from django.conf import settings
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
_MULTIMATCH_SEPARATOR = settings.SEARCH_MULTIMATCH_SEPARATOR
|
||||
|
||||
try:
|
||||
import cPickle as pickle
|
||||
|
|
@ -1226,3 +1229,60 @@ def m_len(target):
|
|||
if inherits_from(target, basestring):
|
||||
return len(ANSI_PARSER.strip_mxp(target))
|
||||
return len(target)
|
||||
|
||||
#------------------------------------------------------------------
|
||||
# Search handler function
|
||||
#------------------------------------------------------------------
|
||||
#
|
||||
# Replace this hook function by changing settings.SEARCH_AT_RESULT.
|
||||
#
|
||||
|
||||
def at_search_result(matches, caller, query="", quiet=False, **kwargs):
|
||||
"""
|
||||
This is a generic hook for handling all processing of a search
|
||||
result, including error reporting.
|
||||
|
||||
Args:
|
||||
matches (list): This is a list of 0, 1 or more typeclass instances,
|
||||
the matched result of the search. If 0, a nomatch error should
|
||||
be echoed, and if >1, multimatch errors should be given. Only
|
||||
if a single match should the result pass through.
|
||||
caller (Object): The object performing the search and/or which should
|
||||
receive error messages.
|
||||
query (str, optional): The search query used to produce `matches`.
|
||||
quiet (bool, optional): If `True`, no messages will be echoed to caller
|
||||
on errors.
|
||||
|
||||
Kwargs:
|
||||
nofound_string (str): Replacement string to echo on a notfound error.
|
||||
multimatch_string (str): Replacement string to echo on a multimatch error.
|
||||
|
||||
Returns:
|
||||
processed_result (Object or None): This is always a single result
|
||||
or `None`. If `None`, any error reporting/handling should
|
||||
already have happened.
|
||||
"""
|
||||
|
||||
error = ""
|
||||
if not matches:
|
||||
# no results.
|
||||
error = kwargs.get("nofound_string", _("Could not find '%s'." % query))
|
||||
matches = None
|
||||
elif len(matches) > 1:
|
||||
error = kwargs.get("multimatch_string", None)
|
||||
if not error:
|
||||
error = _("More than one match for '%s'" \
|
||||
" (please narrow target):" % query)
|
||||
for num, result in enumerate(matches):
|
||||
error += "\n %i%s%s%s" % (
|
||||
num + 1, _MULTIMATCH_SEPARATOR,
|
||||
result.get_display_name(caller) if hasattr(result, "get_display_name") else result.key,
|
||||
result.get_extra_info(caller))
|
||||
matches = None
|
||||
else:
|
||||
# exactly one match
|
||||
matches = matches[0]
|
||||
|
||||
if error and not quiet:
|
||||
caller.msg(error.strip())
|
||||
return matches
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue