# -*- coding: utf-8 -*- # Copyright 2008-2016 Mir Calculate. 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 .. import cl_overriding import re import sys from itertools import * from ..cl_lang import setLocalTranslate _ = lambda x: x setLocalTranslate('cl_lib3', sys.modules[__name__]) # _u = lambda t: t.decode('UTF-8', 'replace') if isinstance(t, str) else t _u = lambda t: t if isinstance(t, str) else t _uu = lambda *tt: tuple(_u(t) for t in tt) # _u8 = lambda t: t.encode('UTF-8', 'replace') if isinstance(t, str) else t _u8 = lambda t: t _uu8 = lambda *tt: tuple(_u8(t) for t in tt) def columnMatrix(*cols): """ Split columns text to matrix """ def splitter(s, wid): """Split string by width and \\n""" for part in _u(s).split('\n'): if wid > 0: while part: p, part = part[:wid], part[wid:] yield p else: yield part return list(zip_longest( *starmap(splitter, zip_longest(islice(cols, 0, None, 2), islice(cols, 1, None, 2))), fillvalue="")) def columnStr(*cols): """ Get string for write, data by columns Parameters: cols list(tuple(text,width)) Return: string for display Example: columnStr( "Some text", 10, "Next column", 20 ) """ def generateStrings(*cols): colWidth = list(islice(cols, 1, None, 2)) for line in (zip_longest(colWidth, x) for x in columnMatrix(*cols)): yield " ".join(("{0:%d}" % (width or 1)).format(s) for \ width, s in line) return "\n".join(generateStrings(*cols)) def justify(s, width): """ Выровнить текст по ширине Параметры: s выводимая строка width ширина на которую надо выровнить строку Возвращаямые параметры: Выровненная строка """ # если подана строка без пробелов - прекратить обработку if s.find(' ') == -1: return s pos = 0 # переводим в юникод для правильного вычисления длины try: s = s.decode('utf-8') # пропуск если это не utf-8 except UnicodeEncodeError: pass # пока длина строки меньше указанной while len(s) < width: # находим очередной пробел pos = s.find(' ', pos) # если не найден искать сначала if pos == -1: pos = s.find(' ') # вставить в позицию еще один пробел s = s[:pos] + ' ' + s[pos:] # оставить удвоенный пробел pos += 3 # вернуть строку в utf8 если она пришла в utf8 return s.encode('utf-8') class tableReport(): """Display data in table""" def __init__(self, title, headerList, dataList, colSpan=1): # title self.title = title # elements list for first table row self.headerList = headerList # row list (each row is list) self.dataList = [[str(y).expandtabs() for y in [z or "" for z in x]] for x in dataList] # calculate columns list self.columnsWidth = self.getColumsnWidth() self.vertChar = colSpan * " " + "|" + colSpan * " " self.crossChar = colSpan * "-" + "+" + colSpan * "-" self.colSpan = colSpan self.align = "<" + "^" * (len(headerList) - 2) + "<" def setAutosize(self, colNum=-1, maxSize=None, forceMax=False): """ Set width for specified column """ if maxSize is None: maxSize = get_term_size()[1] if not maxSize: maxSize = 80 spanSize = self.colSpan * 2 + 1 if colNum < 0: colNum += len(self.columnsWidth) currentSize = sum((x + spanSize for x in self.columnsWidth)) + 1 if currentSize < maxSize and not forceMax: return excludeSize = sum((x[1] + spanSize for x in enumerate(self.columnsWidth) if x[0] != colNum)) + spanSize + 1 if maxSize - excludeSize > 5: self.columnsWidth[colNum] = maxSize - excludeSize def getColumsnWidth(self): """Находит максимальную ширину каждой колонки результат список ширин колонок """ columnsWidth = [] for s in self.headerList: columnsWidth.append(len(_u(s))) lenCol = len(self.headerList) maxLenCol = lenCol # Вычисляем максимальное количество столбцов for s in self.dataList: lenS = len(s) if maxLenCol < lenS: maxLenCol = lenS if maxLenCol > lenCol: appCol = maxLenCol - lenCol # Добавляем элементы в список ширин for i in range(appCol): columnsWidth.append(0) # Вычисляем ширину столбцов for e in self.dataList: for i, s in enumerate(e): lenS = len(_u(s))+1 if columnsWidth[i] < lenS: columnsWidth[i] = lenS return columnsWidth def createFormatStr(self, listStr, offset=0): """Создает список (текст, ширина ...)""" return chain(*zip_longest(listStr, [x-offset for x in self.columnsWidth], fillvalue="")) def printFunc(self, s): """ Overriding print """ cl_overriding.printSUCCESS(s, printBR=False) def line_printer(self, s): """ Вывод используется для отображения "линий" таблицы """ self.printFunc(s) def head_printer(self, s): """ Вывод используется для отображения текста в шапке """ self.printFunc(s) def body_printer(self, s): """ Вывод используется для отображения текста в таблице """ self.printFunc(s) def default_printer(self, s): self.printFunc(s) def printReport(self, printRows=True): """Напечатать данные в табличном виде""" if self.title: self.printFunc(self.title) listStrSep = [] for lenCol in self.columnsWidth: listStrSep.append("-" * lenCol) printData = [[self.crossChar, self.line_printer, columnMatrix(*self.createFormatStr(listStrSep))], [self.vertChar, self.head_printer, columnMatrix(*self.createFormatStr(self.headerList))], [self.crossChar, self.line_printer, columnMatrix(*self.createFormatStr(listStrSep))]] for s in self.dataList: printData.append([self.vertChar, self.body_printer, columnMatrix(*self.createFormatStr(s, 1))]) printData.append([self.crossChar, self.line_printer, columnMatrix(*self.createFormatStr(listStrSep))]) last_col = len(self.headerList) - 1 for char, _print, row in printData: for line in row: self.line_printer(char[self.colSpan:]) for i, width, txt in zip(count(), self.columnsWidth, line): if _print == self.body_printer: align = "<" offset = " " if width: width -= 1 else: align = "^" offset = "" _print(("%s{0:%s%d}" % (offset, align, width)).format(txt)) if i < last_col: self.line_printer(char) self.line_printer(char[:-self.colSpan + 1]) self.default_printer('\n') if printRows: self.printFunc("(%s %s)" % (len(self.dataList), _("rows"))) class MultiReplace: """MultiReplace function object Usage: replacer = MultiReplace({'str':'efg','in':'asd'}) s = replacer("string") """ def __init__(self, repl_dict): # string to string mapping; use a regular expression keys = list(repl_dict.keys()) keys.sort(reverse=True) # lexical order pattern = "|".join([re.escape(key) for key in keys]) self.pattern = re.compile(pattern) self.dict = repl_dict def replace(self, s): # apply replacement dictionary to string def repl(match, get=self.dict.get): item = match.group(0) return get(item, item) return self.pattern.sub(repl, s) __call__ = replace def str2dict(s): """Convert string to dictionary: String format: {'key1':'value1','key2':'val\'ue2'} """ value = r'(?:\\\\|\\\'|[^\'])' pair = r""" \s*' # begin key (%(v)s*) ' # end key \s*:\s* # delimeter key/value ' # begin value (%(v)s*) '\s* # end value """ % {"v": value} reDict = re.compile(pair, re.X) reMatchDict = re.compile(""" ^{ # begin dict ((%(v)s,)* # many pair with comma at end %(v)s)? # pair without comma }$ # end dict """ % {'v': pair}, re.X) if reMatchDict.match(s.strip()): d = dict(reDict.findall(s)) replaceSlash = MultiReplace({'\\\\': '\\', '\\\'': '\''}) for i in d.keys(): d[i] = replaceSlash(d[i]) return d else: cl_overriding.printERROR(_("wrong dict value: %s" % s)) cl_overriding.exit(1) def str2list(s): """Convert string to list: String format: ['value1','val\'ue2'] """ value = r'(?:\\\\|\\\'|[^\'])' element = r""" \s*' # begin value (%(v)s*) '\s* # end value """ % {"v": value} reList = re.compile(element, re.X) reMatchList = re.compile(""" ^\[ # begin dict ((%(v)s,)* # many elements with comma at end %(v)s)? # element without comma \]$ # end dict """ % {'v': element}, re.X) if reMatchList.match(s.strip()): replaceSlash = MultiReplace({'\\\\': '\\', '\\\'': '\''}) return [replaceSlash(i) for i in reList.findall(s)] else: cl_overriding.printERROR(_("wrong list value: %s" % s)) cl_overriding.exit(1) def list2str(list): """Convert list to string Return string with escaped \ and '. """ replaceSlash = MultiReplace({'\\': '\\\\', '\'': '\\\''}) return "[%s]" % ','.join(["'%s'" % replaceSlash(str(i)) for i in list]) def dict2str(dict): """Convert dictionry to string Return string with escaped \ and '. """ replaceSlash = MultiReplace({'\\': '\\\\', '\'': '\\\''}) return '{%s}' % ','.join(["'%s':'%s'" % (str(k), replaceSlash(str(v))) \ for (k, v) in dict.items()]) def convertStrListDict(val): """Convert data between string, list, dict""" # if val is list if isinstance(val, list): return list2str(val) # if val is dictionary elif isinstance(val, dict): return dict2str(val) # else it is string else: # detect dictionary if re.match("^{.*}$", val): return str2dict(val) # detect list elif re.match("^\[.*\]$", val): return str2list(val) # else is simple string else: return val def formatListOr(lst): """Convert list to string like this: [1,2,3] -> 1,2 or 3""" lststr = [str(x) for x in lst] return (" %s " % _("or")).join((x for x in [", ".join((y or "''" for y in lststr[:-1])),"".join(lststr[-1:])] if x)) def get_term_size(fd=sys.stdout.fileno()): """ Get terminal size """ try: import struct import fcntl import termios except ImportError: struct, fcntl, termios = None, None, None return None, None try: s = struct.pack("HHHH", 0, 0, 0, 0) data = fcntl.ioctl(fd, termios.TIOCGWINSZ, s) height, width = struct.unpack('4H', data)[:2] return height, width except Exception: return None, None def simplify_profiles(profiles, drop_words=("amd64", "x86")): """ Упростить названия профилей (удалить одинаковое начало и конец в строках) :param profiles: список названий профилей :param drop_words: список слов из профилей, которые можно отбросить если профиль один :return: список упрощенных названий профилей """ if len(profiles) < 2: if len(profiles) == 1: return ["/".join((x for x in profiles[0].split('/') if x not in drop_words))] return profiles matrix = [x.split('/') for x in profiles] # удаляем одинаковые начальные и конечные поля matrix = zip(*reversed(list(dropwhile(lambda x: x.count(x[0]) == len(x), (reversed(list(dropwhile( lambda x: x.count(x[0]) == len(x), zip_longest(*matrix, fillvalue=""))))))))) return ["/".join((y for y in x if y)) for x in matrix]