Merge conflicts

This commit is contained in:
Griatch 2020-01-17 22:29:07 +01:00
commit de936b60c0
2 changed files with 96 additions and 49 deletions

View file

@ -3,7 +3,7 @@ Building and world design commands
""" """
import re import re
from django.conf import settings from django.conf import settings
from django.db.models import Q from django.db.models import Q, Min, Max
from evennia.objects.models import ObjectDB from evennia.objects.models import ObjectDB
from evennia.locks.lockhandler import LockException from evennia.locks.lockhandler import LockException
from evennia.commands.cmdhandler import get_and_merge_cmdsets from evennia.commands.cmdhandler import get_and_merge_cmdsets
@ -13,6 +13,7 @@ from evennia.utils.utils import (
class_from_module, class_from_module,
get_all_typeclasses, get_all_typeclasses,
variable_from_module, variable_from_module,
dbref,
) )
from evennia.utils.eveditor import EvEditor from evennia.utils.eveditor import EvEditor
from evennia.utils.evmore import EvMore from evennia.utils.evmore import EvMore
@ -2641,7 +2642,7 @@ class CmdFind(COMMAND_DEFAULT_CLASS):
caller = self.caller caller = self.caller
switches = self.switches switches = self.switches
if not self.args: if not self.args or (not self.lhs and not self.rhs):
caller.msg("Usage: find <string> [= low [-high]]") caller.msg("Usage: find <string> [= low [-high]]")
return return
@ -2649,18 +2650,40 @@ class CmdFind(COMMAND_DEFAULT_CLASS):
switches.append("loc") switches.append("loc")
searchstring = self.lhs searchstring = self.lhs
low, high = 1, ObjectDB.objects.all().order_by("-id")[0].id
try:
# Try grabbing the actual min/max id values by database aggregation
qs = ObjectDB.objects.values('id').aggregate(low=Min('id'), high=Max('id'))
low, high = sorted(qs.values())
if not (low and high):
raise ValueError(f"{self.__class__.__name__}: Min and max ID not returned by aggregation; falling back to queryset slicing.")
except Exception as e:
logger.log_trace(e)
# If that doesn't work for some reason (empty DB?), guess the lower
# bound and do a less-efficient query to find the upper.
low, high = 1, ObjectDB.objects.all().order_by("-id").first().id
if self.rhs: if self.rhs:
if "-" in self.rhs: try:
# also support low-high syntax # Check that rhs is either a valid dbref or dbref range
limlist = [part.lstrip("#").strip() for part in self.rhs.split("-", 1)] bounds = tuple(sorted(dbref(x, False) for x in re.split('[-\s]+', self.rhs.strip())))
else:
# otherwise split by space # dbref() will return either a valid int or None
limlist = [part.lstrip("#") for part in self.rhs.split(None, 1)] assert bounds
if limlist and limlist[0].isdigit(): # None should not exist in the bounds list
low = max(low, int(limlist[0])) assert None not in bounds
if len(limlist) > 1 and limlist[1].isdigit():
high = min(high, int(limlist[1])) low = bounds[0]
if len(bounds) > 1:
high = bounds[-1]
except AssertionError:
caller.msg("Invalid dbref range provided (not a number).")
return
except IndexError as e:
logger.log_err(f"{self.__class__.__name__}: Error parsing upper and lower bounds of query.")
logger.log_trace(e)
low = min(low, high) low = min(low, high)
high = max(low, high) high = max(low, high)
@ -2672,7 +2695,6 @@ class CmdFind(COMMAND_DEFAULT_CLASS):
restrictions = ", %s" % (", ".join(self.switches)) restrictions = ", %s" % (", ".join(self.switches))
if is_dbref or is_account: if is_dbref or is_account:
if is_dbref: if is_dbref:
# a dbref search # a dbref search
result = caller.search(searchstring, global_search=True, quiet=True) result = caller.search(searchstring, global_search=True, quiet=True)
@ -2703,7 +2725,7 @@ class CmdFind(COMMAND_DEFAULT_CLASS):
) )
else: else:
# Not an account/dbref search but a wider search; build a queryset. # Not an account/dbref search but a wider search; build a queryset.
# Searchs for key and aliases # Searches for key and aliases
if "exact" in switches: if "exact" in switches:
keyquery = Q(db_key__iexact=searchstring, id__gte=low, id__lte=high) keyquery = Q(db_key__iexact=searchstring, id__gte=low, id__lte=high)
aliasquery = Q( aliasquery = Q(
@ -2729,39 +2751,43 @@ class CmdFind(COMMAND_DEFAULT_CLASS):
id__lte=high, id__lte=high,
) )
results = ObjectDB.objects.filter(keyquery | aliasquery).distinct() # Keep the initial queryset handy for later reuse
nresults = results.count() result_qs = ObjectDB.objects.filter(keyquery | aliasquery).distinct()
nresults = result_qs.count()
if nresults: # Use iterator to minimize memory ballooning on large result sets
# convert result to typeclasses. results = result_qs.iterator()
results = [result for result in results]
if "room" in switches: # Check and see if type filtering was requested; skip it if not
results = [obj for obj in results if inherits_from(obj, ROOM_TYPECLASS)] if any(x in switches for x in ("room", "exit", "char")):
if "exit" in switches: obj_ids = set()
results = [obj for obj in results if inherits_from(obj, EXIT_TYPECLASS)] for obj in results:
if "char" in switches: if ("room" in switches and inherits_from(obj, ROOM_TYPECLASS)) \
results = [obj for obj in results if inherits_from(obj, CHAR_TYPECLASS)] or ("exit" in switches and inherits_from(obj, EXIT_TYPECLASS)) \
nresults = len(results) or ("char" in switches and inherits_from(obj, CHAR_TYPECLASS)):
obj_ids.add(obj.id)
# Filter previous queryset instead of requesting another
filtered_qs = result_qs.filter(id__in=obj_ids).distinct()
nresults = filtered_qs.count()
# Use iterator again to minimize memory ballooning
results = filtered_qs.iterator()
# still results after type filtering? # still results after type filtering?
if nresults: if nresults:
if nresults > 1: if nresults > 1: header = f'{nresults} Matches'
string = "|w%i Matches|n(#%i-#%i%s):" % (nresults, low, high, restrictions) else: header = 'One Match'
string = f"|w{header}|n(#{low}-#{high}{restrictions}):"
res = None
for res in results: for res in results:
string += "\n |g%s - %s|n" % (res.get_display_name(caller), res.path) string += f"\n |g{res.get_display_name(caller)} - {res.path}|n"
if "loc" in self.switches and nresults == 1 and res and getattr(res, 'location', None):
string += f" (|wlocation|n: |g{res.location.get_display_name(caller)}|n)"
else: else:
string = "|wOne Match|n(#%i-#%i%s):" % (low, high, restrictions) string = f"|wNo Matches|n(#{low}-#{high}{restrictions}):"
string += "\n |g%s - %s|n" % ( string += f"\n |RNo matches found for '{searchstring}'|n"
results[0].get_display_name(caller),
results[0].path,
)
if "loc" in self.switches and nresults == 1 and results[0].location:
string += " (|wlocation|n: |g{}|n)".format(
results[0].location.get_display_name(caller)
)
else:
string = "|wMatch|n(#%i-#%i%s):" % (low, high, restrictions)
string += "\n |RNo matches found for '%s'|n" % searchstring
# send result # send result
caller.msg(string.strip()) caller.msg(string.strip())

