diff --git a/gestion/dialog/CPS.py b/gestion/dialog/CPS.py index 1ea88eb7..09fd44ee 100644 --- a/gestion/dialog/CPS.py +++ b/gestion/dialog/CPS.py @@ -1,10 +1,23 @@ #!/bin/bash /usr/scripts/python.sh # -*- coding: utf-8 -*- - +# +# Copyright (C) Valentin Samir +# Licence : GPLv3 u""" -Copyright (C) Valentin Samir -Licence : GPLv3 +Module implémentant la "Continuation Passing Style". +Le concept est d'exécuter des fonctions qui sont des TailCallers, +qui elles-mêmes seront amenées à appeler d'autres fonctions, +qui sont des TailCalls, ou qui lèvent des exceptions de type +Continue(TailCall). + +Ces TailCalls pourraient être amenés à exécuter d'autres fonctions, +dans ce cas, pour faire en sorte que leur empreinte mémoire soit +faible, auquel cas, soit ces fonctions sont elles-mêmes des +TailCalls, soit ce sont des TailCallers. + +L'intérêt de ce concept est de favoriser une stack à empreinte +basse. """ import os import sys @@ -16,27 +29,33 @@ import traceback if '/usr/scripts' not in sys.path: sys.path.append('/usr/scripts') + from pythondialog import Dialog as PythonDialog from pythondialog import DialogTerminatedBySignal, PythonDialogErrorBeforeExecInChildProcess from pythondialog import error as DialogError -from gestion.affich_tools import get_screen_size, coul +from gestion import affichage -debug_enable = False -debugf = None +from cranslib import clogger + +# Modifier level à debug pour débugguer. +LOGGER = clogger.CLogger('gest_crans_lc', service="CPS", level='info') +ENCODING = affichage.guess_preferred_encoding() def mydebug(txt): - """Petit fonction pour écrire des messages de débug dans /tmp/gest_crans_lc.log""" - global debugf, debug_enable - if debug_enable: - if debugf is None: - debugf = open('/tmp/gest_crans_lc.log', 'w') - if isinstance(txt, list): - for v in txt: - mydebug(' ' + str(v)) - else: - debugf.write(str(txt)+'\n') - debugf.flush() - os.fsync(debugf) + """Petite fonction pour logguer du debug avec CLogger. + LOGGER.debug n'écrit que si level est à debug""" + if isinstance(txt, list): + txt = "\n".join(txt) + if isinstance(txt, unicode): + txt = txt.encode(ENCODING) + LOGGER.debug(txt) + +class Continue(Exception): + """Exception pour envoyer des TailCall en les raisant + l'argument tailCall est soit une fonction décorée, soit + la fonction retournante elle-même.""" + def __init__(self, tailCall): + self.tailCall = tailCall # Implémentation "à la main" de la tail récursion en python # voir http://kylem.net/programming/tailcall.html @@ -45,63 +64,91 @@ def mydebug(txt): # code en CPS (Continuation Passing Style) class TailCaller(object) : """ - Classe permetant, en décorant des fonctions avec, d'avoir de la tail récursion - faite "à la main" en python (voir http://kylem.net/programming/tailcall.html) + Un TailCaller est le doux nom d'un objet (une fonction décorée) qui crée et + joue avec des TailCall. Les TailCalls sont des jouets qui servent à réduire + l'empreinte dans la stack d'appels récursifs. - Parameters - ---------- - f : function - Fonction décoré + Lorsqu'on __call__ un TailCaller, celui-ci exécute la fonction qu'il décore. + Si le retour de celle-ci est un TailCall, on exécute le call de celui-ci, + et ainsi de suite, jusqu'à avoir un retour qui ne soit pas un TailCall. + + Un call ou la fonction du TailCaller peuvent retourner des erreurs de type + continue, qui généralement contiennent un TailCall, mais peuvent aussi + contenir une sortie. """ other_callers = {} - def __init__(self, f) : + + def __init__(self, f): + """On prend f et son nom et on les stocke dans le TailCaller, wouhou, + c'est trop fou.""" self.f = f self.func_name = f.func_name TailCaller.other_callers[id(self)]=f.func_name + def __del__(self): + """Supprime le TailCaller de la liste""" del(TailCaller.other_callers[id(self)]) - def __call__(self, *args, **kwargs) : + def __call__(self, *args, **kwargs): + """Quand on appelle le TailCaller pour de vrai, + on vérifie la position actuelle du bordel dans la stack, + et on exécute la fonction référencée, et les TailCalls qu'on + peut croiser sur la route, pour peu que leur position en + stack soit postérieure à celle du TailCaller actuel.""" mydebug("***%s calling" % self.func_name) stacklvl = len(inspect.stack()) + + # On applique f une première fois aux arguments + # qui vont bien. + # Cela peut retourner un TailCall, un résultat, ou + # lever une erreur de type Continue. Souvent les fonctions + # raisent des Continue contenant des TailCall, dans le but + # de ne pas augmenter le contenu de la stack. try: - ret = self.f(*args, **kwargs) + ret = self.f(*args, **kwargs) except Continue as c: - ret = c.tailCall + # Le TailCall est là-dedans. + ret = c.tailCall + + # Si f a terminé, ret n'est pas un TailCall mais sa valeur + # de retour. Sinon, on entre dans la boucle. while isinstance(ret, TailCall): - # Si la continuation a été créer par un TailCaller précédent, on propage - # Cela permet d'assurer qu'un TailCaller ne s'occupe que des TailCall - # Crée après lui. - if stacklvl>=ret.stacklvl: + # Ici, on regarde l'endroit dans la stack de l'objet courant + # vis-à-vis de la position dans la stack de ret. (plus le stack + # lvl est élevé, plus on a été appelé récemment) + # On constate donc ici qu'on ne s'intéresse qu'aux TailCall qui + # ont été créés après l'appel du TailCaller actuel. + if stacklvl >= ret.stacklvl: mydebug("***%s terminate" % self.func_name) + # Ici, on peut donc raise un Continue, qui fait sortir + # de l'exécution du TailCaller courant. raise Continue(ret) mydebug("***%s doing %s" % (self.func_name, ret)) + + # L'appel à un TailCall se fait via la méthode handle. + # Si ça retourne une continuation, on récupère le tailCall + # dedans dans ret, et on boucle. Sinon, soit le TailCall + # retourne un TailCall, soit il retourne un résultat. try: ret = ret.handle() except Continue as c: ret = c.tailCall + mydebug("***%s returning %s" % (self.func_name, ret)) return ret - def tailcaller(f): - """ - Décorateur retardant la décoration par TailCaller d'une fonction - À utiliser sur les fonctions faisant de la tail récursion - et devant retourner une valeur. On l'utilise de toute - façon sur la fonction d'entrée dans l'application - Une fonction de devrait pas instancier de TailCall sans - être décoré par ce décorteur de façon générale + """Décorateur permettant aux fonctions d'une classe d'être + décorées comme TailCaller à l'instanciation de ladite classe. + + Cela permet que seules les instances aient des méthodes qui + soient des TailCallers. La conversion se faisant au premier + appel de la fonction dans l'instance. """ f.tailCaller = True return f -class Continue(Exception): - """Exception pour envoyer des TailCall en les raisant""" - def __init__(self, tailCall): - self.tailCall = tailCall - class TailCall(object) : """ Appel tail récursif à une fonction et ses argument @@ -109,12 +156,17 @@ class TailCall(object) : l'appel à une fonction (avec ses listes d'arguements) à un moment futur. La fonction sera appelée dans les cas suivant : - * Le TailCall est retourné par une fonction qui est un TailCaller + * Le TailCall est retourné dans l'appel de la fonction d'un + TailCaller. * L'exception Continue(TailCall(...)) est levée et traverse une fonction qui est un TailCaller """ def __init__(self, call, *args, **kwargs) : + """Création du TailCall, on note la position dans la pile + Si la fonction passée à TailCall est un TailCall, on override + le TailCall qu'on est en train d'instancier. + """ self.stacklvl = len(inspect.stack()) if isinstance(call, TailCall): call.kwargs.update(**kwargs) @@ -129,6 +181,8 @@ class TailCall(object) : self.check(self.args, self.kwargs) def check(self, args, kwargs): + """On vérifie si le TailCall a le bon nombre d'arguments, + et si les keywords arguments sont bons.""" call = self.call if isinstance(call, TailCaller): call = call.f @@ -136,6 +190,7 @@ class TailCall(object) : if targs.varargs is not None: if len(args) + len(kwargs) > len(targs.args): raise TypeError("%s() takes at most %s arguments (%s given)" % (call.func_name, len(targs.args), len(args) + len(kwargs))) + for key in kwargs: if key not in targs.args: raise TypeError("%s() got an unexpected keyword argument '%s'" % (call.func_name, key)) @@ -160,7 +215,11 @@ class TailCall(object) : return result def __call__(self, *args, **kwargs): - tmpkwargs={} + """Met à jour le TailCall en ajoutant args et kwargs + à la liste des arguments à passer au call lors de son appel + via handle. + """ + tmpkwargs = {} tmpkwargs.update(self.kwargs) tmpkwargs.update(kwargs) self.check(self.args + args, tmpkwargs) @@ -171,14 +230,12 @@ class TailCall(object) : def handle(self) : """ Exécute la fonction call sur sa liste d'argument. - on déréférence les TailCaller le plus possible pour réduire - la taille de la stack + + Si la fonction à exécuter est un TailCaller, on déréférence. """ - caller = None call = self.call - while isinstance(call, TailCaller) : - caller = call - call = self.call.f + while isinstance(call, TailCaller): + call = call.f return call(*self.args, **self.kwargs) def unicode_of_Error(x): @@ -227,7 +284,7 @@ class Dialog(object): """ Nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan """ - (lines, cols) = get_screen_size() + (lines, cols) = affichage.getTerminalSize() print "\033[48;5;17m" print " "*(lines * cols) cols = int(min(cols/2, 65)) diff --git a/gestion/dialog/adherent.py b/gestion/dialog/adherent.py index f806060b..dfb11bb3 100644 --- a/gestion/dialog/adherent.py +++ b/gestion/dialog/adherent.py @@ -723,19 +723,19 @@ class Dialog(proprio.Dialog): def set_chambre(adherent, chbre): try: adherent['postalAddress'] = [] - adherent['chbre'] = unicode(output, 'utf-8') + adherent['chbre'] = unicode(chbre, 'utf-8') except UniquenessError: if expulse_squatteur(adherent, chbre): # La chambre est maintenant normalement libre - adherent['chbre'] = unicode(output, 'utf-8') + adherent['chbre'] = unicode(chbre, 'utf-8') else: raise Continue(self_cont) return adherent def todo(chbre, adherent, self_cont, success_cont): - if not output: + if not chbre: raise Continue(self_cont) - if output == "????": + if chbre == "????": raise ValueError("Chambre ???? invalide") if create: return set_chambre(adherent, chbre) @@ -745,7 +745,7 @@ class Dialog(proprio.Dialog): adherent.validate_changes() adherent.history_gen() adherent.save() - self.display_item(item=adherent, title="Adhérent déménagé dans la chambre %s" % output) + self.display_item(item=adherent, title="Adhérent déménagé dans la chambre %s" % chbre) raise Continue(success_cont(adherent=adherent)) (code, output) = self.handle_dialog(cont, box) diff --git a/gestion/gest_crans_lc.py b/gestion/gest_crans_lc.py index 6dcabbf5..7f91f37c 100755 --- a/gestion/gest_crans_lc.py +++ b/gestion/gest_crans_lc.py @@ -1,7 +1,7 @@ #!/bin/bash /usr/scripts/python.sh # -*- coding: utf-8 -*- -u""" +""" Interface utilisateur du système de gestion des machines et adhérents du crans @@ -13,122 +13,268 @@ Licence : GPLv3 ### --default-button pour choisir le bouton sélectionner par defaut ### --not-tags pour masquer les tags quand il ne servent à rien pour l'utilisateur (mais sont utilisé comme index par le programme) +import argparse import os import sys -import argparse +import subprocess + if '/usr/scripts' not in sys.path: sys.path.append('/usr/scripts') -import lc_ldap.objets as objets import lc_ldap.attributs as attributs +import lc_ldap.objets as objets from dialog import adherent, club, machine from dialog.CPS import TailCall, tailcaller from dialog.lc import main -def handle_exit_code(d, code): - """Gère les codes de retour dialog du menu principal""" - if code in (d.DIALOG_CANCEL, d.DIALOG_ESC): - if code == d.DIALOG_CANCEL: - #msg = "Vous avez choisi Annuler dans la dernière fenêtre de dialogue.\n\n" \ - # "Voulez vous quitter le programme ?" - os.system('clear') - sys.exit(0) - else: - msg = "Vous avez appuyé sur ESC ou CTRL+C dans la dernière fenêtre de dialogue.\n\n" \ - "Voulez vous quitter le programme ?" - if d.yesno(msg, width=60) == d.DIALOG_OK: - os.system('clear') - sys.exit(0) + +def handle_dialog_exit_code(dialog, code): + """Gère les codes de retour du menu principal. + + Paramètres: + - ``dialog``: (pythondialog.Dialog) instance courante de l'objet Dialog + - ``code`` : (int) code de retour de la fenêtre Dialog + + Retourne: + - ``True`` quand dialog a quitté correctement (l'utilisateur a bien + saisi une valeur) + - ``False`` quand l'utilisateur annule la sortie du programme + (aucune valeur n'a été saisie) + + Lève les exceptions: + - :py:exc:`SystemExit` quand la sortie du programme est confirmée + + """ + + def exit_from_program(): + """ + Quitte le programme après avoir vidé la console + + Lève l'exception :py:exc:`SystemExit` + """ + subprocess.call('clear') + sys.exit(0) + + if code == dialog.DIALOG_CANCEL: + exit_from_program() + elif code == dialog.DIALOG_ESC: + msg = ( + "Vous avez appuyé sur ESC ou CTRL+C dans la dernière fenêtre" + " de dialogue.\n\nVoulez vous quitter le programme ?" + ) + if dialog.yesno(msg, width=60) == dialog.DIALOG_OK: + exit_from_program() return False else: return True + class GestCrans(adherent.Dialog, club.Dialog, machine.Dialog): @tailcaller def menu_principal(self, tag=None, machine=None, proprio=None): - """Menu principal de l'application affiché au lancement""" - a = attributs + """Menu principal de l'application. + + Paramètres: + - ``tag``: (str) clé du menu sélectionnée par défaut + - ``machine``: (lc_ldap.Machine) machine sélectionnée + - ``proprio``: (lc_ldap.Adherent ou lc_ldap.Club) proprio + sélectionné + + Le menu principal est affiché lorsqu'on démarre l'application, + et lorsqu'on revient au menu principal suite à une action sur un + objet (arguments machine ou proprio). + + Le menu est créé en deux étapes : + - Actions spécifiques à l'objet (`machine`, `proprio`) sélectionné + - Actions génériques + + """ + + # Droits nécessaires pour effectuer les différentes actions. + # Par défaut, on vérifie les droits ''. + # La clé correspond à la clé dans le dictionnaire menu. menu_droits = { - 'default' : [a.cableur, a.nounou], - 'aKM' : [a.nounou], + '': [attributs.cableur, attributs.nounou], + 'aKM': [attributs.nounou], } + + # On initialise le menu avec des actions "génériques" menu = { - 'aA' : {'text':"Inscrire un nouvel adhérent", 'callback': self.create_adherent,}, - 'mA' : {'text':"Modifier l'inscription d'un adhérent", 'callback': self.modif_adherent, 'help':"Changer la chambre, la remarque, la section, la carte d'étudiant ou précâbler."}, - 'aMA': {'text':"Ajouter une machine à un adhérent", 'callback': self.create_machine_adherent}, - #'dA' : {'text':"Détruire un adhérent", 'callback': self.delete_adherent, 'help':"Suppression de l'adhérent ainsi que de ses machines"}, - 'mM' : {'text':"Modifier une machine existante", 'callback': self.modif_machine, 'help':"Changer le nom ou la MAC d'une machine."}, - #'dM' : {'text':"Détruire une machine", 'callback': self.delete_machine}, - 'aC' : {'text':"Inscrire un nouveau club", 'callback': self.create_club}, - 'mC' : {'text':"Modifier un club", 'callback': self.modif_club}, - 'aMC': {'text':"Ajouter une machine à un club", 'callback': self.create_machine_club}, - #'dC' : {'text':"Détruire un club", 'callback': self.delete_club}, - 'aKM': {'text':"Ajouter une machine à l'association", 'callback': self.create_machine_crans}, - '' : {'text':"---------------------------------------",'callback': None}, + 'aA': { + 'text': "Inscrire un nouvel adhérent", + 'callback': self.create_adherent, + }, + + 'mA': { + 'text': "Modifier l'inscription d'un adhérent", + 'callback': self.modif_adherent, + 'help': ( + "Changer la chambre, la remarque, la section, " + "la carte d'étudiant ou précâbler." + ), + }, + + 'aMA': { + 'text': "Ajouter une machine à un adhérent", + 'callback': self.create_machine_adherent, + }, + + # 'dA': { + # 'text': "Détruire un adhérent", + # 'callback': self.delete_adherent, + # 'help': "Suppression de l'adhérent ainsi que de ses machines", + # }, + + 'mM': { + 'text': "Modifier une machine existante", + 'callback': self.modif_machine, + 'help': "Changer le nom ou la MAC d'une machine.", + }, + + # 'dM': { + # 'text': "Détruire une machine", + # 'callback': self.delete_machine, + # }, + + 'aC': { + 'text': "Inscrire un nouveau club", + 'callback': self.create_club, + }, + + 'mC': { + 'text': "Modifier un club", + 'callback': self.modif_club, + }, + + 'aMC': { + 'text': "Ajouter une machine à un club", + 'callback': self.create_machine_club, + }, + + # 'dC': { + # 'text': "Détruire un club", + # 'callback': self.delete_club, + # }, + + 'aKM': { + 'text': "Ajouter une machine à l'association", + 'callback': self.create_machine_crans, + }, + + '': { + 'text': "---------------------------------------", + 'callback': None, + }, } - ### Les clef qui n'existe pas sont toute renvoyé sur la clef '' - #menu_order = ["aA", "mA", "aMA", "dA", "", "mM", "dM", " ", "aC", "mC", "aMC", "dC", " ", "aKM"] - #menu_order = ["aA", "mA", "aMA", "", "mM", " ", "aC", "mC", "aMC", " ", "aKM"] - menu_order = ["aA", "mA", "aMA", "", "mM", "", "aC", "mC", "aMC", "", "aKM"] + + # On liste les actions pour définir leur ordre d'affichage + menu_order = [ + # Actions adhérent + "aA", "mA", "aMA", "", + # Actions machine + "mM", "", + # Actions club + "aC", "mC", "aMC", "", + # Actions Cr@ns + "aKM", + ] + + # Ajout des actions spécifiques en tête du menu + + # On récupère le propriétaire de la machine pour ajouter les + # actions associées if machine and not proprio: proprio = machine.proprio() + + # Sauf si le propriétaire est le Cr@ns if isinstance(proprio, objets.AssociationCrans): proprio = None + + # On a sélectionné un objet. On ajoute les actions spécifiques à + # l'objet avant les actions génériques. if machine or proprio: menu_order = [''] + menu_order + + # Actions spécifiques à une machine if machine: menu_machine = { - 'mMc' : { - 'text':"Modifier la machine %s" % machine['host'][0], - 'callback': TailCall(self.modif_machine, machine=machine), - 'help':"Changer le nom ou la MAC d'une machine." + 'mMc': { + 'text': "Modifier la machine %s" % machine['host'][0], + 'callback': TailCall(self.modif_machine, machine=machine), + 'help': "Changer le nom ou la MAC d'une machine." }, } menu_machine_order = ['mMc'] menu.update(menu_machine) menu_order = menu_machine_order + menu_order + + # Actions spécifiques à un proprio (adhérent ou club) if proprio: - menu_adherent = { - 'mAc' : { - 'text':"Modifier l'inscription de %s" % proprio.get("cn", proprio["nom"])[0], - 'callback': TailCall(self.modif_adherent, adherent=proprio) - }, - 'aMc' : { - 'text':"Ajouter une machine à %s" % proprio.get("cn", proprio["nom"])[0], - 'callback': TailCall(self.create_machine_adherent, adherent=proprio) - }, - } - menu_club = { - 'mCc' : { - 'text':"Modifier l'inscription de %s" % proprio.get("cn", proprio["nom"])[0], - 'callback': TailCall(self.modif_club, club=proprio) - }, - 'aMc' : { - 'text':"Ajouter une machine à %s" % proprio.get("cn", proprio["nom"])[0], - 'callback': TailCall(self.create_machine_club, club=proprio) - }, - } if 'adherent' in proprio['objectClass']: - menu_proprio = menu_adherent + menu_proprio = { + 'mAc': { + 'text': ("Modifier l'inscription de %s" % + proprio.get("cn", proprio["nom"])[0]), + 'callback': TailCall(self.modif_adherent, + adherent=proprio) + }, + 'aMc': { + 'text': ("Ajouter une machine à %s" % + proprio.get("cn", proprio["nom"])[0]), + 'callback': TailCall(self.create_machine_adherent, + adherent=proprio) + }, + } menu_proprio_order = ['mAc', 'aMc'] elif 'club' in proprio['objectClass']: - menu_proprio = menu_club + menu_proprio = { + 'mCc': { + 'text': ("Modifier l'inscription de %s" % + proprio.get("cn", proprio["nom"])[0]), + 'callback': TailCall(self.modif_club, club=proprio) + }, + 'aMc': { + 'text': ("Ajouter une machine à %s" % + proprio.get("cn", proprio["nom"])[0]), + 'callback': TailCall(self.create_machine_club, + club=proprio) + }, + } menu_proprio_order = ['mCc', 'aMc'] else: - raise EnvironmentError("Je ne connais que des adherents et des club comme proprio") + raise EnvironmentError( + "Le proprio sélectionné doit être un adhérent ou un club." + ) menu.update(menu_proprio) menu_order = menu_proprio_order + menu_order - def box(default_item=None): + def dialog_menu_principal(default_item=None): + """Renvoie la boîte de dialogue telle que définie avec le menu + précédent.""" + + # On construit une liste de triplets (clé, titre, texte + # d'aide), passée à dialog, à partir de la liste des clés du + # menu et des fonctions associées choices = [] + + # On reprend les clés dans l'ordre défini par la liste menu_order for key in menu_order: - if self.has_right(menu_droits.get(key, menu_droits['default'])): - choices.append((key, menu[key]['text'], menu[key].get('help', ""))) - while choices[-1][0] == '': - choices=choices[:-1] + # Vérification des droits + if self.has_right(menu_droits.get(key, menu_droits[''])): + choices.append(( + key, + menu[key]['text'], + menu[key].get('help', ""), + )) + + # On enlève les lignes de séparation qui seraient au début + # ou à la fin de la boîte de dialogue while choices[0][0] == '': - choices=choices[1:] + choices = choices[1:] + while choices[-1][0] == '': + choices = choices[:-1] + return self.dialog.menu( "Que souhaitez vous faire ?", width=0, @@ -141,23 +287,79 @@ class GestCrans(adherent.Dialog, club.Dialog, machine.Dialog): timeout=self.timeout, cancel_label="Quitter", backtitle=self._connected_as(), - choices=choices) + choices=choices, + ) - (code, tag) = self.handle_dialog(TailCall(handle_exit_code, self.dialog, self.dialog.DIALOG_ESC), box, tag) - self_cont = TailCall(self.menu_principal, tag=tag, proprio=proprio, machine=machine) - callback = menu.get(tag, menu[''])['callback'] - if handle_exit_code(self.dialog, code) and callback: - return TailCall(callback, cont=TailCall(self.menu_principal, tag=tag, machine=machine, proprio=proprio)) + # Appel de dialog pour afficher le menu construit précédemment. + # Oui, il faut passer la fonction qui gère une annulation d'abord... + (code, tag) = self.handle_dialog( + TailCall( + handle_dialog_exit_code, + self.dialog, + self.dialog.DIALOG_ESC, + ), + dialog_menu_principal, + tag, + ) + + # Appel qui permet de revenir au menu principal + rappel_menu_principal = TailCall( + self.menu_principal, + tag=tag, + proprio=proprio, + machine=machine, + ) + + # On récupère le callback de la ligne sélectionnée par + # l'utilisateur + fonction_selectionnee = menu.get(tag, menu[''])['callback'] + + # On vérifie si l'utilisateur a appelé une ligne non-séparateur + if ( + handle_dialog_exit_code(self.dialog, code) and + fonction_selectionnee + ): + # On appelle la fonction sélectionnée puis on revient au + # menu principal + return TailCall(fonction_selectionnee, cont=rappel_menu_principal) else: - return self_cont - + # La ligne est un séparateur ou l'utilisateur a annulé, on + # revient au menu principal immédiatement + return rappel_menu_principal if __name__ == '__main__': - parser = argparse.ArgumentParser(description='Interface utilisateur du système de gestion des machines et adhérents du crans') - parser.add_argument('--test', help='Utiliser la base de test', dest='ldap_test', default=False, action='store_true') - parser.add_argument('--debug', help='Afficher des info de débug comme les tracebacks', dest='debug_enable', default=False, action='store_true') - parser.add_argument('login', help="Se connecter en tant qu'un autre utilisateur", type=str, default=None, nargs='?') + parser = argparse.ArgumentParser( + description=( + 'Interface utilisateur du système de gestion ' + 'des machines et adhérents du Cr@ns' + ) + ) + parser.add_argument( + '--test', + help='Utiliser la base de test', + dest='ldap_test', + default=False, + action='store_true', + ) + parser.add_argument( + '--debug', + help='Afficher des info de débug comme les tracebacks', + dest='debug_enable', + default=False, + action='store_true', + ) + parser.add_argument( + 'login', + help="Se connecter en tant qu'un autre utilisateur", + type=str, + default=None, + nargs='?', + ) args = parser.parse_args() - main(GestCrans(ldap_test=args.ldap_test, debug_enable=args.debug_enable, custom_user=args.login)) + main(GestCrans( + ldap_test=args.ldap_test, + debug_enable=args.debug_enable, + custom_user=args.login, + )) os.system('clear')