From feebef8d2eda863396f33dc5d9cab0683a294c9d Mon Sep 17 00:00:00 2001 From: Louis Date: Thu, 21 Mar 2013 20:40:24 +0100 Subject: [PATCH 01/11] =?UTF-8?q?[brouillon]=20Je=20sais=20r=C3=A9cup?= =?UTF-8?q?=C3=A9rer=20la=20liste=20des=20chansons=20utilis=C3=A9es?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- parse.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100755 parse.py diff --git a/parse.py b/parse.py new file mode 100755 index 00000000..de007762 --- /dev/null +++ b/parse.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +FILENAME = "naheulbeuk.tex" + +import plasTeX +from plasTeX.TeX import TeX + +doc = TeX(file = FILENAME).parse() + +language_list = set() +for node in doc.allChildNodes: + if node.nodeName == "selectlanguage": + language_list.add(node.argSource) + +print language_list From ce14c65f0f213302b40b5fb00054d1b75cd124b8 Mon Sep 17 00:00:00 2001 From: Louis Date: Mon, 25 Mar 2013 16:44:02 +0100 Subject: [PATCH 02/11] Utilisation d'objets pour le traitement des listes de chansons MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Travail préparatoire pour l'utilisation de PlasTeX --- songbook.py | 62 +++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 48 insertions(+), 14 deletions(-) diff --git a/songbook.py b/songbook.py index db03fba5..1a96d00b 100755 --- a/songbook.py +++ b/songbook.py @@ -59,11 +59,24 @@ def unprefixed(title, prefixes): return match.group(2) return title +class SongsList: + """Manipulation et traitement de liste de chansons""" -def songslist(library, songs, prefixes): - song_objects = [] - for s in songs: - path = library + 'songs/' + s + #: Liste des chansons + songs = [] + + def __init__(self, library, prefixes): + self._library = library + self._prefixes = prefixes + + def append(self, filename): + """Ajout d'une chanson à la liste + + Effets de bord : analyse syntaxique plus ou moins sommaire du fichier + pour en extraire et traiter certaines information (titre, langue, + album, etc.). + """ + path = os.path.join(self._library, 'songs', filename) with open(path, 'r+') as f: data = f.read() title = reTitle.search(data).group(0) @@ -73,14 +86,32 @@ def songslist(library, songs, prefixes): album = match.group(0) else: album = '' - song_objects.append(Song(title, artist, album, path)) + self.songs.append(Song(title, artist, album, path)) + + def append_list(self, filelist): + """Ajoute une liste de chansons à la liste + + L'argument est une liste de chaînes, représentant des noms de fichiers. + """ + for filename in filelist: + self.append(filename) + + def sort(self): + """Trie la liste des chansons selon l'ordre artiste/album/titre. - song_objects = sorted(song_objects, key=lambda x: locale.strxfrm(unprefixed(x.title, prefixes))) - song_objects = sorted(song_objects, key=lambda x: locale.strxfrm(x.album)) - song_objects = sorted(song_objects, key=lambda x: locale.strxfrm(x.artist)) + TODO : Un jour, il sera peut-être possible de laisser l'utilisateur + configurer l'ordre de ce tri. + """ + self.songs = sorted(self.songs, key=lambda x: locale.strxfrm(unprefixed(x.title, self._prefixes))) + self.songs = sorted(self.songs, key=lambda x: locale.strxfrm(x.album)) + self.songs = sorted(self.songs, key=lambda x: locale.strxfrm(x.artist)) - result = [ '\\input{{{0}}}'.format(song.path.replace("\\","/").strip()) for song in song_objects ] - return '\n'.join(result) + def latex(self): + """Renvoie le code LaTeX nécessaire pour intégrer la liste de chansons. + """ + self.sort() + result = [ '\\input{{{0}}}'.format(song.path.replace("\\","/").strip()) for song in self.songs ] + return '\n'.join(result) def parseTemplate(template): embeddedJsonPattern = re.compile(r"^%%:") @@ -151,6 +182,12 @@ def makeTexFile(sb, library, output): parameters = parseTemplate("templates/"+template) + # compute songslist + if songs == "all": + songs = map(lambda x: x[len(library) + 6:], recursiveFind(os.path.join(library, 'songs'), '*.sg')) + songslist = SongsList(library, prefixes) + songslist.append_list(songs) + # output relevant fields out = open(output, 'w') out.write('%% This file has been automatically generated, do not edit!\n') @@ -165,12 +202,9 @@ def makeTexFile(sb, library, output): for name, value in sb.iteritems(): if name in parameters: out.write(formatDefinition(name, toValue(parameters[name],value))) - # output songslist - if songs == "all": - songs = map(lambda x: x[len(library) + 6:], recursiveFind(os.path.join(library, 'songs'), '*.sg')) if len(songs) > 0: - out.write(formatDefinition('songslist', songslist(library, songs, prefixes))) + out.write(formatDefinition('songslist', songslist.latex())) out.write('\\makeatother\n') # output template From 6dd81e9aa57cb8a902357d23241f1bcf9ebae836 Mon Sep 17 00:00:00 2001 From: Louis Date: Mon, 25 Mar 2013 20:55:46 +0100 Subject: [PATCH 03/11] Ajout automatique des langues dans Babel --- parse.py | 16 ---------------- songbook.py | 19 ++++++++++++++++--- templates/ancient.tmpl | 7 +++++-- templates/minimal.tmpl | 7 +++++-- templates/patacrep.tmpl | 8 ++++++-- utils/plastex.py | 31 +++++++++++++++++++++++++++++++ 6 files changed, 63 insertions(+), 25 deletions(-) delete mode 100755 parse.py create mode 100755 utils/plastex.py diff --git a/parse.py b/parse.py deleted file mode 100755 index de007762..00000000 --- a/parse.py +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -FILENAME = "naheulbeuk.tex" - -import plasTeX -from plasTeX.TeX import TeX - -doc = TeX(file = FILENAME).parse() - -language_list = set() -for node in doc.allChildNodes: - if node.nodeName == "selectlanguage": - language_list.add(node.argSource) - -print language_list diff --git a/songbook.py b/songbook.py index 1a96d00b..8851509a 100755 --- a/songbook.py +++ b/songbook.py @@ -12,17 +12,20 @@ import locale import platform from utils.utils import recursiveFind +from utils.plastex import parsetex reTitle = re.compile('(?<=beginsong\\{)(.(? Date: Mon, 25 Mar 2013 21:04:48 +0100 Subject: [PATCH 04/11] Correction de conflits d'imports --- songbook.py | 2 +- utils/__init__.py | 13 +++++++++++++ utils/utils.py | 13 ------------- 3 files changed, 14 insertions(+), 14 deletions(-) delete mode 100644 utils/utils.py diff --git a/songbook.py b/songbook.py index 8851509a..74e5905e 100755 --- a/songbook.py +++ b/songbook.py @@ -11,7 +11,7 @@ import shutil import locale import platform -from utils.utils import recursiveFind +from utils import recursiveFind from utils.plastex import parsetex reTitle = re.compile('(?<=beginsong\\{)(.(? Date: Mon, 25 Mar 2013 21:40:32 +0100 Subject: [PATCH 05/11] =?UTF-8?q?Les=20modules=20LaTeX=20ne=20sont=20charg?= =?UTF-8?q?=C3=A9s=20qu'une=20seule=20fois=20par=20PlasTeX?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils/plastex.py | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/utils/plastex.py b/utils/plastex.py index 7773546a..0402817d 100755 --- a/utils/plastex.py +++ b/utils/plastex.py @@ -1,8 +1,30 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -import plasTeX from plasTeX.TeX import TeX +import codecs +import copy + +from utils import songs + +class SongParser: + """Classe singleton, pour ne charger qu'une fois les modules LaTeX""" + _tex = None + + @classmethod + def _create_TeX(cls): + cls._tex = TeX() + cls._tex.disableLogging() + cls._tex.ownerDocument.context.loadBaseMacros() + cls._tex.ownerDocument.context.loadPackage(cls._tex, "babel") + + @classmethod + def parse(cls, filename): + if not cls._tex: + cls._create_TeX() + tex = copy.copy(cls._tex) + tex.input(codecs.open(filename, 'r+', 'utf-8', 'replace')) + return tex.parse() def parsetex(filename): """Analyse syntaxique d'un fichier .sg @@ -12,13 +34,8 @@ def parsetex(filename): - languages: l'ensemble des langages utilisés (recherche des \selectlanguages{}). """ - # Chargement du fichier .sg (.tex), et des modules nécessaires - tex = TeX(file = filename) - tex.disableLogging() - tex.ownerDocument.context.loadPackage(tex, "babel") - # Analyse syntaxique - doc= tex.parse() + doc = SongParser.parse(filename) # Extraction des données data = { From b4ef42673677c90fa4c71e983a2b0d901f5812e0 Mon Sep 17 00:00:00 2001 From: Louis Date: Mon, 25 Mar 2013 22:59:42 +0100 Subject: [PATCH 06/11] Analyse syntaxique correcte des titres des chansons --- songbook.py | 12 +++++------- utils/plastex.py | 33 ++++++++++++++++++--------------- utils/songs.py | 19 +++++++++++++++++++ 3 files changed, 42 insertions(+), 22 deletions(-) create mode 100644 utils/songs.py diff --git a/songbook.py b/songbook.py index 74e5905e..887799be 100755 --- a/songbook.py +++ b/songbook.py @@ -14,20 +14,19 @@ import platform from utils import recursiveFind from utils.plastex import parsetex -reTitle = re.compile('(?<=beginsong\\{)(.(? Date: Mon, 25 Mar 2013 23:36:05 +0100 Subject: [PATCH 07/11] Analyse syntaxique de \beginsong MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Correction de problèmes dans l'analyse du titre - Analyse des arguments --- songbook.py | 29 ++++++++--------------------- utils/plastex.py | 5 ++++- utils/songs.py | 23 +++++++++++++++++++---- 3 files changed, 31 insertions(+), 26 deletions(-) diff --git a/songbook.py b/songbook.py index 887799be..80117f4e 100755 --- a/songbook.py +++ b/songbook.py @@ -14,19 +14,15 @@ import platform from utils import recursiveFind from utils.plastex import parsetex -reArtist = re.compile('(?<=by=)(.(? Date: Tue, 26 Mar 2013 00:18:14 +0100 Subject: [PATCH 08/11] =?UTF-8?q?Possibilit=C3=A9=20de=20choisir=20le=20tr?= =?UTF-8?q?i=20des=20chansons?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- songbook.py | 59 ++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 43 insertions(+), 16 deletions(-) diff --git a/songbook.py b/songbook.py index 80117f4e..a317969f 100755 --- a/songbook.py +++ b/songbook.py @@ -15,6 +15,9 @@ from utils import recursiveFind from utils.plastex import parsetex class Song: + #: Ordre de tri + sort = [] + def __init__(self, path, languages, titles, args): self.titles = titles self.args = args @@ -24,6 +27,26 @@ class Song: def __repr__(self): return repr((self.titles, self.args, self.path)) + def __cmp__(self, other): + if not isinstance(other, Song): + return NotImplemented + for key in self.sort: + if key == "@title": + self_key = [locale.strxfrm(title) for title in self.titles] + other_key = [locale.strxfrm(title) for title in other.titles] + elif key == "@path": + self.key = locale.strxfrm(self.path) + other_key = locale.strxfrm(other.path) + else: + self_key = locale.strxfrm(self.args.get(key, "")) + other_key = locale.strxfrm(other.args.get(key, "")) + + if self_key < other_key: + return -1 + elif self_key > other_key: + return 1 + return 0 + if platform.system() == "Linux": from xdg.BaseDirectory import * cachePath = os.path.join(xdg_cache_home, 'songbook') @@ -60,13 +83,14 @@ def unprefixed(title, prefixes): class SongsList: """Manipulation et traitement de liste de chansons""" - #: Liste des chansons - songs = [] - def __init__(self, library, prefixes): self._library = library self._prefixes = prefixes + # Liste triée des chansons + self.songs = [] + + def append(self, filename): """Ajout d'une chanson à la liste @@ -78,7 +102,15 @@ class SongsList: # Exécution de PlasTeX data = parsetex(path) - self.songs.append(Song(path, data['languages'], data['titles'], data['args'])) + song = Song(path, data['languages'], data['titles'], data['args']) + low, high = 0, len(self.songs) + while low != high: + middle = (low + high) / 2 + if song < self.songs[middle]: + high = middle + else: + low = middle + 1 + self.songs.insert(low, song) def append_list(self, filelist): """Ajoute une liste de chansons à la liste @@ -88,21 +120,10 @@ class SongsList: for filename in filelist: self.append(filename) - def sort(self): - """Trie la liste des chansons selon l'ordre artiste/album/titre. - - TODO : Un jour, il sera peut-être possible de laisser l'utilisateur - configurer l'ordre de ce tri. - """ - self.songs = sorted(self.songs, key=lambda x: locale.strxfrm(unprefixed(x.titles[0], self._prefixes))) - self.songs = sorted(self.songs, key=lambda x: locale.strxfrm(x.args.get("album", ""))) - self.songs = sorted(self.songs, key=lambda x: locale.strxfrm(x.args.get("by", ""))) - def latex(self): """Renvoie le code LaTeX nécessaire pour intégrer la liste de chansons. """ - self.sort() - result = [ '\\input{{{0}}}'.format(song.path.replace("\\","/").strip()) for song in self.songs ] + result = [ '\\input{{{0}}}'.format(song.path.replace("\\","/").strip()) for song in self.songs] return '\n'.join(result) def languages(self): @@ -174,6 +195,12 @@ def makeTexFile(sb, library, output): for prefix in sb["titleprefixwords"]: titleprefixwords += "\\titleprefixword{%s}\n" % prefix sb["titleprefixwords"] = titleprefixwords + if "sort" in sb: + sort = sb["sort"] + del sb["sort"] + else: + sort = [u"by", u"album", u"@title"] + Song.sort = sort parameters = parseTemplate("templates/"+template) From 83e80f58bf06ed9f8c3951eef46110df733220f6 Mon Sep 17 00:00:00 2001 From: Louis Date: Tue, 26 Mar 2013 00:43:13 +0100 Subject: [PATCH 09/11] =?UTF-8?q?R=C3=A9tablissement=20de=20la=20langue=20?= =?UTF-8?q?principale=20=C3=A0=20la=20fin=20de=20la=20liste=20des=20chanso?= =?UTF-8?q?ns?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- songbook.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/songbook.py b/songbook.py index a317969f..4561b986 100755 --- a/songbook.py +++ b/songbook.py @@ -83,9 +83,10 @@ def unprefixed(title, prefixes): class SongsList: """Manipulation et traitement de liste de chansons""" - def __init__(self, library, prefixes): + def __init__(self, library, prefixes, language): self._library = library self._prefixes = prefixes + self._language = language # Liste triée des chansons self.songs = [] @@ -124,6 +125,7 @@ class SongsList: """Renvoie le code LaTeX nécessaire pour intégrer la liste de chansons. """ result = [ '\\input{{{0}}}'.format(song.path.replace("\\","/").strip()) for song in self.songs] + result.append('\\selectlanguage{%s}' % self._language) return '\n'.join(result) def languages(self): @@ -195,6 +197,8 @@ def makeTexFile(sb, library, output): for prefix in sb["titleprefixwords"]: titleprefixwords += "\\titleprefixword{%s}\n" % prefix sb["titleprefixwords"] = titleprefixwords + if "lang" not in sb: + sb["lang"] = "french" if "sort" in sb: sort = sb["sort"] del sb["sort"] @@ -208,7 +212,7 @@ def makeTexFile(sb, library, output): # compute songslist if songs == "all": songs = map(lambda x: x[len(library) + 6:], recursiveFind(os.path.join(library, 'songs'), '*.sg')) - songslist = SongsList(library, prefixes) + songslist = SongsList(library, prefixes, sb["lang"]) songslist.append_list(songs) sb["languages"] = ",".join(songslist.languages()) From 682045bd79fda029fa26cdbf6e92a1440e236816 Mon Sep 17 00:00:00 2001 From: Louis Date: Wed, 27 Mar 2013 16:56:32 +0100 Subject: [PATCH 10/11] Correction d'un bug de plasTeX --- utils/patchedbabel.py | 51 +++++++++++++++++++++++++++++++++++++++++++ utils/plastex.py | 2 +- 2 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 utils/patchedbabel.py diff --git a/utils/patchedbabel.py b/utils/patchedbabel.py new file mode 100644 index 00000000..abfe920f --- /dev/null +++ b/utils/patchedbabel.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +"""Patch pour le paquet Babel de PlasTeX + +Un bug dans PlasTeX intervient lorsqu'on essaye d'analyser une commande LaTeX +\selectlanguage{}, que nouv voulons utiliser ici. Un patch a été proposé aux +développeurs de plasTeX, et accepté. Mais il faut que cette correction arrive +en production. En attendant, nous utilisons cette version modifiée. + +Dés que la correction sera entrée en production, il faudra supprimer ce +fichier, et remplater l'occurence à "patchedbabel" par "babel" dans le fichier +"plastex.py". +La correction à suveiller est la révision 1.3 du fichier babel.py : +http://plastex.cvs.sourceforge.net/viewvc/plastex/plastex/plasTeX/Packages/babel.py?view=log + +# Comment vérifier si on peut supprimer ce fichier ? + +1) Remplacer l'occurence à patchedbabel par babel dans le fichier plastex.py. + +2) Générer un fichier .tex à partir d'un fichier .sb, ce dernier faisant +intervenir des chansons dans lesquelles \selectlanguage est utilisé (par +exemple, "make -B matteo.tex" ou "make -B naheulbeuk.tex" pour des fichiers pas +trop gros. + +3) Si l'erreur suivante apparaît, c'est qu'il faut encore attendre. + +> Traceback (most recent call last): +> [...] +> File "/usr/lib/pymodules/python2.7/plasTeX/Packages/babel.py", line 18, in invoke +> context.loadLanguage(self.attributes['lang'], self.ownerDocument) +> NameError: global name 'context' is not defined + +3 bis) Si elle n'apparait pas : youpi ! Supprimez ce fichier ! + +# Contact et commentaires + +Mercredi 27 mars 2013 +Louis + +""" + +from plasTeX import Command + +class selectlanguage(Command): + args = 'lang:str' + + def invoke(self, tex): + res = Command.invoke(self, tex) + self.ownerDocument.context.loadLanguage(self.attributes['lang'], self.ownerDocument) + return res diff --git a/utils/plastex.py b/utils/plastex.py index 426b4c76..b316111d 100755 --- a/utils/plastex.py +++ b/utils/plastex.py @@ -15,8 +15,8 @@ class SongParser: tex = TeX() tex.disableLogging() tex.ownerDocument.context.loadBaseMacros() - tex.ownerDocument.context.loadPackage(tex, "babel") sys.path.append(os.path.dirname(__file__)) + tex.ownerDocument.context.loadPackage(tex, "patchedbabel") tex.ownerDocument.context.loadPackage(tex, "songs") sys.path.pop() return tex From b41caf27d8b93bb1f1ffbf89b0a676ddea9a2c47 Mon Sep 17 00:00:00 2001 From: Louis Date: Mon, 1 Apr 2013 23:32:04 +0200 Subject: [PATCH 11/11] =?UTF-8?q?Prise=20en=20compte=20des=20diacritiques?= =?UTF-8?q?=20et=20autres=20caract=C3=A8res=20non-ascii=20dans=20les=20tit?= =?UTF-8?q?res=20et=20les=20pr=C3=A9fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - le tri par titre ignorait les préfixes ; - les préfixes n'étaient pas pris en compte lorsqu'ils étaient directement suivis de caractères non ascii. --- songbook-makeindex.py | 4 ++-- songbook.py | 14 +++++++++----- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/songbook-makeindex.py b/songbook-makeindex.py index f780ecaa..56cef6f9 100755 --- a/songbook-makeindex.py +++ b/songbook-makeindex.py @@ -40,13 +40,13 @@ class index: self.prefix_patterns = [] if 'prefix' in self.keywords: for prefix in self.keywords['prefix']: - self.prefix_patterns.append(re.compile(r"^(%s)\b\s*(.*)$" % prefix)) + self.prefix_patterns.append(re.compile(r"^(%s)(\b|\\)(\s*.*)$" % prefix)) def add(self, key, number, link): for pattern in self.prefix_patterns: match = pattern.match(key) if match: - key = "%s (%s)" % (match.group(2), match.group(1)) + key = "%s (%s)" % (match.group(2) + match.group(3), match.group(1)) break # Only one match per key (first, key) = self.filter(key) if not self.data.has_key(first): diff --git a/songbook.py b/songbook.py index 4561b986..05c533ae 100755 --- a/songbook.py +++ b/songbook.py @@ -10,6 +10,7 @@ import locale import shutil import locale import platform +from unidecode import unidecode from utils import recursiveFind from utils.plastex import parsetex @@ -17,9 +18,12 @@ from utils.plastex import parsetex class Song: #: Ordre de tri sort = [] + #: Préfixes à ignorer pour le tri + prefixes = [] def __init__(self, path, languages, titles, args): self.titles = titles + self.normalized_titles = [locale.strxfrm(unprefixed(unidecode(unicode(title, "utf-8")), self.prefixes)) for title in titles] self.args = args self.path = path self.languages = languages @@ -32,8 +36,8 @@ class Song: return NotImplemented for key in self.sort: if key == "@title": - self_key = [locale.strxfrm(title) for title in self.titles] - other_key = [locale.strxfrm(title) for title in other.titles] + self_key = self.normalized_titles + other_key = other.normalized_titles elif key == "@path": self.key = locale.strxfrm(self.path) other_key = locale.strxfrm(other.path) @@ -83,9 +87,8 @@ def unprefixed(title, prefixes): class SongsList: """Manipulation et traitement de liste de chansons""" - def __init__(self, library, prefixes, language): + def __init__(self, library, language): self._library = library - self._prefixes = prefixes self._language = language # Liste triée des chansons @@ -205,6 +208,7 @@ def makeTexFile(sb, library, output): else: sort = [u"by", u"album", u"@title"] Song.sort = sort + Song.prefixes = prefixes parameters = parseTemplate("templates/"+template) @@ -212,7 +216,7 @@ def makeTexFile(sb, library, output): # compute songslist if songs == "all": songs = map(lambda x: x[len(library) + 6:], recursiveFind(os.path.join(library, 'songs'), '*.sg')) - songslist = SongsList(library, prefixes, sb["lang"]) + songslist = SongsList(library, sb["lang"]) songslist.append_list(songs) sb["languages"] = ",".join(songslist.languages())