Projet

Général

Profil

Tâche récurrente #135 » revue.py

Ce script traite les logs IRC des revues hebdo ; pour faciliter la digestion. - François Poulain, 13/08/2010 17:44

 
#!/usr/bin/python
# -*- coding: utf-8 -*-

# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
# Version 2, December 2004
#
# Copyright (C) 2010 François Poulain <fpoulain@metrodore.fr>
#
# Everyone is permitted to copy and distribute verbatim or modified
# copies of this license document, and changing it is allowed as long
# as the name is changed.
#
# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENCE
# TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
#
# 0. You just DO WHAT THE FUCK YOU WANT TO.




# Ce script traite les logs IRC des revues hebdo ; pour faciliter la
# digestion. Voir https://redmine.april.org/issues/135
#
# Le programme est conçu pour parser les logs de madix selon leur format
# particulier. Il prends le log sur l'entrée standard et sort sur la sortie
# standard. Si tu veux mettre un chemin de fichier en entrée, ça se passe au
# début du code, il y a un exemple commenté.
#
# Il y a trois modalités de contrôle en ligne :
# * commentaire : si quelqu'un veut écrire un truc qui ne soit pas
# passé en revue, il commence par un %.
# Exemple : <madix> % quand vous avez fini vous le dites
# <liot_> % fini
#
# * directive de revue individuelle : avec un #, tu annonces un
# changement d'état dans la revue. Les messages qui suivent
# sont traités/triés individuellement. Les états sont créés
# individuellement lorsqu'il y a un contenu associé à stocker.
# Exemple : <madix> # 2/ Action passées
#
# * directive de revue globale : avec un ##, tu annonces un
# changement d'état dans la revue. Les messages qui suivent
# sont traités/triés collectivement. Les états sont créés
# uniquement lorsqu'il y a un contenu associé à stocker.
# Exemple : <madix> ## 3/ Point bloquants existants ou levés
# récemment
#
# Les directives ne sont interprétées que pour le conducteur de la
# réunion, défini dans le code. Pour des raisons de fainéantise, elles
# sont triées alphanumériquement ; donc il vaut mieux les numéroter.
#
# L'affichage est factorisé dans 3 méthodes title(), subTitle() et
# subSubTitle() ; pour modifier facilement aux gouts de chacun.
#
# Le code intègre un dictionnaire pseudo -> nom.



import re, sys, textwrap

######### Conducteur de la revue : celui qui envoit les directives ########
conducteur = 'madix'

####################### Dictionnaire peudo / nom ##########################

def pseudo2nom(pseudo):
noms = {
'liot_' : 'Lionel Allorge',
'liot' : 'Lionel Allorge',
'Armony' : 'Armony Altinier',
'kult' : 'Tony Bassette',
'raceme' : 'Christophe Boyanique',
'PetiPandaRou' : 'Julia Buchner',
'PetiPandaRou1' : 'Julia Buchner',
'echarp' : 'Emmanuel Charpentier',
'aurelia' : 'Aurélia Gilardi',
'Luk_' : 'Luc Fievet',
'Remaille' : 'Rémi Boulle',
'teymour' : 'Tangui Morlier',
'coin_p' : 'Marc Chauvet',
'coin_pan' : 'Marc Chauvet',
'coin_pan_' : 'Marc Chauvet',
'lcosty' : 'Laurent Costy',
'Flache-Gore-Donn' : 'Laurent Costy',
'theocrite' : 'theocrite',
'cnestel' : 'Charlie Nestel',
'madix' : 'Frédéric Couchet',
'madix`' : 'Frédéric Couchet',
'dachary' : 'Loïc Dachary',
'mmu_man' : 'François Revol',
'benj' : 'Benjamin Drieu',
'bookynette' : 'Magali Garnero',
'Bookynette' : 'Magali Garnero',
'BookyPaNette' : 'Magali Garnero',
'Siltaar' : 'Simon Descarpentries',
'ave' : 'Eva Mathieu',
'ave1' : 'Eva Mathieu',
'_PoluX_' : 'François Poulain',
'_PoLuX_' : 'François Poulain',
'gibus' : 'Gérald Sédrati-Dinet',
'gibus_at_office' : 'Gérald Sédrati-Dinet',
'leobaillard' : 'Léopold Baillard',
'janchou' : 'Jeanne Tadeusz',
}
if pseudo in set(noms): return noms[pseudo] + ' <' + pseudo + '>'
else: return '<' + pseudo + '>'

###########################################################################

# log_brut = open('/tmp/20100806-log-irc-revueIndividuelle-hebdomadaire.txt').read()
log_brut = sys.stdin.read()

################## Filtrage du log, sortie dans une liste #################

# Nettoyage syntaxe + horodatage + notification
aSupprimer = re.compile(
r'\*\*\*.*?(#april|has quit.*?|is now known as.*?)$'
r'|'
r' {2,}'
r'|'
r'\[\d\d:\d\d\]'
r'|'
r'\[\d\d/\d\d/\d\d \d\d:\d\d\]'
r'|'
r'^\s?\*\s?'
,re.MULTILINE|re.DOTALL)
log = aSupprimer.sub('',log_brut)

