Compare commits

...
Sign in to create a new pull request.

4 commits

Author SHA1 Message Date
Pierre-Elliott Bécue
ef07bc5a28 Commentaires dans CPS, à étoffer un peu. Et léger typofix. 2015-03-04 00:01:10 +01:00
Nicolas Dandrimont
890a1067fc [gest_crans_lc] lisibilité et documentation du menu principal 2015-02-23 01:37:36 +01:00
Nicolas Dandrimont
e274be0864 [gest_crans_lc] nettoyage de la fonction handle_exit_code 2015-02-23 01:35:48 +01:00
Nicolas Dandrimont
6ff3e180a2 [gest_crans_lc] Les docstrings sont des strings 2015-02-22 22:29:23 +01:00
3 changed files with 401 additions and 142 deletions

View file

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

View file

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

View file

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