mirror of
https://github.com/evennia/evennia.git
synced 2026-03-20 14:56:30 +01:00
250 lines
8.5 KiB
Python
250 lines
8.5 KiB
Python
"""
|
|
The default command parser. Use your own by assigning
|
|
settings.ALTERNATE_PARSER to a Python path to a module containing the
|
|
replacing cmdparser function. The replacement parser must
|
|
return a CommandCandidates object.
|
|
"""
|
|
|
|
def cmdparser(raw_string, cmdset, caller, match_index=None):
|
|
"""
|
|
This function is called by the cmdhandler once it has
|
|
gathered all valid cmdsets for the calling player. raw_string
|
|
is the unparsed text entered by the caller.
|
|
|
|
The cmdparser understand the following command combinations (where
|
|
[] marks optional parts.
|
|
|
|
[cmdname[ cmdname2 cmdname3 ...] [the rest]
|
|
|
|
A command may consist of any number of space-separated words of any
|
|
length, and contain any character.
|
|
|
|
The parser makes use of the cmdset to find command candidates. The
|
|
parser return a list of matches. Each match is a tuple with its
|
|
first three elements being the parsed cmdname (lower case),
|
|
the remaining arguments, and the matched cmdobject from the cmdset.
|
|
"""
|
|
|
|
def create_match(cmdname, string, cmdobj):
|
|
"""
|
|
Evaluates the quality of a match by counting how many chars of cmdname
|
|
matches string (counting from beginning of string). We also calculate
|
|
a ratio from 0-1 describing how much cmdname matches string.
|
|
We return a tuple (cmdname, count, ratio, args, cmdobj).
|
|
|
|
"""
|
|
cmdlen, strlen = len(cmdname), len(string)
|
|
mratio = 1 - (strlen - cmdlen) / (1.0 * strlen)
|
|
args = string[cmdlen:]
|
|
return (cmdname, args, cmdobj, cmdlen, mratio)
|
|
|
|
if not raw_string:
|
|
return None
|
|
|
|
matches = []
|
|
|
|
# match everything that begins with a matching cmdname.
|
|
l_raw_string = raw_string.lower()
|
|
for cmd in cmdset:
|
|
matches.extend([create_match(cmdname, raw_string, cmd)
|
|
for cmdname in [cmd.key] + cmd.aliases
|
|
if cmdname and l_raw_string.startswith(cmdname.lower())])
|
|
|
|
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, match_index=mindex)
|
|
|
|
# only select command matches we are actually allowed to call.
|
|
matches = [match for match in matches if match[2].access(caller, 'cmd')]
|
|
|
|
if len(matches) > 1:
|
|
# see if it helps to analyze the match with preserved case.
|
|
matches = [match for match in matches if raw_string.startswith(match[0])]
|
|
|
|
if len(matches) > 1:
|
|
# we still have multiple matches. Sort them by count quality.
|
|
matches = sorted(matches, key=lambda m: m[3])
|
|
# only pick the matches with highest count quality
|
|
quality = [mat[3] for mat in matches]
|
|
matches = matches[-quality.count(quality[-1]):]
|
|
|
|
if len(matches) > 1:
|
|
# still multiple matches. Fall back to ratio-based quality.
|
|
matches = sorted(matches, key=lambda m: m[4])
|
|
# only pick the highest rated ratio match
|
|
quality = [mat[4] for mat in matches]
|
|
matches = matches[-quality.count(quality[-1]):]
|
|
|
|
if len(matches) > 1 and match_index != None and 0 <= match_index < len(matches):
|
|
# We couldn't separate match by quality, but we have an index argument to
|
|
# tell us which match to use.
|
|
matches = [matches[match_index]]
|
|
|
|
# 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.
|
|
#
|
|
# This is in its own module due to them being possible to
|
|
# replace from the settings file by setting the variables
|
|
#
|
|
# SEARCH_AT_RESULTERROR_HANDLER
|
|
# SEARCH_MULTIMATCH_PARSER
|
|
#
|
|
# The the replacing modules must have the same inputs and outputs as
|
|
# those in this module.
|
|
#
|
|
|
|
def at_search_result(msg_obj, ostring, results, global_search=False):
|
|
"""
|
|
Called by search methods after a result of any type has been found.
|
|
|
|
Takes a search result (a list) and
|
|
formats eventual errors.
|
|
|
|
msg_obj - object to receive feedback.
|
|
ostring - original search string
|
|
results - list of found matches (0, 1 or more)
|
|
global_search - if this was a global_search or not
|
|
(if it is, there might be an idea of supplying
|
|
dbrefs instead of only numbers)
|
|
|
|
Multiple matches are returned to the searching object
|
|
as
|
|
1-object
|
|
2-object
|
|
3-object
|
|
etc
|
|
|
|
"""
|
|
string = ""
|
|
if not results:
|
|
# no results.
|
|
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.
|
|
|
|
# check if the msg_object may se dbrefs
|
|
show_dbref = global_search
|
|
|
|
string += "More than one match for '%s'" % ostring
|
|
string += " (please narrow target):"
|
|
for num, result in enumerate(results):
|
|
invtext = ""
|
|
dbreftext = ""
|
|
if hasattr(result, "location") and result.location == msg_obj:
|
|
invtext = " (carried)"
|
|
if show_dbref:
|
|
dbreftext = "(#%i)" % result.id
|
|
string += "\n %i-%s%s%s" % (num+1, result.name,
|
|
dbreftext, invtext)
|
|
results = None
|
|
else:
|
|
# we have exactly one match.
|
|
results = results[0]
|
|
|
|
if string:
|
|
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).
|
|
|
|
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 parser version will identify search strings on the following
|
|
forms
|
|
|
|
2-object
|
|
|
|
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".
|
|
|
|
Ex for use in a game session:
|
|
|
|
> 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.
|
|
"""
|
|
string = "There where multiple matches:"
|
|
for num, match in enumerate(matches):
|
|
# each match is a tuple (candidate, cmd)
|
|
cmdname, arg, cmd, dum, dum = match
|
|
|
|
is_channel = hasattr(cmd, "is_channel") and cmd.is_channel
|
|
if is_channel:
|
|
is_channel = " (channel)"
|
|
else:
|
|
is_channel = ""
|
|
is_exit = hasattr(cmd, "is_exit") and cmd.is_exit
|
|
if is_exit and cmd.destination:
|
|
is_exit = " (exit to %s)" % cmd.destination
|
|
else:
|
|
is_exit = ""
|
|
|
|
id1 = ""
|
|
id2 = ""
|
|
if not (is_channel or is_exit) and (hasattr(cmd, 'obj') and cmd.obj != caller):
|
|
# the command is defined on some other object
|
|
id1 = "%s-" % cmd.obj.key
|
|
id2 = " (%s-%s)" % (num + 1, cmdname)
|
|
else:
|
|
id1 = "%s-" % (num + 1)
|
|
id2 = ""
|
|
string += "\n %s%s%s%s%s" % (id1, cmdname, id2, is_channel, is_exit)
|
|
return string
|