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