Merge. Resolves Issue 448. Resolves Issue 444. Resolves Issue 443. Resolves Issue 445.

This commit is contained in:
Griatch 2014-01-04 11:26:32 +01:00
commit acbfa57240
4 changed files with 332 additions and 143 deletions

View file

@ -282,7 +282,7 @@ class CmdDelPlayer(MuxCommand):
caller = caller.player caller = caller.player
if not args: if not args:
self.msg("Usage: @delplayer[/delobj] <player/user name or #id> [: reason]") self.msg("Usage: @delplayer <player/user name or #id> [: reason]")
return return
reason = "" reason = ""
@ -291,66 +291,36 @@ class CmdDelPlayer(MuxCommand):
# We use player_search since we want to be sure to find also players # We use player_search since we want to be sure to find also players
# that lack characters. # that lack characters.
players = caller.search_player(args, quiet=True) players = search.player_search(args)
if not players: if not players:
# try to find a user instead of a Player self.msg('Could not find a player by that name.')
try: return
user = User.objects.get(id=args)
except Exception:
try:
user = User.objects.get(username__iexact=args)
except Exception:
string = "No Player nor User found matching '%s'." % args
self.msg(string)
return
if user and not user.access(caller, 'delete'): if len(players) > 1:
string = "You don't have the permissions to delete this player." string = "There were multiple matches:"
self.msg(string) for player in players:
return string += "\n %s %s" % (player.id, player.key)
return
string = "" # one single match
name = user.username
user.delete() player = players.pop()
if user:
name = user.name if not player.access(caller, 'delete'):
user.delete() string = "You don't have the permissions to delete that player."
string = "Player %s was deleted." % name
else:
string += "The User %s was deleted. It had no Player associated with it." % name
self.msg(string) self.msg(string)
return return
elif utils.is_iter(players): uname = player.username
string = "There were multiple matches:" # boot the player then delete
for user in players: self.msg("Informing and disconnecting player ...")
string += "\n %s %s" % (user.id, user.key) string = "\nYour account '%s' is being *permanently* deleted.\n" % uname
return if reason:
else: string += " Reason given:\n '%s'" % reason
# one single match player.msg(string)
player.delete()
user = players self.msg("Player %s was successfully deleted." % uname)
user = user.user
if not user.access(caller, 'delete'):
string = "You don't have the permissions to delete that player."
self.msg(string)
return
uname = user.username
# boot the player then delete
self.msg("Informing and disconnecting player ...")
string = "\nYour account '%s' is being *permanently* deleted.\n" % uname
if reason:
string += " Reason given:\n '%s'" % reason
user.unpuppet_all()
for session in SESSIONS.sessions_from_player(user):
user.msg(string, sessid=session.sessid)
user.disconnect_session_from_player(session.sessid)
user.delete()
user.delete()
self.msg("Player %s was successfully deleted." % uname)
class CmdEmit(MuxCommand): class CmdEmit(MuxCommand):

View file