aSubstituer = re.compile(r'\n+|\s+')
log = aSubstituer.sub(' ',log)

# Concatenation/découpe des pseudo/propos
pseudo = re.compile(r'(<[^ >]*?>)')

log = pseudo.split(log)

# Supression des éventuelles puces et espaces de début et fin de ligne
puce = re.compile(r'^\s?\*?\s?|\s?$')
for phrase in log:
log[log.index(phrase)] = re.sub(puce, '', phrase)

# Retrait des éventuelles lignes vides
while log.count(''): log.remove('')

################ Parsage du log, stockage en dictionnaire #################

# Regexps pour la reconnaissance des motifs
pseudo = re.compile(r'<([^ >]*?)>')
directive = re.compile(r'^\s?\#\#?\s?')
directiveIndividuelle = re.compile(r'^\s?\#[^#]\s?')
commentaire = re.compile(r'^\s?\%\s?')

# Drapeaux pour le traitement individuel/collectif
individuel = False

# Instantiation des revues
revueIndividuelle = {}
revueCollective = {}

directiveCourante = 'Indéfini'
parleur = 'John Doe' # normalement inutile

# Nombre de colonnes pour l'affichage
text_width = 72

print '\n', ' Commentaires (pour vérification) '.center(text_width, '='), '\n'

for line in log:
# Si on a affaire à un pseudo
if re.match(pseudo,line):
parleur = re.match(pseudo,line).group(1)

# Si on a affaire à une directive proposée par le dictateur
elif re.match(directive, line) and parleur == conducteur:
# Collective ?
individuel = True
if not re.match(directiveIndividuelle, line):
individuel = False

# Individuelle ?
directiveCourante = re.sub(directive, '', line)

# Si on a affaire à un commentaire
elif re.match(commentaire, line):
print parleur, ' : ', line # Affiché pour contrôle visuel

# Sinon il s'agit d'une entrée à stocker
else:
# Traitement individuel ?
if individuel:
# Les nouveaux intervenants sont instanciés
if parleur not in set(revueIndividuelle):
revueIndividuelle[parleur] = {}
# Les nouveaux états sont instanciés
if directiveCourante not in set(revueIndividuelle[parleur]):
revueIndividuelle[parleur][directiveCourante] = []

# Stockage de la ligne dans la revueIndividuelle
revueIndividuelle[parleur][directiveCourante].append(line)

# Sinon : traitement collectif
else:
# Les nouveaux états sont instanciés
if directiveCourante not in set(revueCollective):
revueCollective[directiveCourante] = []

# Stockage de la ligne dans la revueCollective
revueCollective[directiveCourante].append(parleur + ' : ' + line)

############################## Affichage ##############################

# Regexp pour les énumérations
enumeration = re.compile(r'^\s?\d?\/?\s?')

# Procedures d'affichage

def separateur():
print '\n', ''.center(text_width, '=')

def title(texte):
separateur()
print texte.center(text_width)
print ''.center(text_width, '=')
print ''

def subTitle(texte):
separateur()
print ''
print (' ' + texte + ' ').center(text_width,'-')

def subSubTitle(texte):
print '\n=== ', texte, ' ==='
print ''

# Si le conducteur est mal déclaré
if directiveCourante == 'Indéfini':
separateur()
print ''
print textwrap.fill('La réunion n\'a visiblement pas été conduite. Cela peut venir '+
'd\'un problème de formatage de log IRC, ou bien simplement parce '+
'que le conducteur déclaré dans le code est erroné. Êtes vous sur '+
'que le conducteur de la réunion est ' + conducteur + ' ? Vous '+
'pouvez changer ça dans le code revue.py, en éditant la ligne 62.', text_width)
separateur()
exit()

# Affichage des puces

wrapper = textwrap.TextWrapper()
wrapper.initial_indent = "* "
wrapper.subsequent_indent = " "
wrapper.width = text_width

def printPuce(texte):
print wrapper.fill(texte)

title(' Revue de la semaine en cours ')

# Liste des participants
subTitle('Participants')
people = ''
participants = sorted(revueIndividuelle.keys(), reverse = True)
if len(participants) > 0:
while len(participants) > 1:
people += '* ' + pseudo2nom(participants.pop()) + ',\n'
people += '* ' + pseudo2nom(participants.pop()) + '.'
print people

# Revue individuelle
for participant in revueIndividuelle:
subTitle(pseudo2nom(participant))
for directive in sorted(revueIndividuelle[participant]):
subSubTitle(re.sub(enumeration, '', directive))
for puce in revueIndividuelle[participant][directive]:
printPuce(puce)

# Revue collective : passe les états sauf celui indéfini
for directive in sorted(set(revueCollective) - set(['Indéfini'])):
title(re.sub(enumeration, '', directive))
for puce in revueCollective[directive]:
printPuce(puce)

# Passe l'état indéfini
if 'Indéfini' in set(revueCollective):
print '\n', ' Indéfini '.center(text_width, '='), '\n'
for puce in set(revueCollective['Indéfini']):
printPuce(puce)

title('Log IRC brut')

print log_brut
(16-16/732)