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