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