project/scipycon/proceedings/booklet/mk_scipy_paper.py
changeset 94 87e77aa18610
child 96 178b89a3ca4f
equal deleted inserted replaced
93:e86755df35da 94:87e77aa18610
       
     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()