#-*- coding: utf-8 -*-

#Copyright 2008 Calculate Pack, http://www.calculate-linux.ru
#
#  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 filecmp
import string
from random import choice
from re import search, compile, S
import os
import types
import popen2

def getdirlist(s_path):
    #Получить список директорий по указаному пути
    fdir=filecmp.dircmp(s_path, s_path)
    dir_list=fdir.common_dirs
    return dir_list

def prettyColumnStr(*cols):
    '''Функция преобразования строк в текстовые колонки. Если указанный текст
    не помещается в колонку, то строка переносится на следующую этой же колонки
    перенос текста идет по словам, и текст выравнивается по ширине колонки за
    счет дополнительных пробелов между словами. Если в строке используется
    перенос строки, то текст переносится не просто на следующую строку, а также
    на следующую строку колонки, причем если используется \r текст выравнива-
    ется по ширине, а если \n, то просто перевод строки.

    Параметры:
    cols     множестово пар: текст, ширина колонки, причем, если у последней
             колонки не указывать ширину, то она будет выведена вся.

    Возвращаемые параметры:
    строка, которую можно использовать для вывода на экран

    Пример: columnWrite( "Some text", 10, "Next column", 20 )
    '''
    # шаблон поиска переводов строк
    wherenr = compile( '[\n\r]', S )
    retstr = ""
    # перевести кортеж в список, т.к. изменяется
    cols = list(cols)
    # перевести текст в юникод, заодно перевести числа в строку
    noconvert = False
    space = u' '
    nospace = u''
    for i in xrange(0,len(cols),2):
        cols[i] = _toUNICODE(cols[i])
    # флаг "есть еще текст для вывода"
    repeat = True
    while repeat:
        # сбросить итератор на первый элемент
        q = 0
        repeat = False
        # пока не закончили перебирать параметры (перебираем по парам)
        while q < len(cols):
            # если это последний параметр, и для него не указана ширина
            if q == len(cols)-1:
                # выводим его полностью не смотря на ширину окна
                retstr += cols[q] + " "
                cols[q] = ''
            else:
                # вывести часть строки не больше указанной ширины колонки
                partstr = cols[q][:cols[q+1]]
                # искать перевод строки с полученной части
                brfind = wherenr.search(partstr)
                # если это не последняя колонка
                if q + 2 < len(cols):
                    # добавить разделитель между колонками
                    cellspacing = space
                else:
                    # разделитель не нужен
                    cellspacing = nospace

                # если перевод строки найден, то
                if brfind != None:
                    # для текущего вывода в колонку
                    # берем часть строки до перевода
                    partstr = partstr[:brfind.start()]
                    # остальная часть идет в остаток (без перевода)
                    cols[q] = cols[q][brfind.start()+1:]
#                    # если используется перевод каретки
#                    if brfind.group() == '\r':
#                        # то выравниваем по ширине колонки
#                        partstr = partstr.ljust(cols[q+1], ' ')
#                    else:
#                        # добавить отступы чтобы закончить колонку
                    partstr = partstr.ljust(cols[q+1], ' ')
                # если взята часть строки
                elif len(partstr) == cols[q+1] and partstr != cols[q]:
                    # если взята часть строки (разрыв в слове)
                    if cols[q][cols[q+1]] != ' ':
                        # ищем ближайший пробел справа
                        spacepos = partstr.rfind(' ')
                        # если пробел найти не удалось
                        if spacepos == -1:
                            # то на вывод идет часть строки равной ширине
                            cols[q] = cols[q][cols[q+1]:]
                        # если пробел найден
                        else:
                            # обрезаем строку до найденного пробела
                            partstr = partstr[:spacepos]
                            cols[q] = cols[q][spacepos+1:]
                    # если взята часть строки (разрыв на пробеле)
                    else:
                        # ислючить переносной пробел
                        cols[q] = cols[q][cols[q+1]+1:]
                    # выровнить текст по ширине колонки
                    partstr = partstr.ljust(cols[q+1], ' ')
                    #partstr = justify(partstr, cols[q+1])
                # остатки строки
                else:
                    # добавить отступы чтобы закончить колонку
                    partstr = partstr.ljust(cols[q+1], ' ')
                    cols[q] = ''

                retstr+= partstr + cellspacing

                # остальную часть строки оставить на следующую итерацию
                # если от строки что то осаталось
                if len(cols[q]) > 0:
                    # отметить запуск еще одной итерации по параметрам
                    repeat = True
            # следующая пара
            q += 2
        # колонки отображены
        retstr += "\n"
    return retstr.encode('utf8')

