#-*- coding: utf-8 -*- # Copyright 2008-2013 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 types from calculate.lib import cl_overriding import re import sys from itertools import * from calculate.lib.cl_lang import setLocalTranslate setLocalTranslate('cl_lib3',sys.modules[__name__]) def columnMatrix(*cols): """ Split columns text to matrix """ def splitter(s,wid): """Split string by width and \\n""" for part in unicode(s).split('\n'): if wid > 0: while part: p, part = part[:wid],part[wid:] yield p else: yield part return izip_longest( *starmap(splitter, izip_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 map(lambda x:izip_longest(colWidth,x),columnMatrix(*cols)): yield " ".join(("{0:%d}"%(width or 1)).format(s) for \ width,s in line) return "\n".join(generateStrings(*cols)) def columnWrite(*cols): '''Display data by columns Parameters: cols list(tuple(text,width)) Example: columnWrite( "Some text", 10, "Next column", 20 ) ''' colWidth = list(islice(cols,1,None,2)) for line in map(lambda x:izip_longest(colWidth,x),columnMatrix(*cols)): for width,s in line: cl_overriding.printSUCCESS(("{0:%d} "%(width or 1)).format(s), printBR=False) cl_overriding.printSUCCESS("",printBR=True) 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(object): """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 = map(lambda x:map(lambda y:str(y).expandtabs(), map(lambda y:y or "",x)), 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 = getTerminalSize()[1] if not maxSize: maxSize = 80 spanSize = self.colSpan*2+1 if colNum < 0: colNum = len(self.columnsWidth)+colNum currentSize = sum(map(lambda x:x+spanSize,self.columnsWidth))+1 if currentSize < maxSize and not forceMax: return excludeSize = sum(map(lambda x:x[1]+spanSize, filter(lambda x:x[0]!=colNum, enumerate(self.columnsWidth))))+spanSize+1 if maxSize - excludeSize > 5: self.columnsWidth[colNum] = maxSize - excludeSize def getColumsnWidth(self): """Находит максимальную ширину каждой колонки результат список ширин колонок """ columnsWidth = [] for s in self.headerList: columnsWidth.append(len(_toUNICODE(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: i = 0 for s in e: lenS = len(_toUNICODE(s)) if columnsWidth[i] < lenS: columnsWidth[i] = lenS i += 1 return columnsWidth def createFormatStr(self, listStr): """Создает список (текст, ширина ...)""" return chain(*izip_longest(listStr,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 = [] printData.append([self.crossChar, self.line_printer, columnMatrix(*self.createFormatStr(listStrSep))]) printData.append([self.vertChar, self.head_printer, columnMatrix(*self.createFormatStr(self.headerList))]) printData.append([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))]) 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, align, width, txt in izip(count(), self.align, self.columnsWidth, line): _print(("{0:%s%d}" % (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 = repl_dict.keys() keys.sort(reverse=True) # lexical order pattern = u"|".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 type(val) == types.ListType: return list2str(val) # if val is dictionary elif type(val) == types.DictType: 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 _toUNICODE(val): """Convert text to unicode""" if type(val) == types.UnicodeType: return val else: return str(val).decode('UTF-8') def formatListOr(lst): """Convert list to string like this: [1,2,3] -> 1,2 or 3""" lststr = map(lambda x:str(x),lst) return (" %s "%_("or")).join(filter(lambda x:x, [", ".join(map(lambda x:x or "''",lststr[:-1])), "".join(lststr[-1:])])) def getTerminalSize(): """ Get terminal size """ import struct try: import fcntl import termios except ImportError: return (None, None) try: data = fcntl.ioctl(sys.stdout.fileno(), termios.TIOCGWINSZ, 4 * '00') 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(filter(lambda x: x not in drop_words, profiles[0].split('/')))] 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), izip_longest(*matrix, fillvalue=""))))))))) return ["/".join(filter(None, x)) for x in matrix]