##// END OF EJS Templates
Add first cut of markdown converter.x
Fernando Perez -
Show More
@@ -1,1041 +1,1143 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 class ConverterMarkdown(Converter):
463 extension = 'md'
464
465 @DocInherit
466 def render_heading(self, cell):
467 return ['{0} {1}'.format('#'*cell.level, cell.source), '']
468
469 @DocInherit
470 def render_code(self, cell):
471 if not cell.input:
472 return []
473 lines = []
474 #lines.append('----')
475 lines.extend(['*In[%s]:*' % cell.prompt_number, ''])
476 lines.extend([indent(cell.input), ''])
477 if cell.outputs:
478 lines.extend(['==>', ''])
479 for output in cell.outputs:
480 conv_fn = self.dispatch(output.output_type)
481 lines.extend(conv_fn(output))
482
483 #lines.append('----')
484 lines.append('')
485 return lines
486
487 @DocInherit
488 def render_markdown(self, cell):
489 return [cell.source, '']
490 #return [markdown2rst(cell.source)]
491
492 @DocInherit
493 def render_raw(self, cell):
494 if self.raw_as_verbatim:
495 return [indent(cell.source), '']
496 else:
497 return [cell.source, '']
498
499 @DocInherit
500 def render_pyout(self, output):
501 lines = []
502 #lines.extend(['*Out[%s]:*' % output.prompt_number, ''])
503
504 # output is a dictionary like object with type as a key
505 if 'latex' in output:
506 pass
507
508 if 'text' in output:
509 lines.extend([indent(output.text)])
510
511 lines.append('')
512 return lines
513
514 @DocInherit
515 def render_pyerr(self, output):
516 # Note: a traceback is a *list* of frames.
517 return [indent(remove_ansi('\n'.join(output.traceback))), '']
518
519 @DocInherit
520 def _img_lines(self, img_file):
521 return ['', '![image](%s)' % img_file, '']
522
523 @DocInherit
524 def render_display_format_text(self, output):
525 return [indent(output.text)]
526
527 @DocInherit
528 def _unknown_lines(self, data):
529 return ['Warning: Unknown cell', data]
530
531 def render_display_format_html(self, output):
532 """render the html part of an output
533
534 Returns list.
535 """
536 return [output.html]
537
538 def render_display_format_latex(self, output):
539 """render the latex part of an output
540
541 Returns list.
542 """
543 return ['LaTeX::', indent(output.latex)]
544
545 def render_display_format_json(self, output):
546 """render the json part of an output
547
548 Returns list.
549 """
550 return ['JSON:', indent(output.json)]
551
552
553 def render_display_format_javascript(self, output):
554 """render the javascript part of an output
555
556 Returns list.
557 """
558 return ['JavaScript:', indent(output.javascript)]
559
560
462 class ConverterQuickHTML(Converter):
561 class ConverterQuickHTML(Converter):
463 extension = 'html'
562 extension = 'html'
464
563
465 def in_tag(self, tag, src):
564 def in_tag(self, tag, src):
466 """Return a list of elements bracketed by the given tag"""
565 """Return a list of elements bracketed by the given tag"""
467 return ['<%s>' % tag, src, '</%s>' % tag]
566 return ['<%s>' % tag, src, '</%s>' % tag]
468
567
469 def optional_header(self):
568 def optional_header(self):
470 # XXX: inject the IPython standard CSS into here
569 # XXX: inject the IPython standard CSS into here
471 s = """<html>
570 s = """<html>
472 <head>
571 <head>
473 </head>
572 </head>
474
573
475 <body>
574 <body>
476 """
575 """
477 return s.splitlines()
576 return s.splitlines()
478
577
479 def optional_footer(self):
578 def optional_footer(self):
480 s = """</body>
579 s = """</body>
481 </html>
580 </html>
482 """
581 """
483 return s.splitlines()
582 return s.splitlines()
484
583
485 @DocInherit
584 @DocInherit
486 def render_heading(self, cell):
585 def render_heading(self, cell):
487 marker = cell.level
586 marker = cell.level
488 return ['<h{1}>\n {0}\n</h{1}>'.format(cell.source, marker)]
587 return ['<h{1}>\n {0}\n</h{1}>'.format(cell.source, marker)]
489
588
490 @DocInherit
589 @DocInherit
491 def render_code(self, cell):
590 def render_code(self, cell):
492 if not cell.input:
591 if not cell.input:
493 return []
592 return []
494
593
495 lines = ['<table>']
594 lines = ['<table>']
496 lines.append('<tr><td><tt>In [<b>%s</b>]:</tt></td><td><tt>' % cell.prompt_number)
595 lines.append('<tr><td><tt>In [<b>%s</b>]:</tt></td><td><tt>' % cell.prompt_number)
497 lines.append("<br>\n".join(cell.input.splitlines()))
596 lines.append("<br>\n".join(cell.input.splitlines()))
498 lines.append('</tt></td></tr>')
597 lines.append('</tt></td></tr>')
499
598
500 for output in cell.outputs:
599 for output in cell.outputs:
501 lines.append('<tr><td></td><td>')
600 lines.append('<tr><td></td><td>')
502 conv_fn = self.dispatch(output.output_type)
601 conv_fn = self.dispatch(output.output_type)
503 lines.extend(conv_fn(output))
602 lines.extend(conv_fn(output))
504 lines.append('</td></tr>')
603 lines.append('</td></tr>')
505
604
506 lines.append('</table>')
605 lines.append('</table>')
507 return lines
606 return lines
508
607
509 @DocInherit
608 @DocInherit
510 def render_markdown(self, cell):
609 def render_markdown(self, cell):
511 return self.in_tag('pre', cell.source)
610 return self.in_tag('pre', cell.source)
512
611
513 @DocInherit
612 @DocInherit
514 def render_raw(self, cell):
613 def render_raw(self, cell):
515 if self.raw_as_verbatim:
614 if self.raw_as_verbatim:
516 return self.in_tag('pre', cell.source)
615 return self.in_tag('pre', cell.source)
517 else:
616 else:
518 return [cell.source]
617 return [cell.source]
519
618
520 @DocInherit
619 @DocInherit
521 def render_pyout(self, output):
620 def render_pyout(self, output):
522 lines = ['<tr><td><tt>Out[<b>%s</b>]:</tt></td></tr>' %
621 lines = ['<tr><td><tt>Out[<b>%s</b>]:</tt></td></tr>' %
523 output.prompt_number, '<td>']
622 output.prompt_number, '<td>']
524
623
525 # output is a dictionary like object with type as a key
624 # output is a dictionary like object with type as a key
526 for out_type in ('text', 'latex'):
625 for out_type in ('text', 'latex'):
527 if out_type in output:
626 if out_type in output:
528 lines.extend(self.in_tag('pre', indent(output[out_type])))
627 lines.extend(self.in_tag('pre', indent(output[out_type])))
529
628
530 return lines
629 return lines
531
630
532 @DocInherit
631 @DocInherit
533 def render_pyerr(self, output):
632 def render_pyerr(self, output):
534 # Note: a traceback is a *list* of frames.
633 # Note: a traceback is a *list* of frames.
535 return self.in_tag('pre', remove_ansi('\n'.join(output.traceback)))
634 return self.in_tag('pre', remove_ansi('\n'.join(output.traceback)))
536
635
537 @DocInherit
636 @DocInherit
538 def _img_lines(self, img_file):
637 def _img_lines(self, img_file):
539 return ['<img src="%s">' % img_file, '']
638 return ['<img src="%s">' % img_file, '']
540
639
541 @DocInherit
640 @DocInherit
542 def render_display_format_text(self, output):
641 def render_display_format_text(self, output):
543 return [output.text]
642 return [output.text]
544
643
545 @DocInherit
644 @DocInherit
546 def _unknown_lines(self, data):
645 def _unknown_lines(self, data):
547 return ['<h2>Warning:: Unknown cell</h2>'] + self.in_tag('pre', data)
646 return ['<h2>Warning:: Unknown cell</h2>'] + self.in_tag('pre', data)
548
647
549
648
550 def render_display_format_text(self, output):
649 def render_display_format_text(self, output):
551 """render the text part of an output
650 """render the text part of an output
552
651
553 Returns list.
652 Returns list.
554 """
653 """
555 if type(output.text) == type([]):
654 if type(output.text) == type([]):
556 return output.text
655 return output.text
557 return [output.text]
656 return [output.text]
558
657
559 def render_display_format_html(self, output):
658 def render_display_format_html(self, output):
560 """render the html part of an output
659 """render the html part of an output
561
660
562 Returns list.
661 Returns list.
563 """
662 """
564 if type(output.html) == type([]):
663 if type(output.html) == type([]):
565 return output.html
664 return output.html
566 return [output.html]
665 return [output.html]
567
666
568 def render_display_format_latex(self, output):
667 def render_display_format_latex(self, output):
569 """render the latex part of an output
668 """render the latex part of an output
570
669
571 Returns [].
670 Returns [].
572 """
671 """
573 # quickhtml ignores latex
672 # quickhtml ignores latex
574 return []
673 return []
575
674
576 def render_display_format_json(self, output):
675 def render_display_format_json(self, output):
577 """render the json part of an output
676 """render the json part of an output
578
677
579 Returns [].
678 Returns [].
580 """
679 """
581 # quickhtml ignores json
680 # quickhtml ignores json
582 return []
681 return []
583
682
584
683
585 def render_display_format_javascript(self, output):
684 def render_display_format_javascript(self, output):
586 """render the javascript part of an output
685 """render the javascript part of an output
587
686
588 Returns list.
687 Returns list.
589 """
688 """
590 if type(output.javascript) == type([]):
689 if type(output.javascript) == type([]):
591 return output.javascript
690 return output.javascript
592 return [output.javascript]
691 return [output.javascript]
593
692
594
693
595 class ConverterLaTeX(Converter):
694 class ConverterLaTeX(Converter):
596 """Converts a notebook to a .tex file suitable for pdflatex.
695 """Converts a notebook to a .tex file suitable for pdflatex.
597
696
598 Note: this converter *needs*:
697 Note: this converter *needs*:
599
698
600 - `pandoc`: for all conversion of markdown cells. If your notebook only
699 - `pandoc`: for all conversion of markdown cells. If your notebook only
601 has Raw cells, pandoc will not be needed.
700 has Raw cells, pandoc will not be needed.
602
701
603 - `inkscape`: if your notebook has SVG figures. These need to be
702 - `inkscape`: if your notebook has SVG figures. These need to be
604 converted to PDF before inclusion in the TeX file, as LaTeX doesn't
703 converted to PDF before inclusion in the TeX file, as LaTeX doesn't
605 understand SVG natively.
704 understand SVG natively.
606
705
607 You will in general obtain much better final PDF results if you configure
706 You will in general obtain much better final PDF results if you configure
608 the matplotlib backend to create SVG output with
707 the matplotlib backend to create SVG output with
609
708
610 %config InlineBackend.figure_format = 'svg'
709 %config InlineBackend.figure_format = 'svg'
611
710
612 (or set the equivalent flag at startup or in your configuration profile).
711 (or set the equivalent flag at startup or in your configuration profile).
613 """
712 """
614 extension = 'tex'
713 extension = 'tex'
615 documentclass = 'article'
714 documentclass = 'article'
616 documentclass_options = '11pt,english'
715 documentclass_options = '11pt,english'
617 heading_map = {1: r'\section',
716 heading_map = {1: r'\section',
618 2: r'\subsection',
717 2: r'\subsection',
619 3: r'\subsubsection',
718 3: r'\subsubsection',
620 4: r'\paragraph',
719 4: r'\paragraph',
621 5: r'\subparagraph',
720 5: r'\subparagraph',
622 6: r'\subparagraph'}
721 6: r'\subparagraph'}
623
722
624 def in_env(self, environment, lines):
723 def in_env(self, environment, lines):
625 """Return list of environment lines for input lines
724 """Return list of environment lines for input lines
626
725
627 Parameters
726 Parameters
628 ----------
727 ----------
629 env : string
728 env : string
630 Name of the environment to bracket with begin/end.
729 Name of the environment to bracket with begin/end.
631
730
632 lines: """
731 lines: """
633 out = [ur'\begin{%s}' % environment]
732 out = [ur'\begin{%s}' % environment]
634 if isinstance(lines, basestring):
733 if isinstance(lines, basestring):
635 out.append(lines)
734 out.append(lines)
636 else: # list
735 else: # list
637 out.extend(lines)
736 out.extend(lines)
638 out.append(ur'\end{%s}' % environment)
737 out.append(ur'\end{%s}' % environment)
639 return out
738 return out
640
739
641 def convert(self):
740 def convert(self):
642 # The main body is done by the logic in the parent class, and that's
741 # The main body is done by the logic in the parent class, and that's
643 # all we need if preamble support has been turned off.
742 # all we need if preamble support has been turned off.
644 body = super(ConverterLaTeX, self).convert()
743 body = super(ConverterLaTeX, self).convert()
645 if not self.with_preamble:
744 if not self.with_preamble:
646 return body
745 return body
647 # But if preamble is on, then we need to construct a proper, standalone
746 # But if preamble is on, then we need to construct a proper, standalone
648 # tex file.
747 # tex file.
649
748
650 # Tag the document at the top and set latex class
749 # Tag the document at the top and set latex class
651 final = [ r'%% This file was auto-generated by IPython, do NOT edit',
750 final = [ r'%% This file was auto-generated by IPython, do NOT edit',
652 r'%% Conversion from the original notebook file:',
751 r'%% Conversion from the original notebook file:',
653 r'%% {0}'.format(self.infile),
752 r'%% {0}'.format(self.infile),
654 r'%%',
753 r'%%',
655 r'\documentclass[%s]{%s}' % (self.documentclass_options,
754 r'\documentclass[%s]{%s}' % (self.documentclass_options,
656 self.documentclass),
755 self.documentclass),
657 '',
756 '',
658 ]
757 ]
659 # Load our own preamble, which is stored next to the main file. We
758 # Load our own preamble, which is stored next to the main file. We
660 # need to be careful in case the script entry point is a symlink
759 # need to be careful in case the script entry point is a symlink
661 myfile = __file__ if not os.path.islink(__file__) else \
760 myfile = __file__ if not os.path.islink(__file__) else \
662 os.readlink(__file__)
761 os.readlink(__file__)
663 with open(os.path.join(os.path.dirname(myfile), 'preamble.tex')) as f:
762 with open(os.path.join(os.path.dirname(myfile), 'preamble.tex')) as f:
664 final.append(f.read())
763 final.append(f.read())
665
764
666 # Load any additional user-supplied preamble
765 # Load any additional user-supplied preamble
667 if self.user_preamble:
766 if self.user_preamble:
668 final.extend(['', '%% Adding user preamble from file:',
767 final.extend(['', '%% Adding user preamble from file:',
669 '%% {0}'.format(self.user_preamble), ''])
768 '%% {0}'.format(self.user_preamble), ''])
670 with open(self.user_preamble) as f:
769 with open(self.user_preamble) as f:
671 final.append(f.read())
770 final.append(f.read())
672
771
673 # Include document body
772 # Include document body
674 final.extend([ r'\begin{document}', '',
773 final.extend([ r'\begin{document}', '',
675 body,
774 body,
676 r'\end{document}', ''])
775 r'\end{document}', ''])
677 # Retun value must be a string
776 # Retun value must be a string
678 return '\n'.join(final)
777 return '\n'.join(final)
679
778
680 @DocInherit
779 @DocInherit
681 def render_heading(self, cell):
780 def render_heading(self, cell):
682 marker = self.heading_map[cell.level]
781 marker = self.heading_map[cell.level]
683 return ['%s{%s}' % (marker, cell.source) ]
782 return ['%s{%s}' % (marker, cell.source) ]
684
783
685 @DocInherit
784 @DocInherit
686 def render_code(self, cell):
785 def render_code(self, cell):
687 if not cell.input:
786 if not cell.input:
688 return []
787 return []
689
788
690 # Cell codes first carry input code, we use lstlisting for that
789 # Cell codes first carry input code, we use lstlisting for that
691 lines = [ur'\begin{codecell}']
790 lines = [ur'\begin{codecell}']
692
791
693 lines.extend(self.in_env('codeinput',
792 lines.extend(self.in_env('codeinput',
694 self.in_env('lstlisting', cell.input)))
793 self.in_env('lstlisting', cell.input)))
695
794
696 outlines = []
795 outlines = []
697 for output in cell.outputs:
796 for output in cell.outputs:
698 conv_fn = self.dispatch(output.output_type)
797 conv_fn = self.dispatch(output.output_type)
699 outlines.extend(conv_fn(output))
798 outlines.extend(conv_fn(output))
700
799
701 # And then output of many possible types; use a frame for all of it.
800 # And then output of many possible types; use a frame for all of it.
702 if outlines:
801 if outlines:
703 lines.extend(self.in_env('codeoutput', outlines))
802 lines.extend(self.in_env('codeoutput', outlines))
704
803
705 lines.append(ur'\end{codecell}')
804 lines.append(ur'\end{codecell}')
706
805
707 return lines
806 return lines
708
807
709
808
710 @DocInherit
809 @DocInherit
711 def _img_lines(self, img_file):
810 def _img_lines(self, img_file):
712 return self.in_env('center',
811 return self.in_env('center',
713 [r'\includegraphics[width=6in]{%s}' % img_file, r'\par'])
812 [r'\includegraphics[width=6in]{%s}' % img_file, r'\par'])
714
813
715 def _svg_lines(self, img_file):
814 def _svg_lines(self, img_file):
716 base_file = os.path.splitext(img_file)[0]
815 base_file = os.path.splitext(img_file)[0]
717 pdf_file = base_file + '.pdf'
816 pdf_file = base_file + '.pdf'
718 subprocess.check_call([ inkscape, '--export-pdf=%s' % pdf_file,
817 subprocess.check_call([ inkscape, '--export-pdf=%s' % pdf_file,
719 img_file])
818 img_file])
720 return self._img_lines(pdf_file)
819 return self._img_lines(pdf_file)
721
820
722 @DocInherit
821 @DocInherit
723 def render_markdown(self, cell):
822 def render_markdown(self, cell):
724 return [markdown2latex(cell.source)]
823 return [markdown2latex(cell.source)]
725
824
726 @DocInherit
825 @DocInherit
727 def render_pyout(self, output):
826 def render_pyout(self, output):
728 lines = []
827 lines = []
729
828
730 # output is a dictionary like object with type as a key
829 # output is a dictionary like object with type as a key
731 if 'latex' in output:
830 if 'latex' in output:
732 lines.extend(output.latex)
831 lines.extend(output.latex)
733
832
734 if 'text' in output:
833 if 'text' in output:
735 lines.extend(self.in_env('verbatim', output.text))
834 lines.extend(self.in_env('verbatim', output.text))
736
835
737 return lines
836 return lines
738
837
739 @DocInherit
838 @DocInherit
740 def render_pyerr(self, output):
839 def render_pyerr(self, output):
741 # Note: a traceback is a *list* of frames.
840 # Note: a traceback is a *list* of frames.
742 return self.in_env('traceback',
841 return self.in_env('traceback',
743 self.in_env('verbatim',
842 self.in_env('verbatim',
744 remove_ansi('\n'.join(output.traceback))))
843 remove_ansi('\n'.join(output.traceback))))
745
844
746 @DocInherit
845 @DocInherit
747 def render_raw(self, cell):
846 def render_raw(self, cell):
748 if self.raw_as_verbatim:
847 if self.raw_as_verbatim:
749 return self.in_env('verbatim', cell.source)
848 return self.in_env('verbatim', cell.source)
750 else:
849 else:
751 return [cell.source]
850 return [cell.source]
752
851
753 @DocInherit
852 @DocInherit
754 def _unknown_lines(self, data):
853 def _unknown_lines(self, data):
755 return [r'{\vspace{5mm}\bf WARNING:: unknown cell:}'] + \
854 return [r'{\vspace{5mm}\bf WARNING:: unknown cell:}'] + \
756 self.in_env('verbatim', data)
855 self.in_env('verbatim', data)
757
856
758
857
759 @DocInherit
858 @DocInherit
760 def render_display_format_text(self, output):
859 def render_display_format_text(self, output):
761 lines = []
860 lines = []
762
861
763 if 'text' in output:
862 if 'text' in output:
764 lines.extend(self.in_env('verbatim', output.text.strip()))
863 lines.extend(self.in_env('verbatim', output.text.strip()))
765
864
766 return lines
865 return lines
767
866
768 def render_display_format_html(self, output):
867 def render_display_format_html(self, output):
769 """render the html part of an output
868 """render the html part of an output
770
869
771 Returns [].
870 Returns [].
772 """
871 """
773 return []
872 return []
774
873
775 def render_display_format_latex(self, output):
874 def render_display_format_latex(self, output):
776 """render the latex part of an output
875 """render the latex part of an output
777
876
778 Returns list.
877 Returns list.
779 """
878 """
780 if type(output.latex) == type([]):
879 if type(output.latex) == type([]):
781 return output.latex
880 return output.latex
782 return [output.latex]
881 return [output.latex]
783
882
784 def render_display_format_json(self, output):
883 def render_display_format_json(self, output):
785 """render the json part of an output
884 """render the json part of an output
786
885
787 Returns [].
886 Returns [].
788 """
887 """
789 # latex ignores json
888 # latex ignores json
790 return []
889 return []
791
890
792
891
793 def render_display_format_javascript(self, output):
892 def render_display_format_javascript(self, output):
794 """render the javascript part of an output
893 """render the javascript part of an output
795
894
796 Returns [].
895 Returns [].
797 """
896 """
798 # latex ignores javascript
897 # latex ignores javascript
799 return []
898 return []
800
899
801 class ConverterNotebook(Converter):
900 class ConverterNotebook(Converter):
802 """
901 """
803 A converter that is essentially a null-op.
902 A converter that is essentially a null-op.
804 This exists so it can be subclassed
903 This exists so it can be subclassed
805 for custom handlers of .ipynb files
904 for custom handlers of .ipynb files
806 that create new .ipynb files.
905 that create new .ipynb files.
807
906
808 What distinguishes this from JSONWriter is that
907 What distinguishes this from JSONWriter is that
809 subclasses can specify what to do with each type of cell.
908 subclasses can specify what to do with each type of cell.
810
909
811 Writes out a notebook file.
910 Writes out a notebook file.
812
911
813 """
912 """
814 extension = 'ipynb'
913 extension = 'ipynb'
815
914
816 def __init__(self, infile, outbase):
915 def __init__(self, infile, outbase):
817 Converter.__init__(self, infile)
916 Converter.__init__(self, infile)
818 self.outbase = outbase
917 self.outbase = outbase
819 rmtree(self.files_dir)
918 rmtree(self.files_dir)
820
919
821 def convert(self):
920 def convert(self):
822 return json.dumps(json.loads(Converter.convert(self, ',')), indent=1, sort_keys=True)
921 return json.dumps(json.loads(Converter.convert(self, ',')), indent=1, sort_keys=True)
823
922
824 def optional_header(self):
923 def optional_header(self):
825 s = \
924 s = \
826 """{
925 """{
827 "metadata": {
926 "metadata": {
828 "name": "%(name)s"
927 "name": "%(name)s"
829 },
928 },
830 "nbformat": 3,
929 "nbformat": 3,
831 "worksheets": [
930 "worksheets": [
832 {
931 {
833 "cells": [""" % {'name':self.outbase}
932 "cells": [""" % {'name':self.outbase}
834
933
835 return s.split('\n')
934 return s.split('\n')
836
935
837 def optional_footer(self):
936 def optional_footer(self):
838 s = \
937 s = \
839 """]
938 """]
840 }
939 }
841 ]
940 ]
842 }"""
941 }"""
843 return s.split('\n')
942 return s.split('\n')
844
943
845 @DocInherit
944 @DocInherit
846 def render_heading(self, cell):
945 def render_heading(self, cell):
847 return cell_to_lines(cell)
946 return cell_to_lines(cell)
848
947
849 @DocInherit
948 @DocInherit
850 def render_code(self, cell):
949 def render_code(self, cell):
851 return cell_to_lines(cell)
950 return cell_to_lines(cell)
852
951
853 @DocInherit
952 @DocInherit
854 def render_markdown(self, cell):
953 def render_markdown(self, cell):
855 return cell_to_lines(cell)
954 return cell_to_lines(cell)
856
955
857 @DocInherit
956 @DocInherit
858 def render_raw(self, cell):
957 def render_raw(self, cell):
859 return cell_to_lines(cell)
958 return cell_to_lines(cell)
860
959
861 @DocInherit
960 @DocInherit
862 def render_pyout(self, output):
961 def render_pyout(self, output):
863 return cell_to_lines(cell)
962 return cell_to_lines(cell)
864
963
865 @DocInherit
964 @DocInherit
866 def render_pyerr(self, output):
965 def render_pyerr(self, output):
867 return cell_to_lines(cell)
966 return cell_to_lines(cell)
868
967
869 @DocInherit
968 @DocInherit
870 def render_display_format_text(self, output):
969 def render_display_format_text(self, output):
871 return [output.text]
970 return [output.text]
872
971
873 def render_display_format_html(self, output):
972 def render_display_format_html(self, output):
874 """render the html part of an output
973 """render the html part of an output
875
974
876 Returns [].
975 Returns [].
877 """
976 """
878 return [output.html]
977 return [output.html]
879
978
880 def render_display_format_latex(self, output):
979 def render_display_format_latex(self, output):
881 """render the latex part of an output
980 """render the latex part of an output
882
981
883 Returns list.
982 Returns list.
884 """
983 """
885 return [output.latex]
984 return [output.latex]
886
985
887 def render_display_format_json(self, output):
986 def render_display_format_json(self, output):
888 """render the json part of an output
987 """render the json part of an output
889
988
890 Returns [].
989 Returns [].
891 """
990 """
892 return [output.json]
991 return [output.json]
893
992
894
993
895 def render_display_format_javascript(self, output):
994 def render_display_format_javascript(self, output):
896 """render the javascript part of an output
995 """render the javascript part of an output
897
996
898 Returns [].
997 Returns [].
899 """
998 """
900 return [output.javascript]
999 return [output.javascript]
901
1000
902 #-----------------------------------------------------------------------------
1001 #-----------------------------------------------------------------------------
903 # Standalone conversion functions
1002 # Standalone conversion functions
904 #-----------------------------------------------------------------------------
1003 #-----------------------------------------------------------------------------
905
1004
906 def rst2simplehtml(infile):
1005 def rst2simplehtml(infile):
907 """Convert a rst file to simplified html suitable for blogger.
1006 """Convert a rst file to simplified html suitable for blogger.
908
1007
909 This just runs rst2html with certain parameters to produce really simple
1008 This just runs rst2html with certain parameters to produce really simple
910 html and strips the document header, so the resulting file can be easily
1009 html and strips the document header, so the resulting file can be easily
911 pasted into a blogger edit window.
1010 pasted into a blogger edit window.
912 """
1011 """
913
1012
914 # This is the template for the rst2html call that produces the cleanest,
1013 # This is the template for the rst2html call that produces the cleanest,
915 # simplest html I could find. This should help in making it easier to
1014 # simplest html I could find. This should help in making it easier to
916 # paste into the blogspot html window, though I'm still having problems
1015 # paste into the blogspot html window, though I'm still having problems
917 # with linebreaks there...
1016 # with linebreaks there...
918 cmd_template = ("rst2html --link-stylesheet --no-xml-declaration "
1017 cmd_template = ("rst2html --link-stylesheet --no-xml-declaration "
919 "--no-generator --no-datestamp --no-source-link "
1018 "--no-generator --no-datestamp --no-source-link "
920 "--no-toc-backlinks --no-section-numbering "
1019 "--no-toc-backlinks --no-section-numbering "
921 "--strip-comments ")
1020 "--strip-comments ")
922
1021
923 cmd = "%s %s" % (cmd_template, infile)
1022 cmd = "%s %s" % (cmd_template, infile)
924 proc = subprocess.Popen(cmd,
1023 proc = subprocess.Popen(cmd,
925 stdout=subprocess.PIPE,
1024 stdout=subprocess.PIPE,
926 stderr=subprocess.PIPE,
1025 stderr=subprocess.PIPE,
927 shell=True)
1026 shell=True)
928 html, stderr = proc.communicate()
1027 html, stderr = proc.communicate()
929 if stderr:
1028 if stderr:
930 raise IOError(stderr)
1029 raise IOError(stderr)
931
1030
932 # Make an iterator so breaking out holds state. Our implementation of
1031 # Make an iterator so breaking out holds state. Our implementation of
933 # searching for the html body below is basically a trivial little state
1032 # searching for the html body below is basically a trivial little state
934 # machine, so we need this.
1033 # machine, so we need this.
935 walker = iter(html.splitlines())
1034 walker = iter(html.splitlines())
936
1035
937 # Find start of main text, break out to then print until we find end /div.
1036 # Find start of main text, break out to then print until we find end /div.
938 # This may only work if there's a real title defined so we get a 'div class'
1037 # This may only work if there's a real title defined so we get a 'div class'
939 # tag, I haven't really tried.
1038 # tag, I haven't really tried.
940 for line in walker:
1039 for line in walker:
941 if line.startswith('<body>'):
1040 if line.startswith('<body>'):
942 break
1041 break
943
1042
944 newfname = os.path.splitext(infile)[0] + '.html'
1043 newfname = os.path.splitext(infile)[0] + '.html'
945 with open(newfname, 'w') as f:
1044 with open(newfname, 'w') as f:
946 for line in walker:
1045 for line in walker:
947 if line.startswith('</body>'):
1046 if line.startswith('</body>'):
948 break
1047 break
949 f.write(line)
1048 f.write(line)
950 f.write('\n')
1049 f.write('\n')
951
1050
952 return newfname
1051 return newfname
953
1052
954 #-----------------------------------------------------------------------------
1053 #-----------------------------------------------------------------------------
955 # Cell-level functions -- similar to IPython.nbformat.v3.rwbase functions
1054 # Cell-level functions -- similar to IPython.nbformat.v3.rwbase functions
956 # but at cell level instead of whole notebook level
1055 # but at cell level instead of whole notebook level
957 #-----------------------------------------------------------------------------
1056 #-----------------------------------------------------------------------------
958
1057
959 def writes_cell(cell, **kwargs):
1058 def writes_cell(cell, **kwargs):
960 kwargs['cls'] = BytesEncoder
1059 kwargs['cls'] = BytesEncoder
961 kwargs['indent'] = 3
1060 kwargs['indent'] = 3
962 kwargs['sort_keys'] = True
1061 kwargs['sort_keys'] = True
963 kwargs['separators'] = (',',': ')
1062 kwargs['separators'] = (',',': ')
964 if kwargs.pop('split_lines', True):
1063 if kwargs.pop('split_lines', True):
965 cell = split_lines_cell(copy.deepcopy(cell))
1064 cell = split_lines_cell(copy.deepcopy(cell))
966 return py3compat.str_to_unicode(json.dumps(cell, **kwargs), 'utf-8')
1065 return py3compat.str_to_unicode(json.dumps(cell, **kwargs), 'utf-8')
967
1066
968 _multiline_outputs = ['text', 'html', 'svg', 'latex', 'javascript', 'json']
1067 _multiline_outputs = ['text', 'html', 'svg', 'latex', 'javascript', 'json']
969 def split_lines_cell(cell):
1068 def split_lines_cell(cell):
970 """
1069 """
971 Split lines within a cell as in
1070 Split lines within a cell as in
972 IPython.nbformat.v3.rwbase.split_lines
1071 IPython.nbformat.v3.rwbase.split_lines
973
1072
974 """
1073 """
975 if cell.cell_type == 'code':
1074 if cell.cell_type == 'code':
976 if 'input' in cell and isinstance(cell.input, basestring):
1075 if 'input' in cell and isinstance(cell.input, basestring):
977 cell.input = (cell.input + '\n').splitlines()
1076 cell.input = (cell.input + '\n').splitlines()
978 for output in cell.outputs:
1077 for output in cell.outputs:
979 for key in _multiline_outputs:
1078 for key in _multiline_outputs:
980 item = output.get(key, None)
1079 item = output.get(key, None)
981 if isinstance(item, basestring):
1080 if isinstance(item, basestring):
982 output[key] = (item + '\n').splitlines()
1081 output[key] = (item + '\n').splitlines()
983 else: # text, heading cell
1082 else: # text, heading cell
984 for key in ['source', 'rendered']:
1083 for key in ['source', 'rendered']:
985 item = cell.get(key, None)
1084 item = cell.get(key, None)
986 if isinstance(item, basestring):
1085 if isinstance(item, basestring):
987 cell[key] = (item + '\n').splitlines()
1086 cell[key] = (item + '\n').splitlines()
988 return cell
1087 return cell
989
1088
990 def cell_to_lines(cell):
1089 def cell_to_lines(cell):
991 '''
1090 '''
992 Write a cell to json, returning the split lines.
1091 Write a cell to json, returning the split lines.
993 '''
1092 '''
994 split_lines_cell(cell)
1093 split_lines_cell(cell)
995 s = writes_cell(cell).strip()
1094 s = writes_cell(cell).strip()
996 return s.split('\n')
1095 return s.split('\n')
997
1096
998
1097
999 known_formats = "rst (default), html, quick-html, latex"
1098 known_formats = "rst (default), html, quick-html, latex, markdown"
1000
1099
1001 def main(infile, format='rst'):
1100 def main(infile, format='rst'):
1002 """Convert a notebook to html in one step"""
1101 """Convert a notebook to html in one step"""
1003 # XXX: this is just quick and dirty for now. When adding a new format,
1102 # XXX: this is just quick and dirty for now. When adding a new format,
1004 # make sure to add it to the `known_formats` string above, which gets
1103 # make sure to add it to the `known_formats` string above, which gets
1005 # printed in in the catch-all else, as well as in the help
1104 # printed in in the catch-all else, as well as in the help
1006 if format == 'rst':
1105 if format == 'rst':
1007 converter = ConverterRST(infile)
1106 converter = ConverterRST(infile)
1008 converter.render()
1107 converter.render()
1108 elif format == 'markdown':
1109 converter = ConverterMarkdown(infile)
1110 converter.render()
1009 elif format == 'html':
1111 elif format == 'html':
1010 #Currently, conversion to html is a 2 step process, nb->rst->html
1112 #Currently, conversion to html is a 2 step process, nb->rst->html
1011 converter = ConverterRST(infile)
1113 converter = ConverterRST(infile)
1012 rstfname = converter.render()
1114 rstfname = converter.render()
1013 rst2simplehtml(rstfname)
1115 rst2simplehtml(rstfname)
1014 elif format == 'quick-html':
1116 elif format == 'quick-html':
1015 converter = ConverterQuickHTML(infile)
1117 converter = ConverterQuickHTML(infile)
1016 rstfname = converter.render()
1118 rstfname = converter.render()
1017 elif format == 'latex':
1119 elif format == 'latex':
1018 converter = ConverterLaTeX(infile)
1120 converter = ConverterLaTeX(infile)
1019 latexfname = converter.render()
1121 latexfname = converter.render()
1020 else:
1122 else:
1021 raise SystemExit("Unknown format '%s', " % format +
1123 raise SystemExit("Unknown format '%s', " % format +
1022 "known formats are: " + known_formats)
1124 "known formats are: " + known_formats)
1023
1125
1024 #-----------------------------------------------------------------------------
1126 #-----------------------------------------------------------------------------
1025 # Script main
1127 # Script main
1026 #-----------------------------------------------------------------------------
1128 #-----------------------------------------------------------------------------
1027
1129
1028 if __name__ == '__main__':
1130 if __name__ == '__main__':
1029 parser = argparse.ArgumentParser(description=__doc__,
1131 parser = argparse.ArgumentParser(description=__doc__,
1030 formatter_class=argparse.RawTextHelpFormatter)
1132 formatter_class=argparse.RawTextHelpFormatter)
1031 # TODO: consider passing file like object around, rather than filenames
1133 # TODO: consider passing file like object around, rather than filenames
1032 # would allow us to process stdin, or even http streams
1134 # would allow us to process stdin, or even http streams
1033 #parser.add_argument('infile', nargs='?', type=argparse.FileType('r'), default=sys.stdin)
1135 #parser.add_argument('infile', nargs='?', type=argparse.FileType('r'), default=sys.stdin)
1034
1136
1035 #Require a filename as a positional argument
1137 #Require a filename as a positional argument
1036 parser.add_argument('infile', nargs=1)
1138 parser.add_argument('infile', nargs=1)
1037 parser.add_argument('-f', '--format', default='rst',
1139 parser.add_argument('-f', '--format', default='rst',
1038 help='Output format. Supported formats: \n' +
1140 help='Output format. Supported formats: \n' +
1039 known_formats)
1141 known_formats)
1040 args = parser.parse_args()
1142 args = parser.parse_args()
1041 main(infile=args.infile[0], format=args.format)
1143 main(infile=args.infile[0], format=args.format)
General Comments 0
You need to be logged in to leave comments. Login now