View file

@ -1038,11 +1038,32 @@ class TestBuilding(CommandTest):
self.call(building.CmdFind(), self.char1.dbref, "Exact dbref match") self.call(building.CmdFind(), self.char1.dbref, "Exact dbref match")
self.call(building.CmdFind(), "*TestAccount", "Match") self.call(building.CmdFind(), "*TestAccount", "Match")
self.call(building.CmdFind(), "/char Obj") self.call(building.CmdFind(), "/char Obj", "No Matches")
self.call(building.CmdFind(), "/room Obj") self.call(building.CmdFind(), "/room Obj", "No Matches")
self.call(building.CmdFind(), "/exit Obj") self.call(building.CmdFind(), "/exit Obj", "No Matches")
self.call(building.CmdFind(), "/exact Obj", "One Match") self.call(building.CmdFind(), "/exact Obj", "One Match")
# Test multitype filtering
with mock.patch('evennia.commands.default.building.CHAR_TYPECLASS', 'evennia.objects.objects.DefaultCharacter'):
self.call(building.CmdFind(), "/char/room Obj", "No Matches")
self.call(building.CmdFind(), "/char/room/exit Char", "2 Matches")
self.call(building.CmdFind(), "/char/room/exit/startswith Cha", "2 Matches")
# Test null search
self.call(building.CmdFind(), "=", "Usage: ")
# Test bogus dbref range with no search term
self.call(building.CmdFind(), "= obj", "Invalid dbref range provided (not a number).")
self.call(building.CmdFind(), "= #1a", "Invalid dbref range provided (not a number).")
# Test valid dbref ranges with no search term
self.call(building.CmdFind(), "=#1", "7 Matches(#1-#7)")
self.call(building.CmdFind(), "=1-2", "2 Matches(#1-#2):")
self.call(building.CmdFind(), "=1 - 2", "2 Matches(#1-#2):")
self.call(building.CmdFind(), "=1- #2", "2 Matches(#1-#2):")
self.call(building.CmdFind(), "=1-#2", "2 Matches(#1-#2):")
self.call(building.CmdFind(), "=#1-2", "2 Matches(#1-#2):")
def test_script(self): def test_script(self):
self.call(building.CmdScript(), "Obj = ", "No scripts defined on Obj") self.call(building.CmdScript(), "Obj = ", "No scripts defined on Obj")
self.call( self.call(