change how regex is used

also a couple other fixes i found; still needs some cleanup
This commit is contained in:
InspectorCaracal 2022-04-21 18:44:48 -06:00 committed by GitHub
parent 4127297a3a
commit 969e9d9ae5

View file

@ -151,12 +151,13 @@ Extra Installation Instructions:
import re import re
from re import escape as re_escape from re import escape as re_escape
import itertools import itertools
from string import punctuation
from django.conf import settings from django.conf import settings
from evennia.objects.objects import DefaultObject, DefaultCharacter from evennia.objects.objects import DefaultObject, DefaultCharacter
from evennia.objects.models import ObjectDB from evennia.objects.models import ObjectDB
from evennia.commands.command import Command from evennia.commands.command import Command
from evennia.commands.cmdset import CmdSet from evennia.commands.cmdset import CmdSet
from evennia.utils import ansi from evennia.utils import ansi, logger
from evennia.utils.utils import lazy_property, make_iter, variable_from_module from evennia.utils.utils import lazy_property, make_iter, variable_from_module
_REGEX_TUPLE_CACHE = {} _REGEX_TUPLE_CACHE = {}
@ -430,24 +431,11 @@ def parse_sdescs_and_recogs(sender, candidates, string, search_mode=False, case_
- says, "..." are - says, "..." are
""" """
# Load all candidate regex tuples [(regex, obj, sdesc/recog),...] candidate_map = [(sender, 'me')]
candidate_regexes = ( candidate_map += [ (obj, sender.recog.get(obj)) for obj in candidates if sender.recog.get(obj)] if hasattr(sender, "recog") else []
([(_RE_SELF_REF, sender, sender.sdesc.get())] if hasattr(sender, "sdesc") else []) candidate_map += [ (obj, obj.sdesc.get()) for obj in candidates if hasattr(obj, "sdesc") ]
+ ( for obj in candidates:
[sender.recog.get_regex_tuple(obj) for obj in candidates] candidate_map += [(obj, obj.key)] + [(obj, alias) for alias in obj.aliases.all()]
if hasattr(sender, "recog")
else []
)
+ [obj.sdesc.get_regex_tuple() for obj in candidates if hasattr(obj, "sdesc")]
+ [
regex_tuple_from_key_alias(obj) # handle objects without sdescs
for obj in candidates
if not hasattr(obj, "recog") and not hasattr(obj, "sdesc")
]
)
# filter out non-found data
candidate_regexes = [tup for tup in candidate_regexes if tup]
# escape mapping syntax on the form {#id} if it exists already in emote, # escape mapping syntax on the form {#id} if it exists already in emote,
# if so it is replaced with just "id". # if so it is replaced with just "id".
@ -469,17 +457,29 @@ def parse_sdescs_and_recogs(sender, candidates, string, search_mode=False, case_
# first see if there is a number given (e.g. 1-tall) # first see if there is a number given (e.g. 1-tall)
num_identifier, _ = marker_match.groups("") # return "" if no match, rather than None num_identifier, _ = marker_match.groups("") # return "" if no match, rather than None
istart0 = marker_match.start() istart0 = marker_match.start()
istart = istart0 istart = istart0 + 1
# loop over all candidate regexes and match against the string following the match if search_mode:
matches = ((reg.match(string[istart:]), obj, text) for reg, obj, text in candidate_regexes) rquery = "".join([r"\b(" + re.escape(word.strip(punctuation)) + r").*" for word in iter(string[istart:].split())])
rquery = re.compile(rquery, _RE_FLAGS)
# score matches by how long part of the string was matched matches = ((rquery.search(text), obj, text) for obj, text in candidate_map)
matches = [(match.end() if match else -1, obj, text) for match, obj, text in matches] bestmatches = [(obj, match.group()) for match, obj, text in matches if match]
maxscore = max(score for score, obj, text in matches)
else:
word_list = []
bestmatches = []
for next_word in iter(string[istart:].split()):
word_list.append(next_word.strip(punctuation))
rquery = "".join([r"\b(" + re.escape(word) + r").*" for word in word_list])
rquery = re.compile(rquery, _RE_FLAGS)
matches = ((rquery.search(text), obj, text) for obj, text in candidate_map)
matches = [(obj, match.group()) for match, obj, text in matches if match]
if len(matches) == 0:
# no matches at this length, keep previous iteration
break
# set latest match set as best matches
bestmatches = matches
# we have a valid maxscore, extract all matches with this value # we have a valid maxscore, extract all matches with this value
bestmatches = [(obj, text) for score, obj, text in matches if maxscore == score != -1]
nmatches = len(bestmatches) nmatches = len(bestmatches)
if not nmatches: if not nmatches:
@ -488,12 +488,11 @@ def parse_sdescs_and_recogs(sender, candidates, string, search_mode=False, case_
nmatches = 0 nmatches = 0
elif nmatches == 1: elif nmatches == 1:
# an exact match. # an exact match.
obj = bestmatches[0][0] obj, match_str = bestmatches[0]
nmatches = 1
elif all(bestmatches[0][0].id == obj.id for obj, text in bestmatches): elif all(bestmatches[0][0].id == obj.id for obj, text in bestmatches):
# multi-match but all matches actually reference the same # multi-match but all matches actually reference the same
# obj (could happen with clashing recogs + sdescs) # obj (could happen with clashing recogs + sdescs)
obj = bestmatches[0][0] obj, match_str = bestmatches[0]
nmatches = 1 nmatches = 1
else: else:
# multi-match. # multi-match.
@ -501,7 +500,7 @@ def parse_sdescs_and_recogs(sender, candidates, string, search_mode=False, case_
inum = min(max(0, int(num_identifier) - 1), nmatches - 1) if num_identifier else None inum = min(max(0, int(num_identifier) - 1), nmatches - 1) if num_identifier else None
if inum is not None: if inum is not None:
# A valid inum is given. Use this to separate data. # A valid inum is given. Use this to separate data.
obj = bestmatches[inum][0] obj, match_str = bestmatches[inum]
nmatches = 1 nmatches = 1
else: else:
# no identifier given - a real multimatch. # no identifier given - a real multimatch.
@ -522,19 +521,16 @@ def parse_sdescs_and_recogs(sender, candidates, string, search_mode=False, case_
# - ^ for all upercase input (likle /NAME) # - ^ for all upercase input (likle /NAME)
# - v for lower-case input (like /name) # - v for lower-case input (like /name)
# - ~ for mixed case input (like /nAmE) # - ~ for mixed case input (like /nAmE)
matchtext = marker_match.group() matchtext = marker_match.group().lstrip(_PREFIX)
if not _RE_SELF_REF.match(matchtext): if matchtext.istitle():
# self-refs are kept as-is, others are parsed by case case = "t"
matchtext = marker_match.group().lstrip(_PREFIX) elif matchtext.isupper():
if matchtext.istitle(): case = "^"
case = "t" elif matchtext.islower():
elif matchtext.isupper(): case = "v"
case = "^"
elif matchtext.islower():
case = "v"
key = "#%i%s" % (obj.id, case) key = "#%i%s" % (obj.id, case)
string = string[:istart0] + "{%s}" % key + string[istart + maxscore :] string = string[:istart0] + "{%s}" % key + string[istart + len(match_str) :]
mapping[key] = obj mapping[key] = obj
else: else:
@ -619,14 +615,16 @@ def send_emote(sender, receivers, emote, anonymous_add="first", **kwargs):
# if anonymous_add is passed as a kwarg, collect and remove it from kwargs # if anonymous_add is passed as a kwarg, collect and remove it from kwargs
if "anonymous_add" in kwargs: if "anonymous_add" in kwargs:
anonymous_add = kwargs.pop("anonymous_add") anonymous_add = kwargs.pop("anonymous_add")
if anonymous_add and not any(1 for tag in obj_mapping if tag.startswith(skey)): self_refs = (f"{skey}{ref}" for ref in ('t','^','v','~',''))
if anonymous_add and not any(1 for tag in obj_mapping if tag in self_refs):
# no self-reference in the emote - add to the end # no self-reference in the emote - add to the end
obj_mapping[skey] = sender
if anonymous_add == "first": if anonymous_add == "first":
skey = skey + 't'
possessive = "" if emote.startswith("'") else " " possessive = "" if emote.startswith("'") else " "
emote = "%s%s%s" % ("{{%s}}" % skey, possessive, emote) emote = "%s%s%s" % ("{{%s}}" % skey, possessive, emote)
else: else:
emote = "%s [%s]" % (emote, "{{%s}}" % skey) emote = "%s [%s]" % (emote, "{{%s}}" % skey)
obj_mapping[skey] = sender
# broadcast emote to everyone # broadcast emote to everyone
for receiver in receivers: for receiver in receivers:
@ -684,7 +682,6 @@ class SdescHandler:
""" """
self.obj = obj self.obj = obj
self.sdesc = "" self.sdesc = ""
self.sdesc_regex = ""
self._cache() self._cache()
def _cache(self): def _cache(self):
@ -692,11 +689,6 @@ class SdescHandler:
Cache data from storage Cache data from storage
""" """
self.sdesc = self.obj.attributes.get("_sdesc", default=self.obj.key) self.sdesc = self.obj.attributes.get("_sdesc", default=self.obj.key)
sdesc_regex = self.obj.attributes.get("_sdesc_regex", default="")
if not sdesc_regex:
permutation_string = " ".join([self.obj.key] + self.obj.aliases.all())
sdesc_regex = ordered_permutation_regex(permutation_string)
self.sdesc_regex = re.compile(sdesc_regex, _RE_FLAGS)
def add(self, sdesc, max_length=60): def add(self, sdesc, max_length=60):
""" """
@ -737,12 +729,9 @@ class SdescHandler:
) )
# store to attributes # store to attributes
sdesc_regex = ordered_permutation_regex(cleaned_sdesc)
self.obj.attributes.add("_sdesc", sdesc) self.obj.attributes.add("_sdesc", sdesc)
self.obj.attributes.add("_sdesc_regex", sdesc_regex)
# local caching # local caching
self.sdesc = sdesc self.sdesc = sdesc
self.sdesc_regex = re.compile(sdesc_regex, _RE_FLAGS)
return sdesc return sdesc
@ -881,10 +870,10 @@ class RecogHandler:
# check an eventual recog_masked lock on the object # check an eventual recog_masked lock on the object
# to avoid revealing masked characters. If lock # to avoid revealing masked characters. If lock
# does not exist, pass automatically. # does not exist, pass automatically.
return self.obj2recog.get(obj, obj.sdesc.get() if hasattr(obj, "sdesc") else obj.key) return self.obj2recog.get(obj, None)
else: else:
# recog_mask log not passed, disable recog # recog_mask lock not passed, disable recog
return obj.sdesc.get() if hasattr(obj, "sdesc") else obj.key return None
def all(self): def all(self):
""" """
@ -998,7 +987,6 @@ class CmdSay(RPCommand): # replaces standard say
# calling the speech modifying hook # calling the speech modifying hook
speech = caller.at_pre_say(self.args) speech = caller.at_pre_say(self.args)
# preparing the speech with sdesc/speech parsing.
targets = self.caller.location.contents targets = self.caller.location.contents
send_emote(self.caller, targets, speech, anonymous_add=None) send_emote(self.caller, targets, speech, anonymous_add=None)
@ -1651,8 +1639,8 @@ class ContribRPCharacter(DefaultCharacter, ContribRPObject):
""" """
if kwargs.get("whisper"): if kwargs.get("whisper"):
return f'/me whispers "{message}"' return f'/Me whispers "{message}"'
return f'/me says, "{message}"' return f'/Me says, "{message}"'
def get_sdesc(self, obj, process=False, **kwargs): def get_sdesc(self, obj, process=False, **kwargs):
""" """