Python Facile

Source : folders.py

 
#!/usr/bin/env python
"""
Nom: folder.py
Description: Tri par ordre alphabétique la liste des 'Folders' dans Xnews.

Auteur: Lionel Grolleau <lionel.grolleau @ free.fr>
Date: 28/09/02
Version: 0.2.0
License: GPL
Test: python 2.2.1 sous Windows 98
Site: http://lionel.grolleau.free.fr


Organisation:
Le fichier 'folders.ini' contient la liste des 'folders'.

Chaque 'folder' est associé à une 'mailbox' (fichiers '.mbx' et '.hdr') stockée dans
le répertoire 'folders'. Chaque message dans la mailbox est séparé par une ligne
commençant par 'From ' (avec un espace).

Structure du fichier 'folders.ini':

[Folders]     => entete
count=n       => nombre de 'folder'
1=folder_1    => liste des n 'folder' précédé
2=folder_2    => d'un numéro d'ordre
n=folder_n

Variables:
CFILE = Fichier de configuration.
RUN = booléan: fonctionnement normal ou test.
XNEWS = Répertoire d'installation d'Xnews.
FINI = 'folders.ini' nom du fichier à trier.
FBAK = 'folders.bak' nom du fichier de sauvegarde.
FMBX = repertoire contenant les 'mailbox'.
LIST = Action : Lister.
SORT = Action : Trier.
PACK = Action : Compacter.
MDEL = Liste des 'mailbox' à supprimer.

Code de sortie:
0: Fichier 'folders.ini' mis à jour.
1: Erreur => Mauvaise options dans la ligne de commande.

Usage:
    -l, --list: Affiche le fichier 'folders.ini' et le nombre de messages de chaque 'mailbox'.
    -s, --sort: Trie le fichier 'folders.ini'.
    -p, --pack: Supprime les 'folders' ayant aucun message.
    -f, --file='repertoire d'XNEWS'.
    -h, --help : affiche cette aide.
    -S, --dry-run: test (n'applique pas les modifications sur les fichiers).
    -B, --no-backup: ne fait pas de sauvegarde du fichier 'folders.ini'. (todo)
    -C, --no-conf: ne tiens pas compte du fichier de configuration. (todo)
    -c, --conf='fichier de configuration'. (todo)
"""

import os

__all__ = ["ErreurFichierInexistant", "ErreurFichierNonComforme",
            "ErreurMajImpossible", "Folders"]

# classes 'exception'
class __Erreur(Exception):
    def __init__(self, msg=''):
        self._msg = msg
        Exception.__init__(self, msg)
    def __repr__(self):
        return self._msg
    __str__ = __repr__

class ErreurFichierInexistant(__Erreur):
    def __init__(self, file):
        msg = "Le fichier %s est introuvable.\n"
        msg += "Utiliser l\'option -f (--file) de la ligne de commande."
        __Erreur.__init__(self, msg % file)
        self.file = file

class ErreurFichierNonComforme(__Erreur):
    def __init__(self, file):
        msg = "Le fichier %s n'est pas un fichier conforme."
        __Erreur.__init__(self, msg % file)
        self.file = file
        
class ErreurMajImpossible(__Erreur):
    def __init__(self, file):
        msg = "Impossible de faire la mise à jour de %s."
        __Erreur.__init__(self, msg % file)
        self.file = file

