import config
+quote_matcher = re.compile(config.quote_regexp, flags=re.UNICODE)
+quote_matcher_with_timestamp = re.compile(config.quote_regexp_with_timestamp, flags=re.UNICODE)
+spaces_matcher = re.compile(u"\s", flags=re.U)
+
+def equivalence_partition(iterable, relation):
+ """ Partitionne l'itérable en classes d'équivalences. """
+ classes = []
+ for o in iterable:
+ # find the class it is in
+ found = False
+ for c in classes:
+ if relation( iter(c).next(), o ): # is it equivalent to this class?
+ c.add( o )
+ found = True
+ break
+ if not found: # it is in a new class
+ classes.append( set( [ o ] ) )
+ return classes
+
def get_now():
""" Renvoie la date actuelle """
return datetime.datetime(*time.localtime()[:6])
+def sanitize_author(raw):
+ """Proprifie l'auteur : enlève les espaces insécables."""
+ return spaces_matcher.sub(u" ", raw)
+
class Quote(object):
""" Une citation """
- def __init__(self, author, content, timestamp=None):
+ def __init__(self, author, content, timestamp=None, place=None, quoter=None):
if timestamp is None:
timestamp = get_now()
elif isinstance(timestamp, basestring):
timestamp = datetime.datetime(*time.strptime(timestamp, u"%Y-%m-%d_%H:%M:%S")[:6])
- self.author = author
+ self.author = sanitize_author(author)
self.content = content
self.timestamp = timestamp
+ self.place = place
+ self.quoter = quoter
def jsonize(self):
d = {"author" : self.author, "content" : self.content,
- "timestamp" : self.timestamp.strftime(u"%F_%T")}
+ "timestamp" : self.timestamp.strftime(u"%F_%T"),
+ "place" : self.place, "quoter" : self.quoter}
return d
+ def __get_proper_place(self):
+ """
+ property function pour récupérer ``self.place``
+ mais en virant None ou des chaînes ne contenant que des whitespace.
+ """
+ return self.place if self.place and self.place.strip() != u"" else u""
+ proper_place = property(__get_proper_place)
+
+ def __get_proper_quoter(self):
+ """
+ property function pour récupérer ``self.place``
+ mais en virant None ou des chaînes ne contenant que des whitespace.
+ """
+ return self.quoter if self.quoter and self.quoter.strip() != u"" else u""
+ proper_quoter = property(__get_proper_quoter)
+
def __unicode__(self):
""" Retourne la quote affichable """
return config.quote_template % self.__dict__
def __str__(self):
return unicode(self).encode("utf-8")
+ def display(self, show_context=False):
+ """
+ Retourne une chaîne contenant toujours la quote et l'auteur,
+ et le contexte ssi ``show_context = True``.
+ """
+ s = config.quote_template % self.__dict__
+ if show_context:
+ s = u"%s | %s" % (s, self.proper_place)
+ return s.encode("utf-8")
+
+ def full_str(self):
+ """ Retourne une chaîne représentant la totalité des infos de la quote,
+ tout en étant parsable et human-readable. """
+ s = u"%s %s | %s | %s" % (
+ self.timestamp.strftime("%F_%T"),
+ config.quote_template % self.__dict__,
+ self.proper_place,
+ self.proper_quoter)
+ return s.encode("utf-8")
+
def __eq__(self, otherquote):
""" Vérifie si cette phrase n'a pas déjà été dite par la même personne.
- Indépendamment de la date. """
- return [self.author, self.content] == [otherquote.author, otherquote.content]
+ Indépendamment de la date et de la casse. """
+ return [self.author.lower(), self.content.lower()] == [otherquote.author.lower(), otherquote.content.lower()]
-quote_matcher = re.compile(config.quote_regexp)
def parse(text, date=None):
""" Parse le ``text`` et renvoie une quote ou None. """
""" Sauvegarde la DB dans le fichier de quotes """
save_file(self.quotelist, config.quote_file)
- def store(self, author, content, timestamp=None):
+ def _collapse_author(self, author):
+ """ Renvoie ``author`` avec la casse déjà utilisée si il a déjà été quoté
+ sinon, le renvoie sans le modifier. """
+ authors = list(set([q.author for q in self.quotelist if q.author.lower() == author.lower()]))
+ if len(authors) > 1:
+ print "Warning : authors %s" % authors
+ if authors:
+ return authors[0]
+ else:
+ return author
+
+ def get_clash_authors(self):
+ """ Renvoie une liste de liste d'auteurs qui sont enregistrés avec des casses différentes. """
+ authors = list(set([q.author for q in self.quotelist]))
+ authors = equivalence_partition(authors, lambda x,y: x.lower() == y.lower())
+ authors = [list(c) for c in authors if len(c) > 1]
+ return authors
+
+ def store(self, timestamp=None, **kwargs):
""" Enregistre une nouvelle quote, sauf si elle existe déjà.
+ Force l'auteur à utiliser la même casse si un auteur de casse différente existait déjà.
Renvoie ``True`` si elle a été ajoutée, ``False`` si elle existait. """
- newquote = Quote(author, content, timestamp)
+ kwargs["author"] = self._collapse_author(kwargs["author"])
+ kwargs["timestamp"] = timestamp
+ newquote = Quote(**kwargs)
if not newquote in self.quotelist:
self.quotelist.append(newquote)
return True
return False
-
+
def __repr__(self):
return repr(self.quotelist)
-
+
def random(self):
""" Sort une quote aléatoire """
return random.choice(self.quotelist)
+
+ def quotesfrom(self, author):
+ """ Sort toutes les quotes de ``author`` """
+ return [q for q in self.quotelist if q.author == author]
def randomfrom(self, author):
""" Sort une quote aléatoire de ``author`` """
- return random.choice([q for q in self.quotelist if q.author == author])
+ return random.choice(self.quotesfrom(author))
+
+ def search(self, inquote=None, author=None, regexp=False):
+ """
+ Fait une recherche dans les quotes.
+ C'est une disjonction de cas : on garde la quote si
+ ``inquote`` matche dans le contenu
+ *ou* si ``author`` matche l'auteur
+
+ Si ``regexp=True``, utilise directement les termes comme des regexp.
+ """
+ params = [inquote, author]
+ regexps = []
+ for param in params:
+ if param is None:
+ param = u".*"
+ elif not regexp:
+ param = u".*%s.*" % param
+ regexps.append(re.compile(param, flags=re.UNICODE + re.IGNORECASE))
+ l = [q for q in self.quotelist if all([reg.match(truc) for (reg, truc) in zip(regexps, [q.content, q.author])])]
+ return l
+
+ def search_authors(self, author=None, regexp=False):
+ """Renvoie la liste des auteurs contenant ``author`` ou qui matchent la regexp."""
+ if author is None:
+ author = u".*"
+ elif not regexp:
+ author = u".*%s.*" % author
+ areg = re.compile(author, flags=re.UNICODE + re.IGNORECASE)
+ l = list(set([q.author for q in self.quotelist if areg.match(q.author)]))
+ return l
def dump(quotedb, dump_file=None):
"""Pour exporter les quotes dans un format readable vers un fichier."""
if dump_file is None:
dump_file = config.quote_dump_file
- t = "\n".join(["%s %s" % (q.timestamp.strftime("%F_%T"), q) for q in quotedb.quotelist]) + "\n"
+ t = "\n".join([q.full_str() for q in quotedb.quotelist]) + "\n"
with open(dump_file, "w") as f:
f.write(t)
with open(dump_file) as f:
t = f.read()
t = t.decode("utf-8") # Oui, ça peut fail, mais on ne doit alors pas continuer
- regex = re.compile(config.quote_regexp_with_timestamp)
- l = [m.groupdict() for m in regex.finditer(t)]
+ l = [m.groupdict() for m in quote_matcher_with_timestamp.finditer(t)]
# On instancie les quotes grâce aux dicos qui ont déjà la bonne tronche
l = [Quote(**q) for q in l]
newquotedb = QuoteDB()