From 69740f684328771ea27bee266aaee1bbdbb92549 Mon Sep 17 00:00:00 2001 From: Mike Hiretsky Date: Wed, 9 Apr 2014 15:43:07 +0400 Subject: [PATCH] =?UTF-8?q?=D0=9C=D0=BD=D0=BE=D0=B6=D0=B5=D1=81=D1=82?= =?UTF-8?q?=D0=B2=D0=B5=D0=BD=D0=BD=D1=8B=D0=B5=20=D0=B8=D0=B7=D0=BC=D0=B5?= =?UTF-8?q?=D0=BD=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B4=D0=BB=D1=8F=20=D1=80?= =?UTF-8?q?=D0=B0=D0=B1=D0=BE=D1=82=D1=8B=20=D1=81=20=D1=86=D0=B2=D0=B5?= =?UTF-8?q?=D1=82=D0=BD=D1=8B=D0=BC=20=D0=B2=D1=8B=D0=B2=D0=BE=D0=B4=D0=BE?= =?UTF-8?q?=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- calculate/lib/cl_lang.py | 103 ++++++++------ calculate/lib/datavars.py | 2 +- calculate/lib/utils/colortext/__init__.py | 52 +++++++ calculate/lib/utils/colortext/converter.py | 149 +++++++++++++++------ calculate/lib/utils/colortext/info.py | 43 ++++++ calculate/lib/utils/colortext/output.py | 127 +++++++++++++++++- calculate/lib/utils/colortext/palette.py | 52 +++++++ calculate/lib/utils/colortext/printing.py | 81 +++++++++++ calculate/lib/utils/files.py | 10 ++ calculate/lib/utils/tools.py | 86 ++++++++++++ 10 files changed, 622 insertions(+), 83 deletions(-) create mode 100644 calculate/lib/utils/colortext/info.py create mode 100644 calculate/lib/utils/colortext/printing.py 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("" % 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 "" % 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)