You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
439 lines
15 KiB
439 lines
15 KiB
# -*- 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.
|
|
|
|
import types
|
|
from calculate.lib import cl_overriding
|
|
import re
|
|
import sys
|
|
from itertools import *
|
|
|
|
from calculate.lib.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
|
|
_uu = lambda *tt: tuple(_u(t) for t in tt)
|
|
_u8 = lambda t: t.encode('UTF-8', 'replace') if isinstance(t, unicode) else 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 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 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 = get_term_size()[1]
|
|
if not maxSize:
|
|
maxSize = 80
|
|
spanSize = self.colSpan * 2 + 1
|
|
if colNum < 0:
|
|
colNum += len(self.columnsWidth)
|
|
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(_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(*izip_longest(listStr,
|
|
map(lambda x:x-offset, 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 izip(count(),
|
|
self.columnsWidth, line):
|
|
if _print == self.body_printer:
|
|
align = "<"
|
|
offset = " "
|
|
if width:
|
|
width -= 1
|
|
else:
|
|
align = "^"
|
|
offset = ""
|
|
_print((u"%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 = 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 isinstance(val, types.ListType):
|
|
return list2str(val)
|
|
# if val is dictionary
|
|
elif isinstance(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 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 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(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]
|