##// END OF EJS Templates
Use our own find_cmd instead of shutil.which
Thomas Kluyver -
Show More
@@ -1,151 +1,147 b''
1 """Export to PDF via latex"""
1 """Export to PDF via latex"""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 import subprocess
6 import subprocess
7 import os
7 import os
8 import sys
8 import sys
9 import shutil
10
9
10 from IPython.utils.process import find_cmd
11 from IPython.utils.traitlets import Integer, List, Bool, Instance
11 from IPython.utils.traitlets import Integer, List, Bool, Instance
12 from IPython.utils.tempdir import TemporaryWorkingDirectory
12 from IPython.utils.tempdir import TemporaryWorkingDirectory
13 from .latex import LatexExporter
13 from .latex import LatexExporter
14
14
15
15
16 class PDFExporter(LatexExporter):
16 class PDFExporter(LatexExporter):
17 """Writer designed to write to PDF files"""
17 """Writer designed to write to PDF files"""
18
18
19 latex_count = Integer(3, config=True,
19 latex_count = Integer(3, config=True,
20 help="How many times latex will be called."
20 help="How many times latex will be called."
21 )
21 )
22
22
23 latex_command = List([u"pdflatex", u"{filename}"], config=True,
23 latex_command = List([u"pdflatex", u"{filename}"], config=True,
24 help="Shell command used to compile latex."
24 help="Shell command used to compile latex."
25 )
25 )
26
26
27 bib_command = List([u"bibtex", u"{filename}"], config=True,
27 bib_command = List([u"bibtex", u"{filename}"], config=True,
28 help="Shell command used to run bibtex."
28 help="Shell command used to run bibtex."
29 )
29 )
30
30
31 verbose = Bool(False, config=True,
31 verbose = Bool(False, config=True,
32 help="Whether to display the output of latex commands."
32 help="Whether to display the output of latex commands."
33 )
33 )
34
34
35 temp_file_exts = List(['.aux', '.bbl', '.blg', '.idx', '.log', '.out'], config=True,
35 temp_file_exts = List(['.aux', '.bbl', '.blg', '.idx', '.log', '.out'], config=True,
36 help="File extensions of temp files to remove after running."
36 help="File extensions of temp files to remove after running."
37 )
37 )
38
38
39 writer = Instance("IPython.nbconvert.writers.FilesWriter", args=())
39 writer = Instance("IPython.nbconvert.writers.FilesWriter", args=())
40
40
41 def valid_on_path(self, command):
42 """Ensure the given command exists in the OS PATH."""
43 if (shutil.which(command)==None) :
44 raise FileNotFoundError("NBConvert requires this command to be on the System PATH: "+str(command))
45
46 def run_command(self, command_list, filename, count, log_function):
41 def run_command(self, command_list, filename, count, log_function):
47 """Run command_list count times.
42 """Run command_list count times.
48
43
49 Parameters
44 Parameters
50 ----------
45 ----------
51 command_list : list
46 command_list : list
52 A list of args to provide to Popen. Each element of this
47 A list of args to provide to Popen. Each element of this
53 list will be interpolated with the filename to convert.
48 list will be interpolated with the filename to convert.
54 filename : unicode
49 filename : unicode
55 The name of the file to convert.
50 The name of the file to convert.
56 count : int
51 count : int
57 How many times to run the command.
52 How many times to run the command.
58
53
59 Returns
54 Returns
60 -------
55 -------
61 success : bool
56 success : bool
62 A boolean indicating if the command was successful (True)
57 A boolean indicating if the command was successful (True)
63 or failed (False).
58 or failed (False).
64 """
59 """
65 command = [c.format(filename=filename) for c in command_list]
60 command = [c.format(filename=filename) for c in command_list]
66
61
67 # On windows with python 2.x there is a bug in subprocess.Popen and
62 # On windows with python 2.x there is a bug in subprocess.Popen and
68 # unicode commands are not supported
63 # unicode commands are not supported
69 if sys.platform == 'win32' and sys.version_info < (3,0):
64 if sys.platform == 'win32' and sys.version_info < (3,0):
70 #We must use cp1252 encoding for calling subprocess.Popen
65 #We must use cp1252 encoding for calling subprocess.Popen
71 #Note that sys.stdin.encoding and encoding.DEFAULT_ENCODING
66 #Note that sys.stdin.encoding and encoding.DEFAULT_ENCODING
72 # could be different (cp437 in case of dos console)
67 # could be different (cp437 in case of dos console)
73 command = [c.encode('cp1252') for c in command]
68 command = [c.encode('cp1252') for c in command]
74
69
75 self.valid_on_path(command_list[0])
70 # This will throw a clearer error if the command is not found
71 find_cmd(command_list[0])
76
72
77 times = 'time' if count == 1 else 'times'
73 times = 'time' if count == 1 else 'times'
78 self.log.info("Running %s %i %s: %s", command_list[0], count, times, command)
74 self.log.info("Running %s %i %s: %s", command_list[0], count, times, command)
79 with open(os.devnull, 'rb') as null:
75 with open(os.devnull, 'rb') as null:
80 stdout = subprocess.PIPE if not self.verbose else None
76 stdout = subprocess.PIPE if not self.verbose else None
81 for index in range(count):
77 for index in range(count):
82 p = subprocess.Popen(command, stdout=stdout, stdin=null)
78 p = subprocess.Popen(command, stdout=stdout, stdin=null)
83 out, err = p.communicate()
79 out, err = p.communicate()
84 if p.returncode:
80 if p.returncode:
85 if self.verbose:
81 if self.verbose:
86 # verbose means I didn't capture stdout with PIPE,
82 # verbose means I didn't capture stdout with PIPE,
87 # so it's already been displayed and `out` is None.
83 # so it's already been displayed and `out` is None.
88 out = u''
84 out = u''
89 else:
85 else:
90 out = out.decode('utf-8', 'replace')
86 out = out.decode('utf-8', 'replace')
91 log_function(command, out)
87 log_function(command, out)
92 return False # failure
88 return False # failure
93 return True # success
89 return True # success
94
90
95 def run_latex(self, filename):
91 def run_latex(self, filename):
96 """Run pdflatex self.latex_count times."""
92 """Run pdflatex self.latex_count times."""
97
93
98 def log_error(command, out):
94 def log_error(command, out):
99 self.log.critical(u"%s failed: %s\n%s", command[0], command, out)
95 self.log.critical(u"%s failed: %s\n%s", command[0], command, out)
100
96
101 return self.run_command(self.latex_command, filename,
97 return self.run_command(self.latex_command, filename,
102 self.latex_count, log_error)
98 self.latex_count, log_error)
103
99
104 def run_bib(self, filename):
100 def run_bib(self, filename):
105 """Run bibtex self.latex_count times."""
101 """Run bibtex self.latex_count times."""
106 filename = os.path.splitext(filename)[0]
102 filename = os.path.splitext(filename)[0]
107
103
108 def log_error(command, out):
104 def log_error(command, out):
109 self.log.warn('%s had problems, most likely because there were no citations',
105 self.log.warn('%s had problems, most likely because there were no citations',
110 command[0])
106 command[0])
111 self.log.debug(u"%s output: %s\n%s", command[0], command, out)
107 self.log.debug(u"%s output: %s\n%s", command[0], command, out)
112
108
113 return self.run_command(self.bib_command, filename, 1, log_error)
109 return self.run_command(self.bib_command, filename, 1, log_error)
114
110
115 def clean_temp_files(self, filename):
111 def clean_temp_files(self, filename):
116 """Remove temporary files created by pdflatex/bibtex."""
112 """Remove temporary files created by pdflatex/bibtex."""
117 self.log.info("Removing temporary LaTeX files")
113 self.log.info("Removing temporary LaTeX files")
118 filename = os.path.splitext(filename)[0]
114 filename = os.path.splitext(filename)[0]
119 for ext in self.temp_file_exts:
115 for ext in self.temp_file_exts:
120 try:
116 try:
121 os.remove(filename+ext)
117 os.remove(filename+ext)
122 except OSError:
118 except OSError:
123 pass
119 pass
124
120
125 def from_notebook_node(self, nb, resources=None, **kw):
121 def from_notebook_node(self, nb, resources=None, **kw):
126 latex, resources = super(PDFExporter, self).from_notebook_node(
122 latex, resources = super(PDFExporter, self).from_notebook_node(
127 nb, resources=resources, **kw
123 nb, resources=resources, **kw
128 )
124 )
129 with TemporaryWorkingDirectory() as td:
125 with TemporaryWorkingDirectory() as td:
130 notebook_name = "notebook"
126 notebook_name = "notebook"
131 tex_file = self.writer.write(latex, resources, notebook_name=notebook_name)
127 tex_file = self.writer.write(latex, resources, notebook_name=notebook_name)
132 self.log.info("Building PDF")
128 self.log.info("Building PDF")
133 rc = self.run_latex(tex_file)
129 rc = self.run_latex(tex_file)
134 if not rc:
130 if not rc:
135 rc = self.run_bib(tex_file)
131 rc = self.run_bib(tex_file)
136 if not rc:
132 if not rc:
137 rc = self.run_latex(tex_file)
133 rc = self.run_latex(tex_file)
138
134
139 pdf_file = notebook_name + '.pdf'
135 pdf_file = notebook_name + '.pdf'
140 if not os.path.isfile(pdf_file):
136 if not os.path.isfile(pdf_file):
141 raise RuntimeError("PDF creating failed")
137 raise RuntimeError("PDF creating failed")
142 self.log.info('PDF successfully created')
138 self.log.info('PDF successfully created')
143 with open(pdf_file, 'rb') as f:
139 with open(pdf_file, 'rb') as f:
144 pdf_data = f.read()
140 pdf_data = f.read()
145
141
146 # convert output extension to pdf
142 # convert output extension to pdf
147 # the writer above required it to be tex
143 # the writer above required it to be tex
148 resources['output_extension'] = 'pdf'
144 resources['output_extension'] = 'pdf'
149
145
150 return pdf_data, resources
146 return pdf_data, resources
151
147
General Comments 0
You need to be logged in to leave comments. Login now