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 | """) | |
44 | """ |
|
47 | ||
45 | command = [c.format(filename=input) for c in self.command] |
|
48 | def run_command(self, command_list, filename, count, log_function): | |
46 | self.log.info("Building PDF: %s", command) |
|
49 | """Run pdflatex or bibtext count times. | |
47 | with open(os.devnull, 'rb') as null: |
|
50 | ||
48 | stdout = subprocess.PIPE if not self.verbose else None |
|
51 | Parameters | |
49 | for index in range(self.iteration_count): |
|
52 | ---------- | |
50 | p = subprocess.Popen(command, stdout=stdout, stdin=null) |
|
53 | command_list : list | |
51 | out, err = p.communicate() |
|
54 | A list of args to provide to Popen. Each element of this | |
52 | if p.returncode: |
|
55 | list will be interpolated with the filename to convert. | |
53 | if self.verbose: |
|
56 | filename : unicode | |
54 | # verbose means I didn't capture stdout with PIPE, |
|
57 | The name of the file to convert. | |
55 | # so it's already been displayed and `out` is None. |
|
58 | count : int | |
56 | out = u'' |
|
59 | How many times to run the command. | |
57 | else: |
|
60 | ||
58 | out = out.decode('utf-8', 'replace') |
|
61 | Returns | |
59 | self.log.critical(u"PDF conversion failed: %s\n%s", command, out) |
|
62 | ------- | |
60 | return |
|
63 | continue : bool | |
|
64 | A boolean indicating if the command was successful (True) | |||
|
65 | or failed (False). | |||
|
66 | """ | |||
|
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) | |||
|
70 | with open(os.devnull, 'rb') as null: | |||
|
71 | stdout = subprocess.PIPE if not self.verbose else None | |||
|
72 | for index in range(count): | |||
|
73 | p = subprocess.Popen(command, stdout=stdout, stdin=null) | |||
|
74 | out, err = p.communicate() | |||
|
75 | if p.returncode: | |||
|
76 | if self.verbose: | |||
|
77 | # verbose means I didn't capture stdout with PIPE, | |||
|
78 | # so it's already been displayed and `out` is None. | |||
|
79 | out = u'' | |||
|
80 | else: | |||
|
81 | out = out.decode('utf-8', 'replace') | |||
|
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) | |||
|
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