##// END OF EJS Templates
Updates to markdown conversion.
Fernando Perez -
Show More
@@ -1,1143 +1,1208 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 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 # Imports
13 # Imports
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 from __future__ import print_function
15 from __future__ import print_function
16
16
17 # Stdlib
17 # Stdlib
18 import codecs
18 import codecs
19 import logging
19 import logging
20 import os
20 import os
21 import pprint
21 import pprint
22 import re
22 import re
23 import subprocess
23 import subprocess
24 import sys
24 import sys
25 import json
25 import json
26 import copy
26 import copy
27 from shutil import rmtree
27 from shutil import rmtree
28
28
29 inkscape = 'inkscape'
29 inkscape = 'inkscape'
30 if sys.platform == 'darwin':
30 if sys.platform == 'darwin':
31 inkscape = '/Applications/Inkscape.app/Contents/Resources/bin/inkscape'
31 inkscape = '/Applications/Inkscape.app/Contents/Resources/bin/inkscape'
32 if not os.path.exists(inkscape):
32 if not os.path.exists(inkscape):
33 inkscape = None
33 inkscape = None
34
34
35 # From IPython
35 # From IPython
36 from IPython.external import argparse
36 from IPython.external import argparse
37 from IPython.nbformat import current as nbformat
37 from IPython.nbformat import current as nbformat
38 from IPython.utils.text import indent
38 from IPython.utils.text import indent
39 from decorators import DocInherit
39 from decorators import DocInherit
40 from IPython.nbformat.v3.nbjson import BytesEncoder
40 from IPython.nbformat.v3.nbjson import BytesEncoder
41 from IPython.utils import py3compat
41 from IPython.utils import py3compat
42
42
43 #-----------------------------------------------------------------------------
43 #-----------------------------------------------------------------------------
44 # Utility functions
44 # Utility functions
45 #-----------------------------------------------------------------------------
45 #-----------------------------------------------------------------------------
46
46
47 def DocInherit(f):
47 def DocInherit(f):
48 return f
48 return f
49
49
50 def remove_fake_files_url(cell):
50 def remove_fake_files_url(cell):
51 """Remove from the cell source the /files/ pseudo-path we use.
51 """Remove from the cell source the /files/ pseudo-path we use.
52 """
52 """
53 src = cell.source
53 src = cell.source
54 cell.source = src.replace('/files/', '')
54 cell.source = src.replace('/files/', '')
55
55
56
56
57 def remove_ansi(src):
57 def remove_ansi(src):
58 """Strip all ANSI color escape sequences from input string.
58 """Strip all ANSI color escape sequences from input string.
59
59
60 Parameters
60 Parameters
61 ----------
61 ----------
62 src : string
62 src : string
63
63
64 Returns
64 Returns
65 -------
65 -------
66 string
66 string
67 """
67 """
68 return re.sub(r'\033\[(0|\d;\d\d)m', '', src)
68 return re.sub(r'\033\[(0|\d;\d\d)m', '', src)
69
69
70
70
71 # Pandoc-dependent code
71 # Pandoc-dependent code
72 def markdown2latex(src):
72 def markdown2latex(src):
73 """Convert a markdown string to LaTeX via pandoc.
73 """Convert a markdown string to LaTeX via pandoc.
74
74
75 This function will raise an error if pandoc is not installed.
75 This function will raise an error if pandoc is not installed.
76
76
77 Any error messages generated by pandoc are printed to stderr.
77 Any error messages generated by pandoc are printed to stderr.
78
78
79 Parameters
79 Parameters
80 ----------
80 ----------
81 src : string
81 src : string
82 Input string, assumed to be valid markdown.
82 Input string, assumed to be valid markdown.
83
83
84 Returns
84 Returns
85 -------
85 -------
86 out : string
86 out : string
87 Output as returned by pandoc.
87 Output as returned by pandoc.
88 """
88 """
89 p = subprocess.Popen('pandoc -f markdown -t latex'.split(),
89 p = subprocess.Popen('pandoc -f markdown -t latex'.split(),
90 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
90 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
91 out, err = p.communicate(src.encode('utf-8'))
91 out, err = p.communicate(src.encode('utf-8'))
92 if err:
92 if err:
93 print(err, file=sys.stderr)
93 print(err, file=sys.stderr)
94 #print('*'*20+'\n', out, '\n'+'*'*20) # dbg
94 #print('*'*20+'\n', out, '\n'+'*'*20) # dbg
95 return unicode(out,'utf-8')
95 return unicode(out,'utf-8')
96
96
97
97
98 def markdown2rst(src):
98 def markdown2rst(src):
99 """Convert a markdown string to LaTeX via pandoc.
99 """Convert a markdown string to LaTeX via pandoc.
100
100
101 This function will raise an error if pandoc is not installed.
101 This function will raise an error if pandoc is not installed.
102
102
103 Any error messages generated by pandoc are printed to stderr.
103 Any error messages generated by pandoc are printed to stderr.
104
104
105 Parameters
105 Parameters
106 ----------
106 ----------
107 src : string
107 src : string
108 Input string, assumed to be valid markdown.
108 Input string, assumed to be valid markdown.
109
109
110 Returns
110 Returns
111 -------
111 -------
112 out : string
112 out : string
113 Output as returned by pandoc.
113 Output as returned by pandoc.
114 """
114 """
115 p = subprocess.Popen('pandoc -f markdown -t rst'.split(),
115 p = subprocess.Popen('pandoc -f markdown -t rst'.split(),
116 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
116 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
117 out, err = p.communicate(src.encode('utf-8'))
117 out, err = p.communicate(src.encode('utf-8'))
118 if err:
118 if err:
119 print(err, file=sys.stderr)
119 print(err, file=sys.stderr)
120 #print('*'*20+'\n', out, '\n'+'*'*20) # dbg
120 #print('*'*20+'\n', out, '\n'+'*'*20) # dbg
121 return unicode(out,'utf-8')
121 return unicode(out,'utf-8')
122
122
123
123
124 def rst_directive(directive, text=''):
124 def rst_directive(directive, text=''):
125 out = [directive, '']
125 out = [directive, '']
126 if text:
126 if text:
127 out.extend([indent(text), ''])
127 out.extend([indent(text), ''])
128 return out
128 return out
129
129
130 #-----------------------------------------------------------------------------
130 #-----------------------------------------------------------------------------
131 # Class declarations
131 # Class declarations
132 #-----------------------------------------------------------------------------
132 #-----------------------------------------------------------------------------
133
133
134 class ConversionException(Exception):
134 class ConversionException(Exception):
135 pass
135 pass
136
136
137
137
138 class Converter(object):
138 class Converter(object):
139 default_encoding = 'utf-8'
139 default_encoding = 'utf-8'
140 extension = str()
140 extension = str()
141 figures_counter = 0
141 figures_counter = 0
142 infile = str()
142 infile = str()
143 infile_dir = str()
143 infile_dir = str()
144 infile_root = str()
144 infile_root = str()
145 files_dir = str()
145 files_dir = str()
146 with_preamble = True
146 with_preamble = True
147 user_preamble = None
147 user_preamble = None
148 output = str()
148 output = str()
149 raw_as_verbatim = False
149 raw_as_verbatim = False
150
150
151 def __init__(self, infile):
151 def __init__(self, infile):
152 self.infile = infile
152 self.infile = infile
153 self.infile_dir, infile_root = os.path.split(infile)
153 self.infile_dir, infile_root = os.path.split(infile)
154 infile_root = os.path.splitext(infile_root)[0]
154 infile_root = os.path.splitext(infile_root)[0]
155 files_dir = os.path.join(self.infile_dir, infile_root + '_files')
155 files_dir = os.path.join(self.infile_dir, infile_root + '_files')
156 if not os.path.isdir(files_dir):
156 if not os.path.isdir(files_dir):
157 os.mkdir(files_dir)
157 os.mkdir(files_dir)
158 self.infile_root = infile_root
158 self.infile_root = infile_root
159 self.files_dir = files_dir
159 self.files_dir = files_dir
160 self.outbase = os.path.join(self.infile_dir, infile_root)
160 self.outbase = os.path.join(self.infile_dir, infile_root)
161
161
162 def dispatch(self, cell_type):
162 def dispatch(self, cell_type):
163 """return cell_type dependent render method, for example render_code
163 """return cell_type dependent render method, for example render_code
164 """
164 """
165 return getattr(self, 'render_' + cell_type, self.render_unknown)
165 return getattr(self, 'render_' + cell_type, self.render_unknown)
166
166
167 def dispatch_display_format(self, format):
167 def dispatch_display_format(self, format):
168 """return output_type dependent render method, for example render_output_text
168 """return output_type dependent render method, for example render_output_text
169 """
169 """
170 return getattr(self, 'render_display_format_' + format, self.render_unknown)
170 return getattr(self, 'render_display_format_' + format, self.render_unknown)
171
171
172 def convert(self, cell_separator='\n'):
172 def convert(self, cell_separator='\n'):
173 lines = []
173 lines = []
174 lines.extend(self.optional_header())
174 lines.extend(self.optional_header())
175 converted_cells = []
175 converted_cells = []
176 for worksheet in self.nb.worksheets:
176 for worksheet in self.nb.worksheets:
177 for cell in worksheet.cells:
177 for cell in worksheet.cells:
178 #print(cell.cell_type) # dbg
178 #print(cell.cell_type) # dbg
179 conv_fn = self.dispatch(cell.cell_type)
179 conv_fn = self.dispatch(cell.cell_type)
180 if cell.cell_type in ('markdown', 'raw'):
180 if cell.cell_type in ('markdown', 'raw'):
181 remove_fake_files_url(cell)
181 remove_fake_files_url(cell)
182 converted_cells.append('\n'.join(conv_fn(cell)))
182 converted_cells.append('\n'.join(conv_fn(cell)))
183 cell_lines = cell_separator.join(converted_cells).split('\n')
183 cell_lines = cell_separator.join(converted_cells).split('\n')
184 lines.extend(cell_lines)
184 lines.extend(cell_lines)
185 lines.extend(self.optional_footer())
185 lines.extend(self.optional_footer())
186 return u'\n'.join(lines)
186 return u'\n'.join(lines)
187
187
188 def render(self):
188 def render(self):
189 "read, convert, and save self.infile"
189 "read, convert, and save self.infile"
190 if not hasattr(self, 'nb'):
190 if not hasattr(self, 'nb'):
191 self.read()
191 self.read()
192 self.output = self.convert()
192 self.output = self.convert()
193 return self.save()
193 return self.save()
194
194
195 def read(self):
195 def read(self):
196 "read and parse notebook into NotebookNode called self.nb"
196 "read and parse notebook into NotebookNode called self.nb"
197 with open(self.infile) as f:
197 with open(self.infile) as f:
198 self.nb = nbformat.read(f, 'json')
198 self.nb = nbformat.read(f, 'json')
199
199
200 def save(self, outfile=None, encoding=None):
200 def save(self, outfile=None, encoding=None):
201 "read and parse notebook into self.nb"
201 "read and parse notebook into self.nb"
202 if outfile is None:
202 if outfile is None:
203 outfile = self.outbase + '.' + self.extension
203 outfile = self.outbase + '.' + self.extension
204 if encoding is None:
204 if encoding is None:
205 encoding = self.default_encoding
205 encoding = self.default_encoding
206 with open(outfile, 'w') as f:
206 with open(outfile, 'w') as f:
207 f.write(self.output.encode(encoding))
207 f.write(self.output.encode(encoding))
208 return os.path.abspath(outfile)
208 return os.path.abspath(outfile)
209
209
210 def optional_header(self):
210 def optional_header(self):
211 return []
211 return []
212
212
213 def optional_footer(self):
213 def optional_footer(self):
214 return []
214 return []
215
215
216 def _new_figure(self, data, fmt):
216 def _new_figure(self, data, fmt):
217 """Create a new figure file in the given format.
217 """Create a new figure file in the given format.
218
218
219 Returns a path relative to the input file.
219 Returns a path relative to the input file.
220 """
220 """
221 figname = '%s_fig_%02i.%s' % (self.infile_root,
221 figname = '%s_fig_%02i.%s' % (self.infile_root,
222 self.figures_counter, fmt)
222 self.figures_counter, fmt)
223 self.figures_counter += 1
223 self.figures_counter += 1
224 fullname = os.path.join(self.files_dir, figname)
224 fullname = os.path.join(self.files_dir, figname)
225
225
226 # Binary files are base64-encoded, SVG is already XML
226 # Binary files are base64-encoded, SVG is already XML
227 if fmt in ('png', 'jpg', 'pdf'):
227 if fmt in ('png', 'jpg', 'pdf'):
228 data = data.decode('base64')
228 data = data.decode('base64')
229 fopen = lambda fname: open(fname, 'wb')
229 fopen = lambda fname: open(fname, 'wb')
230 else:
230 else:
231 fopen = lambda fname: codecs.open(fname, 'wb', self.default_encoding)
231 fopen = lambda fname: codecs.open(fname, 'wb', self.default_encoding)
232
232
233 with fopen(fullname) as f:
233 with fopen(fullname) as f:
234 f.write(data)
234 f.write(data)
235
235
236 return fullname
236 return fullname
237
237
238 def render_heading(self, cell):
238 def render_heading(self, cell):
239 """convert a heading cell
239 """convert a heading cell
240
240
241 Returns list."""
241 Returns list."""
242 raise NotImplementedError
242 raise NotImplementedError
243
243
244 def render_code(self, cell):
244 def render_code(self, cell):
245 """Convert a code cell
245 """Convert a code cell
246
246
247 Returns list."""
247 Returns list."""
248 raise NotImplementedError
248 raise NotImplementedError
249
249
250 def render_markdown(self, cell):
250 def render_markdown(self, cell):
251 """convert a markdown cell
251 """convert a markdown cell
252
252
253 Returns list."""
253 Returns list."""
254 raise NotImplementedError
254 raise NotImplementedError
255
255
256 def _img_lines(self, img_file):
256 def _img_lines(self, img_file):
257 """Return list of lines to include an image file."""
257 """Return list of lines to include an image file."""
258 # Note: subclasses may choose to implement format-specific _FMT_lines
258 # Note: subclasses may choose to implement format-specific _FMT_lines
259 # methods if they so choose (FMT in {png, svg, jpg, pdf}).
259 # methods if they so choose (FMT in {png, svg, jpg, pdf}).
260 raise NotImplementedError
260 raise NotImplementedError
261
261
262 def render_display_data(self, output):
262 def render_display_data(self, output):
263 """convert display data from the output of a code cell
263 """convert display data from the output of a code cell
264
264
265 Returns list.
265 Returns list.
266 """
266 """
267 lines = []
267 lines = []
268
268
269 for fmt in output.keys():
269 for fmt in output.keys():
270 if fmt in ['png', 'svg', 'jpg', 'pdf']:
270 if fmt in ['png', 'svg', 'jpg', 'pdf']:
271 img_file = self._new_figure(output[fmt], fmt)
271 img_file = self._new_figure(output[fmt], fmt)
272 # Subclasses can have format-specific render functions (e.g.,
272 # Subclasses can have format-specific render functions (e.g.,
273 # latex has to auto-convert all SVG to PDF first).
273 # latex has to auto-convert all SVG to PDF first).
274 lines_fun = getattr(self, '_%s_lines' % fmt, None)
274 lines_fun = getattr(self, '_%s_lines' % fmt, None)
275 if not lines_fun:
275 if not lines_fun:
276 lines_fun = self._img_lines
276 lines_fun = self._img_lines
277 lines.extend(lines_fun(img_file))
277 lines.extend(lines_fun(img_file))
278 elif fmt != 'output_type':
278 elif fmt != 'output_type':
279 conv_fn = self.dispatch_display_format(fmt)
279 conv_fn = self.dispatch_display_format(fmt)
280 lines.extend(conv_fn(output))
280 lines.extend(conv_fn(output))
281 return lines
281 return lines
282
282
283 def render_raw(self, cell):
283 def render_raw(self, cell):
284 """convert a cell with raw text
284 """convert a cell with raw text
285
285
286 Returns list."""
286 Returns list."""
287 raise NotImplementedError
287 raise NotImplementedError
288
288
289 def render_unknown(self, cell):
289 def render_unknown(self, cell):
290 """Render cells of unkown type
290 """Render cells of unkown type
291
291
292 Returns list."""
292 Returns list."""
293 data = pprint.pformat(cell)
293 data = pprint.pformat(cell)
294 logging.warning('Unknown cell:\n%s' % data)
294 logging.warning('Unknown cell:\n%s' % data)
295 return self._unknown_lines(data)
295 return self._unknown_lines(data)
296
296
297 def render_stream(self, output):
297 def render_stream(self, output):
298 """render the stream part of an output
298 """render the stream part of an output
299
299
300 Returns list.
300 Returns list.
301
301
302 Identical to render_display_format_text
302 Identical to render_display_format_text
303 """
303 """
304 return self.render_display_format_text(output)
304 return self.render_display_format_text(output)
305
305
306 def render_pyout(self, output):
306 def render_pyout(self, output):
307 """convert pyout part of a code cell
307 """convert pyout part of a code cell
308
308
309 Returns list."""
309 Returns list."""
310 raise NotImplementedError
310 raise NotImplementedError
311
311
312
312
313 def render_pyerr(self, output):
313 def render_pyerr(self, output):
314 """convert pyerr part of a code cell
314 """convert pyerr part of a code cell
315
315
316 Returns list."""
316 Returns list."""
317 raise NotImplementedError
317 raise NotImplementedError
318
318
319 def _unknown_lines(self, data):
319 def _unknown_lines(self, data):
320 """Return list of lines for an unknown cell.
320 """Return list of lines for an unknown cell.
321
321
322 Parameters
322 Parameters
323 ----------
323 ----------
324 data : str
324 data : str
325 The content of the unknown data as a single string.
325 The content of the unknown data as a single string.
326 """
326 """
327 raise NotImplementedError
327 raise NotImplementedError
328
328
329 # These are the possible format types in an output node
329 # These are the possible format types in an output node
330
330
331 def render_display_format_text(self, output):
331 def render_display_format_text(self, output):
332 """render the text part of an output
332 """render the text part of an output
333
333
334 Returns list.
334 Returns list.
335 """
335 """
336 raise NotImplementedError
336 raise NotImplementedError
337
337
338 def render_display_format_html(self, output):
338 def render_display_format_html(self, output):
339 """render the html part of an output
339 """render the html part of an output
340
340
341 Returns list.
341 Returns list.
342 """
342 """
343 raise NotImplementedError
343 raise NotImplementedError
344
344
345 def render_display_format_latex(self, output):
345 def render_display_format_latex(self, output):
346 """render the latex part of an output
346 """render the latex part of an output
347
347
348 Returns list.
348 Returns list.
349 """
349 """
350 raise NotImplementedError
350 raise NotImplementedError
351
351
352 def render_display_format_json(self, output):
352 def render_display_format_json(self, output):
353 """render the json part of an output
353 """render the json part of an output
354
354
355 Returns list.
355 Returns list.
356 """
356 """
357 raise NotImplementedError
357 raise NotImplementedError
358
358
359 def render_display_format_javascript(self, output):
359 def render_display_format_javascript(self, output):
360 """render the javascript part of an output
360 """render the javascript part of an output
361
361
362 Returns list.
362 Returns list.
363 """
363 """
364 raise NotImplementedError
364 raise NotImplementedError
365
365
366
366
367 class ConverterRST(Converter):
367 class ConverterRST(Converter):
368 extension = 'rst'
368 extension = 'rst'
369 heading_level = {1: '=', 2: '-', 3: '`', 4: '\'', 5: '.', 6: '~'}
369 heading_level = {1: '=', 2: '-', 3: '`', 4: '\'', 5: '.', 6: '~'}
370
370
371 @DocInherit
371 @DocInherit
372 def render_heading(self, cell):
372 def render_heading(self, cell):
373 marker = self.heading_level[cell.level]
373 marker = self.heading_level[cell.level]
374 return ['{0}\n{1}\n'.format(cell.source, marker * len(cell.source))]
374 return ['{0}\n{1}\n'.format(cell.source, marker * len(cell.source))]
375
375
376 @DocInherit
376 @DocInherit
377 def render_code(self, cell):
377 def render_code(self, cell):
378 if not cell.input:
378 if not cell.input:
379 return []
379 return []
380
380
381 lines = ['In[%s]:' % cell.prompt_number, '']
381 lines = ['In[%s]:' % cell.prompt_number, '']
382 lines.extend(rst_directive('.. code:: python', cell.input))
382 lines.extend(rst_directive('.. code:: python', cell.input))
383
383
384 for output in cell.outputs:
384 for output in cell.outputs:
385 conv_fn = self.dispatch(output.output_type)
385 conv_fn = self.dispatch(output.output_type)
386 lines.extend(conv_fn(output))
386 lines.extend(conv_fn(output))
387
387
388 return lines
388 return lines
389
389
390 @DocInherit
390 @DocInherit
391 def render_markdown(self, cell):
391 def render_markdown(self, cell):
392 #return [cell.source]
392 #return [cell.source]
393 return [markdown2rst(cell.source)]
393 return [markdown2rst(cell.source)]
394
394
395 @DocInherit
395 @DocInherit
396 def render_raw(self, cell):
396 def render_raw(self, cell):
397 if self.raw_as_verbatim:
397 if self.raw_as_verbatim:
398 return ['::', '', indent(cell.source), '']
398 return ['::', '', indent(cell.source), '']
399 else:
399 else:
400 return [cell.source]
400 return [cell.source]
401
401
402 @DocInherit
402 @DocInherit
403 def render_pyout(self, output):
403 def render_pyout(self, output):
404 lines = ['Out[%s]:' % output.prompt_number, '']
404 lines = ['Out[%s]:' % output.prompt_number, '']
405
405
406 # output is a dictionary like object with type as a key
406 # output is a dictionary like object with type as a key
407 if 'latex' in output:
407 if 'latex' in output:
408 lines.extend(rst_directive('.. math::', output.latex))
408 lines.extend(rst_directive('.. math::', output.latex))
409
409
410 if 'text' in output:
410 if 'text' in output:
411 lines.extend(rst_directive('.. parsed-literal::', output.text))
411 lines.extend(rst_directive('.. parsed-literal::', output.text))
412
412
413 return lines
413 return lines
414
414
415 @DocInherit
415 @DocInherit
416 def render_pyerr(self, output):
416 def render_pyerr(self, output):
417 # Note: a traceback is a *list* of frames.
417 # Note: a traceback is a *list* of frames.
418 return ['::', '', indent(remove_ansi('\n'.join(output.traceback))), '']
418 return ['::', '', indent(remove_ansi('\n'.join(output.traceback))), '']
419
419
420 @DocInherit
420 @DocInherit
421 def _img_lines(self, img_file):
421 def _img_lines(self, img_file):
422 return ['.. image:: %s' % img_file, '']
422 return ['.. image:: %s' % img_file, '']
423
423
424 @DocInherit
424 @DocInherit
425 def render_display_format_text(self, output):
425 def render_display_format_text(self, output):
426 return rst_directive('.. parsed-literal::', output.text)
426 return rst_directive('.. parsed-literal::', output.text)
427
427
428 @DocInherit
428 @DocInherit
429 def _unknown_lines(self, data):
429 def _unknown_lines(self, data):
430 return rst_directive('.. warning:: Unknown cell') + [data]
430 return rst_directive('.. warning:: Unknown cell') + [data]
431
431
432 def render_display_format_html(self, output):
432 def render_display_format_html(self, output):
433 """render the html part of an output
433 """render the html part of an output
434
434
435 Returns list.
435 Returns list.
436 """
436 """
437 return rst_directive('.. raw:: html', output.html)
437 return rst_directive('.. raw:: html', output.html)
438
438
439 def render_display_format_latex(self, output):
439 def render_display_format_latex(self, output):
440 """render the latex part of an output
440 """render the latex part of an output
441
441
442 Returns list.
442 Returns list.
443 """
443 """
444 return rst_directive('.. math::', output.latex)
444 return rst_directive('.. math::', output.latex)
445
445
446 def render_display_format_json(self, output):
446 def render_display_format_json(self, output):
447 """render the json part of an output
447 """render the json part of an output
448
448
449 Returns list.
449 Returns list.
450 """
450 """
451 return rst_directive('.. raw:: json', output.json)
451 return rst_directive('.. raw:: json', output.json)
452
452
453
453
454 def render_display_format_javascript(self, output):
454 def render_display_format_javascript(self, output):
455 """render the javascript part of an output
455 """render the javascript part of an output
456
456
457 Returns list.
457 Returns list.
458 """
458 """
459 return rst_directive('.. raw:: javascript', output.javascript)
459 return rst_directive('.. raw:: javascript', output.javascript)
460
460
461
461
462
463
464 def highlight(src, lang='python'):
465 """Return a syntax-highlighted version of the input source.
466 """
467 from pygments import highlight
468 from pygments.lexers import get_lexer_by_name
469 from pygments.formatters import HtmlFormatter
470
471 lexer = get_lexer_by_name(lang, stripall=True)
472 return highlight(src, lexer, HtmlFormatter())
473
474
462 class ConverterMarkdown(Converter):
475 class ConverterMarkdown(Converter):
463 extension = 'md'
476 extension = 'md'
464
477
478 def __init__(self, infile, highlight_source=False):
479 super(ConverterMarkdown, self).__init__(infile)
480 self.highlight_source = highlight_source
481
465 @DocInherit
482 @DocInherit
466 def render_heading(self, cell):
483 def render_heading(self, cell):
467 return ['{0} {1}'.format('#'*cell.level, cell.source), '']
484 return ['{0} {1}'.format('#'*cell.level, cell.source), '']
468
485
469 @DocInherit
486 @DocInherit
470 def render_code(self, cell):
487 def render_code(self, cell):
471 if not cell.input:
488 if not cell.input:
472 return []
489 return []
473 lines = []
490 lines = []
474 #lines.append('----')
491 #lines.append('----')
475 lines.extend(['*In[%s]:*' % cell.prompt_number, ''])
492 lines.extend(['*In[%s]:*' % cell.prompt_number, ''])
476 lines.extend([indent(cell.input), ''])
493 src = highlight(cell.input) if self.highlight_source else \
494 indent(cell.input)
495 lines.extend([src, ''])
477 if cell.outputs:
496 if cell.outputs:
478 lines.extend(['==>', ''])
497 lines.extend(['==>', ''])
479 for output in cell.outputs:
498 for output in cell.outputs:
480 conv_fn = self.dispatch(output.output_type)
499 conv_fn = self.dispatch(output.output_type)
481 lines.extend(conv_fn(output))
500 lines.extend(conv_fn(output))
482
501
483 #lines.append('----')
502 #lines.append('----')
484 lines.append('')
503 lines.append('')
485 return lines
504 return lines
486
505
487 @DocInherit
506 @DocInherit
488 def render_markdown(self, cell):
507 def render_markdown(self, cell):
489 return [cell.source, '']
508 return [cell.source, '']
490 #return [markdown2rst(cell.source)]
491
509
492 @DocInherit
510 @DocInherit
493 def render_raw(self, cell):
511 def render_raw(self, cell):
494 if self.raw_as_verbatim:
512 if self.raw_as_verbatim:
495 return [indent(cell.source), '']
513 return [indent(cell.source), '']
496 else:
514 else:
497 return [cell.source, '']
515 return [cell.source, '']
498
516
499 @DocInherit
517 @DocInherit
500 def render_pyout(self, output):
518 def render_pyout(self, output):
501 lines = []
519 lines = []
502 #lines.extend(['*Out[%s]:*' % output.prompt_number, ''])
520
521 ## if 'text' in output:
522 ## lines.extend(['*Out[%s]:*' % output.prompt_number, ''])
503
523
504 # output is a dictionary like object with type as a key
524 # output is a dictionary like object with type as a key
505 if 'latex' in output:
525 if 'latex' in output:
506 pass
526 pass
507
527
508 if 'text' in output:
528 if 'text' in output:
509 lines.extend([indent(output.text)])
529 lines.extend(['<pre>', indent(output.text), '</pre>'])
510
530
511 lines.append('')
531 lines.append('')
512 return lines
532 return lines
513
533
514 @DocInherit
534 @DocInherit
515 def render_pyerr(self, output):
535 def render_pyerr(self, output):
516 # Note: a traceback is a *list* of frames.
536 # Note: a traceback is a *list* of frames.
517 return [indent(remove_ansi('\n'.join(output.traceback))), '']
537 return [indent(remove_ansi('\n'.join(output.traceback))), '']
518
538
519 @DocInherit
539 @DocInherit
520 def _img_lines(self, img_file):
540 def _img_lines(self, img_file):
521 return ['', '![image](%s)' % img_file, '']
541 return ['', '![image](%s)' % img_file, '']
522
542
523 @DocInherit
543 @DocInherit
524 def render_display_format_text(self, output):
544 def render_display_format_text(self, output):
525 return [indent(output.text)]
545 return [indent(output.text)]
526
546
527 @DocInherit
547 @DocInherit
528 def _unknown_lines(self, data):
548 def _unknown_lines(self, data):
529 return ['Warning: Unknown cell', data]
549 return ['Warning: Unknown cell', data]
530
550
531 def render_display_format_html(self, output):
551 def render_display_format_html(self, output):
532 """render the html part of an output
552 """render the html part of an output
533
553
534 Returns list.
554 Returns list.
535 """
555 """
536 return [output.html]
556 return [output.html]
537
557
538 def render_display_format_latex(self, output):
558 def render_display_format_latex(self, output):
539 """render the latex part of an output
559 """render the latex part of an output
540
560
541 Returns list.
561 Returns list.
542 """
562 """
543 return ['LaTeX::', indent(output.latex)]
563 return ['LaTeX::', indent(output.latex)]
544
564
545 def render_display_format_json(self, output):
565 def render_display_format_json(self, output):
546 """render the json part of an output
566 """render the json part of an output
547
567
548 Returns list.
568 Returns list.
549 """
569 """
550 return ['JSON:', indent(output.json)]
570 return ['JSON:', indent(output.json)]
551
571
552
572
553 def render_display_format_javascript(self, output):
573 def render_display_format_javascript(self, output):
554 """render the javascript part of an output
574 """render the javascript part of an output
555
575
556 Returns list.
576 Returns list.
557 """
577 """
558 return ['JavaScript:', indent(output.javascript)]
578 return ['JavaScript:', indent(output.javascript)]
559
579
560
580
581 def return_list(x):
582 """Ensure that x is returned as a list or inside one"""
583 return x if isinstance(x, list) else [x]
584
585
561 class ConverterQuickHTML(Converter):
586 class ConverterQuickHTML(Converter):
562 extension = 'html'
587 extension = 'html'
563
588
564 def in_tag(self, tag, src):
589 def in_tag(self, tag, src):
565 """Return a list of elements bracketed by the given tag"""
590 """Return a list of elements bracketed by the given tag"""
566 return ['<%s>' % tag, src, '</%s>' % tag]
591 return ['<%s>' % tag, src, '</%s>' % tag]
567
592
568 def optional_header(self):
593 def optional_header(self):
569 # XXX: inject the IPython standard CSS into here
594 # XXX: inject the IPython standard CSS into here
570 s = """<html>
595 s = """<html>
571 <head>
596 <head>
572 </head>
597 </head>
573
598
574 <body>
599 <body>
575 """
600 """
576 return s.splitlines()
601 return s.splitlines()
577
602
578 def optional_footer(self):
603 def optional_footer(self):
579 s = """</body>
604 s = """</body>
580 </html>
605 </html>
581 """
606 """
582 return s.splitlines()
607 return s.splitlines()
583
608
584 @DocInherit
609 @DocInherit
585 def render_heading(self, cell):
610 def render_heading(self, cell):
586 marker = cell.level
611 marker = cell.level
587 return ['<h{1}>\n {0}\n</h{1}>'.format(cell.source, marker)]
612 return ['<h{1}>\n {0}\n</h{1}>'.format(cell.source, marker)]
588
613
589 @DocInherit
614 @DocInherit
590 def render_code(self, cell):
615 def render_code(self, cell):
591 if not cell.input:
616 if not cell.input:
592 return []
617 return []
593
618
594 lines = ['<table>']
619 lines = ['<table>']
595 lines.append('<tr><td><tt>In [<b>%s</b>]:</tt></td><td><tt>' % cell.prompt_number)
620 lines.append('<tr><td><tt>In [<b>%s</b>]:</tt></td><td><tt>' % cell.prompt_number)
596 lines.append("<br>\n".join(cell.input.splitlines()))
621 lines.append("<br>\n".join(cell.input.splitlines()))
597 lines.append('</tt></td></tr>')
622 lines.append('</tt></td></tr>')
598
623
599 for output in cell.outputs:
624 for output in cell.outputs:
600 lines.append('<tr><td></td><td>')
625 lines.append('<tr><td></td><td>')
601 conv_fn = self.dispatch(output.output_type)
626 conv_fn = self.dispatch(output.output_type)
602 lines.extend(conv_fn(output))
627 lines.extend(conv_fn(output))
603 lines.append('</td></tr>')
628 lines.append('</td></tr>')
604
629
605 lines.append('</table>')
630 lines.append('</table>')
606 return lines
631 return lines
607
632
608 @DocInherit
633 @DocInherit
609 def render_markdown(self, cell):
634 def render_markdown(self, cell):
610 return self.in_tag('pre', cell.source)
635 return self.in_tag('pre', cell.source)
611
636
612 @DocInherit
637 @DocInherit
613 def render_raw(self, cell):
638 def render_raw(self, cell):
614 if self.raw_as_verbatim:
639 if self.raw_as_verbatim:
615 return self.in_tag('pre', cell.source)
640 return self.in_tag('pre', cell.source)
616 else:
641 else:
617 return [cell.source]
642 return [cell.source]
618
643
619 @DocInherit
644 @DocInherit
620 def render_pyout(self, output):
645 def render_pyout(self, output):
621 lines = ['<tr><td><tt>Out[<b>%s</b>]:</tt></td></tr>' %
646 lines = ['<tr><td><tt>Out[<b>%s</b>]:</tt></td></tr>' %
622 output.prompt_number, '<td>']
647 output.prompt_number, '<td>']
623
648
624 # output is a dictionary like object with type as a key
649 # output is a dictionary like object with type as a key
625 for out_type in ('text', 'latex'):
650 for out_type in ('text', 'latex'):
626 if out_type in output:
651 if out_type in output:
627 lines.extend(self.in_tag('pre', indent(output[out_type])))
652 lines.extend(self.in_tag('pre', indent(output[out_type])))
628
653
629 return lines
654 return lines
630
655
631 @DocInherit
656 @DocInherit
632 def render_pyerr(self, output):
657 def render_pyerr(self, output):
633 # Note: a traceback is a *list* of frames.
658 # Note: a traceback is a *list* of frames.
634 return self.in_tag('pre', remove_ansi('\n'.join(output.traceback)))
659 return self.in_tag('pre', remove_ansi('\n'.join(output.traceback)))
635
660
636 @DocInherit
661 @DocInherit
637 def _img_lines(self, img_file):
662 def _img_lines(self, img_file):
638 return ['<img src="%s">' % img_file, '']
663 return ['<img src="%s">' % img_file, '']
639
664
640 @DocInherit
665 @DocInherit
641 def render_display_format_text(self, output):
666 def render_display_format_text(self, output):
642 return [output.text]
667 return_list(output.text)
643
668
644 @DocInherit
669 @DocInherit
645 def _unknown_lines(self, data):
670 def _unknown_lines(self, data):
646 return ['<h2>Warning:: Unknown cell</h2>'] + self.in_tag('pre', data)
671 return ['<h2>Warning:: Unknown cell</h2>'] + self.in_tag('pre', data)
647
672
648
673
649 def render_display_format_text(self, output):
674 def render_display_format_text(self, output):
650 """render the text part of an output
675 """render the text part of an output
651
676
652 Returns list.
677 Returns list.
653 """
678 """
654 if type(output.text) == type([]):
679 return_list(output.text)
655 return output.text
656 return [output.text]
657
680
658 def render_display_format_html(self, output):
681 def render_display_format_html(self, output):
659 """render the html part of an output
682 """render the html part of an output
660
683
661 Returns list.
684 Returns list.
662 """
685 """
663 if type(output.html) == type([]):
686 return_list(output.html)
664 return output.html
665 return [output.html]
666
687
667 def render_display_format_latex(self, output):
688 def render_display_format_latex(self, output):
668 """render the latex part of an output
689 """render the latex part of an output
669
690
670 Returns [].
691 Returns [].
671 """
692 """
672 # quickhtml ignores latex
693 # quickhtml ignores latex
673 return []
694 return []
674
695
675 def render_display_format_json(self, output):
696 def render_display_format_json(self, output):
676 """render the json part of an output
697 """render the json part of an output
677
698
678 Returns [].
699 Returns [].
679 """
700 """
680 # quickhtml ignores json
701 # quickhtml ignores json
681 return []
702 return []
682
703
683
704
684 def render_display_format_javascript(self, output):
705 def render_display_format_javascript(self, output):
685 """render the javascript part of an output
706 """render the javascript part of an output
686
707
687 Returns list.
708 Returns list.
688 """
709 """
689 if type(output.javascript) == type([]):
710 return_list(output.javascript)
690 return output.javascript
691 return [output.javascript]
692
711
693
712
694 class ConverterLaTeX(Converter):
713 class ConverterLaTeX(Converter):
695 """Converts a notebook to a .tex file suitable for pdflatex.
714 """Converts a notebook to a .tex file suitable for pdflatex.
696
715
697 Note: this converter *needs*:
716 Note: this converter *needs*:
698
717
699 - `pandoc`: for all conversion of markdown cells. If your notebook only
718 - `pandoc`: for all conversion of markdown cells. If your notebook only
700 has Raw cells, pandoc will not be needed.
719 has Raw cells, pandoc will not be needed.
701
720
702 - `inkscape`: if your notebook has SVG figures. These need to be
721 - `inkscape`: if your notebook has SVG figures. These need to be
703 converted to PDF before inclusion in the TeX file, as LaTeX doesn't
722 converted to PDF before inclusion in the TeX file, as LaTeX doesn't
704 understand SVG natively.
723 understand SVG natively.
705
724
706 You will in general obtain much better final PDF results if you configure
725 You will in general obtain much better final PDF results if you configure
707 the matplotlib backend to create SVG output with
726 the matplotlib backend to create SVG output with
708
727
709 %config InlineBackend.figure_format = 'svg'
728 %config InlineBackend.figure_format = 'svg'
710
729
711 (or set the equivalent flag at startup or in your configuration profile).
730 (or set the equivalent flag at startup or in your configuration profile).
712 """
731 """
713 extension = 'tex'
732 extension = 'tex'
714 documentclass = 'article'
733 documentclass = 'article'
715 documentclass_options = '11pt,english'
734 documentclass_options = '11pt,english'
716 heading_map = {1: r'\section',
735 heading_map = {1: r'\section',
717 2: r'\subsection',
736 2: r'\subsection',
718 3: r'\subsubsection',
737 3: r'\subsubsection',
719 4: r'\paragraph',
738 4: r'\paragraph',
720 5: r'\subparagraph',
739 5: r'\subparagraph',
721 6: r'\subparagraph'}
740 6: r'\subparagraph'}
722
741
723 def in_env(self, environment, lines):
742 def in_env(self, environment, lines):
724 """Return list of environment lines for input lines
743 """Return list of environment lines for input lines
725
744
726 Parameters
745 Parameters
727 ----------
746 ----------
728 env : string
747 env : string
729 Name of the environment to bracket with begin/end.
748 Name of the environment to bracket with begin/end.
730
749
731 lines: """
750 lines: """
732 out = [ur'\begin{%s}' % environment]
751 out = [ur'\begin{%s}' % environment]
733 if isinstance(lines, basestring):
752 if isinstance(lines, basestring):
734 out.append(lines)
753 out.append(lines)
735 else: # list
754 else: # list
736 out.extend(lines)
755 out.extend(lines)
737 out.append(ur'\end{%s}' % environment)
756 out.append(ur'\end{%s}' % environment)
738 return out
757 return out
739
758
740 def convert(self):
759 def convert(self):
741 # The main body is done by the logic in the parent class, and that's
760 # The main body is done by the logic in the parent class, and that's
742 # all we need if preamble support has been turned off.
761 # all we need if preamble support has been turned off.
743 body = super(ConverterLaTeX, self).convert()
762 body = super(ConverterLaTeX, self).convert()
744 if not self.with_preamble:
763 if not self.with_preamble:
745 return body
764 return body
746 # But if preamble is on, then we need to construct a proper, standalone
765 # But if preamble is on, then we need to construct a proper, standalone
747 # tex file.
766 # tex file.
748
767
749 # Tag the document at the top and set latex class
768 # Tag the document at the top and set latex class
750 final = [ r'%% This file was auto-generated by IPython, do NOT edit',
769 final = [ r'%% This file was auto-generated by IPython, do NOT edit',
751 r'%% Conversion from the original notebook file:',
770 r'%% Conversion from the original notebook file:',
752 r'%% {0}'.format(self.infile),
771 r'%% {0}'.format(self.infile),
753 r'%%',
772 r'%%',
754 r'\documentclass[%s]{%s}' % (self.documentclass_options,
773 r'\documentclass[%s]{%s}' % (self.documentclass_options,
755 self.documentclass),
774 self.documentclass),
756 '',
775 '',
757 ]
776 ]
758 # Load our own preamble, which is stored next to the main file. We
777 # Load our own preamble, which is stored next to the main file. We
759 # need to be careful in case the script entry point is a symlink
778 # need to be careful in case the script entry point is a symlink
760 myfile = __file__ if not os.path.islink(__file__) else \
779 myfile = __file__ if not os.path.islink(__file__) else \
761 os.readlink(__file__)
780 os.readlink(__file__)
762 with open(os.path.join(os.path.dirname(myfile), 'preamble.tex')) as f:
781 with open(os.path.join(os.path.dirname(myfile), 'preamble.tex')) as f:
763 final.append(f.read())
782 final.append(f.read())
764
783
765 # Load any additional user-supplied preamble
784 # Load any additional user-supplied preamble
766 if self.user_preamble:
785 if self.user_preamble:
767 final.extend(['', '%% Adding user preamble from file:',
786 final.extend(['', '%% Adding user preamble from file:',
768 '%% {0}'.format(self.user_preamble), ''])
787 '%% {0}'.format(self.user_preamble), ''])
769 with open(self.user_preamble) as f:
788 with open(self.user_preamble) as f:
770 final.append(f.read())
789 final.append(f.read())
771
790
772 # Include document body
791 # Include document body
773 final.extend([ r'\begin{document}', '',
792 final.extend([ r'\begin{document}', '',
774 body,
793 body,
775 r'\end{document}', ''])
794 r'\end{document}', ''])
776 # Retun value must be a string
795 # Retun value must be a string
777 return '\n'.join(final)
796 return '\n'.join(final)
778
797
779 @DocInherit
798 @DocInherit
780 def render_heading(self, cell):
799 def render_heading(self, cell):
781 marker = self.heading_map[cell.level]
800 marker = self.heading_map[cell.level]
782 return ['%s{%s}' % (marker, cell.source) ]
801 return ['%s{%s}' % (marker, cell.source) ]
783
802
784 @DocInherit
803 @DocInherit
785 def render_code(self, cell):
804 def render_code(self, cell):
786 if not cell.input:
805 if not cell.input:
787 return []
806 return []
788
807
789 # Cell codes first carry input code, we use lstlisting for that
808 # Cell codes first carry input code, we use lstlisting for that
790 lines = [ur'\begin{codecell}']
809 lines = [ur'\begin{codecell}']
791
810
792 lines.extend(self.in_env('codeinput',
811 lines.extend(self.in_env('codeinput',
793 self.in_env('lstlisting', cell.input)))
812 self.in_env('lstlisting', cell.input)))
794
813
795 outlines = []
814 outlines = []
796 for output in cell.outputs:
815 for output in cell.outputs:
797 conv_fn = self.dispatch(output.output_type)
816 conv_fn = self.dispatch(output.output_type)
798 outlines.extend(conv_fn(output))
817 outlines.extend(conv_fn(output))
799
818
800 # And then output of many possible types; use a frame for all of it.
819 # And then output of many possible types; use a frame for all of it.
801 if outlines:
820 if outlines:
802 lines.extend(self.in_env('codeoutput', outlines))
821 lines.extend(self.in_env('codeoutput', outlines))
803
822
804 lines.append(ur'\end{codecell}')
823 lines.append(ur'\end{codecell}')
805
824
806 return lines
825 return lines
807
826
808
827
809 @DocInherit
828 @DocInherit
810 def _img_lines(self, img_file):
829 def _img_lines(self, img_file):
811 return self.in_env('center',
830 return self.in_env('center',
812 [r'\includegraphics[width=6in]{%s}' % img_file, r'\par'])
831 [r'\includegraphics[width=6in]{%s}' % img_file, r'\par'])
813
832
814 def _svg_lines(self, img_file):
833 def _svg_lines(self, img_file):
815 base_file = os.path.splitext(img_file)[0]
834 base_file = os.path.splitext(img_file)[0]
816 pdf_file = base_file + '.pdf'
835 pdf_file = base_file + '.pdf'
817 subprocess.check_call([ inkscape, '--export-pdf=%s' % pdf_file,
836 subprocess.check_call([ inkscape, '--export-pdf=%s' % pdf_file,
818 img_file])
837 img_file])
819 return self._img_lines(pdf_file)
838 return self._img_lines(pdf_file)
820
839
821 @DocInherit
840 @DocInherit
822 def render_markdown(self, cell):
841 def render_markdown(self, cell):
823 return [markdown2latex(cell.source)]
842 return [markdown2latex(cell.source)]
824
843
825 @DocInherit
844 @DocInherit
826 def render_pyout(self, output):
845 def render_pyout(self, output):
827 lines = []
846 lines = []
828
847
829 # output is a dictionary like object with type as a key
848 # output is a dictionary like object with type as a key
830 if 'latex' in output:
849 if 'latex' in output:
831 lines.extend(output.latex)
850 lines.extend(output.latex)
832
851
833 if 'text' in output:
852 if 'text' in output:
834 lines.extend(self.in_env('verbatim', output.text))
853 lines.extend(self.in_env('verbatim', output.text))
835
854
836 return lines
855 return lines
837
856
838 @DocInherit
857 @DocInherit
839 def render_pyerr(self, output):
858 def render_pyerr(self, output):
840 # Note: a traceback is a *list* of frames.
859 # Note: a traceback is a *list* of frames.
841 return self.in_env('traceback',
860 return self.in_env('traceback',
842 self.in_env('verbatim',
861 self.in_env('verbatim',
843 remove_ansi('\n'.join(output.traceback))))
862 remove_ansi('\n'.join(output.traceback))))
844
863
845 @DocInherit
864 @DocInherit
846 def render_raw(self, cell):
865 def render_raw(self, cell):
847 if self.raw_as_verbatim:
866 if self.raw_as_verbatim:
848 return self.in_env('verbatim', cell.source)
867 return self.in_env('verbatim', cell.source)
849 else:
868 else:
850 return [cell.source]
869 return [cell.source]
851
870
852 @DocInherit
871 @DocInherit
853 def _unknown_lines(self, data):
872 def _unknown_lines(self, data):
854 return [r'{\vspace{5mm}\bf WARNING:: unknown cell:}'] + \
873 return [r'{\vspace{5mm}\bf WARNING:: unknown cell:}'] + \
855 self.in_env('verbatim', data)
874 self.in_env('verbatim', data)
856
875
857
876
858 @DocInherit
877 @DocInherit
859 def render_display_format_text(self, output):
878 def render_display_format_text(self, output):
860 lines = []
879 lines = []
861
880
862 if 'text' in output:
881 if 'text' in output:
863 lines.extend(self.in_env('verbatim', output.text.strip()))
882 lines.extend(self.in_env('verbatim', output.text.strip()))
864
883
865 return lines
884 return lines
866
885
867 def render_display_format_html(self, output):
886 def render_display_format_html(self, output):
868 """render the html part of an output
887 """render the html part of an output
869
888
870 Returns [].
889 Returns [].
871 """
890 """
872 return []
891 return []
873
892
874 def render_display_format_latex(self, output):
893 def render_display_format_latex(self, output):
875 """render the latex part of an output
894 """render the latex part of an output
876
895
877 Returns list.
896 Returns list.
878 """
897 """
879 if type(output.latex) == type([]):
898 if type(output.latex) == type([]):
880 return output.latex
899 return output.latex
881 return [output.latex]
900 return [output.latex]
882
901
883 def render_display_format_json(self, output):
902 def render_display_format_json(self, output):
884 """render the json part of an output
903 """render the json part of an output
885
904
886 Returns [].
905 Returns [].
887 """
906 """
888 # latex ignores json
907 # latex ignores json
889 return []
908 return []
890
909
891
910
892 def render_display_format_javascript(self, output):
911 def render_display_format_javascript(self, output):
893 """render the javascript part of an output
912 """render the javascript part of an output
894
913
895 Returns [].
914 Returns [].
896 """
915 """
897 # latex ignores javascript
916 # latex ignores javascript
898 return []
917 return []
899
918
900 class ConverterNotebook(Converter):
919 class ConverterNotebook(Converter):
901 """
920 """
902 A converter that is essentially a null-op.
921 A converter that is essentially a null-op.
903 This exists so it can be subclassed
922 This exists so it can be subclassed
904 for custom handlers of .ipynb files
923 for custom handlers of .ipynb files
905 that create new .ipynb files.
924 that create new .ipynb files.
906
925
907 What distinguishes this from JSONWriter is that
926 What distinguishes this from JSONWriter is that
908 subclasses can specify what to do with each type of cell.
927 subclasses can specify what to do with each type of cell.
909
928
910 Writes out a notebook file.
929 Writes out a notebook file.
911
930
912 """
931 """
913 extension = 'ipynb'
932 extension = 'ipynb'
914
933
915 def __init__(self, infile, outbase):
934 def __init__(self, infile, outbase):
916 Converter.__init__(self, infile)
935 Converter.__init__(self, infile)
917 self.outbase = outbase
936 self.outbase = outbase
918 rmtree(self.files_dir)
937 rmtree(self.files_dir)
919
938
920 def convert(self):
939 def convert(self):
921 return json.dumps(json.loads(Converter.convert(self, ',')), indent=1, sort_keys=True)
940 return json.dumps(json.loads(Converter.convert(self, ',')), indent=1, sort_keys=True)
922
941
923 def optional_header(self):
942 def optional_header(self):
924 s = \
943 s = \
925 """{
944 """{
926 "metadata": {
945 "metadata": {
927 "name": "%(name)s"
946 "name": "%(name)s"
928 },
947 },
929 "nbformat": 3,
948 "nbformat": 3,
930 "worksheets": [
949 "worksheets": [
931 {
950 {
932 "cells": [""" % {'name':self.outbase}
951 "cells": [""" % {'name':self.outbase}
933
952
934 return s.split('\n')
953 return s.split('\n')
935
954
936 def optional_footer(self):
955 def optional_footer(self):
937 s = \
956 s = \
938 """]
957 """]
939 }
958 }
940 ]
959 ]
941 }"""
960 }"""
942 return s.split('\n')
961 return s.split('\n')
943
962
944 @DocInherit
963 @DocInherit
945 def render_heading(self, cell):
964 def render_heading(self, cell):
946 return cell_to_lines(cell)
965 return cell_to_lines(cell)
947
966
948 @DocInherit
967 @DocInherit
949 def render_code(self, cell):
968 def render_code(self, cell):
950 return cell_to_lines(cell)
969 return cell_to_lines(cell)
951
970
952 @DocInherit
971 @DocInherit
953 def render_markdown(self, cell):
972 def render_markdown(self, cell):
954 return cell_to_lines(cell)
973 return cell_to_lines(cell)
955
974
956 @DocInherit
975 @DocInherit
957 def render_raw(self, cell):
976 def render_raw(self, cell):
958 return cell_to_lines(cell)
977 return cell_to_lines(cell)
959
978
960 @DocInherit
979 @DocInherit
961 def render_pyout(self, output):
980 def render_pyout(self, output):
962 return cell_to_lines(cell)
981 return cell_to_lines(cell)
963
982
964 @DocInherit
983 @DocInherit
965 def render_pyerr(self, output):
984 def render_pyerr(self, output):
966 return cell_to_lines(cell)
985 return cell_to_lines(cell)
967
986
968 @DocInherit
987 @DocInherit
969 def render_display_format_text(self, output):
988 def render_display_format_text(self, output):
970 return [output.text]
989 return [output.text]
971
990
972 def render_display_format_html(self, output):
991 def render_display_format_html(self, output):
973 """render the html part of an output
992 """render the html part of an output
974
993
975 Returns [].
994 Returns [].
976 """
995 """
977 return [output.html]
996 return [output.html]
978
997
979 def render_display_format_latex(self, output):
998 def render_display_format_latex(self, output):
980 """render the latex part of an output
999 """render the latex part of an output
981
1000
982 Returns list.
1001 Returns list.
983 """
1002 """
984 return [output.latex]
1003 return [output.latex]
985
1004
986 def render_display_format_json(self, output):
1005 def render_display_format_json(self, output):
987 """render the json part of an output
1006 """render the json part of an output
988
1007
989 Returns [].
1008 Returns [].
990 """
1009 """
991 return [output.json]
1010 return [output.json]
992
1011
993
1012
994 def render_display_format_javascript(self, output):
1013 def render_display_format_javascript(self, output):
995 """render the javascript part of an output
1014 """render the javascript part of an output
996
1015
997 Returns [].
1016 Returns [].
998 """
1017 """
999 return [output.javascript]
1018 return [output.javascript]
1000
1019
1001 #-----------------------------------------------------------------------------
1020 #-----------------------------------------------------------------------------
1002 # Standalone conversion functions
1021 # Standalone conversion functions
1003 #-----------------------------------------------------------------------------
1022 #-----------------------------------------------------------------------------
1004
1023
1005 def rst2simplehtml(infile):
1024 def rst2simplehtml(infile):
1006 """Convert a rst file to simplified html suitable for blogger.
1025 """Convert a rst file to simplified html suitable for blogger.
1007
1026
1008 This just runs rst2html with certain parameters to produce really simple
1027 This just runs rst2html with certain parameters to produce really simple
1009 html and strips the document header, so the resulting file can be easily
1028 html and strips the document header, so the resulting file can be easily
1010 pasted into a blogger edit window.
1029 pasted into a blogger edit window.
1011 """
1030 """
1012
1031
1013 # This is the template for the rst2html call that produces the cleanest,
1032 # This is the template for the rst2html call that produces the cleanest,
1014 # simplest html I could find. This should help in making it easier to
1033 # simplest html I could find. This should help in making it easier to
1015 # paste into the blogspot html window, though I'm still having problems
1034 # paste into the blogspot html window, though I'm still having problems
1016 # with linebreaks there...
1035 # with linebreaks there...
1017 cmd_template = ("rst2html --link-stylesheet --no-xml-declaration "
1036 cmd_template = ("rst2html --link-stylesheet --no-xml-declaration "
1018 "--no-generator --no-datestamp --no-source-link "
1037 "--no-generator --no-datestamp --no-source-link "
1019 "--no-toc-backlinks --no-section-numbering "
1038 "--no-toc-backlinks --no-section-numbering "
1020 "--strip-comments ")
1039 "--strip-comments ")
1021
1040
1022 cmd = "%s %s" % (cmd_template, infile)
1041 cmd = "%s %s" % (cmd_template, infile)
1023 proc = subprocess.Popen(cmd,
1042 proc = subprocess.Popen(cmd,
1024 stdout=subprocess.PIPE,
1043 stdout=subprocess.PIPE,
1025 stderr=subprocess.PIPE,
1044 stderr=subprocess.PIPE,
1026 shell=True)
1045 shell=True)
1027 html, stderr = proc.communicate()
1046 html, stderr = proc.communicate()
1028 if stderr:
1047 if stderr:
1029 raise IOError(stderr)
1048 raise IOError(stderr)
1030
1049
1031 # Make an iterator so breaking out holds state. Our implementation of
1050 # Make an iterator so breaking out holds state. Our implementation of
1032 # searching for the html body below is basically a trivial little state
1051 # searching for the html body below is basically a trivial little state
1033 # machine, so we need this.
1052 # machine, so we need this.
1034 walker = iter(html.splitlines())
1053 walker = iter(html.splitlines())
1035
1054
1036 # Find start of main text, break out to then print until we find end /div.
1055 # Find start of main text, break out to then print until we find end /div.
1037 # This may only work if there's a real title defined so we get a 'div class'
1056 # This may only work if there's a real title defined so we get a 'div class'
1038 # tag, I haven't really tried.
1057 # tag, I haven't really tried.
1039 for line in walker:
1058 for line in walker:
1040 if line.startswith('<body>'):
1059 if line.startswith('<body>'):
1041 break
1060 break
1042
1061
1043 newfname = os.path.splitext(infile)[0] + '.html'
1062 newfname = os.path.splitext(infile)[0] + '.html'
1044 with open(newfname, 'w') as f:
1063 with open(newfname, 'w') as f:
1045 for line in walker:
1064 for line in walker:
1046 if line.startswith('</body>'):
1065 if line.startswith('</body>'):
1047 break
1066 break
1048 f.write(line)
1067 f.write(line)
1049 f.write('\n')
1068 f.write('\n')
1050
1069
1051 return newfname
1070 return newfname
1052
1071
1072
1073 def md2html(infile):
1074 """Convert a markdown file to simplified html suitable for blogger.
1075
1076 """
1077
1078 proc = subprocess.Popen(['markdown', infile],
1079 stdout=subprocess.PIPE,
1080 stderr=subprocess.PIPE)
1081 html, stderr = proc.communicate()
1082 if stderr:
1083 raise IOError(stderr)
1084
1085 from pygments.formatters import HtmlFormatter
1086 css = HtmlFormatter().get_style_defs('.highlight')
1087
1088 template = """
1089 <!DOCTYPE HTML>
1090 <html>
1091
1092 <head>
1093 <title>{infile}</title>
1094
1095 <style type="text/css">
1096 {css}
1097 </style>
1098
1099 </head>
1100
1101 <body>
1102 {html}
1103 </body>
1104
1105 </html>
1106 """
1107 full_html = template.format(**locals())
1108 newfname = os.path.splitext(infile)[0] + '.html'
1109 with open(newfname, 'w') as f:
1110 f.write(full_html)
1111
1112 return newfname
1113
1053 #-----------------------------------------------------------------------------
1114 #-----------------------------------------------------------------------------
1054 # Cell-level functions -- similar to IPython.nbformat.v3.rwbase functions
1115 # Cell-level functions -- similar to IPython.nbformat.v3.rwbase functions
1055 # but at cell level instead of whole notebook level
1116 # but at cell level instead of whole notebook level
1056 #-----------------------------------------------------------------------------
1117 #-----------------------------------------------------------------------------
1057
1118
1058 def writes_cell(cell, **kwargs):
1119 def writes_cell(cell, **kwargs):
1059 kwargs['cls'] = BytesEncoder
1120 kwargs['cls'] = BytesEncoder
1060 kwargs['indent'] = 3
1121 kwargs['indent'] = 3
1061 kwargs['sort_keys'] = True
1122 kwargs['sort_keys'] = True
1062 kwargs['separators'] = (',',': ')
1123 kwargs['separators'] = (',',': ')
1063 if kwargs.pop('split_lines', True):
1124 if kwargs.pop('split_lines', True):
1064 cell = split_lines_cell(copy.deepcopy(cell))
1125 cell = split_lines_cell(copy.deepcopy(cell))
1065 return py3compat.str_to_unicode(json.dumps(cell, **kwargs), 'utf-8')
1126 return py3compat.str_to_unicode(json.dumps(cell, **kwargs), 'utf-8')
1066
1127
1128
1067 _multiline_outputs = ['text', 'html', 'svg', 'latex', 'javascript', 'json']
1129 _multiline_outputs = ['text', 'html', 'svg', 'latex', 'javascript', 'json']
1130
1131
1068 def split_lines_cell(cell):
1132 def split_lines_cell(cell):
1069 """
1133 """
1070 Split lines within a cell as in
1134 Split lines within a cell as in
1071 IPython.nbformat.v3.rwbase.split_lines
1135 IPython.nbformat.v3.rwbase.split_lines
1072
1136
1073 """
1137 """
1074 if cell.cell_type == 'code':
1138 if cell.cell_type == 'code':
1075 if 'input' in cell and isinstance(cell.input, basestring):
1139 if 'input' in cell and isinstance(cell.input, basestring):
1076 cell.input = (cell.input + '\n').splitlines()
1140 cell.input = (cell.input + '\n').splitlines()
1077 for output in cell.outputs:
1141 for output in cell.outputs:
1078 for key in _multiline_outputs:
1142 for key in _multiline_outputs:
1079 item = output.get(key, None)
1143 item = output.get(key, None)
1080 if isinstance(item, basestring):
1144 if isinstance(item, basestring):
1081 output[key] = (item + '\n').splitlines()
1145 output[key] = (item + '\n').splitlines()
1082 else: # text, heading cell
1146 else: # text, heading cell
1083 for key in ['source', 'rendered']:
1147 for key in ['source', 'rendered']:
1084 item = cell.get(key, None)
1148 item = cell.get(key, None)
1085 if isinstance(item, basestring):
1149 if isinstance(item, basestring):
1086 cell[key] = (item + '\n').splitlines()
1150 cell[key] = (item + '\n').splitlines()
1087 return cell
1151 return cell
1088
1152
1153
1089 def cell_to_lines(cell):
1154 def cell_to_lines(cell):
1090 '''
1155 '''
1091 Write a cell to json, returning the split lines.
1156 Write a cell to json, returning the split lines.
1092 '''
1157 '''
1093 split_lines_cell(cell)
1158 split_lines_cell(cell)
1094 s = writes_cell(cell).strip()
1159 s = writes_cell(cell).strip()
1095 return s.split('\n')
1160 return s.split('\n')
1096
1161
1097
1162
1098 known_formats = "rst (default), html, quick-html, latex, markdown"
1163 known_formats = "rst (default), html, quick-html, latex, markdown"
1099
1164
1100 def main(infile, format='rst'):
1165 def main(infile, format='rst'):
1101 """Convert a notebook to html in one step"""
1166 """Convert a notebook to html in one step"""
1102 # XXX: this is just quick and dirty for now. When adding a new format,
1167 # XXX: this is just quick and dirty for now. When adding a new format,
1103 # make sure to add it to the `known_formats` string above, which gets
1168 # make sure to add it to the `known_formats` string above, which gets
1104 # printed in in the catch-all else, as well as in the help
1169 # printed in in the catch-all else, as well as in the help
1105 if format == 'rst':
1170 if format == 'rst':
1106 converter = ConverterRST(infile)
1171 converter = ConverterRST(infile)
1107 converter.render()
1172 converter.render()
1108 elif format == 'markdown':
1173 elif format == 'markdown':
1109 converter = ConverterMarkdown(infile)
1174 converter = ConverterMarkdown(infile)
1110 converter.render()
1175 converter.render()
1111 elif format == 'html':
1176 elif format == 'html':
1112 #Currently, conversion to html is a 2 step process, nb->rst->html
1177 #Currently, conversion to html is a 2 step process, nb->md->html
1113 converter = ConverterRST(infile)
1178 converter = ConverterMarkdown(infile, True)
1114 rstfname = converter.render()
1179 mdfname = converter.render()
1115 rst2simplehtml(rstfname)
1180 md2html(mdfname)
1116 elif format == 'quick-html':
1181 elif format == 'quick-html':
1117 converter = ConverterQuickHTML(infile)
1182 converter = ConverterQuickHTML(infile)
1118 rstfname = converter.render()
1183 rstfname = converter.render()
1119 elif format == 'latex':
1184 elif format == 'latex':
1120 converter = ConverterLaTeX(infile)
1185 converter = ConverterLaTeX(infile)
1121 latexfname = converter.render()
1186 latexfname = converter.render()
1122 else:
1187 else:
1123 raise SystemExit("Unknown format '%s', " % format +
1188 raise SystemExit("Unknown format '%s', " % format +
1124 "known formats are: " + known_formats)
1189 "known formats are: " + known_formats)
1125
1190
1126 #-----------------------------------------------------------------------------
1191 #-----------------------------------------------------------------------------
1127 # Script main
1192 # Script main
1128 #-----------------------------------------------------------------------------
1193 #-----------------------------------------------------------------------------
1129
1194
1130 if __name__ == '__main__':
1195 if __name__ == '__main__':
1131 parser = argparse.ArgumentParser(description=__doc__,
1196 parser = argparse.ArgumentParser(description=__doc__,
1132 formatter_class=argparse.RawTextHelpFormatter)
1197 formatter_class=argparse.RawTextHelpFormatter)
1133 # TODO: consider passing file like object around, rather than filenames
1198 # TODO: consider passing file like object around, rather than filenames
1134 # would allow us to process stdin, or even http streams
1199 # would allow us to process stdin, or even http streams
1135 #parser.add_argument('infile', nargs='?', type=argparse.FileType('r'), default=sys.stdin)
1200 #parser.add_argument('infile', nargs='?', type=argparse.FileType('r'), default=sys.stdin)
1136
1201
1137 #Require a filename as a positional argument
1202 #Require a filename as a positional argument
1138 parser.add_argument('infile', nargs=1)
1203 parser.add_argument('infile', nargs=1)
1139 parser.add_argument('-f', '--format', default='rst',
1204 parser.add_argument('-f', '--format', default='rst',
1140 help='Output format. Supported formats: \n' +
1205 help='Output format. Supported formats: \n' +
1141 known_formats)
1206 known_formats)
1142 args = parser.parse_args()
1207 args = parser.parse_args()
1143 main(infile=args.infile[0], format=args.format)
1208 main(infile=args.infile[0], format=args.format)
General Comments 0
You need to be logged in to leave comments. Login now