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