Project

General

Profile

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

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

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

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

    
18

    
19

    
20

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

    
57

    
58

    
59
import re, sys, textwrap
60

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

    
64
####################### Dictionnaire peudo / nom ##########################
65

    
66
def pseudo2nom(pseudo):
67
	noms = {
68
		'liot_'			: 'Lionel Allorge',
69
		'liot'			: 'Lionel Allorge',
70
		'Armony'		: 'Armony Altinier',
71
		'kult' 			: 'Tony Bassette',
72
		'raceme'		: 'Christophe Boyanique',
73
		'PetiPandaRou'		: 'Julia Buchner',
74
		'PetiPandaRou1'		: 'Julia Buchner',
75
		'echarp'		: 'Emmanuel Charpentier',
76
		'aurelia'		: 'Aurélia Gilardi',
77
		'Luk_'		: 'Luc Fievet',
78
		'Remaille'		: 'Rémi Boulle',
79
		'teymour'		: 'Tangui Morlier',
80
		'coin_p'		: 'Marc Chauvet',
81
		'coin_pan'		: 'Marc Chauvet',
82
		'coin_pan_'		: 'Marc Chauvet',
83
		'lcosty'		: 'Laurent Costy',
84
		'Flache-Gore-Donn'		: 'Laurent Costy',
85
                'theocrite'             : 'theocrite',
86
		'cnestel'		: 'Charlie Nestel',
87
		'madix'			: 'Frédéric Couchet',
88
		'madix`'		: 'Frédéric Couchet',
89
		'dachary'		: 'Loïc Dachary',
90
		'mmu_man'		: 'François Revol',
91
		'benj'			: 'Benjamin Drieu',
92
		'bookynette'		: 'Magali Garnero',
93
		'Bookynette'		: 'Magali Garnero',
94
		'BookyPaNette'		: 'Magali Garnero',
95
		'Siltaar'		: 'Simon Descarpentries',
96
		'ave'			: 'Eva Mathieu',
97
		'ave1'			: 'Eva Mathieu',
98
		'_PoluX_'		: 'François Poulain',
99
		'_PoLuX_'		: 'François Poulain',
100
		'gibus'			: 'Gérald Sédrati-Dinet',
101
		'gibus_at_office'	: 'Gérald Sédrati-Dinet',
102
		'leobaillard'	: 'Léopold Baillard',
103
		'janchou'		: 'Jeanne Tadeusz',
104
	}
105
	if pseudo in set(noms): return noms[pseudo] + ' <' + pseudo + '>'
106
	else: return '<' + pseudo + '>'
107

    
108
###########################################################################
109

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

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

    
115
# Nettoyage syntaxe + horodatage + notification
116
aSupprimer = re.compile(
117
	r'\*\*\*.*?(#april|has quit.*?|is now known as.*?)$'
118
	r'|'
119
	r' {2,}'
120
	r'|'
121
	r'\[\d\d:\d\d\]'
122
	r'|'
123
	r'\[\d\d/\d\d/\d\d \d\d:\d\d\]'
124
	r'|'
125
	r'^\s?\*\s?'
126
	,re.MULTILINE|re.DOTALL)
127
log = aSupprimer.sub('',log_brut)
128

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

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

    
135
log = pseudo.split(log)
136

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

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

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

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

    
153
# Drapeaux pour le traitement individuel/collectif
154
individuel = False
155

    
156
# Instantiation des revues
157
revueIndividuelle = {}
158
revueCollective = {}
159

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

    
163
# Nombre de colonnes pour l'affichage
164
text_width = 72
165

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

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

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

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

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

    
187
	# Sinon il s'agit d'une entrée à stocker
188
	else:
189
		# Traitement individuel ?
190
		if individuel:
191
			# Les nouveaux intervenants sont instanciés
192
			if parleur not in set(revueIndividuelle):
193
				revueIndividuelle[parleur] = {}
194
			
195
			# Les nouveaux états sont instanciés
196
			if directiveCourante not in set(revueIndividuelle[parleur]):
197
				revueIndividuelle[parleur][directiveCourante] = []
198

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

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

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

    
211
##############################  Affichage  ##############################
212

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

    
216
# Procedures d'affichage
217

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

    
221
def title(texte):
222
	separateur()
223
	print texte.center(text_width)
224
	print ''.center(text_width, '=')
225
	print ''
226

    
227
def subTitle(texte):
228
	separateur()
229
	print ''
230
	print (' ' + texte + ' ').center(text_width,'-')
231

    
232
def subSubTitle(texte):
233
	print '\n=== ', texte, ' ==='
234
	print ''
235

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

    
248
# Affichage des puces
249

    
250
wrapper = textwrap.TextWrapper()
251
wrapper.initial_indent = "* "
252
wrapper.subsequent_indent = "  "
253
wrapper.width = text_width
254

    
255
def printPuce(texte):
256
	print wrapper.fill(texte)
257

    
258
title('  Revue de la semaine en cours ')
259

    
260
# Liste des participants
261
subTitle('Participants')
262
people = ''
263
participants = sorted(revueIndividuelle.keys(), reverse = True)
264
if len(participants) > 0:
265
	while len(participants) > 1: 
266
		people += '* ' + pseudo2nom(participants.pop()) + ',\n'
267
	people += '* ' + pseudo2nom(participants.pop()) + '.'
268
	print people
269

    
270
# Revue individuelle
271
for participant in revueIndividuelle:
272
	subTitle(pseudo2nom(participant))
273
	for directive in sorted(revueIndividuelle[participant]):
274
		subSubTitle(re.sub(enumeration, '', directive))
275
		for puce in revueIndividuelle[participant][directive]:
276
			printPuce(puce)
277

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

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

    
290
title('Log IRC brut')
291

    
292
print log_brut
(16-16/582)