#!/usr/bin/env python # -*- coding: utf-8 -*- import sys import os import fcntl import termios import struct import functools def static_var(name, val): """Decorator setting static variable to a function. """ def decorate(fun): functools.wraps(fun) setattr(fun, name, val) return fun return decorate def getTerminalSize(): """Dummy function to get term dimensions. Thanks to http://stackoverflow.com/questions/566746/how-to-get-console-window-width-in-python Could be done using only env variables or thing like stty size, but would be less portable. """ env = os.environ def ioctl_GWINSZ(fd): try: cr = struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ, '1234')) except: return return cr cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2) if not cr: try: fd = os.open(os.ctermid(), os.O_RDONLY) cr = ioctl_GWINSZ(fd) os.close(fd) except: pass if not cr: cr = (env.get('LINES', 25), env.get('COLUMNS', 80)) ### Use get(key[, default]) instead of a try/catch #try: # cr = (env['LINES'], env['COLUMNS']) #except: # cr = (25, 80) return int(cr[1]), int(cr[0]) def cap_text(text, length): """Cap text length to length """ if len(text) > length: return text[0:length-1] + "…" else: return text def format_percent(percent): """Formatte les pourcentages en ajoutant des espaces si nécessaire. """ if percent < 10: return " %s" % (percent,) elif percent < 100: return " %s" % (percent,) else: return str(percent) def rojv(percent, seuils=(100, 75, 50, 25), couls=("cyan", "vert", "jaune", "orange", "rouge")): """Retourne la couleur qui va bien avec le pourcentage Les pourcentages seuil, et les couleurs associées, doivent être rangés en ordre décroissant. """ lens = len(seuils) lenc = len(couls) if lens+1 == lenc: coul = couls[0] for i in range(lens): if percent < seuils[i]: coul = couls[i+1] else: raise EnvironmentError("Seuils doit contenir une variable de moins par rapport à couls") return coul class Animation(object): """Propose une animation lors de la mise à jour ou de l'évolution d'une tâche. Si le nombre de cycles est défini, affiche un pourcentage d'évolution. Si l'option kikoo est définit, met une jauge en plus. Sinon, affiche un truc qui tourne. """ def __init__(self, texte, nb_cycles=0, couleur=True, kikoo=True): """__init__ """ self.texte = texte self.nb_cycles = nb_cycles self.kikoo = kikoo self.step = 0 self.couleur = couleur def kikoo(self, kikoo): """Switch pour la valeur kikoo""" self.kikoo = kikoo def new_step(self): """Effectue une étape dans l'affichage """ cols, _ = getTerminalSize() if not self.nb_cycles > 0: sys.stdout.write("\r%s ..... %s" % (cap_text(self.texte, cols - 10), "\|/-"[self.step%4])) else: percent = int((self.step+1.0)/self.nb_cycles*100) if not self.kikoo or cols <= 55: if self.couleur: percent = style("%s %%" % (format_percent(percent),), rojv(percent)) else: percent = "%s %%" % (format_percent(percent),) sys.stdout.write("\r%s : %s" % (cap_text(self.texte, cols - 10), percent)) else: # La kikoo bar est une barre de la forme [###### ] # Nombre de # amount = percent/4 # On remplit le reste avec des espaces. if self.couleur: # kikoo_print contient la barre et le pourcentage, colorés en fonction du pourcentage kikoo_print = style("[%s%s%s] %s %%" % (amount * '=', ">", (25-amount) * ' ', format_percent(percent)), rojv(percent)) else: kikoo_print = "[%s%s%s] %s %%" % (amount * '=', ">", (25-amount) * ' ', format_percent(percent)) sys.stdout.write("\r%s %s" % (cap_text(self.texte, cols - 45), kikoo_print)) sys.stdout.flush() self.step += 1 def end(self): """Prints a line return """ sys.stdout.write("\n") sys.stdout.flush() @static_var("styles", {}) def style(texte, what=[]): """ Pretty text is pretty On peut appliquer plusieurs styles d'affilée, ils seront alors traités de gauche à droite (les plus à droite sont donc ceux appliqués en dernier, et les plus à gauche en premier, ce qui veut dire que les plus à droite viennent après, et non avant, de fait, comme ils arrivent après, ils n'arrivent pas avant, et du coup, ceux qui arrivent avant peuvent être écrasés par ceux qui arrivent après… Tout ça pour dire que style(texte, ['vert', 'blanc', 'bleu', 'kaki']) est équivalent à style(texte, ['kaki']) qui équivaut à KeyError Traceback (most recent call last) in () ----> 1 styles['kaki'] KeyError: 'kaki' Sinon, il est possible de changer la couleur de fond grace aux couleur f_, et de mettre du GRAS (je songe encore à un module qui fasse du UPPER, parce que c'est inutile, donc kewl. """ if isinstance(what, str): what = [what] # Pour l'éventuelle coloration du fond. add = 0 if not what: return texte if style.styles == {}: zeros = {'noir' : 30, 'rougefonce': 31, 'vertfonce' : 32, 'orange' : 33, 'bleufonce' : 34, 'violet' : 35, 'cyanfonce' : 36, 'grisclair' : 37, } f_zeros = { "f_"+coul : val+10 for (coul, val) in zeros.iteritems() } zeros.update(f_zeros) zeros = { coul: "0;%s" % (val,) for (coul, val) in zeros.items() } ones = { 'gris': 30, 'rouge': 31, 'vert': 32, 'jaune': 33, 'bleu': 34, 'magenta': 35, 'cyan': 36, 'blanc': 37, 'gras': 50, } f_ones = { "f_"+coul : val+10 for (coul, val) in ones.iteritems() } ones.update(f_ones) ones = { coul: "1;%s" % (val,) for (coul, val) in ones.items() } style.styles.update(zeros) style.styles.update(ones) for element in what: texte = "\033[%sm%s\033[1;0m" % (style.styles[element], texte) return texte # WARNING! TOO MANY UPPER CASES. OK = style('Ok', 'vert') WARNING = style('Warning', 'jaune') ERREUR = style('Erreur', 'rouge') def prettyDoin(what, status): """Affiche une opération en cours et met son statut à jour """ if status == "...": sys.stdout.write("\r[%s] %s" % (style(status, "jaune"), what)) elif status == "Ok": sys.stdout.write("\r[%s] %s\n" % (OK, what)) elif status == "Warning": sys.stdout.write("\r[%s] %s\n" % (WARNING, what)) else: sys.stdout.write("\r[%s] %s\n" % (ERREUR, what)) sys.stdout.flush() if __name__ == "__main__": import time a = Animation(texte="Test de l'animation", nb_cycles=10000, couleur=True, kikoo=True) for i in range(0, a.nb_cycles): time.sleep(0.0001) a.new_step() a.end() prettyDoin("Je cuis des carottes.", "...") time.sleep(1) prettyDoin("Les carottes sont cuites." , "Ok")