#!/usr/bin/env python # -*- coding: utf-8 -*- """ Collection de fonction/classe pour avoir un bel affichage Copyright (C) Frédéric Pauget Licence : GPLv2 """ import sys, re, os, tempfile, subprocess # Détermination de l'encodage encoding = None try: from locale import getpreferredencoding encoding = getpreferredencoding() except: pass if not encoding: encoding = sys.stdin.encoding or 'UTF-8' # Si aucune locale n'est définie, on se met en... if encoding == "ANSI_X3.4-1968": encoding = 'UTF-8' if 'TERM' in os.environ and os.environ['TERM'] != 'unknown': el = subprocess.Popen('tput cr ; tput el', shell=True, stdout=subprocess.PIPE).stdout.read() else: el = subprocess.Popen('tput -Tvt100 cr ; tput -Tvt100 el', shell=True, stdout=subprocess.PIPE).stdout.read() try: stdout_atty = sys.stdout.isatty() except (IOError, AttributeError): stdout_atty = False def dialog(backtitle,arg,dialogrc='') : """ Affiche la boite de dialogue défine avec les arguments fournis (cf man dialog) si tout se déroule bien retourne : [ 0, [ reponse(s) ] ] si annulatin retourne : [ 1, [] ] si appui sur ESC demande confirmation de l'abandon et exécute sys.exit(0) si erreur dans les arguments raise RuntimeError """ f = tempfile.NamedTemporaryFile() cmd = u'%s /usr/bin/dialog --backtitle "%s" --cancel-label "Retour" %s 2>%s' % (dialogrc, backtitle,arg,f.name) res = os.system(cmd.encode(encoding, 'ignore')) if res == 256 : # Annuler f.close() return [ 1, [] ] # Lecture du fichier de résultat et effacement try: result= f.readlines() f.close() except : result = [ "n'importe quoi", ''] res = 65280 # Traitement if res==65280 and result: # Erreur dans les arguments raise RuntimeError( arg, result[1].strip() ) elif res==65280 : # Appui sur ESC arg1 = u'--title "Annulation" --yesno "Quitter ?\nLes dernières modifications seront perdues." 6 48' print backtitle cmd = u'%s /usr/bin/dialog --backtitle "%s" %s' % (dialogrc, backtitle,arg1) res = os.system(cmd.encode(encoding ,'ignore') ) if res==0 : sys.exit(0) else : return dialog(backtitle,arg) elif not result : result=[''] return [ 0, result ] def coul(txt, col=None): """ Retourne la chaine donnée encadrée des séquences qui vont bien pour obtenir la couleur souhaitée Les couleur sont celles de codecol Il est possible de changer la couleur de fond grace aux couleur f_ """ if not stdout_atty or not col: return txt codecol = { 'rouge': 31, 'vert': 32, 'jaune': 33, 'bleu': 34, 'violet': 35, 'cyan': 36, 'gris': 30, 'gras': 50 } try: if col[:2] == 'f_': add = 10 col = col[2:] else: add = 0 txt = "\033[1;%sm%s\033[1;0m" % (codecol[col] + add, txt) finally: return txt OK = coul('OK', 'vert') WARNING = coul('WARNING', 'jaune') ERREUR = coul('ERREUR', 'rouge') def to_unicode(txt, enc=encoding): if isinstance(txt, unicode): return txt else: # On suppose d'abord que le texte est en UTF-8 try: return txt.decode("UTF-8") except: # Sinon c'est surement de l'iso return txt.decode("UTF-8") def to_encoding(txt, enc=encoding): return to_unicode(txt).encode(enc, 'ignore') def cprint(txt, col='blanc', newline=True): t = coul(to_encoding(txt), col) if newline: print t else: print t, def tableau(data, titre=None, largeur=None, alignement=None, format=None): """ Retourne une chaine formatée repésentant un tableau. data : liste de listes, chacune contenant les valeurs d'une ligne titre : liste des titres Si none, n'affiche pas de ligne de titre largeur : liste des largeurs des colonnes, '*' met la plus grande largeur possible. Si None, réduit aux max chaque colonne alignement : liste des alignements : c = centrer g = gauche d = droit Si None, met c pour chaque colonne format : liste des formats : s = string o = octet Si None, s pour chaque colonne """ sep_col = u'|' if data : nbcols = len(data[0]) elif titre : nbcols = len(titre) else : return u'Aucune donnée' # Formats ######### if not format : format = ['s'] * nbcols def reformate(data, format): if format == 's': return unicode(data) elif format == 'o' : data = float(data) if data > 1024**3: return str(round(data/1024**3, 1)) + 'Go' elif data > 1024**2: return str(round(data/1024**2, 1)) + 'Mo' elif data > 1024: return str(round(data/1024, 1)) + 'ko' else: return str(round(data, 1)) + 'o' data = [ [ reformate(ligne[i],format[i]) for i in range(nbcols) ] for ligne in data ] # Largeurs ########## if not largeur : largeur = [ max([len(re.sub('\x1b\[1;([0-9]|[0-9][0-9])m','',ligne[i])) for ligne in data]) for i in range(nbcols) ] elif '*' in largeur: rows, cols = get_screen_size() for i in range(nbcols) : if largeur[i] in ['*',-1] : largeur[i] = max(cols - sum([l for l in largeur if l != '*']) - nbcols - 1, 3) break # Alignement ############ if not alignement : alignement = ['c'] * nbcols def aligne (data, alignement, largeur) : # Longeur sans les chaines de formatage l = len(re.sub('\x1b\[1;([0-9]|[0-9][0-9])m','',data)) # Alignement if l > largeur : # découpage d'une chaine trop longue regexp = re.compile('\x1b\[1;([0-9]|[0-9][0-9])m') new_data = u'' new_len = 0 # On laisse la mise en forme et on coupe les caratères affichés while True : s = regexp.search(data) if s and not s.start() : # c'est de la mise en forme new_data += data[:s.end()] data = data[s.end():] elif new_len < largeur - 1 : # c'est un caratère normal, et il y a la place new_data += data[0] data = data[1:] new_len += 1 else : # c'est un caratère normal mais on a dépassé le max data = data[1:] if not data : return new_data + '*' elif l == largeur : return data elif alignement == 'g' : return u' ' + data + u' '*(largeur-l-1) elif alignement == 'd' : return u' '*(largeur-l-1) + data + u' ' else : return u' '*((largeur-l)/2) + data + u' '*((largeur-l+1)/2) data = [ [ aligne(ligne[i],alignement[i],largeur[i]) for i in range(nbcols) ] for ligne in data ] # Le titre ########## if titre : # ligne de titre chaine = sep_col + sep_col.join([aligne(titre[i],'c',largeur[i]) for i in range(nbcols)]) + sep_col + u'\n' # ligne de séparation chaine += sep_col + u'+'.join([u'-'*largeur[i] for i in range(nbcols)]) + sep_col + u'\n' else : chaine = u'' # Les données ############# chaine += u'\n'.join([sep_col + sep_col.join([ligne[i] for i in range(nbcols)]) + sep_col for ligne in data]) return chaine def get_screen_size(): """Retourne la taille de l'écran. Sous la forme d'un tuble (lignes, colonnes)""" try: from termios import TIOCGWINSZ from struct import pack, unpack from fcntl import ioctl s = pack("HHHH", 0, 0, 0, 0) rows, cols = unpack("HHHH", ioctl(sys.stdout.fileno(), TIOCGWINSZ, s))[:2] return (rows, cols) except: return (24, 80) def prompt(prompt, defaut='', couleur='gras'): u""" Pose la question prompt en couleur (défaut gras), retourne la réponse. """ sys.stdout.write(coul(prompt, couleur).encode(encoding)) if defaut : sys.stdout.write((" [%s]" % defaut).encode(encoding)) sys.stdout.write(" ".encode(encoding)) v = sys.stdin.readline().decode(encoding).strip() if not v: v = defaut return v class anim : """ Permet de créer une animation : truc................./ truc.................- truc.................\ truc.................| ou une barre de progression si le nombre total d'itérations est founi. """ def __init__(self,truc,iter=0) : """ Affichage de : truc.................""" self.txt = truc + '.'*(45-len(truc)) self.c = 1 self.iter = iter sys.stdout.write(self.txt) sys.stdout.flush() def reinit(self) : """ Efface la ligne courrante et affiche : truc................. """ sys.stdout.write(el + self.txt) if self.iter : sys.stdout.write(' '*28) sys.stdout.write(el + self.txt) sys.stdout.flush() def cycle(self) : """ Efface la ligne courrante et affiche : truc..................? ? caratère variant à chaque appel """ if self.iter!=0 : prog = float(self.c) / float(self.iter) pprog = float(self.c-1) / float(self.iter) n = int(20 * prog) if 100*prog != 100*pprog: msg = "%s[%s>%s] %3i%%" % (self.txt, '='*n, ' '*(20 - n), int (100*prog)) sys.stdout.write(el + msg) else : sys.stdout.write(el + self.txt) sys.stdout.write('/-\|'[self.c%4]) sys.stdout.flush() self.c += 1