deb-python-couleur/couleur/__init__.py

321 lines
8.6 KiB
Python

# -*- coding: utf-8 -*-
# <Couleur - fancy shell output for python>
# Copyright (C) <2010-2012> Gabriel Falcão <gabriel@nacaolivre.org>
#
# 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 <http://www.gnu.org/licenses/>.
import os
import re
import sys
import uuid
import platform
version = '0.5.0'
from StringIO import StringIO
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<main>(\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 translate_colors(string):
for attr in re.findall("[#][{]on[:](\w+)[}]", string):
string = string.replace(
u"#{on:%s}" % unicode(attr),
getattr(backcolors, attr)
)
for attr in re.findall("[#][{](\w+)[}]", string):
string = string.replace(
u"#{%s}" % unicode(attr),
getattr(forecolors, attr, "#{%s}" % attr)
).replace(
u"#{%s}" % unicode(attr),
getattr(modifiers, attr, "#{%s}" % attr)
)
return minify(string)
def ignore_colors(string):
up_count_regex = re.compile(ur'[#][{]up[}]')
up_count = len(up_count_regex.findall(string)) or 1
expression = u'^(?P<start>.*)([#][{]up[}])+(.*\\n){%d}' % up_count
up_supress_regex = re.compile(expression, re.MULTILINE)
string = up_supress_regex.sub('\g<start>', string)
for attr in re.findall("[#][{]on[:](\w+)[}]", string):
string = string.replace(u"#{on:%s}" % unicode(attr), u"")
for attr in re.findall("[#][{](\w+)[}]", string):
string = (string
.replace(u"#{%s}" % unicode(attr), u"")
.replace(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))
class StdOutMocker(Writer):
original = sys.__stdout__
class StdErrMocker(Writer):
original = sys.__stderr__
class Proxy(object):
def __init__(self, output):
self.old_write = output.write
if output is sys.__stdout__:
output = StdOutMocker()
elif output is sys.__stderr__:
output = StdErrMocker()
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))
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))
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):
if output not in _proxy_registry.keys():
_proxy_registry[output] = Proxy(output)
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)