##// END OF EJS Templates
Adding better logic to the PDF postprocessor.
Brian E. Granger -
Show More
@@ -1,60 +1,130 b''
1 1 """
2 2 Contains writer for writing nbconvert output to PDF.
3 3 """
4 4 #-----------------------------------------------------------------------------
5 5 #Copyright (c) 2013, the IPython Development Team.
6 6 #
7 7 #Distributed under the terms of the Modified BSD License.
8 8 #
9 9 #The full license is in the file COPYING.txt, distributed with this software.
10 10 #-----------------------------------------------------------------------------
11 11
12 12 #-----------------------------------------------------------------------------
13 13 # Imports
14 14 #-----------------------------------------------------------------------------
15 15
16 16 import subprocess
17 17 import os
18 18
19 19 from IPython.utils.traitlets import Integer, List, Bool
20 20
21 21 from .base import PostProcessorBase
22 22
23 23 #-----------------------------------------------------------------------------
24 24 # Classes
25 25 #-----------------------------------------------------------------------------
26 26 class PDFPostProcessor(PostProcessorBase):
27 27 """Writer designed to write to PDF files"""
28 28
29 29 iteration_count = Integer(3, config=True, help="""
30 30 How many times pdflatex will be called.
31 31 """)
32 32
33 command = List(["pdflatex", "{filename}"], config=True, help="""
33 pdflatex_command = List(["pdflatex", "{filename}"], config=True, help="""
34 34 Shell command used to compile PDF.""")
35 35
36 bibtex_command = List(["bibtex", "{filename}"], config=True, help="""
37 Shell command used to run bibtex.""")
38
36 39 verbose = Bool(False, config=True, help="""
37 40 Whether or not to display the output of the compile call.
38 41 """)
39 42
40 def postprocess(self, input):
41 """
42 Consume and write Jinja output a PDF.
43 See files.py for more...
43 temp_file_exts = List(['.aux', '.bbl', '.blg', '.idx', '.log', '.out'],
44 config=True, help="""
45 Filename extensions of temp files to remove after running
46 """)
47
48 def run_command(self, command_list, filename, count, log_function):
49 """Run pdflatex or bibtext count times.
50
51 Parameters
52 ----------
53 command_list : list
54 A list of args to provide to Popen. Each element of this
55 list will be interpolated with the filename to convert.
56 filename : unicode
57 The name of the file to convert.
58 count : int
59 How many times to run the command.
60
61 Returns
62 -------
63 continue : bool
64 A boolean indicating if the command was successful (True)
65 or failed (False).
44 66 """
45 command = [c.format(filename=input) for c in self.command]
46 self.log.info("Building PDF: %s", command)
67 command = [c.format(filename=filename) for c in command_list]
68 times = 'time' if count == 1 else 'times'
69 self.log.info("Running %s %i %s: %s", command_list[0], count, times, command)
47 70 with open(os.devnull, 'rb') as null:
48 71 stdout = subprocess.PIPE if not self.verbose else None
49 for index in range(self.iteration_count):
72 for index in range(count):
50 73 p = subprocess.Popen(command, stdout=stdout, stdin=null)
51 74 out, err = p.communicate()
52 75 if p.returncode:
53 76 if self.verbose:
54 77 # verbose means I didn't capture stdout with PIPE,
55 78 # so it's already been displayed and `out` is None.
56 79 out = u''
57 80 else:
58 81 out = out.decode('utf-8', 'replace')
59 self.log.critical(u"PDF conversion failed: %s\n%s", command, out)
82 log_function(command, out)
83 return False # failure
84 return True # success
85
86 def run_pdflatex(self, filename):
87 """Run pdflatex self.iteration_count times."""
88
89 def log_error(command, out):
90 self.log.critical(u"pdflatex failed: %s\n%s", command, out)
91
92 return self.run_command(self.pdflatex_command, filename,
93 self.iteration_count, log_error)
94
95 def run_bibtex(self, filename):
96 """Run bibtex self.iteration_count times."""
97 filename = filename.rstrip('.tex')
98
99 def log_error(command, out):
100 self.log.warn('bibtex had problems, most likely because there were no citations')
101 self.log.debug(u"bibtex output: %s\n%s", command, out)
102
103 return self.run_command(self.bibtex_command, filename, 1, log_error)
104
105 def clean_temp_files(self, filename):
106 """Remove temporary files created by pdflatex/bibtext."""
107 self.log.info("Removing temporary LaTeX files")
108 filename = filename.strip('.tex')
109 for ext in self.temp_file_exts:
110 try:
111 os.remove(filename+ext)
112 except OSError:
113 pass
114
115 def postprocess(self, filename):
116 """Build a PDF by running pdflatex and bibtex"""
117 self.log.info("Building PDF")
118 cont = self.run_pdflatex(filename)
119 if cont:
120 cont = self.run_bibtex(filename)
121 else:
122 self.clean_temp_files(filename)
60 123 return
124 if cont:
125 cont = self.run_pdflatex(filename)
126 self.clean_temp_files(filename)
127 if os.path.isfile(filename.rstrip('.tex')+'.pdf'):
128 self.log.info('PDF successfully created')
129 return
130
@@ -1,64 +1,68 b''
1 1 """
2 2 Module with tests for the PDF post-processor
3 3 """
4 4
5 5 #-----------------------------------------------------------------------------
6 6 # Copyright (c) 2013, the IPython Development Team.
7 7 #
8 8 # Distributed under the terms of the Modified BSD License.
9 9 #
10 10 # The full license is in the file COPYING.txt, distributed with this software.
11 11 #-----------------------------------------------------------------------------
12 12
13 13 #-----------------------------------------------------------------------------
14 14 # Imports
15 15 #-----------------------------------------------------------------------------
16 16
17 17 import os
18 18
19 19 from IPython.testing import decorators as dec
20 20
21 21 from ...tests.base import TestsBase
22 22 from ..pdf import PDFPostProcessor
23 23
24 24
25 25 #-----------------------------------------------------------------------------
26 26 # Constants
27 27 #-----------------------------------------------------------------------------
28 28
29 29 HELLO_WORLD = r"""% hello.tex - Our first LaTeX example!
30 30 \documentclass{article}
31 31 \begin{document}
32 32 Hello World!
33 33 \end{document}"""
34 34
35 35
36 36 #-----------------------------------------------------------------------------
37 37 # Class
38 38 #-----------------------------------------------------------------------------
39 39
40 40 class TestPDF(TestsBase):
41 41 """Contains test functions for pdf.py"""
42 42
43 43
44 44 def test_constructor(self):
45 45 """Can a PDFPostProcessor be constructed?"""
46 46 PDFPostProcessor()
47 47
48 48
49 49 @dec.onlyif_cmds_exist('pdflatex')
50 50 def test_pdf(self):
51 51 """Can a PDF be made using the PDFPostProcessor?"""
52 52
53 53 # Work in a temporary directory with hello world latex in it.
54 54 with self.create_temp_cwd():
55 55 with open('a.tex', 'w') as f:
56 56 f.write(HELLO_WORLD)
57 57
58 58 # Construct post-processor
59 59 processor = PDFPostProcessor()
60 60 processor.verbose = False
61 61 processor('a.tex')
62 62
63 63 # Check that the PDF was created.
64 64 assert os.path.isfile('a.pdf')
65
66 # Make sure that temp files are cleaned up
67 for ext in processor.temp_file_exts:
68 assert not os.path.isfile('a'+ext)
General Comments 0
You need to be logged in to leave comments. Login now