##// END OF EJS Templates
added 'py' format, which is cleaner than PyWriter...
Paul Ivanov -
Show More
@@ -1,1439 +1,1551 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 rst file.ipynb
5 ./nbconvert.py --format rst file.ipynb
6
6
7 Produces 'file.rst', along with auto-generated figure files
7 Produces 'file.rst', along with auto-generated figure files
8 called nb_figure_NN.png.
8 called nb_figure_NN.png.
9 """
9 """
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11 # Imports
11 # Imports
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 from __future__ import print_function
13 from __future__ import print_function
14
14
15 # Stdlib
15 # Stdlib
16 import codecs
16 import codecs
17 import io
17 import io
18 import logging
18 import logging
19 import os
19 import os
20 import pprint
20 import pprint
21 import re
21 import re
22 import subprocess
22 import subprocess
23 import sys
23 import sys
24 import json
24 import json
25 import copy
25 import copy
26 from shutil import rmtree
26 from shutil import rmtree
27
27
28 inkscape = 'inkscape'
28 inkscape = 'inkscape'
29 if sys.platform == 'darwin':
29 if sys.platform == 'darwin':
30 inkscape = '/Applications/Inkscape.app/Contents/Resources/bin/inkscape'
30 inkscape = '/Applications/Inkscape.app/Contents/Resources/bin/inkscape'
31 if not os.path.exists(inkscape):
31 if not os.path.exists(inkscape):
32 inkscape = None
32 inkscape = None
33
33
34 # From IPython
34 # From IPython
35 from IPython.external import argparse
35 from IPython.external import argparse
36 from IPython.nbformat import current as nbformat
36 from IPython.nbformat import current as nbformat
37 from IPython.utils.text import indent
37 from IPython.utils.text import indent
38 from IPython.nbformat.v3.nbjson import BytesEncoder
38 from IPython.nbformat.v3.nbjson import BytesEncoder
39 from IPython.utils import path, py3compat
39 from IPython.utils import path, py3compat
40
40
41 # local
41 # local
42 from decorators import DocInherit
42 from decorators import DocInherit
43 from lexers import IPythonLexer
43 from lexers import IPythonLexer
44
44
45
45
46 #-----------------------------------------------------------------------------
46 #-----------------------------------------------------------------------------
47 # Utility functions
47 # Utility functions
48 #-----------------------------------------------------------------------------
48 #-----------------------------------------------------------------------------
49
49
50 def DocInherit(f):
50 def DocInherit(f):
51 return f
51 return f
52
52
53 def remove_fake_files_url(cell):
53 def remove_fake_files_url(cell):
54 """Remove from the cell source the /files/ pseudo-path we use.
54 """Remove from the cell source the /files/ pseudo-path we use.
55 """
55 """
56 src = cell.source
56 src = cell.source
57 cell.source = src.replace('/files/', '')
57 cell.source = src.replace('/files/', '')
58
58
59
59
60 # ANSI color functions:
60 # ANSI color functions:
61
61
62 def remove_ansi(src):
62 def remove_ansi(src):
63 """Strip all ANSI color escape sequences from input string.
63 """Strip all ANSI color escape sequences from input string.
64
64
65 Parameters
65 Parameters
66 ----------
66 ----------
67 src : string
67 src : string
68
68
69 Returns
69 Returns
70 -------
70 -------
71 string
71 string
72 """
72 """
73 return re.sub(r'\033\[(0|\d;\d\d)m', '', src)
73 return re.sub(r'\033\[(0|\d;\d\d)m', '', src)
74
74
75
75
76 def ansi2html(txt):
76 def ansi2html(txt):
77 """Render ANSI colors as HTML colors
77 """Render ANSI colors as HTML colors
78
78
79 This is equivalent to util.fixConsole in utils.js
79 This is equivalent to util.fixConsole in utils.js
80
80
81 Parameters
81 Parameters
82 ----------
82 ----------
83 txt : string
83 txt : string
84
84
85 Returns
85 Returns
86 -------
86 -------
87 string
87 string
88 """
88 """
89
89
90 ansi_colormap = {
90 ansi_colormap = {
91 '30': 'ansiblack',
91 '30': 'ansiblack',
92 '31': 'ansired',
92 '31': 'ansired',
93 '32': 'ansigreen',
93 '32': 'ansigreen',
94 '33': 'ansiyellow',
94 '33': 'ansiyellow',
95 '34': 'ansiblue',
95 '34': 'ansiblue',
96 '35': 'ansipurple',
96 '35': 'ansipurple',
97 '36': 'ansicyan',
97 '36': 'ansicyan',
98 '37': 'ansigrey',
98 '37': 'ansigrey',
99 '01': 'ansibold',
99 '01': 'ansibold',
100 }
100 }
101
101
102 # do ampersand first
102 # do ampersand first
103 txt = txt.replace('&', '&')
103 txt = txt.replace('&', '&')
104 html_escapes = {
104 html_escapes = {
105 '<': '&lt;',
105 '<': '&lt;',
106 '>': '&gt;',
106 '>': '&gt;',
107 "'": '&apos;',
107 "'": '&apos;',
108 '"': '&quot;',
108 '"': '&quot;',
109 '`': '&#96;',
109 '`': '&#96;',
110 }
110 }
111 for c, escape in html_escapes.iteritems():
111 for c, escape in html_escapes.iteritems():
112 txt = txt.replace(c, escape)
112 txt = txt.replace(c, escape)
113
113
114 ansi_re = re.compile('\x1b' + r'\[([\dA-Fa-f;]*?)m')
114 ansi_re = re.compile('\x1b' + r'\[([\dA-Fa-f;]*?)m')
115 m = ansi_re.search(txt)
115 m = ansi_re.search(txt)
116 opened = False
116 opened = False
117 cmds = []
117 cmds = []
118 opener = ''
118 opener = ''
119 closer = ''
119 closer = ''
120 while m:
120 while m:
121 cmds = m.groups()[0].split(';')
121 cmds = m.groups()[0].split(';')
122 closer = '</span>' if opened else ''
122 closer = '</span>' if opened else ''
123 opened = len(cmds) > 1 or cmds[0] != '0'*len(cmds[0]);
123 opened = len(cmds) > 1 or cmds[0] != '0'*len(cmds[0]);
124 classes = []
124 classes = []
125 for cmd in cmds:
125 for cmd in cmds:
126 if cmd in ansi_colormap:
126 if cmd in ansi_colormap:
127 classes.append(ansi_colormap.get(cmd))
127 classes.append(ansi_colormap.get(cmd))
128
128
129 if classes:
129 if classes:
130 opener = '<span class="%s">' % (' '.join(classes))
130 opener = '<span class="%s">' % (' '.join(classes))
131 else:
131 else:
132 opener = ''
132 opener = ''
133 txt = re.sub(ansi_re, closer + opener, txt, 1)
133 txt = re.sub(ansi_re, closer + opener, txt, 1)
134
134
135 m = ansi_re.search(txt)
135 m = ansi_re.search(txt)
136
136
137 if opened:
137 if opened:
138 txt += '</span>'
138 txt += '</span>'
139 return txt
139 return txt
140
140
141
141
142 # Pandoc-dependent code
142 # Pandoc-dependent code
143
143
144 def markdown2latex(src):
144 def markdown2latex(src):
145 """Convert a markdown string to LaTeX via pandoc.
145 """Convert a markdown string to LaTeX via pandoc.
146
146
147 This function will raise an error if pandoc is not installed.
147 This function will raise an error if pandoc is not installed.
148
148
149 Any error messages generated by pandoc are printed to stderr.
149 Any error messages generated by pandoc are printed to stderr.
150
150
151 Parameters
151 Parameters
152 ----------
152 ----------
153 src : string
153 src : string
154 Input string, assumed to be valid markdown.
154 Input string, assumed to be valid markdown.
155
155
156 Returns
156 Returns
157 -------
157 -------
158 out : string
158 out : string
159 Output as returned by pandoc.
159 Output as returned by pandoc.
160 """
160 """
161 p = subprocess.Popen('pandoc -f markdown -t latex'.split(),
161 p = subprocess.Popen('pandoc -f markdown -t latex'.split(),
162 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
162 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
163 out, err = p.communicate(src.encode('utf-8'))
163 out, err = p.communicate(src.encode('utf-8'))
164 if err:
164 if err:
165 print(err, file=sys.stderr)
165 print(err, file=sys.stderr)
166 #print('*'*20+'\n', out, '\n'+'*'*20) # dbg
166 #print('*'*20+'\n', out, '\n'+'*'*20) # dbg
167 return unicode(out,'utf-8')
167 return unicode(out,'utf-8')
168
168
169
169
170 def markdown2rst(src):
170 def markdown2rst(src):
171 """Convert a markdown string to LaTeX via pandoc.
171 """Convert a markdown string to LaTeX via pandoc.
172
172
173 This function will raise an error if pandoc is not installed.
173 This function will raise an error if pandoc is not installed.
174
174
175 Any error messages generated by pandoc are printed to stderr.
175 Any error messages generated by pandoc are printed to stderr.
176
176
177 Parameters
177 Parameters
178 ----------
178 ----------
179 src : string
179 src : string
180 Input string, assumed to be valid markdown.
180 Input string, assumed to be valid markdown.
181
181
182 Returns
182 Returns
183 -------
183 -------
184 out : string
184 out : string
185 Output as returned by pandoc.
185 Output as returned by pandoc.
186 """
186 """
187 p = subprocess.Popen('pandoc -f markdown -t rst'.split(),
187 p = subprocess.Popen('pandoc -f markdown -t rst'.split(),
188 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
188 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
189 out, err = p.communicate(src.encode('utf-8'))
189 out, err = p.communicate(src.encode('utf-8'))
190 if err:
190 if err:
191 print(err, file=sys.stderr)
191 print(err, file=sys.stderr)
192 #print('*'*20+'\n', out, '\n'+'*'*20) # dbg
192 #print('*'*20+'\n', out, '\n'+'*'*20) # dbg
193 return unicode(out,'utf-8')
193 return unicode(out,'utf-8')
194
194
195
195
196 def rst_directive(directive, text=''):
196 def rst_directive(directive, text=''):
197 out = [directive, '']
197 out = [directive, '']
198 if text:
198 if text:
199 out.extend([indent(text), ''])
199 out.extend([indent(text), ''])
200 return out
200 return out
201
201
202
202
203 def coalesce_streams(outputs):
203 def coalesce_streams(outputs):
204 """merge consecutive sequences of stream output into single stream
204 """merge consecutive sequences of stream output into single stream
205
205
206 to prevent extra newlines inserted at flush calls
206 to prevent extra newlines inserted at flush calls
207
207
208 TODO: handle \r deletion
208 TODO: handle \r deletion
209 """
209 """
210 new_outputs = []
210 new_outputs = []
211 last = outputs[0]
211 last = outputs[0]
212 new_outputs = [last]
212 new_outputs = [last]
213 for output in outputs[1:]:
213 for output in outputs[1:]:
214 if (output.output_type == 'stream' and
214 if (output.output_type == 'stream' and
215 last.output_type == 'stream' and
215 last.output_type == 'stream' and
216 last.stream == output.stream
216 last.stream == output.stream
217 ):
217 ):
218 last.text += output.text
218 last.text += output.text
219 else:
219 else:
220 new_outputs.append(output)
220 new_outputs.append(output)
221
221
222 return new_outputs
222 return new_outputs
223
223
224
224
225 #-----------------------------------------------------------------------------
225 #-----------------------------------------------------------------------------
226 # Class declarations
226 # Class declarations
227 #-----------------------------------------------------------------------------
227 #-----------------------------------------------------------------------------
228
228
229 class ConversionException(Exception):
229 class ConversionException(Exception):
230 pass
230 pass
231
231
232
232
233 class Converter(object):
233 class Converter(object):
234 default_encoding = 'utf-8'
234 default_encoding = 'utf-8'
235 extension = str()
235 extension = str()
236 figures_counter = 0
236 figures_counter = 0
237 infile = str()
237 infile = str()
238 infile_dir = str()
238 infile_dir = str()
239 infile_root = str()
239 infile_root = str()
240 files_dir = str()
240 files_dir = str()
241 with_preamble = True
241 with_preamble = True
242 user_preamble = None
242 user_preamble = None
243 output = str()
243 output = str()
244 raw_as_verbatim = False
244 raw_as_verbatim = False
245
245
246 def __init__(self, infile):
246 def __init__(self, infile):
247 self.infile = infile
247 self.infile = infile
248 self.infile_dir, infile_root = os.path.split(infile)
248 self.infile_dir, infile_root = os.path.split(infile)
249 infile_root = os.path.splitext(infile_root)[0]
249 infile_root = os.path.splitext(infile_root)[0]
250 files_dir = os.path.join(self.infile_dir, infile_root + '_files')
250 files_dir = os.path.join(self.infile_dir, infile_root + '_files')
251 if not os.path.isdir(files_dir):
251 if not os.path.isdir(files_dir):
252 os.mkdir(files_dir)
252 os.mkdir(files_dir)
253 self.infile_root = infile_root
253 self.infile_root = infile_root
254 self.files_dir = files_dir
254 self.files_dir = files_dir
255 self.outbase = os.path.join(self.infile_dir, infile_root)
255 self.outbase = os.path.join(self.infile_dir, infile_root)
256
256
257 def dispatch(self, cell_type):
257 def dispatch(self, cell_type):
258 """return cell_type dependent render method, for example render_code
258 """return cell_type dependent render method, for example render_code
259 """
259 """
260 return getattr(self, 'render_' + cell_type, self.render_unknown)
260 return getattr(self, 'render_' + cell_type, self.render_unknown)
261
261
262 def dispatch_display_format(self, format):
262 def dispatch_display_format(self, format):
263 """return output_type dependent render method, for example render_output_text
263 """return output_type dependent render method, for example render_output_text
264 """
264 """
265 return getattr(self, 'render_display_format_' + format, self.render_unknown_display)
265 return getattr(self, 'render_display_format_' + format, self.render_unknown_display)
266
266
267 def convert(self, cell_separator='\n'):
267 def convert(self, cell_separator='\n'):
268 lines = []
268 lines = []
269 lines.extend(self.optional_header())
269 lines.extend(self.optional_header())
270 converted_cells = []
270 converted_cells = []
271 for worksheet in self.nb.worksheets:
271 for worksheet in self.nb.worksheets:
272 for cell in worksheet.cells:
272 for cell in worksheet.cells:
273 #print(cell.cell_type) # dbg
273 #print(cell.cell_type) # dbg
274 conv_fn = self.dispatch(cell.cell_type)
274 conv_fn = self.dispatch(cell.cell_type)
275 if cell.cell_type in ('markdown', 'raw'):
275 if cell.cell_type in ('markdown', 'raw'):
276 remove_fake_files_url(cell)
276 remove_fake_files_url(cell)
277 converted_cells.append('\n'.join(conv_fn(cell)))
277 converted_cells.append('\n'.join(conv_fn(cell)))
278 cell_lines = cell_separator.join(converted_cells).split('\n')
278 cell_lines = cell_separator.join(converted_cells).split('\n')
279 lines.extend(cell_lines)
279 lines.extend(cell_lines)
280 lines.extend(self.optional_footer())
280 lines.extend(self.optional_footer())
281 return u'\n'.join(lines)
281 return u'\n'.join(lines)
282
282
283 def render(self):
283 def render(self):
284 "read, convert, and save self.infile"
284 "read, convert, and save self.infile"
285 if not hasattr(self, 'nb'):
285 if not hasattr(self, 'nb'):
286 self.read()
286 self.read()
287 self.output = self.convert()
287 self.output = self.convert()
288 return self.save()
288 return self.save()
289
289
290 def read(self):
290 def read(self):
291 "read and parse notebook into NotebookNode called self.nb"
291 "read and parse notebook into NotebookNode called self.nb"
292 with open(self.infile) as f:
292 with open(self.infile) as f:
293 self.nb = nbformat.read(f, 'json')
293 self.nb = nbformat.read(f, 'json')
294
294
295 def save(self, outfile=None, encoding=None):
295 def save(self, outfile=None, encoding=None):
296 "read and parse notebook into self.nb"
296 "read and parse notebook into self.nb"
297 if outfile is None:
297 if outfile is None:
298 outfile = self.outbase + '.' + self.extension
298 outfile = self.outbase + '.' + self.extension
299 if encoding is None:
299 if encoding is None:
300 encoding = self.default_encoding
300 encoding = self.default_encoding
301 with io.open(outfile, 'w', encoding=encoding) as f:
301 with io.open(outfile, 'w', encoding=encoding) as f:
302 f.write(self.output)
302 f.write(self.output)
303 return os.path.abspath(outfile)
303 return os.path.abspath(outfile)
304
304
305 def optional_header(self):
305 def optional_header(self):
306 return []
306 return []
307
307
308 def optional_footer(self):
308 def optional_footer(self):
309 return []
309 return []
310
310
311 def _new_figure(self, data, fmt):
311 def _new_figure(self, data, fmt):
312 """Create a new figure file in the given format.
312 """Create a new figure file in the given format.
313
313
314 Returns a path relative to the input file.
314 Returns a path relative to the input file.
315 """
315 """
316 figname = '%s_fig_%02i.%s' % (self.infile_root,
316 figname = '%s_fig_%02i.%s' % (self.infile_root,
317 self.figures_counter, fmt)
317 self.figures_counter, fmt)
318 self.figures_counter += 1
318 self.figures_counter += 1
319 fullname = os.path.join(self.files_dir, figname)
319 fullname = os.path.join(self.files_dir, figname)
320
320
321 # Binary files are base64-encoded, SVG is already XML
321 # Binary files are base64-encoded, SVG is already XML
322 if fmt in ('png', 'jpg', 'pdf'):
322 if fmt in ('png', 'jpg', 'pdf'):
323 data = data.decode('base64')
323 data = data.decode('base64')
324 fopen = lambda fname: open(fname, 'wb')
324 fopen = lambda fname: open(fname, 'wb')
325 else:
325 else:
326 fopen = lambda fname: codecs.open(fname, 'wb', self.default_encoding)
326 fopen = lambda fname: codecs.open(fname, 'wb', self.default_encoding)
327
327
328 with fopen(fullname) as f:
328 with fopen(fullname) as f:
329 f.write(data)
329 f.write(data)
330
330
331 return fullname
331 return fullname
332
332
333 def render_heading(self, cell):
333 def render_heading(self, cell):
334 """convert a heading cell
334 """convert a heading cell
335
335
336 Returns list."""
336 Returns list."""
337 raise NotImplementedError
337 raise NotImplementedError
338
338
339 def render_code(self, cell):
339 def render_code(self, cell):
340 """Convert a code cell
340 """Convert a code cell
341
341
342 Returns list."""
342 Returns list."""
343 raise NotImplementedError
343 raise NotImplementedError
344
344
345 def render_markdown(self, cell):
345 def render_markdown(self, cell):
346 """convert a markdown cell
346 """convert a markdown cell
347
347
348 Returns list."""
348 Returns list."""
349 raise NotImplementedError
349 raise NotImplementedError
350
350
351 def _img_lines(self, img_file):
351 def _img_lines(self, img_file):
352 """Return list of lines to include an image file."""
352 """Return list of lines to include an image file."""
353 # Note: subclasses may choose to implement format-specific _FMT_lines
353 # Note: subclasses may choose to implement format-specific _FMT_lines
354 # methods if they so choose (FMT in {png, svg, jpg, pdf}).
354 # methods if they so choose (FMT in {png, svg, jpg, pdf}).
355 raise NotImplementedError
355 raise NotImplementedError
356
356
357 def render_display_data(self, output):
357 def render_display_data(self, output):
358 """convert display data from the output of a code cell
358 """convert display data from the output of a code cell
359
359
360 Returns list.
360 Returns list.
361 """
361 """
362 lines = []
362 lines = []
363
363
364 for fmt in output.keys():
364 for fmt in output.keys():
365 if fmt in ['png', 'svg', 'jpg', 'pdf']:
365 if fmt in ['png', 'svg', 'jpg', 'pdf']:
366 img_file = self._new_figure(output[fmt], fmt)
366 img_file = self._new_figure(output[fmt], fmt)
367 # Subclasses can have format-specific render functions (e.g.,
367 # Subclasses can have format-specific render functions (e.g.,
368 # latex has to auto-convert all SVG to PDF first).
368 # latex has to auto-convert all SVG to PDF first).
369 lines_fun = getattr(self, '_%s_lines' % fmt, None)
369 lines_fun = getattr(self, '_%s_lines' % fmt, None)
370 if not lines_fun:
370 if not lines_fun:
371 lines_fun = self._img_lines
371 lines_fun = self._img_lines
372 lines.extend(lines_fun(img_file))
372 lines.extend(lines_fun(img_file))
373 elif fmt != 'output_type':
373 elif fmt != 'output_type':
374 conv_fn = self.dispatch_display_format(fmt)
374 conv_fn = self.dispatch_display_format(fmt)
375 lines.extend(conv_fn(output))
375 lines.extend(conv_fn(output))
376 return lines
376 return lines
377
377
378 def render_raw(self, cell):
378 def render_raw(self, cell):
379 """convert a cell with raw text
379 """convert a cell with raw text
380
380
381 Returns list."""
381 Returns list."""
382 raise NotImplementedError
382 raise NotImplementedError
383
383
384 def render_unknown(self, cell):
384 def render_unknown(self, cell):
385 """Render cells of unkown type
385 """Render cells of unkown type
386
386
387 Returns list."""
387 Returns list."""
388 data = pprint.pformat(cell)
388 data = pprint.pformat(cell)
389 logging.warning('Unknown cell: %s' % cell.cell_type)
389 logging.warning('Unknown cell: %s' % cell.cell_type)
390 return self._unknown_lines(data)
390 return self._unknown_lines(data)
391
391
392 def render_unknown_display(self, output, type):
392 def render_unknown_display(self, output, type):
393 """Render cells of unkown type
393 """Render cells of unkown type
394
394
395 Returns list."""
395 Returns list."""
396 data = pprint.pformat(output)
396 data = pprint.pformat(output)
397 logging.warning('Unknown output: %s' % output.output_type)
397 logging.warning('Unknown output: %s' % output.output_type)
398 return self._unknown_lines(data)
398 return self._unknown_lines(data)
399
399
400 def render_stream(self, output):
400 def render_stream(self, output):
401 """render the stream part of an output
401 """render the stream part of an output
402
402
403 Returns list.
403 Returns list.
404
404
405 Identical to render_display_format_text
405 Identical to render_display_format_text
406 """
406 """
407 return self.render_display_format_text(output)
407 return self.render_display_format_text(output)
408
408
409 def render_pyout(self, output):
409 def render_pyout(self, output):
410 """convert pyout part of a code cell
410 """convert pyout part of a code cell
411
411
412 Returns list."""
412 Returns list."""
413 raise NotImplementedError
413 raise NotImplementedError
414
414
415
415
416 def render_pyerr(self, output):
416 def render_pyerr(self, output):
417 """convert pyerr part of a code cell
417 """convert pyerr part of a code cell
418
418
419 Returns list."""
419 Returns list."""
420 raise NotImplementedError
420 raise NotImplementedError
421
421
422 def _unknown_lines(self, data):
422 def _unknown_lines(self, data):
423 """Return list of lines for an unknown cell.
423 """Return list of lines for an unknown cell.
424
424
425 Parameters
425 Parameters
426 ----------
426 ----------
427 data : str
427 data : str
428 The content of the unknown data as a single string.
428 The content of the unknown data as a single string.
429 """
429 """
430 raise NotImplementedError
430 raise NotImplementedError
431
431
432 # These are the possible format types in an output node
432 # These are the possible format types in an output node
433
433
434 def render_display_format_text(self, output):
434 def render_display_format_text(self, output):
435 """render the text part of an output
435 """render the text part of an output
436
436
437 Returns list.
437 Returns list.
438 """
438 """
439 raise NotImplementedError
439 raise NotImplementedError
440
440
441 def render_display_format_html(self, output):
441 def render_display_format_html(self, output):
442 """render the html part of an output
442 """render the html part of an output
443
443
444 Returns list.
444 Returns list.
445 """
445 """
446 raise NotImplementedError
446 raise NotImplementedError
447
447
448 def render_display_format_latex(self, output):
448 def render_display_format_latex(self, output):
449 """render the latex part of an output
449 """render the latex part of an output
450
450
451 Returns list.
451 Returns list.
452 """
452 """
453 raise NotImplementedError
453 raise NotImplementedError
454
454
455 def render_display_format_json(self, output):
455 def render_display_format_json(self, output):
456 """render the json part of an output
456 """render the json part of an output
457
457
458 Returns list.
458 Returns list.
459 """
459 """
460 raise NotImplementedError
460 raise NotImplementedError
461
461
462 def render_display_format_javascript(self, output):
462 def render_display_format_javascript(self, output):
463 """render the javascript part of an output
463 """render the javascript part of an output
464
464
465 Returns list.
465 Returns list.
466 """
466 """
467 raise NotImplementedError
467 raise NotImplementedError
468
468
469
469
470 class ConverterRST(Converter):
470 class ConverterRST(Converter):
471 extension = 'rst'
471 extension = 'rst'
472 heading_level = {1: '=', 2: '-', 3: '`', 4: '\'', 5: '.', 6: '~'}
472 heading_level = {1: '=', 2: '-', 3: '`', 4: '\'', 5: '.', 6: '~'}
473
473
474 @DocInherit
474 @DocInherit
475 def render_heading(self, cell):
475 def render_heading(self, cell):
476 marker = self.heading_level[cell.level]
476 marker = self.heading_level[cell.level]
477 return ['{0}\n{1}\n'.format(cell.source, marker * len(cell.source))]
477 return ['{0}\n{1}\n'.format(cell.source, marker * len(cell.source))]
478
478
479 @DocInherit
479 @DocInherit
480 def render_code(self, cell):
480 def render_code(self, cell):
481 if not cell.input:
481 if not cell.input:
482 return []
482 return []
483
483
484 lines = ['In[%s]:' % cell.prompt_number, '']
484 lines = ['In[%s]:' % cell.prompt_number, '']
485 lines.extend(rst_directive('.. code:: python', cell.input))
485 lines.extend(rst_directive('.. code:: python', cell.input))
486
486
487 for output in cell.outputs:
487 for output in cell.outputs:
488 conv_fn = self.dispatch(output.output_type)
488 conv_fn = self.dispatch(output.output_type)
489 lines.extend(conv_fn(output))
489 lines.extend(conv_fn(output))
490
490
491 return lines
491 return lines
492
492
493 @DocInherit
493 @DocInherit
494 def render_markdown(self, cell):
494 def render_markdown(self, cell):
495 #return [cell.source]
495 #return [cell.source]
496 return [markdown2rst(cell.source)]
496 return [markdown2rst(cell.source)]
497
497
498 @DocInherit
498 @DocInherit
499 def render_raw(self, cell):
499 def render_raw(self, cell):
500 if self.raw_as_verbatim:
500 if self.raw_as_verbatim:
501 return ['::', '', indent(cell.source), '']
501 return ['::', '', indent(cell.source), '']
502 else:
502 else:
503 return [cell.source]
503 return [cell.source]
504
504
505 @DocInherit
505 @DocInherit
506 def render_pyout(self, output):
506 def render_pyout(self, output):
507 lines = ['Out[%s]:' % output.prompt_number, '']
507 lines = ['Out[%s]:' % output.prompt_number, '']
508
508
509 # output is a dictionary like object with type as a key
509 # output is a dictionary like object with type as a key
510 if 'latex' in output:
510 if 'latex' in output:
511 lines.extend(rst_directive('.. math::', output.latex))
511 lines.extend(rst_directive('.. math::', output.latex))
512
512
513 if 'text' in output:
513 if 'text' in output:
514 lines.extend(rst_directive('.. parsed-literal::', output.text))
514 lines.extend(rst_directive('.. parsed-literal::', output.text))
515
515
516 return lines
516 return lines
517
517
518 @DocInherit
518 @DocInherit
519 def render_pyerr(self, output):
519 def render_pyerr(self, output):
520 # Note: a traceback is a *list* of frames.
520 # Note: a traceback is a *list* of frames.
521 return ['::', '', indent(remove_ansi('\n'.join(output.traceback))), '']
521 return ['::', '', indent(remove_ansi('\n'.join(output.traceback))), '']
522
522
523 @DocInherit
523 @DocInherit
524 def _img_lines(self, img_file):
524 def _img_lines(self, img_file):
525 return ['.. image:: %s' % img_file, '']
525 return ['.. image:: %s' % img_file, '']
526
526
527 @DocInherit
527 @DocInherit
528 def render_display_format_text(self, output):
528 def render_display_format_text(self, output):
529 return rst_directive('.. parsed-literal::', output.text)
529 return rst_directive('.. parsed-literal::', output.text)
530
530
531 @DocInherit
531 @DocInherit
532 def _unknown_lines(self, data):
532 def _unknown_lines(self, data):
533 return rst_directive('.. warning:: Unknown cell') + [data]
533 return rst_directive('.. warning:: Unknown cell') + [data]
534
534
535 def render_display_format_html(self, output):
535 def render_display_format_html(self, output):
536 """render the html part of an output
536 """render the html part of an output
537
537
538 Returns list.
538 Returns list.
539 """
539 """
540 return rst_directive('.. raw:: html', output.html)
540 return rst_directive('.. raw:: html', output.html)
541
541
542 def render_display_format_latex(self, output):
542 def render_display_format_latex(self, output):
543 """render the latex part of an output
543 """render the latex part of an output
544
544
545 Returns list.
545 Returns list.
546 """
546 """
547 return rst_directive('.. math::', output.latex)
547 return rst_directive('.. math::', output.latex)
548
548
549 def render_display_format_json(self, output):
549 def render_display_format_json(self, output):
550 """render the json part of an output
550 """render the json part of an output
551
551
552 Returns list.
552 Returns list.
553 """
553 """
554 return rst_directive('.. raw:: json', output.json)
554 return rst_directive('.. raw:: json', output.json)
555
555
556
556
557 def render_display_format_javascript(self, output):
557 def render_display_format_javascript(self, output):
558 """render the javascript part of an output
558 """render the javascript part of an output
559
559
560 Returns list.
560 Returns list.
561 """
561 """
562 return rst_directive('.. raw:: javascript', output.javascript)
562 return rst_directive('.. raw:: javascript', output.javascript)
563
563
564
564
565
565
566
566
567 def highlight(src, lang='ipython'):
567 def highlight(src, lang='ipython'):
568 """Return a syntax-highlighted version of the input source.
568 """Return a syntax-highlighted version of the input source.
569 """
569 """
570 from pygments import highlight
570 from pygments import highlight
571 from pygments.lexers import get_lexer_by_name
571 from pygments.lexers import get_lexer_by_name
572 from pygments.formatters import HtmlFormatter
572 from pygments.formatters import HtmlFormatter
573
573
574 if lang == 'ipython':
574 if lang == 'ipython':
575 lexer = IPythonLexer()
575 lexer = IPythonLexer()
576 else:
576 else:
577 lexer = get_lexer_by_name(lang, stripall=True)
577 lexer = get_lexer_by_name(lang, stripall=True)
578
578
579 return highlight(src, lexer, HtmlFormatter())
579 return highlight(src, lexer, HtmlFormatter())
580
580
581
581
582 class ConverterMarkdown(Converter):
582 class ConverterMarkdown(Converter):
583 extension = 'md'
583 extension = 'md'
584
584
585 def __init__(self, infile, highlight_source=True, show_prompts=False,
585 def __init__(self, infile, highlight_source=True, show_prompts=False,
586 inline_prompt=False):
586 inline_prompt=False):
587 super(ConverterMarkdown, self).__init__(infile)
587 super(ConverterMarkdown, self).__init__(infile)
588 self.highlight_source = highlight_source
588 self.highlight_source = highlight_source
589 self.show_prompts = show_prompts
589 self.show_prompts = show_prompts
590 self.inline_prompt = inline_prompt
590 self.inline_prompt = inline_prompt
591
591
592 @DocInherit
592 @DocInherit
593 def render_heading(self, cell):
593 def render_heading(self, cell):
594 return ['{0} {1}'.format('#'*cell.level, cell.source), '']
594 return ['{0} {1}'.format('#'*cell.level, cell.source), '']
595
595
596 @DocInherit
596 @DocInherit
597 def render_code(self, cell):
597 def render_code(self, cell):
598 if not cell.input:
598 if not cell.input:
599 return []
599 return []
600 lines = []
600 lines = []
601 if self.show_prompts and not self.inline_prompt:
601 if self.show_prompts and not self.inline_prompt:
602 lines.extend(['*In[%s]:*' % cell.prompt_number, ''])
602 lines.extend(['*In[%s]:*' % cell.prompt_number, ''])
603 if self.show_prompts and self.inline_prompt:
603 if self.show_prompts and self.inline_prompt:
604 prompt = 'In[%s]: ' % cell.prompt_number
604 prompt = 'In[%s]: ' % cell.prompt_number
605 input_lines = cell.input.split('\n')
605 input_lines = cell.input.split('\n')
606 src = prompt + input_lines[0] + '\n' + indent('\n'.join(input_lines[1:]), nspaces=len(prompt))
606 src = prompt + input_lines[0] + '\n' + indent('\n'.join(input_lines[1:]), nspaces=len(prompt))
607 else:
607 else:
608 src = cell.input
608 src = cell.input
609 src = highlight(src) if self.highlight_source else indent(src)
609 src = highlight(src) if self.highlight_source else indent(src)
610 lines.extend([src, ''])
610 lines.extend([src, ''])
611 if cell.outputs and self.show_prompts and not self.inline_prompt:
611 if cell.outputs and self.show_prompts and not self.inline_prompt:
612 lines.extend(['*Out[%s]:*' % cell.prompt_number, ''])
612 lines.extend(['*Out[%s]:*' % cell.prompt_number, ''])
613 for output in cell.outputs:
613 for output in cell.outputs:
614 conv_fn = self.dispatch(output.output_type)
614 conv_fn = self.dispatch(output.output_type)
615 lines.extend(conv_fn(output))
615 lines.extend(conv_fn(output))
616
616
617 #lines.append('----')
617 #lines.append('----')
618 lines.append('')
618 lines.append('')
619 return lines
619 return lines
620
620
621 @DocInherit
621 @DocInherit
622 def render_markdown(self, cell):
622 def render_markdown(self, cell):
623 return [cell.source, '']
623 return [cell.source, '']
624
624
625 @DocInherit
625 @DocInherit
626 def render_raw(self, cell):
626 def render_raw(self, cell):
627 if self.raw_as_verbatim:
627 if self.raw_as_verbatim:
628 return [indent(cell.source), '']
628 return [indent(cell.source), '']
629 else:
629 else:
630 return [cell.source, '']
630 return [cell.source, '']
631
631
632 @DocInherit
632 @DocInherit
633 def render_pyout(self, output):
633 def render_pyout(self, output):
634 lines = []
634 lines = []
635
635
636 ## if 'text' in output:
636 ## if 'text' in output:
637 ## lines.extend(['*Out[%s]:*' % output.prompt_number, ''])
637 ## lines.extend(['*Out[%s]:*' % output.prompt_number, ''])
638
638
639 # output is a dictionary like object with type as a key
639 # output is a dictionary like object with type as a key
640 if 'latex' in output:
640 if 'latex' in output:
641 pass
641 pass
642
642
643 if 'text' in output:
643 if 'text' in output:
644 lines.extend(['<pre>', indent(output.text), '</pre>'])
644 lines.extend(['<pre>', indent(output.text), '</pre>'])
645
645
646 lines.append('')
646 lines.append('')
647 return lines
647 return lines
648
648
649 @DocInherit
649 @DocInherit
650 def render_pyerr(self, output):
650 def render_pyerr(self, output):
651 # Note: a traceback is a *list* of frames.
651 # Note: a traceback is a *list* of frames.
652 return [indent(remove_ansi('\n'.join(output.traceback))), '']
652 return [indent(remove_ansi('\n'.join(output.traceback))), '']
653
653
654 @DocInherit
654 @DocInherit
655 def _img_lines(self, img_file):
655 def _img_lines(self, img_file):
656 return ['', '![](%s)' % img_file, '']
656 return ['', '![](%s)' % img_file, '']
657
657
658 @DocInherit
658 @DocInherit
659 def render_display_format_text(self, output):
659 def render_display_format_text(self, output):
660 return [indent(output.text)]
660 return [indent(output.text)]
661
661
662 @DocInherit
662 @DocInherit
663 def _unknown_lines(self, data):
663 def _unknown_lines(self, data):
664 return ['Warning: Unknown cell', data]
664 return ['Warning: Unknown cell', data]
665
665
666 def render_display_format_html(self, output):
666 def render_display_format_html(self, output):
667 """render the html part of an output
667 """render the html part of an output
668
668
669 Returns list.
669 Returns list.
670 """
670 """
671 return [output.html]
671 return [output.html]
672
672
673 def render_display_format_latex(self, output):
673 def render_display_format_latex(self, output):
674 """render the latex part of an output
674 """render the latex part of an output
675
675
676 Returns list.
676 Returns list.
677 """
677 """
678 return ['LaTeX::', indent(output.latex)]
678 return ['LaTeX::', indent(output.latex)]
679
679
680 def render_display_format_json(self, output):
680 def render_display_format_json(self, output):
681 """render the json part of an output
681 """render the json part of an output
682
682
683 Returns list.
683 Returns list.
684 """
684 """
685 return ['JSON:', indent(output.json)]
685 return ['JSON:', indent(output.json)]
686
686
687
687
688 def render_display_format_javascript(self, output):
688 def render_display_format_javascript(self, output):
689 """render the javascript part of an output
689 """render the javascript part of an output
690
690
691 Returns list.
691 Returns list.
692 """
692 """
693 return ['JavaScript:', indent(output.javascript)]
693 return ['JavaScript:', indent(output.javascript)]
694
694
695
695
696 def return_list(x):
696 def return_list(x):
697 """Ensure that x is returned as a list or inside one"""
697 """Ensure that x is returned as a list or inside one"""
698 return x if isinstance(x, list) else [x]
698 return x if isinstance(x, list) else [x]
699
699
700
700
701 # decorators for HTML output
701 # decorators for HTML output
702 def output_container(f):
702 def output_container(f):
703 """add a prompt-area next to an output"""
703 """add a prompt-area next to an output"""
704 def wrapped(self, output):
704 def wrapped(self, output):
705 rendered = f(self, output)
705 rendered = f(self, output)
706 if not rendered:
706 if not rendered:
707 # empty output
707 # empty output
708 return []
708 return []
709 lines = []
709 lines = []
710 lines.append('<div class="hbox output_area">')
710 lines.append('<div class="hbox output_area">')
711 lines.extend(self._out_prompt(output))
711 lines.extend(self._out_prompt(output))
712 classes = "output_subarea output_%s" % output.output_type
712 classes = "output_subarea output_%s" % output.output_type
713 if output.output_type == 'stream':
713 if output.output_type == 'stream':
714 classes += " output_%s" % output.stream
714 classes += " output_%s" % output.stream
715 lines.append('<div class="%s">' % classes)
715 lines.append('<div class="%s">' % classes)
716 lines.extend(rendered)
716 lines.extend(rendered)
717 lines.append('</div>') # subarea
717 lines.append('</div>') # subarea
718 lines.append('</div>') # output_area
718 lines.append('</div>') # output_area
719
719
720 return lines
720 return lines
721
721
722 return wrapped
722 return wrapped
723
723
724 def text_cell(f):
724 def text_cell(f):
725 """wrap text cells in appropriate divs"""
725 """wrap text cells in appropriate divs"""
726 def wrapped(self, cell):
726 def wrapped(self, cell):
727 rendered = f(self, cell)
727 rendered = f(self, cell)
728 classes = "text_cell_render border-box-sizing rendered_html"
728 classes = "text_cell_render border-box-sizing rendered_html"
729 lines = ['<div class="%s">' % classes] + rendered + ['</div>']
729 lines = ['<div class="%s">' % classes] + rendered + ['</div>']
730 return lines
730 return lines
731 return wrapped
731 return wrapped
732
732
733 class ConverterHTML(Converter):
733 class ConverterHTML(Converter):
734 extension = 'html'
734 extension = 'html'
735
735
736 def in_tag(self, tag, src, attrs={}):
736 def in_tag(self, tag, src, attrs={}):
737 """Return a list of elements bracketed by the given tag"""
737 """Return a list of elements bracketed by the given tag"""
738 attr_s = ""
738 attr_s = ""
739 for attr, value in attrs.iteritems():
739 for attr, value in attrs.iteritems():
740 attr_s += "%s=%s" % (attr, value)
740 attr_s += "%s=%s" % (attr, value)
741 return ['<%s %s>' % (tag, attr_s), src, '</%s>' % tag]
741 return ['<%s %s>' % (tag, attr_s), src, '</%s>' % tag]
742
742
743 def _ansi_colored(self, text):
743 def _ansi_colored(self, text):
744 return ['<pre>%s</pre>' % ansi2html(text)]
744 return ['<pre>%s</pre>' % ansi2html(text)]
745
745
746 def _stylesheet(self, fname):
746 def _stylesheet(self, fname):
747 with io.open(fname, encoding='utf-8') as f:
747 with io.open(fname, encoding='utf-8') as f:
748 s = f.read()
748 s = f.read()
749 return self.in_tag('style', s, dict(type='text/css'))
749 return self.in_tag('style', s, dict(type='text/css'))
750
750
751 def _out_prompt(self, output):
751 def _out_prompt(self, output):
752 if output.output_type == 'pyout':
752 if output.output_type == 'pyout':
753 n = output.prompt_number if output.prompt_number is not None else '&nbsp;'
753 n = output.prompt_number if output.prompt_number is not None else '&nbsp;'
754 content = 'Out [%s]:' % n
754 content = 'Out [%s]:' % n
755 else:
755 else:
756 content = ''
756 content = ''
757 return ['<div class="prompt output_prompt">%s</div>' % content]
757 return ['<div class="prompt output_prompt">%s</div>' % content]
758
758
759 def optional_header(self):
759 def optional_header(self):
760 from pygments.formatters import HtmlFormatter
760 from pygments.formatters import HtmlFormatter
761
761
762 header = ['<html>', '<head>']
762 header = ['<html>', '<head>']
763
763
764 static = os.path.join(path.get_ipython_package_dir(),
764 static = os.path.join(path.get_ipython_package_dir(),
765 'frontend', 'html', 'notebook', 'static',
765 'frontend', 'html', 'notebook', 'static',
766 )
766 )
767 here = os.path.split(os.path.abspath(__file__))[0]
767 here = os.path.split(os.path.abspath(__file__))[0]
768 css = os.path.join(static, 'css')
768 css = os.path.join(static, 'css')
769 for sheet in [
769 for sheet in [
770 # do we need jquery and prettify?
770 # do we need jquery and prettify?
771 # os.path.join(static, 'jquery', 'css', 'themes', 'base', 'jquery-ui.min.css'),
771 # os.path.join(static, 'jquery', 'css', 'themes', 'base', 'jquery-ui.min.css'),
772 # os.path.join(static, 'prettify', 'prettify.css'),
772 # os.path.join(static, 'prettify', 'prettify.css'),
773 os.path.join(css, 'boilerplate.css'),
773 os.path.join(css, 'boilerplate.css'),
774 os.path.join(css, 'fbm.css'),
774 os.path.join(css, 'fbm.css'),
775 os.path.join(css, 'notebook.css'),
775 os.path.join(css, 'notebook.css'),
776 os.path.join(css, 'renderedhtml.css'),
776 os.path.join(css, 'renderedhtml.css'),
777 # our overrides:
777 # our overrides:
778 os.path.join(here, 'css', 'static_html.css'),
778 os.path.join(here, 'css', 'static_html.css'),
779 ]:
779 ]:
780 header.extend(self._stylesheet(sheet))
780 header.extend(self._stylesheet(sheet))
781
781
782 # pygments css
782 # pygments css
783 pygments_css = HtmlFormatter().get_style_defs('.highlight')
783 pygments_css = HtmlFormatter().get_style_defs('.highlight')
784 header.extend(['<meta charset="UTF-8">'])
784 header.extend(['<meta charset="UTF-8">'])
785 header.extend(self.in_tag('style', pygments_css, dict(type='text/css')))
785 header.extend(self.in_tag('style', pygments_css, dict(type='text/css')))
786
786
787 # TODO: this should be allowed to use local mathjax:
787 # TODO: this should be allowed to use local mathjax:
788 header.extend(self.in_tag('script', '', {'type':'text/javascript',
788 header.extend(self.in_tag('script', '', {'type':'text/javascript',
789 'src': '"https://c328740.ssl.cf1.rackcdn.com/mathjax/latest/MathJax.js?config=TeX-AMS_HTML"',
789 'src': '"https://c328740.ssl.cf1.rackcdn.com/mathjax/latest/MathJax.js?config=TeX-AMS_HTML"',
790 }))
790 }))
791 with io.open(os.path.join(here, 'js', 'initmathjax.js'), encoding='utf-8') as f:
791 with io.open(os.path.join(here, 'js', 'initmathjax.js'), encoding='utf-8') as f:
792 header.extend(self.in_tag('script', f.read(), {'type': 'text/javascript'}))
792 header.extend(self.in_tag('script', f.read(), {'type': 'text/javascript'}))
793
793
794 header.extend(['</head>', '<body>'])
794 header.extend(['</head>', '<body>'])
795
795
796 return header
796 return header
797
797
798 def optional_footer(self):
798 def optional_footer(self):
799 lines = []
799 lines = []
800 lines.extend([
800 lines.extend([
801 '</body>',
801 '</body>',
802 '</html>',
802 '</html>',
803 ])
803 ])
804 return lines
804 return lines
805
805
806 @DocInherit
806 @DocInherit
807 @text_cell
807 @text_cell
808 def render_heading(self, cell):
808 def render_heading(self, cell):
809 marker = cell.level
809 marker = cell.level
810 return [u'<h{1}>\n {0}\n</h{1}>'.format(cell.source, marker)]
810 return [u'<h{1}>\n {0}\n</h{1}>'.format(cell.source, marker)]
811
811
812 @DocInherit
812 @DocInherit
813 def render_code(self, cell):
813 def render_code(self, cell):
814 if not cell.input:
814 if not cell.input:
815 return []
815 return []
816
816
817 lines = ['<div class="cell border-box-sizing code_cell vbox">']
817 lines = ['<div class="cell border-box-sizing code_cell vbox">']
818
818
819 lines.append('<div class="input hbox">')
819 lines.append('<div class="input hbox">')
820 n = cell.prompt_number if getattr(cell, 'prompt_number', None) is not None else '&nbsp;'
820 n = cell.prompt_number if getattr(cell, 'prompt_number', None) is not None else '&nbsp;'
821 lines.append('<div class="prompt input_prompt">In [%s]:</div>' % n)
821 lines.append('<div class="prompt input_prompt">In [%s]:</div>' % n)
822 lines.append('<div class="input_area box-flex1">')
822 lines.append('<div class="input_area box-flex1">')
823 lines.append(highlight(cell.input))
823 lines.append(highlight(cell.input))
824 lines.append('</div>') # input_area
824 lines.append('</div>') # input_area
825 lines.append('</div>') # input
825 lines.append('</div>') # input
826
826
827 if cell.outputs:
827 if cell.outputs:
828 lines.append('<div class="vbox output_wrapper">')
828 lines.append('<div class="vbox output_wrapper">')
829 lines.append('<div class="output vbox">')
829 lines.append('<div class="output vbox">')
830
830
831 for output in coalesce_streams(cell.outputs):
831 for output in coalesce_streams(cell.outputs):
832 conv_fn = self.dispatch(output.output_type)
832 conv_fn = self.dispatch(output.output_type)
833 lines.extend(conv_fn(output))
833 lines.extend(conv_fn(output))
834
834
835 lines.append('</div>') # output
835 lines.append('</div>') # output
836 lines.append('</div>') # output_wrapper
836 lines.append('</div>') # output_wrapper
837
837
838 lines.append('</div>') # cell
838 lines.append('</div>') # cell
839
839
840 return lines
840 return lines
841
841
842 @DocInherit
842 @DocInherit
843 @text_cell
843 @text_cell
844 def render_markdown(self, cell):
844 def render_markdown(self, cell):
845 p = subprocess.Popen(['markdown'], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
845 p = subprocess.Popen(['markdown'], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
846 out, _ = p.communicate(cell.source.encode('utf-8'))
846 out, _ = p.communicate(cell.source.encode('utf-8'))
847 return [out.decode('utf-8')]
847 return [out.decode('utf-8')]
848
848
849 @DocInherit
849 @DocInherit
850 def render_raw(self, cell):
850 def render_raw(self, cell):
851 if self.raw_as_verbatim:
851 if self.raw_as_verbatim:
852 return self.in_tag('pre', cell.source)
852 return self.in_tag('pre', cell.source)
853 else:
853 else:
854 return [cell.source]
854 return [cell.source]
855
855
856 @DocInherit
856 @DocInherit
857 @output_container
857 @output_container
858 def render_pyout(self, output):
858 def render_pyout(self, output):
859 for fmt in ['html', 'latex', 'png', 'jpeg', 'svg', 'text']:
859 for fmt in ['html', 'latex', 'png', 'jpeg', 'svg', 'text']:
860 if fmt in output:
860 if fmt in output:
861 conv_fn = self.dispatch_display_format(fmt)
861 conv_fn = self.dispatch_display_format(fmt)
862 return conv_fn(output)
862 return conv_fn(output)
863 return []
863 return []
864
864
865 render_display_data = render_pyout
865 render_display_data = render_pyout
866
866
867 @DocInherit
867 @DocInherit
868 @output_container
868 @output_container
869 def render_stream(self, output):
869 def render_stream(self, output):
870 return self._ansi_colored(output.text)
870 return self._ansi_colored(output.text)
871
871
872
872
873 @DocInherit
873 @DocInherit
874 @output_container
874 @output_container
875 def render_pyerr(self, output):
875 def render_pyerr(self, output):
876 # Note: a traceback is a *list* of frames.
876 # Note: a traceback is a *list* of frames.
877 # lines = []
877 # lines = []
878
878
879 # stb =
879 # stb =
880 return self._ansi_colored('\n'.join(output.traceback))
880 return self._ansi_colored('\n'.join(output.traceback))
881
881
882 @DocInherit
882 @DocInherit
883 def _img_lines(self, img_file):
883 def _img_lines(self, img_file):
884 return ['<img src="%s">' % img_file, '</img>']
884 return ['<img src="%s">' % img_file, '</img>']
885
885
886 @DocInherit
886 @DocInherit
887 def _unknown_lines(self, data):
887 def _unknown_lines(self, data):
888 return ['<h2>Warning:: Unknown cell</h2>'] + self.in_tag('pre', data)
888 return ['<h2>Warning:: Unknown cell</h2>'] + self.in_tag('pre', data)
889
889
890
890
891 def render_display_format_png(self, output):
891 def render_display_format_png(self, output):
892 """render the png part of an output
892 """render the png part of an output
893
893
894 Returns list.
894 Returns list.
895 """
895 """
896 return ['<img src="data:image/png;base64,%s"></img>' % output.png]
896 return ['<img src="data:image/png;base64,%s"></img>' % output.png]
897
897
898 def render_display_format_svg(self, output):
898 def render_display_format_svg(self, output):
899 """render the svg part of an output
899 """render the svg part of an output
900
900
901 Returns list.
901 Returns list.
902 """
902 """
903 return [output.svg]
903 return [output.svg]
904
904
905 def render_display_format_jpeg(self, output):
905 def render_display_format_jpeg(self, output):
906 """render the jpeg part of an output
906 """render the jpeg part of an output
907
907
908 Returns list.
908 Returns list.
909 """
909 """
910 return ['<img src="data:image/jpeg;base64,%s"></img>' % output.jpeg]
910 return ['<img src="data:image/jpeg;base64,%s"></img>' % output.jpeg]
911
911
912 def render_display_format_text(self, output):
912 def render_display_format_text(self, output):
913 """render the text part of an output
913 """render the text part of an output
914
914
915 Returns list.
915 Returns list.
916 """
916 """
917 return self._ansi_colored(output.text)
917 return self._ansi_colored(output.text)
918
918
919 def render_display_format_html(self, output):
919 def render_display_format_html(self, output):
920 """render the html part of an output
920 """render the html part of an output
921
921
922 Returns list.
922 Returns list.
923 """
923 """
924 return [output.html]
924 return [output.html]
925
925
926 def render_display_format_latex(self, output):
926 def render_display_format_latex(self, output):
927 """render the latex part of an output
927 """render the latex part of an output
928
928
929 Returns list.
929 Returns list.
930 """
930 """
931 return [output.latex]
931 return [output.latex]
932
932
933 def render_display_format_json(self, output):
933 def render_display_format_json(self, output):
934 """render the json part of an output
934 """render the json part of an output
935
935
936 Returns [].
936 Returns [].
937 """
937 """
938 # html ignores json
938 # html ignores json
939 return []
939 return []
940
940
941
941
942 def render_display_format_javascript(self, output):
942 def render_display_format_javascript(self, output):
943 """render the javascript part of an output
943 """render the javascript part of an output
944
944
945 Returns list.
945 Returns list.
946 """
946 """
947 return [output.javascript]
947 return [output.javascript]
948
948
949
949
950 class ConverterLaTeX(Converter):
950 class ConverterLaTeX(Converter):
951 """Converts a notebook to a .tex file suitable for pdflatex.
951 """Converts a notebook to a .tex file suitable for pdflatex.
952
952
953 Note: this converter *needs*:
953 Note: this converter *needs*:
954
954
955 - `pandoc`: for all conversion of markdown cells. If your notebook only
955 - `pandoc`: for all conversion of markdown cells. If your notebook only
956 has Raw cells, pandoc will not be needed.
956 has Raw cells, pandoc will not be needed.
957
957
958 - `inkscape`: if your notebook has SVG figures. These need to be
958 - `inkscape`: if your notebook has SVG figures. These need to be
959 converted to PDF before inclusion in the TeX file, as LaTeX doesn't
959 converted to PDF before inclusion in the TeX file, as LaTeX doesn't
960 understand SVG natively.
960 understand SVG natively.
961
961
962 You will in general obtain much better final PDF results if you configure
962 You will in general obtain much better final PDF results if you configure
963 the matplotlib backend to create SVG output with
963 the matplotlib backend to create SVG output with
964
964
965 %config InlineBackend.figure_format = 'svg'
965 %config InlineBackend.figure_format = 'svg'
966
966
967 (or set the equivalent flag at startup or in your configuration profile).
967 (or set the equivalent flag at startup or in your configuration profile).
968 """
968 """
969 extension = 'tex'
969 extension = 'tex'
970 documentclass = 'article'
970 documentclass = 'article'
971 documentclass_options = '11pt,english'
971 documentclass_options = '11pt,english'
972 heading_map = {1: r'\section',
972 heading_map = {1: r'\section',
973 2: r'\subsection',
973 2: r'\subsection',
974 3: r'\subsubsection',
974 3: r'\subsubsection',
975 4: r'\paragraph',
975 4: r'\paragraph',
976 5: r'\subparagraph',
976 5: r'\subparagraph',
977 6: r'\subparagraph'}
977 6: r'\subparagraph'}
978
978
979 def in_env(self, environment, lines):
979 def in_env(self, environment, lines):
980 """Return list of environment lines for input lines
980 """Return list of environment lines for input lines
981
981
982 Parameters
982 Parameters
983 ----------
983 ----------
984 env : string
984 env : string
985 Name of the environment to bracket with begin/end.
985 Name of the environment to bracket with begin/end.
986
986
987 lines: """
987 lines: """
988 out = [ur'\begin{%s}' % environment]
988 out = [ur'\begin{%s}' % environment]
989 if isinstance(lines, basestring):
989 if isinstance(lines, basestring):
990 out.append(lines)
990 out.append(lines)
991 else: # list
991 else: # list
992 out.extend(lines)
992 out.extend(lines)
993 out.append(ur'\end{%s}' % environment)
993 out.append(ur'\end{%s}' % environment)
994 return out
994 return out
995
995
996 def convert(self):
996 def convert(self):
997 # The main body is done by the logic in the parent class, and that's
997 # The main body is done by the logic in the parent class, and that's
998 # all we need if preamble support has been turned off.
998 # all we need if preamble support has been turned off.
999 body = super(ConverterLaTeX, self).convert()
999 body = super(ConverterLaTeX, self).convert()
1000 if not self.with_preamble:
1000 if not self.with_preamble:
1001 return body
1001 return body
1002 # But if preamble is on, then we need to construct a proper, standalone
1002 # But if preamble is on, then we need to construct a proper, standalone
1003 # tex file.
1003 # tex file.
1004
1004
1005 # Tag the document at the top and set latex class
1005 # Tag the document at the top and set latex class
1006 final = [ r'%% This file was auto-generated by IPython, do NOT edit',
1006 final = [ r'%% This file was auto-generated by IPython, do NOT edit',
1007 r'%% Conversion from the original notebook file:',
1007 r'%% Conversion from the original notebook file:',
1008 r'%% {0}'.format(self.infile),
1008 r'%% {0}'.format(self.infile),
1009 r'%%',
1009 r'%%',
1010 r'\documentclass[%s]{%s}' % (self.documentclass_options,
1010 r'\documentclass[%s]{%s}' % (self.documentclass_options,
1011 self.documentclass),
1011 self.documentclass),
1012 '',
1012 '',
1013 ]
1013 ]
1014 # Load our own preamble, which is stored next to the main file. We
1014 # Load our own preamble, which is stored next to the main file. We
1015 # need to be careful in case the script entry point is a symlink
1015 # need to be careful in case the script entry point is a symlink
1016 myfile = __file__ if not os.path.islink(__file__) else \
1016 myfile = __file__ if not os.path.islink(__file__) else \
1017 os.readlink(__file__)
1017 os.readlink(__file__)
1018 with open(os.path.join(os.path.dirname(myfile), 'preamble.tex')) as f:
1018 with open(os.path.join(os.path.dirname(myfile), 'preamble.tex')) as f:
1019 final.append(f.read())
1019 final.append(f.read())
1020
1020
1021 # Load any additional user-supplied preamble
1021 # Load any additional user-supplied preamble
1022 if self.user_preamble:
1022 if self.user_preamble:
1023 final.extend(['', '%% Adding user preamble from file:',
1023 final.extend(['', '%% Adding user preamble from file:',
1024 '%% {0}'.format(self.user_preamble), ''])
1024 '%% {0}'.format(self.user_preamble), ''])
1025 with open(self.user_preamble) as f:
1025 with open(self.user_preamble) as f:
1026 final.append(f.read())
1026 final.append(f.read())
1027
1027
1028 # Include document body
1028 # Include document body
1029 final.extend([ r'\begin{document}', '',
1029 final.extend([ r'\begin{document}', '',
1030 body,
1030 body,
1031 r'\end{document}', ''])
1031 r'\end{document}', ''])
1032 # Retun value must be a string
1032 # Retun value must be a string
1033 return '\n'.join(final)
1033 return '\n'.join(final)
1034
1034
1035 @DocInherit
1035 @DocInherit
1036 def render_heading(self, cell):
1036 def render_heading(self, cell):
1037 marker = self.heading_map[cell.level]
1037 marker = self.heading_map[cell.level]
1038 return ['%s{%s}' % (marker, cell.source) ]
1038 return ['%s{%s}' % (marker, cell.source) ]
1039
1039
1040 @DocInherit
1040 @DocInherit
1041 def render_code(self, cell):
1041 def render_code(self, cell):
1042 if not cell.input:
1042 if not cell.input:
1043 return []
1043 return []
1044
1044
1045 # Cell codes first carry input code, we use lstlisting for that
1045 # Cell codes first carry input code, we use lstlisting for that
1046 lines = [ur'\begin{codecell}']
1046 lines = [ur'\begin{codecell}']
1047
1047
1048 lines.extend(self.in_env('codeinput',
1048 lines.extend(self.in_env('codeinput',
1049 self.in_env('lstlisting', cell.input)))
1049 self.in_env('lstlisting', cell.input)))
1050
1050
1051 outlines = []
1051 outlines = []
1052 for output in cell.outputs:
1052 for output in cell.outputs:
1053 conv_fn = self.dispatch(output.output_type)
1053 conv_fn = self.dispatch(output.output_type)
1054 outlines.extend(conv_fn(output))
1054 outlines.extend(conv_fn(output))
1055
1055
1056 # And then output of many possible types; use a frame for all of it.
1056 # And then output of many possible types; use a frame for all of it.
1057 if outlines:
1057 if outlines:
1058 lines.extend(self.in_env('codeoutput', outlines))
1058 lines.extend(self.in_env('codeoutput', outlines))
1059
1059
1060 lines.append(ur'\end{codecell}')
1060 lines.append(ur'\end{codecell}')
1061
1061
1062 return lines
1062 return lines
1063
1063
1064
1064
1065 @DocInherit
1065 @DocInherit
1066 def _img_lines(self, img_file):
1066 def _img_lines(self, img_file):
1067 return self.in_env('center',
1067 return self.in_env('center',
1068 [r'\includegraphics[width=6in]{%s}' % img_file, r'\par'])
1068 [r'\includegraphics[width=6in]{%s}' % img_file, r'\par'])
1069
1069
1070 def _svg_lines(self, img_file):
1070 def _svg_lines(self, img_file):
1071 base_file = os.path.splitext(img_file)[0]
1071 base_file = os.path.splitext(img_file)[0]
1072 pdf_file = base_file + '.pdf'
1072 pdf_file = base_file + '.pdf'
1073 subprocess.check_call([ inkscape, '--export-pdf=%s' % pdf_file,
1073 subprocess.check_call([ inkscape, '--export-pdf=%s' % pdf_file,
1074 img_file])
1074 img_file])
1075 return self._img_lines(pdf_file)
1075 return self._img_lines(pdf_file)
1076
1076
1077 @DocInherit
1077 @DocInherit
1078 def render_markdown(self, cell):
1078 def render_markdown(self, cell):
1079 return [markdown2latex(cell.source)]
1079 return [markdown2latex(cell.source)]
1080
1080
1081 @DocInherit
1081 @DocInherit
1082 def render_pyout(self, output):
1082 def render_pyout(self, output):
1083 lines = []
1083 lines = []
1084
1084
1085 # output is a dictionary like object with type as a key
1085 # output is a dictionary like object with type as a key
1086 if 'latex' in output:
1086 if 'latex' in output:
1087 lines.extend(output.latex)
1087 lines.extend(output.latex)
1088
1088
1089 if 'text' in output:
1089 if 'text' in output:
1090 lines.extend(self.in_env('verbatim', output.text))
1090 lines.extend(self.in_env('verbatim', output.text))
1091
1091
1092 return lines
1092 return lines
1093
1093
1094 @DocInherit
1094 @DocInherit
1095 def render_pyerr(self, output):
1095 def render_pyerr(self, output):
1096 # Note: a traceback is a *list* of frames.
1096 # Note: a traceback is a *list* of frames.
1097 return self.in_env('traceback',
1097 return self.in_env('traceback',
1098 self.in_env('verbatim',
1098 self.in_env('verbatim',
1099 remove_ansi('\n'.join(output.traceback))))
1099 remove_ansi('\n'.join(output.traceback))))
1100
1100
1101 @DocInherit
1101 @DocInherit
1102 def render_raw(self, cell):
1102 def render_raw(self, cell):
1103 if self.raw_as_verbatim:
1103 if self.raw_as_verbatim:
1104 return self.in_env('verbatim', cell.source)
1104 return self.in_env('verbatim', cell.source)
1105 else:
1105 else:
1106 return [cell.source]
1106 return [cell.source]
1107
1107
1108 @DocInherit
1108 @DocInherit
1109 def _unknown_lines(self, data):
1109 def _unknown_lines(self, data):
1110 return [r'{\vspace{5mm}\bf WARNING:: unknown cell:}'] + \
1110 return [r'{\vspace{5mm}\bf WARNING:: unknown cell:}'] + \
1111 self.in_env('verbatim', data)
1111 self.in_env('verbatim', data)
1112
1112
1113
1113
1114 @DocInherit
1114 @DocInherit
1115 def render_display_format_text(self, output):
1115 def render_display_format_text(self, output):
1116 lines = []
1116 lines = []
1117
1117
1118 if 'text' in output:
1118 if 'text' in output:
1119 lines.extend(self.in_env('verbatim', output.text.strip()))
1119 lines.extend(self.in_env('verbatim', output.text.strip()))
1120
1120
1121 return lines
1121 return lines
1122
1122
1123 def render_display_format_html(self, output):
1123 def render_display_format_html(self, output):
1124 """render the html part of an output
1124 """render the html part of an output
1125
1125
1126 Returns [].
1126 Returns [].
1127 """
1127 """
1128 return []
1128 return []
1129
1129
1130 def render_display_format_latex(self, output):
1130 def render_display_format_latex(self, output):
1131 """render the latex part of an output
1131 """render the latex part of an output
1132
1132
1133 Returns list.
1133 Returns list.
1134 """
1134 """
1135 if type(output.latex) == type([]):
1135 if type(output.latex) == type([]):
1136 return output.latex
1136 return output.latex
1137 return [output.latex]
1137 return [output.latex]
1138
1138
1139 def render_display_format_json(self, output):
1139 def render_display_format_json(self, output):
1140 """render the json part of an output
1140 """render the json part of an output
1141
1141
1142 Returns [].
1142 Returns [].
1143 """
1143 """
1144 # latex ignores json
1144 # latex ignores json
1145 return []
1145 return []
1146
1146
1147
1147
1148 def render_display_format_javascript(self, output):
1148 def render_display_format_javascript(self, output):
1149 """render the javascript part of an output
1149 """render the javascript part of an output
1150
1150
1151 Returns [].
1151 Returns [].
1152 """
1152 """
1153 # latex ignores javascript
1153 # latex ignores javascript
1154 return []
1154 return []
1155
1155
1156 class ConverterNotebook(Converter):
1156 class ConverterNotebook(Converter):
1157 """
1157 """
1158 A converter that is essentially a null-op.
1158 A converter that is essentially a null-op.
1159 This exists so it can be subclassed
1159 This exists so it can be subclassed
1160 for custom handlers of .ipynb files
1160 for custom handlers of .ipynb files
1161 that create new .ipynb files.
1161 that create new .ipynb files.
1162
1162
1163 What distinguishes this from JSONWriter is that
1163 What distinguishes this from JSONWriter is that
1164 subclasses can specify what to do with each type of cell.
1164 subclasses can specify what to do with each type of cell.
1165
1165
1166 Writes out a notebook file.
1166 Writes out a notebook file.
1167
1167
1168 """
1168 """
1169 extension = 'ipynb'
1169 extension = 'ipynb'
1170
1170
1171 def __init__(self, infile, outbase):
1171 def __init__(self, infile, outbase):
1172 Converter.__init__(self, infile)
1172 Converter.__init__(self, infile)
1173 self.outbase = outbase
1173 self.outbase = outbase
1174 rmtree(self.files_dir)
1174 rmtree(self.files_dir)
1175
1175
1176 def convert(self):
1176 def convert(self):
1177 return json.dumps(json.loads(Converter.convert(self, ',')), indent=1, sort_keys=True)
1177 return json.dumps(json.loads(Converter.convert(self, ',')), indent=1, sort_keys=True)
1178
1178
1179 def optional_header(self):
1179 def optional_header(self):
1180 s = \
1180 s = \
1181 """{
1181 """{
1182 "metadata": {
1182 "metadata": {
1183 "name": "%(name)s"
1183 "name": "%(name)s"
1184 },
1184 },
1185 "nbformat": 3,
1185 "nbformat": 3,
1186 "worksheets": [
1186 "worksheets": [
1187 {
1187 {
1188 "cells": [""" % {'name':self.outbase}
1188 "cells": [""" % {'name':self.outbase}
1189
1189
1190 return s.split('\n')
1190 return s.split('\n')
1191
1191
1192 def optional_footer(self):
1192 def optional_footer(self):
1193 s = \
1193 s = \
1194 """]
1194 """]
1195 }
1195 }
1196 ]
1196 ]
1197 }"""
1197 }"""
1198 return s.split('\n')
1198 return s.split('\n')
1199
1199
1200 @DocInherit
1200 @DocInherit
1201 def render_heading(self, cell):
1201 def render_heading(self, cell):
1202 return cell_to_lines(cell)
1202 return cell_to_lines(cell)
1203
1203
1204 @DocInherit
1204 @DocInherit
1205 def render_code(self, cell):
1205 def render_code(self, cell):
1206 return cell_to_lines(cell)
1206 return cell_to_lines(cell)
1207
1207
1208 @DocInherit
1208 @DocInherit
1209 def render_markdown(self, cell):
1209 def render_markdown(self, cell):
1210 return cell_to_lines(cell)
1210 return cell_to_lines(cell)
1211
1211
1212 @DocInherit
1212 @DocInherit
1213 def render_raw(self, cell):
1213 def render_raw(self, cell):
1214 return cell_to_lines(cell)
1214 return cell_to_lines(cell)
1215
1215
1216 @DocInherit
1216 @DocInherit
1217 def render_pyout(self, output):
1217 def render_pyout(self, output):
1218 return cell_to_lines(output)
1218 return cell_to_lines(output)
1219
1219
1220 @DocInherit
1220 @DocInherit
1221 def render_pyerr(self, output):
1221 def render_pyerr(self, output):
1222 return cell_to_lines(output)
1222 return cell_to_lines(output)
1223
1223
1224 @DocInherit
1224 @DocInherit
1225 def render_display_format_text(self, output):
1225 def render_display_format_text(self, output):
1226 return [output.text]
1226 return [output.text]
1227
1227
1228 def render_display_format_html(self, output):
1228 def render_display_format_html(self, output):
1229 """render the html part of an output
1229 """render the html part of an output
1230
1230
1231 Returns [].
1231 Returns [].
1232 """
1232 """
1233 return [output.html]
1233 return [output.html]
1234
1234
1235 def render_display_format_latex(self, output):
1235 def render_display_format_latex(self, output):
1236 """render the latex part of an output
1236 """render the latex part of an output
1237
1237
1238 Returns list.
1238 Returns list.
1239 """
1239 """
1240 return [output.latex]
1240 return [output.latex]
1241
1241
1242 def render_display_format_json(self, output):
1242 def render_display_format_json(self, output):
1243 """render the json part of an output
1243 """render the json part of an output
1244
1244
1245 Returns [].
1245 Returns [].
1246 """
1246 """
1247 return [output.json]
1247 return [output.json]
1248
1248
1249
1249
1250 def render_display_format_javascript(self, output):
1250 def render_display_format_javascript(self, output):
1251 """render the javascript part of an output
1251 """render the javascript part of an output
1252
1252
1253 Returns [].
1253 Returns [].
1254 """
1254 """
1255 return [output.javascript]
1255 return [output.javascript]
1256
1256
1257 class ConverterPy(Converter):
1258 """
1259 A converter that takes a notebook and converts it to a .py file.
1260
1261 What distinguishes this from PyWriter and PyReader in IPython.nbformat is
1262 that subclasses can specify what to do with each type of cell.
1263 Additionally, unlike PyWriter, this does not preserve the '# <markdown>'
1264 opening and closing comments style comments in favor of a cleaner looking
1265 python program.
1266
1267 Note:
1268 Even though this produces a .py file, it is not guaranteed to be valid
1269 python file, since the notebook may be using magics and even cell
1270 magics.
1271 """
1272 extension = 'py'
1273
1274 def __init__(self, infile, show_prompts=True, show_output=True):
1275 super(ConverterPy, self).__init__(infile)
1276 self.show_prompts = show_prompts
1277 self.show_output = show_output
1278
1279 @staticmethod
1280 def comment(input):
1281 "returns every line in input as commented out"
1282 return "# "+input.replace("\n", "\n# ")
1283
1284 @DocInherit
1285 def render_heading(self, cell):
1286 return ['#{0} {1}'.format('#'*cell.level, cell.source), '']
1287
1288 @DocInherit
1289 def render_code(self, cell):
1290 if not cell.input:
1291 return []
1292 lines = []
1293 if self.show_prompts:
1294 lines.extend(['# In[%s]:' % cell.prompt_number])
1295 src = cell.input
1296 lines.extend([src, ''])
1297 if self.show_output:
1298 if cell.outputs :
1299 lines.extend(['# Out[%s]:' % cell.prompt_number])
1300 for output in cell.outputs:
1301 conv_fn = self.dispatch(output.output_type)
1302 lines.extend(conv_fn(output))
1303 return lines
1304
1305 @DocInherit
1306 def render_markdown(self, cell):
1307 return [self.comment(cell.source), '']
1308
1309 @DocInherit
1310 def render_raw(self, cell):
1311 if self.raw_as_verbatim:
1312 return [self.comment(indent(cell.source)), '']
1313 else:
1314 return [self.comment(cell.source), '']
1315
1316 @DocInherit
1317 def render_pyout(self, output):
1318 lines = []
1319
1320 ## if 'text' in output:
1321 ## lines.extend(['*Out[%s]:*' % output.prompt_number, ''])
1322
1323 # output is a dictionary like object with type as a key
1324 if 'latex' in output:
1325 pass
1326
1327 if 'text' in output:
1328 lines.extend([self.comment(indent(output.text)), ''])
1329
1330 lines.append('')
1331 return lines
1332
1333 @DocInherit
1334 def render_pyerr(self, output):
1335 # Note: a traceback is a *list* of frames.
1336 return [indent(remove_ansi('\n'.join(output.traceback))), '']
1337
1338 @DocInherit
1339 def _img_lines(self, img_file):
1340 return [ self.comment('image file: %s' % img_file), '']
1341
1342 @DocInherit
1343 def render_display_format_text(self, output):
1344 return [self.comment(indent(output.text))]
1345
1346 @DocInherit
1347 def _unknown_lines(self, data):
1348 return [self.comment('Warning: Unknown cell'+ str(data))]
1349
1350 @DocInherit
1351 def render_display_format_html(self, output):
1352 return [self.comment(output.html)]
1353
1354 @DocInherit
1355 def render_display_format_latex(self, output):
1356 return []
1357
1358 @DocInherit
1359 def render_display_format_json(self, output):
1360 return []
1361
1362 @DocInherit
1363 def render_display_format_javascript(self, output):
1364 return []
1365
1257 #-----------------------------------------------------------------------------
1366 #-----------------------------------------------------------------------------
1258 # Standalone conversion functions
1367 # Standalone conversion functions
1259 #-----------------------------------------------------------------------------
1368 #-----------------------------------------------------------------------------
1260
1369
1261 def rst2simplehtml(infile):
1370 def rst2simplehtml(infile):
1262 """Convert a rst file to simplified html suitable for blogger.
1371 """Convert a rst file to simplified html suitable for blogger.
1263
1372
1264 This just runs rst2html with certain parameters to produce really simple
1373 This just runs rst2html with certain parameters to produce really simple
1265 html and strips the document header, so the resulting file can be easily
1374 html and strips the document header, so the resulting file can be easily
1266 pasted into a blogger edit window.
1375 pasted into a blogger edit window.
1267 """
1376 """
1268
1377
1269 # This is the template for the rst2html call that produces the cleanest,
1378 # This is the template for the rst2html call that produces the cleanest,
1270 # simplest html I could find. This should help in making it easier to
1379 # simplest html I could find. This should help in making it easier to
1271 # paste into the blogspot html window, though I'm still having problems
1380 # paste into the blogspot html window, though I'm still having problems
1272 # with linebreaks there...
1381 # with linebreaks there...
1273 cmd_template = ("rst2html --link-stylesheet --no-xml-declaration "
1382 cmd_template = ("rst2html --link-stylesheet --no-xml-declaration "
1274 "--no-generator --no-datestamp --no-source-link "
1383 "--no-generator --no-datestamp --no-source-link "
1275 "--no-toc-backlinks --no-section-numbering "
1384 "--no-toc-backlinks --no-section-numbering "
1276 "--strip-comments ")
1385 "--strip-comments ")
1277
1386
1278 cmd = "%s %s" % (cmd_template, infile)
1387 cmd = "%s %s" % (cmd_template, infile)
1279 proc = subprocess.Popen(cmd,
1388 proc = subprocess.Popen(cmd,
1280 stdout=subprocess.PIPE,
1389 stdout=subprocess.PIPE,
1281 stderr=subprocess.PIPE,
1390 stderr=subprocess.PIPE,
1282 shell=True)
1391 shell=True)
1283 html, stderr = proc.communicate()
1392 html, stderr = proc.communicate()
1284 if stderr:
1393 if stderr:
1285 raise IOError(stderr)
1394 raise IOError(stderr)
1286
1395
1287 # Make an iterator so breaking out holds state. Our implementation of
1396 # Make an iterator so breaking out holds state. Our implementation of
1288 # searching for the html body below is basically a trivial little state
1397 # searching for the html body below is basically a trivial little state
1289 # machine, so we need this.
1398 # machine, so we need this.
1290 walker = iter(html.splitlines())
1399 walker = iter(html.splitlines())
1291
1400
1292 # Find start of main text, break out to then print until we find end /div.
1401 # Find start of main text, break out to then print until we find end /div.
1293 # This may only work if there's a real title defined so we get a 'div class'
1402 # This may only work if there's a real title defined so we get a 'div class'
1294 # tag, I haven't really tried.
1403 # tag, I haven't really tried.
1295 for line in walker:
1404 for line in walker:
1296 if line.startswith('<body>'):
1405 if line.startswith('<body>'):
1297 break
1406 break
1298
1407
1299 newfname = os.path.splitext(infile)[0] + '.html'
1408 newfname = os.path.splitext(infile)[0] + '.html'
1300 with open(newfname, 'w') as f:
1409 with open(newfname, 'w') as f:
1301 for line in walker:
1410 for line in walker:
1302 if line.startswith('</body>'):
1411 if line.startswith('</body>'):
1303 break
1412 break
1304 f.write(line)
1413 f.write(line)
1305 f.write('\n')
1414 f.write('\n')
1306
1415
1307 return newfname
1416 return newfname
1308
1417
1309
1418
1310 def md2html(infile):
1419 def md2html(infile):
1311 """Convert a markdown file to simplified html suitable for blogger.
1420 """Convert a markdown file to simplified html suitable for blogger.
1312
1421
1313 """
1422 """
1314
1423
1315 proc = subprocess.Popen(['markdown', infile],
1424 proc = subprocess.Popen(['markdown', infile],
1316 stdout=subprocess.PIPE,
1425 stdout=subprocess.PIPE,
1317 stderr=subprocess.PIPE)
1426 stderr=subprocess.PIPE)
1318 html, stderr = proc.communicate()
1427 html, stderr = proc.communicate()
1319 if stderr:
1428 if stderr:
1320 raise IOError(stderr)
1429 raise IOError(stderr)
1321
1430
1322 from pygments.formatters import HtmlFormatter
1431 from pygments.formatters import HtmlFormatter
1323 css = HtmlFormatter().get_style_defs('.highlight')
1432 css = HtmlFormatter().get_style_defs('.highlight')
1324
1433
1325 template = """
1434 template = """
1326 <!DOCTYPE HTML>
1435 <!DOCTYPE HTML>
1327 <html>
1436 <html>
1328
1437
1329 <head>
1438 <head>
1330 <title>{infile}</title>
1439 <title>{infile}</title>
1331 <style type="text/css">
1440 <style type="text/css">
1332 {css}
1441 {css}
1333 </style>
1442 </style>
1334
1443
1335 </head>
1444 </head>
1336
1445
1337 <body>
1446 <body>
1338 {html}
1447 {html}
1339 </body>
1448 </body>
1340
1449
1341 </html>
1450 </html>
1342 """
1451 """
1343 full_html = template.format(**locals())
1452 full_html = template.format(**locals())
1344 newfname = os.path.splitext(infile)[0] + '.html'
1453 newfname = os.path.splitext(infile)[0] + '.html'
1345 with open(newfname, 'w') as f:
1454 with open(newfname, 'w') as f:
1346 f.write(full_html)
1455 f.write(full_html)
1347
1456
1348 return newfname
1457 return newfname
1349
1458
1350 #-----------------------------------------------------------------------------
1459 #-----------------------------------------------------------------------------
1351 # Cell-level functions -- similar to IPython.nbformat.v3.rwbase functions
1460 # Cell-level functions -- similar to IPython.nbformat.v3.rwbase functions
1352 # but at cell level instead of whole notebook level
1461 # but at cell level instead of whole notebook level
1353 #-----------------------------------------------------------------------------
1462 #-----------------------------------------------------------------------------
1354
1463
1355 def writes_cell(cell, **kwargs):
1464 def writes_cell(cell, **kwargs):
1356 kwargs['cls'] = BytesEncoder
1465 kwargs['cls'] = BytesEncoder
1357 kwargs['indent'] = 3
1466 kwargs['indent'] = 3
1358 kwargs['sort_keys'] = True
1467 kwargs['sort_keys'] = True
1359 kwargs['separators'] = (',',': ')
1468 kwargs['separators'] = (',',': ')
1360 if kwargs.pop('split_lines', True):
1469 if kwargs.pop('split_lines', True):
1361 cell = split_lines_cell(copy.deepcopy(cell))
1470 cell = split_lines_cell(copy.deepcopy(cell))
1362 return py3compat.str_to_unicode(json.dumps(cell, **kwargs), 'utf-8')
1471 return py3compat.str_to_unicode(json.dumps(cell, **kwargs), 'utf-8')
1363
1472
1364
1473
1365 _multiline_outputs = ['text', 'html', 'svg', 'latex', 'javascript', 'json']
1474 _multiline_outputs = ['text', 'html', 'svg', 'latex', 'javascript', 'json']
1366
1475
1367
1476
1368 def split_lines_cell(cell):
1477 def split_lines_cell(cell):
1369 """
1478 """
1370 Split lines within a cell as in
1479 Split lines within a cell as in
1371 IPython.nbformat.v3.rwbase.split_lines
1480 IPython.nbformat.v3.rwbase.split_lines
1372
1481
1373 """
1482 """
1374 if cell.cell_type == 'code':
1483 if cell.cell_type == 'code':
1375 if 'input' in cell and isinstance(cell.input, basestring):
1484 if 'input' in cell and isinstance(cell.input, basestring):
1376 cell.input = (cell.input + '\n').splitlines()
1485 cell.input = (cell.input + '\n').splitlines()
1377 for output in cell.outputs:
1486 for output in cell.outputs:
1378 for key in _multiline_outputs:
1487 for key in _multiline_outputs:
1379 item = output.get(key, None)
1488 item = output.get(key, None)
1380 if isinstance(item, basestring):
1489 if isinstance(item, basestring):
1381 output[key] = (item + '\n').splitlines()
1490 output[key] = (item + '\n').splitlines()
1382 else: # text, heading cell
1491 else: # text, heading cell
1383 for key in ['source', 'rendered']:
1492 for key in ['source', 'rendered']:
1384 item = cell.get(key, None)
1493 item = cell.get(key, None)
1385 if isinstance(item, basestring):
1494 if isinstance(item, basestring):
1386 cell[key] = (item + '\n').splitlines()
1495 cell[key] = (item + '\n').splitlines()
1387 return cell
1496 return cell
1388
1497
1389
1498
1390 def cell_to_lines(cell):
1499 def cell_to_lines(cell):
1391 '''
1500 '''
1392 Write a cell to json, returning the split lines.
1501 Write a cell to json, returning the split lines.
1393 '''
1502 '''
1394 split_lines_cell(cell)
1503 split_lines_cell(cell)
1395 s = writes_cell(cell).strip()
1504 s = writes_cell(cell).strip()
1396 return s.split('\n')
1505 return s.split('\n')
1397
1506
1398
1507
1399 known_formats = "rst (default), html, quick-html, latex, markdown"
1508 known_formats = "rst (default), html, quick-html, latex, markdown, py"
1400
1509
1401 def main(infile, format='rst'):
1510 def main(infile, format='rst'):
1402 """Convert a notebook to html in one step"""
1511 """Convert a notebook to html in one step"""
1403 # XXX: this is just quick and dirty for now. When adding a new format,
1512 # XXX: this is just quick and dirty for now. When adding a new format,
1404 # make sure to add it to the `known_formats` string above, which gets
1513 # make sure to add it to the `known_formats` string above, which gets
1405 # printed in in the catch-all else, as well as in the help
1514 # printed in in the catch-all else, as well as in the help
1406 if format == 'rst':
1515 if format == 'rst':
1407 converter = ConverterRST(infile)
1516 converter = ConverterRST(infile)
1408 converter.render()
1517 converter.render()
1409 elif format == 'markdown':
1518 elif format == 'markdown':
1410 converter = ConverterMarkdown(infile)
1519 converter = ConverterMarkdown(infile)
1411 converter.render()
1520 converter.render()
1412 elif format == 'html':
1521 elif format == 'html':
1413 converter = ConverterHTML(infile)
1522 converter = ConverterHTML(infile)
1414 htmlfname = converter.render()
1523 htmlfname = converter.render()
1415 elif format == 'latex':
1524 elif format == 'latex':
1416 converter = ConverterLaTeX(infile)
1525 converter = ConverterLaTeX(infile)
1417 latexfname = converter.render()
1526 latexfname = converter.render()
1527 elif format == 'py':
1528 converter = ConverterPy(infile)
1529 converter.render()
1418 else:
1530 else:
1419 raise SystemExit("Unknown format '%s', " % format +
1531 raise SystemExit("Unknown format '%s', " % format +
1420 "known formats are: " + known_formats)
1532 "known formats are: " + known_formats)
1421
1533
1422 #-----------------------------------------------------------------------------
1534 #-----------------------------------------------------------------------------
1423 # Script main
1535 # Script main
1424 #-----------------------------------------------------------------------------
1536 #-----------------------------------------------------------------------------
1425
1537
1426 if __name__ == '__main__':
1538 if __name__ == '__main__':
1427 parser = argparse.ArgumentParser(description=__doc__,
1539 parser = argparse.ArgumentParser(description=__doc__,
1428 formatter_class=argparse.RawTextHelpFormatter)
1540 formatter_class=argparse.RawTextHelpFormatter)
1429 # TODO: consider passing file like object around, rather than filenames
1541 # TODO: consider passing file like object around, rather than filenames
1430 # would allow us to process stdin, or even http streams
1542 # would allow us to process stdin, or even http streams
1431 #parser.add_argument('infile', nargs='?', type=argparse.FileType('r'), default=sys.stdin)
1543 #parser.add_argument('infile', nargs='?', type=argparse.FileType('r'), default=sys.stdin)
1432
1544
1433 #Require a filename as a positional argument
1545 #Require a filename as a positional argument
1434 parser.add_argument('infile', nargs=1)
1546 parser.add_argument('infile', nargs=1)
1435 parser.add_argument('-f', '--format', default='rst',
1547 parser.add_argument('-f', '--format', default='rst',
1436 help='Output format. Supported formats: \n' +
1548 help='Output format. Supported formats: \n' +
1437 known_formats)
1549 known_formats)
1438 args = parser.parse_args()
1550 args = parser.parse_args()
1439 main(infile=args.infile[0], format=args.format)
1551 main(infile=args.infile[0], format=args.format)
General Comments 0
You need to be logged in to leave comments. Login now