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