|
#!/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
|