##// END OF EJS Templates
Add preamble support for fully self-contained output TeX files.
Fernando Perez -
Show More
@@ -0,0 +1,89 b''
1 %% This is the automatic preamble used by IPython. Note that it does *not*
2 %% include a documentclass declaration, that is added at runtime to the overall
3 %% document.
4
5 \usepackage{amsmath}
6 \usepackage{amssymb}
7 \usepackage{graphicx}
8
9 % Slightly bigger margins than the latex defaults
10 \usepackage{geometry}
11 \geometry{verbose,tmargin=3cm,bmargin=3cm,lmargin=2.5cm,rmargin=2.5cm}
12
13 % Define a few colors for use in code, links and cell shading
14 \usepackage{color}
15 \definecolor{orange}{cmyk}{0,0.4,0.8,0.2}
16 \definecolor{darkorange}{rgb}{.71,0.21,0.01}
17 \definecolor{darkgreen}{rgb}{.12,.54,.11}
18 \definecolor{myteal}{rgb}{.26, .44, .56}
19 \definecolor{gray}{rgb}{0.45, 0.45, 0.45}
20 \definecolor{lightgray}{rgb}{.95, .95, .95}
21 \definecolor{inputbackground}{rgb}{.95, .95, .85}
22 \definecolor{outputbackground}{rgb}{.95, .95, .95}
23 \definecolor{traceback}{rgb}{1, .95, .95}
24
25 % Framed environments for code cells (inputs, outputs, errors, ...). The
26 % various uses of \unskip (or not) at the end were fine-tuned by hand, so don't
27 % randomly change them unless you're sure of the effect it will have.
28 \usepackage{framed}
29
30 % remove extraneous vertical space in boxes
31 \setlength\fboxsep{0pt}
32
33 % codecell is the whole input+output set of blocks that a Code cell can
34 % generate.
35 \newenvironment{codecell}{%
36 \def\FrameCommand{\vrule width 0.5pt \hspace{5pt}}%
37 \MakeFramed{\FrameRestore}}
38 {\unskip\endMakeFramed}
39
40 \newenvironment{codeinput}{%
41 \def\FrameCommand{\colorbox{inputbackground}}%
42 \MakeFramed{\advance\hsize-\width \FrameRestore}}
43 {\unskip\endMakeFramed}
44
45 \newenvironment{codeoutput}{%
46 \def\FrameCommand{\colorbox{outputbackground}}%
47 \MakeFramed{\advance\hsize-\width \FrameRestore}}
48 {\unskip\medskip\endMakeFramed}
49
50 \newenvironment{traceback}{%
51 \def\FrameCommand{\colorbox{traceback}}%
52 \MakeFramed{\advance\hsize-\width \FrameRestore}}
53 {\endMakeFramed}
54
55 % The hyperref package gives us a pdf with properly built
56 % internal navigation ('pdf bookmarks' for the table of contents,
57 % internal cross-reference links, web links for URLs, etc.)
58 \usepackage{hyperref}
59 \hypersetup{
60 breaklinks=true, % so long urls are correctly broken across lines
61 colorlinks=true,
62 urlcolor=blue,
63 linkcolor=darkorange,
64 citecolor=darkgreen,
65 }
66
67 % Use and configure listings package for nicely formatted code
68 \usepackage{listings}
69 \lstset{
70 language=python,
71 aboveskip=\smallskipamount,
72 belowskip=\smallskipamount,
73 %xleftmargin=3mm,
74 breaklines=true,
75 basicstyle=\small \ttfamily,
76 showstringspaces=false,
77 keywordstyle=\color{blue}\bfseries,
78 commentstyle=\color{myteal},
79 stringstyle=\color{darkgreen},
80 identifierstyle=\color{darkorange},
81 }
82
83 % hardcode size of all verbatim environments to be a bit smaller
84 \makeatletter
85 \g@addto@macro\@verbatim\small
86 \makeatother
87
88 % Prevent overflowing lines due to urls and other hard-to-break entities.
89 \sloppy
@@ -1,595 +1,640 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 """Convert IPython notebooks to other formats, such as ReST, and HTML.
2 """Convert IPython notebooks to other formats, such as ReST, and HTML.
3
3
4 Example:
4 Example:
5 ./nbconvert.py --format html file.ipynb
5 ./nbconvert.py --format html file.ipynb
6
6
7 Produces 'file.rst' and 'file.html', along with auto-generated figure files
7 Produces 'file.rst' and 'file.html', along with auto-generated figure files
8 called nb_figure_NN.png. To avoid the two-step process, ipynb -> rst -> html,
8 called nb_figure_NN.png. To avoid the two-step process, ipynb -> rst -> html,
9 use '--format quick-html' which will do ipynb -> html, but won't look as
9 use '--format quick-html' which will do ipynb -> html, but won't look as
10 pretty.
10 pretty.
11 """
11 """
12 from __future__ import print_function
12 from __future__ import print_function
13
13
14 import codecs
14 import codecs
15 import os
15 import os
16 import pprint
16 import pprint
17 import re
17 import re
18 import subprocess
18 import subprocess
19 import sys
19 import sys
20
20
21 from IPython.external import argparse
21 from IPython.external import argparse
22 from IPython.nbformat import current as nbformat
22 from IPython.nbformat import current as nbformat
23 from IPython.utils.text import indent
23 from IPython.utils.text import indent
24 from decorators import DocInherit
24 from decorators import DocInherit
25
25
26 def remove_ansi(src):
26 def remove_ansi(src):
27 """Strip all ANSI color escape sequences from input string.
27 """Strip all ANSI color escape sequences from input string.
28
28
29 Parameters
29 Parameters
30 ----------
30 ----------
31 src : string
31 src : string
32
32
33 Returns
33 Returns
34 -------
34 -------
35 string
35 string
36 """
36 """
37 return re.sub(r'\033\[(0|\d;\d\d)m', '', src)
37 return re.sub(r'\033\[(0|\d;\d\d)m', '', src)
38
38
39 # Pandoc-dependent code
39 # Pandoc-dependent code
40 def markdown2latex(src):
40 def markdown2latex(src):
41 """Convert a markdown string to LaTeX via pandoc.
41 """Convert a markdown string to LaTeX via pandoc.
42
42
43 This function will raise an error if pandoc is not installed.
43 This function will raise an error if pandoc is not installed.
44
44
45 Any error messages generated by pandoc are printed to stderr.
45 Any error messages generated by pandoc are printed to stderr.
46
46
47 Parameters
47 Parameters
48 ----------
48 ----------
49 src : string
49 src : string
50 Input string, assumed to be valid markdown.
50 Input string, assumed to be valid markdown.
51
51
52 Returns
52 Returns
53 -------
53 -------
54 out : string
54 out : string
55 Output as returned by pandoc.
55 Output as returned by pandoc.
56 """
56 """
57 p = subprocess.Popen('pandoc -f markdown -t latex'.split(),
57 p = subprocess.Popen('pandoc -f markdown -t latex'.split(),
58 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
58 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
59 out, err = p.communicate(src)
59 out, err = p.communicate(src)
60 if err:
60 if err:
61 print(err, file=sys.stderr)
61 print(err, file=sys.stderr)
62 #print('*'*20+'\n', out, '\n'+'*'*20) # dbg
62 #print('*'*20+'\n', out, '\n'+'*'*20) # dbg
63 return out
63 return out
64
64
65 # Cell converters
65 # Cell converters
66
66
67
67
68 def rst_directive(directive, text=''):
68 def rst_directive(directive, text=''):
69 out = [directive, '']
69 out = [directive, '']
70 if text:
70 if text:
71 out.extend([indent(text), ''])
71 out.extend([indent(text), ''])
72 return out
72 return out
73
73
74 # Converters for parts of a cell.
74 # Converters for parts of a cell.
75
75
76
76
77 class ConversionException(Exception):
77 class ConversionException(Exception):
78 pass
78 pass
79
79
80
80
81 class Converter(object):
81 class Converter(object):
82 default_encoding = 'utf-8'
82 default_encoding = 'utf-8'
83 extension = str()
83 extension = str()
84 figures_counter = 0
84 figures_counter = 0
85 infile = str()
85 infile = str()
86 infile_dir = str()
86 infile_dir = str()
87 infile_root = str()
87 infile_root = str()
88 files_dir = str()
88 files_dir = str()
89
89 with_preamble = True
90 user_preamble = None
91 output = str()
92
90 def __init__(self, infile):
93 def __init__(self, infile):
91 self.infile = infile
94 self.infile = infile
92 self.infile_dir = os.path.dirname(infile)
95 self.infile_dir = os.path.dirname(infile)
93 infile_root = os.path.splitext(infile)[0]
96 infile_root = os.path.splitext(infile)[0]
94 files_dir = infile_root + '_files'
97 files_dir = infile_root + '_files'
95 if not os.path.isdir(files_dir):
98 if not os.path.isdir(files_dir):
96 os.mkdir(files_dir)
99 os.mkdir(files_dir)
97 self.infile_root = infile_root
100 self.infile_root = infile_root
98 self.files_dir = files_dir
101 self.files_dir = files_dir
99
102
100 def dispatch(self, cell_type):
103 def dispatch(self, cell_type):
101 """return cell_type dependent render method, for example render_code
104 """return cell_type dependent render method, for example render_code
102 """
105 """
103 return getattr(self, 'render_' + cell_type, self.render_unknown)
106 return getattr(self, 'render_' + cell_type, self.render_unknown)
104
107
105 def convert(self):
108 def convert(self):
106 lines = []
109 lines = []
107 lines.extend(self.optional_header())
110 lines.extend(self.optional_header())
108 for worksheet in self.nb.worksheets:
111 for worksheet in self.nb.worksheets:
109 for cell in worksheet.cells:
112 for cell in worksheet.cells:
110 conv_fn = self.dispatch(cell.cell_type)
113 conv_fn = self.dispatch(cell.cell_type)
111 lines.extend(conv_fn(cell))
114 lines.extend(conv_fn(cell))
112 lines.append('')
115 lines.append('')
113 lines.extend(self.optional_footer())
116 lines.extend(self.optional_footer())
114 return '\n'.join(lines)
117 return '\n'.join(lines)
115
118
116 def render(self):
119 def render(self):
117 "read, convert, and save self.infile"
120 "read, convert, and save self.infile"
118 self.read()
121 self.read()
119 self.output = self.convert()
122 self.output = self.convert()
120 return self.save()
123 return self.save()
121
124
122 def read(self):
125 def read(self):
123 "read and parse notebook into NotebookNode called self.nb"
126 "read and parse notebook into NotebookNode called self.nb"
124 with open(self.infile) as f:
127 with open(self.infile) as f:
125 self.nb = nbformat.read(f, 'json')
128 self.nb = nbformat.read(f, 'json')
126
129
127 def save(self, infile=None, encoding=None):
130 def save(self, infile=None, encoding=None):
128 "read and parse notebook into self.nb"
131 "read and parse notebook into self.nb"
129 if infile is None:
132 if infile is None:
130 infile = os.path.splitext(self.infile)[0] + '.' + self.extension
133 infile = os.path.splitext(self.infile)[0] + '.' + self.extension
131 if encoding is None:
134 if encoding is None:
132 encoding = self.default_encoding
135 encoding = self.default_encoding
133 with open(infile, 'w') as f:
136 with open(infile, 'w') as f:
134 f.write(self.output.encode(encoding))
137 f.write(self.output.encode(encoding))
135 return infile
138 return infile
136
139
137 def optional_header(self):
140 def optional_header(self):
138 return []
141 return []
139
142
140 def optional_footer(self):
143 def optional_footer(self):
141 return []
144 return []
142
145
143 def _new_figure(self, data, fmt):
146 def _new_figure(self, data, fmt):
144 """Create a new figure file in the given format.
147 """Create a new figure file in the given format.
145
148
146 Returns a path relative to the input file.
149 Returns a path relative to the input file.
147 """
150 """
148 figname = '%s_fig_%02i.%s' % (self.infile_root,
151 figname = '%s_fig_%02i.%s' % (self.infile_root,
149 self.figures_counter, fmt)
152 self.figures_counter, fmt)
150 self.figures_counter += 1
153 self.figures_counter += 1
151 fullname = os.path.join(self.files_dir, figname)
154 fullname = os.path.join(self.files_dir, figname)
152
155
153 # Binary files are base64-encoded, SVG is already XML
156 # Binary files are base64-encoded, SVG is already XML
154 if fmt in ('png', 'jpg', 'pdf'):
157 if fmt in ('png', 'jpg', 'pdf'):
155 data = data.decode('base64')
158 data = data.decode('base64')
156 fopen = lambda fname: open(fname, 'wb')
159 fopen = lambda fname: open(fname, 'wb')
157 else:
160 else:
158 fopen = lambda fname: codecs.open(fname, 'wb', self.default_encoding)
161 fopen = lambda fname: codecs.open(fname, 'wb', self.default_encoding)
159
162
160 with fopen(fullname) as f:
163 with fopen(fullname) as f:
161 f.write(data)
164 f.write(data)
162
165
163 return fullname
166 return fullname
164
167
165 def render_heading(self, cell):
168 def render_heading(self, cell):
166 """convert a heading cell
169 """convert a heading cell
167
170
168 Returns list."""
171 Returns list."""
169 raise NotImplementedError
172 raise NotImplementedError
170
173
171 def render_code(self, cell):
174 def render_code(self, cell):
172 """Convert a code cell
175 """Convert a code cell
173
176
174 Returns list."""
177 Returns list."""
175 raise NotImplementedError
178 raise NotImplementedError
176
179
177 def render_markdown(self, cell):
180 def render_markdown(self, cell):
178 """convert a markdown cell
181 """convert a markdown cell
179
182
180 Returns list."""
183 Returns list."""
181 raise NotImplementedError
184 raise NotImplementedError
182
185
183 def render_pyout(self, output):
186 def render_pyout(self, output):
184 """convert pyout part of a code cell
187 """convert pyout part of a code cell
185
188
186 Returns list."""
189 Returns list."""
187 raise NotImplementedError
190 raise NotImplementedError
188
191
189
192
190 def render_pyerr(self, output):
193 def render_pyerr(self, output):
191 """convert pyerr part of a code cell
194 """convert pyerr part of a code cell
192
195
193 Returns list."""
196 Returns list."""
194 raise NotImplementedError
197 raise NotImplementedError
195
198
196 def _img_lines(self, img_file):
199 def _img_lines(self, img_file):
197 """Return list of lines to include an image file."""
200 """Return list of lines to include an image file."""
198 # Note: subclasses may choose to implement format-specific _FMT_lines
201 # Note: subclasses may choose to implement format-specific _FMT_lines
199 # methods if they so choose (FMT in {png, svg, jpg, pdf}).
202 # methods if they so choose (FMT in {png, svg, jpg, pdf}).
200 raise NotImplementedError
203 raise NotImplementedError
201
204
202 def render_display_data(self, output):
205 def render_display_data(self, output):
203 """convert display data from the output of a code cell
206 """convert display data from the output of a code cell
204
207
205 Returns list.
208 Returns list.
206 """
209 """
207 lines = []
210 lines = []
208
211
209 for fmt in ['png', 'svg', 'jpg', 'pdf']:
212 for fmt in ['png', 'svg', 'jpg', 'pdf']:
210 if fmt in output:
213 if fmt in output:
211 img_file = self._new_figure(output[fmt], fmt)
214 img_file = self._new_figure(output[fmt], fmt)
212 # Subclasses can have format-specific render functions (e.g.,
215 # Subclasses can have format-specific render functions (e.g.,
213 # latex has to auto-convert all SVG to PDF first).
216 # latex has to auto-convert all SVG to PDF first).
214 lines_fun = getattr(self, '_%s_lines' % fmt, None)
217 lines_fun = getattr(self, '_%s_lines' % fmt, None)
215 if not lines_fun:
218 if not lines_fun:
216 lines_fun = self._img_lines
219 lines_fun = self._img_lines
217 lines.extend(lines_fun(img_file))
220 lines.extend(lines_fun(img_file))
218
221
219 return lines
222 return lines
220
223
221 def render_stream(self, cell):
224 def render_stream(self, cell):
222 """convert stream part of a code cell
225 """convert stream part of a code cell
223
226
224 Returns list."""
227 Returns list."""
225 raise NotImplementedError
228 raise NotImplementedError
226
229
227 def render_plaintext(self, cell):
230 def render_plaintext(self, cell):
228 """convert plain text
231 """convert plain text
229
232
230 Returns list."""
233 Returns list."""
231 raise NotImplementedError
234 raise NotImplementedError
232
235
233 def render_unknown(self, cell):
236 def render_unknown(self, cell):
234 """Render cells of unkown type
237 """Render cells of unkown type
235
238
236 Returns list."""
239 Returns list."""
237 raise NotImplementedError
240 raise NotImplementedError
238
241
239
242
240 class ConverterRST(Converter):
243 class ConverterRST(Converter):
241 extension = 'rst'
244 extension = 'rst'
242 heading_level = {1: '=', 2: '-', 3: '`', 4: '\'', 5: '.', 6: '~'}
245 heading_level = {1: '=', 2: '-', 3: '`', 4: '\'', 5: '.', 6: '~'}
243
246
244 @DocInherit
247 @DocInherit
245 def render_heading(self, cell):
248 def render_heading(self, cell):
246 marker = self.heading_level[cell.level]
249 marker = self.heading_level[cell.level]
247 return ['{0}\n{1}\n'.format(cell.source, marker * len(cell.source))]
250 return ['{0}\n{1}\n'.format(cell.source, marker * len(cell.source))]
248
251
249 @DocInherit
252 @DocInherit
250 def render_code(self, cell):
253 def render_code(self, cell):
251 if not cell.input:
254 if not cell.input:
252 return []
255 return []
253
256
254 lines = ['In[%s]:' % cell.prompt_number, '']
257 lines = ['In[%s]:' % cell.prompt_number, '']
255 lines.extend(rst_directive('.. code:: python', cell.input))
258 lines.extend(rst_directive('.. code:: python', cell.input))
256
259
257 for output in cell.outputs:
260 for output in cell.outputs:
258 conv_fn = self.dispatch(output.output_type)
261 conv_fn = self.dispatch(output.output_type)
259 lines.extend(conv_fn(output))
262 lines.extend(conv_fn(output))
260
263
261 return lines
264 return lines
262
265
263 @DocInherit
266 @DocInherit
264 def render_markdown(self, cell):
267 def render_markdown(self, cell):
265 return [cell.source]
268 return [cell.source]
266
269
267 @DocInherit
270 @DocInherit
268 def render_plaintext(self, cell):
271 def render_plaintext(self, cell):
269 return [cell.source]
272 return [cell.source]
270
273
271 @DocInherit
274 @DocInherit
272 def render_pyout(self, output):
275 def render_pyout(self, output):
273 lines = ['Out[%s]:' % output.prompt_number, '']
276 lines = ['Out[%s]:' % output.prompt_number, '']
274
277
275 # output is a dictionary like object with type as a key
278 # output is a dictionary like object with type as a key
276 if 'latex' in output:
279 if 'latex' in output:
277 lines.extend(rst_directive('.. math::', output.latex))
280 lines.extend(rst_directive('.. math::', output.latex))
278
281
279 if 'text' in output:
282 if 'text' in output:
280 lines.extend(rst_directive('.. parsed-literal::', output.text))
283 lines.extend(rst_directive('.. parsed-literal::', output.text))
281
284
282 return lines
285 return lines
283
286
284 @DocInherit
287 @DocInherit
285 def _img_lines(self, img_file):
288 def _img_lines(self, img_file):
286 return ['.. image:: %s' % figfile, '']
289 return ['.. image:: %s' % img_file, '']
287
290
288 @DocInherit
291 @DocInherit
289 def render_stream(self, output):
292 def render_stream(self, output):
290 lines = []
293 lines = []
291
294
292 if 'text' in output:
295 if 'text' in output:
293 lines.extend(rst_directive('.. parsed-literal::', output.text))
296 lines.extend(rst_directive('.. parsed-literal::', output.text))
294
297
295 return lines
298 return lines
296
299
297 @DocInherit
300 @DocInherit
298 def render_unknown(self, cell):
301 def render_unknown(self, cell):
299 return rst_directive('.. warning:: Unknown cell') + [repr(cell)]
302 return rst_directive('.. warning:: Unknown cell') + [repr(cell)]
300
303
304
301 class ConverterQuickHTML(Converter):
305 class ConverterQuickHTML(Converter):
302 extension = 'html'
306 extension = 'html'
303
307
304 def optional_header(self):
308 def optional_header(self):
305 # XXX: inject the IPython standard CSS into here
309 # XXX: inject the IPython standard CSS into here
306 s = """<html>
310 s = """<html>
307 <head>
311 <head>
308 </head>
312 </head>
309
313
310 <body>
314 <body>
311 """
315 """
312 return s.splitlines()
316 return s.splitlines()
313
317
314 def optional_footer(self):
318 def optional_footer(self):
315 s = """</body>
319 s = """</body>
316 </html>
320 </html>
317 """
321 """
318 return s.splitlines()
322 return s.splitlines()
319
323
320 @DocInherit
324 @DocInherit
321 def render_heading(self, cell):
325 def render_heading(self, cell):
322 marker = cell.level
326 marker = cell.level
323 return ['<h{1}>\n {0}\n</h{1}>'.format(cell.source, marker)]
327 return ['<h{1}>\n {0}\n</h{1}>'.format(cell.source, marker)]
324
328
325 @DocInherit
329 @DocInherit
326 def render_code(self, cell):
330 def render_code(self, cell):
327 if not cell.input:
331 if not cell.input:
328 return []
332 return []
329
333
330 lines = ['<table>']
334 lines = ['<table>']
331 lines.append('<tr><td><tt>In [<b>%s</b>]:</tt></td><td><tt>' % cell.prompt_number)
335 lines.append('<tr><td><tt>In [<b>%s</b>]:</tt></td><td><tt>' % cell.prompt_number)
332 lines.append("<br>\n".join(cell.input.splitlines()))
336 lines.append("<br>\n".join(cell.input.splitlines()))
333 lines.append('</tt></td></tr>')
337 lines.append('</tt></td></tr>')
334
338
335 for output in cell.outputs:
339 for output in cell.outputs:
336 lines.append('<tr><td></td><td>')
340 lines.append('<tr><td></td><td>')
337 conv_fn = self.dispatch(output.output_type)
341 conv_fn = self.dispatch(output.output_type)
338 lines.extend(conv_fn(output))
342 lines.extend(conv_fn(output))
339 lines.append('</td></tr>')
343 lines.append('</td></tr>')
340
344
341 lines.append('</table>')
345 lines.append('</table>')
342 return lines
346 return lines
343
347
344 @DocInherit
348 @DocInherit
345 def render_markdown(self, cell):
349 def render_markdown(self, cell):
346 return ["<pre>"+cell.source+"</pre>"]
350 return ["<pre>"+cell.source+"</pre>"]
347
351
348 @DocInherit
352 @DocInherit
349 def render_plaintext(self, cell):
353 def render_plaintext(self, cell):
350 return ["<pre>"+cell.source+"</pre>"]
354 return ["<pre>"+cell.source+"</pre>"]
351
355
352 @DocInherit
356 @DocInherit
353 def render_pyout(self, output):
357 def render_pyout(self, output):
354 lines = ['<tr><td><tt>Out[<b>%s</b>]:</tt></td></tr>' % output.prompt_number, '<td>']
358 lines = ['<tr><td><tt>Out[<b>%s</b>]:</tt></td></tr>' % output.prompt_number, '<td>']
355
359
356 # output is a dictionary like object with type as a key
360 # output is a dictionary like object with type as a key
357 if 'latex' in output:
361 if 'latex' in output:
358 lines.append("<pre>")
362 lines.append("<pre>")
359 lines.extend(indent(output.latex))
363 lines.extend(indent(output.latex))
360 lines.append("</pre>")
364 lines.append("</pre>")
361
365
362 if 'text' in output:
366 if 'text' in output:
363 lines.append("<pre>")
367 lines.append("<pre>")
364 lines.extend(indent(output.text))
368 lines.extend(indent(output.text))
365 lines.append("</pre>")
369 lines.append("</pre>")
366
370
367 return lines
371 return lines
368
372
369 @DocInherit
373 @DocInherit
370 def _img_lines(self, img_file):
374 def _img_lines(self, img_file):
371 return ['<img src="%s">' % img_file, '']
375 return ['<img src="%s">' % img_file, '']
372
376
373 @DocInherit
377 @DocInherit
374 def render_stream(self, output):
378 def render_stream(self, output):
375 lines = []
379 lines = []
376
380
377 if 'text' in output:
381 if 'text' in output:
378 lines.append(output.text)
382 lines.append(output.text)
379
383
380 return lines
384 return lines
381
385
382
386
383 class ConverterLaTeX(Converter):
387 class ConverterLaTeX(Converter):
384 """Converts a notebook to a .tex file suitable for pdflatex.
388 """Converts a notebook to a .tex file suitable for pdflatex.
385
389
386 Note: this converter *needs*:
390 Note: this converter *needs*:
387
391
388 - `pandoc`: for all conversion of markdown cells. If your notebook only
392 - `pandoc`: for all conversion of markdown cells. If your notebook only
389 has Raw cells, pandoc will not be needed.
393 has Raw cells, pandoc will not be needed.
390
394
391 - `inkscape`: if your notebook has SVG figures. These need to be
395 - `inkscape`: if your notebook has SVG figures. These need to be
392 converted to PDF before inclusion in the TeX file, as LaTeX doesn't
396 converted to PDF before inclusion in the TeX file, as LaTeX doesn't
393 understand SVG natively.
397 understand SVG natively.
394
398
395 You will in general obtain much better final PDF results if you configure
399 You will in general obtain much better final PDF results if you configure
396 the matplotlib backend to create SVG output with
400 the matplotlib backend to create SVG output with
397
401
398 %config InlineBackend.figure_format = 'svg'
402 %config InlineBackend.figure_format = 'svg'
399
403
400 (or set the equivalent flag at startup or in your configuration profile).
404 (or set the equivalent flag at startup or in your configuration profile).
401 """
405 """
402 extension = 'tex'
406 extension = 'tex'
403 heading_marker = {1: r'\section',
407 documentclass = 'article'
404 2: r'\subsection',
408 documentclass_options = '11pt,english'
405 3: r'\subsubsection',
409 heading_map = {1: r'\section',
406 4: r'\paragraph',
410 2: r'\subsection',
407 5: r'\subparagraph',
411 3: r'\subsubsection',
408 6: r'\subparagraph'}
412 4: r'\paragraph',
413 5: r'\subparagraph',
414 6: r'\subparagraph'}
409
415
410 def env(self, environment, lines):
416 def env(self, environment, lines):
411 """Return list of environment lines for input lines
417 """Return list of environment lines for input lines
412
418
413 Parameters
419 Parameters
414 ----------
420 ----------
415 env : string
421 env : string
416 Name of the environment to bracket with begin/end.
422 Name of the environment to bracket with begin/end.
417
423
418 lines: """
424 lines: """
419 out = [r'\begin{%s}' % environment]
425 out = [r'\begin{%s}' % environment]
420 if isinstance(lines, basestring):
426 if isinstance(lines, basestring):
421 out.append(lines)
427 out.append(lines)
422 else: # list
428 else: # list
423 out.extend(lines)
429 out.extend(lines)
424 out.append(r'\end{%s}' % environment)
430 out.append(r'\end{%s}' % environment)
425 return out
431 return out
426
432
433 def convert(self):
434 # The main body is done by the logic in the parent class, and that's
435 # all we need if preamble support has been turned off.
436 body = super(ConverterLaTeX, self).convert()
437 if not self.with_preamble:
438 return body
439 # But if preamble is on, then we need to construct a proper, standalone
440 # tex file.
441
442 # Tag the document at the top and set latex class
443 final = [ r'%% This file was auto-generated by IPython, do NOT edit',
444 r'%% Conversion from the original notebook file:',
445 r'%% {0}'.format(self.infile),
446 r'%%',
447 r'\documentclass[%s]{%s}' % (self.documentclass_options,
448 self.documentclass),
449 '',
450 ]
451 # Load our own preamble, which is stored next to the main file. We
452 # need to be careful in case the script entry point is a symlink
453 myfile = __file__ if not os.path.islink(__file__) else \
454 os.readlink(__file__)
455 with open(os.path.join(os.path.dirname(myfile), 'preamble.tex')) as f:
456 final.append(f.read())
457
458 # Load any additional user-supplied preamble
459 if self.user_preamble:
460 final.extend(['', '%% Adding user preamble from file:',
461 '%% {0}'.format(self.user_preamble), ''])
462 with open(self.user_preamble) as f:
463 final.append(f.read())
464
465 # Include document body
466 final.extend([ r'\begin{document}', '',
467 body,
468 r'\end{document}', ''])
469 # Retun value must be a string
470 return '\n'.join(final)
471
427 @DocInherit
472 @DocInherit
428 def render_heading(self, cell):
473 def render_heading(self, cell):
429 marker = self.heading_marker[cell.level]
474 marker = self.heading_map[cell.level]
430 return ['%s{%s}\n\n' % (marker, cell.source) ]
475 return ['%s{%s}\n\n' % (marker, cell.source) ]
431
476
432 @DocInherit
477 @DocInherit
433 def render_code(self, cell):
478 def render_code(self, cell):
434 if not cell.input:
479 if not cell.input:
435 return []
480 return []
436
481
437 # Cell codes first carry input code, we use lstlisting for that
482 # Cell codes first carry input code, we use lstlisting for that
438 lines = [r'\begin{codecell}']
483 lines = [r'\begin{codecell}']
439
484
440 lines.extend(self.env('codeinput',
485 lines.extend(self.env('codeinput',
441 self.env('lstlisting', cell.input)))
486 self.env('lstlisting', cell.input)))
442
487
443 outlines = []
488 outlines = []
444 for output in cell.outputs:
489 for output in cell.outputs:
445 conv_fn = self.dispatch(output.output_type)
490 conv_fn = self.dispatch(output.output_type)
446 outlines.extend(conv_fn(output))
491 outlines.extend(conv_fn(output))
447
492
448 # And then output of many possible types; use a frame for all of it.
493 # And then output of many possible types; use a frame for all of it.
449 if outlines:
494 if outlines:
450 lines.extend(self.env('codeoutput', outlines))
495 lines.extend(self.env('codeoutput', outlines))
451
496
452 lines.append(r'\end{codecell}')
497 lines.append(r'\end{codecell}')
453
498
454 return lines
499 return lines
455
500
456
501
457 @DocInherit
502 @DocInherit
458 def _img_lines(self, img_file):
503 def _img_lines(self, img_file):
459 return self.env('center',
504 return self.env('center',
460 [r'\includegraphics[width=3in]{%s}' % img_file, r'\par'])
505 [r'\includegraphics[width=3in]{%s}' % img_file, r'\par'])
461
506
462 def _svg_lines(self, img_file):
507 def _svg_lines(self, img_file):
463 base_file = os.path.splitext(img_file)[0]
508 base_file = os.path.splitext(img_file)[0]
464 pdf_file = base_file + '.pdf'
509 pdf_file = base_file + '.pdf'
465 subprocess.check_call(['inkscape', '--export-pdf=%s' % pdf_file,
510 subprocess.check_call(['inkscape', '--export-pdf=%s' % pdf_file,
466 img_file])
511 img_file])
467 return self._img_lines(pdf_file)
512 return self._img_lines(pdf_file)
468
513
469 @DocInherit
514 @DocInherit
470 def render_stream(self, output):
515 def render_stream(self, output):
471 lines = []
516 lines = []
472
517
473 if 'text' in output:
518 if 'text' in output:
474 lines.extend(self.env('verbatim', output.text.strip()))
519 lines.extend(self.env('verbatim', output.text.strip()))
475
520
476 return lines
521 return lines
477
522
478 @DocInherit
523 @DocInherit
479 def render_markdown(self, cell):
524 def render_markdown(self, cell):
480 return [markdown2latex(cell['source'])]
525 return [markdown2latex(cell['source'])]
481
526
482 @DocInherit
527 @DocInherit
483 def render_pyout(self, output):
528 def render_pyout(self, output):
484 lines = []
529 lines = []
485
530
486 # output is a dictionary like object with type as a key
531 # output is a dictionary like object with type as a key
487 if 'latex' in output:
532 if 'latex' in output:
488 lines.extend(output.latex)
533 lines.extend(output.latex)
489
534
490 if 'text' in output:
535 if 'text' in output:
491 lines.extend(self.env('verbatim', output.text))
536 lines.extend(self.env('verbatim', output.text))
492
537
493 return lines
538 return lines
494
539
495 @DocInherit
540 @DocInherit
496 def render_pyerr(self, output):
541 def render_pyerr(self, output):
497 # Note: a traceback is a *list* of frames.
542 # Note: a traceback is a *list* of frames.
498 return self.env('traceback',
543 return self.env('traceback',
499 self.env('verbatim',
544 self.env('verbatim',
500 remove_ansi('\n'.join(output.traceback))))
545 remove_ansi('\n'.join(output.traceback))))
501
546
502 @DocInherit
547 @DocInherit
503 def render_unknown(self, cell):
548 def render_unknown(self, cell):
504 return self.env('verbatim', pprint.pformat(cell))
549 return self.env('verbatim', pprint.pformat(cell))
505
550
506
551
507 def rst2simplehtml(infile):
552 def rst2simplehtml(infile):
508 """Convert a rst file to simplified html suitable for blogger.
553 """Convert a rst file to simplified html suitable for blogger.
509
554
510 This just runs rst2html with certain parameters to produce really simple
555 This just runs rst2html with certain parameters to produce really simple
511 html and strips the document header, so the resulting file can be easily
556 html and strips the document header, so the resulting file can be easily
512 pasted into a blogger edit window.
557 pasted into a blogger edit window.
513 """
558 """
514
559
515 # This is the template for the rst2html call that produces the cleanest,
560 # This is the template for the rst2html call that produces the cleanest,
516 # simplest html I could find. This should help in making it easier to
561 # simplest html I could find. This should help in making it easier to
517 # paste into the blogspot html window, though I'm still having problems
562 # paste into the blogspot html window, though I'm still having problems
518 # with linebreaks there...
563 # with linebreaks there...
519 cmd_template = ("rst2html --link-stylesheet --no-xml-declaration "
564 cmd_template = ("rst2html --link-stylesheet --no-xml-declaration "
520 "--no-generator --no-datestamp --no-source-link "
565 "--no-generator --no-datestamp --no-source-link "
521 "--no-toc-backlinks --no-section-numbering "
566 "--no-toc-backlinks --no-section-numbering "
522 "--strip-comments ")
567 "--strip-comments ")
523
568
524 cmd = "%s %s" % (cmd_template, infile)
569 cmd = "%s %s" % (cmd_template, infile)
525 proc = subprocess.Popen(cmd,
570 proc = subprocess.Popen(cmd,
526 stdout=subprocess.PIPE,
571 stdout=subprocess.PIPE,
527 stderr=subprocess.PIPE,
572 stderr=subprocess.PIPE,
528 shell=True)
573 shell=True)
529 html, stderr = proc.communicate()
574 html, stderr = proc.communicate()
530 if stderr:
575 if stderr:
531 raise IOError(stderr)
576 raise IOError(stderr)
532
577
533 # Make an iterator so breaking out holds state. Our implementation of
578 # Make an iterator so breaking out holds state. Our implementation of
534 # searching for the html body below is basically a trivial little state
579 # searching for the html body below is basically a trivial little state
535 # machine, so we need this.
580 # machine, so we need this.
536 walker = iter(html.splitlines())
581 walker = iter(html.splitlines())
537
582
538 # Find start of main text, break out to then print until we find end /div.
583 # Find start of main text, break out to then print until we find end /div.
539 # This may only work if there's a real title defined so we get a 'div class'
584 # This may only work if there's a real title defined so we get a 'div class'
540 # tag, I haven't really tried.
585 # tag, I haven't really tried.
541 for line in walker:
586 for line in walker:
542 if line.startswith('<body>'):
587 if line.startswith('<body>'):
543 break
588 break
544
589
545 newfname = os.path.splitext(infile)[0] + '.html'
590 newfname = os.path.splitext(infile)[0] + '.html'
546 with open(newfname, 'w') as f:
591 with open(newfname, 'w') as f:
547 for line in walker:
592 for line in walker:
548 if line.startswith('</body>'):
593 if line.startswith('</body>'):
549 break
594 break
550 f.write(line)
595 f.write(line)
551 f.write('\n')
596 f.write('\n')
552
597
553 return newfname
598 return newfname
554
599
555 known_formats = "rst (default), html, quick-html, latex"
600 known_formats = "rst (default), html, quick-html, latex"
556
601
557 def main(infile, format='rst'):
602 def main(infile, format='rst'):
558 """Convert a notebook to html in one step"""
603 """Convert a notebook to html in one step"""
559 # XXX: this is just quick and dirty for now. When adding a new format,
604 # XXX: this is just quick and dirty for now. When adding a new format,
560 # make sure to add it to the `known_formats` string above, which gets
605 # make sure to add it to the `known_formats` string above, which gets
561 # printed in in the catch-all else, as well as in the help
606 # printed in in the catch-all else, as well as in the help
562 if format == 'rst':
607 if format == 'rst':
563 converter = ConverterRST(infile)
608 converter = ConverterRST(infile)
564 converter.render()
609 converter.render()
565 elif format == 'html':
610 elif format == 'html':
566 #Currently, conversion to html is a 2 step process, nb->rst->html
611 #Currently, conversion to html is a 2 step process, nb->rst->html
567 converter = ConverterRST(infile)
612 converter = ConverterRST(infile)
568 rstfname = converter.render()
613 rstfname = converter.render()
569 rst2simplehtml(rstfname)
614 rst2simplehtml(rstfname)
570 elif format == 'quick-html':
615 elif format == 'quick-html':
571 converter = ConverterQuickHTML(infile)
616 converter = ConverterQuickHTML(infile)
572 rstfname = converter.render()
617 rstfname = converter.render()
573 elif format == 'latex':
618 elif format == 'latex':
574 converter = ConverterLaTeX(infile)
619 converter = ConverterLaTeX(infile)
575 latexfname = converter.render()
620 latexfname = converter.render()
576 else:
621 else:
577 raise SystemExit("Unknown format '%s', " % format +
622 raise SystemExit("Unknown format '%s', " % format +
578 "known formats are: " + known_formats)
623 "known formats are: " + known_formats)
579
624
580
625
581
626
582 if __name__ == '__main__':
627 if __name__ == '__main__':
583 parser = argparse.ArgumentParser(description=__doc__,
628 parser = argparse.ArgumentParser(description=__doc__,
584 formatter_class=argparse.RawTextHelpFormatter)
629 formatter_class=argparse.RawTextHelpFormatter)
585 # TODO: consider passing file like object around, rather than filenames
630 # TODO: consider passing file like object around, rather than filenames
586 # would allow us to process stdin, or even http streams
631 # would allow us to process stdin, or even http streams
587 #parser.add_argument('infile', nargs='?', type=argparse.FileType('r'), default=sys.stdin)
632 #parser.add_argument('infile', nargs='?', type=argparse.FileType('r'), default=sys.stdin)
588
633
589 #Require a filename as a positional argument
634 #Require a filename as a positional argument
590 parser.add_argument('infile', nargs=1)
635 parser.add_argument('infile', nargs=1)
591 parser.add_argument('-f', '--format', default='rst',
636 parser.add_argument('-f', '--format', default='rst',
592 help='Output format. Supported formats: \n' +
637 help='Output format. Supported formats: \n' +
593 known_formats)
638 known_formats)
594 args = parser.parse_args()
639 args = parser.parse_args()
595 main(infile=args.infile[0], format=args.format)
640 main(infile=args.infile[0], format=args.format)
General Comments 0
You need to be logged in to leave comments. Login now