Bug #274279 - Add color.map support for mapping a color to a different color
authorZac Medico <zmedico@gentoo.org>
Tue, 16 Jun 2009 21:53:17 +0000 (21:53 -0000)
committerZac Medico <zmedico@gentoo.org>
Tue, 16 Jun 2009 21:53:17 +0000 (21:53 -0000)
(rather than just mapping a class/style to a different color). Thanks to
Sebastian Mingramm (few) <s.mingramm@gmx.de> for this patch.

svn path=/main/trunk/; revision=13643

man/color.map.5
pym/portage/__init__.py
pym/portage/output.py

index 9082e7693749153eed3be769daf0af22b1dddcb5..9ce76ad26f5050560b4a4d935c03d209415467fa 100644 (file)
@@ -9,9 +9,9 @@ Portage will check this file first for color classes settings. If no setting
 of given color class is found in /etc/portage/color.map, Portage uses default
 value defined internally.
 .SH "SYNTAX"
-\fBVARIABLE\fR = \fI[space delimited list of attributes]\fR
+\fBVARIABLE\fR = \fI[space delimited list of attributes or ansi code pattern]\fR
 .TP
-\fBATTRIBUTE\fR = \fI[space delimited list of attributes]\fR
+\fBATTRIBUTE\fR = \fI[space delimited list of attributes or ansi code pattern]\fR
 .SH "VARIABLES"
 .TP
 \fBNORMAL\fR = \fI"normal"\fR
