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