# Classe principale
class Folders:
    def __init__(self, *args):
        """ Interprete la ligne de commande, gére le fichier de configuration et 
            initialise les variables de la classes.
        """
        import PrintIso
        import getopt
        import re
        import sys
        import ConfigParser

        self.DATA = []
        self.MDEL = []
        self.__RECH = re.compile('^From ', re.MULTILINE)

        # options de lancement
        self.RUN = "run"
        self.XNEWS = None
        self.CSAVE = None       # Enregistre la configuration.
        self.LIST = None        # Action : Lister.
        self.SORT = None        # Action : Trier.
        self.PACK = None        # Action : Compacter.
        if args:
            argv = args
        else:
            argv = sys.argv[1:]
        try:
            opts, args = getopt.getopt(argv, "lsphf:S", 
                        ["list", "sort", "pack", "help", "file=", "dry-run"])
        except getopt.GetoptError:
            # affiche l'aide et quitte:
            self.usage()
            sys.exit(1)
        for o, a in opts:
            if o in ('-h', '--help'):
                self.usage()
                sys.exit()
            if o in ('-S', '--dry-run'):
                self.RUN = None
            if o in ('-f', '--file'):
                self.XNEWS = a
                self.CSAVE = 1
            if o in ('-l', '--list'):
                self.LIST = 'list'
            if o in ('-s', '--sort'):
                self.SORT = 'sort'
            if o in ('-p', '--pack'):
                self.PACK = 'pack'
        self.__p=PrintIso.PrintIso()

        # fichier de configuration
        self.CFILE = None
        self.CPARS = None
        if os.name == 'nt':
            #repertoire "Mes Documents"
            from win32com.shell import shell,shellcon
            DOCS = str(shell.SHGetSpecialFolderPath(0, shellcon.CSIDL_PERSONAL))
            if os.path.exists(DOCS):
                try:
                    os.mkdir(DOCS + "/python")
                except OSError:
                    pass
                self.CFILE = DOCS + "/python/folders.conf"
            else:
                print "Attention: Répertoire '%s' inexistant." % (DOCS)
                print "           Impossible de lire/d'écrire le fichier de configuration."
        # else: (unix/linux) self.CFILE = '~/python/folders.conf'
        self.CPARS = ConfigParser.ConfigParser()

        if not self.XNEWS:
            if self.CFILE:
                self.CPARS.read(self.CFILE)
                if self.CPARS.has_section('CONFIG'):
                    self.XNEWS = self.CPARS.get('CONFIG', 'file')
        if not self.XNEWS:
            raise ErreurFichierInexistant('folders.ini')
        self.update(self.XNEWS)

    def update(self, xnewsDir = None):
        """ Mets à jour les variables en fonction du chemin d'Xnews."""
        self.FINI = self.XNEWS + "/folders.ini"
        self.FBAK = self.XNEWS + "/folders.bak"
        self.FMBX = self.XNEWS + "/folders"

    def __del__(self):
        del self.__p

    def usage(self):
        """ Usage:
            -l, --list : affiche le fichier 'folders.ini' et le nombre de messages."
            -s, --sort : trie le fichier 'folders.ini'."
            -p, --pack : supprime les folders ayant aucun message."
            -f, --file='repertoire d'XNEWS'.
            -h, --help : affiche cette aide.
            -S, --dry-run: n'applique pas les modifications sur les fichiers.
        """
        print "folders.py"
        print "Usage:"
        print "    -l, --list : affiche le fichier 'folders.ini' et le nombre de messages."
        print "    -s, --sort : trie le fichier 'folders.ini'."
        print "    -p, --pack : supprime les folders ayant aucun message."
        print "    -f, --file='repertoire d'XNEWS'."
        print "    -h, --help : affiche cette aide."
        print "    -S, --dry-run: n'applique pas les modifications sur les fichiers."

    def lire(self):
        """ Lit et parse le fichier de configuration.
        """
        try:
            r=open(self.FINI,"r")
            self.DATA = filter(None, map(lambda x: x.strip(), r.readlines()))
            r.close()
        except IOError:
            raise ErreurFichierInexistant(self.FINI)
        if self.DATA[0] <> '[Folders]':
            raise ErreurFichierNonComforme(self.FINI)
        if not filter(lambda x: x.startswith('count'), self.DATA[1:]):
            raise ErreurFichierNonComforme(self.FINI)
        self.DATA = filter(lambda x: not x.startswith('count'), self.DATA[1:])
        try:
            self.DATA = map(lambda x: x.split("=")[1].strip(),self.DATA)
        except IndexError:
            raise ErreurFichierNonComforme(self.FINI)

    def backup(self):
        """ Copie le fichier 'Folders.ini' vers 'Folders.bak'
        """
        if self.RUN:
            if os.path.exists(self.FBAK):
                try:
                    os.remove(self.FBAK)
                except OSError:
                    print "Attention: Impossible d'effacer %s" % self.FBAK
        if self.RUN:
            try:
                os.rename(self.FINI, self.FBAK)
            except OSError:
                print "Attention: Impossible de sauvegarder %s" % self.FINI

    def ecrire(self):
        """ Ecrit les fichiers 'Folders.ini' et 'Folders.conf'.
        """
        if self.RUN:
            # Effacement des 'mailbox' des 'folders' supprimés.
            if self.MDEL:
                for mailbox in self.MDEL:
                    os.remove(self.__mailboxFullName(mailbox, 'mbx'))
                    os.remove(self.__mailboxFullName(mailbox, 'hdr'))
            # mise à jour des DATAs.
            c = len(self.DATA)
            for x in range(c):
                self.DATA[x] = ('% 2s = %s' % (x+1, self.DATA[x]))
            self.DATA.insert(0, 'count = %s' % (c))
            self.DATA.insert(0, '[Folders]')
            # Sauvegarde du fichier 'folders.ini'.
            try:
                f = open(self.FINI,'w')
                f.write("\n".join(self.DATA))
                f.close()
            except IOError:
                raise ErreurMajImpossible(self.FINI)
            # Sauvegarde du fichier de configuration.
            try:
                if self.CFILE and self.CSAVE:
                    if not self.CPARS.has_section('CONFIG'):
                        self.CPARS.add_section('CONFIG')
                    self.CPARS.set('CONFIG', 'file', self.XNEWS)
                    self.CPARS.write(open(self.CFILE,'w'))
            except IOError:
                raise ErreurMajImpossible(self.CPARS)
    def list(self):
        """ Affiche la liste et le nombre de messages des 'folders' """
        return map(self.__folderDisplay, self.DATA)
    def sort(self):
        """ Tri la liste des 'folders'."""
        self.DATA.sort()
    def pack(self):
        """ Compacte les 'folders'."""
        for folder in self.DATA:
            if self.__mailboxMessages(folder) == 0:
                print "Supprimer %s." % (folder)
                self.MDEL.append(folder)
                self.DATA.remove(folder)
    def __folderDisplay(self, folder):
        """ Affiche le nombre de messages suivit du nom du 'folder'."""
        return "%3d messages dans %s." % (self.__mailboxMessages(folder), folder)
    def __mailboxFullName(self, basename, ext='mbx'):
        """ Retourne le nom complet d'un fichier 'folder'."""
        return self.FMBX + '/' + basename + '.' + ext
    def __mailboxMessages(self, mailbox):
        """ Retourne le nombre de messages du fichier 'mailbox.mbx'."""
        try:
            f = open(self.__mailboxFullName(mailbox, 'mbx'))
            l = f.read()
        except: # voir gestion des erreurs
            pass
        m = self.__RECH.findall(l)
        return len(m)

    def folders(self):
        """ Procédure principale.
        """
        self.lire()
        if self.SORT:
            self.sort()
            print "Le fichier '%s' a été trier." % self.FINI
        if self.PACK:
            self.pack()
            print "Le fichier '%s' a été compacter." % self.FINI
        if self.LIST:
            for data in self.list():
                print data
        self.backup()
        self.ecrire()

def main():
    """ Exemple d'utilisation.
        f = folders('-h') affiche l'aide.
        f = folders('--dry-run') test, execution 'à blanc'.
        f = folders('--file=C:/Xnews/') execution avec définition du répertoire d'Xnews.
    """
    f = Folders()
    f.folders()

if __name__ == '__main__':
    main()