diff --git a/calculate/lib/utils/color/palette.py b/calculate/lib/utils/color/palette.py
deleted file mode 100644
index bfdbcd8..0000000
--- a/calculate/lib/utils/color/palette.py
+++ /dev/null
@@ -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"]))
-
diff --git a/calculate/lib/utils/color/__init__.py b/calculate/lib/utils/colortext/__init__.py
similarity index 100%
rename from calculate/lib/utils/color/__init__.py
rename to calculate/lib/utils/colortext/__init__.py
diff --git a/calculate/lib/utils/color/converter.py b/calculate/lib/utils/colortext/converter.py
similarity index 89%
rename from calculate/lib/utils/color/converter.py
rename to calculate/lib/utils/colortext/converter.py
index 81ae107..fec602c 100644
--- a/calculate/lib/utils/color/converter.py
+++ b/calculate/lib/utils/colortext/converter.py
@@ -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)
- 'Hello'
+ 'Hello'
"""
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,
diff --git a/calculate/lib/utils/color/output.py b/calculate/lib/utils/colortext/output.py
similarity index 53%
rename from calculate/lib/utils/color/output.py
rename to calculate/lib/utils/colortext/output.py
index dc5af2b..822e632 100644
--- a/calculate/lib/utils/color/output.py
+++ b/calculate/lib/utils/colortext/output.py
@@ -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 ''%"".join(style),''
+ 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 '' % "".join(style), ''
+
+ 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 ""
diff --git a/calculate/lib/utils/colortext/palette.py b/calculate/lib/utils/colortext/palette.py
new file mode 100644
index 0000000..ac8d926
--- /dev/null
+++ b/calculate/lib/utils/colortext/palette.py
@@ -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"]))
diff --git a/setup.py b/setup.py
index bd57c91..2466abf 100755
--- a/setup.py
+++ b/setup.py
@@ -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", [])]