wingo - flumotion/trunk/flumotion/extern/log

flumotion-commit at lists.fluendo.com flumotion-commit at lists.fluendo.com
Tue Jul 24 16:44:22 CEST 2007


Author: wingo
Date: Tue Jul 24 16:44:19 2007
New Revision: 5350

Added:
   flumotion/trunk/flumotion/extern/log/termcolor.py
Modified:
   flumotion/trunk/flumotion/extern/log/ChangeLog
   flumotion/trunk/flumotion/extern/log/log.py
Log:
2007-07-24  Andy Wingo  <wingo at pobox.com>

	Patch by: Philippe Normand <philippe fluendo.com>

	* log.py: Optionally support colorizing the log level. Fixes #642.
	Patch modified slightly.

	* termcolor.py: New file, imported only if we are enabling color
	debug output.



Modified: flumotion/trunk/flumotion/extern/log/ChangeLog
==============================================================================
--- flumotion/trunk/flumotion/extern/log/ChangeLog	(original)
+++ flumotion/trunk/flumotion/extern/log/ChangeLog	Tue Jul 24 16:44:19 2007
@@ -1,3 +1,13 @@
+2007-07-24  Andy Wingo  <wingo at pobox.com>
+
+	Patch by: Philippe Normand <philippe fluendo.com>
+
+	* log.py: Optionally support colorizing the log level. Fixes #642.
+	Patch modified slightly.
+
+	* termcolor.py: New file, imported only if we are enabling color
+	debug output.
+
 2007-06-25  Andy Wingo  <wingo at pobox.com>
 
 	* log.py (safeprintf): New procedure. See #613.

Modified: flumotion/trunk/flumotion/extern/log/log.py
==============================================================================
--- flumotion/trunk/flumotion/extern/log/log.py	(original)
+++ flumotion/trunk/flumotion/extern/log/log.py	Tue Jul 24 16:44:19 2007
@@ -23,7 +23,7 @@
 import time
 import types
 import traceback
-
+    
 # environment variables controlling levels for each category
 _DEBUG = "*:1"
 # name of the environment variable controlling our logging
@@ -52,6 +52,15 @@
 DEBUG = 4
 LOG = 5
 
+COLORS = {ERROR: 'RED',
+          WARN: 'YELLOW',
+          INFO: 'GREEN',
+          DEBUG: 'BLUE',
+          LOG: 'CYAN'
+          }
+
+_FORMATTED_LEVELS = []
+
 def getLevelName(level):
     """
     Return the name of a log level.
@@ -60,6 +69,11 @@
            "Bad debug level"
     return ('ERROR', 'WARN', 'INFO', 'DEBUG', 'LOG')[level - 1]
 
+def getFormattedLevelName(level):
+    assert isinstance(level, int) and level > 0 and level < 6, \
+           "Bad debug level"
+    return _FORMATTED_LEVELS[level - 1]
+
 def registerCategory(category):
     """
     Register a given category in the debug system.
@@ -326,17 +340,44 @@
 
     # level   pid     object   cat      time
     # 5 + 1 + 7 + 1 + 32 + 1 + 17 + 1 + 15 == 80
-    safeprintf(sys.stderr, '%-5s [%5d] %-32s %-17s %-15s ',
-              getLevelName(level), os.getpid(), o, category,
-              time.strftime("%b %d %H:%M:%S"))
+    safeprintf(sys.stderr, '%s [%5d] %-32s %-17s %-15s ',
+               getFormattedLevelName(level), os.getpid(), o, category,
+               time.strftime("%b %d %H:%M:%S"))
     safeprintf(sys.stderr, '%-4s %s %s\n', "", message, where)
 
     sys.stderr.flush()
 
+def _preformatLevels(noColorEnvVarName):
+    levels = ('ERROR', 'WARN', 'INFO', 'DEBUG', 'LOG')
+    format = '%-5s'
+
+    try:
+        import termcolor
+    except ImportError:
+        # we don't need to catch this if termcolor is in same package as
+        # log.py
+        termcolor = None
+
+    if (noColorEnvVarName is not None
+        and termcolor is not None
+        and (noColorEnvVarName not in os.environ
+             or not os.environ[noColorEnvVarName])):
+
+        t = termcolor.TerminalController()
+        def formatter(level):
+            return ''.join((t.BOLD, getattr(t, COLORS[level]),
+                            format % (levels[level-1],), t.NORMAL))
+    else:
+        def formatter(level):
+            return format % (levels[level-1],)
+
+    for level in ERROR, WARN, INFO, DEBUG, LOG:
+        _FORMATTED_LEVELS.append(formatter(level))
+
 ### "public" useful API
 
 # setup functions
