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