System.py 16.4 KB
Newer Older
Jérôme Ortais's avatar
Jérôme Ortais committed
1
#!/usr/bin/python
Jérôme Ortais's avatar
Jérôme Ortais committed
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# -*- coding: utf-8 -*-
#
# Pyromaths
# Un programme en Python qui permet de créer des fiches d'exercices types de
# mathématiques niveau collège ainsi que leur corrigé en LaTeX.
# Copyright (C) 2006 -- Jérôme Ortais (jerome.ortais@pyromaths.org)
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#

24
import sys, os, codecs
25
from lxml import etree
26
from lxml import _elementpath as DONTUSE  # Astuce pour inclure lxml dans Py2exe
27
from re import findall
28
from .TexFiles import mise_en_forme
29
from pyromaths.Values import HOME, VERSION, CONFIGDIR
30

Jérôme Ortais's avatar
Jérôme Ortais committed
31

32
#==============================================================
Jérôme Ortais's avatar
Jérôme Ortais committed
33
#        Gestion des extensions de fichiers
34
#==============================================================
35
def supprime_extension(filename, ext):
36
37
38
39
40
41
    """supprime l'éventuelle extension ext du nom de fichier filename.
    ext est de la forme '.tex'"""
    if os.path.splitext(filename)[1].lower():
        return os.path.splitext(filename)[0]
    return filename

42
def ajoute_extension(filename, ext):
43
44
45
46
47
48
    """ajoute si nécessaire l'extension ext au nom de fichier filename.
    ext est de la forme '.tex'"""
    if os.path.splitext(filename)[1].lower() == ext:
        return filename
    return filename + ext

49
#==============================================================
50
51
52
53
54
55
56
#        Gestion du fichier de configuration de Pyromaths
#==============================================================
def create_config_file():
    """Crée le fichier de configuration au format xml"""
    root = etree.Element("pyromaths")

    child = etree.SubElement(root, "options")
57
    etree.SubElement(child, "nom_fichier").text = _("exercices")
58
    etree.SubElement(child, "chemin_fichier").text = "%s" % HOME
59
    etree.SubElement(child, "titre_fiche").text = _(u"Fiche de révisions")
60
61
62
63
    etree.SubElement(child, "corrige").text = "True"
    etree.SubElement(child, "pdf").text = "True"
    etree.SubElement(child, "unpdf").text = "False"
    etree.SubElement(child, "modele").text = "pyromaths.tex"
64
65

    child = etree.SubElement(root, "informations")
66
    etree.SubElement(child, "version").text = VERSION
67
    etree.SubElement(child, "description").text = _(u"Pyromaths est un programme qui permet de générer des fiches d’exercices de mathématiques de collège ainsi que leur corrigé. Il crée des fichiers au format pdf qui peuvent ensuite être imprimés ou lus sur écran.")
68
    etree.SubElement(child, "icone").text = "pyromaths.ico"
69

70
71
72
73
    subchild = etree.SubElement(child, "auteur")
    etree.SubElement(subchild, "nom").text = u"Jérôme Ortais"
    etree.SubElement(subchild, "email").text = u"jerome.ortais@pyromaths.org"
    etree.SubElement(subchild, "site").text = "http://www.pyromaths.org"
74

75
    return etree.tostring(root, pretty_print=True, encoding=unicode)
76
77
78
79
80

def indent(elem, level=0):
    """Indente correctement les fichiers xml.
    By Filip Salomonsson; published on February 06, 2007.
    http://infix.se/2007/02/06/gentlemen-indent-your-xml"""
81
    i = "\n" + level * "  "
82
83
84
85
    if len(elem):
        if not elem.text or not elem.text.strip():
            elem.text = i + "  "
        for e in elem:
86
            indent(e, level + 1)
87
88
89
90
91
92
93
94
95
            if not e.tail or not e.tail.strip():
                e.tail = i + "  "
        if not e.tail or not e.tail.strip():
            e.tail = i
    else:
        if level and (not elem.tail or not elem.tail.strip()):
            elem.tail = i
    return elem

96
def modify_config_file(fichier):
97
98
    """Modifie le fichier de configuration si besoin, excepté les options utilisateur déjà configurées"""
    modifie = False
99
    oldtree = etree.parse(fichier)
100
101
102
103
104
105
106
    oldroot = oldtree.getroot()
    newroot = etree.XML(create_config_file())
    for element in newroot.iter(tag=etree.Element):
        if not len(element):
            parents = [element]
            e = element.getparent()
            while e is not None:
107
                parents.insert(0, e)
108
109
110
111
112
                e = e.getparent()
            oldtag = oldroot
            for i in range(1, len(parents)):
                if oldtag.find(parents[i].tag) is None and i < len(parents) - 1 :
                    if i > 1:
113
                        etree.SubElement(oldroot.find(parents[i - 1].tag), parents[i].tag)
114
115
116
117
118
                    else:
                        etree.SubElement(oldroot, parents[i].tag)
                    oldtag = oldtag.find(parents[i].tag)
                else:
                    oldtag = oldtag.find(parents[i].tag)
119
                if i == len(parents) - 2: oldparent = oldtag
120
121
122
            if oldtag is None:
                # Ajoute un nouvel item dans le fichier xml
                modifie = True
123
                etree.SubElement(oldparent, element.tag).text = element.text
124
125
126
            elif oldtag.text != element.text and parents[1].tag != "options":
                # Modifie un item existant s'il ne s'agit pas des options
                modifie = True
127
                oldtag.text = element.text
128
    if modifie:
129
        f = codecs.open(os.path.join(CONFIGDIR, "pyromaths.xml"), encoding='utf-8', mode='w')
130
        f.write(etree.tostring(indent(oldroot), pretty_print=True, encoding=unicode))
131
132
133
134
        f.close()

#==============================================================
#        Créer et lance la compilation des fichiers TeX
135
#==============================================================
136
137
138
139
140
141
def _preprocess_pipe(filename, pipe):
    """Exécute chacune des commandes de `pipe` sur `filename`.

    :param str filename: Nom du fichier LaTeX qui va être compilé.
    :param list pipe: Liste de commandes à appliquer, sous la forme de chaînes
        de caractères. Si ces chaînes contiennent `{}`, ceci est remplacé par
Louis's avatar
Louis committed
142
143
144
        le nom du fichier ; sinon, il est ajouté à la fin de la commande. Cet
        élément peut aussi être `None`, auquel cas il correspond à une liste
        vide.
145
146
    """
    from subprocess import call
Louis's avatar
Louis committed
147
148
    if pipe is None:
        pipe = []
149
150
151
152
153
154
    for command in pipe:
        formatted = command.format(filename)
        if formatted == command:
            formatted = '{} {}'.format(command, filename)
        call(formatted, env=os.environ, shell=True)

155
156
157
def creation(parametres):
    """Création et compilation des fiches d'exercices.
    parametres = {'fiche_exo': f0,
158
159
160
                  'fiche_cor': f1,
                  'liste_exos': self.lesexos,
                  'creer_pdf': self.checkBox_create_pdf.checkState(),
161
                  'creer_unpdf': self.checkBox_unpdf.isChecked() and self.checkBox_unpdf.isEnabled(),
162
163
164
                  'titre': unicode(self.lineEdit_titre.text()),
                  'niveau': unicode(self.comboBox_niveau.currentText()),
                }"""
165
166
167
168
    exo = unicode(parametres['fiche_exo'])
    cor = unicode(parametres['fiche_cor'])
    f0 = codecs.open(exo, encoding='utf-8', mode='w')
    f1 = codecs.open(cor, encoding='utf-8', mode='w')
169
170

    if parametres['creer_pdf']:
171
        copie_tronq_modele(f0, parametres, 'entete')
172
173
        if not parametres['creer_unpdf']:
            copie_tronq_modele(f1, parametres, 'entete')
174
175

    for exercice in parametres['liste_exos']:
176
        # write exercise's TeX code (question & answer) to files
177
        f0.write("\n")
178
        f0.writelines(line + "\n" for line in exercice.tex_statement())
179
        f1.write("\n")
180
        f1.writelines(line + "\n" for line in exercice.tex_answer())
181

182
183

    if parametres['creer_pdf']:
184
        if parametres['creer_unpdf']:
185
186
            f0.write("\\label{LastPage}\n")
            f0.write("\\newpage\n")
187
            f0.write(_(u"\\currentpdfbookmark{Le corrigé des exercices}{Corrigé}\n"))
188
            f0.write("\\lhead{\\textsl{{\\footnotesize Page \\thepage/ \\pageref{LastCorPage}}}}\n")
189
190
191
            f0.write("\\setcounter{page}{1} ")
            f0.write("\\setcounter{exo}{0}\n")
            f1.write("\\label{LastCorPage}\n")
192
193
            copie_tronq_modele(f1, parametres, 'pied')
        else:
194
195
            f0.write("\\label{LastPage}\n")
            f1.write("\\label{LastPage}\n")
196
197
            copie_tronq_modele(f0, parametres, 'pied')
            copie_tronq_modele(f1, parametres, 'pied')
198
199
200
201

    f0.close()
    f1.close()

202
203
204
205
206
207
208
209
    if parametres['creer_unpdf']:
        f0 = codecs.open(exo, encoding='utf-8', mode='a')
        f1 = codecs.open(cor, encoding='utf-8', mode='r')
        for line in f1:
            f0.write(line)
        f0.close()
        f1.close()

210
    # indentation des fichiers teX créés
211
    mise_en_forme(exo)
212
    if parametres['corrige'] and not parametres['creer_unpdf']:
213
        mise_en_forme(cor)
214

215
    # Dossiers et fichiers d'enregistrement, définitions qui doivent rester avant le if suivant.
216
217
    dir0 = os.path.dirname(exo)
    dir1 = os.path.dirname(cor)
218
    import socket
219
    if socket.gethostname() == "sd-94439.pyromaths.org":
220
        # Chemin complet pour Pyromaths en ligne car pas d'accents
221
222
        f0noext = os.path.splitext(exo)[0].encode(sys.getfilesystemencoding())
        f1noext = os.path.splitext(cor)[0].encode(sys.getfilesystemencoding())
223
224
225
    else:
        # Pas le chemin pour les autres, au cas où il y aurait un accent dans
        # le chemin (latex ne gère pas le 8 bits)
226
227
        f0noext = os.path.splitext(os.path.basename(exo))[0].encode(sys.getfilesystemencoding())
        f1noext = os.path.splitext(os.path.basename(cor))[0].encode(sys.getfilesystemencoding())
228
229
230
    if parametres['creer_pdf']:
        from subprocess import call

231
        _preprocess_pipe(os.path.join(dir0, '{}.tex'.format(f0noext)), parametres.get('pipe', None))
232
        os.chdir(dir0)
233
        latexmkrc(f0noext)
234
        log = open('%s-pyromaths.log' % f0noext, 'w')
235
236
        if socket.gethostname() == "sd-94439.pyromaths.org":
            os.environ['PATH'] += os.pathsep + "/usr/local/texlive/2016/bin/x86_64-linux"
Jérôme Ortais's avatar
Jérôme Ortais committed
237
238
            call(["latexmk", "-shell-escape", "-silent", "-interaction=nonstopmode", "-output-directory=%s" % dir0, "-pdfps", "%s.tex" % f0noext], env=os.environ, stdout=log)
            call(["latexmk", "-c", "-silent", "-output-directory=%s" % dir0], env=os.environ, stdout=log)
239
        elif os.name == 'nt':
Jérôme Ortais's avatar
Jérôme Ortais committed
240
241
            call(["latexmk", "-pdfps", "-shell-escape", "-silent", "-interaction=nonstopmode", "%s.tex" % f0noext], env={"PATH": os.environ['PATH'], "WINDIR": os.environ['WINDIR'], 'USERPROFILE': os.environ['USERPROFILE']}, stdout=log)
            call(["latexmk", "-silent", "-c"], env={"PATH": os.environ['PATH'], "WINDIR": os.environ['WINDIR'], 'USERPROFILE': os.environ['USERPROFILE']}, stdout=log)
242
        else:
Jérôme Ortais's avatar
Jérôme Ortais committed
243
            call(["latexmk", "-pdfps", "-shell-escape", "-silent", "-interaction=nonstopmode", "%s.tex" % f0noext], stdout=log)
244
            call(["latexmk", "-silent", "-c", "-f"], stdout=log)
245
        log.close()
246
        nettoyage(f0noext)
247
        if not "openpdf" in parametres or parametres["openpdf"]:
248
            if os.name == "nt":  # Cas de Windows.
249
                os.startfile('%s.pdf' % f0noext)
250
            elif sys.platform == "darwin":  # Cas de Mac OS X.
251
252
253
                os.system('open %s.pdf' % f0noext)
            else:
                os.system('xdg-open %s.pdf' % f0noext)
254
255
256

        if parametres['corrige'] and not parametres['creer_unpdf']:
            os.chdir(dir1)
257
            latexmkrc(f1noext)
258
            log = open('%s-pyromaths.log' % f1noext, 'w')
259
260
            if socket.gethostname() == "sd-94439.pyromaths.org":
                os.environ['PATH'] += os.pathsep + "/usr/local/texlive/2016/bin/x86_64-linux"
261
262
263
                call(["latexmk", "-shell-escape", "-silent", "-interaction=nonstopmode", "-output-directory=%s" % dir1, "-pdfps", "%s.tex" % f1noext], env=os.environ, stdout=log)
                call(["latexmk", "-c", "-silent", "-output-directory=%s" % dir1], env=os.environ, stdout=log)
            elif os.name == 'nt':
Jérôme Ortais's avatar
Jérôme Ortais committed
264
265
                call(["latexmk", "%s.tex" % f1noext], env={"PATH": os.environ['PATH'], "WINDIR": os.environ['WINDIR'], 'USERPROFILE': os.environ['USERPROFILE']}, stdout=log)
                call(["latexmk", "-c"], env={"PATH": os.environ['PATH'], "WINDIR": os.environ['WINDIR'], 'USERPROFILE': os.environ['USERPROFILE']}, stdout=log)
266
            else:
Jérôme Ortais's avatar
Jérôme Ortais committed
267
268
                call(["latexmk", "%s.tex" % f1noext], stdout=log)
                call(["latexmk", "-c"], stdout=log)
269
270
            log.close()
            nettoyage(f1noext)
271
            if not "openpdf" in parametres or parametres["openpdf"]:
272
                if os.name == "nt":  # Cas de Windows.
273
                    os.startfile('%s.pdf' % f1noext)
274
                elif sys.platform == "darwin":  # Cas de Mac OS X.
275
276
277
                    os.system('open %s.pdf' % f1noext)
                else:
                    os.system('xdg-open %s.pdf' % f1noext)
278
        else:
279
            os.remove('%s-corrige.tex' % f0noext)
280

281
282
def latexmkrc(basefilename):
    latexmkrc = open('latexmkrc', 'w')
Jérôme Ortais's avatar
Jérôme Ortais committed
283
284
285
286
287
288
289
290
    latexmkrc.write('$pdf_mode = 2;\n')
    latexmkrc.write('$ps2pdf = "ps2pdf %O %S %D";\n')
    latexmkrc.write('$latex = "latex --shell-escape -silent -interaction=nonstopmode  %O %S";\n')
    latexmkrc.write('sub asy {return system("asy \'$_[0]\'");}\n')
    latexmkrc.write('add_cus_dep("asy","eps",0,"asy");\n')
    latexmkrc.write('add_cus_dep("asy","pdf",0,"asy");\n')
    latexmkrc.write('add_cus_dep("asy","tex",0,"asy");\n')
    latexmkrc.write('push @generated_exts, \'pre\', \'dvi\', \'ps\', \'auxlock\', \'fdb_latexmk\', \'fls\', \'out\', \'aux\';\n')
Jérôme Ortais's avatar
Jérôme Ortais committed
291
    latexmkrc.write('$clean_ext .= " %R-?.tex %R-??.tex %R-figure*.dpth %R-figure*.dvi %R-figure*.eps %R-figure*.log %R-figure*.md5 %R-figure*.pre %R-figure*.ps %R-figure*.asy %R-*.asy %R-*_0.eps %R-*.pre";')
292
293
    latexmkrc.close()

294
def nettoyage(basefilename):
295
    """Supprime les fichiers temporaires créés par LaTeX"""
Jérôme Ortais's avatar
Jérôme Ortais committed
296
297
298
299
    #try:
    #    os.remove('latexmkrc')
    #except OSError:
    #        pass
300
301
302
303
304
    if os.path.getsize('%s.pdf' % basefilename) > 1000 :
        for ext in ('.log', '-pyromaths.log'):
            try:
                os.remove(basefilename + ext)
            except OSError:
Jérôme Ortais's avatar
Jérôme Ortais committed
305
                pass
306
307
308
309
310
311
312

def copie_tronq_modele(dest, parametres, master):
    """Copie des morceaux des modèles, suivant le schéma du master."""
    master_fin = '% fin ' + master
    master = '% ' + master
    n = 0

313
    # # Le fichier source doit être un modèle, donc il se trouve dans le dossier 'modeles' de pyromaths.
314
315
    source = parametres['modele']

316
    if os.path.isfile(os.path.join(parametres['datadir'], 'templates', source)):
317
        source = os.path.join(parametres['datadir'], 'templates', source)
318
319
    elif os.path.isfile(os.path.join(parametres['configdir'], 'templates', source)):
        source = os.path.join(parametres['configdir'], 'templates', source)
320
    else:
321
        # TODO: Message d'erreur, le modèle demandé n'existe pas
322
        print(u"Template file not found in %s" %
323
                os.path.join(parametres['datadir'], 'templates'))
324

325
    # # Les variables à remplacer :
326
    titre = parametres['titre']
327
    niveau = parametres['niveau']
328
    if parametres['creer_unpdf']:
329
        bookmark = u"\\currentpdfbookmark{Les énoncés des exercices}{Énoncés}"
330
    else:
331
        bookmark = ""
332
333
334
335
336
337
338
339
340
    #===========================================================================
    # if os.name == 'nt':
    #     os.environ['TEXINPUTS'] = os.path.normpath(os.path.join(parametres['datadir'],
    #         'packages'))
    #     tabvar = 'tabvar.tex'
    # else:
    #     tabvar = os.path.normpath(os.path.join(parametres['datadir'],
    #         'packages', 'tabvar.tex'))
    #===========================================================================
341
    # rawstring pour \tabvar -> tab + abvarsous windows
342
    modele = codecs.open(source, encoding='utf-8', mode='r')
343
344
345
346
    for line in modele:
        if master_fin in line:
            break
        if n > 0:
347
            temp = findall('##{{[A-Z]*}}##', line)
348
            if temp:
349
350
                occ = temp[0][4:len(temp) - 5].lower()
                # line = sub('##{{[A-Z]*}}##',eval(occ),line)
Jérôme Ortais's avatar
Jérôme Ortais committed
351
                line = line.replace(temp[0], eval(occ))
352
353
354
355
356
357
            dest.write(line)

        if master in line:
            n = 1

    modele.close()
358
    return