index 6854eea99cf5ee94bb6a13dd4149f6a806ffc131..c1707287073b6cfca985909e0a55a3060954928d 100644 (file)
@@ -5431,7 +5431,8 @@ def doebuild_environment(myebuild, mydo, myroot, mysettings, debug, use_cache, m
        # Allow color.map to control colors associated with einfo, ewarn, etc...
        mycolors = []
        for c in ("GOOD", "WARN", "BAD", "HILITE", "BRACKET"):
-               mycolors.append("%s=$'%s'" % (c, portage.output.codes[c]))
+               mycolors.append("%s=$'%s'" % \
+                       (c, portage.output.map_code_to_color_code(c)))
        mysettings["PORTAGE_COLORMAP"] = "\n".join(mycolors)
 
 def prepare_build_dirs(myroot, mysettings, cleanup):
index 983cbec60c1f68cc800d5ae52f9774ced7ec29f7..2096b24be6bde4e4a881c07d1f4b321c1b9d2bef 100644 (file)
@@ -4,6 +4,7 @@
 
 __docformat__ = "epytext"
 
+import codecs
 import commands
 import errno
 import formatter
@@ -24,68 +25,47 @@ from portage.exception import CommandNotFound, FileNotFound, \
 havecolor=1
 dotitles=1
 
+codes = {}
+color_codes = {}
+
 esc_seq = "\x1b["
 
-g_attr = {}
-g_attr["normal"]       =  0
-
-g_attr["bold"]         =  1
-g_attr["faint"]        =  2
-g_attr["standout"]     =  3
-g_attr["underline"]    =  4
-g_attr["blink"]        =  5
-g_attr["overline"]     =  6  # Why is overline actually useful?
-g_attr["reverse"]      =  7
-g_attr["invisible"]    =  8
-
-g_attr["no-attr"]      = 22
-g_attr["no-standout"]  = 23
-g_attr["no-underline"] = 24
-g_attr["no-blink"]     = 25
-g_attr["no-overline"]  = 26
-g_attr["no-reverse"]   = 27
-# 28 isn't defined?
-# 29 isn't defined?
-g_attr["black"]        = 30
-g_attr["red"]          = 31
-g_attr["green"]        = 32
-g_attr["yellow"]       = 33
-g_attr["blue"]         = 34
-g_attr["magenta"]      = 35
-g_attr["cyan"]         = 36
-g_attr["white"]        = 37
-# 38 isn't defined?
-g_attr["default"]      = 39
-g_attr["bg_black"]     = 40
-g_attr["bg_red"]       = 41
-g_attr["bg_green"]     = 42
-g_attr["bg_yellow"]    = 43
-g_attr["bg_blue"]      = 44
-g_attr["bg_magenta"]   = 45
-g_attr["bg_cyan"]      = 46
-g_attr["bg_white"]     = 47
-g_attr["bg_default"]   = 49
-
-
-# make_seq("blue", "black", "normal")
+color_codes["normal"]       =  esc_seq + "0m"
+color_codes["reset"]        =  esc_seq + "39;49;00m"
+
+color_codes["bold"]         =  esc_seq + "01m"
+color_codes["faint"]        =  esc_seq + "02m"
+color_codes["standout"]     =  esc_seq + "03m"
+color_codes["underline"]    =  esc_seq + "04m"
+color_codes["blink"]        =  esc_seq + "05m"
+color_codes["overline"]     =  esc_seq + "06m"
+color_codes["reverse"]      =  esc_seq + "07m"
+color_codes["invisible"]    =  esc_seq + "08m"
+
+color_codes["no-attr"]      = esc_seq + "22m"
+color_codes["no-standout"]  = esc_seq + "23m"
+color_codes["no-underline"] = esc_seq + "24m"
+color_codes["no-blink"]     = esc_seq + "25m"
+color_codes["no-overline"]  = esc_seq + "26m"
+color_codes["no-reverse"]   = esc_seq + "27m"
+
+color_codes["bg_black"]      = esc_seq + "40m"
+color_codes["bg_darkred"]    = esc_seq + "41m"
+color_codes["bg_darkgreen"]  = esc_seq + "42m"
+color_codes["bg_brown"]      = esc_seq + "43m"
+color_codes["bg_darkblue"]   = esc_seq + "44m"
+color_codes["bg_purple"]     = esc_seq + "45m"
+color_codes["bg_teal"]       = esc_seq + "46m"
+color_codes["bg_lightgray"]  = esc_seq + "47m"
+color_codes["bg_default"]    = esc_seq + "49m"
+color_codes["bg_darkyellow"] = color_codes["bg_brown"]
+
 def color(fg, bg="default", attr=["normal"]):
-       mystr = esc_seq[:] + "%02d" % g_attr[fg]
+       mystr = color_codes[fg]
        for x in [bg]+attr:
-               mystr += ";%02d" % g_attr[x]
-       return mystr+"m"
-
-
+               mystr += color_codes[x]
+       return mystr
 
-codes={}
-codes["reset"]     = esc_seq + "39;49;00m"
-
-codes["bold"]      = esc_seq + "01m"
-codes["faint"]     = esc_seq + "02m"
-codes["standout"]  = esc_seq + "03m"
-codes["underline"] = esc_seq + "04m"
-codes["blink"]     = esc_seq + "05m"
-codes["overline"]  = esc_seq + "06m"  # Who made this up? Seriously.
-codes["reverse"]   = esc_seq + "07m"
 
 ansi_color_codes = []
 for x in xrange(30, 38):
@@ -97,76 +77,67 @@ rgb_ansi_colors = ['0x000000', '0x555555', '0xAA0000', '0xFF5555', '0x00AA00',
        '0xFF55FF', '0x00AAAA', '0x55FFFF', '0xAAAAAA', '0xFFFFFF']
 
 for x in xrange(len(rgb_ansi_colors)):
-       codes[rgb_ansi_colors[x]] = esc_seq + ansi_color_codes[x]
+       color_codes[rgb_ansi_colors[x]] = esc_seq + ansi_color_codes[x]
 
 del x
 
-codes["black"]     = codes["0x000000"]
-codes["darkgray"]  = codes["0x555555"]
+color_codes["black"]     = color_codes["0x000000"]
+color_codes["darkgray"]  = color_codes["0x555555"]
 
-codes["red"]       = codes["0xFF5555"]
-codes["darkred"]   = codes["0xAA0000"]
+color_codes["red"]       = color_codes["0xFF5555"]
+color_codes["darkred"]   = color_codes["0xAA0000"]
 
-codes["green"]     = codes["0x55FF55"]
-codes["darkgreen"] = codes["0x00AA00"]
+color_codes["green"]     = color_codes["0x55FF55"]
+color_codes["darkgreen"] = color_codes["0x00AA00"]
 
-codes["yellow"]    = codes["0xFFFF55"]
-codes["brown"]     = codes["0xAA5500"]
+color_codes["yellow"]    = color_codes["0xFFFF55"]
+color_codes["brown"]     = color_codes["0xAA5500"]
 
-codes["blue"]      = codes["0x5555FF"]
-codes["darkblue"]  = codes["0x0000AA"]
+color_codes["blue"]      = color_codes["0x5555FF"]
+color_codes["darkblue"]  = color_codes["0x0000AA"]
 
-codes["fuchsia"]   = codes["0xFF55FF"]
-codes["purple"]    = codes["0xAA00AA"]
+color_codes["fuchsia"]   = color_codes["0xFF55FF"]
+color_codes["purple"]    = color_codes["0xAA00AA"]
 
-codes["turquoise"] = codes["0x55FFFF"]
-codes["teal"]      = codes["0x00AAAA"]
+color_codes["turquoise"] = color_codes["0x55FFFF"]
+color_codes["teal"]      = color_codes["0x00AAAA"]
 
-codes["white"]     = codes["0xFFFFFF"]
-codes["lightgray"] = codes["0xAAAAAA"]
+color_codes["white"]     = color_codes["0xFFFFFF"]
+color_codes["lightgray"] = color_codes["0xAAAAAA"]
 
-codes["darkteal"]   = codes["turquoise"]
+color_codes["darkteal"]   = color_codes["turquoise"]
 # Some terminals have darkyellow instead of brown.
-codes["0xAAAA00"]   = codes["brown"]
-codes["darkyellow"] = codes["0xAAAA00"]
+color_codes["0xAAAA00"]   = color_codes["brown"]
+color_codes["darkyellow"] = color_codes["0xAAAA00"]
 
-codes["bg_black"]      = esc_seq + "40m"
-codes["bg_darkred"]    = esc_seq + "41m"
-codes["bg_darkgreen"]  = esc_seq + "42m"
-codes["bg_brown"]      = esc_seq + "43m"
-codes["bg_darkblue"]   = esc_seq + "44m"
-codes["bg_purple"]     = esc_seq + "45m"
-codes["bg_teal"]       = esc_seq + "46m"
-codes["bg_lightgray"]  = esc_seq + "47m"
 
-codes["bg_darkyellow"] = codes["bg_brown"]
 
 # Colors from /etc/init.d/functions.sh
-codes["NORMAL"]     = esc_seq + "0m"
-codes["GOOD"]       = codes["green"]
-codes["WARN"]       = codes["yellow"]
-codes["BAD"]        = codes["red"]
-codes["HILITE"]     = codes["teal"]
-codes["BRACKET"]    = codes["blue"]
+codes["NORMAL"]     = ( "normal", )
+codes["GOOD"]       = ( "green", )
+codes["WARN"]       = ( "yellow", )
+codes["BAD"]        = ( "red", )
+codes["HILITE"]     = ( "teal", )
+codes["BRACKET"]    = ( "blue", )
 
 # Portage functions
-codes["INFORM"]                  = codes["darkgreen"]
-codes["UNMERGE_WARN"]            = codes["red"]
-codes["SECURITY_WARN"]           = codes["red"]
-codes["MERGE_LIST_PROGRESS"]     = codes["yellow"]
-codes["PKG_BLOCKER"]             = codes["red"]
-codes["PKG_BLOCKER_SATISFIED"]   = codes["darkblue"]
-codes["PKG_MERGE"]               = codes["darkgreen"]
-codes["PKG_MERGE_SYSTEM"]        = codes["darkgreen"]
-codes["PKG_MERGE_WORLD"]         = codes["green"]
-codes["PKG_UNINSTALL"]           = codes["red"]
-codes["PKG_NOMERGE"]             = codes["darkblue"]
-codes["PKG_NOMERGE_SYSTEM"]      = codes["darkblue"]
-codes["PKG_NOMERGE_WORLD"]       = codes["blue"]
-codes["PROMPT_CHOICE_DEFAULT"]   = codes["green"]
-codes["PROMPT_CHOICE_OTHER"]     = codes["red"]
-
-def parse_color_map(onerror=None):
+codes["INFORM"]                  = ( "darkgreen", )
+codes["UNMERGE_WARN"]            = ( "red", )
+codes["SECURITY_WARN"]           = ( "red", )
+codes["MERGE_LIST_PROGRESS"]     = ( "yellow", )
+codes["PKG_BLOCKER"]             = ( "red", )
+codes["PKG_BLOCKER_SATISFIED"]   = ( "darkblue", )
+codes["PKG_MERGE"]               = ( "darkgreen", )
+codes["PKG_MERGE_SYSTEM"]        = ( "darkgreen", )
+codes["PKG_MERGE_WORLD"]         = ( "green", )
+codes["PKG_UNINSTALL"]           = ( "red", )
+codes["PKG_NOMERGE"]             = ( "darkblue", )
+codes["PKG_NOMERGE_SYSTEM"]      = ( "darkblue", )
+codes["PKG_NOMERGE_WORLD"]       = ( "blue", )
+codes["PROMPT_CHOICE_DEFAULT"]   = ( "green", )
+codes["PROMPT_CHOICE_OTHER"]     = ( "red", )
+
+def _parse_color_map(onerror=None):
        """
        Parse /etc/portage/color.map and return a dict of error codes.
 
@@ -177,32 +148,40 @@ def parse_color_map(onerror=None):
        @return: a dictionary mapping color classes to color codes
        """
        myfile = COLOR_MAP_FILE
-       ansi_code_pattern = re.compile("^[0-9;]*m$")
-       def strip_quotes(token, quotes):
+       ansi_code_pattern = re.compile("^[0-9;]*m$") 
+       quotes = '\'"'
+       def strip_quotes(token):
                if token[0] in quotes and token[0] == token[-1]:
                        token = token[1:-1]
                return token
        try:
-               s = shlex.shlex(open(myfile))
-               s.wordchars = s.wordchars + ";" # for ansi codes
-               while True:
-                       k, o, v = s.get_token(), s.get_token(), s.get_token()
-                       if k is s.eof:
-                               break
-                       if o != "=":
-                               e = ParseError("%s%s'%s'" % (
-                                       s.error_leader(myfile, s.lineno),
-                                       "expected '=' operator: ", o))
+               lineno=0
+               for line in codecs.open( myfile, mode = 'r', errors = 'replace' ):
+                       lineno += 1
+
+                       commenter_pos = line.find("#")
+                       line = line[:commenter_pos].strip()
+                       
+                       if len(line) == 0:
+                               continue
+                       
+                       split_line = line.split("=")
+                       if len(split_line) != 2:
+                               e = ParseError("'%s', line %s: %s" % (
+                                       myfile, lineno,
+                                       "expected exactly one occurence of '=' operator"))
+                               raise e
                                if onerror:
                                        onerror(e)
                                else:
                                        raise e
                                continue
-                       k = strip_quotes(k, s.quotes)
-                       v = strip_quotes(v, s.quotes)
-                       if not k in codes:
-                               e = ParseError("%s%s'%s'" % (
-                                       s.error_leader(myfile, s.lineno),
+
+                       k = strip_quotes(split_line[0].strip())
+                       v = strip_quotes(split_line[1].strip())
+                       if not k in codes and not k in color_codes:
+                               e = ParseError("'%s', line %s: %s'%s'" % (
+                                       myfile, lineno,
                                        "Unknown variable: ", k))
                                if onerror:
                                        onerror(e)
@@ -210,21 +189,30 @@ def parse_color_map(onerror=None):
                                        raise e
                                continue
                        if ansi_code_pattern.match(v):
-                               codes[k] = esc_seq + v
+                               if k in codes:
+                                       codes[k] = ( esc_seq + v, )
+                               elif k in color_codes:
+                                       color_codes[k] = esc_seq + v
                        else:
                                code_list = []
-                               for x in v.split(" "):
-                                       if x in codes:
-                                               code_list.append(codes[x])
+                               for x in v.split():
+                                       if x in color_codes:
+                                               if k in codes:
+                                                       code_list.append(x)
+                                               elif k in color_codes:
+                                                       code_list.append(color_codes[x])
                                        else:
-                                               e = ParseError("%s%s'%s'" % (
-                                                       s.error_leader(myfile, s.lineno),
+                                               e = ParseError("'%s', line %s: %s'%s'" % (
+                                                       myfile, lineno,
                                                        "Undefined: ", x))
                                                if onerror:
                                                        onerror(e)
                                                else:
                                                        raise e
-                               codes[k] = "".join(code_list)
+                               if k in codes:
+                                       codes[k] = tuple(code_list)
+                               elif k in color_codes:
+                                       color_codes[k] = "".join(code_list)
        except (IOError, OSError), e:
                if e.errno == errno.ENOENT:
                        raise FileNotFound(myfile)
@@ -232,17 +220,6 @@ def parse_color_map(onerror=None):
                        raise PermissionDenied(myfile)
                raise
 
-try:
-       parse_color_map(onerror=lambda e: writemsg("%s\n" % str(e), noiselevel=-1))
-except FileNotFound:
-       pass
-except PermissionDenied, e:
-       writemsg("Permission denied: '%s'\n" % str(e), noiselevel=-1)
-       del e
-except PortageException, e:
-       writemsg("%s\n" % str(e), noiselevel=-1)
-       del e
-
 def nc_len(mystr):
        tmp = re.sub(esc_seq + "^m]+m", "", mystr);
        return len(tmp)
@@ -308,12 +285,24 @@ def nocolor():
        havecolor=0
 
 def resetColor():
-       return codes["reset"]
+       return color_codes["reset"]
+
+def map_code_to_color_code(code):
+       ret = ""
+       for color_code in codes[code]:
+               # allow stuff that has found it's way through ansi_code_pattern
+               ret += color_codes.get(color_code, color_code)
+       return ret
 
 def colorize(color_key, text):
        global havecolor
        if havecolor:
-               return codes[color_key] + text + codes["reset"]
+               if color_key in color_codes:
+                       return color_codes[color_key] + text + color_codes["reset"]
+               elif color_key in codes:
+                       return map_code_to_color_code(color_key) + text + color_codes["reset"]
+               else:
+                       return text
        else:
                return text
 
@@ -350,9 +339,9 @@ class ConsoleStyleFile(object):
                global havecolor
                if havecolor and self._styles:
                        for style in self._styles:
-                               self._file.write(codes[style])
+                               self._file.write(color_codes[style])
                        self._file.write(s)
-                       self._file.write(codes["reset"])
+                       self._file.write(color_codes["reset"])
                else:
                        self._file.write(s)
                if self.write_listener:
@@ -715,3 +704,14 @@ class TermProgressBar(ProgressBar):
                        image = image + "[" + (bar_width * "=") + \
                                ">" + ((max_bar_width - bar_width) * " ") + "]"
                        return image
+
+try:
+       _parse_color_map(onerror=lambda e: writemsg("%s\n" % str(e), noiselevel=-1))
+except FileNotFound:
+       pass
+except PermissionDenied, e:
+       writemsg("Permission denied: '%s'\n" % str(e), noiselevel=-1)
+       del e
+except PortageException, e:
+       writemsg("%s\n" % str(e), noiselevel=-1)
+       del e