diff --git a/calculate/lib/datavars.py b/calculate/lib/datavars.py
index 3c41ea3..82bfe16 100644
--- a/calculate/lib/datavars.py
+++ b/calculate/lib/datavars.py
@@ -814,7 +814,7 @@ class SimpleDataVars:
"""
Unserialize form string for varname
"""
- fixEmpty = lambda x:"" if x=="''" else x
+ fixEmpty = lambda x:"" if x=="''" or x=='""' else x
def getList(delimeter=','):
def wrapper(val):
if val == "":
@@ -825,7 +825,7 @@ class SimpleDataVars:
return getList()(value)
if "table" in varType:
return map(getList(':'),value.split(','))
- return fixEmpty(value).strip("'")
+ return fixEmpty(value).strip("'").strip('"')
class DataVars(SimpleDataVars):
diff --git a/calculate/lib/utils/color/__init__.py b/calculate/lib/utils/color/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/calculate/lib/utils/color/converter.py b/calculate/lib/utils/color/converter.py
new file mode 100644
index 0000000..81ae107
--- /dev/null
+++ b/calculate/lib/utils/color/converter.py
@@ -0,0 +1,188 @@
+#-*- 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.
+
+from output import BaseOutput
+from palette import (TextState, BaseColorMapping, ConsoleCodesInfo,
+ ConsoleCodeMapping, LightColorMapping, ConsoleColor256)
+from calculate.lib.utils.tools import SavableIterator
+from itertools import chain,ifilter
+import re
+
+class ConsoleCodesConverter(object):
+ """Преобразователь текста из цветного консольного вывода через объект
+ форматирования.
+
+ Объект форматирования должен реализовывать BaseOutput
+
+ >>> cct = ConsoleCodesConverter(BaseOutput())
+ >>> outtext = "\033[32;1mHello\033[0;39m"
+ >>> cct.transform(outtext)
+ 'Hello'
+ >>> cct = ConsoleCodesConverter(SpanCssOutput())
+ >>> outtext = "\033[32;1mHello\033[0;39m"
+ >>> cct.transform(outtext)
+ 'Hello'
+ """
+
+ class CodeElement:
+ """Элемент кода в ESC последовательности"""
+ def __init__(self,condition=lambda code:False,action=lambda :None):
+ self.action = action
+ self.condition = condition
+
+ def tryParse(self,code):
+ """Обрабатывает ли экземпляр код"""
+ return self.condition(code)
+
+ def parse(self,code,codes):
+ """Обработать код, вызвать действие"""
+ self.action()
+
+ def _next_code(self,other):
+ """
+ Получить следующий код
+ """
+ try:
+ return int(other.next())
+ except StopIteration:
+ return None
+
+ class ColorElement:
+ """Элемент кода для указания стандартного цвета
+
+ Проверка кода в интервале, запуск действия с передачей цвета
+ """
+ # соответствие консольных цветов внутренним цветам
+ mapColors = BaseColorMapping.mapConsole_TS
+
+ def __init__(self,action=lambda x:None, begin=None, end=None):
+ self.action = action
+ self.begin = begin
+ self.end = end
+
+ def tryParse(self,code):
+ cci = ConsoleCodesInfo
+ return code >=self.begin and code <= self.end
+
+ def parse(self,code,codes):
+ cci = ConsoleCodesInfo
+ return self.action(self.mapColors.get(code-self.begin,
+ TextState.Colors.DEFAULT))
+
+ def __init__(self,output=None,escSymb="\033"):
+ self.output = output or BaseOutput()
+ self.escSymb = escSymb
+ self.reParse = re.compile(
+ "({0}\[\d+(?:;\d+)*m)?(.*?)(?=$|{0}\[\d)".format(escSymb),
+ re.DOTALL)
+ resetBoldHalfbright = lambda : (
+ (self.output.resetBold() or "") +
+ (self.output.resetHalfbright() or ""))
+ cci = ConsoleCodesInfo
+ element = self.CodeElement
+
+ # набор правил обработки кодов
+ reset = element(lambda code:code==cci.RESET, self.output.reset)
+ bold = element(lambda code:code==cci.BOLD, self.output.setBold)
+ halfbright = element(lambda code:code==cci.HALFBRIGHT,
+ self.output.setHalfbright)
+ underline = element(lambda code:code==cci.UNDERLINE,
+ self.output.setUnderline)
+ nounderline = element(lambda code:code==cci.NOUNDERLINE,
+ self.output.resetUnderline)
+ invert = element(lambda code:code==cci.INVERT,
+ self.output.setInvert)
+ noinvert = element(lambda code:code==cci.NOINVERT,
+ self.output.resetInvert)
+ normal = element(lambda code:code==cci.NORMAL,
+ resetBoldHalfbright)
+ reset_foreground = element(lambda code:code==cci.FOREGROUND_DEFAULT,
+ self.output.resetForeground)
+ reset_background = element(lambda code:code==cci.BACKGROUND_DEFAULT,
+ self.output.resetBackground)
+ foreground = self.ColorElement(begin=cci.FOREGROUND,
+ end=cci.FOREGROUND_END,action=self.output.setForeground)
+ background = self.ColorElement(begin=cci.BACKGROUND,
+ end=cci.BACKGROUND_END,action=self.output.setBackground)
+ self.grams = [reset,bold,halfbright,underline,nounderline,normal,
+ invert,noinvert,foreground,background]
+
+ def outputText(self,s):
+ return self.output.outputText(s)
+
+ def transform(self,s):
+ """
+ Запустить преобразование текста
+ """
+ def generator():
+ offset = len(self.escSymb)+1
+ for ctrl,txt in self.reParse.findall(s):
+ if ctrl:
+ codes = SavableIterator(ctrl[offset:-1].split(';'))
+ for code in codes:
+ code = int(code)
+ res = ""
+ for gram in ifilter(lambda x:x.tryParse(code),
+ self.grams):
+ res = gram.parse(code,codes)
+ break
+ if res:
+ yield res
+ if txt:
+ yield self.outputText(txt)
+ yield self.output.endText()
+ return "".join(list(filter(None,generator())))
+
+class ConsoleCodes256Converter(ConsoleCodesConverter):
+ """Расширяет возможность обработки 256 цветного терминала"""
+
+ class Color256Element(ConsoleCodesConverter.CodeElement):
+ def __init__(self,action=lambda x:None, begin=None):
+ self.action = action
+ self.begin = begin
+
+ def tryParse(self,code):
+ return code == self.begin
+
+ def parse(self,code,codes):
+ """
+ Тон: 38;5;0-255
+ Фон: 48;5;0-255
+ """
+ colorMap = LightColorMapping(BaseColorMapping).mapConsole_TS
+
+ codes.save()
+ if self._next_code(codes) == ConsoleCodesInfo.COLOR256:
+ code = self._next_code(codes)
+ if code is not None:
+ if code in colorMap:
+ self.action(colorMap[code])
+ else:
+ self.action(ConsoleColor256.consoleToRgb(code))
+ else:
+ # если после 38 не 5 - не обрабатываем этот код
+ codes.restore()
+
+ def __init__(self,*args,**kwargs):
+ ConsoleCodesConverter.__init__(self,*args,**kwargs)
+ cci = ConsoleCodesInfo
+ # обработчики кодов для вывода в 256
+ foreground256 = self.Color256Element(begin=cci.FOREGROUND256,
+ action=self.output.setForeground)
+ background256 = self.Color256Element(begin=cci.BACKGROUND256,
+ action=self.output.setBackground)
+ self.grams.insert(0,foreground256)
+ self.grams.insert(0,background256)
diff --git a/calculate/lib/utils/color/output.py b/calculate/lib/utils/color/output.py
new file mode 100644
index 0000000..dc5af2b
--- /dev/null
+++ b/calculate/lib/utils/color/output.py
@@ -0,0 +1,396 @@
+#-*- 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.
+
+from calculate.lib.utils.tools import SavableIterator
+from palette import (TextState, BaseColorMapping,
+ ConsoleCodeMapping, LightColorMapping, ConsoleColor256,
+ ConsoleCodesInfo, SpanPalette)
+
+class BaseOutput:
+ """
+ Базовый вывод текста.
+
+ Вывод просто текста без изменения шрифта
+ """
+ def __init__(self,state=None):
+ pass
+
+ def setBold(self):
+ """
+ Выводимый текст будет жирным
+ """
+ pass
+
+ def resetBold(self):
+ """
+ Выводимый текст будет нежирным
+ """
+ pass
+
+ def setUnderline(self):
+ """
+ Выводимый текст будет подчеркнутым
+ """
+ pass
+
+ def resetUnderline(self):
+ """
+ Выводимый текст не будет подчеркнутым
+ """
+ pass
+
+ def setHalfbright(self):
+ """
+ Цвет выводимого текста использует полутона
+ """
+ pass
+
+ def resetHalfbright(self):
+ """
+ Цвет выводимого текста не использует полутона
+ """
+ pass
+
+ def reset(self):
+ """
+ Использовать шрифт по умолчанию
+ """
+ pass
+
+ def endText(self):
+ """
+ Обработка текста завершена
+ """
+ return ""
+
+ def outputText(self,text):
+ """
+ Вывести текст с установленными настройками
+ """
+ return text
+
+ def setForeground(self,color):
+ """
+ Установить цвет шрифта
+ """
+ pass
+
+ def setBackground(self,color):
+ """
+ Установить цвет фона
+ """
+ pass
+
+ def resetForeground(self,color):
+ """
+ Использовать цвет шрифта по умолчанию
+ """
+ pass
+
+ def resetBackground(self,color):
+ """
+ Использовать цвет фона по умолчанию
+ """
+ pass
+
+ def setInvert(self):
+ """
+ Включить инверсию
+ """
+
+ def resetInvert(self):
+ """
+ Выключить инверсию
+ """
+
+class SaveAttrOutput(BaseOutput):
+ """
+ Базовый класс с сохранением атрибутов
+ """
+ def __init__(self,state=None):
+ self.prev_state = state.clone() if state else TextState()
+ self.current_state = self.prev_state.clone()
+
+ def setBold(self):
+ self.current_state.bold = True
+
+ def resetBold(self):
+ self.current_state.bold = False
+
+ def setUnderline(self):
+ self.current_state.underline = True
+
+ def resetUnderline(self):
+ self.current_state.underline = False
+
+ def setHalfbright(self):
+ self.current_state.halfbright = True
+
+ def resetHalfbright(self):
+ self.current_state.halfbright = False
+
+ def reset(self):
+ self.resetBold()
+ self.resetHalfbright()
+ self.resetUnderline()
+ self.resetForeground()
+ self.resetBackground()
+ self.resetInvert()
+
+ def setForeground(self,color):
+ self.current_state.foreground = color
+
+ def resetForeground(self):
+ self.current_state.foreground = TextState.Colors.DEFAULT
+
+ def setBackground(self,color):
+ self.current_state.background = color
+
+ def resetBackground(self):
+ self.current_state.background = TextState.Colors.DEFAULT
+
+ def resetInvert(self):
+ self.current_state.invert = False
+
+ 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
+ mapBackgroundColors = ConsoleCodeMapping(ConsoleCodesInfo.BACKGROUND,
+ BaseColorMapping).mapTS_Console
+
+ def setBold(self):
+ self.resetHalfbright()
+ SaveAttrOutput.setBold(self)
+
+ def setHalfbright(self):
+ self.resetBold()
+ SaveAttrOutput.setHalfbright(self)
+
+ def handleForeground(self,prevstate,curstate,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:
+ attrs.append(cci.BOLD)
+ attrs.append(self.mapLightColors[color])
+ else:
+ attrs.append(cci.FOREGROUND_DEFAULT)
+ self.resetForeground()
+
+ def handleBackground(self,prevstate,curstate,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):
+ if curstate.bold and curstate.bold != prevstate.bold:
+ attrs.append(ConsoleCodesInfo.BOLD)
+ elif (curstate.halfbright and
+ prevstate.halfbright != curstate.halfbright):
+ attrs.append(ConsoleCodesInfo.HALFBRIGHT)
+ else:
+ attrs.append(ConsoleCodesInfo.NORMAL)
+
+ def handleUnderline(self,prevstate,curstate,attrs):
+ if curstate.underline:
+ attrs.append(ConsoleCodesInfo.UNDERLINE)
+ else:
+ attrs.append(ConsoleCodesInfo.NOUNDERLINE)
+
+ def handleInvert(self,prevstate,curstate,attrs):
+ if curstate.invert:
+ attrs.append(ConsoleCodesInfo.INVERT)
+ else:
+ attrs.append(ConsoleCodesInfo.NOINVERT)
+
+ def _createAttrs(self,prevstate,curstate):
+ """
+ Создать ESC последовательность для установки параметров текста
+ """
+ attrs = []
+ # получить интенсивность (полутон и жирность относятся к интенсивности)
+ 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):
+ attrs.append(ConsoleCodesInfo.RESET)
+ else:
+ if intensity(prevstate.attr) != intensity(curstate.attr):
+ self.handleIntensity(prevstate,curstate,attrs)
+ if prevstate.underline != curstate.underline:
+ self.handleUnderline(prevstate,curstate,attrs)
+ if prevstate.invert != curstate.invert:
+ self.handleInvert(prevstate,curstate,attrs)
+ if prevstate.foreground != curstate.foreground:
+ self.handleForeground(prevstate,curstate,attrs)
+ if prevstate.background != curstate.background:
+ self.handleBackground(prevstate,curstate,attrs)
+ return attrs
+
+ def handlePostAttr(self,prevstate,curstate):
+ """
+ Добавление аттрибутов после выведенного текста
+ """
+ if prevstate.foreground != curstate.foreground:
+ color = curstate.foreground
+ if (color in self.mapLightColors and
+ prevstate.bold == curstate.bold and curstate.bold == False):
+ return [ConsoleCodesInfo.NORMAL]
+
+ def _createEscCode(self,attrs):
+ """
+ Создать ESC строку
+ """
+ attrs = map(str,['\033['] + attrs + ['m'])
+ return "%s%s%s"%(attrs[0],";".join(attrs[1:-1]),attrs[-1])
+
+ def outputText(self,s):
+ """
+ Задание параметров текста и вывод его
+ """
+ if self.prev_state != self.current_state:
+ attr = 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)
+ else:
+ postattr = ""
+ self.prev_state = self.current_state.clone()
+ else:
+ attr = ""
+ postattr = ""
+ return attr + s + postattr
+
+ def endText(self):
+ self.reset()
+ return self.outputText("")
+
+class ColorTerminal256Output(ColorTerminalOutput):
+ """
+ Вывод на 256 цветный терминал
+ """
+ mapLightColors = LightColorMapping.mapTS_Console
+ def handleForeground(self,prevstate,curstate,attrs):
+ color = curstate.foreground
+ color256 = ConsoleColor256.rgbToConsole(color)
+ if not color256 and color in self.mapLightColors:
+ color256 = self.mapLightColors[color]
+ if color256:
+ attrs.extend([ConsoleCodesInfo.FOREGROUND256,
+ ConsoleCodesInfo.COLOR256,
+ color256])
+ else:
+ ColorTerminalOutput.handleForeground(self,prevstate,curstate,attrs)
+
+ def handleBackground(self,prevstate,curstate,attrs):
+ color = curstate.background
+ color256 = ConsoleColor256.rgbToConsole(color)
+ if not color256 and color in self.mapLightColors:
+ color256 = self.mapLightColors[color]
+ if color256:
+ attrs.extend([ConsoleCodesInfo.BACKGROUND256,
+ ConsoleCodesInfo.COLOR256,
+ color256])
+ else:
+ ColorTerminalOutput.handleBackground(self,prevstate,curstate,attrs)
+
+ def handlePostAttr(self,prevstate,curstate):
+ pass
+
+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:
+ bright = SpanPalette.NORMAL_BRIGHT
+
+ if background:
+ return self.palette.getBackgroundColor(color)
+ else:
+ return self.palette.getTextColor(color,bright)
+
+ def getTags(self,prevstate,curstate):
+ """
+ Создать ESC последовательность для установки параметров текста
+ """
+ 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;")
+ 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 ""
diff --git a/calculate/lib/utils/color/palette.py b/calculate/lib/utils/color/palette.py
new file mode 100644
index 0000000..bfdbcd8
--- /dev/null
+++ b/calculate/lib/utils/color/palette.py
@@ -0,0 +1,318 @@
+#-*- 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/text.py b/calculate/lib/utils/text.py
index 882c294..50747b0 100644
--- a/calculate/lib/utils/text.py
+++ b/calculate/lib/utils/text.py
@@ -27,116 +27,6 @@ from re import search, compile, S
from calculate.lib.cl_lang import setLocalTranslate
setLocalTranslate('cl_lib3',sys.modules[__name__])
-def prettyColumnStr(*cols):
- '''Функция преобразования строк в текстовые колонки. Если указанный текст
- не помещается в колонку, то строка переносится на следующую этой же колонки
- перенос текста идет по словам, и текст выравнивается по ширине колонки за
- счет дополнительных пробелов между словами. Если в строке используется
- перенос строки, то текст переносится не просто на следующую строку, а также
- на следующую строку колонки, причем если используется \r текст выравнива-
- ется по ширине, а если \n, то просто перевод строки.
-
- Параметры:
- cols множестово пар: текст, ширина колонки, причем, если у последней
- колонки не указывать ширину, то она будет выведена вся.
-
- Возвращаемые параметры:
- строка, которую можно использовать для вывода на экран
-
- Пример: columnWrite( "Some text", 10, "Next column", 20 )
- '''
- # шаблон поиска переводов строк
- wherenr = compile( '[\n\r]', S )
- retstr = ""
- # перевести кортеж в список, т.к. изменяется
- cols = list(cols)
- # перевести текст в юникод, заодно перевести числа в строку
- noconvert = False
- space = u' '
- nospace = u''
- for i in xrange(0,len(cols),2):
- cols[i] = _toUNICODE(cols[i])
- # флаг "есть еще текст для вывода"
- repeat = True
- while repeat:
- # сбросить итератор на первый элемент
- q = 0
- repeat = False
- # пока не закончили перебирать параметры (перебираем по парам)
- while q < len(cols):
- # если это последний параметр, и для него не указана ширина
- if q == len(cols)-1:
- # выводим его полностью не смотря на ширину окна
- retstr += cols[q] + " "
- cols[q] = ''
- else:
- # вывести часть строки не больше указанной ширины колонки
- partstr = cols[q][:cols[q+1]]
- # искать перевод строки с полученной части
- brfind = wherenr.search(partstr)
- # если это не последняя колонка
- if q + 2 < len(cols):
- # добавить разделитель между колонками
- cellspacing = space
- else:
- # разделитель не нужен
- cellspacing = nospace
-
- # если перевод строки найден, то
- if brfind != None:
- # для текущего вывода в колонку
- # берем часть строки до перевода
- partstr = partstr[:brfind.start()]
- # остальная часть идет в остаток (без перевода)
- cols[q] = cols[q][brfind.start()+1:]
-# # если используется перевод каретки
-# if brfind.group() == '\r':
-# # то выравниваем по ширине колонки
-# partstr = partstr.ljust(cols[q+1], ' ')
-# else:
-# # добавить отступы чтобы закончить колонку
- partstr = partstr.ljust(cols[q+1], ' ')
- # если взята часть строки
- elif len(partstr) == cols[q+1] and partstr != cols[q]:
- # если взята часть строки (разрыв в слове)
- if cols[q][cols[q+1]] != ' ':
- # ищем ближайший пробел справа
- spacepos = partstr.rfind(' ')
- # если пробел найти не удалось
- if spacepos == -1:
- # то на вывод идет часть строки равной ширине
- cols[q] = cols[q][cols[q+1]:]
- # если пробел найден
- else:
- # обрезаем строку до найденного пробела
- partstr = partstr[:spacepos]
- cols[q] = cols[q][spacepos+1:]
- # если взята часть строки (разрыв на пробеле)
- else:
- # ислючить переносной пробел
- cols[q] = cols[q][cols[q+1]+1:]
- # выровнить текст по ширине колонки
- partstr = partstr.ljust(cols[q+1], ' ')
- #partstr = justify(partstr, cols[q+1])
- # остатки строки
- else:
- # добавить отступы чтобы закончить колонку
- partstr = partstr.ljust(cols[q+1], ' ')
- cols[q] = ''
-
- retstr+= partstr + cellspacing
-
- # остальную часть строки оставить на следующую итерацию
- # если от строки что то осаталось
- if len(cols[q]) > 0:
- # отметить запуск еще одной итерации по параметрам
- repeat = True
- # следующая пара
- q += 2
- # колонки отображены
- retstr += "\n"
- return retstr.encode('utf8')
-
def columnMatrix(*cols):
"""
Split columns text to matrix
diff --git a/calculate/lib/utils/tools.py b/calculate/lib/utils/tools.py
new file mode 100644
index 0000000..1b2881f
--- /dev/null
+++ b/calculate/lib/utils/tools.py
@@ -0,0 +1,36 @@
+#-*- 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.
+
+from itertools import tee
+
+class SavableIterator:
+ def __init__(self,seq):
+ self.seq = iter(seq)
+ self.back = None
+
+ def __iter__(self):
+ return self
+
+ def save(self):
+ self.seq, self.back = tee(self.seq)
+
+ def restore(self):
+ if self.back:
+ self.seq, self.back = tee(self.back)
+
+ def next(self):
+ return self.seq.next()
+
diff --git a/setup.py b/setup.py
index 19f69a1..bd57c91 100755
--- a/setup.py
+++ b/setup.py
@@ -43,7 +43,8 @@ setup(
module_name + '.server',
module_name + '.variables',
module_name + '.mod',
- module_name + '.utils'],
+ module_name + '.utils',
+ module_name + '.utils.color'],
data_files = [("/etc/calculate", []),
("/var/calculate/remote", []),
("/var/log/calculate", [])]