diff --git a/calculate/lib/cl_lang.py b/calculate/lib/cl_lang.py
index 9e9b111..350d5a4 100644
--- a/calculate/lib/cl_lang.py
+++ b/calculate/lib/cl_lang.py
@@ -14,41 +14,26 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import os, gettext
+import os
+import gettext
+from gettext import gettext as _
import threading
import types
import sys
-from cl_overriding import __findFileMO
+import re
+from gettext import Catalog
-class lang:
- """Multilanguage class
-
- Using:
- import sys
- from calculate.lib.cl_lang import lang
-
- # set engligh language
- tr = lang('en')
- # set autodetect language
- tr = lang()
- # set translate domain calc
- tr.setGlobalDomain('calc')
- # set local translate domain
- #tr.setLocalDomain('calc')
- # set translate for this module
- tr.setLanguage(sys.modules[__name__])
-
- or
- lang(_local="calc",__setLang=sys.modules[__name__])
+class lang:
+ """
+ Multilanguage class
"""
_modnames = {}
GP = [""]
def __init__(self):
self.nameDomain = self.GP[0]
- #self.nameDomain = ''
self.__catalog = None
# translate language for all modules
self._translators = {}
@@ -64,17 +49,16 @@ class lang:
if the module export other modules, then lang will be
set for them
Method must be call after module for translate"""
- t = vars(module)
if glob:
for name,mod in vars(module).items():
if type(mod) == types.ModuleType and \
- not name.startswith('__') and \
- not name in sys.builtin_module_names and \
- (hasattr(mod,'__file__') and (
- "calculate" in mod.__file__ or
- not mod.__file__.startswith('/usr/lib'))):
+ not name.startswith('__') and \
+ not name in sys.builtin_module_names and \
+ (hasattr(mod, '__file__') and (
+ "calculate" in mod.__file__ or
+ not mod.__file__.startswith('/usr/lib'))):
self.__setLang(mod)
- self.setLanguage(mod,True)
+ self.setLanguage(mod, True)
return self.__setLang(module)
def __setLang(self,module):
@@ -86,15 +70,20 @@ class lang:
module._ = self.__translate
self._modnames[module.__name__] = module._
- def __gettranslate(self):
+
+ @staticmethod
+ def get_current_lang():
+ """
+ Получить текущий язык
+ """
env = os.environ
- curThread = threading.currentThread()
- if hasattr(curThread,"lang"):
- l = curThread.lang
- elif env.has_key('LANG'):
- l = env['LANG'].split('.')[0].split("_")[0]
- else:
- l = "en"
+ cur_thread = threading.currentThread()
+ if hasattr(cur_thread, "lang"):
+ return cur_thread.lang
+ return env.get("LANG", "en_US.UTF-8").split('.')[0].split("_")[0]
+
+ def __gettranslate(self):
+ l = self.get_current_lang()
if l in self._translators:
return self._translators[l]
if l == 'en':
@@ -147,8 +136,44 @@ def getLazyLocalTranslate(translateFunc):
class translate:
def __init__(self,s):
self.s = s
+ self._format_args = None
+
def __str__(self):
- return translateFunc(self.s)
+ if self._format_args is None:
+ return translateFunc(self.s)
+ else:
+ return translateFunc(self.s).format(*self._format_args[0],
+ **self._format_args[1])
def __hash__(self):
return hash(self.s)
+
+ def format(self, *args, **kwargs):
+ self._format_args = (args,kwargs)
+ return self
+
return translate
+
+
+class RegexpLocalization(object):
+ def __init__(self, domain, languages=[lang.get_current_lang()]):
+ try:
+ self.set_translate_dict(Catalog(domain,
+ languages=languages)._catalog)
+ except IOError:
+ self._catalog = {}
+
+ def set_translate_dict(self, d):
+ def create_key(k):
+ try:
+ return re.compile(k.replace("\\\\", "\\"))
+ except re.error:
+ return None
+
+ self._catalog = filter(lambda x: x[0],
+ ((create_key(k), v) for k, v in
+ sorted(d.items(), reverse=True) if k))
+
+ def translate(self, s):
+ for k, v in self._catalog:
+ s = k.sub(v, s)
+ return s
diff --git a/calculate/lib/datavars.py b/calculate/lib/datavars.py
index 82bfe16..1881294 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=="''" or x=='""' else x
+ fixEmpty = lambda x:"" if x=="''" or x=='""' else x.strip()
def getList(delimeter=','):
def wrapper(val):
if val == "":
diff --git a/calculate/lib/utils/colortext/__init__.py b/calculate/lib/utils/colortext/__init__.py
index e69de29..59e94f1 100644
--- a/calculate/lib/utils/colortext/__init__.py
+++ b/calculate/lib/utils/colortext/__init__.py
@@ -0,0 +1,52 @@
+#-*- 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 printing import Print
+from palette import TextState
+from info import Terminal
+Colors = TextState.Colors
+
+from converter import ConsoleCodes256Converter, XmlConverter
+from output import XmlOutput, ColorTerminal16Output, TerminalPositionOutput, \
+ ColorTerminal256Output
+
+
+def convert_console_to_xml(s):
+ """Преобразовать вывод консоли в xml для внутреннего использования"""
+ return ConsoleCodes256Converter(output=XmlOutput()).transform(s)
+
+def get_color_print():
+ """
+ Получить объект для вывода текста в цвете
+ """
+ return Print(output=XmlOutput())
+
+def get_terminal_output():
+ return (ColorTerminal256Output()
+ if Terminal().colors > 16
+ else ColorTerminal16Output())
+
+def get_terminal_print(printfunc=lambda x: x):
+ """
+ Получить объект для вывода в терминал
+ """
+ # TODO: возвращать объект 256 или 16 терминал в зависимости от параметров
+ return Print(output=get_terminal_output(),
+ position_controller=TerminalPositionOutput(),
+ printfunc=printfunc)
+
+
+def convert_xml_to_terminal(s):
+ return XmlConverter(output=get_terminal_output()).transform(s)
diff --git a/calculate/lib/utils/colortext/converter.py b/calculate/lib/utils/colortext/converter.py
index f828c18..541b99c 100644
--- a/calculate/lib/utils/colortext/converter.py
+++ b/calculate/lib/utils/colortext/converter.py
@@ -17,13 +17,26 @@
from output import BaseOutput
from palette import (TextState, BaseColorMapping, ConsoleCodesInfo,
LightColorMapping, ConsoleColor256, XmlFormat)
-from calculate.lib.utils.tools import SavableIterator
+from calculate.lib.utils.tools import SavableIterator, ignore
from itertools import ifilter
from HTMLParser import HTMLParser
import re
-class ConsoleCodesConverter(object):
+class BaseConverter(object):
+ """
+ Базовый класс обработки (ничего не конвертирует - возвращает как есть)
+ """
+ def __init__(self, output=BaseOutput()):
+ self.output = output
+
+ def transform(self, s):
+ return self.output.outputText(s)
+
+ def detect(self, s):
+ return True
+
+class ConsoleCodesConverter(BaseConverter):
"""Преобразователь текста из цветного консольного вывода через объект
форматирования.
@@ -33,10 +46,11 @@ class ConsoleCodesConverter(object):
>>> outtext = "\033[32;1mHello\033[0;39m"
>>> cct.transform(outtext)
'Hello'
+ >>> from output import SpanCssOutput
>>> cct = ConsoleCodesConverter(SpanCssOutput())
>>> outtext = "\033[32;1mHello\033[0;39m"
>>> cct.transform(outtext)
- 'Hello'
+ 'Hello'
"""
class CodeElement:
@@ -52,7 +66,7 @@ class ConsoleCodesConverter(object):
def parse(self, code, codes):
"""Обработать код, вызвать действие"""
- self.action()
+ return self.action()
def _next_code(self, other):
"""
@@ -77,21 +91,22 @@ class ConsoleCodesConverter(object):
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.escBlock = r"{esc}\[(\d+(?:;\d+)*)m".format(esc=escSymb)
+ self.escBlock = (r"{esc}(?:\[(\d+(?:;\d+)*)m|"
+ "\]\d+;.*?\x07|\[\d*[A-D])".format(esc=escSymb))
+ self.otherSymb = "\r*\n"
self.reEscBlock = re.compile(self.escBlock)
self.reParse = re.compile(
- "(?:{0})?(.*?)(?=$|{0})".format(self.escBlock),
+ "(?:{0}|({1}))?(.*?)(?=$|{0}|{1})".format(self.escBlock,
+ self.otherSymb),
re.DOTALL)
resetBoldHalfbright = lambda: (
(self.output.resetBold() or "") +
@@ -124,8 +139,19 @@ class ConsoleCodesConverter(object):
background = self.ColorElement(begin=cci.BACKGROUND,
end=cci.BACKGROUND_END,
action=self.output.setBackground)
+ newline = element(lambda code: "\r" in code or "\n" in code,
+ self.output.newLine)
self.grams = [reset, bold, halfbright, underline, nounderline, normal,
- invert, noinvert, foreground, background]
+ invert, noinvert, reset_foreground, reset_background,
+ foreground, background, newline]
+
+ def evaluteGram(self, code, codes=None):
+ """Выполнить грамматику"""
+ if codes is None:
+ codes = SavableIterator([])
+ for gram in ifilter(lambda x: x.tryParse(code),
+ self.grams):
+ return gram.parse(code, codes)
def transform(self, s):
"""
@@ -133,18 +159,18 @@ class ConsoleCodesConverter(object):
"""
def generator():
- for ctrl, txt, _s in self.reParse.findall(s):
+ for ctrl, other, txt, _s in self.reParse.findall(s):
if ctrl:
codes = SavableIterator(ctrl.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
+ res = self.evaluteGram(code, codes)
if res:
yield res
+ elif other:
+ res = self.evaluteGram(other)
+ if res:
+ yield res
if txt:
yield self.output.outputText(txt)
yield self.output.endText()
@@ -199,30 +225,46 @@ class ConsoleCodes256Converter(ConsoleCodesConverter):
self.grams.insert(0, foreground256)
self.grams.insert(0, background256)
-class XmlConverter(object):
+
+class XmlConverter(BaseConverter):
"""
Преобразователь текста из внутреннего xml формата
"""
+ unescaper = XmlFormat.unescaper
def __init__(self, output=None):
Tags = XmlFormat.Tags
FontAttr = XmlFormat.FontAttributes
self.output = output or BaseOutput()
- self.tagMap = {Tags.BOLD: self.output.setBold,
- Tags.HALFBRIGHT: self.output.setHalfbright,
- Tags.INVERT: self.output.setInvert,
- Tags.UNDERLINE: self.output.setUnderline,
- Tags.FONT: self.parseFont}
+ self.tagMap = {
+ Tags.BOLD: self.output.setBold,
+ Tags.HALFBRIGHT: self.output.setHalfbright,
+ Tags.INVERT: self.output.setInvert,
+ Tags.UNDERLINE: self.output.setUnderline,
+ Tags.FONT: self.parseFont
+ }
+ self.singletagMap = {
+ Tags.NEWLINE: self.output.newLine
+ }
self.colorMap = {FontAttr.FOREGROUND.lower(): self.output.setForeground,
FontAttr.BACKGROUND.lower(): self.output.setBackground}
self.reMatch = re.compile("<(?:%s)" % "|".join(self.tagMap.keys()),
re.I)
- self.parser = HTMLParser()
- self.parser.handle_starttag = self.startElementHandler
- self.parser.handle_endtag = self.endElementHandler
- self.parser.handle_data = self.characterDataHandler
+ self.parser = self.createParser()
- def parseFont(self, attrs):
+ def createParser(self):
+ """
+ Создать парсер HTML кода
+ """
+ parser = HTMLParser()
+ parser.handle_starttag = self.startElementHandler
+ parser.handle_endtag = self.endElementHandler
+ parser.handle_data = self.characterDataHandler
+ parser.handle_startendtag = self.startendElementHandler
+ parser.handle_entityref = self.entityrefElementHandler
+ return parser
+
+ def parseFont(self, *attrs):
for k, v in attrs:
k = str(k).lower()
if k in self.colorMap:
@@ -237,44 +279,73 @@ class XmlConverter(object):
def addResultToOutdata(f):
"""Добавить возвращаемый результат в список self.__outdata"""
+
def wrapper(self, *args):
res = f(self, *args)
if res:
self.__outdata.append(res)
return res
+
return wrapper
+ def _buildTaq(self, name, attrs=[], startendTag=False, endTag=False):
+ """
+ Создать тэг по параметрам
+ """
+ lslash, rslash = '', ''
+ if startendTag:
+ rslash = '/'
+ elif endTag:
+ lslash = '/'
+ if attrs:
+ return "<{name} {attrs}{rslash}>".format(
+ name=name, attrs=" ".join(['%s="%s"' % (k, v)
+ for k, v in attrs]),
+ rslash=rslash)
+ else:
+ return "<{lslash}{name}{rslash}>".format(lslash=lslash, name=name,
+ rslash=rslash)
+
@addResultToOutdata
def startElementHandler(self, name, attrs):
+ """Обработчик начального тега"""
if name in self.tagMap:
self.output.pushState()
self.__tagStack.append(name)
- if attrs:
- return self.tagMap[name](attrs)
- else:
- return self.tagMap[name]()
+ with ignore(TypeError):
+ return self.tagMap[name](*attrs)
else:
- if attrs:
- s = "<%s %s>" % (name,
- " ".join(['%s="%s"' % (k, v)
- for k, v in attrs.items()]))
- return self.output.outputText(s)
- else:
- return self.output.outputText("<%s>" % name)
+ return self.output.outputText(self._buildTaq(name, attrs))
+
+ @addResultToOutdata
+ def startendElementHandler(self, name, attrs):
+ """Обработчик одиночного тега"""
+ if name in self.singletagMap:
+ with ignore(TypeError):
+ return self.singletagMap[name](*attrs)
+ else:
+ return self.output.outputText(
+ self._buildTaq(name, attrs, startendTag=True))
@addResultToOutdata
def endElementHandler(self, name):
+ """Обработчик завершающего тега"""
if name in self.tagMap:
if name in self.__tagStack:
while self.__tagStack and self.__tagStack.pop() != name:
self.output.popState()
self.output.popState()
else:
- return self.output.outputText("%s>" % name)
+ return self.output.outputText(self._buildTaq(name, endTag=True))
@addResultToOutdata
def characterDataHandler(self, data):
- return self.output.outputText(data)
+ """Обработчик текста в тэгах"""
+ return self.output.outputText(self.unescaper(data))
+
+ @addResultToOutdata
+ def entityrefElementHandler(self, data):
+ return self.output.outputText(self.unescaper("&%s;" % data))
def detect(self, s):
return bool(self.reMatch.search(s))
diff --git a/calculate/lib/utils/colortext/info.py b/calculate/lib/utils/colortext/info.py
new file mode 100644
index 0000000..99e6ea3
--- /dev/null
+++ b/calculate/lib/utils/colortext/info.py
@@ -0,0 +1,43 @@
+#-*- 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 curses
+from os import environ
+import sys
+import struct
+import fcntl
+import termios
+
+
+class Terminal(object):
+ def __init__(self):
+ curses.setupterm(environ.get('TERM', 'linux'))
+
+ @property
+ def colors(self):
+ return curses.tigetnum('colors')
+
+ @property
+ def width(self):
+ s = struct.pack("HHHH", 0, 0, 0, 0)
+ fd_stdout = sys.stdout.fileno()
+ try:
+ x = fcntl.ioctl(fd_stdout, termios.TIOCGWINSZ, s)
+ except IOError:
+ # если ошибка то ширина 80 символов
+ return 80
+ #(rows, cols, x pixels, y pixels)
+ return struct.unpack("HHHH", x)[1]
diff --git a/calculate/lib/utils/colortext/output.py b/calculate/lib/utils/colortext/output.py
index 3c30800..fccca81 100644
--- a/calculate/lib/utils/colortext/output.py
+++ b/calculate/lib/utils/colortext/output.py
@@ -13,8 +13,10 @@
# 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 xml.etree import ElementTree as ET
+from calculate.lib.utils.text import MultiReplace
from palette import (TextState, BaseColorMapping,
ConsoleCodeMapping, LightColorMapping, ConsoleColor256,
ConsoleCodesInfo, SpanPalette, XmlFormat)
@@ -128,6 +130,12 @@ class BaseOutput(object):
Восстановить состояние текста из стека
"""
+ def newLine(self):
+ """
+ Вывести текст на новой строке
+ """
+ return "\n"
+
class SaveAttrOutput(BaseOutput):
"""
Базовый класс с сохранением атрибутов
@@ -317,6 +325,8 @@ class ColorTerminalOutput(SaveAttrOutput):
self.reset()
return self.outputText("")
+ def newLine(self):
+ return "\n"
class ColorTerminal256Output(ColorTerminalOutput):
"""
@@ -404,7 +414,6 @@ class SpanCssOutput(SaveAttrOutput):
"""
Форматирует текст для вывода в консоль
"""
-
def __init__(self, state=None, palette=SpanPalette()):
SaveAttrOutput.__init__(self, state=state)
self.palette = palette
@@ -465,19 +474,24 @@ class SpanCssOutput(SaveAttrOutput):
lattr, rattr = self.getTags(self.prev_state, self.current_state)
else:
lattr = rattr = ""
- return lattr + s + rattr
+ return lattr + XmlFormat.escaper(s) + rattr
def endText(self):
self.reset()
return ""
+ def newLine(self):
+ return "
"
class XmlOutput(SaveAttrOutput):
"""
Форматирует текст c описанием формата в XML для внутренней передачи
Bugs: игнорирует первоначальное состояние (state)
+ не экономное использование тэгов (например при выводе нескольких строк
"""
+ escaper = XmlFormat.escaper
+
def __init__(self, state=None):
super(XmlOutput, self).__init__(state=state)
self.clear_state = TextState()
@@ -531,7 +545,7 @@ class XmlOutput(SaveAttrOutput):
for text in generator(element):
yield text
if root.text:
- yield root.text
+ yield self.escaper(root.text)
yield "%s>" % root.tag
return "".join(filter(None, list(generator(xml))))
@@ -539,8 +553,113 @@ class XmlOutput(SaveAttrOutput):
if self.clear_state != self.current_state:
return self.xmlToString(self.getXML(self.current_state, s))
else:
- return s
+ return self.escaper(s)
def endText(self):
self.reset()
return ""
+
+ def newLine(self):
+ return "
"
+
+
+class BasePositionOutput(object):
+ """
+ Объект составляющий ESC последовательности для управлением местом вывода
+ """
+ def moveCursorUp(self, count=1):
+ """
+ Переместить курсор вверх
+ """
+ return ""
+
+ def moveCursorDown(self, count=1):
+ """
+ Переместить курсор вниз
+ """
+ return ""
+
+ def moveCursorRight(self, count=1):
+ """
+ Переместить курсор вправо
+ """
+ return ""
+
+ def moveCursorLeft(self, count=1):
+ """
+ Переместить курсор влево
+ """
+ return ""
+
+ def clearLine(self, whole_line=False):
+ """
+ Очистить строку от курсора до конца или всю строку
+ """
+ return ""
+
+ def savePosition(self):
+ """
+ Сохранить положение курсора
+ """
+ return ""
+
+ def restorePosition(self):
+ """
+ Восстановить положение курсора
+ """
+ return ""
+
+
+class TerminalPositionOutput(BasePositionOutput):
+ """
+ Управление позицией вывода текста в терминале
+ """
+ class Codes:
+ UP = 'A'
+ DOWN = 'B'
+ RIGHT = 'C'
+ LEFT = 'D'
+ CLEAR_LINE = 'K'
+ CLEAR_FROM_CURSOR = '1'
+ CLEAR_WHOLE_LINE = '2'
+ SAVE_POSITION = 's'
+ RESTORE_POSITION = 'u'
+
+
+ def _createEscCode(self, attrs):
+ """
+ Создать ESC строку
+ """
+ return '\033[%s' % attrs
+
+ def _moveCursor(self, direct, count):
+ if int(count) > 1:
+ count = str(count)
+ else:
+ count = ""
+ return self._createEscCode("%s%s" % (count, direct))
+
+ def moveCursorDown(self, count=1):
+ return self._moveCursor(self.Codes.DOWN, count)
+
+ def moveCursorUp(self, count=1):
+ return self._moveCursor(self.Codes.UP, count)
+
+ def moveCursorRight(self, count=1):
+ return self._moveCursor(self.Codes.RIGHT, count)
+
+ def moveCursorLeft(self, count=1):
+ return self._moveCursor(self.Codes.LEFT, count)
+
+ def clearLine(self, whole_line=False):
+ if whole_line:
+ mode_code = self.Codes.CLEAR_WHOLE_LINE
+ else:
+ mode_code = self.Codes.CLEAR_FROM_CURSOR
+ return self._createEscCode("%s%s"%(mode_code, self.Codes.CLEAR_LINE))
+
+ def savePosition(self):
+ return self._createEscCode(self.Codes.SAVE_POSITION)
+
+ def restorePosition(self):
+ return self._createEscCode(self.Codes.RESTORE_POSITION)
diff --git a/calculate/lib/utils/colortext/palette.py b/calculate/lib/utils/colortext/palette.py
index dfebd07..f99c414 100644
--- a/calculate/lib/utils/colortext/palette.py
+++ b/calculate/lib/utils/colortext/palette.py
@@ -17,6 +17,7 @@
import re
from itertools import chain
from math import pow
+from calculate.lib.utils.text import MultiReplace
class ConsoleCodesInfo:
@@ -371,6 +372,52 @@ class DarkPastelsPalette(SpanPalette):
["#709080", "#DCA3A3", "#72D5A3", "#F0DFAF",
"#94BFF3", "#EC93D3", "#93E0E3", "#FFFFFF"]))
+
+class WhitePalette(SpanPalette):
+ """
+ Палитра черное на белом
+ """
+
+ defaultColor = ["#141414", "#141414", "#272727"]
+ defaultBackground = "#ffffff"
+
+ normalBright = dict(zip(TextState.normalColors,
+ ["#141414", "#742523", "#237425", "#737423",
+ "#252374", "#742373", "#237374", "#c1c1c1"]))
+ highBright = dict(zip(TextState.lightColors,
+ ["#272727", "#cc5755", "#55cc57", "#cacc55",
+ "#5755cc", "#cc55ca", "#55cacc", "#ffffff"]))
+
+class GreyPaletteOld(SpanPalette):
+ """
+ Палитра черное на светло-сером
+ """
+ defaultColor = ["#101010", "#101010", "#505050"]
+ defaultBackground = "#e4e1e0"
+
+ normalBright = dict(zip(TextState.normalColors,
+ ["#101010", "#902c2b", "#2b902c", "#8f902b",
+ "#2c2b90", "#902b8f", "#2b8f90", "#b9b9b9"]))
+
+ highBright = dict(zip(TextState.lightColors,
+ ["#303030", "#c6403d", "#3dc640", "#c3c63d",
+ "#403dc6", "#c63dc3", "#3dc3c6", "#e4e1e0"]))
+
+class GreyPalette(SpanPalette):
+ """
+ Палитра черное на светло-сером
+ """
+ defaultColor = ["#101010", "#101010", "#505050"]
+ defaultBackground = "#ffffff"
+
+ normalBright = dict(zip(TextState.normalColors,
+ ["#101010", "#902c2b", "#2b902c", "#8f902b",
+ "#2c2b90", "#902b8f", "#2b8f90", "#b9b9b9"]))
+
+ highBright = dict(zip(TextState.lightColors,
+ ["#303030", "#c6403d", "#3dc640", "#c3c63d",
+ "#403dc6", "#c63dc3", "#3dc3c6", "#ffffff"]))
+
class XmlFormat:
"""
Константы для внутреннего формата XML
@@ -381,7 +428,12 @@ class XmlFormat:
FONT = "font"
HALFBRIGHT = "dark"
INVERT = "invert"
+ NEWLINE = "br"
class FontAttributes:
BACKGROUND = "bgColor"
FOREGROUND = "color"
+
+ escape_map = {'<': '<', '>': '>'}
+ escaper = MultiReplace(escape_map)
+ unescaper = MultiReplace({v: k for k, v in escape_map.items()})
diff --git a/calculate/lib/utils/colortext/printing.py b/calculate/lib/utils/colortext/printing.py
new file mode 100644
index 0000000..147d1d7
--- /dev/null
+++ b/calculate/lib/utils/colortext/printing.py
@@ -0,0 +1,81 @@
+#-*- 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, BasePositionOutput
+
+
+class Print(object):
+ """
+ Упрощенное получение строки при помощи Output объектов """
+ def __init__(self, output=BaseOutput(),
+ position_controller=BasePositionOutput(),
+ printfunc=lambda x:x):
+ self.output = output
+ self.position_controller = position_controller
+ self.buffer = []
+ self.printfunc = printfunc
+
+ @property
+ def bold(self):
+ self.buffer.append(self.output.setBold())
+ return self
+
+ def foreground(self, color):
+ self.buffer.append(self.output.setForeground(color))
+ return self
+
+ def background(self, color):
+ self.buffer.append(self.output.setBackground(color))
+ return self
+
+ @property
+ def invert(self):
+ self.buffer.append(self.output.setInvert())
+ return self
+
+ @property
+ def halflight(self):
+ self.buffer.append(self.output.setHalfbright())
+ return self
+
+ def up(self, count):
+ self.buffer.append(self.position_controller.moveCursorUp(count))
+ return self
+
+ def down(self, count):
+ self.buffer.append(self.position_controller.moveCursorDown(count))
+ return self
+
+ def right(self, count):
+ self.buffer.append(self.position_controller.moveCursorRight(count))
+ return self
+
+ def left(self, count):
+ self.buffer.append(self.position_controller.moveCursorLeft(count))
+ return self
+
+ @property
+ def clear_line(self):
+ self.buffer.append(self.position_controller.clearLine(whole_line=True))
+ return self
+
+ def __call__(self, s, *args, **kwargs):
+ s = s.format(*args, **kwargs)
+ self.buffer.append(self.output.outputText(s))
+ self.buffer.append(self.output.endText())
+ try:
+ return self.printfunc("".join(filter(None, self.buffer)))
+ finally:
+ self.buffer = []
diff --git a/calculate/lib/utils/files.py b/calculate/lib/utils/files.py
index 2e0f26f..c56afbb 100644
--- a/calculate/lib/utils/files.py
+++ b/calculate/lib/utils/files.py
@@ -236,6 +236,7 @@ class process:
"""Failed or not"""
return self.returncode() != 0
+
def getModeFile(nameFile, mode="all"):
"""Выдает информацию о файле
mode=="all"
@@ -691,6 +692,15 @@ def readFile(filename,tailbyte=None):
pass
return ""
+def writeFile(filename):
+ """
+ Открыть файл на запись и создать необходимые каталоги
+ """
+ dn = path.dirname(filename)
+ if not path.exists(dn):
+ os.makedirs(dn)
+ return open(filename,'w')
+
import common
class getProgPath:
diff --git a/calculate/lib/utils/tools.py b/calculate/lib/utils/tools.py
index 1b2881f..fb1c8d7 100644
--- a/calculate/lib/utils/tools.py
+++ b/calculate/lib/utils/tools.py
@@ -15,8 +15,12 @@
# limitations under the License.
from itertools import tee
+from contextlib import contextmanager
class SavableIterator:
+ """
+ Итератор с возможность сохранять и восстанавливать состояние
+ """
def __init__(self,seq):
self.seq = iter(seq)
self.back = None
@@ -26,11 +30,93 @@ class SavableIterator:
def save(self):
self.seq, self.back = tee(self.seq)
+ return self
def restore(self):
if self.back:
self.seq, self.back = tee(self.back)
+ return self
def next(self):
return self.seq.next()
+@contextmanager
+def ignore(exception):
+ try:
+ yield
+ except exception:
+ pass
+
+class AddonError(Exception):
+ """
+ Исключение с добавочным сообщением
+ """
+
+ def __init__(self, msg, addon=None):
+ self.message = msg
+ self.addon = addon
+ Exception.__init__(self, msg)
+
+
+class FalseObject(object):
+ """
+ Объект-заглушка при любом сравнении возвращает False
+ """
+ def __lt__(self,other):
+ return False
+
+ def __nonzero__(self):
+ return False
+
+ __gt__ = __ge__ = __le__ = __eq__ = __ne__ = __lt__
+
+
+class Sizes(object):
+ K = KiB = kibibyte = 1024
+ kB = kilobyte = 1000
+ M = MiB = mibibyte = K * 1024
+ Mb = megabyte = kB * 1000
+ G = GiB = gibibyte = M * 1024
+ Gb = gigabyte = Mb * 1000
+ T = TiB = tibibyte = G * 1024
+ Tb = terabyte = Gb * 1000
+
+ def __getattr__(self,name):
+ if name.startswith('from_'):
+ return lambda x:x*getattr(Sizes,name[5:])
+ elif name.startswith('to_'):
+ return lambda x:x/getattr(Sizes,name[3:])
+ else:
+ raise AttributeError
+
+def imap_regexp(re_compiled, l, whole=False):
+ """
+ Обработать список регулярным выражением и вернуть полученные группы
+ """
+ if whole:
+ retfunc = lambda x: x.group()
+ else:
+ retfunc = lambda x: x.groups()
+ return (retfunc(x) for x in (re_compiled.search(x) for x in l) if x)
+
+def cached(each_instance=False):
+ """
+ Кэширование результата
+ """
+ def cache_wrapper(func):
+ value = {}
+
+ def wrapper(*args, **kwargs):
+ if each_instance:
+ if args[0] not in value:
+ value[args[0]] = func(*args, **kwargs)
+ return value[args[0]]
+ else:
+ if not None in value:
+ value[None] = func(*args, **kwargs)
+ return value[None]
+ return wrapper
+ if each_instance in (True,False):
+ return cache_wrapper
+ else:
+ return cache_wrapper(each_instance)