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