Добавлен вывод на 16цветный терминал

master3.3
Mike Hiretsky 10 years ago
parent 11cd50c66d
commit d847825495

@ -1,318 +0,0 @@
#-*- coding: utf-8 -*-
# Copyright 2014 Calculate Ltd. http://www.calculate-linux.org
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import sys
import re
from itertools import chain,ifilter,tee
from os import path
class ConsoleCodesInfo:
"""
Коды цветов
"""
COLORCOUNT = 8
BLACK, RED, GREEN, BROWN, BLUE, PURPLE, CYAN, WHITE = range(0,COLORCOUNT)
DEFAULT = 9
FOREGROUND = 30
FOREGROUND_DEFAULT = 39
FOREGROUND_END = 37
BACKGROUND = 40
BACKGROUND_DEFAULT = 49
BACKGROUND_END = 47
BOLD = 1
HALFBRIGHT = 2
UNDERLINE = 4
NOUNDERLINE = 24
NORMAL = 22
RESET = 0
FOREGROUND256 = 38
COLOR256 = 5
BACKGROUND256 = 48
INVERT = 7
NOINVERT = 27
class ConsoleColor256:
"""Объект перевода RGB цветов в консоль 256color"""
colorList = (0,95,135,175,215,255)
colorMatch = [colorList[x]+(colorList[x+1]-colorList[x])/2
for x in range(0,5)] + [255]
colorHex = ["%02x"%val for val in colorList]
webMatch = re.compile("^#[0-9A-Fa-f]{6}$")
grayMatch = [6 + x * 11 for x in xrange(0,23)] + [255]
@staticmethod
def rgbToConsole(color):
"""Перевести RGB в 256 консоль
>>> ConsoleColor256.rgbToConsole("#5f00ff")
57
>>> ConsoleColor256.rgbToConsole("not #rrggbb") is None
True
"""
if color and ConsoleColor256.webMatch.match(color):
color = [int(x,base=16) for x in (color[5:7],color[3:5],color[1:3])]
# grayscale match
if abs(color[0]-color[1]) + abs(color[0]-color[2]) < 5:
for j,matchGray in enumerate(ConsoleColor256.grayMatch):
if color[0] <= matchGray:
return 232+j
# color match
colorNumber = 16
for i,_color in enumerate(color):
for j,halfColor in enumerate(ConsoleColor256.colorMatch):
if _color <= halfColor:
colorNumber += j * 6 ** i
break
return colorNumber
return None
@staticmethod
def consoleToRgb(color):
"""
Перевести 256 консоль в #RGB
>>> ConsoleColor256.consoleToRgb(216)
'#ffaf87'
>>> ConsoleColor256.consoleToRgb(15) is None
True
"""
if color >= 255:
return "#ffffff"
if color >= 232:
return "#{0:02x}{0:02x}{0:02x}".format((color-232)*11)
elif color >=16:
color-= 16
return "#%s"%"".join(ConsoleColor256.colorHex[color/x%6]
for x in (36,6,1))
else:
return None
class TextState(object):
"""
Параметры текста
>>> ts = TextState()
>>> ts.attr = TextState.Attributes.BOLD | TextState.Attributes.UNDERLINE
>>> ts.bold
True
>>> ts.underline
True
>>> ts.halfbright
False
"""
class Attributes:
NONE = 0
BOLD = 1
HALFBRIGHT = 2
UNDERLINE = 4
INVERT = 8
class Colors:
# обычные цвета
BLACK = "black"
RED = "red"
GREEN = "green"
BROWN = "brown"
BLUE = "blue"
PURPLE = "purple"
CYAN = "cyan"
GRAY = "gray"
# яркие цвета
DARK = "dark"
LIGHT_RED = "lightred"
LIGHT_GREEN = "lightgreen"
YELLOW = "yellow"
LIGHT_BLUE = "lightblue"
LIGHT_PURPLE = "lightpurple"
LIGHT_CYAN = "lightcyan"
WHITE = "white"
# темные цвета (консоль пониженной яркости)
DARK_BLACK = "darkblack"
DARK_RED = "darkred"
DARK_GREEN = "darkgreen"
DARK_BROWN = "darkbrown"
DARK_BLUE = "darkblue"
DARK_PURPLE = "darkpurple"
DARK_CYAN = "darkcyan"
DARK_GRAY = "darkgray"
DEFAULT = None
normalColors = [Colors.BLACK,Colors.RED,Colors.GREEN,Colors.BROWN,
Colors.BLUE,Colors.PURPLE,Colors.CYAN,Colors.GRAY]
lightColors = [Colors.DARK,Colors.LIGHT_RED,Colors.LIGHT_GREEN,
Colors.YELLOW,Colors.LIGHT_BLUE,Colors.LIGHT_PURPLE,
Colors.LIGHT_CYAN,Colors.WHITE]
darkColors = [Colors.DARK_BLACK,Colors.DARK_RED,Colors.DARK_GREEN,
Colors.DARK_BROWN,Colors.DARK_BLUE,Colors.DARK_PURPLE,
Colors.DARK_CYAN,Colors.DARK_GRAY]
def bitProperty(bit):
def set(self,value):
self.attr&=~bit
self.attr|=bit if value else 0
def get(self):
return bool(self.attr & bit)
return property(get,set)
# текст с подчеркиванием
underline = bitProperty(Attributes.UNDERLINE)
# текст жирный
bold = bitProperty(Attributes.BOLD)
# пониженная яркость
halfbright = bitProperty(Attributes.HALFBRIGHT)
# инверсия
invert = bitProperty(Attributes.INVERT)
def __init__(self,foreground=Colors.DEFAULT,
background=Colors.DEFAULT,
attr=Attributes.NONE):
self.foreground = foreground
self.background = background
self.attr = attr
def clone(self):
return self.__class__(self.foreground,self.background,
self.attr)
def __cmp__(self,other):
for i in ["foreground","background","attr"]:
cmp_res = cmp(getattr(self,i),getattr(other,i))
if cmp_res:
return cmp_res
return 0
@classmethod
def colors(cls):
return chain(xrange(0,8),[None])
class BaseColorMapping:
# соответствие внутренних цветов консольным
mapConsole_TS = {ConsoleCodesInfo.BLACK: TextState.Colors.BLACK,
ConsoleCodesInfo.RED: TextState.Colors.RED,
ConsoleCodesInfo.GREEN: TextState.Colors.GREEN,
ConsoleCodesInfo.BROWN: TextState.Colors.BROWN,
ConsoleCodesInfo.BLUE: TextState.Colors.BLUE,
ConsoleCodesInfo.PURPLE:TextState.Colors.PURPLE,
ConsoleCodesInfo.CYAN: TextState.Colors.CYAN,
ConsoleCodesInfo.WHITE: TextState.Colors.GRAY}
mapTS_Console = {v:k for k,v in mapConsole_TS.items()}
def __init__(self,base):
self.mapConsole_TS = self.mapConsole_TS.copy()
self.mapConsole_TS.update(base.mapConsole_TS)
self.mapTS_Console = {v:k for k,v in self.mapConsole_TS.items()}
class LightColorMapping(BaseColorMapping):
# соответствие внутренних цветов консольным
offset = ConsoleCodesInfo.COLORCOUNT
mapConsole_TS = {ConsoleCodesInfo.BLACK+offset: TextState.Colors.DARK,
ConsoleCodesInfo.RED+offset: TextState.Colors.LIGHT_RED,
ConsoleCodesInfo.GREEN+offset: TextState.Colors.LIGHT_GREEN,
ConsoleCodesInfo.BROWN+offset: TextState.Colors.YELLOW,
ConsoleCodesInfo.BLUE+offset: TextState.Colors.LIGHT_BLUE,
ConsoleCodesInfo.PURPLE+offset:TextState.Colors.LIGHT_PURPLE,
ConsoleCodesInfo.CYAN+offset: TextState.Colors.LIGHT_CYAN,
ConsoleCodesInfo.WHITE+offset: TextState.Colors.WHITE}
mapTS_Console = {v:k for k,v in mapConsole_TS.items()}
class ConsoleCodeMapping(BaseColorMapping):
"""
Декоратор для преобразования кодов цвета в код цвета тона или фона
Добавляет смещение к коду (3 -> 3 + codeoffset)
"""
def __init__(self,codeoffset,base):
self.mapConsole_TS = {codeoffset+k:v for k,v in base.mapConsole_TS.items()}
self.mapTS_Console = {v:k for k,v in self.mapConsole_TS.items()}
class SpanPalette:
"""
Палитра для SpanCssOutput (черное на белом)
"""
LOW_BRIGHT, NORMAL_BRIGHT, HIGH_BRIGHT = 0, 1, 2
defaultColor = ["Black","Black","DarkGrey"]
defaultBackground = "White"
normalBright = dict(zip(TextState.normalColors,
["Black","DarkRed","DarkGreen",
"Sienna","DarkBlue","DarkViolet",
"LightSeaGreen","Grey"]))
highBright = dict(zip(TextState.lightColors,
["DarkGrey","Red","Green",
"Yellow","RoyalBlue","Magenta",
"Cyan","White"]))
lowBright = dict(zip(TextState.darkColors,
["Black","Maroon","DarkGreen",
"SaddleBrown","DarkBlue","DarkViolet",
"LightSeaGreen","Grey"]))
mapHighNormal = dict(zip(TextState.normalColors,
TextState.lightColors))
mapLowNormal = dict(zip(TextState.normalColors,
TextState.darkColors))
def __init__(self):
self.colorMap = dict(self.normalBright.items()+
self.highBright.items()+
self.lowBright.items())
self.brightMap = {self.NORMAL_BRIGHT:{},
self.HIGH_BRIGHT:self.mapHighNormal,
self.LOW_BRIGHT:self.mapLowNormal}
def brightTransform(self,color,bright):
"""
Преобразовать основной цвет в зависимости от установленной яркости
"""
mapper = self.brightMap.get(bright,{})
return mapper.get(color,color)
def getBackgroundColor(self,color):
if not color:
return self.defaultBackground
return self.colorMap.get(color,color)
def getTextColor(self,color,bright):
"""
Получить соответствие цвета строкой
"""
if not color:
return self.defaultColor[bright]
color = self.brightTransform(color,bright)
return self.colorMap.get(color,color)
class DarkPastelsPalette(SpanPalette):
"""
Палитра идентичная Calculate в консоли (Dark Pastels)
"""
defaultColor = ["#DCDCDC","#DCDCDC","#709080"]
defaultBackground = "#2C2C2C"
normalBright = dict(zip(TextState.normalColors,
["#2C2C2C","#705050","#60B48A", "#DFAF8F",
"#9AB8D7","#DC8CC3","#8CD0D3","#DCDCDC"]))
highBright = dict(zip(TextState.lightColors,
["#709080","#DCA3A3","#72D5A3", "#F0DFAF",
"#94BFF3","#EC93D3","#93E0E3","#FFFFFF"]))

