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