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

@ -14,18 +14,19 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from calculate.lib.utils.tools import SavableIterator
from palette import (TextState, BaseColorMapping, from palette import (TextState, BaseColorMapping,
ConsoleCodeMapping, LightColorMapping, ConsoleColor256, ConsoleCodeMapping, LightColorMapping, ConsoleColor256,
ConsoleCodesInfo, SpanPalette) ConsoleCodesInfo, SpanPalette)
class BaseOutput:
class BaseOutput(object):
""" """
Базовый вывод текста. Базовый вывод текста.
Вывод просто текста без изменения шрифта Вывод просто текста без изменения шрифта
""" """
def __init__(self,state=None):
def __init__(self, state=None):
pass pass
def setBold(self): def setBold(self):
@ -76,31 +77,31 @@ class BaseOutput:
""" """
return "" return ""
def outputText(self,text): def outputText(self, text):
""" """
Вывести текст с установленными настройками Вывести текст с установленными настройками
""" """
return text return text
def setForeground(self,color): def setForeground(self, color):
""" """
Установить цвет шрифта Установить цвет шрифта
""" """
pass pass
def setBackground(self,color): def setBackground(self, color):
""" """
Установить цвет фона Установить цвет фона
""" """
pass pass
def resetForeground(self,color): def resetForeground(self, color):
""" """
Использовать цвет шрифта по умолчанию Использовать цвет шрифта по умолчанию
""" """
pass pass
def resetBackground(self,color): def resetBackground(self, color):
""" """
Использовать цвет фона по умолчанию Использовать цвет фона по умолчанию
""" """
@ -116,11 +117,13 @@ class BaseOutput:
Выключить инверсию Выключить инверсию
""" """
class SaveAttrOutput(BaseOutput): class SaveAttrOutput(BaseOutput):
""" """
Базовый класс с сохранением атрибутов Базовый класс с сохранением атрибутов
""" """
def __init__(self,state=None):
def __init__(self, state=None):
self.prev_state = state.clone() if state else TextState() self.prev_state = state.clone() if state else TextState()
self.current_state = self.prev_state.clone() self.current_state = self.prev_state.clone()
@ -150,13 +153,13 @@ class SaveAttrOutput(BaseOutput):
self.resetBackground() self.resetBackground()
self.resetInvert() self.resetInvert()
def setForeground(self,color): def setForeground(self, color):
self.current_state.foreground = color self.current_state.foreground = color
def resetForeground(self): def resetForeground(self):
self.current_state.foreground = TextState.Colors.DEFAULT self.current_state.foreground = TextState.Colors.DEFAULT
def setBackground(self,color): def setBackground(self, color):
self.current_state.background = color self.current_state.background = color
def resetBackground(self): def resetBackground(self):
@ -168,17 +171,18 @@ class SaveAttrOutput(BaseOutput):
def setInvert(self): def setInvert(self):
self.current_state.invert = True self.current_state.invert = True
class ColorTerminalOutput(SaveAttrOutput): class ColorTerminalOutput(SaveAttrOutput):
""" """
Форматирует текст для вывода в консоль Форматирует текст для вывода в консоль
""" """
mapColors = ConsoleCodeMapping(ConsoleCodesInfo.FOREGROUND, mapColors = ConsoleCodeMapping(ConsoleCodesInfo.FOREGROUND,
BaseColorMapping).mapTS_Console BaseColorMapping).mapTS_Console
mapLightColors = ConsoleCodeMapping(ConsoleCodesInfo.FOREGROUND- mapLightColors = ConsoleCodeMapping(ConsoleCodesInfo.FOREGROUND -
LightColorMapping.offset, LightColorMapping.offset,
LightColorMapping).mapTS_Console LightColorMapping).mapTS_Console
mapBackgroundColors = ConsoleCodeMapping(ConsoleCodesInfo.BACKGROUND, mapBackgroundColors = ConsoleCodeMapping(ConsoleCodesInfo.BACKGROUND,
BaseColorMapping).mapTS_Console BaseColorMapping).mapTS_Console
def setBold(self): def setBold(self):
self.resetHalfbright() self.resetHalfbright()
@ -188,101 +192,92 @@ class ColorTerminalOutput(SaveAttrOutput):
self.resetBold() self.resetBold()
SaveAttrOutput.setHalfbright(self) SaveAttrOutput.setHalfbright(self)
def handleForeground(self,prevstate,curstate,attrs): def handleForeground(self, prevstate, curstate, attrs, tail_attrs):
cci = ConsoleCodesInfo cci = ConsoleCodesInfo
color = curstate.foreground color = curstate.foreground
if color in self.mapColors: if color in self.mapColors:
attrs.append(self.mapColors[color]) attrs.append(self.mapColors[color])
elif color in self.mapLightColors: 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(cci.BOLD)
attrs.append(self.mapLightColors[color]) attrs.append(self.mapLightColors[color])
else: else:
attrs.append(cci.FOREGROUND_DEFAULT) attrs.append(cci.FOREGROUND_DEFAULT)
self.resetForeground() self.resetForeground()
def handleBackground(self,prevstate,curstate,attrs): def handleBackground(self, prevstate, curstate, attrs, tail_attrs):
color = curstate.background color = curstate.background
if color in self.mapBackgroundColors: if color in self.mapBackgroundColors:
attrs.append(self.mapBackgroundColors[color]) attrs.append(self.mapBackgroundColors[color])
else: else:
attrs.append(ConsoleCodesInfo.BACKGROUND_DEFAULT) 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: if curstate.bold and curstate.bold != prevstate.bold:
attrs.append(ConsoleCodesInfo.BOLD) attrs.append(ConsoleCodesInfo.BOLD)
elif (curstate.halfbright and elif (curstate.halfbright and
prevstate.halfbright != curstate.halfbright): prevstate.halfbright != curstate.halfbright):
attrs.append(ConsoleCodesInfo.HALFBRIGHT) attrs.append(ConsoleCodesInfo.HALFBRIGHT)
else: else:
attrs.append(ConsoleCodesInfo.NORMAL) attrs.append(ConsoleCodesInfo.NORMAL)
def handleUnderline(self,prevstate,curstate,attrs): def handleUnderline(self, prevstate, curstate, attrs, tail_attrs):
if curstate.underline: if curstate.underline:
attrs.append(ConsoleCodesInfo.UNDERLINE) attrs.append(ConsoleCodesInfo.UNDERLINE)
else: else:
attrs.append(ConsoleCodesInfo.NOUNDERLINE) attrs.append(ConsoleCodesInfo.NOUNDERLINE)
def handleInvert(self,prevstate,curstate,attrs): def handleInvert(self, prevstate, curstate, attrs, tail_attrs):
if curstate.invert: if curstate.invert:
attrs.append(ConsoleCodesInfo.INVERT) attrs.append(ConsoleCodesInfo.INVERT)
else: else:
attrs.append(ConsoleCodesInfo.NOINVERT) attrs.append(ConsoleCodesInfo.NOINVERT)
def _createAttrs(self,prevstate,curstate): def _createAttrs(self, prevstate, curstate):
""" """
Создать ESC последовательность для установки параметров текста Создать ESC последовательность для установки параметров текста
""" """
attrs = [] attrs, tail_attrs = [],[]
# получить интенсивность (полутон и жирность относятся к интенсивности) # получить интенсивность (полутон и жирность относятся к интенсивности)
intensity = lambda x:x & (TextState.Attributes.HALFBRIGHT | intensity = lambda x: x & (TextState.Attributes.HALFBRIGHT |
TextState.Attributes.BOLD) TextState.Attributes.BOLD)
if (prevstate.attr != curstate.attr and if (prevstate.attr != curstate.attr and
curstate.attr == TextState.Attributes.NONE and curstate.attr == TextState.Attributes.NONE and
curstate.foreground is TextState.Colors.DEFAULT and curstate.foreground is TextState.Colors.DEFAULT and
curstate.background is TextState.Colors.DEFAULT): curstate.background is TextState.Colors.DEFAULT):
attrs.append(ConsoleCodesInfo.RESET) attrs.append(ConsoleCodesInfo.RESET)
else: else:
if intensity(prevstate.attr) != intensity(curstate.attr): if intensity(prevstate.attr) != intensity(curstate.attr):
self.handleIntensity(prevstate,curstate,attrs) self.handleIntensity(prevstate, curstate, attrs, tail_attrs)
if prevstate.underline != curstate.underline: if prevstate.underline != curstate.underline:
self.handleUnderline(prevstate,curstate,attrs) self.handleUnderline(prevstate, curstate, attrs, tail_attrs)
if prevstate.invert != curstate.invert: if prevstate.invert != curstate.invert:
self.handleInvert(prevstate,curstate,attrs) self.handleInvert(prevstate, curstate, attrs, tail_attrs)
if prevstate.foreground != curstate.foreground: if prevstate.foreground != curstate.foreground:
self.handleForeground(prevstate,curstate,attrs) self.handleForeground(prevstate, curstate, attrs, tail_attrs)
if prevstate.background != curstate.background: if prevstate.background != curstate.background:
self.handleBackground(prevstate,curstate,attrs) self.handleBackground(prevstate, curstate, attrs, tail_attrs)
return attrs return attrs, tail_attrs
def handlePostAttr(self,prevstate,curstate): def _createEscCode(self, attrs):
"""
Добавление аттрибутов после выведенного текста
"""
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):
""" """
Создать ESC строку Создать ESC строку
""" """
attrs = map(str,['\033['] + attrs + ['m']) attrs = map(str, ['\033['] + attrs + ['m'])
return "%s%s%s"%(attrs[0],";".join(attrs[1:-1]),attrs[-1]) 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: 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) attr = self._createEscCode(attr)
postattr = self.handlePostAttr(self.prev_state,self.current_state) if tail_attrs:
if postattr: postattr = self._createEscCode(tail_attrs)
postattr = self._createEscCode(postattr)
else: else:
postattr = "" postattr = ""
self.prev_state = self.current_state.clone() self.prev_state = self.current_state.clone()
@ -295,12 +290,14 @@ class ColorTerminalOutput(SaveAttrOutput):
self.reset() self.reset()
return self.outputText("") return self.outputText("")
class ColorTerminal256Output(ColorTerminalOutput): class ColorTerminal256Output(ColorTerminalOutput):
""" """
Вывод на 256 цветный терминал Вывод на 256 цветный терминал
""" """
mapLightColors = LightColorMapping.mapTS_Console mapLightColors = LightColorMapping.mapTS_Console
def handleForeground(self,prevstate,curstate,attrs):
def handleForeground(self, prevstate, curstate, attrs, tail_attrs):
color = curstate.foreground color = curstate.foreground
color256 = ConsoleColor256.rgbToConsole(color) color256 = ConsoleColor256.rgbToConsole(color)
if not color256 and color in self.mapLightColors: if not color256 and color in self.mapLightColors:
@ -310,9 +307,10 @@ class ColorTerminal256Output(ColorTerminalOutput):
ConsoleCodesInfo.COLOR256, ConsoleCodesInfo.COLOR256,
color256]) color256])
else: 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 color = curstate.background
color256 = ConsoleColor256.rgbToConsole(color) color256 = ConsoleColor256.rgbToConsole(color)
if not color256 and color in self.mapLightColors: if not color256 and color in self.mapLightColors:
@ -322,75 +320,121 @@ class ColorTerminal256Output(ColorTerminalOutput):
ConsoleCodesInfo.COLOR256, ConsoleCodesInfo.COLOR256,
color256]) color256])
else: else:
ColorTerminalOutput.handleBackground(self,prevstate,curstate,attrs) ColorTerminalOutput.handleBackground(self, prevstate, curstate,
attrs, tail_attrs)
def handlePostAttr(self,prevstate,curstate): class ColorTerminal16Output(ColorTerminalOutput):
pass
class SpanCssOutput(SaveAttrOutput):
""" """
Форматирует текст для вывода в консоль Вывод на 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 self.palette = palette
def getStringColor(self,color,bold=False,halfbright=False,background=False): def _handleNearestColors(self, color):
""" """
Получить название цвета по номеру и состоянию текста Обработка преобразования к ближайшему цвету
""" """
if halfbright: standardColors = TextState.normalColors + TextState.lightColors
bright = SpanPalette.LOW_BRIGHT if self.palette and color not in standardColors:
elif bold: return self.palette.getBaseColorByRGB(color)
bright = SpanPalette.HIGH_BRIGHT return color
else:
bright = SpanPalette.NORMAL_BRIGHT
if background: def handleForeground(self, prevstate, curstate, attrs, tail_attrs):
return self.palette.getBackgroundColor(color) """
else: Добавить преобразование RGB к ближайшему базовому
return self.palette.getTextColor(color,bright) """
_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 = [] mapHighNormal = dict(zip(TextState.lightColors,
TextState.normalColors))
colorAttr = ["color","background"] _curstate = curstate.clone()
if curstate.invert: _curstate.background = \
colorAttr = colorAttr[1],colorAttr[0] self._handleNearestColors(curstate.background)
if (prevstate.foreground != curstate.foreground or # преобразовать яркий цвет к обычному
prevstate.bold != curstate.bold or _curstate.background = \
curstate.invert or mapHighNormal.get(_curstate.background, _curstate.background)
prevstate.halfbright != curstate.halfbright): super(ColorTerminal16Output, self).handleBackground(
sColor = self.getStringColor(curstate.foreground, prevstate, _curstate,
curstate.bold, attrs, tail_attrs)
curstate.halfbright,
background=False) class SpanCssOutput(SaveAttrOutput):
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: def __init__(self, state=None, palette=SpanPalette()):
if curstate.underline: SaveAttrOutput.__init__(self, state=state)
style.append("text-decoration:underline;") 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: else:
style.append("text-decoration:none;") bright = SpanPalette.NORMAL_BRIGHT
if prevstate.bold != curstate.bold:
if curstate.bold: if background:
style.append("font-weight:bold;") return self.palette.getBackgroundColor(color)
else: else:
style.append("font-weight:normal;") return self.palette.getTextColor(color, bright)
return '<span style="%s">'%"".join(style),'</span>'
def outputText(self,s): def getTags(self, prevstate, curstate):
if self.prev_state != self.current_state: """
lattr, rattr = self.getTags(self.prev_state,self.current_state) Создать ESC последовательность для установки параметров текста
else: """
lattr = rattr = "" style = []
return lattr + s + rattr
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): def endText(self):
self.reset() self.reset()
return "" 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 + '.variables',
module_name + '.mod', module_name + '.mod',
module_name + '.utils', module_name + '.utils',
module_name + '.utils.color'], module_name + '.utils.colortext'],
data_files = [("/etc/calculate", []), data_files = [("/etc/calculate", []),
("/var/calculate/remote", []), ("/var/calculate/remote", []),
("/var/log/calculate", [])] ("/var/log/calculate", [])]

Loading…
Cancel
Save