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