-def init(envVarName):
+def init(envVarName, enableColorOutput=False):
     """
     Initialize the logging system and parse the environment variable
     of the given name.
@@ -350,6 +391,11 @@
     global _ENV_VAR_NAME
     _ENV_VAR_NAME = envVarName
 
+    if enableColorOutput:
+        _preformatLevels(envVarName + "_NO_COLOR")
+    else:
+        _preformatLevels(None)
+
     if os.environ.has_key(envVarName):
         # install a log handler that uses the value of the environment var
         setDebug(os.environ[envVarName])

Added: flumotion/trunk/flumotion/extern/log/termcolor.py
==============================================================================
--- (empty file)
+++ flumotion/trunk/flumotion/extern/log/termcolor.py	Tue Jul 24 16:44:19 2007
@@ -0,0 +1,201 @@
+# Copyright 2006 Edward Loper. May be distributed under the same terms
+# as Python itself.
+#
+# From http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/475116
+
+import sys, re
+
+class TerminalController:
+    """
+    A class that can be used to portably generate formatted output to
+    a terminal.  
+    
+    `TerminalController` defines a set of instance variables whose
+    values are initialized to the control sequence necessary to
+    perform a given action.  These can be simply included in normal
+    output to the terminal:
+
+        >>> term = TerminalController()
+        >>> print 'This is '+term.GREEN+'green'+term.NORMAL
+
+    Alternatively, the `render()` method can used, which replaces
+    '${action}' with the string required to perform 'action':
+
+        >>> term = TerminalController()
+        >>> print term.render('This is ${GREEN}green${NORMAL}')
+
+    If the terminal doesn't support a given action, then the value of
+    the corresponding instance variable will be set to ''.  As a
+    result, the above code will still work on terminals that do not
+    support color, except that their output will not be colored.
+    Also, this means that you can test whether the terminal supports a
+    given action by simply testing the truth value of the
+    corresponding instance variable:
+
+        >>> term = TerminalController()
+        >>> if term.CLEAR_SCREEN:
+        ...     print 'This terminal supports clearning the screen.'
+
+    Finally, if the width and height of the terminal are known, then
+    they will be stored in the `COLS` and `LINES` attributes.
+    """
+    # Cursor movement:
+    BOL = ''             #: Move the cursor to the beginning of the line
+    UP = ''              #: Move the cursor up one line
+    DOWN = ''            #: Move the cursor down one line
+    LEFT = ''            #: Move the cursor left one char
+    RIGHT = ''           #: Move the cursor right one char
+
+    # Deletion:
+    CLEAR_SCREEN = ''    #: Clear the screen and move to home position
+    CLEAR_EOL = ''       #: Clear to the end of the line.
+    CLEAR_BOL = ''       #: Clear to the beginning of the line.
+    CLEAR_EOS = ''       #: Clear to the end of the screen
+
+    # Output modes:
+    BOLD = ''            #: Turn on bold mode
+    BLINK = ''           #: Turn on blink mode
+    DIM = ''             #: Turn on half-bright mode
+    REVERSE = ''         #: Turn on reverse-video mode
+    NORMAL = ''          #: Turn off all modes
+
+    # Cursor display:
+    HIDE_CURSOR = ''     #: Make the cursor invisible
+    SHOW_CURSOR = ''     #: Make the cursor visible
+
+    # Terminal size:
+    COLS = None          #: Width of the terminal (None for unknown)
+    LINES = None         #: Height of the terminal (None for unknown)
+
+    # Foreground colors:
+    BLACK = BLUE = GREEN = CYAN = RED = MAGENTA = YELLOW = WHITE = ''
+    
+    # Background colors:
+    BG_BLACK = BG_BLUE = BG_GREEN = BG_CYAN = ''
+    BG_RED = BG_MAGENTA = BG_YELLOW = BG_WHITE = ''
+    
+    _STRING_CAPABILITIES = """
+    BOL=cr UP=cuu1 DOWN=cud1 LEFT=cub1 RIGHT=cuf1
+    CLEAR_SCREEN=clear CLEAR_EOL=el CLEAR_BOL=el1 CLEAR_EOS=ed BOLD=bold
+    BLINK=blink DIM=dim REVERSE=rev UNDERLINE=smul NORMAL=sgr0
+    HIDE_CURSOR=cinvis SHOW_CURSOR=cnorm""".split()
+    _COLORS = """BLACK BLUE GREEN CYAN RED MAGENTA YELLOW WHITE""".split()
+    _ANSICOLORS = "BLACK RED GREEN YELLOW BLUE MAGENTA CYAN WHITE".split()
+
+    def __init__(self, term_stream=sys.stdout):
+        """
+        Create a `TerminalController` and initialize its attributes
+        with appropriate values for the current terminal.
+        `term_stream` is the stream that will be used for terminal
+        output; if this stream is not a tty, then the terminal is
+        assumed to be a dumb terminal (i.e., have no capabilities).
+        """
+        # Curses isn't available on all platforms
+        try: import curses
+        except: return
+
+        # If the stream isn't a tty, then assume it has no capabilities.
+        if not term_stream.isatty(): return
+
+        # Check the terminal type.  If we fail, then assume that the
+        # terminal has no capabilities.
+        try: curses.setupterm()
+        except: return
+
+        # Look up numeric capabilities.
+        self.COLS = curses.tigetnum('cols')
+        self.LINES = curses.tigetnum('lines')
+        
+        # Look up string capabilities.
+        for capability in self._STRING_CAPABILITIES:
+            (attrib, cap_name) = capability.split('=')
+            setattr(self, attrib, self._tigetstr(cap_name) or '')
+
+        # Colors
+        set_fg = self._tigetstr('setf')
+        if set_fg:
+            for i,color in zip(range(len(self._COLORS)), self._COLORS):
+                setattr(self, color, curses.tparm(set_fg, i) or '')
+        set_fg_ansi = self._tigetstr('setaf')
+        if set_fg_ansi:
+            for i,color in zip(range(len(self._ANSICOLORS)), self._ANSICOLORS):
+                setattr(self, color, curses.tparm(set_fg_ansi, i) or '')
+        set_bg = self._tigetstr('setb')
+        if set_bg:
+            for i,color in zip(range(len(self._COLORS)), self._COLORS):
+                setattr(self, 'BG_'+color, curses.tparm(set_bg, i) or '')
+        set_bg_ansi = self._tigetstr('setab')
+        if set_bg_ansi:
+            for i,color in zip(range(len(self._ANSICOLORS)), self._ANSICOLORS):
+                setattr(self, 'BG_'+color, curses.tparm(set_bg_ansi, i) or '')
+
+    def _tigetstr(self, cap_name):
+        # String capabilities can include "delays" of the form "$<2>".
+        # For any modern terminal, we should be able to just ignore
+        # these, so strip them out.
+        import curses
+        cap = curses.tigetstr(cap_name) or ''
+        return re.sub(r'\$<\d+>[/*]?', '', cap)
+
+    def render(self, template):
+        """
+        Replace each $-substitutions in the given template string with
+        the corresponding terminal control string (if it's defined) or
+        '' (if it's not).
+        """
+        return re.sub(r'\$\$|\${\w+}', self._render_sub, template)
+
+    def _render_sub(self, match):
+        s = match.group()
+        if s == '$$': return s
+        else: return getattr(self, s[2:-1])
+
+#######################################################################
+# Example use case: progress bar
+#######################################################################
+
+class ProgressBar:
+    """
+    A 3-line progress bar, which looks like::
+    
+                                Header
+        20% [===========----------------------------------]
+                           progress message
+
+    The progress bar is colored, if the terminal supports color
+    output; and adjusts to the width of the terminal.
+    """
+    BAR = '%3d%% ${GREEN}[${BOLD}%s%s${NORMAL}${GREEN}]${NORMAL}\n'
+    HEADER = '${BOLD}${CYAN}%s${NORMAL}\n\n'
+        
+    def __init__(self, term, header):
+        self.term = term
+        if not (self.term.CLEAR_EOL and self.term.UP and self.term.BOL):
+            raise ValueError("Terminal isn't capable enough -- you "
+                             "should use a simpler progress dispaly.")
+        self.width = self.term.COLS or 75
+        self.bar = term.render(self.BAR)
+        self.header = self.term.render(self.HEADER % header.center(self.width))
+        self.cleared = 1 #: true if we haven't drawn the bar yet.
+        self.update(0, '')
+
+    def update(self, percent, message):
+        if self.cleared:
+            sys.stdout.write(self.header)
+            self.cleared = 0
+        n = int((self.width-10)*percent)
+        sys.stdout.write(
+            self.term.BOL + self.term.UP + self.term.CLEAR_EOL +
+            (self.bar % (100*percent, '='*n, '-'*(self.width-10-n))) +
+            self.term.CLEAR_EOL + message.center(self.width))
+
+    def clear(self):
+        if not self.cleared:
+            sys.stdout.write(self.term.BOL + self.term.CLEAR_EOL +
+                             self.term.UP + self.term.CLEAR_EOL +
+                             self.term.UP + self.term.CLEAR_EOL)
+            self.cleared = 1
+
+if __name__ == '__main__':
+    term = TerminalController()
+    print term.render('${BOLD}${RED}Error:${NORMAL}'), 'paper is ripped'


More information about the flumotion-commit mailing list