diff --git a/IPython/nbconvert/postprocessors/pdf.py b/IPython/nbconvert/postprocessors/pdf.py index 53062f3..da6f775 100644 --- a/IPython/nbconvert/postprocessors/pdf.py +++ b/IPython/nbconvert/postprocessors/pdf.py @@ -30,31 +30,101 @@ class PDFPostProcessor(PostProcessorBase): How many times pdflatex will be called. """) - command = List(["pdflatex", "{filename}"], config=True, help=""" + pdflatex_command = List(["pdflatex", "{filename}"], config=True, help=""" Shell command used to compile PDF.""") + bibtex_command = List(["bibtex", "{filename}"], config=True, help=""" + Shell command used to run bibtex.""") + verbose = Bool(False, config=True, help=""" Whether or not to display the output of the compile call. """) - def postprocess(self, input): - """ - Consume and write Jinja output a PDF. - See files.py for more... - """ - command = [c.format(filename=input) for c in self.command] - self.log.info("Building PDF: %s", command) - with open(os.devnull, 'rb') as null: - stdout = subprocess.PIPE if not self.verbose else None - for index in range(self.iteration_count): - p = subprocess.Popen(command, stdout=stdout, stdin=null) - out, err = p.communicate() - if p.returncode: - if self.verbose: - # verbose means I didn't capture stdout with PIPE, - # so it's already been displayed and `out` is None. - out = u'' - else: - out = out.decode('utf-8', 'replace') - self.log.critical(u"PDF conversion failed: %s\n%s", command, out) - return + temp_file_exts = List(['.aux', '.bbl', '.blg', '.idx', '.log', '.out'], + config=True, help=""" + Filename extensions of temp files to remove after running + """) + + def run_command(self, command_list, filename, count, log_function): + """Run pdflatex or bibtext count times. + + Parameters + ---------- + command_list : list + A list of args to provide to Popen. Each element of this + list will be interpolated with the filename to convert. + filename : unicode + The name of the file to convert. + count : int + How many times to run the command. + + Returns + ------- + continue : bool + A boolean indicating if the command was successful (True) + or failed (False). + """ + command = [c.format(filename=filename) for c in command_list] + times = 'time' if count == 1 else 'times' + self.log.info("Running %s %i %s: %s", command_list[0], count, times, command) + with open(os.devnull, 'rb') as null: + stdout = subprocess.PIPE if not self.verbose else None + for index in range(count): + p = subprocess.Popen(command, stdout=stdout, stdin=null) + out, err = p.communicate() + if p.returncode: + if self.verbose: + # verbose means I didn't capture stdout with PIPE, + # so it's already been displayed and `out` is None. + out = u'' + else: + out = out.decode('utf-8', 'replace') + log_function(command, out) + return False # failure + return True # success + + def run_pdflatex(self, filename): + """Run pdflatex self.iteration_count times.""" + + def log_error(command, out): + self.log.critical(u"pdflatex failed: %s\n%s", command, out) + + return self.run_command(self.pdflatex_command, filename, + self.iteration_count, log_error) + + def run_bibtex(self, filename): + """Run bibtex self.iteration_count times.""" + filename = filename.rstrip('.tex') + + def log_error(command, out): + self.log.warn('bibtex had problems, most likely because there were no citations') + self.log.debug(u"bibtex output: %s\n%s", command, out) + + return self.run_command(self.bibtex_command, filename, 1, log_error) + + def clean_temp_files(self, filename): + """Remove temporary files created by pdflatex/bibtext.""" + self.log.info("Removing temporary LaTeX files") + filename = filename.strip('.tex') + for ext in self.temp_file_exts: + try: + os.remove(filename+ext) + except OSError: + pass + + def postprocess(self, filename): + """Build a PDF by running pdflatex and bibtex""" + self.log.info("Building PDF") + cont = self.run_pdflatex(filename) + if cont: + cont = self.run_bibtex(filename) + else: + self.clean_temp_files(filename) + return + if cont: + cont = self.run_pdflatex(filename) + self.clean_temp_files(filename) + if os.path.isfile(filename.rstrip('.tex')+'.pdf'): + self.log.info('PDF successfully created') + return + diff --git a/IPython/nbconvert/postprocessors/tests/test_pdf.py b/IPython/nbconvert/postprocessors/tests/test_pdf.py index c163928..cc650d6 100644 --- a/IPython/nbconvert/postprocessors/tests/test_pdf.py +++ b/IPython/nbconvert/postprocessors/tests/test_pdf.py @@ -62,3 +62,7 @@ class TestPDF(TestsBase): # Check that the PDF was created. assert os.path.isfile('a.pdf') + + # Make sure that temp files are cleaned up + for ext in processor.temp_file_exts: + assert not os.path.isfile('a'+ext)