diff --git a/gestion/affich_tools.py b/gestion/affich_tools.py index 4b5eec2a..53b2e854 100644 --- a/gestion/affich_tools.py +++ b/gestion/affich_tools.py @@ -6,29 +6,29 @@ Copyright (C) Frédéric Pauget Licence : GPLv2 """ -import sys -import re import os -import tempfile +import re +import shlex import subprocess +import sys if not "/usr/scripts" in sys.path: sys.path.append("/usr/scripts") import cranslib.cransstrings # Détermination de l'encodage -encoding = None +ENCODING = None try: from locale import getpreferredencoding - encoding = getpreferredencoding() + ENCODING = getpreferredencoding() except: pass -if not encoding: - encoding = sys.stdin.encoding or 'UTF-8' +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' +# 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() @@ -40,45 +40,90 @@ try: 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 +def dialog(backtitle, arg, dialogrc=''): """ - 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')) + Affiche la boîte de dialogue définie avec les arguments fournis + (cf man dialog) - 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 ] + Paramètres: + - ``backtitle`` : (str) texte de l'en-tête, situé en haut à gauche + - ``arg`` : (str ou list) ensemble des paramètres à passer au + programme dialog + - ``dialogrc`` : (str) chemin vers le dialogrc à utiliser + + Utilises: + - :py:var:`os.environ` + - :py:func:`shlex.split` + - :py:class:`subprocess.Popen` + - :py:func:`sys.exit` + + Peut lever: + - :py:exc:`RuntimeError` + + Retournes: + - soit un tuple dont le premier élément est le code d'erreur (0 ou 1) et + le second la sortie du programme dialog (une liste ou une liste vide) + - soit rien du tout parce que sys.exit(0) + """ + # Tant que gest_crans (au moins) n'a pas été modifié pour que arg soit une + # liste, ce kludge est nécessaire. + if not isinstance(arg, list): + arg = shlex.split(to_encoding(arg)) + + commande = [ + "/usr/bin/dialog", + "--backtitle", backtitle, + "--cancel-label", "Retour" + ] + arg + + # On copie l'environnement afin de pouvoir écraser une variable pour + # l'appel au programme dialog sans que cela affecte les autres processus. + environnement = os.environ.copy() + + # Si dialog est fourni, on modifie effectivement l'environnement + if dialogrc: + environnement["DIALOGRC"] = dialogrc + + # Le résultat du programme dialog est dans stderr, on la récupère dans + # un pipe + processus = subprocess.Popen(commande, stderr=subprocess.PIPE, env=environnement) + + # Récupération du contenu du pipe + _, sortie = processus.communicate() + resultat = sortie.splitlines() + + # Récupération du code d'erreur + code_erreur = processus.returncode + + if code_erreur == 1: + # L'utilisateur a annulé + return (1, []) + elif code_erreur == 255 and resultat: + # La liste des arguments passés au programme dialog est invalide + raise RuntimeError(arg, resultat[1].strip()) + elif code_erreur == 255: + # L'utilisateur a appuyé sur ÉCHAP. Dans ce cas, on lui demande de + # confirmer. + # "0" et "0" sont là pour ajuster la taille de la fenêtre à son + # contenu + arg_confirmation = [ + "--title", "Annulation", + "--yesno", "Quitter ?\nLes dernières modifications seront perdues.", + "0", "0" + ] + code_confirmation, _ = dialog(backtitle, arg_confirmation, dialogrc) + if code_confirmation == 0: + # L'utilisateur veut quitter (vraiment). + sys.exit(0) + else: + # L'appui sur ÉCHAP était une fausse manip, on recommence + return dialog(backtitle, arg, dialogrc) + elif not resultat: + # Quand le code d'erreur est nul, il ne faut pas que resultat soit + # vide. + resultat = [''] + + return (0, resultat) def coul(txt, col=None, dialog=False): """ @@ -90,23 +135,22 @@ def coul(txt, col=None, dialog=False): 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 } - codecol_dialog = { - 'rouge': 1, - 'vert': 2, - 'jaune': 3, - 'bleu': 4, - 'violet': 5, - 'cyan': 6, - 'gris': 0, - 'gras': 'b' } + codecol = {'rouge': 31, + 'vert': 32, + 'jaune': 33, + 'bleu': 34, + 'violet': 35, + 'cyan': 36, + 'gris': 30, + 'gras': 50} + codecol_dialog = {'rouge': 1, + 'vert': 2, + 'jaune': 3, + 'bleu': 4, + 'violet': 5, + 'cyan': 6, + 'gris': 0, + 'gras': 'b'} if dialog: try: @@ -127,7 +171,7 @@ OK = coul('OK', 'vert') WARNING = coul('WARNING', 'jaune') ERREUR = coul('ERREUR', 'rouge') -def to_unicode(txt, enc=encoding): +def to_unicode(txt, enc=ENCODING): if isinstance(txt, unicode): return txt else: @@ -139,7 +183,7 @@ def to_unicode(txt, enc=encoding): # donc on le décode en utf-8 \o/ return txt.decode("UTF-8") -def to_encoding(txt, enc=encoding): +def to_encoding(txt, enc=ENCODING): return to_unicode(txt).encode(enc, 'ignore') def cprint(txt, col='blanc', newline=True): @@ -172,16 +216,16 @@ def tableau(data, titre=None, largeur=None, alignement=None, format=None, dialog Si None, s pour chaque colonne """ sep_col = u'|' - if data : + if data: nbcols = len(data[0]) - elif titre : + elif titre: nbcols = len(titre) - else : + else: return u'Aucune donnée' # Formats ######### - if not format : + if not format: format = ['s'] * nbcols def reformate(data, format): @@ -192,7 +236,7 @@ def tableau(data, titre=None, largeur=None, alignement=None, format=None, dialog sys.stderr.write("Cannot cast to unicode %r\n" % data) return unicode(data, errors='ignore') - elif format == 'o' : + elif format == 'o': data = float(data) if data > 1024**3: return str(round(data/1024**3, 1)) + 'Go' @@ -203,12 +247,12 @@ def tableau(data, titre=None, largeur=None, alignement=None, format=None, dialog else: return str(round(data, 1)) + 'o' - data = [ [ reformate(ligne[i],format[i]) for i in range(nbcols) ] for ligne in data ] + data = [[reformate(ligne[i], format[i]) for i in range(nbcols)] for ligne in data] # Largeurs ########## - if not largeur : - largeur = [ max([len(re.sub('\\\Z.' if dialog else '\x1b\[1;([0-9]|[0-9][0-9])m','',ligne[i])) for ligne in data]) for i in range(nbcols) ] + if not largeur: + largeur = [max([len(re.sub('\\\Z.' if dialog else '\x1b\[1;([0-9]|[0-9][0-9])m', '', ligne[i])) for ligne in data]) for i in range(nbcols)] elif '*' in largeur: if width: cols = width @@ -216,67 +260,67 @@ def tableau(data, titre=None, largeur=None, alignement=None, format=None, dialog rows, cols = get_screen_size() if dialog: cols = cols - 6 - for i in range(nbcols) : - if largeur[i] in ['*',-1] : + 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 : + if not alignement: alignement = ['c'] * nbcols - def aligne (data, alignement, largeur) : + def aligne(data, alignement, largeur): # Longeur sans les chaines de formatage - l = len(re.sub('\\\Z.' if dialog else '\x1b\[1;([0-9]|[0-9][0-9])m','',data)) + l = len(re.sub('\\\Z.' if dialog else '\x1b\[1;([0-9]|[0-9][0-9])m', '', data)) # Alignement - if l > largeur : + if l > largeur: # découpage d'une chaine trop longue regexp = re.compile('\\\Z.' if dialog else '\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 : + while True: s = regexp.search(data) - if s and not s.start() : + 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 : + 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 : + else: # c'est un caratère normal mais on a dépassé le max data = data[1:] - if not data : + if not data: return new_data + '*' - elif l == largeur : + elif l == largeur: return data - elif alignement == 'g' : + elif alignement == 'g': return u' ' + data + u' '*(largeur-l-1) - elif alignement == 'd' : + elif alignement == 'd': return u' '*(largeur-l-1) + data + u' ' - else : + 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 ] + data = [[aligne(ligne[i], alignement[i], largeur[i]) for i in range(nbcols)] for ligne in data] # Le titre ########## - if 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' + 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 : + else: chaine = u'' # Les données @@ -308,7 +352,7 @@ def prompt(prompt, defaut=u'', couleur='gras'): defaut = cranslib.cransstrings.decode_dammit(defaut) # coul renvoie alors un unicode prompt_s = coul(prompt, couleur) - if defaut : + if defaut: prompt_s += u" [%s]" % defaut # On fait tout pour ne pas faire crasher le script appelant # si on lève une erreur, on la rattrappe et on laisse une chance @@ -316,7 +360,7 @@ def prompt(prompt, defaut=u'', couleur='gras'): # On laisse néanmoins la possibilité de sortir sur un Ctrl+C while True: try: - v = raw_input(prompt_s.encode(encoding)) + v = raw_input(prompt_s.encode(ENCODING)) v = cranslib.cransstrings.decode_dammit(v).strip() if not v: v = defaut @@ -327,7 +371,7 @@ def prompt(prompt, defaut=u'', couleur='gras'): except UnicodeDecodeError as error2: print "UnicodeDecodeError catched but could not be displayed. Trying ASCII." -class anim : +class anim: """ Permet de créer une animation : truc................./ truc.................- @@ -335,7 +379,7 @@ class anim : truc.................| ou une barre de progression si le nombre total d'itérations est founi. """ - def __init__(self,truc,iter=0) : + def __init__(self, truc, iter=0): """ Affichage de : truc.................""" self.txt = truc + '.'*(45-len(truc)) @@ -344,27 +388,27 @@ class anim : sys.stdout.write(self.txt) sys.stdout.flush() - def reinit(self) : + def reinit(self): """ Efface la ligne courrante et affiche : truc................. """ sys.stdout.write(el + self.txt) - if self.iter : + if self.iter: sys.stdout.write(' '*28) sys.stdout.write(el + self.txt) sys.stdout.flush() - def cycle(self) : + def cycle(self): """ Efface la ligne courrante et affiche : truc..................? ? caratère variant à chaque appel """ - if self.iter!=0 : + 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)) + msg = "%s[%s>%s] %3i%%" % (self.txt, '='*n, ' '*(20 - n), int(100*prog)) sys.stdout.write(el + msg) - else : + else: sys.stdout.write(el + self.txt) sys.stdout.write('/-\|'[self.c%4]) sys.stdout.flush() diff --git a/gestion/gest_crans.py b/gestion/gest_crans.py index be4081f1..bd718d6d 100755 --- a/gestion/gest_crans.py +++ b/gestion/gest_crans.py @@ -53,19 +53,31 @@ isbureau = u'Bureau' in droits encoding = sys.stdin.encoding or 'UTF-8' if u'Nounou' in droits: + # Si on est nounou if os.path.exists(os.path.expanduser('~/.dialogrc')): - dialogrc='~/.dialogrc' + # Si on a un fichier de configuration du programme dialog dans son + # HOME, alors on récupère son chemin. + DIALOGRC = '~/.dialogrc' else: - dialogrc='/etc/dialog.rc' - dlg = dialog.Dialog(DIALOGRC=dialogrc) - dialog_theme='DIALOGRC='+dialogrc + # Sinon on utilise celui du système. + DIALOGRC = '/etc/dialog.rc' + + dlg = dialog.Dialog(DIALOGRC=DIALOGRC) else: + # Si on est pas nounou, on est libre de faire ce que l'on veut avec la + # variable d'environnement DIALOGRC. + DIALOGRC = '' + dlg = dialog.Dialog() - dialog_theme='' def dialog(arg): - return affich_tools.dialog(u'Gestion des adhérents et machines du Crans', arg, dialog_theme) + """ + Raccourci permettant d'appeler :py:func:`affich_tools.dialog`. + + .. seealso:: :py:func:`affich_tools.dialog` + """ + return affich_tools.dialog(u'Gestion des adhérents et machines du Crans', arg, DIALOGRC) in_facture = None ######################################################################### @@ -92,7 +104,8 @@ def set_bases(adher): # Affichage annul, result = dialog(arg) - if annul: return 1 + if annul: + return 1 # Traitement err = ''