@ -98,8 +98,8 @@ class ObjectManager(TypedObjectManager):
Returns all objects having the given attribute_name defined at all. Returns all objects having the given attribute_name defined at all.
Location should be a valid location object. Location should be a valid location object.
""" """
cand_restriction = candidates != None and Q(objattribute__db_obj__pk__in=[_GA(obj, "id") for obj in make_iter(candidates) if obj]) or Q() cand_restriction = candidates != None and Q(db_attributes__db_obj__pk__in=[_GA(obj, "id") for obj in make_iter(candidates) if obj]) or Q()
return list(self.filter(cand_restriction & Q(objattribute__db_key=attribute_name))) return list(self.filter(cand_restriction & Q(db_attributes__db_key=attribute_name)))
@returns_typeclass_list @returns_typeclass_list
def get_objs_with_attr_value(self, attribute_name, attribute_value, candidates=None, typeclasses=None): def get_objs_with_attr_value(self, attribute_name, attribute_value, candidates=None, typeclasses=None):
@ -156,6 +156,8 @@ class ObjectManager(TypedObjectManager):
if isinstance(property_name, basestring): if isinstance(property_name, basestring):
if not property_name.startswith('db_'): if not property_name.startswith('db_'):
property_name = "db_%s" % property_name property_name = "db_%s" % property_name
if hasattr(property_value, 'dbobj'):
property_value = property_value.dbobj
querykwargs = {property_name:property_value} querykwargs = {property_name:property_value}
cand_restriction = candidates != None and Q(pk__in=[_GA(obj, "id") for obj in make_iter(candidates) if obj]) or Q() cand_restriction = candidates != None and Q(pk__in=[_GA(obj, "id") for obj in make_iter(candidates) if obj]) or Q()
type_restriction = typeclasses and Q(db_typeclass_path__in=make_iter(typeclasses)) or Q() type_restriction = typeclasses and Q(db_typeclass_path__in=make_iter(typeclasses)) or Q()
@ -197,7 +199,6 @@ class ObjectManager(TypedObjectManager):
# if candidates is an empty iterable there can be no matches # if candidates is an empty iterable there can be no matches
# Exit early. # Exit early.
return [] return []
# build query objects # build query objects
candidates_id = [_GA(obj, "id") for obj in make_iter(candidates) if obj] candidates_id = [_GA(obj, "id") for obj in make_iter(candidates) if obj]
cand_restriction = candidates != None and Q(pk__in=make_iter(candidates_id)) or Q() cand_restriction = candidates != None and Q(pk__in=make_iter(candidates_id)) or Q()
@ -205,7 +206,7 @@ class ObjectManager(TypedObjectManager):
if exact: if exact:
# exact match - do direct search # exact match - do direct search
return self.filter(cand_restriction & type_restriction & (Q(db_key__iexact=ostring) | return self.filter(cand_restriction & type_restriction & (Q(db_key__iexact=ostring) |
Q(db_tags__db_key__iexact=ostring) & Q(db_tags__db_category__iexact="object_alias"))).distinct() Q(db_tags__db_key__iexact=ostring) & Q(db_tags__db_category__iexact="objectalias"))).distinct()
elif candidates: elif candidates:
# fuzzy with candidates # fuzzy with candidates
key_candidates = self.filter(cand_restriction & type_restriction) key_candidates = self.filter(cand_restriction & type_restriction)
@ -219,7 +220,8 @@ class ObjectManager(TypedObjectManager):
if index_matches: if index_matches:
return [obj for ind, obj in enumerate(key_candidates) if ind in index_matches] return [obj for ind, obj in enumerate(key_candidates) if ind in index_matches]
else: else:
alias_candidates = self.filter(id__in=candidates_id, db_tags__db_category__iexact="object_alias") alias_candidates = self.filter(id__in=candidates_id, db_tags__db_category__iexact="objectalias")
print alias_candidates
alias_strings = alias_candidates.values_list("db_key", flat=True) alias_strings = alias_candidates.values_list("db_key", flat=True)
index_matches = string_partial_matching(alias_strings, ostring, ret_index=True) index_matches = string_partial_matching(alias_strings, ostring, ret_index=True)
if index_matches: if index_matches:

View file

@ -141,8 +141,8 @@ class ObjectDB(TypedObject):
_GA(self, "cmdset").update(init_mode=True) _GA(self, "cmdset").update(init_mode=True)
_SA(self, "scripts", ScriptHandler(self)) _SA(self, "scripts", ScriptHandler(self))
_SA(self, "attributes", AttributeHandler(self)) _SA(self, "attributes", AttributeHandler(self))
_SA(self, "tags", TagHandler(self, category_prefix="object_")) _SA(self, "tags", TagHandler(self, category_prefix="object"))
_SA(self, "aliases", AliasHandler(self, category_prefix="object_")) _SA(self, "aliases", AliasHandler(self, category_prefix="object"))
_SA(self, "nicks", NickHandler(self)) _SA(self, "nicks", NickHandler(self))
# make sure to sync the contents cache when initializing # make sure to sync the contents cache when initializing
#_GA(self, "contents_update")() #_GA(self, "contents_update")()

View file

