##// END OF EJS Templates
Adding better logic to the PDF postprocessor.
Brian E. Granger -
Show More
@@ -1,60 +1,130 b''
1 """
1 """
2 Contains writer for writing nbconvert output to PDF.
2 Contains writer for writing nbconvert output to PDF.
3 """
3 """
4 #-----------------------------------------------------------------------------
4 #-----------------------------------------------------------------------------
5 #Copyright (c) 2013, the IPython Development Team.
5 #Copyright (c) 2013, the IPython Development Team.
6 #
6 #
7 #Distributed under the terms of the Modified BSD License.
7 #Distributed under the terms of the Modified BSD License.
8 #
8 #
9 #The full license is in the file COPYING.txt, distributed with this software.
9 #The full license is in the file COPYING.txt, distributed with this software.
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11
11
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 # Imports
13 # Imports
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15
15
16 import subprocess
16 import subprocess
17 import os
17 import os
18
18
19 from IPython.utils.traitlets import Integer, List, Bool
19 from IPython.utils.traitlets import Integer, List, Bool
20
20
21 from .base import PostProcessorBase
21 from .base import PostProcessorBase
22
22
23 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
24 # Classes
24 # Classes
25 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
26 class PDFPostProcessor(PostProcessorBase):
26 class PDFPostProcessor(PostProcessorBase):
27 """Writer designed to write to PDF files"""
27 """Writer designed to write to PDF files"""
28
28
29 iteration_count = Integer(3, config=True, help="""
29 iteration_count = Integer(3, config=True, help="""
30 How many times pdflatex will be called.
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 Shell command used to compile PDF.""")
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 verbose = Bool(False, config=True, help="""
39 verbose = Bool(False, config=True, help="""
37 Whether or not to display the output of the compile call.
40 Whether or not to display the output of the compile call.
38 """)
41 """)
39
42
40 def postprocess(self, input):
43 temp_file_exts = List(['.aux', '.bbl', '.blg', '.idx', '.log', '.out'],
41 """
44 config=True, help="""
42 Consume and write Jinja output a PDF.
45 Filename extensions of temp files to remove after running
43 See files.py for more...
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]
67 command = [c.format(filename=filename) for c in command_list]
46 self.log.info("Building PDF: %s", command)
68 times = 'time' if count == 1 else 'times'
69 self.log.info("Running %s %i %s: %s", command_list[0], count, times, command)
47 with open(os.devnull, 'rb') as null:
70 with open(os.devnull, 'rb') as null:
48 stdout = subprocess.PIPE if not self.verbose else None
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 p = subprocess.Popen(command, stdout=stdout, stdin=null)
73 p = subprocess.Popen(command, stdout=stdout, stdin=null)
51 out, err = p.communicate()
74 out, err = p.communicate()
52 if p.returncode:
75 if p.returncode:
53 if self.verbose:
76 if self.verbose:
54 # verbose means I didn't capture stdout with PIPE,
77 # verbose means I didn't capture stdout with PIPE,
55 # so it's already been displayed and `out` is None.
78 # so it's already been displayed and `out` is None.
56 out = u''
79 out = u''
57 else:
80 else:
58 out = out.decode('utf-8', 'replace')
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 return
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 Module with tests for the PDF post-processor
2 Module with tests for the PDF post-processor
3 """
3 """
4
4
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6 # Copyright (c) 2013, the IPython Development Team.
6 # Copyright (c) 2013, the IPython Development Team.
7 #
7 #
8 # Distributed under the terms of the Modified BSD License.
8 # Distributed under the terms of the Modified BSD License.
9 #
9 #
10 # The full license is in the file COPYING.txt, distributed with this software.
10 # The full license is in the file COPYING.txt, distributed with this software.
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16
16
17 import os
17 import os
18
18
19 from IPython.testing import decorators as dec
19 from IPython.testing import decorators as dec
20
20
21 from ...tests.base import TestsBase
21 from ...tests.base import TestsBase
22 from ..pdf import PDFPostProcessor
22 from ..pdf import PDFPostProcessor
23
23
24
24
25 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
26 # Constants
26 # Constants
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28
28
29 HELLO_WORLD = r"""% hello.tex - Our first LaTeX example!
29 HELLO_WORLD = r"""% hello.tex - Our first LaTeX example!
30 \documentclass{article}
30 \documentclass{article}
31 \begin{document}
31 \begin{document}
32 Hello World!
32 Hello World!
33 \end{document}"""
33 \end{document}"""
34
34
35
35
36 #-----------------------------------------------------------------------------
36 #-----------------------------------------------------------------------------
37 # Class
37 # Class
38 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
39
39
40 class TestPDF(TestsBase):
40 class TestPDF(TestsBase):
41 """Contains test functions for pdf.py"""
41 """Contains test functions for pdf.py"""
42
42
43
43
44 def test_constructor(self):
44 def test_constructor(self):
45 """Can a PDFPostProcessor be constructed?"""
45 """Can a PDFPostProcessor be constructed?"""
46 PDFPostProcessor()
46 PDFPostProcessor()
47
47
48
48
49 @dec.onlyif_cmds_exist('pdflatex')
49 @dec.onlyif_cmds_exist('pdflatex')
50 def test_pdf(self):
50 def test_pdf(self):
51 """Can a PDF be made using the PDFPostProcessor?"""
51 """Can a PDF be made using the PDFPostProcessor?"""
52
52
53 # Work in a temporary directory with hello world latex in it.
53 # Work in a temporary directory with hello world latex in it.
54 with self.create_temp_cwd():
54 with self.create_temp_cwd():
55 with open('a.tex', 'w') as f:
55 with open('a.tex', 'w') as f:
56 f.write(HELLO_WORLD)
56 f.write(HELLO_WORLD)
57
57
58 # Construct post-processor
58 # Construct post-processor
59 processor = PDFPostProcessor()
59 processor = PDFPostProcessor()
60 processor.verbose = False
60 processor.verbose = False
61 processor('a.tex')
61 processor('a.tex')
62
62
63 # Check that the PDF was created.
63 # Check that the PDF was created.
64 assert os.path.isfile('a.pdf')
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