@ -16,9 +16,9 @@
from output import BaseOutput
from palette import (TextState, BaseColorMapping, ConsoleCodesInfo,
ConsoleCodeMapping, LightColorMapping, ConsoleColor256)
LightColorMapping, ConsoleColor256)
from calculate.lib.utils.tools import SavableIterator
from itertools import chain,ifilter
from itertools import ifilter
import re
class ConsoleCodesConverter(object):
@ -34,7 +34,7 @@ class ConsoleCodesConverter(object):
>>> cct = ConsoleCodesConverter(SpanCssOutput())
>>> outtext = "\033[32;1mHello\033[0;39m"
>>> cct.transform(outtext)
'<span style="color:Green;font-weight:bold;">Hello</span>'
'<span style="colortext:Green;font-weight:bold;">Hello</span>'
"""
class CodeElement:
@ -85,8 +85,10 @@ class ConsoleCodesConverter(object):
def __init__(self,output=None,escSymb="\033"):
self.output = output or BaseOutput()
self.escSymb = escSymb
self.escBlock = r"{esc}\[(\d+(?:;\d+)*)m".format(esc=escSymb)
self.reEscBlock = re.compile(self.escBlock)
self.reParse = re.compile(
"({0}\[\d+(?:;\d+)*m)?(.*?)(?=$|{0}\[\d)".format(escSymb),
"(?:{0})?(.*?)(?=$|{0})".format(self.escBlock),
re.DOTALL)
resetBoldHalfbright = lambda : (
(self.output.resetBold() or "") +
@ -128,10 +130,9 @@ class ConsoleCodesConverter(object):
Запустить преобразование текста
"""
def generator():
offset = len(self.escSymb)+1
for ctrl,txt in self.reParse.findall(s):
for ctrl,txt,_s in self.reParse.findall(s):
if ctrl:
codes = SavableIterator(ctrl[offset:-1].split(';'))
codes = SavableIterator(ctrl.split(';'))
for code in codes:
code = int(code)
res = ""
@ -146,6 +147,12 @@ class ConsoleCodesConverter(object):
yield self.output.endText()
return "".join(list(filter(None,generator())))
def detect(self,s):
"""
Определить есть ли в тексте управляющие последовательности
"""
return bool(self.reEscBlock.search(s))
class ConsoleCodes256Converter(ConsoleCodesConverter):
"""Расширяет возможность обработки 256 цветного терминала"""

