From 68fd355dad7bfc146d73174379eb33c1ca7349f2 Mon Sep 17 00:00:00 2001 From: Daniel STAN Date: Wed, 27 Jun 2012 21:46:06 +0200 Subject: [PATCH 01/27] =?UTF-8?q?[crans=5Futils]=20ip=5Fof=5Fmid:=20mid=20?= =?UTF-8?q?sp=C3=A9ciaux?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crans_utils.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crans_utils.py b/crans_utils.py index b4b498c..33c5620 100644 --- a/crans_utils.py +++ b/crans_utils.py @@ -41,6 +41,12 @@ def ip_of_mid(mid): break else: raise ValueError("Mid dans aucune plage: %d" % mid) + + if net == 'special': + try: + return netaddr.IPAddress(config.mid_machines_speciales[mid]) + except KeyError: + return ValueError(u"Machine speciale inconnue: %d" % mid) return netaddr.IPAddress(netaddr.IPNetwork(config.NETs[net][0]).first + mid - plage[0]) From 9702b1825cf8a15e36dc869976e58eb0cad6d9bc Mon Sep 17 00:00:00 2001 From: Vincent Le Gallic Date: Tue, 16 Oct 2012 19:28:18 +0200 Subject: [PATCH 02/27] Rage commit --- __init__.py | 5 +++++ attributs.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/__init__.py b/__init__.py index e69de29..9cf1040 100644 --- a/__init__.py +++ b/__init__.py @@ -0,0 +1,5 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from lc_ldap import lc_ldap +__all__ = [lc_ldap] diff --git a/attributs.py b/attributs.py index 657a211..74c1cbc 100644 --- a/attributs.py +++ b/attributs.py @@ -288,7 +288,7 @@ class droits(Attr): def parse_value(self, val, ldif): if val.lower() not in ['apprenti', 'nounou', 'cableur', 'tresorier', 'bureau', - 'webmaster', 'webradio', 'imprimeur', 'multimachines', 'victime', 'moderateur']: + 'webmaster', 'webradio', 'imprimeur', 'multimachines', 'victime', 'moderateur', 'nounours']: raise ValueError("Ces droits n'existent pas ('%s')" % val) if val.lower() == 'webmaster': self.value = u'WebMaster' From 129c4593b59d946d907ff42e07b9446e3cc968ec Mon Sep 17 00:00:00 2001 From: Vincent Le Gallic Date: Tue, 16 Oct 2012 19:30:30 +0200 Subject: [PATCH 03/27] Une fonction pour se connecter facilement en admin en important secrets.py --- lc_ldap.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lc_ldap.py b/lc_ldap.py index dbd983e..d87d67d 100644 --- a/lc_ldap.py +++ b/lc_ldap.py @@ -76,7 +76,13 @@ def cldif_to_ldif(cldif): def lc_ldap_test(): """Binding LDAP à la base de tests""" - return lc_ldap(dn='cn=admin,dc=crans,dc=org', cred='75bdb64f32') + return lc_ldap(uri='ldap://vo.adm.crans.org',dn='cn=admin,dc=crans,dc=org', cred='75bdb64f32') + +def lc_ldap_admin(): + """Binding LDAP à la vraie vase, en admin. + Possible seulement si on peut lire secrets.py""" + secrets = import_secrets() + return lc_ldap(uri='ldap://ldap.adm.crans.org/', dn=secrets.ldap_auth_dn, cred=secrets.ldap_password) class lc_ldap(ldap.ldapobject.LDAPObject): """Connexion à la base ldap crans, chaque instance représente une connexion From 42ebecd9ca96b5cb7a6e086c095a36dc115bacba Mon Sep 17 00:00:00 2001 From: Vincent Le Gallic Date: Sun, 21 Oct 2012 03:40:00 +0200 Subject: [PATCH 04/27] =?UTF-8?q?[lc=5Fldap]=20Maintenant=20on=20peut=20se?= =?UTF-8?q?=20connecter=20=C3=A0=20la=20base=20de=20test=20*et*=20=C3=A0?= =?UTF-8?q?=20la=20vraie=20base.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lc_ldap.py | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/lc_ldap.py b/lc_ldap.py index d87d67d..579c13e 100644 --- a/lc_ldap.py +++ b/lc_ldap.py @@ -43,10 +43,17 @@ import config, crans_utils from attributs import attrify, blacklist from ldap_locks import CransLock -uri = 'ldapi:///' #'ldap://ldap.adm.crans.org/' +uri = 'ldap://ldap.adm.crans.org/' base_dn = 'ou=data,dc=crans,dc=org' log_dn = "cn=log" +# Quand on a besoin du fichier de secrets +def import_secrets(): + if not "/etc/crans/secrets/" in sys.path: + sys.path.append("/etc/crans/secrets/") + import secrets + return secrets + # Champs à ignorer dans l'historique HIST_IGNORE_FIELDS = ["modifiersName", "entryCSN", "modifyTimestamp", "historique"] @@ -79,7 +86,7 @@ def lc_ldap_test(): return lc_ldap(uri='ldap://vo.adm.crans.org',dn='cn=admin,dc=crans,dc=org', cred='75bdb64f32') def lc_ldap_admin(): - """Binding LDAP à la vraie vase, en admin. + """Binding LDAP à la vraie base, en admin. Possible seulement si on peut lire secrets.py""" secrets = import_secrets() return lc_ldap(uri='ldap://ldap.adm.crans.org/', dn=secrets.ldap_auth_dn, cred=secrets.ldap_password) @@ -87,23 +94,31 @@ def lc_ldap_admin(): class lc_ldap(ldap.ldapobject.LDAPObject): """Connexion à la base ldap crans, chaque instance représente une connexion """ - def __init__(self, dn=None, user=None, cred=None, uri=uri): + def __init__(self, dn=None, user=None, cred=None, uri=uri, test=False): """Initialise la connexion ldap, - En authentifiant avec dn et cred s'ils sont précisés - Si dn n'est pas précisé, mais que user est précisé, récupère le dn associé à l'uid user, et effectue l'authentification avec ce dn et cred - Sinon effectue une authentification anonyme + Si test est à True, on se connecte à la base de test sur vo. """ - + if test: + uri = "ldapi:///" ldap.ldapobject.LDAPObject.__init__(self, uri) if user and not re.match('[a-z_][a-z0-9_-]*', user): raise ValueError('Invalid user name: %s' % user) - # Si un username, on récupère le dn associé + # Si un username, on récupère le dn associé… if user and not dn: - self.simple_bind_s(base_dn) + if test: + # …en anonyme si on se connecte à la base de test + self.simple_bind_s(base_dn) + else: + # …sinon, en se connectant en readonly (on récupère les identifiants dans secrets.py) + secrets = import_secrets() + self.simple_bind_s(who=secrets.ldap_readonly_auth_dn, cred=secrets.ldap_readonly_password) res = self.search_s(base_dn, 1, 'uid=%s' % user) if len(res) < 1: raise ldap.INVALID_CREDENTIALS({'desc': 'No such user: %s' % user }) From a06b40b4e054ac39f027db6d4a3b9aea81d310a4 Mon Sep 17 00:00:00 2001 From: Valentin Samir Date: Sun, 4 Nov 2012 03:29:29 +0100 Subject: [PATCH 05/27] =?UTF-8?q?[lc=5Fldap]=20Ajout=20d'une=20m=C3=A9thod?= =?UTF-8?q?e=20carte=5Fok=20pour=20carte=20=C3=A9tudiant=20et=20blackliste?= =?UTF-8?q?=20virtuelles=20pour=20carte=20=C3=A9tudiant=20et=20chambre=20i?= =?UTF-8?q?nvalides?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lc_ldap.py | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/lc_ldap.py b/lc_ldap.py index 579c13e..90b3990 100644 --- a/lc_ldap.py +++ b/lc_ldap.py @@ -453,8 +453,18 @@ class CransLdapObject(object): - Proposer de filtrer les blacklistes avec un arg supplémentaire ? - Vérifier les blacklistes des machines pour les adhérents ? """ + blacklist_liste=[] + # blacklistes virtuelle si on est un adhérent pour carte étudiant et chambre invalides + if self.__class__.__name__ == "adherent" and self.paiement_ok(): + if not config.periode_transitoire and config.bl_carte_et_actif and not self.carte_ok(): + bl = blacklist(u'%s$%s$%s$%s' % ('-', '-', 'carte_etudiant', ''), {}, self.conn, False) + blacklist_liste.append(bl) + if self['chbre'][0].value == '????': + bl = blacklist(u'%s$%s$%s$%s' % ('-', '-', 'chambre_invalide', ''), {}, self.conn, False) + blacklist_liste.append(bl) attrs = (self.attrs if self.mode not in ["w", "rw"] else self._modifs) - return filter((lambda bl: bl.is_actif()), attrs.get("blacklist",[])) + blacklist_liste.extend(filter((lambda bl: bl.is_actif()), attrs.get("blacklist",[]))) + return blacklist_liste def blacklist(self, sanction, commentaire, debut="now", fin = '-'): u""" @@ -506,6 +516,20 @@ class proprio(CransLdapObject): pass return bool_carte and bool_paiement return bool_paiement + + def carte_ok(self): + u"""Renvoie si le propriétaire a donné sa carte pour l'année en cours""" + if not self.dn == base_dn and config.bl_carte_et_actif and not 'club' in map(lambda x:x.value,self["objectClass"]): + bool_carte = False + try: + for carte in self['carteEtudiant']: + if carte.value == config.ann_scol: + bool_carte = True + except KeyError: + pass + return bool_carte + return True + From 5d9c7321973d76f7c8208b0f458af5eb2777b609 Mon Sep 17 00:00:00 2001 From: Valentin Samir Date: Sun, 4 Nov 2012 03:34:38 +0100 Subject: [PATCH 06/27] [attributs] Assouplissement sur le formatage de certain attributs --- attributs.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/attributs.py b/attributs.py index 74c1cbc..0b98e14 100644 --- a/attributs.py +++ b/attributs.py @@ -205,7 +205,7 @@ class yearAttr(intAttr): def parse_value(self, val, ldif): if int(val) < 1998 or int(val) > config.ann_scol: - raise ValueError("Année invalide (%s)" % val) + raise ValueError("Annee invalide (%s)" % val) self.value = int(val) @@ -471,11 +471,11 @@ class blacklist(Attr): def parse_value(self, bl, ldif): bl_debut, bl_fin, bl_type, bl_comm = bl.split('$') now = time.time() - self.value = { 'debut' : int (bl_debut), + self.value = { 'debut' : bl_debut if bl_debut == '-' else int (bl_debut), 'fin' : bl_fin if bl_fin == '-' else int(bl_fin), 'type' : bl_type, 'comm' : bl_comm, - 'actif' : int(bl_debut) < now and (bl_fin == '-' or int(bl_fin) > now) } + 'actif' : (bl_debut == '-' or int(bl_debut) < now) and (bl_fin == '-' or int(bl_fin) > now) } def is_actif(self): return self.value['actif'] From 9963e7cf4cdf7991d6b6baf44b885129ac7b40e3 Mon Sep 17 00:00:00 2001 From: Vincent Le Gallic Date: Mon, 5 Nov 2012 05:26:18 +0100 Subject: [PATCH 07/27] =?UTF-8?q?Fonction=20d'=C3=A9chapement?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lc_ldap.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lc_ldap.py b/lc_ldap.py index 90b3990..f615e27 100644 --- a/lc_ldap.py +++ b/lc_ldap.py @@ -32,6 +32,7 @@ from __future__ import with_statement import os, sys, ldap, re, netaddr, datetime, copy, time, random +import ldap.filter from ldap.modlist import addModlist, modifyModlist try: from Levenshtein import jaro @@ -57,6 +58,11 @@ def import_secrets(): # Champs à ignorer dans l'historique HIST_IGNORE_FIELDS = ["modifiersName", "entryCSN", "modifyTimestamp", "historique"] +def escape(chaine): + """Renvoie une chaîne échapée pour pouvoir la mettre en toute sécurité + dans une requête ldap.""" + return ldap.filter.escape_filter_chars(chaine) + def ldif_to_uldif(ldif): uldif = {} for attr, vals in ldif.items(): From 3c22c3c75e2db35a38f6e3b6ddab539e72af6d6f Mon Sep 17 00:00:00 2001 From: Valentin Samir Date: Tue, 6 Nov 2012 18:41:13 +0100 Subject: [PATCH 08/27] =?UTF-8?q?[lc=5Fldap]=20On=20laisse=20un=20sursis?= =?UTF-8?q?=20d'une=20semaine=20apr=C3=A8s=20paiement=20avant=20de=20d?= =?UTF-8?q?=C3=A9connecter=20pour=20carte=20=C3=A9tudiant=20apr=C3=A8s=20l?= =?UTF-8?q?e=2001/11?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lc_ldap.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lc_ldap.py b/lc_ldap.py index f615e27..cf123f7 100644 --- a/lc_ldap.py +++ b/lc_ldap.py @@ -463,8 +463,13 @@ class CransLdapObject(object): # blacklistes virtuelle si on est un adhérent pour carte étudiant et chambre invalides if self.__class__.__name__ == "adherent" and self.paiement_ok(): if not config.periode_transitoire and config.bl_carte_et_actif and not self.carte_ok(): - bl = blacklist(u'%s$%s$%s$%s' % ('-', '-', 'carte_etudiant', ''), {}, self.conn, False) - blacklist_liste.append(bl) + for h in self['historique'][::-1]: + x=re.match("(.*),.* : .*paiement\+%s.*" % config.ann_scol,h.value) + if x != None: + if (time.time()-time.mktime(time.strptime(x.group(1),'%d/%m/%Y %H:%M')))>config.sursis_carte: + bl = blacklist(u'%s$%s$%s$%s' % ('-', '-', 'carte_etudiant', ''), {}, self.conn, False) + blacklist_liste.append(bl) + break if self['chbre'][0].value == '????': bl = blacklist(u'%s$%s$%s$%s' % ('-', '-', 'chambre_invalide', ''), {}, self.conn, False) blacklist_liste.append(bl) From 2d9cdeaa044373253890f522f609f33142c116c7 Mon Sep 17 00:00:00 2001 From: Valentin Samir Date: Mon, 19 Nov 2012 17:15:17 +0100 Subject: [PATCH 09/27] [lc_ldap] ajout d'une fonction sursis_carte. --- lc_ldap.py | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/lc_ldap.py b/lc_ldap.py index cf123f7..8da4346 100644 --- a/lc_ldap.py +++ b/lc_ldap.py @@ -462,14 +462,9 @@ class CransLdapObject(object): blacklist_liste=[] # blacklistes virtuelle si on est un adhérent pour carte étudiant et chambre invalides if self.__class__.__name__ == "adherent" and self.paiement_ok(): - if not config.periode_transitoire and config.bl_carte_et_actif and not self.carte_ok(): - for h in self['historique'][::-1]: - x=re.match("(.*),.* : .*paiement\+%s.*" % config.ann_scol,h.value) - if x != None: - if (time.time()-time.mktime(time.strptime(x.group(1),'%d/%m/%Y %H:%M')))>config.sursis_carte: - bl = blacklist(u'%s$%s$%s$%s' % ('-', '-', 'carte_etudiant', ''), {}, self.conn, False) - blacklist_liste.append(bl) - break + if not config.periode_transitoire and config.bl_carte_et_actif and not self.carte_ok() and not self.sursis_carte(): + bl = blacklist(u'%s$%s$%s$%s' % ('-', '-', 'carte_etudiant', ''), {}, self.conn, False) + blacklist_liste.append(bl) if self['chbre'][0].value == '????': bl = blacklist(u'%s$%s$%s$%s' % ('-', '-', 'chambre_invalide', ''), {}, self.conn, False) blacklist_liste.append(bl) @@ -506,6 +501,15 @@ class proprio(CransLdapObject): super(proprio, self).__init__(conn, dn, mode, ldif) self._machines = machines + def sursis_carte(self): + for h in self['historique'][::-1]: + x=re.match("(.*),.* : .*(paiement\+%s|inscription).*" % config.ann_scol,h.value) + if x != None: + if (time.time()-time.mktime(time.strptime(x.group(1),'%d/%m/%Y %H:%M')))<=config.sursis_carte: + return True + break + return False + def paiement_ok(self): u"""Renvoie si le propriétaire a payé pour l'année en cours""" if self.dn == base_dn: @@ -515,8 +519,15 @@ class proprio(CransLdapObject): for paiement in self['paiement']: if paiement.value == config.ann_scol: bool_paiement = True + break + # Pour la période transitoire année précédente ok + if config.periode_transitoire and paiement.value == config.ann_scol -1: + bool_paiement = True + break except KeyError: pass + # Doit-on bloquer en cas de manque de la carte d'etudiant ? + # (si période transitoire on ne bloque dans aucun cas) if config.bl_carte_et_definitif and not 'club' in map(lambda x:x.value,self["objectClass"]): bool_carte = False try: @@ -525,6 +536,9 @@ class proprio(CransLdapObject): bool_carte = True except KeyError: pass + # Si inscrit depuis moins de config.sursis_carte, on laisse un sursis + if not bool_carte and self.sursis_carte(): + bool_carte = True return bool_carte and bool_paiement return bool_paiement From fd97ff0940261420e2584963642f564c34b79743 Mon Sep 17 00:00:00 2001 From: Valentin Samir Date: Thu, 22 Nov 2012 18:17:02 +0100 Subject: [PATCH 10/27] [lc_ldap] typo --- lc_ldap.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lc_ldap.py b/lc_ldap.py index 8da4346..2a1457a 100644 --- a/lc_ldap.py +++ b/lc_ldap.py @@ -505,9 +505,7 @@ class proprio(CransLdapObject): for h in self['historique'][::-1]: x=re.match("(.*),.* : .*(paiement\+%s|inscription).*" % config.ann_scol,h.value) if x != None: - if (time.time()-time.mktime(time.strptime(x.group(1),'%d/%m/%Y %H:%M')))<=config.sursis_carte: - return True - break + return ((time.time()-time.mktime(time.strptime(x.group(1),'%d/%m/%Y %H:%M')))<=config.sursis_carte) return False def paiement_ok(self): From 88ffb2f347373a807e75c9726d99a284fa1a7bba Mon Sep 17 00:00:00 2001 From: Vincent Le Gallic Date: Sun, 25 Nov 2012 22:41:57 +0100 Subject: [PATCH 11/27] =?UTF-8?q?On=20peut=20d=C3=A9sormais=20utiliser=20o?= =?UTF-8?q?bjet.nom=20de=20la=20m=C3=AAme=20mani=C3=A8re=20que=20objet["no?= =?UTF-8?q?m"]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lc_ldap.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/lc_ldap.py b/lc_ldap.py index 2a1457a..5607ced 100644 --- a/lc_ldap.py +++ b/lc_ldap.py @@ -393,6 +393,18 @@ class CransLdapObject(object): except KeyError: return default + def __getattribute__(self, attr): + """Essentiellement un wrapper pour __getitem__ + On peut donc utiliser objet.attributLdap de la même manière que objet.["attributLdap"]""" + # hack + try: + return super(CransLdapObject, self).__getattribute__(attr) + except AttributeError: + try: + return self.__getitem__(attr) + except KeyError: + raise AttributeError(u"'%s' object has no attribute '%s'" % (self.__class__.__name__, attr)) + def __getitem__(self, attr): if self.mode in [ 'w', 'rw' ]: return [ v for v in self._modifs[attr] ] @@ -407,6 +419,15 @@ class CransLdapObject(object): return attr in self.ofields or attr in self.xfields or\ attr in self.ufields or attr in self.mfields + def __setattr__(self, attr, value): + """Essentiellement un wrapper pour __setitem__ + On peut donc utiliser objet.attributLdap = bidule de la même manière que objet.["attributLdap"] = bidule""" + # On n'utilise __setiitem__ que si l'attributLdap existe déjà + if self.has_key(attr): + return self.__setitem__(attr, value) + else: + return object.__setattr__(self, attr, value) + def __setitem__(self, attr, values): if self.mode not in ['w', 'rw']: raise ValueError("Objet en lecture seule") From 85b0c6b7fa87622089bf7b876b97c6260bbaa977 Mon Sep 17 00:00:00 2001 From: Vincent Le Gallic Date: Tue, 27 Nov 2012 20:03:48 +0100 Subject: [PATCH 12/27] =?UTF-8?q?En=20fait=20une=20ann=C3=A9e=20dans=20le?= =?UTF-8?q?=20futur=20n'est=20pas=20interdite.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Osm ayant adhéré jusqu'en 2021, on en a légèrement besoin… --- attributs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/attributs.py b/attributs.py index 0b98e14..822f27a 100644 --- a/attributs.py +++ b/attributs.py @@ -204,7 +204,7 @@ class yearAttr(intAttr): optional= True def parse_value(self, val, ldif): - if int(val) < 1998 or int(val) > config.ann_scol: + if int(val) < 1998: raise ValueError("Annee invalide (%s)" % val) self.value = int(val) From b4efbf2cd12188145d6c804af785560e23fa4343 Mon Sep 17 00:00:00 2001 From: Vincent Le Gallic Date: Wed, 28 Nov 2012 01:56:42 +0100 Subject: [PATCH 13/27] =?UTF-8?q?Revert=20"On=20peut=20d=C3=A9sormais=20ut?= =?UTF-8?q?iliser=20objet.nom=20de=20la=20m=C3=AAme=20mani=C3=A8re=20que?= =?UTF-8?q?=20objet["nom"]"=20En=20fait=20=C3=A7a=20marche=20pas,=20ou=20s?= =?UTF-8?q?eulement=20=C3=A0=20moiti=C3=A9,=20donc=20c'est=20dangereux=20d?= =?UTF-8?q?e=20le=20laisser.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 88ffb2f347373a807e75c9726d99a284fa1a7bba. --- lc_ldap.py | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/lc_ldap.py b/lc_ldap.py index 5607ced..2a1457a 100644 --- a/lc_ldap.py +++ b/lc_ldap.py @@ -393,18 +393,6 @@ class CransLdapObject(object): except KeyError: return default - def __getattribute__(self, attr): - """Essentiellement un wrapper pour __getitem__ - On peut donc utiliser objet.attributLdap de la même manière que objet.["attributLdap"]""" - # hack - try: - return super(CransLdapObject, self).__getattribute__(attr) - except AttributeError: - try: - return self.__getitem__(attr) - except KeyError: - raise AttributeError(u"'%s' object has no attribute '%s'" % (self.__class__.__name__, attr)) - def __getitem__(self, attr): if self.mode in [ 'w', 'rw' ]: return [ v for v in self._modifs[attr] ] @@ -419,15 +407,6 @@ class CransLdapObject(object): return attr in self.ofields or attr in self.xfields or\ attr in self.ufields or attr in self.mfields - def __setattr__(self, attr, value): - """Essentiellement un wrapper pour __setitem__ - On peut donc utiliser objet.attributLdap = bidule de la même manière que objet.["attributLdap"] = bidule""" - # On n'utilise __setiitem__ que si l'attributLdap existe déjà - if self.has_key(attr): - return self.__setitem__(attr, value) - else: - return object.__setattr__(self, attr, value) - def __setitem__(self, attr, values): if self.mode not in ['w', 'rw']: raise ValueError("Objet en lecture seule") From 1c6af7a15df100cb1abdd4ea79c7ed9fbb409f3a Mon Sep 17 00:00:00 2001 From: Vincent Le Gallic Date: Wed, 28 Nov 2012 02:18:03 +0100 Subject: [PATCH 14/27] =?UTF-8?q?Ajout=20=C3=A0=20la=20classe=20CransLdapO?= =?UTF-8?q?bject=20d'une=20property=20pour=20voir=20facilement=20tous=20le?= =?UTF-8?q?s=20champs=20LDAP?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lc_ldap.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lc_ldap.py b/lc_ldap.py index 2a1457a..bb7c201 100644 --- a/lc_ldap.py +++ b/lc_ldap.py @@ -354,6 +354,11 @@ class CransLdapObject(object): self._modifs = ldif_to_cldif(ldif_to_uldif(res[0][1]), conn, check_ctxt = False) + def _get_fields(self): + """Renvoie la liste des champs LDAP de l'objet""" + return self.ofields + self.xfields + self.ufields + self.mfields + fields = property(_get_fields) + def save(self): """Sauvegarde dans la base les modifications apportées à l'objet. Interne: Vérifie que self._modifs contient des valeurs correctes et From 750a23602ab3e966a60e99e2bba09f5ce3285885 Mon Sep 17 00:00:00 2001 From: Vincent Le Gallic Date: Wed, 28 Nov 2012 15:42:18 +0100 Subject: [PATCH 15/27] =?UTF-8?q?Modification=20des=20exceptions=20lev?= =?UTF-8?q?=C3=A9es=20en=20cas=20d'erreur=20afin=20qu'elle=20ne=20crashent?= =?UTF-8?q?=20pas=20elles-m=C3=AAme.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ça consiste essentiellement en du s/%s/%r/ --- attributs.py | 31 +++++++++++++++---------------- lc_ldap.py | 4 ++-- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/attributs.py b/attributs.py index 822f27a..95c9696 100644 --- a/attributs.py +++ b/attributs.py @@ -141,8 +141,7 @@ class objectClass(Attr): 'adherent', 'club', 'machine', 'machineCrans', 'borneWifi', 'machineWifi', 'machineFixe', 'cransAccount', 'service', 'facture', 'freeMid' ]: - print(val) - raise ValueError("Pourquoi insérer un objectClass=%s ?" % val) + raise ValueError("Pourquoi insérer un objectClass=%r ?" % val) else: self.value = unicode(val) @@ -150,7 +149,7 @@ class objectClass(Attr): class intAttr(Attr): def parse_value(self, val, ldif): if int(val) <= 0: - raise ValueError("Valeur entière invalide : %s" % val) + raise ValueError("Valeur entière invalide : %r" % val) self.value = int(val) def __unicode__(self): @@ -196,7 +195,7 @@ class tel(Attr): def parse_value(self, val, ldif): self.value = format_tel(val) if len(self.value) == 0: - raise ValueError("Numéro de téléphone invalide ('%s')" % val) + raise ValueError("Numéro de téléphone invalide (%r)" % val) class yearAttr(intAttr): @@ -205,7 +204,7 @@ class yearAttr(intAttr): def parse_value(self, val, ldif): if int(val) < 1998: - raise ValueError("Annee invalide (%s)" % val) + raise ValueError("Année invalide (%r)" % val) self.value = int(val) @@ -226,7 +225,7 @@ class mailAlias(Attr): def parse_value(self, val, ldif): val = val.lower() if not re.match('[a-z][-_.0-9a-z]+', val): - raise ValueError("Alias mail invalide (%s)" % val) + raise ValueError("Alias mail invalide (%r)" % val) self.value = val @@ -266,7 +265,7 @@ class chbre(Attr): self.value = val return else: - raise ValueError("Club devrait etre en XclN, pas en %s" % val) + raise ValueError("Club devrait etre en XclN, pas en %r" % val) if val in (u"EXT", u"????"): self.value = val @@ -289,7 +288,7 @@ class droits(Attr): def parse_value(self, val, ldif): if val.lower() not in ['apprenti', 'nounou', 'cableur', 'tresorier', 'bureau', 'webmaster', 'webradio', 'imprimeur', 'multimachines', 'victime', 'moderateur', 'nounours']: - raise ValueError("Ces droits n'existent pas ('%s')" % val) + raise ValueError("Ces droits n'existent pas (%r)" % val) if val.lower() == 'webmaster': self.value = u'WebMaster' else: @@ -304,7 +303,7 @@ class solde(Attr): def parse_value(self, solde, ldif): # on évite les dépassements, sauf si on nous dit de ne pas vérifier if self.ctxt_check and not (float(solde) >= config.impression.decouvert and float(solde) <= 1024.): - raise ValueError("Solde invalide: %s" % solde) + raise ValueError("Solde invalide: %r" % solde) self.value = solde class dnsAttr(Attr): @@ -313,7 +312,7 @@ class dnsAttr(Attr): name, net = dns.split('.', 1) if self.ctxt_check and (net not in ['crans.org', 'wifi.crans.org'] or not re.match('[a-z][-_a-z0-9]+', name)): - raise ValueError("Nom d'hote invalide '%s'" % dns) + raise ValueError("Nom d'hote invalide %r" % dns) self.value = dns @@ -395,18 +394,18 @@ class portAttr(Attr): a,b = port.split(":", 1) if a: if int(a) <0 or int(a)> 65535: - raise ValueError("Port invalide: %s" % port) + raise ValueError("Port invalide: %r" % port) else: a = 0 if b: if int(a) <0 or int(a)> 65535: - raise ValueError("Port invalide: %s" % port) + raise ValueError("Port invalide: %r" % port) else: b = 65535 self.value = [int(a), int(b)] else: if int(port) <0 or int(port)> 65535: - raise ValueError("Port invalide: %s" % port) + raise ValueError("Port invalide: %r" % port) self.value = [int(port)] def __unicode__(self): @@ -512,7 +511,7 @@ class charteMA(Attr): def parse_value(self, signed, ldif): if signed.upper() not in ["TRUE", "FALSE"]: - raise ValueError("La charte MA est soit TRUE ou FALSE, pas %s" % signed) + raise ValueError("La charte MA est soit TRUE ou FALSE, pas %r" % signed) self.value = signed.upper() class homeDirectory(Attr): @@ -525,7 +524,7 @@ class homeDirectory(Attr): if uid.startswith('club-'): uid = uid.split('-',1)[1] if home != u'/home/%s' % uid and home != u'/home/club/%s' % uid: - raise ValueError("Le répertoire personnel n'est pas bon: %s (devrait être %s ou %s)" % + raise ValueError("Le répertoire personnel n'est pas bon: %r (devrait être %r ou %r)" % (home, '/home/%s' % ldif['uid'][0], '/home/club/%s' % ldif['uid'][0])) self.value = home @@ -562,7 +561,7 @@ class loginShell(Attr): '/bin//zsh' ''] if self.ctxt_check and (shell not in shells): - raise ValueError("Shell %s invalide" % shell) + raise ValueError("Shell %r invalide" % shell) self.value = shell class uidNumber(intAttr): diff --git a/lc_ldap.py b/lc_ldap.py index bb7c201..09a2321 100644 --- a/lc_ldap.py +++ b/lc_ldap.py @@ -114,7 +114,7 @@ class lc_ldap(ldap.ldapobject.LDAPObject): ldap.ldapobject.LDAPObject.__init__(self, uri) if user and not re.match('[a-z_][a-z0-9_-]*', user): - raise ValueError('Invalid user name: %s' % user) + raise ValueError('Invalid user name: %r' % user) # Si un username, on récupère le dn associé… if user and not dn: @@ -219,7 +219,7 @@ class lc_ldap(ldap.ldapobject.LDAPObject): assert isinstance(owner, adherent) or isinstance(owner, club) # XXX - Vérifier les droits - else: raise ValueError("Realm inconnu: %s" % realm) + else: raise ValueError("Realm inconnu: %r" % realm) # On récupère la plage des mids if realm == 'fil': From aec6088f00c8b768de1c71a0376c32cb971b6508 Mon Sep 17 00:00:00 2001 From: Vincent Le Gallic Date: Wed, 28 Nov 2012 18:28:26 +0100 Subject: [PATCH 16/27] =?UTF-8?q?La=20base=20est=20en=20ISO,=20donc=20on?= =?UTF-8?q?=20fait=20=C3=A7a=20correctement?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- attributs.py | 2 +- lc_ldap.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/attributs.py b/attributs.py index 95c9696..06b1c36 100644 --- a/attributs.py +++ b/attributs.py @@ -82,7 +82,7 @@ class Attr(object): self.value = val def __str__(self): - return self.__unicode__().encode('utf-8') + return self.__unicode__().encode('ISO8859-1') def __unicode__(self): # XXX - Vérifier que cette méthode produit un objet parsable diff --git a/lc_ldap.py b/lc_ldap.py index 09a2321..c0b50b6 100644 --- a/lc_ldap.py +++ b/lc_ldap.py @@ -66,7 +66,7 @@ def escape(chaine): def ldif_to_uldif(ldif): uldif = {} for attr, vals in ldif.items(): - uldif[attr] = [ unicode(val, 'utf-8') for val in vals ] + uldif[attr] = [ unicode(val, 'ISO8859-1') for val in vals ] return uldif def ldif_to_cldif(ldif, conn, check_ctxt = True): From 9cba9ca0cd25e598f1251779aa040a6ffa84c7b0 Mon Sep 17 00:00:00 2001 From: Vincent Le Gallic Date: Wed, 28 Nov 2012 18:29:10 +0100 Subject: [PATCH 17/27] =?UTF-8?q?Clarification=20de=20l'erreur=20"=CE=BBv.?= =?UTF-8?q?=20str(Attr(v))=20non=20projection"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lc_ldap.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lc_ldap.py b/lc_ldap.py index c0b50b6..09e61c8 100644 --- a/lc_ldap.py +++ b/lc_ldap.py @@ -341,6 +341,7 @@ class CransLdapObject(object): self.attrs = ldif_to_cldif(self.attrs, conn, check_ctxt = False) if mode in ['w', 'rw']: ### Vérification que `λv. str(Attr(v))` est bien une projection + ### C'est-à-dire que si on str(Attr(str(Attr(v)))) on retombe sur str(Attr(v)) oldif = res[0][1] nldif = cldif_to_ldif(self.attrs) @@ -350,7 +351,7 @@ class CransLdapObject(object): if v in vals: vals.remove(v) nvals = [nldif[attr][v.index(v)] for v in vals ] - raise EnvironmentError("λv. str(Attr(v)) n'est peut-être pas une projection:", attr, nvals, vals) + raise EnvironmentError("λv. str(Attr(v)) n'est peut-être pas une projection (ie non idempotente):", attr, nvals, vals) self._modifs = ldif_to_cldif(ldif_to_uldif(res[0][1]), conn, check_ctxt = False) From c6f49d83aaaf9d97ebf1928a7025df4b59b9c38b Mon Sep 17 00:00:00 2001 From: Vincent Le Gallic Date: Thu, 29 Nov 2012 01:43:42 +0100 Subject: [PATCH 18/27] =?UTF-8?q?Fixing=20du=20bug=20introduit=20par=20"""?= =?UTF-8?q?680446be8b8a5aa3452b694e2a136db0e14b0eff=20[attributs,lc=5Fldap?= =?UTF-8?q?]=20On=20va=20chercher=20les=20responsables=20de=20club=20de=20?= =?UTF-8?q?mani=C3=A8re=20paresseuse"""?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit En fait le __unicode__ d'un club cherchait à acceder à self.value.attrs, or self.value était devenu une property donc on ne pouvait plus. Or on ne cherchait à obtenir que l'aid, qui a été déplacé dans self.__resp par le commit ci-dessus. --- attributs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/attributs.py b/attributs.py index 06b1c36..c0064d1 100644 --- a/attributs.py +++ b/attributs.py @@ -459,7 +459,7 @@ class responsable(Attr): self.value = property(self.get_respo) def __unicode__(self): - return self.value.attrs['aid'][0].__unicode__() + return self.__resp class blacklist(Attr): From 1d48d08b18f464992901d1d949b9f32bfec851b1 Mon Sep 17 00:00:00 2001 From: Vincent Le Gallic Date: Thu, 29 Nov 2012 01:50:42 +0100 Subject: [PATCH 19/27] =?UTF-8?q?Ajout=20de=20la=20m=C3=A9thode=20history?= =?UTF-8?q?=5Fadd=20pour=20ajouter=20des=20lignes=20dans=20l'historique?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lc_ldap.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/lc_ldap.py b/lc_ldap.py index 09e61c8..6b133fe 100644 --- a/lc_ldap.py +++ b/lc_ldap.py @@ -48,6 +48,11 @@ uri = 'ldap://ldap.adm.crans.org/' base_dn = 'ou=data,dc=crans,dc=org' log_dn = "cn=log" +# Pour enregistrer dans l'historique, on a besoin de savoir qui exécute le script +# Si le script a été exécuté via un sudo, la variable SUDO_USER (l'utilisateur qui a effectué le sudo) +# est plus pertinente que USER (qui sera root) +current_user = os.getenv("SUDO_USER") or os.getenv("USER") + # Quand on a besoin du fichier de secrets def import_secrets(): if not "/etc/crans/secrets/" in sys.path: @@ -360,6 +365,18 @@ class CransLdapObject(object): return self.ofields + self.xfields + self.ufields + self.mfields fields = property(_get_fields) + def history_add(self, login, chain): + """Ajoute une ligne à l'historique de l'objet. + ###ATTENTION : C'est un kludge pour pouvoir continuer à faire "comme avant", + ### mais on devrait tout recoder pour utiliser l'historique LDAP""" + assert isinstance(login, str) or isinstance(login, unicode) + assert isinstance(chain, unicode) + + new_line = u"%s, %s : %s" % (time.strftime("%d/%m/%Y %H:%M"), login, chain) + # Attention, le __setitem__ est surchargé, mais pas .append sur l'historique + self["historique"] = self["historique"] + [new_line] + + def save(self): """Sauvegarde dans la base les modifications apportées à l'objet. Interne: Vérifie que self._modifs contient des valeurs correctes et From 9846ab8c5ded3a3d143089f684115be2c6319f02 Mon Sep 17 00:00:00 2001 From: Vincent Le Gallic Date: Thu, 29 Nov 2012 01:53:14 +0100 Subject: [PATCH 20/27] =?UTF-8?q?Ajout=20de=20la=20m=C3=A9thode=20update?= =?UTF-8?q?=5Fsolde=20=C3=A0=20la=20classe=20proprio?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lc_ldap.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/lc_ldap.py b/lc_ldap.py index 6b133fe..7d0ea69 100644 --- a/lc_ldap.py +++ b/lc_ldap.py @@ -576,8 +576,22 @@ class proprio(CransLdapObject): return bool_carte return True - - + def update_solde(self, diff, comment=u""): + """Modifie le solde du proprio. diff peut être négatif ou positif.""" + assert isinstance(diff, int) or isinstance(diff, float) + assert isinstance(comment, unicode) + + solde = float(self["solde"][0].value) + new_solde = solde + diff + + # On vérifie qu'on ne dépasse par le découvert autorisé + if new_solde < config.impression.decouvert: + raise ValueError(u"Solde minimal atteint, opération non effectuée.") + + transaction = u"credit" if diff >=0 else u"debit" + new_solde = u"%.2f" % new_solde + self.history_add(current_user, u"%s %.2f Euros [%s]" % (transaction, abs(diff), comment)) + self["solde"] = new_solde def machines(self): """Renvoie la liste des machines""" From 042f6fc7c305f735dc0c0f13d7dc4738c2cf7782 Mon Sep 17 00:00:00 2001 From: Vincent Le Gallic Date: Thu, 29 Nov 2012 03:18:49 +0100 Subject: [PATCH 21/27] =?UTF-8?q?Revert=20"La=20base=20est=20en=20ISO,=20d?= =?UTF-8?q?onc=20on=20fait=20=C3=A7a=20correctement"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit aec6088f00c8b768de1c71a0376c32cb971b6508. --- attributs.py | 2 +- lc_ldap.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/attributs.py b/attributs.py index c0064d1..04026cd 100644 --- a/attributs.py +++ b/attributs.py @@ -82,7 +82,7 @@ class Attr(object): self.value = val def __str__(self): - return self.__unicode__().encode('ISO8859-1') + return self.__unicode__().encode('utf-8') def __unicode__(self): # XXX - Vérifier que cette méthode produit un objet parsable diff --git a/lc_ldap.py b/lc_ldap.py index 7d0ea69..e81f5db 100644 --- a/lc_ldap.py +++ b/lc_ldap.py @@ -71,7 +71,7 @@ def escape(chaine): def ldif_to_uldif(ldif): uldif = {} for attr, vals in ldif.items(): - uldif[attr] = [ unicode(val, 'ISO8859-1') for val in vals ] + uldif[attr] = [ unicode(val, 'utf-8') for val in vals ] return uldif def ldif_to_cldif(ldif, conn, check_ctxt = True): From e26db649444a4d0dd2b2a44277791c0c0b014328 Mon Sep 17 00:00:00 2001 From: Vincent Le Gallic Date: Sat, 1 Dec 2012 05:24:42 +0100 Subject: [PATCH 22/27] =?UTF-8?q?On=20laisse=20la=20possiblit=C3=A9=20de?= =?UTF-8?q?=20fournir=20le=20login=20quand=20on=20rajoute=20une=20ligne=20?= =?UTF-8?q?d'historique.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit C'est utilisé par l'intranet2 actuellement parce que $USER et $SUDO_USER ne contiennent rien, et de toutes façons ce serait sans rapport avec l'user logué. Par contre j'aime pas le fait qu'on doive fournir ce login dès qu'on veut faire un truc. (là il faut le fournir à update_solde) mais je vois pas trop comment faire autrement. --- lc_ldap.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lc_ldap.py b/lc_ldap.py index e81f5db..940c981 100644 --- a/lc_ldap.py +++ b/lc_ldap.py @@ -576,8 +576,9 @@ class proprio(CransLdapObject): return bool_carte return True - def update_solde(self, diff, comment=u""): + def update_solde(self, diff, comment=u"", login=None): """Modifie le solde du proprio. diff peut être négatif ou positif.""" + login = login or current_user assert isinstance(diff, int) or isinstance(diff, float) assert isinstance(comment, unicode) @@ -590,7 +591,7 @@ class proprio(CransLdapObject): transaction = u"credit" if diff >=0 else u"debit" new_solde = u"%.2f" % new_solde - self.history_add(current_user, u"%s %.2f Euros [%s]" % (transaction, abs(diff), comment)) + self.history_add(login, u"%s %.2f Euros [%s]" % (transaction, abs(diff), comment)) self["solde"] = new_solde def machines(self): From 6dc761ef3ba0809bd407afc12e17428712b09f27 Mon Sep 17 00:00:00 2001 From: Olivier Iffrig Date: Tue, 8 Jan 2013 20:56:50 +0100 Subject: [PATCH 23/27] =?UTF-8?q?D=C3=A9but=20de=20doc=20sphinx?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- attributs.py | 2 +- doc/Makefile | 158 ++++++++++++++++++++ doc/README | 3 + doc/source/README.rst | 17 +++ doc/source/conf.py | 243 +++++++++++++++++++++++++++++++ doc/source/index.rst | 20 +++ doc/source/lc_ldap/attributs.rst | 7 + doc/source/lc_ldap/index.rst | 7 + 8 files changed, 456 insertions(+), 1 deletion(-) create mode 100644 doc/Makefile create mode 100644 doc/README create mode 100644 doc/source/README.rst create mode 100644 doc/source/conf.py create mode 100644 doc/source/index.rst create mode 100644 doc/source/lc_ldap/attributs.rst create mode 100644 doc/source/lc_ldap/index.rst diff --git a/attributs.py b/attributs.py index 04026cd..a4f49bc 100644 --- a/attributs.py +++ b/attributs.py @@ -54,13 +54,13 @@ def attrify(val, attr, ldif, conn, ctxt_check = True): return CRANS_ATTRIBUTES.get(attr, Attr)(val, ldif, conn, ctxt_check) class Attr(object): + """La liste des droits qui suffisent à avoir le droit de modifier la valeur""" legend = "Human-readable description of attribute" singlevalue = None optional = None conn = None can_modify = ['Nounou'] - """La liste des droits qui suffisent à avoir le droit de modifier la valeur""" def __init__(self, val, ldif, conn, ctxt_check): """Crée un nouvel objet représentant un attribut. diff --git a/doc/Makefile b/doc/Makefile new file mode 100644 index 0000000..fa18b24 --- /dev/null +++ b/doc/Makefile @@ -0,0 +1,158 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = build + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + -rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/LightweightCrans-LDAP.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/LightweightCrans-LDAP.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/LightweightCrans-LDAP" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/LightweightCrans-LDAP" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +coverage: + $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage + @echo + @echo "Coverage test finished. The results are in $(BUILDDIR)/coverage." diff --git a/doc/README b/doc/README new file mode 100644 index 0000000..1bd398e --- /dev/null +++ b/doc/README @@ -0,0 +1,3 @@ + +Voir le fichier source/README.rst + diff --git a/doc/source/README.rst b/doc/source/README.rst new file mode 100644 index 0000000..d142efd --- /dev/null +++ b/doc/source/README.rst @@ -0,0 +1,17 @@ + +Utilisation de Sphinx +===================== + +La doc est généré à partir des fichiers dans :file:`source/`, et est écrite en +`reStructuredText`_. + +.. _reStructuredText: http://sphinx-doc.org/rest.html + +Les plugins ``autodoc`` et ``coverage`` sont activés, ils permettent +respectivement de récupérer les docstrings depuis le code source, et de voir ce +qui n'est pas documenté (cf. :file:`build/coverage/python.txt`). + +Pour générer la doc, il y a un :file:`Makefile`. Les règles les plus utiles : + * :command:`make html` qui génère la documentation HTML dans :file:`build/html`, + * :command:`make coverage` qui actualise la liste du code non documenté. + diff --git a/doc/source/conf.py b/doc/source/conf.py new file mode 100644 index 0000000..a2d7439 --- /dev/null +++ b/doc/source/conf.py @@ -0,0 +1,243 @@ +# -*- coding: utf-8 -*- +# +# Lightweight Crans-LDAP documentation build configuration file, created by +# sphinx-quickstart on Tue Jan 8 12:43:48 2013. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) +sys.path.insert(0, os.path.abspath('../../')) + +# -- General configuration ----------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.viewcode'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'Lightweight Crans-LDAP' +copyright = u'2013, Olivier Iffrig' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '0.1' +# The full version, including alpha/beta/rc tags. +release = '0.1' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +language = 'fr' + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = [] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'lc_ldap_doc' + + +# -- Options for LaTeX output -------------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'lc_ldap_doc.tex', u'Lightweight Crans-LDAP Documentation', + u'Olivier Iffrig', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output -------------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'lc_ldap', u'Lightweight Crans-LDAP Documentation', + [u'Olivier Iffrig'], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------------ + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'lc_ldap', u'Lightweight Crans-LDAP Documentation', + u'Olivier Iffrig', 'lc_ldap', 'Binding LDAP du Cr@ns', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' diff --git a/doc/source/index.rst b/doc/source/index.rst new file mode 100644 index 0000000..bb002e1 --- /dev/null +++ b/doc/source/index.rst @@ -0,0 +1,20 @@ + +lc_ldap -- Lightweight Crans-LDAP +================================= + +Table des matières : + +.. toctree:: + :maxdepth: 2 + + lc_ldap/index + lc_ldap/attributs + +Index +===== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` +* :doc:`README` + diff --git a/doc/source/lc_ldap/attributs.rst b/doc/source/lc_ldap/attributs.rst new file mode 100644 index 0000000..093d760 --- /dev/null +++ b/doc/source/lc_ldap/attributs.rst @@ -0,0 +1,7 @@ + +lc_ldap.attributs -- Conteneurs pour les attributs LDAP +======================================================= + +.. automodule:: attributs + :members: + diff --git a/doc/source/lc_ldap/index.rst b/doc/source/lc_ldap/index.rst new file mode 100644 index 0000000..b85686d --- /dev/null +++ b/doc/source/lc_ldap/index.rst @@ -0,0 +1,7 @@ + +lc_ldap -- Module principal +=========================== + +.. automodule:: lc_ldap + :members: + From 4f1318bf039a629172eee07353e973279914f478 Mon Sep 17 00:00:00 2001 From: Olivier Iffrig Date: Wed, 9 Jan 2013 09:58:24 +0100 Subject: [PATCH 24/27] =?UTF-8?q?[doc]=20R=C3=A9organisation,=20ajout=20d'?= =?UTF-8?q?une=20page=20pour=20la=20couverture?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/source/coverage.rst | 9 +++++++++ doc/source/index.rst | 3 ++- doc/source/others.rst | 11 +++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 doc/source/coverage.rst create mode 100644 doc/source/others.rst diff --git a/doc/source/coverage.rst b/doc/source/coverage.rst new file mode 100644 index 0000000..d9c8274 --- /dev/null +++ b/doc/source/coverage.rst @@ -0,0 +1,9 @@ + +Objets non documentés +===================== + +Cette page liste les objets non documentés. + +.. include:: ../build/coverage/python.txt + :start-line: 2 + diff --git a/doc/source/index.rst b/doc/source/index.rst index bb002e1..2887011 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -10,11 +10,12 @@ Table des matières : lc_ldap/index lc_ldap/attributs + others + Index ===== * :ref:`genindex` * :ref:`modindex` * :ref:`search` -* :doc:`README` diff --git a/doc/source/others.rst b/doc/source/others.rst new file mode 100644 index 0000000..de1e8ca --- /dev/null +++ b/doc/source/others.rst @@ -0,0 +1,11 @@ + +Annexes +======= + +.. toctree:: + :maxdepth: 2 + + README + coverage +.. var/coverage + From 2d38d87bb5c21b61a2abd6a6d754880c648f7b16 Mon Sep 17 00:00:00 2001 From: Nicolas Dandrimont Date: Wed, 9 Jan 2013 21:41:18 +0100 Subject: [PATCH 25/27] =?UTF-8?q?On=20pr=C3=A9pare=20l'arriv=C3=A9e=20du?= =?UTF-8?q?=20ldif=20dans=20search?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lc_ldap.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lc_ldap.py b/lc_ldap.py index 940c981..2ad870b 100644 --- a/lc_ldap.py +++ b/lc_ldap.py @@ -152,8 +152,11 @@ class lc_ldap(ldap.ldapobject.LDAPObject): def search(self, filterstr='(objectClass=*)', mode='ro', dn= base_dn, scope= 2, sizelimit=400): """La fonction de recherche dans la base ldap qui renvoie un liste de CransLdapObjects. On utilise la feature de sizelimit de python ldap""" - res = self.search_ext_s(dn, scope, filterstr, sizelimit=sizelimit) - return [ new_cransldapobject(self, r[0], mode=mode) for r in res ] + ldap_res = self.search_ext_s(dn, scope, filterstr, sizelimit=sizelimit) + ret = [] + for dn, ldif in ldap_res: + ret.append(new_cransldapobject(self, dn, mode=mode)) + return ret def allMachinesAdherents(self): """Renvoie la liste de toutes les machines et de tous les adherents From 12b8166e82c4439f63ec8c9f2796057893550913 Mon Sep 17 00:00:00 2001 From: Nicolas Dandrimont Date: Wed, 9 Jan 2013 21:45:56 +0100 Subject: [PATCH 26/27] Copyright notices --- attributs.py | 8 +++++--- lc_ldap.py | 10 +++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/attributs.py b/attributs.py index a4f49bc..02aca80 100644 --- a/attributs.py +++ b/attributs.py @@ -3,9 +3,11 @@ # # ATTRIBUTS.PY-- Description des attributs ldap # -# Copyright (C) 2010 Cr@ns -# Author: Antoine Durand-Gasselin -# All rights reserved. +# Copyright (C) 2010-2013 Cr@ns +# Authors: Antoine Durand-Gasselin +# Nicolas Dandrimont +# Valentin Samir +# Vincent Le Gallic # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: diff --git a/lc_ldap.py b/lc_ldap.py index 2ad870b..69b4d36 100644 --- a/lc_ldap.py +++ b/lc_ldap.py @@ -3,9 +3,13 @@ # # LC_LDAP.PY-- LightWeight CransLdap # -# Copyright (C) 2010 Cr@ns -# Author: Antoine Durand-Gasselin -# All rights reserved. +# Copyright (C) 2010-2013 Cr@ns +# Authors: Antoine Durand-Gasselin +# Nicolas Dandrimont +# Olivier Iffrig +# Valentin Samir +# Daniel Stan +# Vincent Le Gallic # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: From a629e4c82f67fc914339f77897ffea65ba003ce5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre-Elliott=20B=C3=A9cue?= Date: Sat, 12 Jan 2013 09:13:42 +0100 Subject: [PATCH 27/27] Ajout de filter.py, qui contient un parseur de filtres. --- filter.py | 244 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 244 insertions(+) create mode 100644 filter.py diff --git a/filter.py b/filter.py new file mode 100644 index 0000000..8b0171f --- /dev/null +++ b/filter.py @@ -0,0 +1,244 @@ +#/usr/bin/env python +# -*- coding: utf8 -*- + +def human_to_ldap(filtre): + """ + Transforme les filtres "human readables" en + filtres respectant la syntaxe LDAP. + """ + # Piles, quand on croise une parenthèse ouvrante + # on change de pile + stacks = {0:''} + + # Position dans la pile + pos = 0 + + # operateur \in {&, |, &|, } + operateur = "" + + # La pile externe, qu'on merge à la pile en cours quand c'est utile. + ext_stack = "" + + # Élement de la forme paiement=2012 ou retour de la pile + # supérieure (quand on croise une parenthèse fermante, la + # pile est dumpée dans argument) + # Est systématiquement dumpé dans ext_stack quand on croise + # un opérateur. + argument = "" + + # Y a-t-il un ! dans la salle ? + neg = False + + # Quand on quitte une pile parenthésée, on veut savoir quel était + # l'opérateur actif avant, pour que le comportement de la fonction + # soit défini. + anciens_operateurs = [] + + for char in filtre: + if char == "(": + + # Une nouvelle stack ne démarre que si le dernier argument a été dumpé + # dans l'ext stack (si ce n'est pas le cas quand on respecte la syntaxe + # des filtres, la fonction est mal codée, donc on plante). + if argument != "": + raise ValueError('Argument entammé non terminé !') + + # Un dumpe ext_stack dans la stack en cours. + stacks[pos] += "%s" % (ext_stack) + + # On augmente le conteur de la stack + pos = pos + 1 + + # On (ré)?initialise la stack + stacks[pos] = '' + + # On stocke l'opérateur en cours dans la stack inférieure dans + # une liste. + anciens_operateurs.append(operateur) + + # On flush operateur + operateur = "" + + # On flush ext_stack, l'environnement est enfin propre pour + # bosser dans la nouvelle pile. + ext_stack = "" + + elif char == ")": + # Cas classique + if operateur == "|": + ext_stack += "(%s)" % argument + + # Moins classique, &| est un opérateur servant à dire qu'on + # a déjà croisé un | dans la stack en cours, le cas échéant, + # celui-ci doit être pris en compte, pour respecter la + # priorité du &. Dans ce cas seulement, une parenthèse + # est ouverte et non fermée (cf elif char == '&' cas + # operateur == "|"), on la ferme ici. + elif operateur == "&|": + argument += ")" + ext_stack += "(%s)" % argument + + # Classique + elif operateur == "&": + ext_stack += "(%s)" % argument + + # Pas d'opérateur, pas de parenthèse superflue. + else: + ext_stack += "%s" % argument + + # On passe la stack en argument, les parenthèses + # l'encadrant seront placées d'un qu'un opérateur + # sera croisé. + argument = "%s%s" % (stacks[pos], ext_stack) + + # Ménage + stacks[pos] = "" + ext_stack = "" + pos = pos - 1 + + # Retour à l'opérateur de la stack précédente. + operateur = anciens_operateurs.pop() + + elif char == "|": + if not argument: + raise ValueError('Aucun argument') + + # Ce cas permet d'éviter une répétition de la forme : + # (|(a)(|(b)(c))), quand on est déjà dans un ou, on + # rajoute juste l'argument suivant sans remettre de + # symbole. + if operateur == "|": + # neg est True si on a croisé un ! dans la chaîne. + # À améliorer pour qu'il ne marche que pour != + if neg: + argument = "!(%s)" % argument + neg = False + + # Ajout à la stack externe de l'argument + ext_stack += "(%s)" % argument + argument = "" + + elif operateur == "&": + if neg: + argument = "!(%s)" % argument + neg = False + + ext_stack += "(%s)" % argument + + # | prend le relais sur &, on dumpe ext_stack et on commence une nouvelle + # chaîne qui sera ajoutée à droite. Le ou va tout à gauche de la stack + # en cours, pour s'appliquer sur tout son contenu. + stacks[pos] = "%s(%s%s)" % (char, stacks[pos], ext_stack) + ext_stack = "" + argument = "" + operateur = "|" + + # C'est un & dans un |, donc on ferme juste la chaîne + # des &, d'où la parenthèse fermante en trop. + elif operateur == "&|": + if neg: + argument = "!(%s)" % argument + neg = False + + ext_stack += "(%s)" % argument + argument = "" + stacks[pos] = "%s%s)" % (stacks[pos], ext_stack) + ext_stack = "" + operateur = "|" + + # Pas encore d'opérateur annoncé + elif operateur == "": + if neg: + argument = "!(%s)" % argument + neg = False + + ext_stack += "%s" % argument + argument = "" + + # ouverture + stacks[pos] = "%s(%s%s)" % (char, stacks[pos], ext_stack) + ext_stack = "" + operateur = "|" + + else: + raise TypeError('Erreur d\'opérateur.') + + elif char == "&": + if not argument: + raise ValueError('Aucun argument') + + if operateur == "&": + if neg: + argument = "!(%s)" % argument + neg = False + + ext_stack += "(%s)" % argument + argument = "" + + # Le cas spécial, on ouvre un & après un |, donc pour respecter + # la priorité de &, on démarre une nouvelle chaîne, mais dans + # l'ext_stack (contrairement à char == '|', operateur == "&") + elif operateur == "|": + if neg: + argument = "!(%s)" % argument + neg = False + + ext_stack += "(%s(%s)" % (char, argument) + argument = "" + operateur = "&|" + + # On était déjà dans un &|... + elif operateur == "&|": + if neg: + argument = "!(%s)" % argument + neg = False + + ext_stack += "(%s)" % argument + argument = "" + + # Comme ci-dessus + elif operateur == "": + if neg: + argument = "!(%s)" % argument + neg = False + + ext_stack += "%s" % argument + argument = "" + + stacks[pos] = "%s(%s%s)" % (char, stacks[pos], ext_stack) + ext_stack = "" + operateur = "&" + + else: + raise TypeError('Erreur d\'opérateur.') + + elif char == "!": + neg = True + + # Remplissage d'argument + else: + argument += char + + # Décommenter pour débug. + # En modifiant un peu, encore plus utile pour savoir ce qu'il + # fait à chaque étape ! + #print stacks + #print pos, argument, ext_stack, operateur + + if pos > 0: + raise Exception("Tu ne sais pas parenthéser, crétin.") + + else: + # Comme parenthèse fermante. + if neg: + argument = "!(%s)" % argument + if operateur == "&|": + argument += ')' + ext_stack += "(%s)" % argument + + argument = "" + stacks[pos] = "(%s%s)" % (stacks[pos], ext_stack) + ext_stack = "" + + # On retourne la pile de plus haut niveau + return stacks[0]