# -*- coding: utf-8 -*- # # Copyright (C) <2010-2012> Gabriel Falcão # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . import os import re import sys import uuid import platform version = '0.6.0' from StringIO import StringIO class delimiters: DEFAULT = ("#{", "}") SQUARE_BRACKETS = ("[", "]") SUPPORTS_ANSI = False for handle in [sys.stdout, sys.stderr]: if (hasattr(handle, "isatty") and handle.isatty()) or \ ('TERM' in os.environ and os.environ['TERM'] == 'ANSI'): if platform.system() == 'Windows' and not ( 'TERM' in os.environ and os.environ['TERM'] == 'ANSI'): SUPPORTS_ANSI = False else: SUPPORTS_ANSI = True if os.getenv('COULEUR_DISABLE'): SUPPORTS_ANSI = False if os.getenv('FORCE_COULEUR'): SUPPORTS_ANSI = True def minify(string): regex_items = re.compile('(\033\[(\d+)m)') regex_main = re.compile('(?P
(\033\[(\d+)m){2,})') replaced = string main_found = regex_main.search(string) if main_found: existent = main_found.group('main') found = ";".join(x[1] for x in regex_items.findall(existent)) if found: replaced = string.replace(existent, '\033[%sm' % found) return replaced def wrapper_factory(delimiter): delimiter_in, delimiter_out = delimiter def wrap_escaped(middle_part): din = "".join([r"[{0}]".format(d) for d in delimiter_in]) dout = "".join([r"[{0}]".format(d) for d in delimiter_out]) return r"".join([din, middle_part, dout]) def wrap_normal(middle_part): return r"".join([delimiter_in, middle_part, delimiter_out]) return wrap_escaped, wrap_normal def translate_colors(string, delimiter): wrap_escaped, wrap_normal = wrapper_factory(delimiter) for attr in re.findall(wrap_escaped("on[:](\w+)"), string): string = string.replace( wrap_normal(u"on:%s") % unicode(attr), getattr(backcolors, attr) ) for attr in re.findall(wrap_escaped("(\w+)"), string): string = string.replace( wrap_normal(u"%s") % unicode(attr), getattr(forecolors, attr, wrap_normal("%s") % attr) ).replace( wrap_normal(u"%s") % unicode(attr), getattr(modifiers, attr, wrap_normal("%s") % attr) ) return minify(string) def ignore_colors(string, delimiter): wrap_escaped, wrap_normal = wrapper_factory(delimiter) up_count_regex = re.compile(wrap_escaped(ur'up')) up_count = len(up_count_regex.findall(string)) or 1 expression = (u'^(?P.*)(' + wrap_escaped('up') + u')(.*\\n){%d}') % up_count up_supress_regex = re.compile(expression, re.MULTILINE) string = up_supress_regex.sub('\g', string) for attr in re.findall(wrap_escaped("on[:](\w+)"), string): string = string.replace(wrap_normal(u"on:%s") % unicode(attr), u"") for attr in re.findall(wrap_escaped("(\w+)"), string): string = (string .replace(wrap_normal(u"%s") % unicode(attr), u"") .replace(wrap_normal(u"%s") % unicode(attr), u"")) return string class Writer(StringIO): original = None translate = True def write(self, string): f = self.translate and translate_colors or ignore_colors self.original.write(f(string, self.delimiter)) class StdOutMocker(Writer): original = sys.__stdout__ class StdErrMocker(Writer): original = sys.__stderr__ class Proxy(object): def __init__(self, output, delimiter=delimiters.DEFAULT): self.old_write = output.write self.delimiter = delimiter if output is sys.__stdout__: output = StdOutMocker(delimiter) elif output is sys.__stderr__: output = StdErrMocker(delimiter) self.output = output def ignore(self): self.output.translate = False if not isinstance(self.output, (StdErrMocker, StdOutMocker)): self.output.write = lambda x: self.old_write(ignore_colors(x, self.delimiter)) def enable(self): self.disable() self.output.translate = True if isinstance(self.output, StdOutMocker): sys.stdout = self.output elif isinstance(self.output, StdErrMocker): sys.stderr = self.output else: self.output.write = lambda x: self.old_write(translate_colors(x, self.delimiter)) def disable(self): if isinstance(self.output, StdOutMocker): sys.stdout = self.output.original elif isinstance(self.output, StdErrMocker): sys.stderr = self.output.original else: self.output.write = self.old_write _proxy_registry = {} def proxy(output, delimiter=delimiters.DEFAULT): if output not in _proxy_registry.keys(): _proxy_registry[output] = Proxy(output, delimiter) return _proxy_registry[output] def ansify(number): """Wraps the given ansi code to a proper escaped python output Arguments: - `number`: the code in question """ number = unicode(number) return u'\033[%sm' % number class modifiers: reset = ansify(0) bold = ansify(1) blink = ansify(5) italic = ansify(3) underline = ansify(4) inverse = ansify(7) strikethrough = ansify(9) up = u'\r\033[A' class forecolors: black = ansify(30) red = ansify(31) green = ansify(32) yellow = ansify(33) blue = ansify(34) magenta = ansify(35) cyan = ansify(36) white = ansify(37) normal = ansify(39) class backcolors: black = ansify(40) red = ansify(41) green = ansify(42) yellow = ansify(43) blue = ansify(44) magenta = ansify(45) cyan = ansify(46) white = ansify(47) normal = ansify(49) class empty(object): def __getattr__(self, attr): if attr != 'up': return u'' else: return modifiers.up _sep1 = u'_on_' _sep2 = u'_and_' class Shell(object): def __init__(self, output=None, indent=2, linebreak=False, bold=False, disabled=not SUPPORTS_ANSI): self._indentation_factor = indent self._indent = 0 self._linebreak = linebreak self._bold = bold self._in_format = False self._disabled = disabled self.output = output or sys.stdout if disabled: self._backcolors = empty() self._forecolors = empty() self._modifiers = empty() else: self._backcolors = backcolors() self._forecolors = forecolors() self._modifiers = modifiers() def indent(self): self._indent += self._indentation_factor def dedent(self): self._indent -= self._indentation_factor def _fore(self, color): def get(what): try: r = getattr(self._modifiers, what) except AttributeError: r = getattr(self._forecolors, what) return r args = map(get, color.split(u"_")) return u"".join(args) def _back(self, color): return getattr(self._backcolors, color) def _printer_for(self, color): colors = color.split(_sep1) parts = [ self._fore(colors.pop(0)), u"%s", self._modifiers.reset ] if colors: parts.insert(0, self._back(colors[0])) if not self._in_format: if self._bold: parts.insert(0, self._modifiers.bold) if self._indent: parts.insert(0, u' ' * self._indent) if self._linebreak: parts.append(u"\n") string = u"".join(map(unicode, parts)) def dec(z, replace=False): pre = unicode(replace and self._modifiers.up or u'') self.output.write(pre) self.output.write(string % unicode(z.decode('utf-8'))) return dec def __getattr__(self, attr): if not attr.startswith(u"_"): if _sep2 in attr: self._in_format = True printers = map(self._printer_for, attr.split(_sep2)) def dec(string, replace=False): unique = str(uuid.uuid4()) string = string.replace(ur'\|', unique) parts = string.split(ur"|") if replace: self.output.write(self._modifiers.up) if self._indent: self.output.write(u' ' * self._indent) if self._bold: self.output.write(self._modifiers.bold) for part, output in zip(parts, printers): output(part.replace(unique, ur"|")) if self._linebreak: self.output.write(u"\n") self._in_format = False return dec return self._printer_for(attr) return super(Shell, self).__getattribute__(attr)