@ -70,87 +70,6 @@ class ANSIParser(object):
an extra { for Merc-style codes an extra { for Merc-style codes
""" """
def __init__(self):
"Sets the mappings"
# MUX-style mappings %cr %cn etc
self.mux_ansi_map = [
# commented out by default; they (especially blink) are
# potentially annoying
(r'%r', ANSI_RETURN),
(r'%t', ANSI_TAB),
(r'%b', ANSI_SPACE),
#(r'%cf', ANSI_BLINK),
#(r'%ci', ANSI_INVERSE),
(r'%cr', ANSI_RED),
(r'%cR', ANSI_BACK_RED),
(r'%cg', ANSI_GREEN),
(r'%cG', ANSI_BACK_GREEN),
(r'%cy', ANSI_YELLOW),
(r'%cY', ANSI_BACK_YELLOW),
(r'%cb', ANSI_BLUE),
(r'%cB', ANSI_BACK_BLUE),
(r'%cm', ANSI_MAGENTA),
(r'%cM', ANSI_BACK_MAGENTA),
(r'%cc', ANSI_CYAN),
(r'%cC', ANSI_BACK_CYAN),
(r'%cw', ANSI_WHITE),
(r'%cW', ANSI_BACK_WHITE),
(r'%cx', ANSI_BLACK),
(r'%cX', ANSI_BACK_BLACK),
(r'%ch', ANSI_HILITE),
(r'%cn', ANSI_NORMAL),
]
# Expanded mapping {r {n etc
hilite = ANSI_HILITE
normal = ANSI_NORMAL
self.ext_ansi_map = [
(r'{r', hilite + ANSI_RED),
(r'{R', normal + ANSI_RED),
(r'{g', hilite + ANSI_GREEN),
(r'{G', normal + ANSI_GREEN),
(r'{y', hilite + ANSI_YELLOW),
(r'{Y', normal + ANSI_YELLOW),
(r'{b', hilite + ANSI_BLUE),
(r'{B', normal + ANSI_BLUE),
(r'{m', hilite + ANSI_MAGENTA),
(r'{M', normal + ANSI_MAGENTA),
(r'{c', hilite + ANSI_CYAN),
(r'{C', normal + ANSI_CYAN),
(r'{w', hilite + ANSI_WHITE), # pure white
(r'{W', normal + ANSI_WHITE), # light grey
(r'{x', hilite + ANSI_BLACK), # dark grey
(r'{X', normal + ANSI_BLACK), # pure black
(r'{n', normal) # reset
]
# xterm256 {123, %c134,
self.xterm256_map = [
(r'%c([0-5]{3})', self.parse_rgb), # %c123 - foreground colour
(r'%c(b[0-5]{3})', self.parse_rgb), # %cb123 - background colour
(r'{([0-5]{3})', self.parse_rgb), # {123 - foreground colour
(r'{(b[0-5]{3})', self.parse_rgb) # {b123 - background colour
]
# obs - order matters here, we want to do the xterms first since
# they collide with some of the other mappings otherwise.
self.ansi_map = self.xterm256_map + self.mux_ansi_map + self.ext_ansi_map
# prepare regex matching
self.ansi_sub = [(re.compile(sub[0], re.DOTALL), sub[1])
for sub in self.ansi_map]
# prepare matching ansi codes overall
self.ansi_regex = re.compile("\033\[[0-9;]+m")
# escapes - these double-chars will be replaced with a single
# instance of each
self.ansi_escapes = re.compile(r"(%s)" % "|".join(ANSI_ESCAPES), re.DOTALL)
def parse_rgb(self, rgbmatch): def parse_rgb(self, rgbmatch):
""" """
This is a replacer method called by re.sub with the matched This is a replacer method called by re.sub with the matched
@ -172,7 +91,7 @@ class ANSIParser(object):
if self.do_xterm256: if self.do_xterm256:
colval = 16 + (red * 36) + (green * 6) + blue colval = 16 + (red * 36) + (green * 6) + blue
#print "RGB colours:", red, green, blue #print "RGB colours:", red, green, blue
return "\033[%s8;5;%s%s%sm" % (3 + int(background), colval/100, (colval%100)/10, colval%10) return "\033[%s8;5;%s%s%sm" % (3 + int(background), colval/100, (colval % 100)/10, colval%10)
else: else:
#print "ANSI convert:", red, green, blue #print "ANSI convert:", red, green, blue
# xterm256 not supported, convert the rgb value to ansi instead # xterm256 not supported, convert the rgb value to ansi instead
@ -259,6 +178,84 @@ class ANSIParser(object):
string = self.ansi_regex.sub("", string) string = self.ansi_regex.sub("", string)
return string return string
# MUX-style mappings %cr %cn etc
mux_ansi_map = [
# commented out by default; they (especially blink) are
# potentially annoying
(r'%r', ANSI_RETURN),
(r'%t', ANSI_TAB),
(r'%b', ANSI_SPACE),
#(r'%cf', ANSI_BLINK),
#(r'%ci', ANSI_INVERSE),
(r'%cr', ANSI_RED),
(r'%cR', ANSI_BACK_RED),
(r'%cg', ANSI_GREEN),
(r'%cG', ANSI_BACK_GREEN),
(r'%cy', ANSI_YELLOW),
(r'%cY', ANSI_BACK_YELLOW),
(r'%cb', ANSI_BLUE),
(r'%cB', ANSI_BACK_BLUE),
(r'%cm', ANSI_MAGENTA),
(r'%cM', ANSI_BACK_MAGENTA),
(r'%cc', ANSI_CYAN),
(r'%cC', ANSI_BACK_CYAN),
(r'%cw', ANSI_WHITE),
(r'%cW', ANSI_BACK_WHITE),
(r'%cx', ANSI_BLACK),
(r'%cX', ANSI_BACK_BLACK),
(r'%ch', ANSI_HILITE),
(r'%cn', ANSI_NORMAL),
]
# Expanded mapping {r {n etc
hilite = ANSI_HILITE
normal = ANSI_NORMAL
ext_ansi_map = [
(r'{r', hilite + ANSI_RED),
(r'{R', normal + ANSI_RED),
(r'{g', hilite + ANSI_GREEN),
(r'{G', normal + ANSI_GREEN),
(r'{y', hilite + ANSI_YELLOW),
(r'{Y', normal + ANSI_YELLOW),
(r'{b', hilite + ANSI_BLUE),
(r'{B', normal + ANSI_BLUE),
(r'{m', hilite + ANSI_MAGENTA),
(r'{M', normal + ANSI_MAGENTA),
(r'{c', hilite + ANSI_CYAN),
(r'{C', normal + ANSI_CYAN),
(r'{w', hilite + ANSI_WHITE), # pure white
(r'{W', normal + ANSI_WHITE), # light grey
(r'{x', hilite + ANSI_BLACK), # dark grey
(r'{X', normal + ANSI_BLACK), # pure black
(r'{n', normal) # reset
]
# xterm256 {123, %c134,
xterm256_map = [
(r'%([0-5]{3})', parse_rgb), # %123 - foreground colour
(r'%(-[0-5]{3})', parse_rgb), # %-123 - background colour
(r'{([0-5]{3})', parse_rgb), # {123 - foreground colour
(r'{(-[0-5]{3})', parse_rgb) # {-123 - background colour
]
# obs - order matters here, we want to do the xterms first since
# they collide with some of the other mappings otherwise.
ansi_map = xterm256_map + mux_ansi_map + ext_ansi_map
# prepare regex matching
ansi_sub = [(re.compile(sub[0], re.DOTALL), sub[1])
for sub in ansi_map]
# prepare matching ansi codes overall
ansi_regex = re.compile("\033\[[0-9;]+m")
# escapes - these double-chars will be replaced with a single
# instance of each
ansi_escapes = re.compile(r"(%s)" % "|".join(ANSI_ESCAPES), re.DOTALL)
ANSI_PARSER = ANSIParser() ANSI_PARSER = ANSIParser()
@ -279,3 +276,223 @@ def raw(string):
Escapes a string into a form which won't be colorized by the ansi parser. Escapes a string into a form which won't be colorized by the ansi parser.
""" """
return string.replace('{', '{{').replace('%', '%%') return string.replace('{', '{{').replace('%', '%%')
def group(lst, n):
for i in range(0, len(lst), n):
val = lst[i:i+n]
if len(val) == n:
yield tuple(val)
def _spacing_preflight(func):
def wrapped(self, width, fillchar=None):
if fillchar is None:
fillchar = " "
if (len(fillchar) != 1) or (not isinstance(fillchar, str)):
raise TypeError("must be char, not %s" % type(fillchar))
if not isinstance(width, int):
raise TypeError("integer argument expected, got %s" % type(width))
difference = width - len(self)
if difference <= 0:
return self
return func(self, width, fillchar, difference)
return wrapped
# ----------------------------------------------------------------------
# OBS - work in progress, do not use!
# ----------------------------------------------------------------------
class ANSIString(unicode):
"""
String-like object that is aware of ANSI codes.
This isn't especially efficient, as it doesn't really have an
understanding of what the codes mean in order to eliminate
redundant characters, but a proper parser would have to be written for
that.
Take note of the instructions at the bottom of the module, which modify
this class.
"""
def __new__(cls, *args, **kwargs):
"""
When creating a new ANSIString, you may use a custom parser that has
the same attributes as the standard one, and you may declare the
string to be handled as already decoded. It is important not to double
decode strings, as escapes can only be respected once.
"""
string = args[0] if args else ""
args = args[1:] if args else ()
parser = kwargs.pop('parser', ANSI_PARSER)
decoded = kwargs.pop('decoded', False)
if not decoded:
string = parser.parse_ansi(string)
# assign needed methods
for func_name in [
'count', 'startswith', 'endswith', 'find', 'index', 'isalnum',
'isalpha', 'isdigit', 'islower', 'isspace', 'istitle', 'isupper',
'rfind', 'rindex']:
setattr(cls, func_name, _query_super(func_name))
for func_name in [
'__mul__', '__mod__', '__add__', '__radd__', 'expandtabs',
'__rmul__', 'join', 'decode', 'replace', 'format']:
setattr(cls, func_name, _on_raw(func_name))
for func_name in [
'capitalize', 'translate', 'lower', 'upper', 'swapcase']:
setattr(cls, func_name, _transform(func_name))
return super(ANSIString, cls).__new__(ANSIString, string, *args, **kwargs)
def __repr__(self):
return "ANSIString(%s, decoded=True)" % repr(self.raw_string)
def __init__(self, *args, **kwargs):
self.parser = kwargs.pop('parser', ANSI_PARSER)
super(ANSIString, self).__init__(*args, **kwargs)
self.raw_string = unicode(self)
self.clean_string = self.parser.parse_ansi(
self.raw_string, strip_ansi=True)
self._code_indexes, self._char_indexes = self._get_indexes()
def __len__(self):
return len(self.clean_string)
def __getslice__(self, i, j):
return self.__getitem__(slice(i, j))
def _slice(self, item):
slice_indexes = self._char_indexes[item]
if not slice_indexes:
return ANSIString('')
try:
string = self[item.start].raw_string
except IndexError:
return ANSIString('')
last_mark = slice_indexes[0]
for i in slice_indexes[1:]:
for index in range(last_mark, i):
if index in self._code_indexes:
string += self.raw_string[index]
last_mark = i
try:
string += self.raw_string[i]
except IndexError:
pass
return ANSIString(string, decoded=True)
def __getitem__(self, item):
if isinstance(item, slice):
return self._slice(item)
item = self._char_indexes[item]
clean = self.raw_string[item]
result = ''
for index in range(0, item + 1):
if index in self._code_indexes:
result += self.raw_string[index]
return ANSIString(result + clean, decoded=True)
def _get_indexes(self):
matches = [
(match.start(), match.end())
for match in self.parser.ansi_regex.finditer(self.raw_string)]
code_indexes = []
# These are all the indexes which hold code characters.
for start, end in matches:
code_indexes.extend(range(start, end))
flat_ranges = []
# We need to get the ones between them, but the code might start at
# the beginning, and there might be codes at the end.
for tup in matches:
flat_ranges.extend(tup)
# Is the beginning of the string a code character?
if flat_ranges[0] == 0:
flat_ranges.pop(0)
else:
flat_ranges.insert(0, 0)
# How about the end?
end_index = (len(self.raw_string) - 1)
if flat_ranges[-1] == end_index:
flat_ranges.pop()
else:
flat_ranges.append(end_index)
char_indexes = []
for start, end in list(group(flat_ranges, 2)):
char_indexes.extend(range(start, end))
# The end character will be left off if it's a normal character. Fix
# that here.
if end_index in flat_ranges:
char_indexes.append(end_index)
return code_indexes, char_indexes
@_spacing_preflight
def center(self, width, fillchar, difference):
remainder = difference % 2
difference /= 2
spacing = difference * fillchar
result = spacing + self + spacing + (remainder * fillchar)
return result
@_spacing_preflight
def ljust(self, width, fillchar, difference):
return self + (difference * fillchar)
@_spacing_preflight
def rjust(self, width, fillchar, difference):
return (difference * fillchar) + self
def _query_super(func_name):
"""
Have the string class handle this with the cleaned string instead of
ANSIString.
"""
def query_func(self, *args, **kwargs):
return getattr(self.raw_string, func_name)(*args, **kwargs)
return query_func
def _on_raw(func_name):
"""
Like query_super, but makes the operation run on the raw string.
"""
def wrapped(self, *args, **kwargs):
args = list(args)
try:
string = args.pop(0)
if hasattr(string, 'raw_string'):
args.insert(0, string.raw_string)
else:
args.insert(0, string)
except IndexError:
pass
result = _query_super(func_name)(self, *args, **kwargs)
if isinstance(result, unicode):
return ANSIString(result, decoded=True)
return result
return wrapped
def _transform(func_name):
"""
Some string functions, like those manipulating capital letters,
return a string the same length as the original. This function
allows us to do the same, replacing all the non-coded characters
with the resulting string.
"""
def wrapped(self, *args, **kwargs):
replacement_string = _query_super(func_name)(*args, **kwargs)
to_string = []
for index in range(0, len(self.raw_string)):
if index in self._code_indexes:
to_string.append(self.raw_string[index])
elif index in self._char_indexes:
to_string.append(replacement_string[index])
return ANSIString(''.join(to_string), decoded=True)
return wrapped