def columnStr(*cols):
    '''Вывод данных по колонкам, причем, если данные не вмещаются в указнаную
    колонку, то они переносятся на следующую строку в нужную колонку. В строку.

    Параметры:
    cols     множестово пар: текст, ширина колонки, причем, если у последней
             колонки не указывать ширину, то она будет выведена вся.

    Возвращаемые параметры:
    строка, которую можно использовать для вывода на экран

    Пример: columnWrite( "Some text", 10, "Next column", 20 )
    '''
    retstr = ""
    # перевести кортеж в список, т.к. изменяется
    cols = list(cols)
    # перевести текст в юникод, заодно перевести числа в строку
    for i in xrange(0,len(cols),2):
        cols[i] = (str(cols[i])).decode('utf8')

    # флаг "есть еще текст для вывода"
    repeat = True
    while repeat:
        # сбросить итератор на первый элемент
        q = 0
        repeat = False
        # пока не закончили перебирать параметры (перебираем по парам)
        while q < len(cols):
            # если это последний параметр, и для него не указана ширина
            if q == len(cols)-1:
                # выводим его полностью не смотря на ширину окна
                retstr += cols[q] + " "
                cols[q] = ''
            else:
                # вывести часть строки не больше указанной ширины колонки
                retstr+=(cols[q][:cols[q+1]].ljust(cols[q+1])).encode('utf8') \
                    + " "
                # остальную часть строки оставить на следующую итерацию
                cols[q] = cols[q][cols[q+1]:]
                # если от строки что то осаталось
                if len(cols[q]) > 0:
                    # отметить запуск еще одной итерации по параметрам
                    repeat = True
            # следующая пара
            q += 2
        # колонки отображены
        retstr += "\n"
    return retstr

def columnWrite(*cols):
    '''Вывод данных по колонкам, причем, если данные не вмещаются в указнаную
    колонку, то они переносятся на следующую строку в нужную колонку.

    Параметры:
    cols     множестово пар: текст, ширина колонки, причем, если у последней
             колонки не указывать ширину, то она будет выведена вся.

    Пример: columnWrite( "Some text", 10, "Next column", 20 )
    '''
    # перевести кортеж в список, т.к. изменяется
    cols = list(cols)
    # перевести текст в юникод, заодно перевести числа в строку
    for i in xrange(0,len(cols),2):
        cols[i] = (str(cols[i])).decode('utf8')

    # флаг "есть еще текст для вывода"
    repeat = True
    while repeat:
        # сбросить итератор на первый элемент
        q = 0
        repeat = False
        # пока не закончили перебирать параметры (перебираем по парам)
        while q < len(cols):
            # если это последний параметр, и для него не указана ширина
            if q == len(cols)-1:
                # выводим его полностью не смотря на ширину окна
                print cols[q].encode('utf8'),
                cols[q] = ''
            else:
                # вывести часть строки не больше указанной ширины колонки
                print (cols[q][:cols[q+1]].ljust(cols[q+1])).encode('utf8'),
                # остальную часть строки оставить на следующую итерацию
                cols[q] = cols[q][cols[q+1]:]
                # если от строки что то осаталось
                if len(cols[q]) > 0:
                    # отметить запуск еще одной итерации по параметрам
                    repeat = True
            # следующая пара
            q += 2
        # колонки отображены
        print

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')

def runOsCommand(cmd, inStr=None, ret_first=None):
    """Выполняет внешнюю программу

    Параметры:
    cmdStrProg     внешняя программа
    inStr          данные передаваемые программе на страндартный вход.
    ret_first      вернуть только первую строку
    Возвращаемые параметры:
    строка/строки которую выведет внешняя программа
    """
    fout, fin = popen2.popen4(cmd)
    # если есть данные на вход, передать их
    if inStr:
        fin.write(inStr)
    fin.close()
    res = fout.readlines()
    fout.close()
    if res:
        if len(res) > 1:
            if ret_first:
                return res[0]
            else:
                return res
        else:
            return res[0].strip()


def genpassword(passlen=9):
    '''Вернуть случайный пассворд указанной длины

    Параметры:
    passlen     длина пароля который нужно сгенерировать

    Возвращаемые параметры:
    Сгенерированный пароль указанной длины
    '''
    res=''.join([choice(string.ascii_letters+string.digits)\
        for i in xrange(passlen)])
    return res

def fillstr(char, width):
    '''Заполнить строку указанным числом символов. Псеводоним символ*кол-во'''
    return str(char) * width

    #вернуть пути для запуска утилит
def getpathenv():
    bindir=['/sbin','/bin','/usr/sbin','/usr/bin']
    env=os.environ
    if env and env.has_key('PATH'):
        lpath=env['PATH'].split(":")
        npath=[]
        for dirname in bindir:
            if os.path.exists(dirname) and dirname not in lpath:
                npath.append(dirname)
        lpath=npath+lpath
        return "PATH="+":".join(lpath)+" && "

