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