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