#класс для работы с установленными пакетами
class pakages:
    #путь к директории установленнх пакетов
    pkgdir="/var/db/pkg/"
    #список установленных пакетов
    pkglist={}
    #Объект содержащий параметры пакета
    class pakage(object):
        #имя пакета с версией
        fullname=""
        #имя пакета
        name=""
        #версия
        ver=""
        #тип пакета в портижах
        portdir=""
        def __init__(self, **args):
            for atname,atvalue in args.items():
                setattr(self,atname, atvalue)

    def __init__(self):
        self.pkglist=self.__getpkglist()

    #разбить имя пакета на тип и имя
    def __getpkgver(self, fname):
        res=search('^(.+)\-([0-9]+.*)$',fname)
        if res:
            return res.groups()
        else:
            return (None,None)

    #собрать установленные в системе пакеты
    def __getpkglist(self):
        portageDirs=[]
        instaledPkg={}
        #проверим на существование директории с установленными пакетами
        if os.path.exists(self.pkgdir):
            #получим список типов пакетов
            portageDirs=getdirlist(self.pkgdir)
            if len(portageDirs)>0:
                #обрабатываем содержимое каждого из типов
                for portageDir in portageDirs:
                    pkgList=getdirlist(self.pkgdir+portageDir)
                    for pkg in pkgList:
                        fullname=pkg
                        pkgName,pkgVer= self.__getpkgver(pkg)
                        pobj=self.pakage(fullname=fullname,
                                    name=pkgName, \
                                    ver=pkgVer,\
                                    portdir=portageDir)
                        fpkg=portageDir+"/"+pkgName
                        if instaledPkg.has_key(fpkg):
                            instaledPkg[fpkg].append(pobj)
                        else:
                            instaledPkg[fpkg]=[pobj]
        return instaledPkg

    #разбить pkgname на составляющие имени пакета
    def __partname(self, pkgname):
        if not pkgname.strip():
            return False
        res=search('^(.+\/)?(.+)',pkgname)
        tname=None
        if res.group(1):
            tname=res.group(1)
        if res.group(2):
            res2=search('^(.+)(\-[0-9]+.+$)',res.group(2))
            if res2:
                name=res2.group(1)
                ver=res2.group(2)
            else:
                name=res.group(2)
                ver=None
        if res:
            if name and name[-1:]=='-':
                name=name[:-1]
            if tname and tname[-1:]=='/':
                tname=tname[:-1]
            if ver and ver[0]=='-':
                ver=ver[1:]
            return [tname, name, ver]


    #проверить установленн ли пакет
    #isinstalled('dev-db/postgresql')
    def isinstalled(self, pkgname):
        res=self.getinstpkg(pkgname)
        if len(res)>0:
            return True
        else:
            return False

    #вернуть список объектов pakage() соответствующих pkgname
    #getinstpkg('dev-db/postgresql')
    #в случае отсутствия пакетов возвращает пустой список
    def getinstpkg(self, pkgname):
        pinfo=self.__partname(pkgname)
        if pinfo:
            ret=[]
            if pinfo[0] and pinfo[1] and pinfo[2]:
                if not self.pkglist.has_key(pinfo[0]+'/'+pinfo[1]):
                    return []
                fpkg=self.pkglist[pinfo[0]+'/'+pinfo[1]]
                ret=[]
                for i in fpkg:
                    if i.ver==pinfo[2]:
                        ret.append(i)
                return ret
            elif pinfo[0] and pinfo[1]:
                if not self.pkglist.has_key(pinfo[0]+'/'+pinfo[1]):
                    return []
                return self.pkglist[pinfo[0]+'/'+pinfo[1]]
            elif pinfo[1] and pinfo[2]:
                for i in self.pkglist.keys():
                    if search('^.+\/%s$'%(pinfo[1]),i):
                        for el in self.pkglist[i]:
                            if el.ver==pinfo[2]:
                                ret.append(el)
                return ret
            elif pinfo[1]:
                for i in self.pkglist.keys():
                    if search('^.+\/%s$'%(pinfo[1]),i):
                        ret+=self.pkglist[i]
                return ret
        return []

    def getListPkg(self):
        return self.pkglist

def list2str(list):
    '''Функция переводит список в строку'''
    return '['+','.join(list)+']'

def str2list(s):
    '''Функция переводит строку в список'''
    return s[1:-1].split(',')

def dict2str(dict):
    '''Функция перводит словарь в строку'''
    return '{'+','.join(["%s:%s" % (str(k),str(v)) \
            for (k,v) in dict.items()])+'}' #:

def str2dict(s):
    '''Функция переводит строку в словарь'''
    dict = {}
    for i in  s[1:-1].split(','):
        k,v = i.split(':')
        dict[k] = v
    return dict

def convertStrListDict(val):
    '''Функция определеяется что на входе (строка, список, словарь)
    и переводит их в строку и обратно'''
    # если подан список
    if type(val) == types.ListType:
        return list2str(val)
    # если подан словарь
    elif type(val) == types.DictType:
        return dict2str(val)
    # если подана строка
    else:
        # если поданная строка содержит словарь
        if ':' in val and '{' in val:
            return str2dict(val)
        # если поданная строка содержит список
        elif ',' in val and '[' in val:
            return str2list(val)
        # если это просто строка
        else:
            return val

def _toUNICODE(val):
    """перевод текста в юникод"""
    if type(val) == types.UnicodeType:
        return val
    else:
        return str(val).decode('UTF-8')
