Le but : imprimer de façon lisible le code Python des partitions de musique électronique en ayant accès aux fontes cmtt, cmbtt, cmsltt de TeX.
Le système d'édition Latex comporte quelques paquetages pour aider à la mise en page de code informatique. Ce qui nous intéresse ici est d'éditer du code Python qui comporte:
Je m'étonne que Latex - qui est un outil destiné surtout aux scientifiques - ne comporte aucun paquetage dont l'usage permettrait d'appliquer simplement, confortablement, ces exigences pourtant limitées.
Il y a bien dans «Le petit livre de TeX» de Raymond Séroul (chap. 13) les sections Typographie d'un programme et Une macro programme élaborée, mais c'est très complexe, car le tout est écrit en TeX. Une autre solution consisterait à écrire un python qui écrit du TeX.
Plusieurs obstacles :
#!/usr/bin/python
#-*-coding:utf-8-*-
# py2tex.py
"""
Fonctions et classes pour imprimer les partitions:
c.-à-d. mettre en forme les programmes, les pythons, en mettant
- en gras les mots réservés,
- en italiques les informations et documentations.
Selon le choix, on obtient une version PDF(r) ou PostScript(r)
en passant par une version en 'plain tex'.
TeX me semble le mieux adapté car les caractères tt sont plus
agréables que les Helvetica disponibles en PS de base.
Comme d'habitude, le python n'est pas destiné à être utilisé de
façon fermée, mais est destiné à des utilisateurs du langage Python
qui essaieront de l'adapter à leurs besoins personnels, d'où
l'absence d'options de lancement.
On initialise une classe avec un grand titre, un sous-titre, le nom
de l'auteur, puis le titre et le nom d'auteur à rappeler sur chaque page,
une mention de copyright à porter sur la première page.
Cette classe prépare une fichier <nom>.tex à partir du <nom>.py
ECART1 = 3 : écart entre le grand titre de la première ligne et le
soustitre de la 2e ligne
ECART2 = 5 : écart entre la ligne de sous-titre et la barre horizontale
ECART3 = 8 : écart entre la barre horizontale et le début du texte
"""
import keyword as _k
import re
__author__ = """Copyright (c) 2009 Raymond Séroul et
René Bastian (Wissembourg, FRANCE)"""
__date__ = "2009.12.25"
ECART1 = 3
ECART2 = 5
ECART3 = 8
def nonascii(texte):
"""transcrit les caractères non ascii en TeX -
tout n'est pas testé."""
dicononascii = {
"ç" : "\\c c",
"Ç" : "\\c C",
"é" : "\\'e",
"É" : "\\'E",
"è" : "\\`e",
"à" : "\\`a",
"è" : "\\`e",
"ì" : "\\`i",
"ò" : "\\`o",
"ù" : "\\`u",
"â" : "\\^a",
"ê" : "\\^e",
"î" : "\\^i",
"ô" : "\\^o",
"û" : "\\^u",
"ä" : '\\"a',
"ë" : '\\"e',
"ä" : '\\"a',
"ü" : '\\"u',
"ö" : '\\"o',
"Â" : "\\^A",
"Ê" : "\\^E",
"Î" : "\\^I",
"Ô" : "\\^O",
"Û" : "\\^U",
"Ä" : '\\"A',
"Ë" : '\\"E',
"Ï" : '\\"I',
"Ö" : '\\"O',
"Ü" : '\\"U',
"À" : "\\`A",
"È" : "\\`E",
"Ì" : "\\`I",
"Ò" : "\\`O",
"Ù" : "\\`U",
"ÿ" : '\\"y',
"Ÿ" : '\\"Y'
}
for acc in dicononascii.keys():
texte = texte.replace(acc, dicononascii[acc])
return texte
def fairetitrepython(titre, stitre, date, auteur):
"""fabrique le titre en haut d'un python;
fonction à adapter à ce qu'on veut
"""
p = " ".join(["\line{\\ftitre ", titre, "}\\vskip", str(ECART1), "mm"])
p += " ".join(["\line{\\fstitre ", stitre, "-", date, "\hfill",
auteur, "}"])
p += " ".join(["\\bigskip\hrule\\bigskip\n"])
return p
def preparer(texte, eliminer=False):
"""rassemble différentes fonctions pour avoir un
texte texifié.
L'ordre est important à cause de collisions."""
# le texte est formaté, mais il faut se débarasser de l'antislash
texte = eliminercomments(texte)
texte = enleverlignesvides(texte)
texte = antislash(texte)
texte = formater(texte)
# on change les caractères
texte = specials(texte)
texte = nonascii(texte)
texte = italiserstrings(texte, eliminer)
texte = traitermotsreserves(texte)
texte = blancsinitials(texte)
return texte
def antislash(texte):
"traite les \ "
return texte.replace("\\", "\\char`\\\\")
def specials(texte):
"""change les caractères spéciaux de TeX
Il faut encore tester les accolades !!.
À traiter avec char ?"""
speciaux = ["_", "&", "%", "$", "#", "^", "~"]
dico = {
"$" : "\$",
"#" : "\#",
"^" : "\^",
"_" : "\_",
"&" : "\&",
"~" : "\~",
"%" : "\%"
}
for c in speciaux:
texte = texte.replace(c, dico[c])
return texte
def motsreservesrs(ligne):
"""mettre en gras les mots réservés; version RS"""
mots = ligne.split(" ")
r = []
for mot in mots:
if mot in _k.kwlist:
r.append("\\\\"+ mot)
elif mot in ["else:", "try:", "finally:", "raise:",
"else:\n", "try:\n", "finally:\n", "raise:\n"]:
r.append("\\\\"+ mot[:-1] + ":")
else:
r.append(mot)
return " ".join(r)
def motsreserves(ligne):
"""mettre en gras les mots réservés"""
mots = ligne.split(" ")
r = []
for mot in mots:
if mot in _k.kwlist:
r.append("{\\fgras{}"+ mot + "}")
elif mot in ["else:", "try:", "finally:", "raise:",
"else:\n", "try:\n", "finally:\n", "raise:\n"]:
r.append("{\\fgras{}"+ mot[:-1] + "}:")
else:
r.append(mot)
return " ".join(r)
def traitermotsreserves(texte):
"traite les mots réservés de Python"
c = texte.split("\n")
r = []
for l in c:
r.append(motsreserves(l))
return "\n".join(r)
def eliminercomments(texte):
""" enlever les commentaires """
lignes = texte.split("\n")
gr = [ligne.split("#")[0] for ligne in lignes]
return "\n".join(gr)
def enleverlignesvides(texte):
" enlève les lignes vides ou blanches "
lignes = texte.split("\n")
r = []
for ligne in lignes:
if ligne.isspace():
pass
else:
r.append(ligne)
s = "\n".join(r)
while s.count("\n\n") > 0:
s = s.replace("\n\n", "\n")
return s
def blancinitial(ligne):
"traite les blancs des débuts de ligne"
i = 0
r = ""
try:
while ligne[i] == " ":
r += "\ "
i += 1
except IndexError:
pass
r += ligne[i:]
r = str(r)
return r
def blancsinitials(texte):
""" trabscrit les blancs initiaux """
coll = texte.split("\n")
r = []
for ligne in coll:
if len(ligne) == 0:
pass
elif ligne[0] == " ":
r.append(blancinitial(ligne))
else:
r.append(ligne)
return "\n".join(r)
def formater(texte):
"""ajoute une ligne vide avant les classes et les fonctions ou méthodes."""
saut = "\n\saut\n"
c = texte.split("\n")
r = []
for a in c:
if a[:6] == "class ":
r.append(saut + a)
elif a[:4] == "def ":
r.append(saut + a)
elif a[:11] == "if __name__":
r.append(saut + a)
elif a[:8] == " def ":
if a[:14] == " def __init":
r.append(a)
else:
r.append(saut + a)
elif a[:10] == "__author__":
r.append("\n" + a)
else:
r.append(a)
return "\n".join(r)
def italiserstrings(texte, eliminer=False):
"""toutes les strings sont imprimées en italique"""
chlong = re.compile(r'""".+?"""', re.DOTALL)
chbref = re.compile(r'".+?"', re.DOTALL)
stexte = texte.replace('"""', '&&&')
stexte = stexte.replace('""', '$$')
motifs = chbref.finditer(stexte)
r = []
px = 0
for x in motifs:
p0, p1 = x.span()
r.append(stexte[px:p0])
r.append('{\\fital{}' + stexte[p0:p1] + '}')
px = p1
r.append(stexte[px:])
stexte = "".join(r)
stexte = stexte.replace('$$', '""')
stexte = stexte.replace('&&&', '"""')
motifs = chlong.finditer(stexte)
r = []
px = 0
for x in motifs:
p0, p1 = x.span()
r.append(stexte[px:p0]
if eliminer:
r.append('{\\fital{} xxx }')
else:
r.append('{\\fital{}' + stexte[p0:p1] + '}')
px = p1
r.append(stexte[px:])
return "".join(r)
class Texifieur(object):
"classe pour texifier un python"
def __init__(self, gtitre, stitre, auteur, titreh="",
auteurh="", acopyright=""):
"""
gtitre : titre qu'on veut donner au texte
stitre : sous-titre ...
auteur : auteur
titreh, auteurh : titre et auteur utilisés sur le haut de chaque page
s'ils doivent être différents de gtitre et auteur
(n'est pas encore utilisable)
acopyright : mention qui n'est pas encore utilisée
Pour régler l'espacement vertical des hauts de page, on peut intervenir sur les
medskip et bigskip dans self.prelude.
"""
self.gtitre = gtitre
self.stitre = stitre
self.auteur = auteur
self.titreh = titreh
self.auteurh = auteurh
self.copyright = acopyright
self.npage = None
self.prelude = """
\\newif\ifpagetitre \pagetitretrue
\\newtoks\hautpagetitre \hautpagetitre={\hfil}
\\newtoks\\baspagetitre \\baspagetitre={\hfil}
\\newtoks\\auteurcourant \\auteurcourant={\hfil}
\\newtoks\\titrecourant \\titrecourant={\hfil}
\\newtoks\hautpagegauche \\newtoks\hautpagedroite
\\newtoks\\baspagegauche \\newtoks\\baspagedroite
\\topskip=20pt
\hsize=160mm \\vsize=250mm % ADAPTER À L'IMPRIMANTE
\leftskip=-5mm % au lieu de -5
\\font\\fgras=cmbtt10 % at 32pt % GRAS DES MOTS RÉSERVÉS
\\font\\fitalfont=cmsltt10 % at 10pt % ITALIQUE DES TEXTES
\def \\fital {\\fitalfont \\baselineskip=11pt} % OK
\\font\\ftitrefont=cmsltt10 at 24pt % TITRE au lieu de 16
\\def \\ftitre {\\ftitrefont \\baselineskip=20pt} % OK
\\font\\fstitrefont=cmsltt10 at 12pt % SOUS-TITRE
\def \\fstitre {\\fstitrefont \\baselineskip=15pt} % OK
\\font\\fauteurfont=cmsltt10 at 12pt % SOUS-TITRE
\def \\fauteur {\\fauteurfont \\baselineskip=15pt} % OK
\\font\\fgrasgrosfont=cmbtt10 at 32pt % TITRE DE PREMIÈRE PAGE
\def \\fgrasgros {\\fgrasgrosfont \\baselineskip=36pt} % OK
\\font\\ttfont=cmtt10 %at 32pt % TITRE DE PREMIÈRE PAGE
\def \\tt {\\ttfont \\baselineskip=12pt} % OK
\def\saut {\\vskip 3mm plus 20pt minus 20pt} % OK
\long \def\\begincode {\\begingroup
\\saut
\obeylines}
\def \endcode {\endgroup}
\hautpagegauche{\\vbox{\line{\\fstitre\\folio\hfill\\the\\titrecourant\hfill\\the\\auteurcourant}%
\medskip\hrule\\bigskip}}
\hautpagedroite{\\vbox{\line{\\the\\auteurcourant\hfill\\the\\titrecourant\hfill\\folio}%
\medskip\hrule\\bigskip}}
\\baspagetitre={\\vbox{\\vskip 10pt\\vbox{\hrule width 124mm}%
{\copyright\ \uppercase\expandafter{\\romannumeral 2010} Ren\\'e Bastian -
Tous droits r\\'eserv\\'es pour tous pays}}} % je n'en ai pas besoin
\\baspagetitre={}
\headline={\ifpagetitre\\the\hautpagetitre
\else\ifodd\pageno\\the\hautpagedroite
\else\\the\hautpagegauche\\fi\\fi}
\\footline={\ifpagetitre\\the\\baspagetitre
\global\pagetitrefalse
\else\ifodd\pageno\\the\\baspagedroite
\else\\the\\baspagegauche
\\fi\\fi}
"""
self.postlude = "\\bye"
def evt(self, texte, primal):
"nouvelle version"
texte = "".join(texte)
if primal:
R = [self.prelude]
else:
R = []
if self.npage:
R.append(self.npage)
titrechapitre = """\pagetitretrue
\hautpagetitre={\\vbox{\obeylines{\\ftitre %s\hfill}
{\\fauteur %s \hfill Ren\\'e Bastian}
\medskip\hrule\medskip
}}""" % (self.gtitre, self.stitre)
R.extend([
titrechapitre,
"\\auteurcourant={\\fstitre " + self.auteur + "}\n",
"\\titrecourant={\\fstitre " + self.gtitre + "}\n",
"\n{\\tt\\begincode",
texte,
"\n\endcode}\n",
"\\vfill\eject\n"
])
return "".join(R)
def __str__(self):
""" affiche presque tout."""
try:
n = max([len(str(x)) for x in self.__dict__])
xformat = "%-" + str(n) + "s : %s \n"
except ValueError:
print "Texifieur:__str__ (1)"
R = [self.__class__.__name__+"\n"]
try:
for x in self.__dict__:
s = xformat % (str(x), self.__dict__[x])
R.append(s)
except ValueError:
print "Texifieur:__str__ (2)"
return "".join(R)
def numeropage(self, n):
"change le numéro de la page"
self.npage = "\pageno=" + str(n) + "\n"
def usage(self):
"usage de la classe"
print self.__init__.__doc__
def attributs(self):
" affiche les attributs "
print(self.gtitre,
self.stitre,
self.auteur,
self.titreh,
self.auteurh,
self.copyright,
self.npage)
Essayez de colorier tout ça ...
Ici on peut pomper le code du module py2tex.pyIl faut évidemment adapter ce code aux besoins particuliers : ajouter des caractères, des mots réservés, changer les avis de copyright, etc. car il n'est pas destiné à un public qui ne sait pas pythoner. Le code TeX qui en résulte est monstrueux. Qu'importe. Et selon ma propre expérience, il ne peut pas se texifier lui-même.
Voici un extrait d'un module imprimé :
Copyright 2010-11 (c) René Bastian - rbastian (arrobe) free.fr