From 7b5128d0a9494e9b578a418c163da71cf238140c Mon Sep 17 00:00:00 2001 From: Kelketek Date: Mon, 23 Dec 2013 12:59:06 -0600 Subject: [PATCH 1/8] Fixed issue with @delplayer not working correctly. --- src/commands/default/admin.py | 78 +++++++++++------------------------ 1 file changed, 24 insertions(+), 54 deletions(-) diff --git a/src/commands/default/admin.py b/src/commands/default/admin.py index 1b0b975cb9..c7943d9e5f 100644 --- a/src/commands/default/admin.py +++ b/src/commands/default/admin.py @@ -282,7 +282,7 @@ class CmdDelPlayer(MuxCommand): caller = caller.player if not args: - self.msg("Usage: @delplayer[/delobj] [: reason]") + self.msg("Usage: @delplayer [: reason]") return reason = "" @@ -291,66 +291,36 @@ class CmdDelPlayer(MuxCommand): # We use player_search since we want to be sure to find also players # that lack characters. - players = caller.search_player(args, quiet=True) + players = search.player_search(args) if not players: - # try to find a user instead of a Player - try: - 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 + self.msg('Could not find a player by that name.') + return - if user and not user.access(caller, 'delete'): - string = "You don't have the permissions to delete this player." - self.msg(string) - return + if len(players) > 1: + string = "There were multiple matches:" + for player in players: + string += "\n %s %s" % (player.id, player.key) + return - string = "" - name = user.username - user.delete() - if user: - name = user.name - user.delete() - string = "Player %s was deleted." % name - else: - string += "The User %s was deleted. It had no Player associated with it." % name + # one single match + + player = players.pop() + + if not player.access(caller, 'delete'): + string = "You don't have the permissions to delete that player." self.msg(string) return - elif utils.is_iter(players): - string = "There were multiple matches:" - for user in players: - string += "\n %s %s" % (user.id, user.key) - return - else: - # one single match - - user = players - 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) + uname = player.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 + player.msg(string) + player.delete() + self.msg("Player %s was successfully deleted." % uname) class CmdEmit(MuxCommand): From 46a7e891d13768c82d09c7a390ad340c6d290bad Mon Sep 17 00:00:00 2001 From: Kelketek Date: Mon, 23 Dec 2013 13:38:40 -0600 Subject: [PATCH 2/8] Fixed issue where object aliases were not being included in searches. --- src/objects/manager.py | 6 +++--- src/objects/models.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/objects/manager.py b/src/objects/manager.py index e98c0c9812..91e54ec812 100644 --- a/src/objects/manager.py +++ b/src/objects/manager.py @@ -197,7 +197,6 @@ class ObjectManager(TypedObjectManager): # if candidates is an empty iterable there can be no matches # Exit early. return [] - # build query objects 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() @@ -205,7 +204,7 @@ class ObjectManager(TypedObjectManager): if exact: # exact match - do direct search 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: # fuzzy with candidates key_candidates = self.filter(cand_restriction & type_restriction) @@ -219,7 +218,8 @@ class ObjectManager(TypedObjectManager): if index_matches: return [obj for ind, obj in enumerate(key_candidates) if ind in index_matches] 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) index_matches = string_partial_matching(alias_strings, ostring, ret_index=True) if index_matches: diff --git a/src/objects/models.py b/src/objects/models.py index e9cdfb17a2..0586d9168e 100644 --- a/src/objects/models.py +++ b/src/objects/models.py @@ -141,8 +141,8 @@ class ObjectDB(TypedObject): _GA(self, "cmdset").update(init_mode=True) _SA(self, "scripts", ScriptHandler(self)) _SA(self, "attributes", AttributeHandler(self)) - _SA(self, "tags", TagHandler(self, category_prefix="object_")) - _SA(self, "aliases", AliasHandler(self, category_prefix="object_")) + _SA(self, "tags", TagHandler(self, category_prefix="object")) + _SA(self, "aliases", AliasHandler(self, category_prefix="object")) _SA(self, "nicks", NickHandler(self)) # make sure to sync the contents cache when initializing #_GA(self, "contents_update")() From 56601d0bbc86d85d5d61e35558bea05def18bacb Mon Sep 17 00:00:00 2001 From: Kelketek Date: Mon, 23 Dec 2013 13:55:33 -0600 Subject: [PATCH 3/8] Fixed issue #448, which prevented searching for objects with a specific attribute name. --- src/objects/manager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/objects/manager.py b/src/objects/manager.py index 91e54ec812..652fffcae6 100644 --- a/src/objects/manager.py +++ b/src/objects/manager.py @@ -98,8 +98,8 @@ class ObjectManager(TypedObjectManager): Returns all objects having the given attribute_name defined at all. 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() - return list(self.filter(cand_restriction & Q(objattribute__db_key=attribute_name))) + 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(db_attributes__db_key=attribute_name))) @returns_typeclass_list def get_objs_with_attr_value(self, attribute_name, attribute_value, candidates=None, typeclasses=None): From 81c57da56b1a04610bf2c6477653b36ec9273a75 Mon Sep 17 00:00:00 2001 From: Kelketek Date: Mon, 23 Dec 2013 14:12:09 -0600 Subject: [PATCH 4/8] Made search_object slightly less picky about db fields, per #445 --- src/objects/manager.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/objects/manager.py b/src/objects/manager.py index 652fffcae6..a06d0c41da 100644 --- a/src/objects/manager.py +++ b/src/objects/manager.py @@ -156,6 +156,8 @@ class ObjectManager(TypedObjectManager): if isinstance(property_name, basestring): if not property_name.startswith('db_'): property_name = "db_%s" % property_name + if hasattr(property_value, 'dbobj'): + property_value = property_value.dbobj 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() type_restriction = typeclasses and Q(db_typeclass_path__in=make_iter(typeclasses)) or Q() From 63761f66ec52ac6142a73a53f025062452454322 Mon Sep 17 00:00:00 2001 From: Kelketek Date: Tue, 31 Dec 2013 08:37:42 -0600 Subject: [PATCH 5/8] ANSIString in progress. Checking in what I have so far. --- src/utils/ansi.py | 277 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 195 insertions(+), 82 deletions(-) diff --git a/src/utils/ansi.py b/src/utils/ansi.py index 54d29fe342..566588e923 100644 --- a/src/utils/ansi.py +++ b/src/utils/ansi.py @@ -70,87 +70,6 @@ class ANSIParser(object): 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): """ This is a replacer method called by re.sub with the matched @@ -172,7 +91,7 @@ class ANSIParser(object): if self.do_xterm256: colval = 16 + (red * 36) + (green * 6) + 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: #print "ANSI convert:", red, green, blue # xterm256 not supported, convert the rgb value to ansi instead @@ -259,6 +178,84 @@ class ANSIParser(object): string = self.ansi_regex.sub("", 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() @@ -279,3 +276,119 @@ def raw(string): Escapes a string into a form which won't be colorized by the ansi parser. """ 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) + + +class ANSIString(str): + """ + 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. + """ + + def __new__(cls, *args, **kwargs): + string = args[0] + args = args[1:] + parser = kwargs.get('parser', ANSI_PARSER) + string = parser.parse_ansi(string) + return super(ANSIString, cls).__new__(ANSIString, string, *args) + + def __init__(self, *args, **kwargs): + self.parser = kwargs.pop('parser', ANSI_PARSER) + super(ANSIString, self).__init__(*args, **kwargs) + self.raw_string = super(ANSIString, self).__str__() + self.clean_string = self.parser.parse_ansi( + self.raw_string, strip_ansi=True) + for func_name in [ + 'count', 'startswith', 'endswith', 'find', 'index', 'isalnum', + 'isalpha', 'isdigit', 'islower', 'isspace', 'istitle', 'isupper', + 'rfind', 'rindex']: + setattr(self, func_name, _query_super(func_name)) + 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) + + 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) + + 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 + + +def _query_super(func_name): + def query_func(self, *args, **kwargs): + getattr(self.raw_string, func_name)(self, *args, **kwargs) + return query_func \ No newline at end of file From b9d333180a72115aa08605a4f453bd0faf6d5ac2 Mon Sep 17 00:00:00 2001 From: Kelketek Date: Tue, 31 Dec 2013 22:18:13 -0600 Subject: [PATCH 6/8] Several more methods added to ANSIString. Existing ones from init moved out to bind properly. --- src/utils/ansi.py | 86 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 76 insertions(+), 10 deletions(-) diff --git a/src/utils/ansi.py b/src/utils/ansi.py index 566588e923..bf58c661ad 100644 --- a/src/utils/ansi.py +++ b/src/utils/ansi.py @@ -285,6 +285,21 @@ def group(lst, n): yield tuple(val) +def _spacing_preflight(func): + def _spacing_preflight(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 _spacing_preflight + + class ANSIString(str): """ String-like object that is aware of ANSI codes. @@ -293,26 +308,29 @@ class ANSIString(str): 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): string = args[0] args = args[1:] parser = kwargs.get('parser', ANSI_PARSER) - string = parser.parse_ansi(string) + decoded = kwargs.get('decoded', False) + if not decoded: + string = parser.parse_ansi(string) return super(ANSIString, cls).__new__(ANSIString, string, *args) + def __repr__(self): + return "ANSIString(%s, decode=False)" % 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 = super(ANSIString, self).__str__() self.clean_string = self.parser.parse_ansi( self.raw_string, strip_ansi=True) - for func_name in [ - 'count', 'startswith', 'endswith', 'find', 'index', 'isalnum', - 'isalpha', 'isdigit', 'islower', 'isspace', 'istitle', 'isupper', - 'rfind', 'rindex']: - setattr(self, func_name, _query_super(func_name)) self._code_indexes, self._char_indexes = self._get_indexes() def __len__(self): @@ -339,7 +357,7 @@ class ANSIString(str): string += self.raw_string[i] except IndexError: pass - return ANSIString(string) + return ANSIString(string, decoded=True) def __getitem__(self, item): if isinstance(item, slice): @@ -351,7 +369,7 @@ class ANSIString(str): for index in range(0, item + 1): if index in self._code_indexes: result += self.raw_string[index] - return ANSIString(result + clean) + return ANSIString(result + clean, decoded=True) def _get_indexes(self): matches = [ @@ -387,8 +405,56 @@ class ANSIString(str): 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): - getattr(self.raw_string, func_name)(self, *args, **kwargs) - return query_func \ No newline at end of file + 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 on_raw_func(self, *args, **kwargs): + args = list(args) + string = args.pop(0) + if hasattr(string, 'raw_string'): + args.insert(0, string.raw_string) + else: + args.insert(0, string) + result = _query_super(func_name)(self, *args, **kwargs) + if isinstance(result, str): + return ANSIString(result, decoded=True) + return on_raw_func + + +for func_name in [ + 'count', 'startswith', 'endswith', 'find', 'index', 'isalnum', + 'isalpha', 'isdigit', 'islower', 'isspace', 'istitle', 'isupper', + 'rfind', 'rindex']: + setattr(ANSIString, func_name, _query_super(func_name)) +for func_name in [ + '__mul__', '__mod__', '__add__', '__radd__', 'expandtabs', + '__rmul__']: + setattr(ANSIString, func_name, _on_raw(func_name)) \ No newline at end of file From f8f592af75214d1f26220e0992285757ca655978 Mon Sep 17 00:00:00 2001 From: Kelketek Date: Wed, 1 Jan 2014 14:25:26 -0600 Subject: [PATCH 7/8] Added transformation-type string methods to ANSIString. --- src/utils/ansi.py | 62 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 47 insertions(+), 15 deletions(-) diff --git a/src/utils/ansi.py b/src/utils/ansi.py index bf58c661ad..7d4487aef5 100644 --- a/src/utils/ansi.py +++ b/src/utils/ansi.py @@ -300,7 +300,7 @@ def _spacing_preflight(func): return _spacing_preflight -class ANSIString(str): +class ANSIString(unicode): """ String-like object that is aware of ANSI codes. @@ -314,6 +314,12 @@ class ANSIString(str): """ 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] args = args[1:] parser = kwargs.get('parser', ANSI_PARSER) @@ -323,12 +329,12 @@ class ANSIString(str): return super(ANSIString, cls).__new__(ANSIString, string, *args) def __repr__(self): - return "ANSIString(%s, decode=False)" % repr(self.raw_string) + 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 = super(ANSIString, self).__str__() + 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() @@ -438,23 +444,49 @@ def _on_raw(func_name): """ def on_raw_func(self, *args, **kwargs): args = list(args) - string = args.pop(0) - if hasattr(string, 'raw_string'): - args.insert(0, string.raw_string) - else: - args.insert(0, string) + 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, str): + if isinstance(result, unicode): return ANSIString(result, decoded=True) + return result return on_raw_func +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 transform_func(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 transform_func + + for func_name in [ - 'count', 'startswith', 'endswith', 'find', 'index', 'isalnum', - 'isalpha', 'isdigit', 'islower', 'isspace', 'istitle', 'isupper', - 'rfind', 'rindex']: + 'count', 'startswith', 'endswith', 'find', 'index', 'isalnum', + 'isalpha', 'isdigit', 'islower', 'isspace', 'istitle', 'isupper', + 'rfind', 'rindex']: setattr(ANSIString, func_name, _query_super(func_name)) for func_name in [ - '__mul__', '__mod__', '__add__', '__radd__', 'expandtabs', - '__rmul__']: - setattr(ANSIString, func_name, _on_raw(func_name)) \ No newline at end of file + '__mul__', '__mod__', '__add__', '__radd__', 'expandtabs', + '__rmul__', 'join', 'decode']: + setattr(ANSIString, func_name, _on_raw(func_name)) +for func_name in [ + 'capitalize', 'translate', 'lower', 'upper', 'swapcase']: + setattr(ANSIString, func_name, _transform(func_name)) \ No newline at end of file From 5ae4995f0faf16d50208cf087c9061637940ea36 Mon Sep 17 00:00:00 2001 From: Kelketek Date: Thu, 2 Jan 2014 09:12:40 -0600 Subject: [PATCH 8/8] Some naming tweaks. --- src/utils/ansi.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/utils/ansi.py b/src/utils/ansi.py index 7d4487aef5..3ade54d6eb 100644 --- a/src/utils/ansi.py +++ b/src/utils/ansi.py @@ -286,7 +286,7 @@ def group(lst, n): def _spacing_preflight(func): - def _spacing_preflight(self, width, fillchar=None): + def wrapped(self, width, fillchar=None): if fillchar is None: fillchar = " " if (len(fillchar) != 1) or (not isinstance(fillchar, str)): @@ -297,7 +297,7 @@ def _spacing_preflight(func): if difference <= 0: return self return func(self, width, fillchar, difference) - return _spacing_preflight + return wrapped class ANSIString(unicode): @@ -442,7 +442,7 @@ def _on_raw(func_name): """ Like query_super, but makes the operation run on the raw string. """ - def on_raw_func(self, *args, **kwargs): + def wrapped(self, *args, **kwargs): args = list(args) try: string = args.pop(0) @@ -456,7 +456,7 @@ def _on_raw(func_name): if isinstance(result, unicode): return ANSIString(result, decoded=True) return result - return on_raw_func + return wrapped def _transform(func_name): @@ -466,7 +466,7 @@ def _transform(func_name): allows us to do the same, replacing all the non-coded characters with the resulting string. """ - def transform_func(self, *args, **kwargs): + def wrapped(self, *args, **kwargs): replacement_string = _query_super(func_name)(*args, **kwargs) to_string = [] for index in range(0, len(self.raw_string)): @@ -475,7 +475,7 @@ def _transform(func_name): elif index in self._char_indexes: to_string.append(replacement_string[index]) return ANSIString(''.join(to_string), decoded=True) - return transform_func + return wrapped for func_name in [ @@ -485,7 +485,7 @@ for func_name in [ setattr(ANSIString, func_name, _query_super(func_name)) for func_name in [ '__mul__', '__mod__', '__add__', '__radd__', 'expandtabs', - '__rmul__', 'join', 'decode']: + '__rmul__', 'join', 'decode', 'replace', 'format']: setattr(ANSIString, func_name, _on_raw(func_name)) for func_name in [ 'capitalize', 'translate', 'lower', 'upper', 'swapcase']: