1 # encoding: utf-8 |
|
2 |
|
3 import os |
|
4 import re |
|
5 import sys |
|
6 import shutil |
|
7 import codecs |
|
8 from glob import glob |
|
9 |
|
10 from docutils import core as docCore |
|
11 |
|
12 conf_name = 'SciPy2009' |
|
13 |
|
14 current_dir = '/media/python/workspace/kiwipycon/project/kiwipycon/proceedings/booklet' |
|
15 |
|
16 outdir = current_dir + os.sep + 'output' |
|
17 |
|
18 sourcedir = current_dir + os.sep + 'sources' |
|
19 try: |
|
20 os.mkdir(outdir) |
|
21 except: |
|
22 pass |
|
23 |
|
24 outfilename = outdir + os.sep + 'booklet.tex' |
|
25 |
|
26 ############################################################################## |
|
27 # Routines for supervised execution |
|
28 ############################################################################## |
|
29 |
|
30 from threading import Thread |
|
31 import os |
|
32 import signal |
|
33 from subprocess import Popen |
|
34 from time import sleep |
|
35 |
|
36 def delayed_kill(pid, delay=10): |
|
37 sleep(delay) |
|
38 try: |
|
39 os.kill(pid, signal.SIGTERM) |
|
40 except OSError: |
|
41 pass |
|
42 |
|
43 def supervise_popen(command, timeout=10): |
|
44 process = Popen(command) |
|
45 Thread(target=delayed_kill, args=(process.pid,timeout),).start() |
|
46 |
|
47 process.wait() |
|
48 |
|
49 |
|
50 |
|
51 ############################################################################## |
|
52 # LaTeX generation functions. |
|
53 ############################################################################## |
|
54 |
|
55 def protect(string): |
|
56 r''' Protects all the "\" in a string by adding a second one before |
|
57 |
|
58 >>> protect(r'\foo \*') |
|
59 '\\\\foo \\\\*' |
|
60 ''' |
|
61 return re.sub(r"\\", r"\\\\", string) |
|
62 |
|
63 |
|
64 def safe_unlink(filename): |
|
65 """ Remove a file from the disk only if it exists, if not r=fails silently |
|
66 """ |
|
67 if os.path.exists(filename): |
|
68 os.unlink(filename) |
|
69 |
|
70 rxcountpages = re.compile(r"$\s*/Type\s*/Page[/\s]", re.MULTILINE|re.DOTALL) |
|
71 |
|
72 def count_pages(filename): |
|
73 data = file(filename,"rb").read() |
|
74 return len(rxcountpages.findall(data)) |
|
75 |
|
76 |
|
77 def tex2pdf(filename, remove_tex=True, timeout=10, runs=2): |
|
78 """ Compiles a TeX file with pdfLaTeX (or LaTeX, if or dvi ps requested) |
|
79 and cleans up the mess afterwards |
|
80 """ |
|
81 current_dir = os.getcwd() |
|
82 os.chdir(os.path.dirname(filename)) |
|
83 print >> sys.stderr, "Compiling document to pdf" |
|
84 basename = os.path.splitext(os.path.basename(filename))[0] |
|
85 if os.path.exists(basename + '.pdf'): |
|
86 os.unlink(basename + '.pdf') |
|
87 for _ in range(runs): |
|
88 supervise_popen(("pdflatex", "--interaction", "scrollmode", |
|
89 os.path.basename(filename)), timeout=timeout) |
|
90 error_file = None |
|
91 errors = file(os.path.abspath(basename + '.log')).readlines()[-1] |
|
92 if not os.path.exists(basename + '.pdf') or \ |
|
93 "Fatal error" in errors: |
|
94 error_file = os.path.abspath(basename + '.log') |
|
95 if remove_tex: |
|
96 safe_unlink(filename+".tex") |
|
97 safe_unlink(filename+".log") |
|
98 safe_unlink(filename+".aux") |
|
99 safe_unlink(filename+".out") |
|
100 os.chdir(current_dir) |
|
101 return error_file |
|
102 |
|
103 |
|
104 def rst2latex(rst_string, no_preamble=True, allow_latex=True): |
|
105 """ Calls docutils' engine to convert a rst string to a LaTeX file. |
|
106 """ |
|
107 overrides = {'output_encoding': 'utf-8', 'initial_header_level': 3, |
|
108 'no_doc_title': True, 'use_latex_citations': True, |
|
109 'use_latex_footnotes':True} |
|
110 if allow_latex: |
|
111 rst_string = u'''.. role:: math(raw) |
|
112 :format: latex |
|
113 \n\n''' + rst_string |
|
114 tex_string = docCore.publish_string( |
|
115 source=rst_string, |
|
116 writer_name='latex2e', |
|
117 settings_overrides=overrides) |
|
118 if no_preamble: |
|
119 extract_document = \ |
|
120 re.compile(r'.*\\begin\{document\}(.*)\\end\{document\}', |
|
121 re.DOTALL) |
|
122 matches = extract_document.match(tex_string) |
|
123 tex_string = matches.groups()[0] |
|
124 return tex_string |
|
125 |
|
126 |
|
127 def get_latex_preamble(): |
|
128 """ Retrieve the required preamble from docutils. |
|
129 """ |
|
130 full_document = rst2latex('\n', no_preamble=False) |
|
131 preamble = re.split(r'\\begin\{document\}', full_document)[0] |
|
132 ## Remove the documentclass. |
|
133 preamble = r""" |
|
134 %s |
|
135 \makeatletter |
|
136 \newcommand{\class@name}{gael} |
|
137 \makeatother |
|
138 \usepackage{ltxgrid} |
|
139 %s |
|
140 """ % ( |
|
141 preamble.split('\n')[0], |
|
142 '\n'.join(preamble.split('\n')[1:]), |
|
143 ) |
|
144 return preamble |
|
145 |
|
146 |
|
147 ############################################################################## |
|
148 # Functions to generate part of the booklet |
|
149 ############################################################################## |
|
150 def addfile(outfile, texfilename): |
|
151 """ Includes the content of a tex file in our outfile. |
|
152 """ |
|
153 include = codecs.open(texfilename, 'r') |
|
154 data = include.readlines() |
|
155 outfile.write(ur'\thispagestyle{empty}' + u'\n') |
|
156 outfile.writelines(data) |
|
157 |
|
158 |
|
159 def preamble(outfile): |
|
160 outfile.write(r''' |
|
161 %s |
|
162 \usepackage{abstracts} |
|
163 \usepackage{ltxgrid} |
|
164 \usepackage{amssymb,latexsym,amsmath,amsthm} |
|
165 \usepackage{longtable} |
|
166 \geometry{left=.8cm, textwidth=17cm, bindingoffset=0.6cm, |
|
167 textheight=25.3cm, twoside} |
|
168 \usepackage{hyperref} |
|
169 \hypersetup{pdftitle={Proceedings of the 8th Annual Python in Science Conference}} |
|
170 \begin{document} |
|
171 |
|
172 '''.encode('utf-8') % get_latex_preamble()) |
|
173 |
|
174 # XXX SciPy08 should not be hard coded, but to run out of the webapp |
|
175 |
|
176 def hack_include_graphics(latex_text): |
|
177 """ Replaces all the \includegraphics call with call that impose the |
|
178 width to be 0.9\linewidth. |
|
179 """ |
|
180 latex_text = re.sub(r'\\setlength\{\\rightmargin\}\{\\leftmargin\}', |
|
181 r'\\setlength{\\leftmargin}{4ex}\\setlength{\\rightmargin}{0ex}', |
|
182 latex_text) |
|
183 latex_text = re.sub(r'\\begin\{quote\}\n\\begin\{itemize\}', |
|
184 r'\\begin{itemize}', |
|
185 latex_text) |
|
186 latex_text = re.sub(r'\\end\{itemize\}\n\\end\{quote\}', |
|
187 r'\\end{itemize}', |
|
188 latex_text) |
|
189 latex_text = re.sub(r'\\includegraphics(\[.*\])?\{', |
|
190 r'\includegraphics[width=\linewidth]{', |
|
191 latex_text) |
|
192 latex_text = re.sub(r'\\href\{([^}]+)\}\{http://(([^{}]|(\{[^}]*\}))+)\}', |
|
193 r'''% |
|
194 % Break penalties to have URL break easily: |
|
195 \mathchardef\UrlBreakPenalty=0 |
|
196 \mathchardef\UrlBigBreakPenalty=0 |
|
197 %\hskip 0pt plus 2em |
|
198 \href{\1}{\url{\1}}''', |
|
199 latex_text) |
|
200 latex_text = re.sub(r'\\href\{([^}]+)\}\{https://(([^{}]|(\{[^}]*\}))+)\}', |
|
201 r'''% |
|
202 % Break penalties to have URL break easily: |
|
203 \mathchardef\UrlBreakPenalty=0 |
|
204 \mathchardef\UrlBigBreakPenalty=0 |
|
205 \linebreak |
|
206 \href{\1}{\url{\1}}''', |
|
207 latex_text) |
|
208 |
|
209 return latex_text |
|
210 |
|
211 |
|
212 def render_abstract(outfile, abstract, start_page=None): |
|
213 """ Writes the LaTeX string corresponding to one abstract. |
|
214 """ |
|
215 if start_page is not None: |
|
216 outfile.write(r""" |
|
217 \setcounter{page}{%i} |
|
218 """ % start_page) |
|
219 else: |
|
220 if hasattr(abstract, 'start_page'): |
|
221 start_page = abstract.start_page |
|
222 else: |
|
223 start_page = 1 |
|
224 if not abstract.authors: |
|
225 author_list = abstract.owners |
|
226 else: |
|
227 author_list = abstract.authors |
|
228 authors = [] |
|
229 print dir(author_list[0]) |
|
230 for author in author_list: |
|
231 # If the author has no surname, he is not an author |
|
232 if author.surname: |
|
233 if author.email_address: |
|
234 email = r'(\email{%s})' % author.email_address |
|
235 else: |
|
236 email = '' |
|
237 authors.append(ur'''\otherauthors{ |
|
238 %s %s |
|
239 %s -- |
|
240 \address{%s, %s} |
|
241 }''' % (author.first_names, author.surname, |
|
242 email, |
|
243 author.institution, |
|
244 author.city)) |
|
245 if authors: |
|
246 authors = u'\n'.join(authors) |
|
247 authors += r'\addauthorstoc{%s}' % ', '.join( |
|
248 '%s. %s' % (author.first_names[0], author.surname) |
|
249 for author in author_list |
|
250 ) |
|
251 author_cite_list = ['%s. %s' % (a.first_names[0], a.surname) |
|
252 for a in author_list] |
|
253 if len(author_cite_list) > 4: |
|
254 author_cite_list = author_cite_list[:3] |
|
255 author_cite_list.append('et al.') |
|
256 citation = ', '.join(author_cite_list) + \ |
|
257 'in Proc. SciPy 2009, G. Varoquaux, S. van der Walt, J. Millman (Eds), ' |
|
258 copyright = '\\copyright 2009, %s' % ( ', '.join(author_cite_list)) |
|
259 else: |
|
260 authors = '' |
|
261 citation = 'Citation' |
|
262 copyright = 'Copyright' |
|
263 if hasattr(abstract, 'num_pages'): |
|
264 citation += 'pp. %i--%i' % (start_page, start_page + |
|
265 abstract.num_pages) |
|
266 else: |
|
267 citation += 'p. %i'% start_page |
|
268 if hasattr(abstract, 'number'): |
|
269 abstract.url = 'http://conference.scipy.org/proceedings/%s/paper_%i' \ |
|
270 % (conf_name, abstract.number) |
|
271 url = r'\url{%s}' % abstract.url |
|
272 else: |
|
273 url = '' |
|
274 paper_text = abstract.paper_text |
|
275 if paper_text == '': |
|
276 paper_text = abstract.summary |
|
277 # XXX: It doesn't seem to be right to be doing this, but I get a |
|
278 # nasty UnicodeDecodeError on some rare abstracts, elsewhere. |
|
279 paper_text = codecs.utf_8_decode(hack_include_graphics( |
|
280 rst2latex(paper_text)))[0] |
|
281 paper_abstract = abstract.paper_abstract |
|
282 if paper_abstract is None: |
|
283 paper_abstract = '' |
|
284 if not paper_abstract=='': |
|
285 paper_abstract = ur'\begin{abstract}%s\end{abstract}' % \ |
|
286 paper_abstract#.encode('utf-8') |
|
287 abstract_dict = { |
|
288 'text': paper_text.encode('utf-8'), |
|
289 'abstract': paper_abstract.encode('utf-8'), |
|
290 'authors': authors.encode('utf-8'), |
|
291 'title': abstract.title.encode('utf-8'), |
|
292 'citation': citation.encode('utf-8'), |
|
293 'copyright': copyright.encode('utf-8'), |
|
294 'url': url.encode('utf-8'), |
|
295 } |
|
296 outfile.write(codecs.utf_8_decode(ur''' |
|
297 \phantomsection |
|
298 \hypertarget{chapter}{} |
|
299 \vspace*{-2em} |
|
300 |
|
301 \resetheadings{%(title)s}{%(citation)s}{%(url)s}{%(copyright)s} |
|
302 \title{%(title)s} |
|
303 |
|
304 \begin{minipage}{\linewidth} |
|
305 %(authors)s |
|
306 \end{minipage} |
|
307 |
|
308 \noindent\rule{\linewidth}{0.2ex} |
|
309 \vspace*{-0.5ex} |
|
310 \twocolumngrid |
|
311 %(abstract)s |
|
312 |
|
313 \sloppy |
|
314 |
|
315 %(text)s |
|
316 |
|
317 \fussy |
|
318 \onecolumngrid |
|
319 \smallskip |
|
320 \vfill |
|
321 \filbreak |
|
322 \clearpage |
|
323 |
|
324 '''.encode('utf-8') % abstract_dict )[0]) |
|
325 |
|
326 def copy_files(dest=outfilename): |
|
327 """ Copy the required file from the source dir to the output dir. |
|
328 """ |
|
329 dirname = os.path.dirname(dest) |
|
330 if dirname == '': |
|
331 dirname = '.' |
|
332 for filename in glob(sourcedir+os.sep+'*'): |
|
333 destfile = os.path.abspath(dirname + os.sep + |
|
334 os.path.basename(filename)) |
|
335 shutil.copy2(filename, destfile) |
|
336 |
|
337 |
|
338 def mk_abstract_preview(abstract, outfilename, attach_dir, start_page=None): |
|
339 """ Generate a preview for an given paper. |
|
340 """ |
|
341 copy_files() |
|
342 outdir = os.path.dirname(os.path.abspath(outfilename)) |
|
343 for f in glob(os.path.join(attach_dir, '*')): |
|
344 if os.path.isdir(f) and not os.path.exists(f): |
|
345 os.makedirs(f) |
|
346 else: |
|
347 if not outdir == os.path.dirname(os.path.abspath(f)): |
|
348 shutil.copy2(f, outdir) |
|
349 for f in glob(os.path.join(sourcedir, '*')): |
|
350 if os.path.isdir(f): |
|
351 os.makedirs(f) |
|
352 else: |
|
353 destfile = os.path.abspath(os.path.join(outdir, f)) |
|
354 shutil.copy2(f, outdir) |
|
355 |
|
356 outbasename = os.path.splitext(outfilename)[0] |
|
357 outfilename = outbasename + '.tex' |
|
358 |
|
359 outfile = codecs.open(outfilename, 'w', 'utf-8') |
|
360 preamble(outfile) |
|
361 render_abstract(outfile, abstract, start_page=start_page) |
|
362 outfile.write(ur'\end{document}' + u'\n') |
|
363 outfile.close() |
|
364 |
|
365 tex2pdf(outbasename, remove_tex=False) |
|
366 abstract.num_pages = count_pages(outbasename + '.pdf') |
|
367 |
|
368 # Generate the tex file again, now that we know the length. |
|
369 outfile = codecs.open(outfilename, 'w', 'utf-8') |
|
370 preamble(outfile) |
|
371 render_abstract(outfile, abstract, start_page=start_page) |
|
372 outfile.write(ur'\end{document}' + u'\n') |
|
373 outfile.close() |
|
374 |
|
375 return tex2pdf(os.path.splitext(outfilename)[0], remove_tex=False) |
|
376 |
|
377 |
|
378 ############################################################################## |
|
379 # Code for using outside of the webapp. |
|
380 ############################################################################## |
|
381 |
|
382 def mk_zipfile(): |
|
383 """ Generates a zipfile with the required files to build an |
|
384 abstract. |
|
385 """ |
|
386 from zipfile import ZipFile |
|
387 zipfilename = os.path.join(os.path.dirname(__file__), |
|
388 'mk_scipy_paper.zip') |
|
389 z = ZipFile(zipfilename, 'w') |
|
390 for filename in glob(os.path.join(sourcedir, '*')): |
|
391 if not os.path.isdir(filename): |
|
392 z.write(filename, arcname='source/' + os.path.basename(filename)) |
|
393 z.write(__file__, arcname='mk_scipy_paper.py') |
|
394 return zipfilename |
|
395 |
|
396 class Bunch(dict): |
|
397 def __init__(self, **kwargs): |
|
398 dict.__init__(self, **kwargs) |
|
399 self.__dict__ = self |
|
400 |
|
401 def __reprt(self): |
|
402 return repr(self.__dict__) |
|
403 |
|
404 author_like = Bunch( |
|
405 first_names='XX', |
|
406 surname='XXX', |
|
407 email_address='xxx@XXX', |
|
408 institution='XXX', |
|
409 address='XXX', |
|
410 country='XXX' |
|
411 ) |
|
412 |
|
413 |
|
414 abstract_like = Bunch( |
|
415 paper_abstract='An abstract', |
|
416 authors=[author_like, ], |
|
417 title='', |
|
418 ) |
|
419 |
|
420 if __name__ == '__main__': |
|
421 from optparse import OptionParser |
|
422 parser = OptionParser() |
|
423 parser.add_option("-o", "--output", dest="outfilename", |
|
424 default="./paper.pdf", |
|
425 help="output to FILE", metavar="FILE") |
|
426 parser.usage = """%prog [options] rst_file [data_file] |
|
427 Compiles a given rest file and information file to pdf for the SciPy |
|
428 proceedings. |
|
429 """ |
|
430 |
|
431 (options, args) = parser.parse_args() |
|
432 if not len(args) in (1, 2): |
|
433 print "One or two arguments required: the input rest file and " \ |
|
434 "the input data file" |
|
435 print '' |
|
436 parser.print_help() |
|
437 sys.exit(1) |
|
438 infile = args[0] |
|
439 if len(args)==1: |
|
440 data_file = 'data.py' |
|
441 if os.path.exists('data.py'): |
|
442 print "Using data file 'data.py'" |
|
443 else: |
|
444 print "Generating the data file and storing it in data.py" |
|
445 print "You will need to edit this file to add title, author " \ |
|
446 "information, and abstract." |
|
447 abstract = abstract_like |
|
448 file('data.py', 'w').write(repr(abstract)) |
|
449 elif len(args)==2: |
|
450 data_file = args[1] |
|
451 |
|
452 abstract = Bunch( **eval(file(data_file).read())) |
|
453 abstract.authors = [Bunch(**a) for a in abstract.authors] |
|
454 |
|
455 abstract['summary'] = u'' |
|
456 abstract['paper_text'] = file(infile).read().decode('utf-8') |
|
457 |
|
458 outfilename = options.outfilename |
|
459 |
|
460 mk_abstract_preview(abstract, options.outfilename, |
|
461 os.path.dirname(options.outfilename)) |
|
462 # Ugly, but I don't want to wait on the thread. |
|
463 sys.exit() |
|