@ -14,17 +14,18 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from calculate.lib.utils.tools import SavableIterator
from palette import (TextState, BaseColorMapping,
ConsoleCodeMapping, LightColorMapping, ConsoleColor256,
ConsoleCodesInfo, SpanPalette)
class BaseOutput:
class BaseOutput(object):
"""
Базовый вывод текста.
Вывод просто текста без изменения шрифта
"""
def __init__(self, state=None):
pass
@ -116,10 +117,12 @@ class BaseOutput:
Выключить инверсию
"""
class SaveAttrOutput(BaseOutput):
"""
Базовый класс с сохранением атрибутов
"""
def __init__(self, state=None):
self.prev_state = state.clone() if state else TextState()
self.current_state = self.prev_state.clone()
@ -168,6 +171,7 @@ class SaveAttrOutput(BaseOutput):
def setInvert(self):
self.current_state.invert = True
class ColorTerminalOutput(SaveAttrOutput):
"""
Форматирует текст для вывода в консоль
@ -188,27 +192,28 @@ class ColorTerminalOutput(SaveAttrOutput):
self.resetBold()
SaveAttrOutput.setHalfbright(self)
def handleForeground(self,prevstate,curstate,attrs):
def handleForeground(self, prevstate, curstate, attrs, tail_attrs):
cci = ConsoleCodesInfo
color = curstate.foreground
if color in self.mapColors:
attrs.append(self.mapColors[color])
elif color in self.mapLightColors:
if prevstate.bold == curstate.bold and curstate.bold == False:
if prevstate.bold == curstate.bold and not curstate.bold:
tail_attrs.append(cci.NORMAL)
attrs.append(cci.BOLD)
attrs.append(self.mapLightColors[color])
else:
attrs.append(cci.FOREGROUND_DEFAULT)
self.resetForeground()
def handleBackground(self,prevstate,curstate,attrs):
def handleBackground(self, prevstate, curstate, attrs, tail_attrs):
color = curstate.background
if color in self.mapBackgroundColors:
attrs.append(self.mapBackgroundColors[color])
else:
attrs.append(ConsoleCodesInfo.BACKGROUND_DEFAULT)
def handleIntensity(self,prevstate,curstate,attrs):
def handleIntensity(self, prevstate, curstate, attrs, tail_attrs):
if curstate.bold and curstate.bold != prevstate.bold:
attrs.append(ConsoleCodesInfo.BOLD)
elif (curstate.halfbright and
@ -217,13 +222,13 @@ class ColorTerminalOutput(SaveAttrOutput):
else:
attrs.append(ConsoleCodesInfo.NORMAL)
def handleUnderline(self,prevstate,curstate,attrs):
def handleUnderline(self, prevstate, curstate, attrs, tail_attrs):
if curstate.underline:
attrs.append(ConsoleCodesInfo.UNDERLINE)
else:
attrs.append(ConsoleCodesInfo.NOUNDERLINE)
def handleInvert(self,prevstate,curstate,attrs):
def handleInvert(self, prevstate, curstate, attrs, tail_attrs):
if curstate.invert:
attrs.append(ConsoleCodesInfo.INVERT)
else:
@ -233,7 +238,7 @@ class ColorTerminalOutput(SaveAttrOutput):
"""
Создать ESC последовательность для установки параметров текста
"""
attrs = []
attrs, tail_attrs = [],[]
# получить интенсивность (полутон и жирность относятся к интенсивности)
intensity = lambda x: x & (TextState.Attributes.HALFBRIGHT |
TextState.Attributes.BOLD)
@ -245,26 +250,16 @@ class ColorTerminalOutput(SaveAttrOutput):
attrs.append(ConsoleCodesInfo.RESET)
else:
if intensity(prevstate.attr) != intensity(curstate.attr):
self.handleIntensity(prevstate,curstate,attrs)
self.handleIntensity(prevstate, curstate, attrs, tail_attrs)
if prevstate.underline != curstate.underline:
self.handleUnderline(prevstate,curstate,attrs)
self.handleUnderline(prevstate, curstate, attrs, tail_attrs)
if prevstate.invert != curstate.invert:
self.handleInvert(prevstate,curstate,attrs)
self.handleInvert(prevstate, curstate, attrs, tail_attrs)
if prevstate.foreground != curstate.foreground:
self.handleForeground(prevstate,curstate,attrs)
self.handleForeground(prevstate, curstate, attrs, tail_attrs)
if prevstate.background != curstate.background:
self.handleBackground(prevstate,curstate,attrs)
return attrs
def handlePostAttr(self,prevstate,curstate):
"""
Добавление аттрибутов после выведенного текста
"""
if prevstate.foreground != curstate.foreground:
color = curstate.foreground
if (color in self.mapLightColors and
prevstate.bold == curstate.bold and curstate.bold == False):
return [ConsoleCodesInfo.NORMAL]
self.handleBackground(prevstate, curstate, attrs, tail_attrs)
return attrs, tail_attrs
def _createEscCode(self, attrs):
"""
@ -278,11 +273,11 @@ class ColorTerminalOutput(SaveAttrOutput):
Задание параметров текста и вывод его
"""
if self.prev_state != self.current_state:
attr = self._createAttrs(self.prev_state,self.current_state)
attr, tail_attrs = \
self._createAttrs(self.prev_state, self.current_state)
attr = self._createEscCode(attr)
postattr = self.handlePostAttr(self.prev_state,self.current_state)
if postattr:
postattr = self._createEscCode(postattr)
if tail_attrs:
postattr = self._createEscCode(tail_attrs)
else:
postattr = ""
self.prev_state = self.current_state.clone()
@ -295,12 +290,14 @@ class ColorTerminalOutput(SaveAttrOutput):
self.reset()
return self.outputText("")
class ColorTerminal256Output(ColorTerminalOutput):
"""
Вывод на 256 цветный терминал
"""
mapLightColors = LightColorMapping.mapTS_Console
def handleForeground(self,prevstate,curstate,attrs):
def handleForeground(self, prevstate, curstate, attrs, tail_attrs):
color = curstate.foreground
color256 = ConsoleColor256.rgbToConsole(color)
if not color256 and color in self.mapLightColors:
@ -310,9 +307,10 @@ class ColorTerminal256Output(ColorTerminalOutput):
ConsoleCodesInfo.COLOR256,
color256])
else:
ColorTerminalOutput.handleForeground(self,prevstate,curstate,attrs)
ColorTerminalOutput.handleForeground(self, prevstate, curstate,
attrs, tail_attrs)
def handleBackground(self,prevstate,curstate,attrs):
def handleBackground(self, prevstate, curstate, attrs, tail_attrs):
color = curstate.background
color256 = ConsoleColor256.rgbToConsole(color)
if not color256 and color in self.mapLightColors:
@ -322,20 +320,65 @@ class ColorTerminal256Output(ColorTerminalOutput):
ConsoleCodesInfo.COLOR256,
color256])
else:
ColorTerminalOutput.handleBackground(self,prevstate,curstate,attrs)
ColorTerminalOutput.handleBackground(self, prevstate, curstate,
attrs, tail_attrs)
def handlePostAttr(self,prevstate,curstate):
pass
class ColorTerminal16Output(ColorTerminalOutput):
"""
Вывод на 16 цветный терминал с преобразованием RGB к ближайшему базовому
"""
def __init__(self, state=None, palette=None):
SaveAttrOutput.__init__(self, state=state)
self.palette = palette
def _handleNearestColors(self, color):
"""
Обработка преобразования к ближайшему цвету
"""
standardColors = TextState.normalColors + TextState.lightColors
if self.palette and color not in standardColors:
return self.palette.getBaseColorByRGB(color)
return color
def handleForeground(self, prevstate, curstate, attrs, tail_attrs):
"""
Добавить преобразование RGB к ближайшему базовому
"""
_curstate = curstate.clone()
_curstate.foreground = \
self._handleNearestColors(curstate.foreground)
super(ColorTerminal16Output, self).handleForeground(
prevstate, _curstate,
attrs, tail_attrs)
def handleBackground(self, prevstate, curstate, attrs, tail_attrs):
"""
Добавить преобразование RGB к ближайшему базовому
"""
mapHighNormal = dict(zip(TextState.lightColors,
TextState.normalColors))
_curstate = curstate.clone()
_curstate.background = \
self._handleNearestColors(curstate.background)
# преобразовать яркий цвет к обычному
_curstate.background = \
mapHighNormal.get(_curstate.background, _curstate.background)
super(ColorTerminal16Output, self).handleBackground(
prevstate, _curstate,
attrs, tail_attrs)
class SpanCssOutput(SaveAttrOutput):
"""
Форматирует текст для вывода в консоль
"""
def __init__(self, state=None, palette=SpanPalette()):
SaveAttrOutput.__init__(self, state=state)
self.palette = palette
def getStringColor(self,color,bold=False,halfbright=False,background=False):
def getStringColor(self, color, bold=False, halfbright=False,
background=False):
"""
Получить название цвета по номеру и состоянию текста
"""
@ -357,7 +400,7 @@ class SpanCssOutput(SaveAttrOutput):
"""
style = []
colorAttr = ["color","background"]
colorAttr = ["colortext", "background"]
if curstate.invert:
colorAttr = colorAttr[1], colorAttr[0]
if (prevstate.foreground != curstate.foreground or
@ -370,7 +413,8 @@ class SpanCssOutput(SaveAttrOutput):
background=False)
style.append("%s:%s;" % (colorAttr[0], sColor))
if prevstate.background != curstate.background or curstate.invert:
sColor = self.getStringColor(curstate.background,background=True)
sColor = self.getStringColor(curstate.background,
background=True)
style.append("%s:%s;" % (colorAttr[1], sColor))
if prevstate.underline != curstate.underline:
if curstate.underline:

@ -0,0 +1,373 @@
#-*- coding: utf-8 -*-
# Copyright 2014 Calculate Ltd. http://www.calculate-linux.org
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import re
from itertools import chain
from math import pow
class ConsoleCodesInfo:
"""
Коды цветов
"""
COLORCOUNT = 8
BLACK, RED, GREEN, BROWN, BLUE, PURPLE, CYAN, WHITE = range(0, COLORCOUNT)
DEFAULT = 9
FOREGROUND = 30
FOREGROUND_DEFAULT = 39
FOREGROUND_END = 37
BACKGROUND = 40
BACKGROUND_DEFAULT = 49
BACKGROUND_END = 47
BOLD = 1
HALFBRIGHT = 2
UNDERLINE = 4
NOUNDERLINE = 24
NORMAL = 22
RESET = 0
FOREGROUND256 = 38
COLOR256 = 5
BACKGROUND256 = 48
INVERT = 7
NOINVERT = 27
class ConsoleColor256:
"""Объект перевода RGB цветов в консоль 256color"""
colorList = (0, 95, 135, 175, 215, 255)
colorMatch = [colorList[x] + (colorList[x + 1] - colorList[x]) / 2
for x in range(0, 5)] + [255]
colorHex = ["%02x" % val for val in colorList]
webMatch = re.compile("^#[0-9A-Fa-f]{6}$")
grayMatch = [6 + x * 11 for x in xrange(0, 23)] + [255]
@staticmethod
def rgbToConsole(color):
"""Перевести RGB в 256 консоль
>>> ConsoleColor256.rgbToConsole("#5f00ff")
57
>>> ConsoleColor256.rgbToConsole("not #rrggbb") is None
True
"""
color = ConsoleColor256.convertRgbToIntGroup(color)
if color:
# grayscale match
if abs(color[0] - color[1]) + abs(color[0] - color[2]) < 5:
for j, matchGray in enumerate(ConsoleColor256.grayMatch):
if color[0] <= matchGray:
return 232 + j
# colortext match
colorNumber = 16
for i, _color in enumerate(color):
for j, halfColor in enumerate(ConsoleColor256.colorMatch):
if _color <= halfColor:
colorNumber += j * 6 ** i
break
return colorNumber
return None
@staticmethod
def convertRgbToIntGroup(color):
"""Преобразовать #RRGGBB в кортеж целых (dec(RR),dec(GG),dec(BB))
В случае ошибки возвращает None
"""
if color and ConsoleColor256.webMatch.match(color):
return [int(x, base=16) for x in
(color[5:7], color[3:5], color[1:3])]
return None
@staticmethod
def consoleToRgb(color):
"""
Перевести 256 консоль в #RGB
>>> ConsoleColor256.consoleToRgb(216)
'#ffaf87'
>>> ConsoleColor256.consoleToRgb(15) is None
True
"""
if color >= 255:
return "#ffffff"
if color >= 232:
return "#{0:02x}{0:02x}{0:02x}".format((color - 232) * 11)
elif color >= 16:
color -= 16
return "#%s" % "".join(ConsoleColor256.colorHex[color / x % 6]
for x in (36, 6, 1))
else:
return None
class TextState(object):
"""
Параметры текста
>>> ts = TextState()
>>> ts.attr = TextState.Attributes.BOLD | TextState.Attributes.UNDERLINE
>>> ts.bold
True
>>> ts.underline
True
>>> ts.halfbright
False
"""
class Attributes:
NONE = 0
BOLD = 1
HALFBRIGHT = 2
UNDERLINE = 4
INVERT = 8
class Colors:
# обычные цвета
BLACK = "black"
RED = "red"
GREEN = "green"
BROWN = "brown"
BLUE = "blue"
PURPLE = "purple"
CYAN = "cyan"
GRAY = "gray"
# яркие цвета
DARK = "dark"
LIGHT_RED = "lightred"
LIGHT_GREEN = "lightgreen"
YELLOW = "yellow"
LIGHT_BLUE = "lightblue"
LIGHT_PURPLE = "lightpurple"
LIGHT_CYAN = "lightcyan"
WHITE = "white"
# темные цвета (консоль пониженной яркости)
DARK_BLACK = "darkblack"
DARK_RED = "darkred"
DARK_GREEN = "darkgreen"
DARK_BROWN = "darkbrown"
DARK_BLUE = "darkblue"
DARK_PURPLE = "darkpurple"
DARK_CYAN = "darkcyan"
DARK_GRAY = "darkgray"
DEFAULT = None
normalColors = [Colors.BLACK, Colors.RED, Colors.GREEN, Colors.BROWN,
Colors.BLUE, Colors.PURPLE, Colors.CYAN, Colors.GRAY]
lightColors = [Colors.DARK, Colors.LIGHT_RED, Colors.LIGHT_GREEN,
Colors.YELLOW, Colors.LIGHT_BLUE, Colors.LIGHT_PURPLE,
Colors.LIGHT_CYAN, Colors.WHITE]
darkColors = [Colors.DARK_BLACK, Colors.DARK_RED, Colors.DARK_GREEN,
Colors.DARK_BROWN, Colors.DARK_BLUE, Colors.DARK_PURPLE,
Colors.DARK_CYAN, Colors.DARK_GRAY]
def bitProperty(bit):
def set(self, value):
self.attr &= ~bit
self.attr |= bit if value else 0
def get(self):
return bool(self.attr & bit)
return property(get, set)
# текст с подчеркиванием
underline = bitProperty(Attributes.UNDERLINE)
# текст жирный
bold = bitProperty(Attributes.BOLD)
# пониженная яркость
halfbright = bitProperty(Attributes.HALFBRIGHT)
# инверсия
invert = bitProperty(Attributes.INVERT)
def __init__(self, foreground=Colors.DEFAULT,
background=Colors.DEFAULT,
attr=Attributes.NONE):
self.foreground = foreground
self.background = background
self.attr = attr
def clone(self):
return self.__class__(self.foreground, self.background,
self.attr)
def __cmp__(self, other):
for i in ["foreground", "background", "attr"]:
cmp_res = cmp(getattr(self, i), getattr(other, i))
if cmp_res:
return cmp_res
return 0
@classmethod
def colors(cls):
return chain(xrange(0, 8), [None])
class BaseColorMapping:
# соответствие внутренних цветов консольным
mapConsole_TS = {ConsoleCodesInfo.BLACK: TextState.Colors.BLACK,
ConsoleCodesInfo.RED: TextState.Colors.RED,
ConsoleCodesInfo.GREEN: TextState.Colors.GREEN,
ConsoleCodesInfo.BROWN: TextState.Colors.BROWN,
ConsoleCodesInfo.BLUE: TextState.Colors.BLUE,
ConsoleCodesInfo.PURPLE: TextState.Colors.PURPLE,
ConsoleCodesInfo.CYAN: TextState.Colors.CYAN,
ConsoleCodesInfo.WHITE: TextState.Colors.GRAY}
mapTS_Console = {v: k for k, v in mapConsole_TS.items()}
def __init__(self, base):
self.mapConsole_TS = self.mapConsole_TS.copy()
self.mapConsole_TS.update(base.mapConsole_TS)
self.mapTS_Console = {v: k for k, v in self.mapConsole_TS.items()}
class LightColorMapping(BaseColorMapping):
# соответствие внутренних цветов консольным
offset = ConsoleCodesInfo.COLORCOUNT
mapConsole_TS = {ConsoleCodesInfo.BLACK + offset: TextState.Colors.DARK,
ConsoleCodesInfo.RED + offset: TextState.Colors.LIGHT_RED,
ConsoleCodesInfo.GREEN + offset: TextState.Colors.LIGHT_GREEN,
ConsoleCodesInfo.BROWN + offset: TextState.Colors.YELLOW,
ConsoleCodesInfo.BLUE + offset: TextState.Colors.LIGHT_BLUE,
ConsoleCodesInfo.PURPLE + offset: TextState.Colors.LIGHT_PURPLE,
ConsoleCodesInfo.CYAN + offset: TextState.Colors.LIGHT_CYAN,
ConsoleCodesInfo.WHITE + offset: TextState.Colors.WHITE}
mapTS_Console = {v: k for k, v in mapConsole_TS.items()}
class ConsoleCodeMapping(BaseColorMapping):
"""
Декоратор для преобразования кодов цвета в код цвета тона или фона
Добавляет смещение к коду (3 -> 3 + codeoffset)
"""
def __init__(self, codeoffset, base):
self.mapConsole_TS = {codeoffset + k: v for k, v in
base.mapConsole_TS.items()}
self.mapTS_Console = {v: k for k, v in self.mapConsole_TS.items()}
class SpanPalette:
"""
Палитра для SpanCssOutput (черное на белом)
"""
LOW_BRIGHT, NORMAL_BRIGHT, HIGH_BRIGHT = 0, 1, 2
defaultColor = ["Black", "Black", "DarkGrey"]
defaultBackground = "White"
normalBright = dict(zip(TextState.normalColors,
["Black", "DarkRed", "DarkGreen",
"Sienna", "DarkBlue", "DarkViolet",
"LightSeaGreen", "Grey"]))
highBright = dict(zip(TextState.lightColors,
["DarkGrey", "Red", "Green",
"Yellow", "RoyalBlue", "Magenta",
"Cyan", "White"]))
lowBright = dict(zip(TextState.darkColors,
["Black", "Maroon", "DarkGreen",
"SaddleBrown", "DarkBlue", "DarkViolet",
"LightSeaGreen", "Grey"]))
mapHighNormal = dict(zip(TextState.normalColors,
TextState.lightColors))
mapLowNormal = dict(zip(TextState.normalColors,
TextState.darkColors))
def __init__(self):
self.colorMap = dict(self.normalBright.items() +
self.highBright.items() +
self.lowBright.items())
self.brightMap = {self.NORMAL_BRIGHT: {},
self.HIGH_BRIGHT: self.mapHighNormal,
self.LOW_BRIGHT: self.mapLowNormal}
def brightTransform(self, color, bright):
"""
Преобразовать основной цвет в зависимости от установленной яркости
"""
mapper = self.brightMap.get(bright, {})
return mapper.get(color, color)
def getBackgroundColor(self, color=TextState.Colors.DEFAULT):
if not color:
return self.defaultBackground
return self.colorMap.get(color, color)
def getTextColor(self, color=TextState.Colors.DEFAULT,
bright=NORMAL_BRIGHT):
"""
Получить соответствие цвета строкой
"""
if not color:
return self.defaultColor[bright]
color = self.brightTransform(color, bright)
return self.colorMap.get(color, color)
def getBaseColorByRGB(self, rgb_color):
"""Получить ближайший базовый цвет, согласно палитре
Например: #709080 -> TextState.Colors.DARK
Args:
rgb_color: цвет #rrggbb (или словесный)
Returns: TextState.Colors если подходящий цвет найден
"""
# TODO: исключить при приобразовании одинаковые цвет фона и тона
def calculate_color_diff(Ri,Ro,Gi,Go,Bi,Bo):
# вычислить отличие цветов
kR, kG, kB = 30, 59, 25
return kR*pow(Ri-Ro, 2)+kG*pow(Gi-Go, 2)+kB*pow(Bi-Bo, 2)
color = ConsoleColor256.convertRgbToIntGroup(rgb_color)
diffList = []
if color:
for key, val in self.colorMap.items():
intVal = ConsoleColor256.convertRgbToIntGroup(val)
if intVal:
diffList.append((
calculate_color_diff(*chain(*zip(color, intVal))), key))
if diffList:
#print diffList,sorted(diffList)[0]
for diff, color in sorted(diffList):
return color
# если вместо RGB в палитре цвет указан словом
elif rgb_color in self.colorMap.values():
for key, val in self.colorMap.items():
if val == rgb_color:
return key
return None
class DarkPastelsPalette(SpanPalette):
"""
Палитра идентичная Calculate в консоли (Dark Pastels)
"""
defaultColor = ["#DCDCDC", "#DCDCDC", "#709080"]
defaultBackground = "#2C2C2C"
normalBright = dict(zip(TextState.normalColors,
["#2C2C2C", "#705050", "#60B48A", "#DFAF8F",
"#9AB8D7", "#DC8CC3", "#8CD0D3", "#DCDCDC"]))
highBright = dict(zip(TextState.lightColors,
["#709080", "#DCA3A3", "#72D5A3", "#F0DFAF",
"#94BFF3", "#EC93D3", "#93E0E3", "#FFFFFF"]))

@ -44,7 +44,7 @@ setup(
module_name + '.variables',
module_name + '.mod',
module_name + '.utils',
module_name + '.utils.color'],
module_name + '.utils.colortext'],
data_files = [("/etc/calculate", []),
("/var/calculate/remote", []),
("/var/log/calculate", [])]

Loading…
Cancel
Save