Добавлен вывод на 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,18 +147,24 @@ 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 цветного терминала"""
class Color256Element(ConsoleCodesConverter.CodeElement):
def __init__(self,action=lambda x:None, begin=None):
def __init__(self, action=lambda x: None, begin=None):
self.action = action
self.begin = begin
def tryParse(self,code):
def tryParse(self, code):
return code == self.begin
def parse(self,code,codes):
def parse(self, code, codes):
"""
Тон: 38;5;0-255
Фон: 48;5;0-255
@ -176,8 +183,8 @@ class ConsoleCodes256Converter(ConsoleCodesConverter):
# если после 38 не 5 - не обрабатываем этот код
codes.restore()
def __init__(self,*args,**kwargs):
ConsoleCodesConverter.__init__(self,*args,**kwargs)
def __init__(self, *args, **kwargs):
ConsoleCodesConverter.__init__(self, *args, **kwargs)
cci = ConsoleCodesInfo
# обработчики кодов для вывода в 256
foreground256 = self.Color256Element(begin=cci.FOREGROUND256,

@ -14,18 +14,19 @@
# 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)
ConsoleCodeMapping, LightColorMapping, ConsoleColor256,
ConsoleCodesInfo, SpanPalette)
class BaseOutput:
class BaseOutput(object):
"""
Базовый вывод текста.
Вывод просто текста без изменения шрифта
"""
def __init__(self,state=None):
def __init__(self, state=None):
pass
def setBold(self):
@ -76,31 +77,31 @@ class BaseOutput:
"""
return ""
def outputText(self,text):
def outputText(self, text):
"""
Вывести текст с установленными настройками
"""
return text
def setForeground(self,color):
def setForeground(self, color):
"""
Установить цвет шрифта
"""
pass
def setBackground(self,color):
def setBackground(self, color):
"""
Установить цвет фона
"""
pass
def resetForeground(self,color):
def resetForeground(self, color):
"""
Использовать цвет шрифта по умолчанию
"""
pass
def resetBackground(self,color):
def resetBackground(self, color):
"""
Использовать цвет фона по умолчанию
"""
@ -116,11 +117,13 @@ class BaseOutput:
Выключить инверсию
"""
class SaveAttrOutput(BaseOutput):
"""
Базовый класс с сохранением атрибутов
"""
def __init__(self,state=None):
def __init__(self, state=None):
self.prev_state = state.clone() if state else TextState()
self.current_state = self.prev_state.clone()
@ -150,13 +153,13 @@ class SaveAttrOutput(BaseOutput):
self.resetBackground()
self.resetInvert()
def setForeground(self,color):
def setForeground(self, color):
self.current_state.foreground = color
def resetForeground(self):
self.current_state.foreground = TextState.Colors.DEFAULT
def setBackground(self,color):
def setBackground(self, color):
self.current_state.background = color
def resetBackground(self):
@ -168,17 +171,18 @@ class SaveAttrOutput(BaseOutput):
def setInvert(self):
self.current_state.invert = True
class ColorTerminalOutput(SaveAttrOutput):
"""
Форматирует текст для вывода в консоль
"""
mapColors = ConsoleCodeMapping(ConsoleCodesInfo.FOREGROUND,
BaseColorMapping).mapTS_Console
mapLightColors = ConsoleCodeMapping(ConsoleCodesInfo.FOREGROUND-
LightColorMapping.offset,
LightColorMapping).mapTS_Console
BaseColorMapping).mapTS_Console
mapLightColors = ConsoleCodeMapping(ConsoleCodesInfo.FOREGROUND -
LightColorMapping.offset,
LightColorMapping).mapTS_Console
mapBackgroundColors = ConsoleCodeMapping(ConsoleCodesInfo.BACKGROUND,
BaseColorMapping).mapTS_Console
BaseColorMapping).mapTS_Console
def setBold(self):
self.resetHalfbright()
@ -188,101 +192,92 @@ 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
prevstate.halfbright != curstate.halfbright):
elif (curstate.halfbright and
prevstate.halfbright != curstate.halfbright):
attrs.append(ConsoleCodesInfo.HALFBRIGHT)
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:
attrs.append(ConsoleCodesInfo.NOINVERT)
def _createAttrs(self,prevstate,curstate):
def _createAttrs(self, prevstate, curstate):
"""
Создать ESC последовательность для установки параметров текста
"""
attrs = []
attrs, tail_attrs = [],[]
# получить интенсивность (полутон и жирность относятся к интенсивности)
intensity = lambda x:x & (TextState.Attributes.HALFBRIGHT |
TextState.Attributes.BOLD)
intensity = lambda x: x & (TextState.Attributes.HALFBRIGHT |
TextState.Attributes.BOLD)
if (prevstate.attr != curstate.attr and
curstate.attr == TextState.Attributes.NONE and
curstate.foreground is TextState.Colors.DEFAULT and
curstate.background is TextState.Colors.DEFAULT):
if (prevstate.attr != curstate.attr and
curstate.attr == TextState.Attributes.NONE and
curstate.foreground is TextState.Colors.DEFAULT and
curstate.background is TextState.Colors.DEFAULT):
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
self.handleBackground(prevstate, curstate, attrs, tail_attrs)
return attrs, tail_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]
def _createEscCode(self,attrs):
def _createEscCode(self, attrs):
"""
Создать ESC строку
"""
attrs = map(str,['\033['] + attrs + ['m'])
return "%s%s%s"%(attrs[0],";".join(attrs[1:-1]),attrs[-1])
attrs = map(str, ['\033['] + attrs + ['m'])
return "%s%s%s" % (attrs[0], ";".join(attrs[1:-1]), attrs[-1])
def outputText(self,s):
def outputText(self, s):
"""
Задание параметров текста и вывод его
"""
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,75 +320,121 @@ 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 SpanCssOutput(SaveAttrOutput):
class ColorTerminal16Output(ColorTerminalOutput):
"""
Форматирует текст для вывода в консоль
Вывод на 16 цветный терминал с преобразованием RGB к ближайшему базовому
"""
def __init__(self,state=None,palette=SpanPalette()):
SaveAttrOutput.__init__(self,state=state)
def __init__(self, state=None, palette=None):
SaveAttrOutput.__init__(self, state=state)
self.palette = palette
def getStringColor(self,color,bold=False,halfbright=False,background=False):
def _handleNearestColors(self, color):
"""
Получить название цвета по номеру и состоянию текста
Обработка преобразования к ближайшему цвету
"""
if halfbright:
bright = SpanPalette.LOW_BRIGHT
elif bold:
bright = SpanPalette.HIGH_BRIGHT
else:
bright = SpanPalette.NORMAL_BRIGHT
standardColors = TextState.normalColors + TextState.lightColors
if self.palette and color not in standardColors:
return self.palette.getBaseColorByRGB(color)
return color
if background:
return self.palette.getBackgroundColor(color)
else:
return self.palette.getTextColor(color,bright)
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 getTags(self,prevstate,curstate):
def handleBackground(self, prevstate, curstate, attrs, tail_attrs):
"""
Создать ESC последовательность для установки параметров текста
Добавить преобразование RGB к ближайшему базовому
"""
style = []
colorAttr = ["color","background"]
if curstate.invert:
colorAttr = colorAttr[1],colorAttr[0]
if (prevstate.foreground != curstate.foreground or
prevstate.bold != curstate.bold or
curstate.invert or
prevstate.halfbright != curstate.halfbright):
sColor = self.getStringColor(curstate.foreground,
curstate.bold,
curstate.halfbright,
background=False)
style.append("%s:%s;"%(colorAttr[0],sColor))
if prevstate.background != curstate.background or curstate.invert:
sColor = self.getStringColor(curstate.background,background=True)
style.append("%s:%s;"%(colorAttr[1],sColor))
if prevstate.underline != curstate.underline:
if curstate.underline:
style.append("text-decoration:underline;")
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):
"""
Получить название цвета по номеру и состоянию текста
"""
if halfbright:
bright = SpanPalette.LOW_BRIGHT
elif bold:
bright = SpanPalette.HIGH_BRIGHT
else:
style.append("text-decoration:none;")
if prevstate.bold != curstate.bold:
if curstate.bold:
style.append("font-weight:bold;")
bright = SpanPalette.NORMAL_BRIGHT
if background:
return self.palette.getBackgroundColor(color)
else:
style.append("font-weight:normal;")
return '<span style="%s">'%"".join(style),'</span>'
return self.palette.getTextColor(color, bright)
def outputText(self,s):
if self.prev_state != self.current_state:
lattr, rattr = self.getTags(self.prev_state,self.current_state)
else:
lattr = rattr = ""
return lattr + s + rattr
def getTags(self, prevstate, curstate):
"""
Создать ESC последовательность для установки параметров текста
"""
style = []
colorAttr = ["colortext", "background"]
if curstate.invert:
colorAttr = colorAttr[1], colorAttr[0]
if (prevstate.foreground != curstate.foreground or
prevstate.bold != curstate.bold or
curstate.invert or
prevstate.halfbright != curstate.halfbright):
sColor = self.getStringColor(curstate.foreground,
curstate.bold,
curstate.halfbright,
background=False)
style.append("%s:%s;" % (colorAttr[0], sColor))
if prevstate.background != curstate.background or curstate.invert:
sColor = self.getStringColor(curstate.background,
background=True)
style.append("%s:%s;" % (colorAttr[1], sColor))
if prevstate.underline != curstate.underline:
if curstate.underline:
style.append("text-decoration:underline;")
else:
style.append("text-decoration:none;")
if prevstate.bold != curstate.bold:
if curstate.bold:
style.append("font-weight:bold;")
else:
style.append("font-weight:normal;")
return '<span style="%s">' % "".join(style), '</span>'
def outputText(self, s):
if self.prev_state != self.current_state:
lattr, rattr = self.getTags(self.prev_state, self.current_state)
else:
lattr = rattr = ""
return lattr + s + rattr
def endText(self):
self.reset()
return ""
def endText(self):
self.reset()
return ""

@ -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