2 # -*- encoding: utf-8 -*-
4 """ Gestion des quotes """
14 quote_matcher
= re
.compile(config
.quote_regexp
, flags
=re
.UNICODE
)
15 quote_matcher_with_timestamp
= re
.compile(config
.quote_regexp_with_timestamp
, flags
=re
.UNICODE
)
16 spaces_matcher
= re
.compile(u
"\s", flags
=re
.U
)
18 def equivalence_partition(iterable
, relation
):
19 """ Partitionne l'itérable en classes d'équivalences. """
22 # find the class it is in
25 if relation( iter(c
).next(), o
): # is it equivalent to this class?
29 if not found
: # it is in a new class
30 classes
.append( set( [ o
] ) )
34 """ Renvoie la date actuelle """
35 return datetime
.datetime(*time
.localtime()[:6])
37 def sanitize_author(raw
):
38 """Proprifie l'auteur : enlève les espaces insécables."""
39 return spaces_matcher
.sub(u
" ", raw
)
43 def __init__(self
, author
, content
, timestamp
=None, place
=None, quoter
=None):
46 elif isinstance(timestamp
, basestring
):
47 timestamp
= datetime
.datetime(*time
.strptime(timestamp
, u
"%Y-%m-%d_%H:%M:%S")[:6])
48 self
.author
= sanitize_author(author
)
49 self
.content
= content
50 self
.timestamp
= timestamp
55 d
= {"author" : self
.author
, "content" : self
.content
,
56 "timestamp" : self
.timestamp
.strftime(u
"%F_%T"),
57 "place" : self
.proper_place
, "quoter" : self
.proper_quoter
}
60 def __get_proper_place(self
):
62 property function pour récupérer ``self.place``
63 mais en virant None ou des chaînes ne contenant que des whitespace.
65 return self
.place
if self
.place
and self
.place
.strip() != u
"" else u
""
66 proper_place
= property(__get_proper_place
)
68 def __get_proper_quoter(self
):
70 property function pour récupérer ``self.place``
71 mais en virant None ou des chaînes ne contenant que des whitespace.
73 return self
.quoter
if self
.quoter
and self
.quoter
.strip() != u
"" else u
""
74 proper_quoter
= property(__get_proper_quoter
)
76 def __unicode__(self
):
77 """ Retourne la quote affichable """
78 return config
.quote_template
% self
.__dict
__
80 return unicode(self
).encode("utf-8")
82 def display(self
, show_context
=False):
84 Retourne une chaîne contenant toujours la quote et l'auteur,
85 et le contexte ssi ``show_context = True``.
87 s
= config
.quote_template
% self
.__dict
__
89 s
= u
"%s | %s" % (s
, self
.proper_place
)
90 return s
.encode("utf-8")
93 """ Retourne une chaîne représentant la totalité des infos de la quote,
94 tout en étant parsable et human-readable. """
95 s
= u
"%s %s | %s | %s" % (
96 self
.timestamp
.strftime("%F_%T"),
97 config
.quote_template
% self
.__dict
__,
100 return s
.encode("utf-8")
102 def __eq__(self
, otherquote
):
103 """ Vérifie si cette phrase n'a pas déjà été dite par la même personne.
104 Indépendamment de la date et de la casse. """
105 return [self
.author
.lower(), self
.content
.lower()] == [otherquote
.author
.lower(), otherquote
.content
.lower()]
108 def parse(text
, date
=None):
109 """ Parse le ``text`` et renvoie une quote ou None. """
112 get
= quote_matcher
.match(text
)
115 return Quote(d
["author"], d
["content"], date
)
117 def load_file(filename
):
118 """ Récupère les quotes depuis le fichier """
119 with
open(filename
) as f
:
120 jsonquotes
= json
.load(f
)
121 quotes
= [Quote(**q
) for q
in jsonquotes
]
124 def save_file(quotes
, filename
):
125 """ Enregistre les quotes dans le fichier """
126 with
open(filename
, "w") as f
:
127 raws
= [q
.jsonize() for q
in quotes
]
130 class QuoteDB(object):
131 """ Stocke et distribue des quotes. """
136 """ Charge le fichier de quotes dans la DB """
137 self
.quotelist
= load_file(config
.quote_file
)
140 """ Sauvegarde la DB dans le fichier de quotes """
141 save_file(self
.quotelist
, config
.quote_file
)
143 def _collapse_author(self
, author
):
144 """ Renvoie ``author`` avec la casse déjà utilisée si il a déjà été quoté
145 sinon, le renvoie sans le modifier. """
146 authors
= list(set([q
.author
for q
in self
.quotelist
if q
.author
.lower() == author
.lower()]))
148 print "Warning : authors %s" % authors
154 def get_clash_authors(self
):
155 """ Renvoie une liste de liste d'auteurs qui sont enregistrés avec des casses différentes. """
156 authors
= list(set([q
.author
for q
in self
.quotelist
]))
157 authors
= equivalence_partition(authors
, lambda x
,y
: x
.lower() == y
.lower())
158 authors
= [list(c
) for c
in authors
if len(c
) > 1]
161 def store(self
, timestamp
=None, **kwargs
):
162 """ Enregistre une nouvelle quote, sauf si elle existe déjà.
163 Force l'auteur à utiliser la même casse si un auteur de casse différente existait déjà.
164 Renvoie ``True`` si elle a été ajoutée, ``False`` si elle existait. """
165 kwargs
["author"] = self
._collapse
_author
(kwargs
["author"])
166 kwargs
["timestamp"] = timestamp
167 newquote
= Quote(**kwargs
)
168 if not newquote
in self
.quotelist
:
169 self
.quotelist
.append(newquote
)
174 return repr(self
.quotelist
)
177 """ Sort une quote aléatoire """
178 return random
.choice(self
.quotelist
)
180 def quotesfrom(self
, author
):
181 """ Sort toutes les quotes de ``author`` """
182 return [q
for q
in self
.quotelist
if q
.author
== author
]
183 def randomfrom(self
, author
):
184 """ Sort une quote aléatoire de ``author`` """
185 return random
.choice(self
.quotesfrom(author
))
187 def search(self
, inquote
=None, author
=None, regexp
=False):
189 Fait une recherche dans les quotes.
190 C'est une conjonction de cas : on garde la quote si
191 ``inquote`` matche dans le contenu
192 *et* si ``author`` matche l'auteur
194 Si ``regexp=True``, utilise directement les termes comme des regexp.
196 params
= [inquote
, author
]
202 regexps
.append(re
.compile(param
, flags
=re
.UNICODE
+ re
.IGNORECASE
))
203 l
= [q
for q
in self
.quotelist
if all([reg
.match(truc
) for (reg
, truc
) in zip(regexps
, [q
.content
, q
.author
, q
.proper_place
])])]
205 for (i
, param
) in enumerate(params
):
208 l
= [q
for q
in self
.quotelist
if all([param
.lower() in truc
.lower() for (param
, truc
) in zip(params
, [q
.content
, q
.author
, q
.proper_place
])])]
211 def search_authors(self
, author
=None, regexp
=False):
212 """Renvoie la liste des auteurs contenant ``author`` ou qui matchent la regexp."""
216 areg
= re
.compile(author
, flags
=re
.UNICODE
+ re
.IGNORECASE
)
217 l
= list(set([q
.author
for q
in self
.quotelist
if areg
.match(q
.author
)]))
221 l
= list(set([q
.author
for q
in self
.quotelist
if author
.lower() in q
.author
.lower()]))
224 def dump(quotedb
, dump_file
=None):
225 """Pour exporter les quotes dans un format readable vers un fichier."""
226 if dump_file
is None:
227 dump_file
= config
.quote_dump_file
228 t
= "\n".join([q
.full_str() for q
in quotedb
.quotelist
]) + "\n"
229 with
open(dump_file
, "w") as f
:
232 def restore(dump_file
=None):
233 """Crée un DB de quotes en parsant le contenu d'un fichier de dump."""
234 if dump_file
is None:
235 dump_file
= config
.quote_dump_file
236 with
open(dump_file
) as f
:
238 t
= t
.decode("utf-8") # Oui, ça peut fail, mais on ne doit alors pas continuer
239 l
= [m
.groupdict() for m
in quote_matcher_with_timestamp
.finditer(t
)]
240 # On instancie les quotes grâce aux dicos qui ont déjà la bonne tronche
241 l
= [Quote(**q
) for q
in l
]
242 newquotedb
= QuoteDB()
243 newquotedb
.quotelist
= l